diff --git a/app/src/main/java/im/vector/riotredesign/core/extensions/Context.kt b/app/src/main/java/im/vector/riotredesign/core/extensions/Context.kt new file mode 100644 index 0000000000..4b75026649 --- /dev/null +++ b/app/src/main/java/im/vector/riotredesign/core/extensions/Context.kt @@ -0,0 +1,13 @@ +package im.vector.riotredesign.core.extensions + +import android.content.Context +import android.graphics.drawable.Drawable +import android.support.v4.content.ContextCompat +import com.amulyakhare.textdrawable.TextDrawable +import im.vector.riotredesign.R + + +fun Context.avatarDrawable(name: String): Drawable { + val avatarColor = ContextCompat.getColor(this, R.color.pale_teal) + return TextDrawable.builder().buildRound(name.firstCharAsString().toUpperCase(), avatarColor) +} \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt index b2fd9987a6..c79aa62e04 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt @@ -12,10 +12,10 @@ import im.vector.matrix.android.api.session.events.model.EnrichedEvent import im.vector.matrix.android.api.session.room.Room import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.riotredesign.R +import im.vector.riotredesign.core.extensions.avatarDrawable import im.vector.riotredesign.core.platform.RiotFragment import im.vector.riotredesign.core.platform.ToolbarConfigurable import im.vector.riotredesign.core.utils.FragmentArgumentDelegate -import im.vector.riotredesign.features.home.RoomSummaryViewHelper import kotlinx.android.synthetic.main.fragment_room_detail.* import org.koin.android.ext.android.inject @@ -33,7 +33,7 @@ class RoomDetailFragment : RiotFragment() { private val matrix by inject() private val currentSession = matrix.currentSession!! private var roomId by FragmentArgumentDelegate() - private val timelineEventController = TimelineEventController() + private lateinit var timelineEventController: TimelineEventController private lateinit var room: Room override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { @@ -60,14 +60,14 @@ class RoomDetailFragment : RiotFragment() { private fun setupRecyclerView() { val layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, true) recyclerView.layoutManager = layoutManager + timelineEventController = TimelineEventController(riotActivity) recyclerView.setController(timelineEventController) } private fun renderRoomSummary(roomSummary: RoomSummary?) { roomSummary?.let { - val roomSummaryViewHelper = RoomSummaryViewHelper(it) toolbarTitleView.text = it.displayName - toolbarAvatarImageView.setImageDrawable(roomSummaryViewHelper.avatarDrawable(riotActivity)) + toolbarAvatarImageView.setImageDrawable(riotActivity.avatarDrawable(it.displayName)) if (it.topic.isNotEmpty()) { toolbarSubtitleView.visibility = View.VISIBLE toolbarSubtitleView.text = it.topic diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/TimelineEventController.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/TimelineEventController.kt index 1f98aefab1..1d4f024042 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/TimelineEventController.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/TimelineEventController.kt @@ -1,22 +1,23 @@ package im.vector.riotredesign.features.home.room.detail import android.arch.paging.PagedList +import android.content.Context import com.airbnb.epoxy.EpoxyAsyncUtil import com.airbnb.epoxy.EpoxyController import im.vector.matrix.android.api.session.events.model.EnrichedEvent -import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.EventType +import im.vector.matrix.android.api.session.events.model.roomMember import im.vector.matrix.android.api.session.room.model.MessageContent -import im.vector.matrix.android.api.session.room.model.RoomMember +import im.vector.riotredesign.core.extensions.avatarDrawable import im.vector.riotredesign.features.home.LoadingItemModel_ -private const val PREFETCH_DISTANCE = 5 - -class TimelineEventController : EpoxyController( +class TimelineEventController(private val context: Context) : EpoxyController( EpoxyAsyncUtil.getAsyncBackgroundHandler(), EpoxyAsyncUtil.getAsyncBackgroundHandler() ) { + private val messagesLoadedWithInformation = HashSet() + private val pagedListCallback = object : PagedList.Callback() { override fun onChanged(position: Int, count: Int) { requestModelBuild() @@ -38,7 +39,6 @@ class TimelineEventController : EpoxyController( field?.addWeakCallback(null, pagedListCallback) } - override fun buildModels() { buildModels(timeline) } @@ -47,22 +47,37 @@ class TimelineEventController : EpoxyController( if (data.isNullOrEmpty()) { return } - data.forEachIndexed { index, enrichedEvent -> - val item = if (enrichedEvent.root.type == EventType.MESSAGE) { - val messageContent = enrichedEvent.root.content() - val roomMember = enrichedEvent.getMetadata(EventType.STATE_ROOM_MEMBER)?.content() - val title = "${roomMember?.displayName} : ${messageContent?.body}" - TimelineEventItem(title = title) - } else { - TimelineEventItem(title = enrichedEvent.toString()) + for (index in 0 until data.size) { + val event = data[index] + val nextEvent = if (index + 1 < data.size) data[index + 1] else null + + if (event.root.type == EventType.MESSAGE) { + val messageContent = event.root.content() + val roomMember = event.roomMember() + if (messageContent == null || roomMember == null) { + continue + } + val nextRoomMember = nextEvent?.roomMember() + if (nextRoomMember != roomMember) { + messagesLoadedWithInformation.add(event.root.eventId) + } + val showInformation = messagesLoadedWithInformation.contains(event.root.eventId) + + val avatarDrawable = context.avatarDrawable(roomMember.displayName ?: "") + TimelineMessageItem( + message = messageContent.body, + showInformation = showInformation, + avatarDrawable = avatarDrawable, + memberName = roomMember.displayName + ) + .onBind { timeline?.loadAround(index) } + .id(event.root.eventId) + .addTo(this) } - item - .onBind { timeline?.loadAround(index) } - .id(enrichedEvent.root.eventId) - .addTo(this) } - val isLastEvent = data.last().getMetadata(EnrichedEvent.IS_LAST_EVENT) ?: false + //It's a hack at the moment + val isLastEvent = data.last().root.type == EventType.STATE_ROOM_CREATE LoadingItemModel_() .id("backward_loading_item") .addIf(!isLastEvent, this) diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/TimelineMessageItem.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/TimelineMessageItem.kt new file mode 100644 index 0000000000..bb51dbd0cd --- /dev/null +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/TimelineMessageItem.kt @@ -0,0 +1,39 @@ +package im.vector.riotredesign.features.home.room.detail + +import android.graphics.drawable.Drawable +import android.view.View +import android.widget.ImageView +import android.widget.TextView +import im.vector.riotredesign.R +import im.vector.riotredesign.core.epoxy.KotlinModel + +data class TimelineMessageItem( + val message: CharSequence? = null, + val time: CharSequence? = null, + val avatarDrawable: Drawable? = null, + val memberName: CharSequence? = null, + val showInformation: Boolean = true +) : KotlinModel(R.layout.item_event_message) { + + private val avatarImageView by bind(R.id.messageAvatarImageView) + private val memberNameView by bind(R.id.messageMemberNameView) + private val timeView by bind(R.id.messageTimeView) + private val messageView by bind(R.id.messageTextView) + + override fun bind() { + messageView.text = message + if (showInformation) { + avatarImageView.visibility = View.VISIBLE + memberNameView.visibility = View.VISIBLE + timeView.visibility = View.VISIBLE + + avatarImageView.setImageDrawable(avatarDrawable) + timeView.text = time + memberNameView.text = memberName + } else { + avatarImageView.visibility = View.GONE + memberNameView.visibility = View.GONE + timeView.visibility = View.GONE + } + } +} \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryController.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryController.kt index 7ff52eb2e3..74ee23fe97 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryController.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryController.kt @@ -3,8 +3,7 @@ package im.vector.riotredesign.features.home.room.list import android.content.Context import com.airbnb.epoxy.Typed2EpoxyController import im.vector.matrix.android.api.session.room.model.RoomSummary -import im.vector.riotredesign.R -import im.vector.riotredesign.features.home.RoomSummaryViewHelper +import im.vector.riotredesign.core.extensions.avatarDrawable class RoomSummaryController(private val context: Context, private val callback: Callback? = null @@ -53,10 +52,9 @@ class RoomSummaryController(private val context: Context, private fun buildRoomModels(summaries: List, selected: RoomSummary?) { summaries.forEach { - val roomSummaryViewHelper = RoomSummaryViewHelper(it) RoomSummaryItem( title = it.displayName, - avatarDrawable = roomSummaryViewHelper.avatarDrawable(context), + avatarDrawable = context.avatarDrawable(it.displayName), isSelected = it.roomId == selected?.roomId, listener = { callback?.onRoomSelected(it) } ) diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryItem.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryItem.kt index c9f943c061..bd3d8aed28 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryItem.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryItem.kt @@ -16,7 +16,7 @@ data class RoomSummaryItem( ) : KotlinModel(R.layout.item_room) { private val titleView by bind(R.id.titleView) - private val avatarImageView by bind(R.id.avatarImageView) + private val avatarImageView by bind(R.id.messageAvatarImageView) private val rootView by bind(R.id.itemRoomLayout) override fun bind() { diff --git a/app/src/main/res/layout/item_event_day_separator.xml b/app/src/main/res/layout/item_event_day_separator.xml new file mode 100644 index 0000000000..a481e9be16 --- /dev/null +++ b/app/src/main/res/layout/item_event_day_separator.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_event_message.xml b/app/src/main/res/layout/item_event_message.xml new file mode 100644 index 0000000000..9ed6fe9be3 --- /dev/null +++ b/app/src/main/res/layout/item_event_message.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_room.xml b/app/src/main/res/layout/item_room.xml index 3f81df9e51..d53c2101a4 100644 --- a/app/src/main/res/layout/item_room.xml +++ b/app/src/main/res/layout/item_room.xml @@ -21,7 +21,7 @@ android:minHeight="48dp"> diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 5dd52be53e..982636bc9b 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -15,4 +15,5 @@ #9fa9ba #a5aab2 #ebedf8 + #a5a5a5 diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/EnrichedEvent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/EnrichedEvent.kt index b1c13120b4..77042029fc 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/EnrichedEvent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/EnrichedEvent.kt @@ -1,5 +1,7 @@ package im.vector.matrix.android.api.session.events.model +import im.vector.matrix.android.api.session.room.model.RoomMember + data class EnrichedEvent(val root: Event) { val metadata = HashMap() @@ -30,4 +32,8 @@ data class EnrichedEvent(val root: Event) { const val READ_RECEIPTS = "READ_RECEIPTS" } -} \ No newline at end of file +} + +fun EnrichedEvent.roomMember(): RoomMember? { + return getMetadata(EventType.STATE_ROOM_MEMBER)?.content() +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/events/interceptor/IsLastEventInterceptor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/events/interceptor/IsLastEventInterceptor.kt deleted file mode 100644 index 2415488360..0000000000 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/events/interceptor/IsLastEventInterceptor.kt +++ /dev/null @@ -1,37 +0,0 @@ -package im.vector.matrix.android.internal.session.events.interceptor - -import com.zhuinden.monarchy.Monarchy -import im.vector.matrix.android.api.session.events.interceptor.EnrichedEventInterceptor -import im.vector.matrix.android.api.session.events.model.EnrichedEvent -import im.vector.matrix.android.internal.database.model.ChunkEntity -import im.vector.matrix.android.internal.database.model.EventEntity -import im.vector.matrix.android.internal.database.model.EventEntityFields -import im.vector.matrix.android.internal.database.query.findAllIncludingEvents -import im.vector.matrix.android.internal.database.query.where -import io.realm.Sort -import java.util.* - - -class IsLastEventInterceptor(val monarchy: Monarchy) : EnrichedEventInterceptor { - - override fun canEnrich(event: EnrichedEvent): Boolean { - return true - } - - override fun enrich(roomId: String, event: EnrichedEvent) { - monarchy.doWithRealm { realm -> - if (event.root.eventId == null) { - return@doWithRealm - } - val eventEntity = EventEntity.where(realm, event.root.eventId).findFirst() - val chunkEntity = ChunkEntity.findAllIncludingEvents(realm, Collections.singletonList(event.root.eventId)).firstOrNull() - if (eventEntity == null || chunkEntity == null) { - return@doWithRealm - } - val sortedChunkEvents = chunkEntity.events.where().sort(EventEntityFields.ORIGIN_SERVER_TS, Sort.ASCENDING).findAll() - val isLastEvent = chunkEntity.prevToken == null && sortedChunkEvents?.indexOf(eventEntity) == 0 - event.enrichWith(EnrichedEvent.IS_LAST_EVENT, isLastEvent) - } - } - -} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineHolder.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineHolder.kt index dacd70a2fd..4028ecddef 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineHolder.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineHolder.kt @@ -11,7 +11,6 @@ import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.model.ChunkEntity import im.vector.matrix.android.internal.database.model.EventEntityFields import im.vector.matrix.android.internal.database.query.where -import im.vector.matrix.android.internal.session.events.interceptor.IsLastEventInterceptor import im.vector.matrix.android.internal.session.events.interceptor.MessageEventInterceptor import io.realm.Sort @@ -24,7 +23,6 @@ class DefaultTimelineHolder(private val roomId: String, init { eventInterceptors.add(MessageEventInterceptor(monarchy)) - eventInterceptors.add(IsLastEventInterceptor(monarchy)) } override fun liveTimeline(): LiveData> {