From 622ada71254f62a4849253afa3560f014dd0d339 Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 16 Jun 2022 16:42:15 +0200 Subject: [PATCH 1/5] ensure ageLocalTs is set --- .../verification/VerificationMessageProcessor.kt | 2 +- .../database/helper/ThreadSummaryHelper.kt | 2 +- .../sdk/internal/database/mapper/EventMapper.kt | 2 +- .../session/room/membership/LoadRoomMembersTask.kt | 2 +- .../relation/threads/FetchThreadTimelineTask.kt | 3 ++- .../room/timeline/TokenChunkEventPersistor.kt | 4 ++-- .../session/sync/handler/room/RoomSyncHandler.kt | 14 ++++++++------ .../sync/handler/room/ThreadsAwarenessHandler.kt | 6 ++++-- 8 files changed, 20 insertions(+), 15 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationMessageProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationMessageProcessor.kt index 9f123f0c08..821663bcff 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationMessageProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationMessageProcessor.kt @@ -62,7 +62,7 @@ internal class VerificationMessageProcessor @Inject constructor( // If the request is in the future by more than 5 minutes or more than 10 minutes in the past, // the message should be ignored by the receiver. - if (event.ageLocalTs != null && !VerificationService.isValidRequest(event.ageLocalTs, clock.epochMillis())) return Unit.also { + if (!VerificationService.isValidRequest(event.ageLocalTs, clock.epochMillis())) return Unit.also { Timber.d("## SAS Verification live observer: msgId: ${event.eventId} is outdated age:$event.ageLocalTs ms") } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadSummaryHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadSummaryHelper.kt index 79a99cdfac..0754d0127f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadSummaryHelper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadSummaryHelper.kt @@ -271,7 +271,7 @@ private fun HashMap.addSenderState(realm: Realm, roo * Create an EventEntity for the root thread event or get an existing one. */ private fun createEventEntity(realm: Realm, roomId: String, event: Event, currentTimeMillis: Long): EventEntity { - val ageLocalTs = event.unsignedData?.age?.let { currentTimeMillis - it } + val ageLocalTs = event.unsignedData?.age?.let { currentTimeMillis - it } ?: currentTimeMillis return event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, EventInsertType.PAGINATION) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventMapper.kt index 5b60c53642..0f0a847c78 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventMapper.kt @@ -130,7 +130,7 @@ internal fun EventEntity.asDomain(castJsonNumbers: Boolean = false): Event { internal fun Event.toEntity( roomId: String, sendState: SendState, - ageLocalTs: Long?, + ageLocalTs: Long, contentToInject: String? = null ): EventEntity { return EventMapper.map(this, roomId).apply { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/LoadRoomMembersTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/LoadRoomMembersTask.kt index 15d0889255..f33c8d29be 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/LoadRoomMembersTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/LoadRoomMembersTask.kt @@ -114,7 +114,7 @@ internal class DefaultLoadRoomMembersTask @Inject constructor( if (roomMemberEvent.eventId == null || roomMemberEvent.stateKey == null || roomMemberEvent.type == null) { continue } - val ageLocalTs = roomMemberEvent.unsignedData?.age?.let { now - it } + val ageLocalTs = roomMemberEvent.unsignedData?.age?.let { now - it } ?: now val eventEntity = roomMemberEvent.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, EventInsertType.PAGINATION) CurrentStateEventEntity.getOrCreate( realm, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadTimelineTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadTimelineTask.kt index bad734173e..23f92befb1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadTimelineTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadTimelineTask.kt @@ -209,7 +209,8 @@ internal class DefaultFetchThreadTimelineTask @Inject constructor( * Create an EventEntity to be added in the TimelineEventEntity. */ private fun createEventEntity(roomId: String, event: Event, realm: Realm): EventEntity { - val ageLocalTs = event.unsignedData?.age?.let { clock.epochMillis() - it } + val now = clock.epochMillis() + val ageLocalTs = event.unsignedData?.age?.let { now - it } ?: now return event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, EventInsertType.PAGINATION) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt index fd1703dbc8..36552a21f5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt @@ -142,7 +142,7 @@ internal class TokenChunkEventPersistor @Inject constructor( val now = clock.epochMillis() stateEvents?.forEach { stateEvent -> - val ageLocalTs = stateEvent.unsignedData?.age?.let { now - it } + val ageLocalTs = stateEvent.unsignedData?.age?.let { now - it } ?: now val stateEventEntity = stateEvent.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, EventInsertType.PAGINATION) currentChunk.addStateEvent(roomId, stateEventEntity, direction) if (stateEvent.type == EventType.STATE_ROOM_MEMBER && stateEvent.stateKey != null) { @@ -155,7 +155,7 @@ internal class TokenChunkEventPersistor @Inject constructor( if (event.eventId == null || event.senderId == null) { return@forEach } - val ageLocalTs = event.unsignedData?.age?.let { now - it } + val ageLocalTs = event.unsignedData?.age?.let { now - it } ?: now val eventEntity = event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, EventInsertType.PAGINATION) if (event.type == EventType.STATE_ROOM_MEMBER && event.stateKey != null) { val contentToUse = if (direction == PaginationDirection.BACKWARDS) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt index f99fe96410..52bdf637ef 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt @@ -244,7 +244,7 @@ internal class RoomSyncHandler @Inject constructor( if (event.eventId == null || event.stateKey == null || event.type == null) { continue } - val ageLocalTs = event.unsignedData?.age?.let { syncLocalTimestampMillis - it } + val ageLocalTs = event.unsignedData?.age?.let { syncLocalTimestampMillis - it } ?: syncLocalTimestampMillis val eventEntity = event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, insertType) Timber.v("## received state event ${event.type} and key ${event.stateKey}") CurrentStateEventEntity.getOrCreate(realm, roomId, event.stateKey, event.type).apply { @@ -306,7 +306,7 @@ internal class RoomSyncHandler @Inject constructor( if (event.stateKey == null || event.type == null) { return@forEach } - val ageLocalTs = event.unsignedData?.age?.let { syncLocalTimestampMillis - it } + val ageLocalTs = event.unsignedData?.age?.let { syncLocalTimestampMillis - it } ?: syncLocalTimestampMillis val eventEntity = event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, insertType) CurrentStateEventEntity.getOrCreate(realm, roomId, event.stateKey, event.type).apply { eventId = eventEntity.eventId @@ -336,7 +336,7 @@ internal class RoomSyncHandler @Inject constructor( if (event.eventId == null || event.stateKey == null || event.type == null) { continue } - val ageLocalTs = event.unsignedData?.age?.let { syncLocalTimestampMillis - it } + val ageLocalTs = event.unsignedData?.age?.let { syncLocalTimestampMillis - it } ?: syncLocalTimestampMillis val eventEntity = event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, insertType) CurrentStateEventEntity.getOrCreate(realm, roomId, event.stateKey, event.type).apply { eventId = event.eventId @@ -348,7 +348,7 @@ internal class RoomSyncHandler @Inject constructor( if (event.eventId == null || event.senderId == null || event.type == null) { continue } - val ageLocalTs = event.unsignedData?.age?.let { syncLocalTimestampMillis - it } + val ageLocalTs = event.unsignedData?.age?.let { syncLocalTimestampMillis - it } ?: syncLocalTimestampMillis val eventEntity = event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, insertType) if (event.stateKey != null) { CurrentStateEventEntity.getOrCreate(realm, roomId, event.stateKey, event.type).apply { @@ -401,7 +401,10 @@ internal class RoomSyncHandler @Inject constructor( for (rawEvent in eventList) { // It's annoying roomId is not there, but lot of code rely on it. // And had to do it now as copy would delete all decryption results.. - val event = rawEvent.copy(roomId = roomId) + val ageLocalTs = rawEvent.unsignedData?.age?.let { syncLocalTimestampMillis - it } ?: syncLocalTimestampMillis + val event = rawEvent.copy(roomId = roomId).also { + it.ageLocalTs = ageLocalTs + } if (event.eventId == null || event.senderId == null || event.type == null) { continue } @@ -423,7 +426,6 @@ internal class RoomSyncHandler @Inject constructor( contentToInject = threadsAwarenessHandler.makeEventThreadAware(realm, roomId, event) } - val ageLocalTs = event.unsignedData?.age?.let { syncLocalTimestampMillis - it } val eventEntity = event.toEntity(roomId, SendState.SYNCED, ageLocalTs, contentToInject).copyToRealmOrIgnore(realm, insertType) if (event.stateKey != null) { CurrentStateEventEntity.getOrCreate(realm, roomId, event.stateKey, event.type).apply { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ThreadsAwarenessHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ThreadsAwarenessHandler.kt index 8c7557a5b8..70553359ff 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ThreadsAwarenessHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ThreadsAwarenessHandler.kt @@ -53,6 +53,7 @@ import org.matrix.android.sdk.internal.session.permalinks.PermalinkFactory import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory import org.matrix.android.sdk.internal.session.room.timeline.GetEventTask import org.matrix.android.sdk.internal.util.awaitTransaction +import org.matrix.android.sdk.internal.util.time.Clock import javax.inject.Inject /** @@ -64,7 +65,8 @@ internal class ThreadsAwarenessHandler @Inject constructor( private val permalinkFactory: PermalinkFactory, @SessionDatabase private val monarchy: Monarchy, private val lightweightSettingsStorage: LightweightSettingsStorage, - private val getEventTask: GetEventTask + private val getEventTask: GetEventTask, + private val clock: Clock, ) { // This caching is responsible to improve the performance when we receive a root event @@ -120,7 +122,7 @@ internal class ThreadsAwarenessHandler @Inject constructor( private suspend fun fetchThreadsEvents(threadsToFetch: Map) { val eventEntityList = threadsToFetch.mapNotNull { (eventId, roomId) -> fetchEvent(eventId, roomId)?.let { - it.toEntity(roomId, SendState.SYNCED, it.ageLocalTs) + it.toEntity(roomId, SendState.SYNCED, it.ageLocalTs ?: clock.epochMillis()) } } From 142c87314ca4754f59888016d52589f4bf1c59a1 Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 16 Jun 2022 16:44:30 +0200 Subject: [PATCH 2/5] show option to accept other verif not ready --- .../crypto/verification/VerificationAction.kt | 2 ++ .../VerificationBottomSheetViewModel.kt | 21 +++++++++++++++++++ .../VerificationChooseMethodController.kt | 17 +++++++++++++++ .../VerificationChooseMethodFragment.kt | 12 +++++++++++ .../VerificationChooseMethodViewModel.kt | 6 ++++-- 5 files changed, 56 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationAction.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationAction.kt index c4ae2d278b..1b18117cf3 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationAction.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationAction.kt @@ -30,6 +30,8 @@ sealed class VerificationAction : VectorViewModelAction { data class GotItConclusion(val verified: Boolean) : VerificationAction() object SkipVerification : VerificationAction() object VerifyFromPassphrase : VerificationAction() + object ReadyPendingVerification : VerificationAction() + object CancelPendingVerification : VerificationAction() data class GotResultFromSsss(val cypherData: String, val alias: String) : VerificationAction() object CancelledFromSsss : VerificationAction() object SecuredStorageHasBeenReset : VerificationAction() diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt index 46f7adb911..b8146b8041 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt @@ -360,6 +360,27 @@ class VerificationBottomSheetViewModel @AssistedInject constructor( as? SasVerificationTransaction) ?.shortCodeDoesNotMatch() } + is VerificationAction.ReadyPendingVerification -> { + state.pendingRequest.invoke()?.let { request -> + // will only be there for dm verif + if (state.roomId != null) { + session.cryptoService().verificationService() + .readyPendingVerificationInDMs( + supportedVerificationMethodsProvider.provide(), + state.otherUserId, + state.roomId, + request.transactionId ?: "" + ) + } + } + } + is VerificationAction.CancelPendingVerification -> { + state.pendingRequest.invoke()?.let { + session.cryptoService().verificationService() + .cancelVerificationRequest(it) + } + _viewEvents.post(VerificationBottomSheetViewEvents.Dismiss) + } is VerificationAction.GotItConclusion -> { if (state.isVerificationRequired && !action.verified) { // we should go back to first screen diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/choose/VerificationChooseMethodController.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/choose/VerificationChooseMethodController.kt index acc8cf61b9..85dbd7d462 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/choose/VerificationChooseMethodController.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/choose/VerificationChooseMethodController.kt @@ -21,6 +21,7 @@ import im.vector.app.R import im.vector.app.core.epoxy.bottomSheetDividerItem import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.StringProvider +import im.vector.app.core.ui.list.genericButtonItem import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationActionItem import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationNoticeItem import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationQrCodeItem @@ -108,6 +109,20 @@ class VerificationChooseMethodController @Inject constructor( iconColor(host.colorProvider.getColorFromAttribute(R.attr.vctr_content_primary)) listener { host.listener?.doVerifyBySas() } } + } else if (!state.isReadied) { + // a bit of a special case, if you tapped on the timeline cell but not on a button + genericButtonItem { + id("accept_request") + textColor(host.colorProvider.getColorFromAttribute(R.attr.colorPrimary)) + text(host.stringProvider.getString(R.string.action_accept)) + buttonClickAction { host.listener?.acceptRequest() } + } + genericButtonItem { + id("decline_request") + textColor(host.colorProvider.getColorFromAttribute(R.attr.colorError)) + text(host.stringProvider.getString(R.string.action_decline)) + buttonClickAction { host.listener?.declineRequest() } + } } if (state.isMe && state.canCrossSign) { @@ -131,5 +146,7 @@ class VerificationChooseMethodController @Inject constructor( fun openCamera() fun doVerifyBySas() fun onClickOnWasNotMe() + fun acceptRequest() + fun declineRequest() } } diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/choose/VerificationChooseMethodFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/choose/VerificationChooseMethodFragment.kt index cf6bcc58c0..f8f2145406 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/choose/VerificationChooseMethodFragment.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/choose/VerificationChooseMethodFragment.kt @@ -100,6 +100,18 @@ class VerificationChooseMethodFragment @Inject constructor( sharedViewModel.itWasNotMe() } + override fun acceptRequest() { + withState(viewModel) { + sharedViewModel.handle(VerificationAction.ReadyPendingVerification) + } + } + + override fun declineRequest() { + withState(viewModel) { + sharedViewModel.handle(VerificationAction.CancelPendingVerification) + } + } + private fun doOpenQRCodeScanner() { QrCodeScannerActivity.startForResult(requireActivity(), scanActivityResultLauncher) } diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/choose/VerificationChooseMethodViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/choose/VerificationChooseMethodViewModel.kt index a1f902f8f4..dec0a773df 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/choose/VerificationChooseMethodViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/choose/VerificationChooseMethodViewModel.kt @@ -44,7 +44,8 @@ data class VerificationChooseMethodViewState( val qrCodeText: String? = null, val sasModeAvailable: Boolean = false, val isMe: Boolean = false, - val canCrossSign: Boolean = false + val canCrossSign: Boolean = false, + val isReadied: Boolean = false ) : MavericksState class VerificationChooseMethodViewModel @AssistedInject constructor( @@ -81,7 +82,8 @@ class VerificationChooseMethodViewModel @AssistedInject constructor( copy( otherCanShowQrCode = pvr?.otherCanShowQrCode().orFalse(), otherCanScanQrCode = pvr?.otherCanScanQrCode().orFalse(), - sasModeAvailable = pvr?.isSasSupported().orFalse() + sasModeAvailable = pvr?.isSasSupported().orFalse(), + isReadied = pvr?.isReady ?: false, ) } } From c4c62acdaaae1163ca363249fe77f177e83a8197 Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 17 Jun 2022 10:49:56 +0200 Subject: [PATCH 3/5] Add change log --- changelog.d/6328.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/6328.bugfix diff --git a/changelog.d/6328.bugfix b/changelog.d/6328.bugfix new file mode 100644 index 0000000000..7a41996e57 --- /dev/null +++ b/changelog.d/6328.bugfix @@ -0,0 +1 @@ +Fix | Some user verification requests couldn't be accepted/declined From 9929d6a4ebf2655139b27625bc321d314911f602 Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 27 Jun 2022 10:13:18 +0200 Subject: [PATCH 4/5] Update button design --- .../ButtonPositiveDestructiveButtonBarItem.kt | 59 +++++++++++++++++++ .../VerificationChooseMethodController.kt | 19 +++--- .../item_positive_destrutive_buttons.xml | 25 ++++++++ 3 files changed, 91 insertions(+), 12 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/core/ui/list/ButtonPositiveDestructiveButtonBarItem.kt create mode 100644 vector/src/main/res/layout/item_positive_destrutive_buttons.xml diff --git a/vector/src/main/java/im/vector/app/core/ui/list/ButtonPositiveDestructiveButtonBarItem.kt b/vector/src/main/java/im/vector/app/core/ui/list/ButtonPositiveDestructiveButtonBarItem.kt new file mode 100644 index 0000000000..95c1a4457d --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/ui/list/ButtonPositiveDestructiveButtonBarItem.kt @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2020 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.app.core.ui.list + +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import com.google.android.material.button.MaterialButton +import im.vector.app.R +import im.vector.app.core.epoxy.ClickListener +import im.vector.app.core.epoxy.VectorEpoxyHolder +import im.vector.app.core.epoxy.VectorEpoxyModel +import im.vector.app.core.epoxy.onClick +import im.vector.lib.core.utils.epoxy.charsequence.EpoxyCharSequence + +/** + * A generic button list item. + */ +@EpoxyModelClass(layout = R.layout.item_positive_destrutive_buttons) +abstract class ButtonPositiveDestructiveButtonBarItem : VectorEpoxyModel() { + + @EpoxyAttribute + var positiveText: EpoxyCharSequence? = null + + @EpoxyAttribute + var destructiveText: EpoxyCharSequence? = null + + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) + var positiveButtonClickAction: ClickListener? = null + + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) + var destructiveButtonClickAction: ClickListener? = null + + override fun bind(holder: Holder) { + super.bind(holder) + positiveText?.charSequence?.let { holder.positiveButton.text = it } + destructiveText?.charSequence?.let { holder.destructiveButton.text = it } + + holder.positiveButton.onClick(positiveButtonClickAction) + holder.destructiveButton.onClick(destructiveButtonClickAction) + } + + class Holder : VectorEpoxyHolder() { + val destructiveButton by bind(R.id.destructive_button) + val positiveButton by bind(R.id.positive_button) + } +} diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/choose/VerificationChooseMethodController.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/choose/VerificationChooseMethodController.kt index 85dbd7d462..d8fb5b81c4 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/choose/VerificationChooseMethodController.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/choose/VerificationChooseMethodController.kt @@ -21,7 +21,7 @@ import im.vector.app.R import im.vector.app.core.epoxy.bottomSheetDividerItem import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.StringProvider -import im.vector.app.core.ui.list.genericButtonItem +import im.vector.app.core.ui.list.buttonPositiveDestructiveButtonBarItem import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationActionItem import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationNoticeItem import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationQrCodeItem @@ -111,17 +111,12 @@ class VerificationChooseMethodController @Inject constructor( } } else if (!state.isReadied) { // a bit of a special case, if you tapped on the timeline cell but not on a button - genericButtonItem { - id("accept_request") - textColor(host.colorProvider.getColorFromAttribute(R.attr.colorPrimary)) - text(host.stringProvider.getString(R.string.action_accept)) - buttonClickAction { host.listener?.acceptRequest() } - } - genericButtonItem { - id("decline_request") - textColor(host.colorProvider.getColorFromAttribute(R.attr.colorError)) - text(host.stringProvider.getString(R.string.action_decline)) - buttonClickAction { host.listener?.declineRequest() } + buttonPositiveDestructiveButtonBarItem { + id("accept_decline") + positiveText(host.stringProvider.getString(R.string.action_accept).toEpoxyCharSequence()) + destructiveText(host.stringProvider.getString(R.string.action_decline).toEpoxyCharSequence()) + positiveButtonClickAction { host.listener?.acceptRequest() } + destructiveButtonClickAction { host.listener?.declineRequest() } } } diff --git a/vector/src/main/res/layout/item_positive_destrutive_buttons.xml b/vector/src/main/res/layout/item_positive_destrutive_buttons.xml new file mode 100644 index 0000000000..1e0ab34458 --- /dev/null +++ b/vector/src/main/res/layout/item_positive_destrutive_buttons.xml @@ -0,0 +1,25 @@ + + + +