forked from GitHub-Mirror/riotX-android
Read receipts: add read receipts bottom sheet
This commit is contained in:
parent
1dbb02a80d
commit
70639f180c
@ -18,6 +18,7 @@ package im.vector.matrix.rx
|
|||||||
|
|
||||||
import im.vector.matrix.android.api.session.room.Room
|
import im.vector.matrix.android.api.session.room.Room
|
||||||
import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
|
import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
|
||||||
|
import im.vector.matrix.android.api.session.room.model.ReadReceipt
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
@ -49,6 +50,10 @@ class RxRoom(private val room: Room) {
|
|||||||
room.join(viaServers, MatrixCallbackSingle(it)).toSingle(it)
|
room.join(viaServers, MatrixCallbackSingle(it)).toSingle(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun liveEventReadReceipts(eventId: String): Observable<List<ReadReceipt>> {
|
||||||
|
return room.getEventReadReceiptsLive(eventId).asObservable()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Room.rx(): RxRoom {
|
fun Room.rx(): RxRoom {
|
||||||
|
@ -16,7 +16,9 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.api.session.room.read
|
package im.vector.matrix.android.api.session.room.read
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
|
import im.vector.matrix.android.api.session.room.model.ReadReceipt
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This interface defines methods to handle read receipts and read marker in a room. It's implemented at the room level.
|
* This interface defines methods to handle read receipts and read marker in a room. It's implemented at the room level.
|
||||||
@ -39,4 +41,6 @@ interface ReadService {
|
|||||||
fun setReadMarker(fullyReadEventId: String, callback: MatrixCallback<Unit>)
|
fun setReadMarker(fullyReadEventId: String, callback: MatrixCallback<Unit>)
|
||||||
|
|
||||||
fun isEventRead(eventId: String): Boolean
|
fun isEventRead(eventId: String): Boolean
|
||||||
|
|
||||||
|
fun getEventReadReceiptsLive(eventId: String): LiveData<List<ReadReceipt>>
|
||||||
}
|
}
|
@ -21,6 +21,7 @@ import com.zhuinden.monarchy.Monarchy
|
|||||||
import im.vector.matrix.android.api.auth.data.Credentials
|
import im.vector.matrix.android.api.auth.data.Credentials
|
||||||
import im.vector.matrix.android.api.session.crypto.CryptoService
|
import im.vector.matrix.android.api.session.crypto.CryptoService
|
||||||
import im.vector.matrix.android.api.session.room.Room
|
import im.vector.matrix.android.api.session.room.Room
|
||||||
|
import im.vector.matrix.android.internal.database.mapper.ReadReceiptsSummaryMapper
|
||||||
import im.vector.matrix.android.internal.database.mapper.RoomSummaryMapper
|
import im.vector.matrix.android.internal.database.mapper.RoomSummaryMapper
|
||||||
import im.vector.matrix.android.internal.database.mapper.TimelineEventMapper
|
import im.vector.matrix.android.internal.database.mapper.TimelineEventMapper
|
||||||
import im.vector.matrix.android.internal.session.room.membership.DefaultMembershipService
|
import im.vector.matrix.android.internal.session.room.membership.DefaultMembershipService
|
||||||
@ -49,6 +50,7 @@ internal class RoomFactory @Inject constructor(private val context: Context,
|
|||||||
private val eventFactory: LocalEchoEventFactory,
|
private val eventFactory: LocalEchoEventFactory,
|
||||||
private val roomSummaryMapper: RoomSummaryMapper,
|
private val roomSummaryMapper: RoomSummaryMapper,
|
||||||
private val timelineEventMapper: TimelineEventMapper,
|
private val timelineEventMapper: TimelineEventMapper,
|
||||||
|
private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper,
|
||||||
private val taskExecutor: TaskExecutor,
|
private val taskExecutor: TaskExecutor,
|
||||||
private val loadRoomMembersTask: LoadRoomMembersTask,
|
private val loadRoomMembersTask: LoadRoomMembersTask,
|
||||||
private val inviteTask: InviteTask,
|
private val inviteTask: InviteTask,
|
||||||
@ -67,7 +69,7 @@ internal class RoomFactory @Inject constructor(private val context: Context,
|
|||||||
val sendService = DefaultSendService(context, credentials, roomId, eventFactory, cryptoService, monarchy)
|
val sendService = DefaultSendService(context, credentials, roomId, eventFactory, cryptoService, monarchy)
|
||||||
val stateService = DefaultStateService(roomId, monarchy.realmConfiguration, taskExecutor, sendStateTask)
|
val stateService = DefaultStateService(roomId, monarchy.realmConfiguration, taskExecutor, sendStateTask)
|
||||||
val roomMembersService = DefaultMembershipService(roomId, monarchy, taskExecutor, loadRoomMembersTask, inviteTask, joinRoomTask, leaveRoomTask)
|
val roomMembersService = DefaultMembershipService(roomId, monarchy, taskExecutor, loadRoomMembersTask, inviteTask, joinRoomTask, leaveRoomTask)
|
||||||
val readService = DefaultReadService(roomId, monarchy, taskExecutor, setReadMarkersTask, credentials)
|
val readService = DefaultReadService(roomId, monarchy, taskExecutor, setReadMarkersTask, readReceiptsSummaryMapper, credentials)
|
||||||
val relationService = DefaultRelationService(context,
|
val relationService = DefaultRelationService(context,
|
||||||
credentials, roomId, eventFactory, cryptoService, findReactionEventForUndoTask, fetchEditHistoryTask, monarchy, taskExecutor)
|
credentials, roomId, eventFactory, cryptoService, findReactionEventForUndoTask, fetchEditHistoryTask, monarchy, taskExecutor)
|
||||||
|
|
||||||
|
@ -16,12 +16,21 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.internal.session.room.read
|
package im.vector.matrix.android.internal.session.room.read
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.Transformations
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
import im.vector.matrix.android.api.auth.data.Credentials
|
import im.vector.matrix.android.api.auth.data.Credentials
|
||||||
|
import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
|
||||||
|
import im.vector.matrix.android.api.session.room.model.ReadReceipt
|
||||||
import im.vector.matrix.android.api.session.room.read.ReadService
|
import im.vector.matrix.android.api.session.room.read.ReadService
|
||||||
|
import im.vector.matrix.android.internal.database.RealmLiveData
|
||||||
|
import im.vector.matrix.android.internal.database.mapper.ReadReceiptsSummaryMapper
|
||||||
|
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.ChunkEntity
|
||||||
|
import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity
|
||||||
import im.vector.matrix.android.internal.database.model.ReadReceiptEntity
|
import im.vector.matrix.android.internal.database.model.ReadReceiptEntity
|
||||||
|
import im.vector.matrix.android.internal.database.model.ReadReceiptsSummaryEntity
|
||||||
import im.vector.matrix.android.internal.database.query.find
|
import im.vector.matrix.android.internal.database.query.find
|
||||||
import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom
|
import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom
|
||||||
import im.vector.matrix.android.internal.database.query.where
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
@ -33,6 +42,7 @@ internal class DefaultReadService @Inject constructor(private val roomId: String
|
|||||||
private val monarchy: Monarchy,
|
private val monarchy: Monarchy,
|
||||||
private val taskExecutor: TaskExecutor,
|
private val taskExecutor: TaskExecutor,
|
||||||
private val setReadMarkersTask: SetReadMarkersTask,
|
private val setReadMarkersTask: SetReadMarkersTask,
|
||||||
|
private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper,
|
||||||
private val credentials: Credentials) : ReadService {
|
private val credentials: Credentials) : ReadService {
|
||||||
|
|
||||||
override fun markAllAsRead(callback: MatrixCallback<Unit>) {
|
override fun markAllAsRead(callback: MatrixCallback<Unit>) {
|
||||||
@ -67,16 +77,26 @@ internal class DefaultReadService @Inject constructor(private val roomId: String
|
|||||||
var isEventRead = false
|
var isEventRead = false
|
||||||
monarchy.doWithRealm {
|
monarchy.doWithRealm {
|
||||||
val readReceipt = ReadReceiptEntity.where(it, roomId, credentials.userId).findFirst()
|
val readReceipt = ReadReceiptEntity.where(it, roomId, credentials.userId).findFirst()
|
||||||
?: return@doWithRealm
|
?: return@doWithRealm
|
||||||
val liveChunk = ChunkEntity.findLastLiveChunkFromRoom(it, roomId)
|
val liveChunk = ChunkEntity.findLastLiveChunkFromRoom(it, roomId)
|
||||||
?: return@doWithRealm
|
?: return@doWithRealm
|
||||||
val readReceiptIndex = liveChunk.timelineEvents.find(readReceipt.eventId)?.root?.displayIndex
|
val readReceiptIndex = liveChunk.timelineEvents.find(readReceipt.eventId)?.root?.displayIndex
|
||||||
?: Int.MIN_VALUE
|
?: Int.MIN_VALUE
|
||||||
val eventToCheckIndex = liveChunk.timelineEvents.find(eventId)?.root?.displayIndex
|
val eventToCheckIndex = liveChunk.timelineEvents.find(eventId)?.root?.displayIndex
|
||||||
?: Int.MAX_VALUE
|
?: Int.MAX_VALUE
|
||||||
isEventRead = eventToCheckIndex <= readReceiptIndex
|
isEventRead = eventToCheckIndex <= readReceiptIndex
|
||||||
}
|
}
|
||||||
return isEventRead
|
return isEventRead
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getEventReadReceiptsLive(eventId: String): LiveData<List<ReadReceipt>> {
|
||||||
|
val liveEntity = RealmLiveData(monarchy.realmConfiguration) { realm ->
|
||||||
|
ReadReceiptsSummaryEntity.where(realm, eventId)
|
||||||
|
}
|
||||||
|
return Transformations.map(liveEntity) { realmResults ->
|
||||||
|
realmResults.firstOrNull()?.let {
|
||||||
|
readReceiptsSummaryMapper.map(it)
|
||||||
|
} ?: emptyList()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -14,15 +14,18 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package im.vector.riotx.features.home.room.detail.timeline.helper
|
package im.vector.riotx.core.date
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.text.format.DateUtils
|
||||||
import im.vector.riotx.core.resources.LocaleProvider
|
import im.vector.riotx.core.resources.LocaleProvider
|
||||||
import org.threeten.bp.LocalDateTime
|
import org.threeten.bp.LocalDateTime
|
||||||
import org.threeten.bp.format.DateTimeFormatter
|
import org.threeten.bp.format.DateTimeFormatter
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
|
||||||
class TimelineDateFormatter @Inject constructor (private val localeProvider: LocaleProvider) {
|
class VectorDateFormatter @Inject constructor(private val context: Context,
|
||||||
|
private val localeProvider: LocaleProvider) {
|
||||||
|
|
||||||
private val messageHourFormatter by lazy {
|
private val messageHourFormatter by lazy {
|
||||||
DateTimeFormatter.ofPattern("H:mm", localeProvider.current())
|
DateTimeFormatter.ofPattern("H:mm", localeProvider.current())
|
||||||
@ -39,4 +42,11 @@ class TimelineDateFormatter @Inject constructor (private val localeProvider: Loc
|
|||||||
return messageDayFormatter.format(localDateTime)
|
return messageDayFormatter.format(localDateTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun formatRelativeDateTime(time: Long?): String {
|
||||||
|
if (time == null) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return DateUtils.getRelativeDateTimeString(context, time, DateUtils.DAY_IN_MILLIS, 2 * DateUtils.DAY_IN_MILLIS, DateUtils.FORMAT_SHOW_WEEKDAY).toString()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -41,7 +41,12 @@ import im.vector.riotx.features.home.createdirect.CreateDirectRoomDirectoryUsers
|
|||||||
import im.vector.riotx.features.home.createdirect.CreateDirectRoomKnownUsersFragment
|
import im.vector.riotx.features.home.createdirect.CreateDirectRoomKnownUsersFragment
|
||||||
import im.vector.riotx.features.home.group.GroupListFragment
|
import im.vector.riotx.features.home.group.GroupListFragment
|
||||||
import im.vector.riotx.features.home.room.detail.RoomDetailFragment
|
import im.vector.riotx.features.home.room.detail.RoomDetailFragment
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.action.*
|
import im.vector.riotx.features.home.room.detail.readreceipts.DisplayReadReceiptsBottomSheet
|
||||||
|
import im.vector.riotx.features.home.room.detail.timeline.action.MessageActionsBottomSheet
|
||||||
|
import im.vector.riotx.features.home.room.detail.timeline.action.MessageMenuFragment
|
||||||
|
import im.vector.riotx.features.home.room.detail.timeline.action.QuickReactionFragment
|
||||||
|
import im.vector.riotx.features.home.room.detail.timeline.action.ViewEditHistoryBottomSheet
|
||||||
|
import im.vector.riotx.features.home.room.detail.timeline.action.ViewReactionBottomSheet
|
||||||
import im.vector.riotx.features.home.room.filtered.FilteredRoomsActivity
|
import im.vector.riotx.features.home.room.filtered.FilteredRoomsActivity
|
||||||
import im.vector.riotx.features.home.room.list.RoomListFragment
|
import im.vector.riotx.features.home.room.list.RoomListFragment
|
||||||
import im.vector.riotx.features.invite.VectorInviteView
|
import im.vector.riotx.features.invite.VectorInviteView
|
||||||
@ -165,6 +170,8 @@ interface ScreenComponent {
|
|||||||
|
|
||||||
fun inject(createDirectRoomActivity: CreateDirectRoomActivity)
|
fun inject(createDirectRoomActivity: CreateDirectRoomActivity)
|
||||||
|
|
||||||
|
fun inject(displayReadReceiptsBottomSheet: DisplayReadReceiptsBottomSheet)
|
||||||
|
|
||||||
@Component.Factory
|
@Component.Factory
|
||||||
interface Factory {
|
interface Factory {
|
||||||
fun create(vectorComponent: VectorComponent,
|
fun create(vectorComponent: VectorComponent,
|
||||||
|
@ -29,7 +29,11 @@ import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupSettingsVie
|
|||||||
import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupSettingsViewModel_AssistedFactory
|
import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupSettingsViewModel_AssistedFactory
|
||||||
import im.vector.riotx.features.crypto.keysbackup.setup.KeysBackupSetupSharedViewModel
|
import im.vector.riotx.features.crypto.keysbackup.setup.KeysBackupSetupSharedViewModel
|
||||||
import im.vector.riotx.features.crypto.verification.SasVerificationViewModel
|
import im.vector.riotx.features.crypto.verification.SasVerificationViewModel
|
||||||
import im.vector.riotx.features.home.*
|
import im.vector.riotx.features.home.HomeActivityViewModel
|
||||||
|
import im.vector.riotx.features.home.HomeActivityViewModel_AssistedFactory
|
||||||
|
import im.vector.riotx.features.home.HomeDetailViewModel
|
||||||
|
import im.vector.riotx.features.home.HomeDetailViewModel_AssistedFactory
|
||||||
|
import im.vector.riotx.features.home.HomeNavigationViewModel
|
||||||
import im.vector.riotx.features.home.createdirect.CreateDirectRoomNavigationViewModel
|
import im.vector.riotx.features.home.createdirect.CreateDirectRoomNavigationViewModel
|
||||||
import im.vector.riotx.features.home.createdirect.CreateDirectRoomViewModel
|
import im.vector.riotx.features.home.createdirect.CreateDirectRoomViewModel
|
||||||
import im.vector.riotx.features.home.createdirect.CreateDirectRoomViewModel_AssistedFactory
|
import im.vector.riotx.features.home.createdirect.CreateDirectRoomViewModel_AssistedFactory
|
||||||
@ -39,7 +43,18 @@ import im.vector.riotx.features.home.room.detail.RoomDetailViewModel
|
|||||||
import im.vector.riotx.features.home.room.detail.RoomDetailViewModel_AssistedFactory
|
import im.vector.riotx.features.home.room.detail.RoomDetailViewModel_AssistedFactory
|
||||||
import im.vector.riotx.features.home.room.detail.composer.TextComposerViewModel
|
import im.vector.riotx.features.home.room.detail.composer.TextComposerViewModel
|
||||||
import im.vector.riotx.features.home.room.detail.composer.TextComposerViewModel_AssistedFactory
|
import im.vector.riotx.features.home.room.detail.composer.TextComposerViewModel_AssistedFactory
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.action.*
|
import im.vector.riotx.features.home.room.detail.readreceipts.DisplayReadReceiptsViewModel
|
||||||
|
import im.vector.riotx.features.home.room.detail.readreceipts.DisplayReadReceiptsViewModel_AssistedFactory
|
||||||
|
import im.vector.riotx.features.home.room.detail.timeline.action.MessageActionsViewModel
|
||||||
|
import im.vector.riotx.features.home.room.detail.timeline.action.MessageActionsViewModel_AssistedFactory
|
||||||
|
import im.vector.riotx.features.home.room.detail.timeline.action.MessageMenuViewModel
|
||||||
|
import im.vector.riotx.features.home.room.detail.timeline.action.MessageMenuViewModel_AssistedFactory
|
||||||
|
import im.vector.riotx.features.home.room.detail.timeline.action.QuickReactionViewModel
|
||||||
|
import im.vector.riotx.features.home.room.detail.timeline.action.QuickReactionViewModel_AssistedFactory
|
||||||
|
import im.vector.riotx.features.home.room.detail.timeline.action.ViewEditHistoryViewModel
|
||||||
|
import im.vector.riotx.features.home.room.detail.timeline.action.ViewEditHistoryViewModel_AssistedFactory
|
||||||
|
import im.vector.riotx.features.home.room.detail.timeline.action.ViewReactionViewModel
|
||||||
|
import im.vector.riotx.features.home.room.detail.timeline.action.ViewReactionViewModel_AssistedFactory
|
||||||
import im.vector.riotx.features.home.room.list.RoomListViewModel
|
import im.vector.riotx.features.home.room.list.RoomListViewModel
|
||||||
import im.vector.riotx.features.home.room.list.RoomListViewModel_AssistedFactory
|
import im.vector.riotx.features.home.room.list.RoomListViewModel_AssistedFactory
|
||||||
import im.vector.riotx.features.reactions.EmojiChooserViewModel
|
import im.vector.riotx.features.reactions.EmojiChooserViewModel
|
||||||
@ -182,4 +197,8 @@ interface ViewModelModule {
|
|||||||
@Binds
|
@Binds
|
||||||
fun bindPushGatewaysViewModelFactory(factory: PushGatewaysViewModel_AssistedFactory): PushGatewaysViewModel.Factory
|
fun bindPushGatewaysViewModelFactory(factory: PushGatewaysViewModel_AssistedFactory): PushGatewaysViewModel.Factory
|
||||||
|
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
fun bindDisplayReadReceiptsViewModel(factory: DisplayReadReceiptsViewModel_AssistedFactory): DisplayReadReceiptsViewModel.Factory
|
||||||
|
|
||||||
}
|
}
|
@ -23,6 +23,7 @@ import android.widget.LinearLayout
|
|||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import butterknife.ButterKnife
|
import butterknife.ButterKnife
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
|
import im.vector.riotx.core.utils.DebouncedClickListener
|
||||||
import im.vector.riotx.features.home.AvatarRenderer
|
import im.vector.riotx.features.home.AvatarRenderer
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.item.ReadReceiptData
|
import im.vector.riotx.features.home.room.detail.timeline.item.ReadReceiptData
|
||||||
import kotlinx.android.synthetic.main.view_read_receipts.view.*
|
import kotlinx.android.synthetic.main.view_read_receipts.view.*
|
||||||
@ -48,7 +49,8 @@ class ReadReceiptsView @JvmOverloads constructor(
|
|||||||
ButterKnife.bind(this)
|
ButterKnife.bind(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun render(readReceipts: List<ReadReceiptData>, avatarRenderer: AvatarRenderer) {
|
fun render(readReceipts: List<ReadReceiptData>, avatarRenderer: AvatarRenderer, clickListener: OnClickListener) {
|
||||||
|
setOnClickListener(clickListener)
|
||||||
if (readReceipts.isNotEmpty()) {
|
if (readReceipts.isNotEmpty()) {
|
||||||
isVisible = true
|
isVisible = true
|
||||||
for (index in 0 until MAX_RECEIPT_DISPLAYED) {
|
for (index in 0 until MAX_RECEIPT_DISPLAYED) {
|
||||||
|
@ -91,6 +91,7 @@ import im.vector.riotx.features.home.room.detail.composer.TextComposerActions
|
|||||||
import im.vector.riotx.features.home.room.detail.composer.TextComposerView
|
import im.vector.riotx.features.home.room.detail.composer.TextComposerView
|
||||||
import im.vector.riotx.features.home.room.detail.composer.TextComposerViewModel
|
import im.vector.riotx.features.home.room.detail.composer.TextComposerViewModel
|
||||||
import im.vector.riotx.features.home.room.detail.composer.TextComposerViewState
|
import im.vector.riotx.features.home.room.detail.composer.TextComposerViewState
|
||||||
|
import im.vector.riotx.features.home.room.detail.readreceipts.DisplayReadReceiptsBottomSheet
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
|
import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.action.*
|
import im.vector.riotx.features.home.room.detail.timeline.action.*
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.helper.EndlessRecyclerViewScrollListener
|
import im.vector.riotx.features.home.room.detail.timeline.helper.EndlessRecyclerViewScrollListener
|
||||||
@ -315,17 +316,17 @@ class RoomDetailFragment :
|
|||||||
if (messageContent is MessageTextContent && messageContent.format == MessageType.FORMAT_MATRIX_HTML) {
|
if (messageContent is MessageTextContent && messageContent.format == MessageType.FORMAT_MATRIX_HTML) {
|
||||||
val parser = Parser.builder().build()
|
val parser = Parser.builder().build()
|
||||||
val document = parser.parse(messageContent.formattedBody
|
val document = parser.parse(messageContent.formattedBody
|
||||||
?: messageContent.body)
|
?: messageContent.body)
|
||||||
formattedBody = eventHtmlRenderer.render(document)
|
formattedBody = eventHtmlRenderer.render(document)
|
||||||
}
|
}
|
||||||
composerLayout.composerRelatedMessageContent.text = formattedBody
|
composerLayout.composerRelatedMessageContent.text = formattedBody
|
||||||
?: nonFormattedBody
|
?: nonFormattedBody
|
||||||
|
|
||||||
composerLayout.composerEditText.setText(if (useText) event.getTextEditableContent() else "")
|
composerLayout.composerEditText.setText(if (useText) event.getTextEditableContent() else "")
|
||||||
composerLayout.composerRelatedMessageActionIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), iconRes))
|
composerLayout.composerRelatedMessageActionIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), iconRes))
|
||||||
|
|
||||||
avatarRenderer.render(event.senderAvatar, event.root.senderId
|
avatarRenderer.render(event.senderAvatar, event.root.senderId
|
||||||
?: "", event.senderName, composerLayout.composerRelatedMessageAvatar)
|
?: "", event.senderName, composerLayout.composerRelatedMessageAvatar)
|
||||||
|
|
||||||
composerLayout.composerEditText.setSelection(composerLayout.composerEditText.text.length)
|
composerLayout.composerEditText.setSelection(composerLayout.composerEditText.text.length)
|
||||||
composerLayout.expand {
|
composerLayout.expand {
|
||||||
@ -354,9 +355,9 @@ class RoomDetailFragment :
|
|||||||
REQUEST_FILES_REQUEST_CODE, TAKE_IMAGE_REQUEST_CODE -> handleMediaIntent(data)
|
REQUEST_FILES_REQUEST_CODE, TAKE_IMAGE_REQUEST_CODE -> handleMediaIntent(data)
|
||||||
REACTION_SELECT_REQUEST_CODE -> {
|
REACTION_SELECT_REQUEST_CODE -> {
|
||||||
val eventId = data.getStringExtra(EmojiReactionPickerActivity.EXTRA_EVENT_ID)
|
val eventId = data.getStringExtra(EmojiReactionPickerActivity.EXTRA_EVENT_ID)
|
||||||
?: return
|
?: return
|
||||||
val reaction = data.getStringExtra(EmojiReactionPickerActivity.EXTRA_REACTION_RESULT)
|
val reaction = data.getStringExtra(EmojiReactionPickerActivity.EXTRA_REACTION_RESULT)
|
||||||
?: return
|
?: return
|
||||||
//TODO check if already reacted with that?
|
//TODO check if already reacted with that?
|
||||||
roomDetailViewModel.process(RoomDetailActions.SendReaction(reaction, eventId))
|
roomDetailViewModel.process(RoomDetailActions.SendReaction(reaction, eventId))
|
||||||
}
|
}
|
||||||
@ -391,26 +392,26 @@ class RoomDetailFragment :
|
|||||||
|
|
||||||
if (VectorPreferences.swipeToReplyIsEnabled(requireContext())) {
|
if (VectorPreferences.swipeToReplyIsEnabled(requireContext())) {
|
||||||
val swipeCallback = RoomMessageTouchHelperCallback(requireContext(),
|
val swipeCallback = RoomMessageTouchHelperCallback(requireContext(),
|
||||||
R.drawable.ic_reply,
|
R.drawable.ic_reply,
|
||||||
object : RoomMessageTouchHelperCallback.QuickReplayHandler {
|
object : RoomMessageTouchHelperCallback.QuickReplayHandler {
|
||||||
override fun performQuickReplyOnHolder(model: EpoxyModel<*>) {
|
override fun performQuickReplyOnHolder(model: EpoxyModel<*>) {
|
||||||
(model as? AbsMessageItem)?.informationData?.let {
|
(model as? AbsMessageItem)?.informationData?.let {
|
||||||
val eventId = it.eventId
|
val eventId = it.eventId
|
||||||
roomDetailViewModel.process(RoomDetailActions.EnterReplyMode(eventId))
|
roomDetailViewModel.process(RoomDetailActions.EnterReplyMode(eventId))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun canSwipeModel(model: EpoxyModel<*>): Boolean {
|
override fun canSwipeModel(model: EpoxyModel<*>): Boolean {
|
||||||
return when (model) {
|
return when (model) {
|
||||||
is MessageFileItem,
|
is MessageFileItem,
|
||||||
is MessageImageVideoItem,
|
is MessageImageVideoItem,
|
||||||
is MessageTextItem -> {
|
is MessageTextItem -> {
|
||||||
return (model as AbsMessageItem).informationData.sendState == SendState.SYNCED
|
return (model as AbsMessageItem).informationData.sendState == SendState.SYNCED
|
||||||
}
|
}
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
val touchHelper = ItemTouchHelper(swipeCallback)
|
val touchHelper = ItemTouchHelper(swipeCallback)
|
||||||
touchHelper.attachToRecyclerView(recyclerView)
|
touchHelper.attachToRecyclerView(recyclerView)
|
||||||
}
|
}
|
||||||
@ -816,6 +817,11 @@ class RoomDetailFragment :
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onReadReceiptsClicked(informationData: MessageInformationData) {
|
||||||
|
DisplayReadReceiptsBottomSheet.newInstance(roomDetailArgs.roomId, informationData)
|
||||||
|
.show(requireActivity().supportFragmentManager, "DISPLAY_READ_RECEIPTS")
|
||||||
|
}
|
||||||
|
|
||||||
// AutocompleteUserPresenter.Callback
|
// AutocompleteUserPresenter.Callback
|
||||||
|
|
||||||
override fun onQueryUsers(query: CharSequence?) {
|
override fun onQueryUsers(query: CharSequence?) {
|
||||||
|
@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.riotx.features.home.room.detail.readreceipts
|
||||||
|
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import com.airbnb.epoxy.EpoxyAttribute
|
||||||
|
import com.airbnb.epoxy.EpoxyModelClass
|
||||||
|
import com.airbnb.epoxy.EpoxyModelWithHolder
|
||||||
|
import im.vector.riotx.R
|
||||||
|
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
|
||||||
|
import im.vector.riotx.features.home.AvatarRenderer
|
||||||
|
|
||||||
|
@EpoxyModelClass(layout = R.layout.item_display_read_receipt)
|
||||||
|
abstract class DisplayReadReceiptItem : EpoxyModelWithHolder<DisplayReadReceiptItem.Holder>() {
|
||||||
|
|
||||||
|
@EpoxyAttribute var name: String? = null
|
||||||
|
@EpoxyAttribute var userId: String = ""
|
||||||
|
@EpoxyAttribute var avatarUrl: String? = null
|
||||||
|
@EpoxyAttribute var timestamp: CharSequence? = null
|
||||||
|
@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
|
||||||
|
|
||||||
|
override fun bind(holder: Holder) {
|
||||||
|
avatarRenderer.render(avatarUrl, userId, name, holder.avatarView)
|
||||||
|
holder.displayNameView.text = name ?: userId
|
||||||
|
timestamp?.let {
|
||||||
|
holder.timestampView.text = it
|
||||||
|
holder.timestampView.isVisible = true
|
||||||
|
} ?: run {
|
||||||
|
holder.timestampView.isVisible = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Holder : VectorEpoxyHolder() {
|
||||||
|
val avatarView by bind<ImageView>(R.id.readReceiptAvatar)
|
||||||
|
val displayNameView by bind<TextView>(R.id.readReceiptName)
|
||||||
|
val timestampView by bind<TextView>(R.id.readReceiptDate)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,93 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.riotx.features.home.room.detail.readreceipts
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import androidx.recyclerview.widget.DividerItemDecoration
|
||||||
|
import butterknife.BindView
|
||||||
|
import butterknife.ButterKnife
|
||||||
|
import com.airbnb.epoxy.EpoxyRecyclerView
|
||||||
|
import com.airbnb.mvrx.MvRx
|
||||||
|
import com.airbnb.mvrx.fragmentViewModel
|
||||||
|
import com.airbnb.mvrx.withState
|
||||||
|
import im.vector.riotx.EmojiCompatFontProvider
|
||||||
|
import im.vector.riotx.R
|
||||||
|
import im.vector.riotx.core.di.ScreenComponent
|
||||||
|
import im.vector.riotx.features.home.room.detail.timeline.action.TimelineEventFragmentArgs
|
||||||
|
import im.vector.riotx.features.home.room.detail.timeline.action.VectorBaseBottomSheetDialogFragment
|
||||||
|
import im.vector.riotx.features.home.room.detail.timeline.action.ViewReactionBottomSheet
|
||||||
|
import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData
|
||||||
|
import kotlinx.android.synthetic.main.bottom_sheet_epoxylist_with_title.*
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bottom sheet displaying list of read receipts for a given event ordered by descending timestamp
|
||||||
|
*/
|
||||||
|
class DisplayReadReceiptsBottomSheet : VectorBaseBottomSheetDialogFragment() {
|
||||||
|
|
||||||
|
private val viewModel: DisplayReadReceiptsViewModel by fragmentViewModel()
|
||||||
|
|
||||||
|
@Inject lateinit var displayReadReceiptsViewModelFactory: DisplayReadReceiptsViewModel.Factory
|
||||||
|
@Inject lateinit var epoxyController: DisplayReadReceiptsController
|
||||||
|
|
||||||
|
@BindView(R.id.bottom_sheet_display_reactions_list)
|
||||||
|
lateinit var epoxyRecyclerView: EpoxyRecyclerView
|
||||||
|
|
||||||
|
|
||||||
|
override fun injectWith(screenComponent: ScreenComponent) {
|
||||||
|
screenComponent.inject(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
|
val view = inflater.inflate(R.layout.bottom_sheet_epoxylist_with_title, container, false)
|
||||||
|
ButterKnife.bind(this, view)
|
||||||
|
return view
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||||
|
super.onActivityCreated(savedInstanceState)
|
||||||
|
epoxyRecyclerView.setController(epoxyController)
|
||||||
|
val dividerItemDecoration = DividerItemDecoration(epoxyRecyclerView.context,
|
||||||
|
LinearLayout.VERTICAL)
|
||||||
|
epoxyRecyclerView.addItemDecoration(dividerItemDecoration)
|
||||||
|
bottomSheetTitle.text = getString(R.string.read_receipts_list)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun invalidate() = withState(viewModel) {
|
||||||
|
epoxyController.setData(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun newInstance(roomId: String, informationData: MessageInformationData): DisplayReadReceiptsBottomSheet {
|
||||||
|
val args = Bundle()
|
||||||
|
val parcelableArgs = TimelineEventFragmentArgs(
|
||||||
|
informationData.eventId,
|
||||||
|
roomId,
|
||||||
|
informationData
|
||||||
|
)
|
||||||
|
args.putParcelable(MvRx.KEY_ARG, parcelableArgs)
|
||||||
|
return DisplayReadReceiptsBottomSheet().apply { arguments = args }
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,71 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.riotx.features.home.room.detail.readreceipts
|
||||||
|
|
||||||
|
import com.airbnb.epoxy.TypedEpoxyController
|
||||||
|
import com.airbnb.mvrx.Fail
|
||||||
|
import com.airbnb.mvrx.Incomplete
|
||||||
|
import com.airbnb.mvrx.Success
|
||||||
|
import im.vector.matrix.android.api.session.Session
|
||||||
|
import im.vector.riotx.R
|
||||||
|
import im.vector.riotx.core.date.VectorDateFormatter
|
||||||
|
import im.vector.riotx.core.resources.StringProvider
|
||||||
|
import im.vector.riotx.core.ui.list.genericFooterItem
|
||||||
|
import im.vector.riotx.core.ui.list.genericLoaderItem
|
||||||
|
import im.vector.riotx.features.home.AvatarRenderer
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Epoxy controller for read receipt event list
|
||||||
|
*/
|
||||||
|
class DisplayReadReceiptsController @Inject constructor(private val dateFormatter: VectorDateFormatter,
|
||||||
|
private val stringProvider: StringProvider,
|
||||||
|
private val session: Session,
|
||||||
|
private val avatarRender: AvatarRenderer)
|
||||||
|
: TypedEpoxyController<DisplayReadReceiptsViewState>() {
|
||||||
|
|
||||||
|
|
||||||
|
override fun buildModels(state: DisplayReadReceiptsViewState) {
|
||||||
|
when (state.readReceipts) {
|
||||||
|
is Incomplete -> {
|
||||||
|
genericLoaderItem {
|
||||||
|
id("loading")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is Fail -> {
|
||||||
|
genericFooterItem {
|
||||||
|
id("failure")
|
||||||
|
text(stringProvider.getString(R.string.unknown_error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is Success -> {
|
||||||
|
state.readReceipts()?.forEach {
|
||||||
|
val timestamp = dateFormatter.formatRelativeDateTime(it.originServerTs)
|
||||||
|
DisplayReadReceiptItem_()
|
||||||
|
.id(it.user.userId)
|
||||||
|
.userId(it.user.userId)
|
||||||
|
.avatarUrl(it.user.avatarUrl)
|
||||||
|
.name(it.user.displayName)
|
||||||
|
.avatarRenderer(avatarRender)
|
||||||
|
.timestamp(timestamp)
|
||||||
|
.addIf(session.myUserId != it.user.userId, this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.riotx.features.home.room.detail.readreceipts
|
||||||
|
|
||||||
|
import com.airbnb.mvrx.*
|
||||||
|
import com.squareup.inject.assisted.Assisted
|
||||||
|
import com.squareup.inject.assisted.AssistedInject
|
||||||
|
import im.vector.matrix.android.api.session.Session
|
||||||
|
import im.vector.matrix.rx.RxRoom
|
||||||
|
import im.vector.riotx.core.platform.VectorViewModel
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to display the list of read receipts to a given event
|
||||||
|
*/
|
||||||
|
class DisplayReadReceiptsViewModel @AssistedInject constructor(@Assisted initialState: DisplayReadReceiptsViewState,
|
||||||
|
private val session: Session
|
||||||
|
) : VectorViewModel<DisplayReadReceiptsViewState>(initialState) {
|
||||||
|
|
||||||
|
private val roomId = initialState.roomId
|
||||||
|
private val eventId = initialState.eventId
|
||||||
|
private val room = session.getRoom(roomId)
|
||||||
|
?: throw IllegalStateException("Shouldn't use this ViewModel without a room")
|
||||||
|
|
||||||
|
@AssistedInject.Factory
|
||||||
|
interface Factory {
|
||||||
|
fun create(initialState: DisplayReadReceiptsViewState): DisplayReadReceiptsViewModel
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object : MvRxViewModelFactory<DisplayReadReceiptsViewModel, DisplayReadReceiptsViewState> {
|
||||||
|
|
||||||
|
override fun create(viewModelContext: ViewModelContext, state: DisplayReadReceiptsViewState): DisplayReadReceiptsViewModel? {
|
||||||
|
val fragment: DisplayReadReceiptsBottomSheet = (viewModelContext as FragmentViewModelContext).fragment()
|
||||||
|
return fragment.displayReadReceiptsViewModelFactory.create(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
observeEventAnnotationSummaries()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun observeEventAnnotationSummaries() {
|
||||||
|
RxRoom(room)
|
||||||
|
.liveEventReadReceipts(eventId)
|
||||||
|
.execute {
|
||||||
|
copy(readReceipts = it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.riotx.features.home.room.detail.readreceipts
|
||||||
|
|
||||||
|
import com.airbnb.mvrx.Async
|
||||||
|
import com.airbnb.mvrx.MvRxState
|
||||||
|
import com.airbnb.mvrx.Uninitialized
|
||||||
|
import im.vector.matrix.android.api.session.room.model.ReadReceipt
|
||||||
|
import im.vector.riotx.features.home.room.detail.timeline.action.TimelineEventFragmentArgs
|
||||||
|
|
||||||
|
data class DisplayReadReceiptsViewState(
|
||||||
|
val eventId: String,
|
||||||
|
val roomId: String,
|
||||||
|
val readReceipts: Async<List<ReadReceipt>> = Uninitialized
|
||||||
|
) : MvRxState {
|
||||||
|
|
||||||
|
constructor(args: TimelineEventFragmentArgs) : this(roomId = args.roomId, eventId = args.eventId)
|
||||||
|
|
||||||
|
}
|
@ -27,6 +27,7 @@ import com.airbnb.epoxy.EpoxyModel
|
|||||||
import im.vector.matrix.android.api.session.room.model.message.*
|
import im.vector.matrix.android.api.session.room.model.message.*
|
||||||
import im.vector.matrix.android.api.session.room.timeline.Timeline
|
import im.vector.matrix.android.api.session.room.timeline.Timeline
|
||||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||||
|
import im.vector.riotx.core.date.VectorDateFormatter
|
||||||
import im.vector.riotx.core.epoxy.LoadingItem_
|
import im.vector.riotx.core.epoxy.LoadingItem_
|
||||||
import im.vector.riotx.core.extensions.localDateTime
|
import im.vector.riotx.core.extensions.localDateTime
|
||||||
import im.vector.riotx.core.resources.UserPreferencesProvider
|
import im.vector.riotx.core.resources.UserPreferencesProvider
|
||||||
@ -42,7 +43,7 @@ import im.vector.riotx.features.media.VideoContentRenderer
|
|||||||
import org.threeten.bp.LocalDateTime
|
import org.threeten.bp.LocalDateTime
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class TimelineEventController @Inject constructor(private val dateFormatter: TimelineDateFormatter,
|
class TimelineEventController @Inject constructor(private val dateFormatter: VectorDateFormatter,
|
||||||
private val timelineItemFactory: TimelineItemFactory,
|
private val timelineItemFactory: TimelineItemFactory,
|
||||||
private val timelineMediaSizeProvider: TimelineMediaSizeProvider,
|
private val timelineMediaSizeProvider: TimelineMediaSizeProvider,
|
||||||
private val avatarRenderer: AvatarRenderer,
|
private val avatarRenderer: AvatarRenderer,
|
||||||
@ -51,7 +52,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Tim
|
|||||||
userPreferencesProvider: UserPreferencesProvider
|
userPreferencesProvider: UserPreferencesProvider
|
||||||
) : EpoxyController(backgroundHandler, backgroundHandler), Timeline.Listener {
|
) : EpoxyController(backgroundHandler, backgroundHandler), Timeline.Listener {
|
||||||
|
|
||||||
interface Callback : ReactionPillCallback, AvatarCallback, BaseCallback, UrlClickCallback {
|
interface Callback : BaseCallback, ReactionPillCallback, AvatarCallback, UrlClickCallback, ReadReceiptsCallback {
|
||||||
fun onEventVisible(event: TimelineEvent)
|
fun onEventVisible(event: TimelineEvent)
|
||||||
fun onRoomCreateLinkClicked(url: String)
|
fun onRoomCreateLinkClicked(url: String)
|
||||||
fun onEncryptedMessageClicked(informationData: MessageInformationData, view: View)
|
fun onEncryptedMessageClicked(informationData: MessageInformationData, view: View)
|
||||||
@ -77,6 +78,10 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Tim
|
|||||||
fun onMemberNameClicked(informationData: MessageInformationData)
|
fun onMemberNameClicked(informationData: MessageInformationData)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ReadReceiptsCallback {
|
||||||
|
fun onReadReceiptsClicked(informationData: MessageInformationData)
|
||||||
|
}
|
||||||
|
|
||||||
interface UrlClickCallback {
|
interface UrlClickCallback {
|
||||||
fun onUrlClicked(url: String): Boolean
|
fun onUrlClicked(url: String): Boolean
|
||||||
fun onUrlLongClicked(url: String): Boolean
|
fun onUrlLongClicked(url: String): Boolean
|
||||||
@ -158,7 +163,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Tim
|
|||||||
synchronized(modelCache) {
|
synchronized(modelCache) {
|
||||||
for (i in 0 until modelCache.size) {
|
for (i in 0 until modelCache.size) {
|
||||||
if (modelCache[i]?.eventId == eventIdToHighlight
|
if (modelCache[i]?.eventId == eventIdToHighlight
|
||||||
|| modelCache[i]?.eventId == this.eventIdToHighlight) {
|
|| modelCache[i]?.eventId == this.eventIdToHighlight) {
|
||||||
modelCache[i] = null
|
modelCache[i] = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -219,8 +224,8 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Tim
|
|||||||
// Should be build if not cached or if cached but contains mergedHeader or formattedDay
|
// Should be build if not cached or if cached but contains mergedHeader or formattedDay
|
||||||
// We then are sure we always have items up to date.
|
// We then are sure we always have items up to date.
|
||||||
if (modelCache[position] == null
|
if (modelCache[position] == null
|
||||||
|| modelCache[position]?.mergedHeaderModel != null
|
|| modelCache[position]?.mergedHeaderModel != null
|
||||||
|| modelCache[position]?.formattedDayModel != null) {
|
|| modelCache[position]?.formattedDayModel != null) {
|
||||||
modelCache[position] = buildItemModels(position, currentSnapshot)
|
modelCache[position] = buildItemModels(position, currentSnapshot)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -293,7 +298,8 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Tim
|
|||||||
// We try to find if one of the item id were used as mergeItemCollapseStates key
|
// We try to find if one of the item id were used as mergeItemCollapseStates key
|
||||||
// => handle case where paginating from mergeable events and we get more
|
// => handle case where paginating from mergeable events and we get more
|
||||||
val previousCollapseStateKey = mergedEventIds.intersect(mergeItemCollapseStates.keys).firstOrNull()
|
val previousCollapseStateKey = mergedEventIds.intersect(mergeItemCollapseStates.keys).firstOrNull()
|
||||||
val initialCollapseState = mergeItemCollapseStates.remove(previousCollapseStateKey) ?: true
|
val initialCollapseState = mergeItemCollapseStates.remove(previousCollapseStateKey)
|
||||||
|
?: true
|
||||||
val isCollapsed = mergeItemCollapseStates.getOrPut(event.localId) { initialCollapseState }
|
val isCollapsed = mergeItemCollapseStates.getOrPut(event.localId) { initialCollapseState }
|
||||||
if (isCollapsed) {
|
if (isCollapsed) {
|
||||||
collapsedEventIds.addAll(mergedEventIds)
|
collapsedEventIds.addAll(mergedEventIds)
|
||||||
|
@ -49,7 +49,7 @@ class ViewEditHistoryBottomSheet : VectorBaseBottomSheetDialogFragment() {
|
|||||||
lateinit var epoxyRecyclerView: EpoxyRecyclerView
|
lateinit var epoxyRecyclerView: EpoxyRecyclerView
|
||||||
|
|
||||||
private val epoxyController by lazy {
|
private val epoxyController by lazy {
|
||||||
ViewEditHistoryEpoxyController(requireContext(), viewModel.timelineDateFormatter, eventHtmlRenderer)
|
ViewEditHistoryEpoxyController(requireContext(), viewModel.dateFormatter, eventHtmlRenderer)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun injectWith(screenComponent: ScreenComponent) {
|
override fun injectWith(screenComponent: ScreenComponent) {
|
||||||
|
@ -33,7 +33,7 @@ import im.vector.riotx.core.ui.list.genericFooterItem
|
|||||||
import im.vector.riotx.core.ui.list.genericItem
|
import im.vector.riotx.core.ui.list.genericItem
|
||||||
import im.vector.riotx.core.ui.list.genericItemHeader
|
import im.vector.riotx.core.ui.list.genericItemHeader
|
||||||
import im.vector.riotx.core.ui.list.genericLoaderItem
|
import im.vector.riotx.core.ui.list.genericLoaderItem
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineDateFormatter
|
import im.vector.riotx.core.date.VectorDateFormatter
|
||||||
import im.vector.riotx.features.html.EventHtmlRenderer
|
import im.vector.riotx.features.html.EventHtmlRenderer
|
||||||
import me.gujun.android.span.span
|
import me.gujun.android.span.span
|
||||||
import name.fraser.neil.plaintext.diff_match_patch
|
import name.fraser.neil.plaintext.diff_match_patch
|
||||||
@ -44,7 +44,7 @@ import java.util.*
|
|||||||
* Epoxy controller for reaction event list
|
* Epoxy controller for reaction event list
|
||||||
*/
|
*/
|
||||||
class ViewEditHistoryEpoxyController(private val context: Context,
|
class ViewEditHistoryEpoxyController(private val context: Context,
|
||||||
val timelineDateFormatter: TimelineDateFormatter,
|
val dateFormatter: VectorDateFormatter,
|
||||||
val eventHtmlRenderer: EventHtmlRenderer) : TypedEpoxyController<ViewEditHistoryViewState>() {
|
val eventHtmlRenderer: EventHtmlRenderer) : TypedEpoxyController<ViewEditHistoryViewState>() {
|
||||||
|
|
||||||
override fun buildModels(state: ViewEditHistoryViewState) {
|
override fun buildModels(state: ViewEditHistoryViewState) {
|
||||||
@ -84,7 +84,7 @@ class ViewEditHistoryEpoxyController(private val context: Context,
|
|||||||
if (lastDate?.get(Calendar.DAY_OF_YEAR) != evDate.get(Calendar.DAY_OF_YEAR)) {
|
if (lastDate?.get(Calendar.DAY_OF_YEAR) != evDate.get(Calendar.DAY_OF_YEAR)) {
|
||||||
//need to display header with day
|
//need to display header with day
|
||||||
val dateString = if (DateUtils.isToday(evDate.timeInMillis)) context.getString(R.string.today)
|
val dateString = if (DateUtils.isToday(evDate.timeInMillis)) context.getString(R.string.today)
|
||||||
else timelineDateFormatter.formatMessageDay(timelineEvent.localDateTime())
|
else dateFormatter.formatMessageDay(timelineEvent.localDateTime())
|
||||||
genericItemHeader {
|
genericItemHeader {
|
||||||
id(evDate.hashCode())
|
id(evDate.hashCode())
|
||||||
text(dateString)
|
text(dateString)
|
||||||
@ -136,7 +136,7 @@ class ViewEditHistoryEpoxyController(private val context: Context,
|
|||||||
}
|
}
|
||||||
genericItem {
|
genericItem {
|
||||||
id(timelineEvent.eventId)
|
id(timelineEvent.eventId)
|
||||||
title(timelineDateFormatter.formatMessageHour(timelineEvent.localDateTime()))
|
title(dateFormatter.formatMessageHour(timelineEvent.localDateTime()))
|
||||||
description(spannedDiff ?: body)
|
description(spannedDiff ?: body)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
|||||||
import im.vector.matrix.android.api.session.room.model.message.isReply
|
import im.vector.matrix.android.api.session.room.model.message.isReply
|
||||||
import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult
|
import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult
|
||||||
import im.vector.riotx.core.platform.VectorViewModel
|
import im.vector.riotx.core.platform.VectorViewModel
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineDateFormatter
|
import im.vector.riotx.core.date.VectorDateFormatter
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@ -46,7 +46,7 @@ data class ViewEditHistoryViewState(
|
|||||||
class ViewEditHistoryViewModel @AssistedInject constructor(@Assisted
|
class ViewEditHistoryViewModel @AssistedInject constructor(@Assisted
|
||||||
initialState: ViewEditHistoryViewState,
|
initialState: ViewEditHistoryViewState,
|
||||||
val session: Session,
|
val session: Session,
|
||||||
val timelineDateFormatter: TimelineDateFormatter
|
val dateFormatter: VectorDateFormatter
|
||||||
) : VectorViewModel<ViewEditHistoryViewState>(initialState) {
|
) : VectorViewModel<ViewEditHistoryViewState>(initialState) {
|
||||||
|
|
||||||
private val roomId = initialState.roomId
|
private val roomId = initialState.roomId
|
||||||
|
@ -16,16 +16,20 @@
|
|||||||
|
|
||||||
package im.vector.riotx.features.home.room.detail.timeline.action
|
package im.vector.riotx.features.home.room.detail.timeline.action
|
||||||
|
|
||||||
import com.airbnb.mvrx.*
|
import com.airbnb.mvrx.Async
|
||||||
|
import com.airbnb.mvrx.FragmentViewModelContext
|
||||||
|
import com.airbnb.mvrx.MvRxState
|
||||||
|
import com.airbnb.mvrx.MvRxViewModelFactory
|
||||||
|
import com.airbnb.mvrx.Uninitialized
|
||||||
|
import com.airbnb.mvrx.ViewModelContext
|
||||||
import com.squareup.inject.assisted.Assisted
|
import com.squareup.inject.assisted.Assisted
|
||||||
import com.squareup.inject.assisted.AssistedInject
|
import com.squareup.inject.assisted.AssistedInject
|
||||||
import im.vector.matrix.android.api.session.Session
|
import im.vector.matrix.android.api.session.Session
|
||||||
import im.vector.matrix.android.api.session.room.model.ReactionAggregatedSummary
|
import im.vector.matrix.android.api.session.room.model.ReactionAggregatedSummary
|
||||||
import im.vector.matrix.rx.RxRoom
|
import im.vector.matrix.rx.RxRoom
|
||||||
import im.vector.riotx.core.extensions.localDateTime
|
|
||||||
import im.vector.riotx.core.platform.VectorViewModel
|
import im.vector.riotx.core.platform.VectorViewModel
|
||||||
import im.vector.riotx.core.utils.isSingleEmoji
|
import im.vector.riotx.core.utils.isSingleEmoji
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineDateFormatter
|
import im.vector.riotx.core.date.VectorDateFormatter
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
import io.reactivex.Single
|
import io.reactivex.Single
|
||||||
|
|
||||||
@ -54,13 +58,13 @@ data class ReactionInfo(
|
|||||||
class ViewReactionViewModel @AssistedInject constructor(@Assisted
|
class ViewReactionViewModel @AssistedInject constructor(@Assisted
|
||||||
initialState: DisplayReactionsViewState,
|
initialState: DisplayReactionsViewState,
|
||||||
private val session: Session,
|
private val session: Session,
|
||||||
private val timelineDateFormatter: TimelineDateFormatter
|
private val dateFormatter: VectorDateFormatter
|
||||||
) : VectorViewModel<DisplayReactionsViewState>(initialState) {
|
) : VectorViewModel<DisplayReactionsViewState>(initialState) {
|
||||||
|
|
||||||
private val roomId = initialState.roomId
|
private val roomId = initialState.roomId
|
||||||
private val eventId = initialState.eventId
|
private val eventId = initialState.eventId
|
||||||
private val room = session.getRoom(roomId)
|
private val room = session.getRoom(roomId)
|
||||||
?: throw IllegalStateException("Shouldn't use this ViewModel without a room")
|
?: throw IllegalStateException("Shouldn't use this ViewModel without a room")
|
||||||
|
|
||||||
@AssistedInject.Factory
|
@AssistedInject.Factory
|
||||||
interface Factory {
|
interface Factory {
|
||||||
@ -100,14 +104,14 @@ class ViewReactionViewModel @AssistedInject constructor(@Assisted
|
|||||||
.fromIterable(summary.sourceEvents)
|
.fromIterable(summary.sourceEvents)
|
||||||
.map {
|
.map {
|
||||||
val event = room.getTimeLineEvent(it)
|
val event = room.getTimeLineEvent(it)
|
||||||
?: throw RuntimeException("Your eventId is not valid")
|
?: throw RuntimeException("Your eventId is not valid")
|
||||||
val localDate = event.root.localDateTime()
|
|
||||||
ReactionInfo(
|
ReactionInfo(
|
||||||
event.root.eventId!!,
|
event.root.eventId!!,
|
||||||
summary.key,
|
summary.key,
|
||||||
event.root.senderId ?: "",
|
event.root.senderId ?: "",
|
||||||
event.getDisambiguatedDisplayName(),
|
event.getDisambiguatedDisplayName(),
|
||||||
timelineDateFormatter.formatMessageHour(localDate)
|
dateFormatter.formatRelativeDateTime(event.root.originServerTs)
|
||||||
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}.toList()
|
}.toList()
|
||||||
|
@ -18,6 +18,7 @@ package im.vector.riotx.features.home.room.detail.timeline.action
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Typeface
|
import android.graphics.Typeface
|
||||||
|
import android.text.format.DateUtils
|
||||||
import com.airbnb.epoxy.TypedEpoxyController
|
import com.airbnb.epoxy.TypedEpoxyController
|
||||||
import com.airbnb.mvrx.Fail
|
import com.airbnb.mvrx.Fail
|
||||||
import com.airbnb.mvrx.Incomplete
|
import com.airbnb.mvrx.Incomplete
|
||||||
|
@ -161,6 +161,7 @@ class MessageItemFactory @Inject constructor(
|
|||||||
.informationData(informationData)
|
.informationData(informationData)
|
||||||
.highlighted(highlight)
|
.highlighted(highlight)
|
||||||
.avatarCallback(callback)
|
.avatarCallback(callback)
|
||||||
|
.readReceiptsCallback(callback)
|
||||||
.filename(messageContent.body)
|
.filename(messageContent.body)
|
||||||
.iconRes(R.drawable.filetype_audio)
|
.iconRes(R.drawable.filetype_audio)
|
||||||
.reactionPillCallback(callback)
|
.reactionPillCallback(callback)
|
||||||
@ -191,6 +192,7 @@ class MessageItemFactory @Inject constructor(
|
|||||||
.avatarCallback(callback)
|
.avatarCallback(callback)
|
||||||
.filename(messageContent.body)
|
.filename(messageContent.body)
|
||||||
.reactionPillCallback(callback)
|
.reactionPillCallback(callback)
|
||||||
|
.readReceiptsCallback(callback)
|
||||||
.emojiTypeFace(emojiCompatFontProvider.typeface)
|
.emojiTypeFace(emojiCompatFontProvider.typeface)
|
||||||
.iconRes(R.drawable.filetype_attachment)
|
.iconRes(R.drawable.filetype_attachment)
|
||||||
.cellClickListener(
|
.cellClickListener(
|
||||||
@ -205,10 +207,6 @@ class MessageItemFactory @Inject constructor(
|
|||||||
DebouncedClickListener(View.OnClickListener { _ ->
|
DebouncedClickListener(View.OnClickListener { _ ->
|
||||||
callback?.onFileMessageClicked(informationData.eventId, messageContent)
|
callback?.onFileMessageClicked(informationData.eventId, messageContent)
|
||||||
}))
|
}))
|
||||||
.longClickListener { view ->
|
|
||||||
return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view)
|
|
||||||
?: false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildNotHandledMessageItem(messageContent: MessageContent, highlight: Boolean): DefaultItem? {
|
private fun buildNotHandledMessageItem(messageContent: MessageContent, highlight: Boolean): DefaultItem? {
|
||||||
@ -246,6 +244,7 @@ class MessageItemFactory @Inject constructor(
|
|||||||
.avatarCallback(callback)
|
.avatarCallback(callback)
|
||||||
.mediaData(data)
|
.mediaData(data)
|
||||||
.reactionPillCallback(callback)
|
.reactionPillCallback(callback)
|
||||||
|
.readReceiptsCallback(callback)
|
||||||
.emojiTypeFace(emojiCompatFontProvider.typeface)
|
.emojiTypeFace(emojiCompatFontProvider.typeface)
|
||||||
.clickListener(
|
.clickListener(
|
||||||
DebouncedClickListener(View.OnClickListener { view ->
|
DebouncedClickListener(View.OnClickListener { view ->
|
||||||
@ -297,6 +296,7 @@ class MessageItemFactory @Inject constructor(
|
|||||||
.avatarCallback(callback)
|
.avatarCallback(callback)
|
||||||
.mediaData(thumbnailData)
|
.mediaData(thumbnailData)
|
||||||
.reactionPillCallback(callback)
|
.reactionPillCallback(callback)
|
||||||
|
.readReceiptsCallback(callback)
|
||||||
.emojiTypeFace(emojiCompatFontProvider.typeface)
|
.emojiTypeFace(emojiCompatFontProvider.typeface)
|
||||||
.cellClickListener(
|
.cellClickListener(
|
||||||
DebouncedClickListener(View.OnClickListener { view ->
|
DebouncedClickListener(View.OnClickListener { view ->
|
||||||
@ -336,6 +336,7 @@ class MessageItemFactory @Inject constructor(
|
|||||||
.avatarCallback(callback)
|
.avatarCallback(callback)
|
||||||
.urlClickCallback(callback)
|
.urlClickCallback(callback)
|
||||||
.reactionPillCallback(callback)
|
.reactionPillCallback(callback)
|
||||||
|
.readReceiptsCallback(callback)
|
||||||
.emojiTypeFace(emojiCompatFontProvider.typeface)
|
.emojiTypeFace(emojiCompatFontProvider.typeface)
|
||||||
//click on the text
|
//click on the text
|
||||||
.cellClickListener(
|
.cellClickListener(
|
||||||
@ -402,6 +403,7 @@ class MessageItemFactory @Inject constructor(
|
|||||||
.avatarCallback(callback)
|
.avatarCallback(callback)
|
||||||
.reactionPillCallback(callback)
|
.reactionPillCallback(callback)
|
||||||
.urlClickCallback(callback)
|
.urlClickCallback(callback)
|
||||||
|
.readReceiptsCallback(callback)
|
||||||
.emojiTypeFace(emojiCompatFontProvider.typeface)
|
.emojiTypeFace(emojiCompatFontProvider.typeface)
|
||||||
.memberClickListener(
|
.memberClickListener(
|
||||||
DebouncedClickListener(View.OnClickListener { view ->
|
DebouncedClickListener(View.OnClickListener { view ->
|
||||||
@ -441,6 +443,7 @@ class MessageItemFactory @Inject constructor(
|
|||||||
.highlighted(highlight)
|
.highlighted(highlight)
|
||||||
.avatarCallback(callback)
|
.avatarCallback(callback)
|
||||||
.reactionPillCallback(callback)
|
.reactionPillCallback(callback)
|
||||||
|
.readReceiptsCallback(callback)
|
||||||
.urlClickCallback(callback)
|
.urlClickCallback(callback)
|
||||||
.emojiTypeFace(emojiCompatFontProvider.typeface)
|
.emojiTypeFace(emojiCompatFontProvider.typeface)
|
||||||
.cellClickListener(
|
.cellClickListener(
|
||||||
@ -462,6 +465,7 @@ class MessageItemFactory @Inject constructor(
|
|||||||
.informationData(informationData)
|
.informationData(informationData)
|
||||||
.highlighted(highlight)
|
.highlighted(highlight)
|
||||||
.avatarCallback(callback)
|
.avatarCallback(callback)
|
||||||
|
.readReceiptsCallback(callback)
|
||||||
.cellClickListener(
|
.cellClickListener(
|
||||||
DebouncedClickListener(View.OnClickListener { view ->
|
DebouncedClickListener(View.OnClickListener { view ->
|
||||||
callback?.onEventCellClicked(informationData, null, view)
|
callback?.onEventCellClicked(informationData, null, view)
|
||||||
|
@ -44,6 +44,7 @@ class NoticeItemFactory @Inject constructor(private val eventFormatter: NoticeEv
|
|||||||
.highlighted(highlight)
|
.highlighted(highlight)
|
||||||
.informationData(informationData)
|
.informationData(informationData)
|
||||||
.baseCallback(callback)
|
.baseCallback(callback)
|
||||||
|
.readReceiptsCallback(callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
package im.vector.riotx.features.home.room.detail.timeline.item
|
package im.vector.riotx.features.home.room.detail.timeline.item
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.graphics.Typeface
|
import android.graphics.Typeface
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.view.View
|
import android.view.View
|
||||||
@ -70,6 +69,9 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : BaseEventItem<H>() {
|
|||||||
@EpoxyAttribute
|
@EpoxyAttribute
|
||||||
var avatarCallback: TimelineEventController.AvatarCallback? = null
|
var avatarCallback: TimelineEventController.AvatarCallback? = null
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var readReceiptsCallback: TimelineEventController.ReadReceiptsCallback? = null
|
||||||
|
|
||||||
private val _avatarClickListener = DebouncedClickListener(View.OnClickListener {
|
private val _avatarClickListener = DebouncedClickListener(View.OnClickListener {
|
||||||
avatarCallback?.onAvatarClicked(informationData)
|
avatarCallback?.onAvatarClicked(informationData)
|
||||||
})
|
})
|
||||||
@ -77,6 +79,9 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : BaseEventItem<H>() {
|
|||||||
avatarCallback?.onMemberNameClicked(informationData)
|
avatarCallback?.onMemberNameClicked(informationData)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
private val _readReceiptsClickListener = DebouncedClickListener(View.OnClickListener {
|
||||||
|
readReceiptsCallback?.onReadReceiptsClicked(informationData)
|
||||||
|
})
|
||||||
|
|
||||||
var reactionClickListener: ReactionButton.ReactedListener = object : ReactionButton.ReactedListener {
|
var reactionClickListener: ReactionButton.ReactedListener = object : ReactionButton.ReactedListener {
|
||||||
override fun onReacted(reactionButton: ReactionButton) {
|
override fun onReacted(reactionButton: ReactionButton) {
|
||||||
@ -124,7 +129,7 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : BaseEventItem<H>() {
|
|||||||
holder.memberNameView.setOnLongClickListener(null)
|
holder.memberNameView.setOnLongClickListener(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
holder.readReceiptsView.render(informationData.readReceipts, avatarRenderer)
|
holder.readReceiptsView.render(informationData.readReceipts, avatarRenderer, _readReceiptsClickListener)
|
||||||
|
|
||||||
if (!shouldShowReactionAtBottom() || informationData.orderedReactionList.isNullOrEmpty()) {
|
if (!shouldShowReactionAtBottom() || informationData.orderedReactionList.isNullOrEmpty()) {
|
||||||
holder.reactionWrapper?.isVisible = false
|
holder.reactionWrapper?.isVisible = false
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
package im.vector.riotx.features.home.room.detail.timeline.item
|
package im.vector.riotx.features.home.room.detail.timeline.item
|
||||||
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import im.vector.matrix.android.api.session.room.model.ReadReceipt
|
|
||||||
import im.vector.matrix.android.api.session.room.send.SendState
|
import im.vector.matrix.android.api.session.room.send.SendState
|
||||||
import kotlinx.android.parcel.Parcelize
|
import kotlinx.android.parcel.Parcelize
|
||||||
|
|
||||||
|
@ -23,6 +23,7 @@ import com.airbnb.epoxy.EpoxyAttribute
|
|||||||
import com.airbnb.epoxy.EpoxyModelClass
|
import com.airbnb.epoxy.EpoxyModelClass
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.ui.views.ReadReceiptsView
|
import im.vector.riotx.core.ui.views.ReadReceiptsView
|
||||||
|
import im.vector.riotx.core.utils.DebouncedClickListener
|
||||||
import im.vector.riotx.features.home.AvatarRenderer
|
import im.vector.riotx.features.home.AvatarRenderer
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
|
import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
|
||||||
|
|
||||||
@ -45,6 +46,13 @@ abstract class NoticeItem : BaseEventItem<NoticeItem.Holder>() {
|
|||||||
return@OnLongClickListener baseCallback?.onEventLongClicked(informationData, null, it) == true
|
return@OnLongClickListener baseCallback?.onEventLongClicked(informationData, null, it) == true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var readReceiptsCallback: TimelineEventController.ReadReceiptsCallback? = null
|
||||||
|
|
||||||
|
private val _readReceiptsClickListener = DebouncedClickListener(View.OnClickListener {
|
||||||
|
readReceiptsCallback?.onReadReceiptsClicked(informationData)
|
||||||
|
})
|
||||||
|
|
||||||
override fun bind(holder: Holder) {
|
override fun bind(holder: Holder) {
|
||||||
super.bind(holder)
|
super.bind(holder)
|
||||||
holder.noticeTextView.text = noticeText
|
holder.noticeTextView.text = noticeText
|
||||||
@ -56,7 +64,7 @@ abstract class NoticeItem : BaseEventItem<NoticeItem.Holder>() {
|
|||||||
holder.avatarImageView
|
holder.avatarImageView
|
||||||
)
|
)
|
||||||
holder.view.setOnLongClickListener(longClickListener)
|
holder.view.setOnLongClickListener(longClickListener)
|
||||||
holder.readReceiptsView.render(informationData.readReceipts, avatarRenderer)
|
holder.readReceiptsView.render(informationData.readReceipts, avatarRenderer, _readReceiptsClickListener)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getViewType() = STUB_ID
|
override fun getViewType() = STUB_ID
|
||||||
|
@ -24,7 +24,7 @@ import im.vector.riotx.core.extensions.localDateTime
|
|||||||
import im.vector.riotx.core.resources.ColorProvider
|
import im.vector.riotx.core.resources.ColorProvider
|
||||||
import im.vector.riotx.core.utils.isSingleEmoji
|
import im.vector.riotx.core.utils.isSingleEmoji
|
||||||
import im.vector.riotx.features.home.getColorFromUserId
|
import im.vector.riotx.features.home.getColorFromUserId
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineDateFormatter
|
import im.vector.riotx.core.date.VectorDateFormatter
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData
|
import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.item.ReactionInfoData
|
import im.vector.riotx.features.home.room.detail.timeline.item.ReactionInfoData
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.item.ReadReceiptData
|
import im.vector.riotx.features.home.room.detail.timeline.item.ReadReceiptData
|
||||||
@ -35,7 +35,7 @@ import javax.inject.Inject
|
|||||||
* This class compute if data of an event (such has avatar, display name, ...) should be displayed, depending on the previous event in the timeline
|
* This class compute if data of an event (such has avatar, display name, ...) should be displayed, depending on the previous event in the timeline
|
||||||
*/
|
*/
|
||||||
class MessageInformationDataFactory @Inject constructor(private val session: Session,
|
class MessageInformationDataFactory @Inject constructor(private val session: Session,
|
||||||
private val timelineDateFormatter: TimelineDateFormatter,
|
private val dateFormatter: VectorDateFormatter,
|
||||||
private val colorProvider: ColorProvider) {
|
private val colorProvider: ColorProvider) {
|
||||||
|
|
||||||
fun create(event: TimelineEvent, nextEvent: TimelineEvent?): MessageInformationData {
|
fun create(event: TimelineEvent, nextEvent: TimelineEvent?): MessageInformationData {
|
||||||
@ -55,7 +55,7 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
|
|||||||
|| (nextEvent?.root?.getClearType() != EventType.MESSAGE && nextEvent?.root?.getClearType() != EventType.ENCRYPTED)
|
|| (nextEvent?.root?.getClearType() != EventType.MESSAGE && nextEvent?.root?.getClearType() != EventType.ENCRYPTED)
|
||||||
|| isNextMessageReceivedMoreThanOneHourAgo
|
|| isNextMessageReceivedMoreThanOneHourAgo
|
||||||
|
|
||||||
val time = timelineDateFormatter.formatMessageHour(date)
|
val time = dateFormatter.formatMessageHour(date)
|
||||||
val avatarUrl = event.senderAvatar
|
val avatarUrl = event.senderAvatar
|
||||||
val memberName = event.getDisambiguatedDisplayName()
|
val memberName = event.getDisambiguatedDisplayName()
|
||||||
val formattedMemberName = span(memberName) {
|
val formattedMemberName = span(memberName) {
|
||||||
@ -79,12 +79,14 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
|
|||||||
hasBeenEdited = event.hasBeenEdited(),
|
hasBeenEdited = event.hasBeenEdited(),
|
||||||
hasPendingEdits = event.annotations?.editSummary?.localEchos?.any() ?: false,
|
hasPendingEdits = event.annotations?.editSummary?.localEchos?.any() ?: false,
|
||||||
readReceipts = event.readReceipts
|
readReceipts = event.readReceipts
|
||||||
|
.asSequence()
|
||||||
.filter {
|
.filter {
|
||||||
it.user.userId != session.myUserId
|
it.user.userId != session.myUserId
|
||||||
}
|
}
|
||||||
.map {
|
.map {
|
||||||
ReadReceiptData(it.user.userId, it.user.avatarUrl, it.user.displayName, it.originServerTs)
|
ReadReceiptData(it.user.userId, it.user.avatarUrl, it.user.displayName, it.originServerTs)
|
||||||
}
|
}
|
||||||
|
.toList()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -29,13 +29,13 @@ import im.vector.riotx.core.resources.DateProvider
|
|||||||
import im.vector.riotx.core.resources.StringProvider
|
import im.vector.riotx.core.resources.StringProvider
|
||||||
import im.vector.riotx.features.home.AvatarRenderer
|
import im.vector.riotx.features.home.AvatarRenderer
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.format.NoticeEventFormatter
|
import im.vector.riotx.features.home.room.detail.timeline.format.NoticeEventFormatter
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineDateFormatter
|
import im.vector.riotx.core.date.VectorDateFormatter
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.helper.senderName
|
import im.vector.riotx.features.home.room.detail.timeline.helper.senderName
|
||||||
import me.gujun.android.span.span
|
import me.gujun.android.span.span
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class RoomSummaryItemFactory @Inject constructor(private val noticeEventFormatter: NoticeEventFormatter,
|
class RoomSummaryItemFactory @Inject constructor(private val noticeEventFormatter: NoticeEventFormatter,
|
||||||
private val timelineDateFormatter: TimelineDateFormatter,
|
private val dateFormatter: VectorDateFormatter,
|
||||||
private val colorProvider: ColorProvider,
|
private val colorProvider: ColorProvider,
|
||||||
private val stringProvider: StringProvider,
|
private val stringProvider: StringProvider,
|
||||||
private val avatarRenderer: AvatarRenderer) {
|
private val avatarRenderer: AvatarRenderer) {
|
||||||
@ -94,7 +94,7 @@ class RoomSummaryItemFactory @Inject constructor(private val noticeEventFormatte
|
|||||||
val currentDate = DateProvider.currentLocalDateTime()
|
val currentDate = DateProvider.currentLocalDateTime()
|
||||||
val isSameDay = date.toLocalDate() == currentDate.toLocalDate()
|
val isSameDay = date.toLocalDate() == currentDate.toLocalDate()
|
||||||
latestFormattedEvent = if (latestEvent.root.isEncrypted()
|
latestFormattedEvent = if (latestEvent.root.isEncrypted()
|
||||||
&& latestEvent.root.mxDecryptionResult == null) {
|
&& latestEvent.root.mxDecryptionResult == null) {
|
||||||
stringProvider.getString(R.string.encrypted_message)
|
stringProvider.getString(R.string.encrypted_message)
|
||||||
} else if (latestEvent.root.getClearType() == EventType.MESSAGE) {
|
} else if (latestEvent.root.getClearType() == EventType.MESSAGE) {
|
||||||
val senderName = latestEvent.senderName() ?: latestEvent.root.senderId
|
val senderName = latestEvent.senderName() ?: latestEvent.root.senderId
|
||||||
@ -117,10 +117,9 @@ class RoomSummaryItemFactory @Inject constructor(private val noticeEventFormatte
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
latestEventTime = if (isSameDay) {
|
latestEventTime = if (isSameDay) {
|
||||||
timelineDateFormatter.formatMessageHour(date)
|
dateFormatter.formatMessageHour(date)
|
||||||
} else {
|
} else {
|
||||||
//TODO: change this
|
dateFormatter.formatMessageDay(date)
|
||||||
timelineDateFormatter.formatMessageDay(date)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return RoomSummaryItem_()
|
return RoomSummaryItem_()
|
||||||
|
43
vector/src/main/res/layout/item_display_read_receipt.xml
Normal file
43
vector/src/main/res/layout/item_display_read_receipt.xml
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="44dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingStart="8dp"
|
||||||
|
android:paddingLeft="8dp"
|
||||||
|
android:paddingEnd="8dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/readReceiptAvatar"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
tools:src="@tools:sample/avatars" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/readReceiptName"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="4dp"
|
||||||
|
android:layout_marginLeft="4dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:lines="1"
|
||||||
|
android:textColor="?android:textColorPrimary"
|
||||||
|
android:textSize="16sp"
|
||||||
|
tools:text="@sample/matrix.json/data/displayName" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/readReceiptDate"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:lines="1"
|
||||||
|
android:textColor="?android:textColorSecondary"
|
||||||
|
android:textSize="12sp"
|
||||||
|
tools:text="10:44" />
|
||||||
|
|
||||||
|
|
||||||
|
</LinearLayout>
|
Loading…
Reference in New Issue
Block a user