From 8efd389a3cf5f2686331fafea171a35fcd53fce5 Mon Sep 17 00:00:00 2001 From: SpiritCroc Date: Wed, 9 Mar 2022 13:40:05 +0100 Subject: [PATCH 001/565] Fix a case of missing read markers Case: bottom-most loaded event has read marker, but there are messages below it that haven't been loaded yet. --- .../app/features/home/room/detail/TimelineViewModel.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt index 3bdcbc6529..07c25ce326 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt @@ -1120,6 +1120,11 @@ class TimelineViewModel @AssistedInject constructor( } else { UnreadState.Unknown } + // If the read marker is at the bottom-most event, this doesn't mean we read all, in case we just haven't loaded more events. + // Avoid incorrectly returning HasNoUnread in this case. + if (firstDisplayableEventIndex == 0 && timeline.hasMoreToLoad(Timeline.Direction.FORWARDS)) { + return UnreadState.Unknown + } for (i in (firstDisplayableEventIndex - 1) downTo 0) { val timelineEvent = events.getOrNull(i) ?: return UnreadState.Unknown val eventId = timelineEvent.root.eventId ?: return UnreadState.Unknown From 1206c31e3ab01501b02953b28140bd25c65017ee Mon Sep 17 00:00:00 2001 From: SpiritCroc Date: Wed, 9 Mar 2022 13:43:52 +0100 Subject: [PATCH 002/565] Fix another case of missing read markers HasUnread might not be correct on the first try while loading the timeline. --- .../vector/app/features/home/room/detail/TimelineViewModel.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt index 07c25ce326..78e3469a58 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt @@ -1099,9 +1099,11 @@ class TimelineViewModel @AssistedInject constructor( computeUnreadState(timelineEvents, roomSummary) } // We don't want live update of unread so we skip when we already had a HasUnread or HasNoUnread + // However, we want to update an existing HasUnread, as we might get additional information during loading of events. .distinctUntilChanged { previous, current -> when { previous is UnreadState.Unknown || previous is UnreadState.ReadMarkerNotLoaded -> false + previous is UnreadState.HasUnread && current is UnreadState.HasUnread -> false current is UnreadState.HasUnread || current is UnreadState.HasNoUnread -> true else -> false } From 884ae1cedd8144e470a1055019d55a4c9eeb7c85 Mon Sep 17 00:00:00 2001 From: SpiritCroc Date: Wed, 9 Mar 2022 13:50:49 +0100 Subject: [PATCH 003/565] Add changelog.d/5475.bugfix --- changelog.d/5475.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/5475.bugfix diff --git a/changelog.d/5475.bugfix b/changelog.d/5475.bugfix new file mode 100644 index 0000000000..03364f6a73 --- /dev/null +++ b/changelog.d/5475.bugfix @@ -0,0 +1 @@ +Fix some cases where the read marker line would not show up From 6c4e404ba135eb755235e33d5196695dd3e55719 Mon Sep 17 00:00:00 2001 From: SpiritCroc Date: Wed, 9 Mar 2022 17:56:02 +0100 Subject: [PATCH 004/565] Fix updating unread marker if not to latest chunk SetReadMarkerTask was not updating the read marker when both the old and the new fully read eventId weren't in the last chunk, even when the new one was after the first one. --- changelog.d/5481.bugfix | 1 + .../matrix/android/sdk/internal/database/query/ReadQueries.kt | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 changelog.d/5481.bugfix diff --git a/changelog.d/5481.bugfix b/changelog.d/5481.bugfix new file mode 100644 index 0000000000..64891b503c --- /dev/null +++ b/changelog.d/5481.bugfix @@ -0,0 +1 @@ +Fix sometimes read marker not properly updating diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ReadQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ReadQueries.kt index 8cc99c3d2f..cb98029aeb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ReadQueries.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ReadQueries.kt @@ -94,7 +94,7 @@ internal fun isReadMarkerMoreRecent(realmConfiguration: RealmConfiguration, val eventToCheckIndex = eventToCheck?.displayIndex ?: Int.MAX_VALUE eventToCheckIndex <= readMarkerIndex } else { - eventToCheckChunk?.isLastForward == false + eventToCheckChunk?.isLastForward == false && readMarkerChunk?.isLastForward == true } } } From 0564942b0cdf7699421cc9ba9583a9460d8bcaa1 Mon Sep 17 00:00:00 2001 From: SpiritCroc Date: Thu, 10 Mar 2022 20:22:38 +0100 Subject: [PATCH 005/565] isReadMarkerMoreRecent(): use helper to properly compare chunks --- .../matrix/android/sdk/internal/database/query/ReadQueries.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ReadQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ReadQueries.kt index cb98029aeb..6c587dfcae 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ReadQueries.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ReadQueries.kt @@ -94,7 +94,7 @@ internal fun isReadMarkerMoreRecent(realmConfiguration: RealmConfiguration, val eventToCheckIndex = eventToCheck?.displayIndex ?: Int.MAX_VALUE eventToCheckIndex <= readMarkerIndex } else { - eventToCheckChunk?.isLastForward == false && readMarkerChunk?.isLastForward == true + eventToCheckChunk != null && readMarkerChunk?.isMoreRecentThan(eventToCheckChunk) == true } } } From 6ba02629ec461c949d9e7400a630af703d82e6a8 Mon Sep 17 00:00:00 2001 From: SpiritCroc Date: Fri, 11 Mar 2022 11:22:26 +0100 Subject: [PATCH 006/565] Fix ChunkEntity.isMoreRecentThan() if both chunks linked to last forward Imagine scenario: [this] -> [chunkToCheck] -> [lastForwardChunk] Then, both `isLastForward` checks will not return, and also the `chunkToCheck.doesNextChunksVerifyCondition { it == this }` will return false. Since both chunks are connected to the last forward chunk, `isMoreRecent()` will still return `true`, which is wrong in this case. So do not only check if chunkToCheck has this as any of the next chunks, but also the other way round. --- .../android/sdk/internal/database/helper/ChunkEntityHelper.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ChunkEntityHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ChunkEntityHelper.kt index 289db9fa15..8bec2a443c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ChunkEntityHelper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ChunkEntityHelper.kt @@ -223,6 +223,9 @@ internal fun ChunkEntity.isMoreRecentThan(chunkToCheck: ChunkEntity): Boolean { if (chunkToCheck.doesNextChunksVerifyCondition { it == this }) { return true } + if (this.doesNextChunksVerifyCondition { it == chunkToCheck }) { + return false + } // Otherwise check if this chunk is linked to last forward if (this.doesNextChunksVerifyCondition { it.isLastForward }) { return true From 84bd205014b3e9d46b4804f693d68af990705c69 Mon Sep 17 00:00:00 2001 From: NIkita Fedrunov Date: Mon, 14 Mar 2022 09:34:28 +0100 Subject: [PATCH 007/565] "Add space" copy is replaced with "create space" in left sliding panel --- changelog.d/5516.misc | 1 + vector/src/main/res/layout/item_space_add.xml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 changelog.d/5516.misc diff --git a/changelog.d/5516.misc b/changelog.d/5516.misc new file mode 100644 index 0000000000..0b925fcdcd --- /dev/null +++ b/changelog.d/5516.misc @@ -0,0 +1 @@ +"Add space" copy is replaced with "create space" in left sliding panel \ No newline at end of file diff --git a/vector/src/main/res/layout/item_space_add.xml b/vector/src/main/res/layout/item_space_add.xml index e723753adf..a329ac760b 100644 --- a/vector/src/main/res/layout/item_space_add.xml +++ b/vector/src/main/res/layout/item_space_add.xml @@ -36,7 +36,7 @@ android:layout_marginEnd="@dimen/layout_horizontal_margin" android:ellipsize="end" android:maxLines="1" - android:text="@string/add_space" + android:text="@string/create_space" android:textColor="?colorPrimary" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" From 38f3b88395199317b434ca92f9a4ea59c59258f8 Mon Sep 17 00:00:00 2001 From: ericdecanini Date: Tue, 15 Mar 2022 15:30:02 +0100 Subject: [PATCH 008/565] Adds debug test to check roomSummary for voice call button --- .../features/home/room/detail/RoomDetailViewState.kt | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt index e2b97b0900..4b3fdc4c43 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt @@ -31,6 +31,7 @@ import org.matrix.android.sdk.api.session.sync.SyncState import org.matrix.android.sdk.api.session.threads.ThreadNotificationBadgeState import org.matrix.android.sdk.api.session.widgets.model.Widget import org.matrix.android.sdk.api.session.widgets.model.WidgetType +import timber.log.Timber sealed class UnreadState { object Unknown : UnreadState() @@ -87,7 +88,15 @@ data class RoomDetailViewState( rootThreadEventId = args.threadTimelineArgs?.rootThreadEventId ) - fun isWebRTCCallOptionAvailable() = (asyncRoomSummary.invoke()?.joinedMembersCount ?: 0) <= 2 + // TODO: Add condition here to check that room is DM-based group chat and not group room + fun isWebRTCCallOptionAvailable() = asyncRoomSummary.invoke()?.let { roomSummary -> + val joinedMembersCount = roomSummary.joinedMembersCount ?: 0 + val isDirect = roomSummary.isDirect + Timber.d("WebRTCTest roomSummary: $roomSummary") + Timber.d("WebRTCTest joined members: $joinedMembersCount") + Timber.d("WebRTCTest isDirect: $isDirect") + joinedMembersCount <= 2 + } fun isSearchAvailable() = asyncRoomSummary()?.isEncrypted == false From d74d569f4b9bdccd837272c7cb8b58e2bcd5b6e3 Mon Sep 17 00:00:00 2001 From: ericdecanini Date: Tue, 15 Mar 2022 18:15:46 +0100 Subject: [PATCH 009/565] Adds back default to true on isWebRTCCallOptionAvailable --- .../vector/app/features/home/room/detail/RoomDetailViewState.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt index 4b3fdc4c43..b05cf294cd 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt @@ -96,7 +96,7 @@ data class RoomDetailViewState( Timber.d("WebRTCTest joined members: $joinedMembersCount") Timber.d("WebRTCTest isDirect: $isDirect") joinedMembersCount <= 2 - } + } ?: true // if room summary cannot be found, assume call option should be available fun isSearchAvailable() = asyncRoomSummary()?.isEncrypted == false From 82ead4f3f5756fd7adc60405dab9f9e0db011310 Mon Sep 17 00:00:00 2001 From: ericdecanini Date: Tue, 15 Mar 2022 19:09:19 +0100 Subject: [PATCH 010/565] Replaces call option condition with isDirect --- .../features/home/room/detail/RoomDetailViewState.kt | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt index b05cf294cd..87eae1bc2d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt @@ -88,15 +88,7 @@ data class RoomDetailViewState( rootThreadEventId = args.threadTimelineArgs?.rootThreadEventId ) - // TODO: Add condition here to check that room is DM-based group chat and not group room - fun isWebRTCCallOptionAvailable() = asyncRoomSummary.invoke()?.let { roomSummary -> - val joinedMembersCount = roomSummary.joinedMembersCount ?: 0 - val isDirect = roomSummary.isDirect - Timber.d("WebRTCTest roomSummary: $roomSummary") - Timber.d("WebRTCTest joined members: $joinedMembersCount") - Timber.d("WebRTCTest isDirect: $isDirect") - joinedMembersCount <= 2 - } ?: true // if room summary cannot be found, assume call option should be available + fun isWebRTCCallOptionAvailable() = asyncRoomSummary.invoke()?.isDirect ?: true fun isSearchAvailable() = asyncRoomSummary()?.isEncrypted == false From 5d496d5f3d3ec1302e76827b98e13034805c7c42 Mon Sep 17 00:00:00 2001 From: ericdecanini Date: Tue, 15 Mar 2022 19:16:04 +0100 Subject: [PATCH 011/565] Adds changelog file --- changelog.d/5548.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/5548.bugfix diff --git a/changelog.d/5548.bugfix b/changelog.d/5548.bugfix new file mode 100644 index 0000000000..ccf07a7ee3 --- /dev/null +++ b/changelog.d/5548.bugfix @@ -0,0 +1 @@ +Fixes voice call button disappearing in DM rooms with more than 2 members From f722b2eb85182ea9dffbaaaf2a735c453d0ad2c8 Mon Sep 17 00:00:00 2001 From: ericdecanini Date: Tue, 15 Mar 2022 19:16:22 +0100 Subject: [PATCH 012/565] Removes unused import --- .../vector/app/features/home/room/detail/RoomDetailViewState.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt index 87eae1bc2d..84e618a8fe 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt @@ -31,7 +31,6 @@ import org.matrix.android.sdk.api.session.sync.SyncState import org.matrix.android.sdk.api.session.threads.ThreadNotificationBadgeState import org.matrix.android.sdk.api.session.widgets.model.Widget import org.matrix.android.sdk.api.session.widgets.model.WidgetType -import timber.log.Timber sealed class UnreadState { object Unknown : UnreadState() From 682f4c35d2956efe1e1827ee6834466494ca4b71 Mon Sep 17 00:00:00 2001 From: SpiritCroc Date: Wed, 16 Mar 2022 13:39:19 +0100 Subject: [PATCH 013/565] Fix endless loading timeline due to conflicting chunks --- changelog.d/5554.bugfix | 1 + .../database/query/ChunkEntityQueries.kt | 11 ++++++++++ .../room/timeline/TokenChunkEventPersistor.kt | 22 ++++++++++++++++++- 3 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 changelog.d/5554.bugfix diff --git a/changelog.d/5554.bugfix b/changelog.d/5554.bugfix new file mode 100644 index 0000000000..ee69f0dbfe --- /dev/null +++ b/changelog.d/5554.bugfix @@ -0,0 +1 @@ +Fix sometimes endless loading timeline diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ChunkEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ChunkEntityQueries.kt index ece46555a7..a33ba82f7a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ChunkEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ChunkEntityQueries.kt @@ -40,6 +40,17 @@ internal fun ChunkEntity.Companion.find(realm: Realm, roomId: String, prevToken: return query.findFirst() } +internal fun ChunkEntity.Companion.findAll(realm: Realm, roomId: String, prevToken: String? = null, nextToken: String? = null): RealmResults? { + val query = where(realm, roomId) + if (prevToken != null) { + query.equalTo(ChunkEntityFields.PREV_TOKEN, prevToken) + } + if (nextToken != null) { + query.equalTo(ChunkEntityFields.NEXT_TOKEN, nextToken) + } + return query.findAll() +} + internal fun ChunkEntity.Companion.findLastForwardChunkOfRoom(realm: Realm, roomId: String): ChunkEntity? { return where(realm, roomId) .equalTo(ChunkEntityFields.IS_LAST_FORWARD, true) 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 63383a99b3..874915a6f0 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 @@ -38,6 +38,7 @@ import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields import org.matrix.android.sdk.internal.database.query.copyToRealmOrIgnore import org.matrix.android.sdk.internal.database.query.create import org.matrix.android.sdk.internal.database.query.find +import org.matrix.android.sdk.internal.database.query.findAll import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.UserId @@ -80,7 +81,26 @@ internal class TokenChunkEventPersistor @Inject constructor( val existingChunk = ChunkEntity.find(realm, roomId, prevToken = prevToken, nextToken = nextToken) if (existingChunk != null) { - Timber.v("This chunk is already in the db, returns") + Timber.v("This chunk is already in the db, checking if this might be caused by broken links") + if (direction == PaginationDirection.FORWARDS) { + val prevChunks = ChunkEntity.findAll(realm, roomId, nextToken = prevToken) + Timber.v("Found ${prevChunks?.size} prevChunks") + prevChunks?.forEach { + if (it.nextChunk != existingChunk) { + Timber.i("Set nextChunk for ${it.identifier()} from ${it.nextChunk?.identifier()} to ${existingChunk.identifier()}") + it.nextChunk = existingChunk + } + } + } else { + val nextChunks = ChunkEntity.findAll(realm, roomId, prevToken = nextToken) + Timber.v("Found ${nextChunks?.size} nextChunks") + nextChunks?.forEach { + if (it.prevChunk != existingChunk) { + Timber.i("Set prevChunk for ${it.identifier()} from ${it.prevChunk?.identifier()} to ${existingChunk.identifier()}") + it.prevChunk = existingChunk + } + } + } return@awaitTransaction } val prevChunk = ChunkEntity.find(realm, roomId, nextToken = prevToken) From 00bced95000b80b7d5e8d01f18c298be9a0ee64c Mon Sep 17 00:00:00 2001 From: ericdecanini Date: Thu, 17 Mar 2022 16:40:35 +0100 Subject: [PATCH 014/565] Changes unTrack to untrack --- .../im/vector/app/features/home/room/detail/TimelineFragment.kt | 2 +- .../room/detail/timeline/helper/VoiceMessagePlaybackTracker.kt | 2 +- .../features/home/room/detail/timeline/item/MessageVoiceItem.kt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt index 4c53c32d57..c2ab12595f 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt @@ -1244,7 +1244,7 @@ class TimelineFragment @Inject constructor( override fun onPause() { super.onPause() notificationDrawerManager.setCurrentRoom(null) - voiceMessagePlaybackTracker.unTrack(VoiceMessagePlaybackTracker.RECORDING_ID) + voiceMessagePlaybackTracker.untrack(VoiceMessagePlaybackTracker.RECORDING_ID) if (withState(messageComposerViewModel) { it.isVoiceRecording } && requireActivity().isChangingConfigurations) { // we're rotating, maintain any active recordings diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/VoiceMessagePlaybackTracker.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/VoiceMessagePlaybackTracker.kt index c6204bff1c..c033af1ef5 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/VoiceMessagePlaybackTracker.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/VoiceMessagePlaybackTracker.kt @@ -46,7 +46,7 @@ class VoiceMessagePlaybackTracker @Inject constructor() { } } - fun unTrack(id: String) { + fun untrack(id: String) { listeners.remove(id) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt index e9f728d976..7a7fd67209 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt @@ -128,7 +128,7 @@ abstract class MessageVoiceItem : AbsMessageItem() { super.unbind(holder) contentUploadStateTrackerBinder.unbind(attributes.informationData.eventId) contentDownloadStateTrackerBinder.unbind(mxcUrl) - voiceMessagePlaybackTracker.unTrack(attributes.informationData.eventId) + voiceMessagePlaybackTracker.untrack(attributes.informationData.eventId) } override fun getViewStubId() = STUB_ID From cde10ca0325322ee351b540d99c4657a6a4f6476 Mon Sep 17 00:00:00 2001 From: ericdecanini Date: Thu, 17 Mar 2022 17:58:11 +0100 Subject: [PATCH 015/565] Fixes wrong spellings in Message file and voice items --- .../room/detail/timeline/item/MessageFileItem.kt | 8 ++++---- .../room/detail/timeline/item/MessageVoiceItem.kt | 13 +++++++------ 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageFileItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageFileItem.kt index 8b6899daee..17d4a87ac6 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageFileItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageFileItem.kt @@ -51,10 +51,10 @@ abstract class MessageFileItem : AbsMessageItem() { // var clickListener: ClickListener? = null @EpoxyAttribute - var izLocalFile = false + var isLocalFile = false @EpoxyAttribute - var izDownloaded = false + var isDownloaded = false @EpoxyAttribute lateinit var contentUploadStateTrackerBinder: ContentUploadStateTrackerBinder @@ -66,7 +66,7 @@ abstract class MessageFileItem : AbsMessageItem() { super.bind(holder) renderSendState(holder.fileLayout, holder.filenameView) if (!attributes.informationData.sendState.hasFailed()) { - contentUploadStateTrackerBinder.bind(attributes.informationData.eventId, izLocalFile, holder.progressLayout) + contentUploadStateTrackerBinder.bind(attributes.informationData.eventId, isLocalFile, holder.progressLayout) } else { holder.fileImageView.setImageResource(R.drawable.ic_cross) holder.progressLayout.isVisible = false @@ -75,7 +75,7 @@ abstract class MessageFileItem : AbsMessageItem() { if (attributes.informationData.sendState.isSending()) { holder.fileImageView.setImageResource(iconRes) } else { - if (izDownloaded) { + if (isDownloaded) { holder.fileImageView.setImageResource(iconRes) holder.fileDownloadProgress.progress = 0 } else { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt index 7a7fd67209..c44ddcd843 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt @@ -48,10 +48,10 @@ abstract class MessageVoiceItem : AbsMessageItem() { var waveform: List = emptyList() @EpoxyAttribute - var izLocalFile = false + var isLocalFile = false @EpoxyAttribute - var izDownloaded = false + var isDownloaded = false @EpoxyAttribute lateinit var contentUploadStateTrackerBinder: ContentUploadStateTrackerBinder @@ -69,7 +69,7 @@ abstract class MessageVoiceItem : AbsMessageItem() { super.bind(holder) renderSendState(holder.voiceLayout, null) if (!attributes.informationData.sendState.hasFailed()) { - contentUploadStateTrackerBinder.bind(attributes.informationData.eventId, izLocalFile, holder.progressLayout) + contentUploadStateTrackerBinder.bind(attributes.informationData.eventId, isLocalFile, holder.progressLayout) } else { holder.voicePlaybackControlButton.setImageResource(R.drawable.ic_cross) holder.voicePlaybackControlButton.contentDescription = holder.view.context.getString(R.string.error_voice_message_unable_to_play) @@ -85,11 +85,11 @@ abstract class MessageVoiceItem : AbsMessageItem() { } } - val backgroundTint = if (attributes.informationData.messageLayout is TimelineMessageLayout.Bubble) { + val backgroundTint = if (attributes.informationData.messageLayout is TimelineMessageLayout.Bubble) Color.TRANSPARENT - } else { + else ThemeUtils.getColor(holder.view.context, R.attr.vctr_content_quinary) - } + holder.voicePlaybackLayout.backgroundTintList = ColorStateList.valueOf(backgroundTint) holder.voicePlaybackControlButton.setOnClickListener { playbackControlButtonClickListener?.invoke(it) } @@ -99,6 +99,7 @@ abstract class MessageVoiceItem : AbsMessageItem() { is VoiceMessagePlaybackTracker.Listener.State.Idle -> renderIdleState(holder) is VoiceMessagePlaybackTracker.Listener.State.Playing -> renderPlayingState(holder, state) is VoiceMessagePlaybackTracker.Listener.State.Paused -> renderPausedState(holder, state) + is VoiceMessagePlaybackTracker.Listener.State.Recording -> Unit } } }) From 6878a973ed48056b4982cea5232ab129f7a49810 Mon Sep 17 00:00:00 2001 From: SpiritCroc Date: Fri, 18 Mar 2022 07:59:32 +0100 Subject: [PATCH 016/565] TokenChunkEventPersistor: always link all matching chunks The previous fix only works around the issue when it is detected. This may require re-entering the room once when it gets stuck. If we ensure proper linking from the beginning, hopefully we don't run into any issues at all. --- .../session/room/timeline/TokenChunkEventPersistor.kt | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) 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 874915a6f0..7aceeb4a49 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 @@ -109,8 +109,14 @@ internal class TokenChunkEventPersistor @Inject constructor( this.nextChunk = nextChunk this.prevChunk = prevChunk } - nextChunk?.prevChunk = currentChunk - prevChunk?.nextChunk = currentChunk + val allNextChunks = ChunkEntity.findAll(realm, roomId, prevToken = nextToken) + val allPrevChunks = ChunkEntity.findAll(realm, roomId, nextToken = prevToken) + allNextChunks?.forEach { + it.prevChunk = currentChunk + } + allPrevChunks?.forEach { + it.nextChunk = currentChunk + } if (receivedChunk.events.isEmpty() && !receivedChunk.hasMore()) { handleReachEnd(roomId, direction, currentChunk) } else { From 26176328c460086dd3d1fb7179c20f209aa1e501 Mon Sep 17 00:00:00 2001 From: ericdecanini Date: Fri, 18 Mar 2022 11:58:23 +0100 Subject: [PATCH 017/565] Fixes epoxy errors with renamed epoxy attributes --- .../detail/timeline/factory/MessageItemFactory.kt | 12 ++++++------ .../room/detail/timeline/item/MessageFileItem.kt | 2 ++ .../room/detail/timeline/item/MessageVoiceItem.kt | 2 ++ 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt index 3189954e20..e70da45216 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -326,8 +326,8 @@ class MessageItemFactory @Inject constructor( } ?: "" return MessageFileItem_() .attributes(attributes) - .izLocalFile(localFilesHelper.isLocalFile(fileUrl)) - .izDownloaded(session.fileService().isFileInCache( + .isLocalFile(localFilesHelper.isLocalFile(fileUrl)) + .isDownloaded(session.fileService().isFileInCache( fileUrl, messageContent.getFileName(), messageContent.mimeType, @@ -368,8 +368,8 @@ class MessageItemFactory @Inject constructor( .waveform(messageContent.audioWaveformInfo?.waveform?.toFft().orEmpty()) .playbackControlButtonClickListener(playbackControlButtonClickListener) .voiceMessagePlaybackTracker(voiceMessagePlaybackTracker) - .izLocalFile(localFilesHelper.isLocalFile(fileUrl)) - .izDownloaded(session.fileService().isFileInCache( + .isLocalFile(localFilesHelper.isLocalFile(fileUrl)) + .isDownloaded(session.fileService().isFileInCache( fileUrl, messageContent.getFileName(), messageContent.mimeType, @@ -431,8 +431,8 @@ class MessageItemFactory @Inject constructor( return MessageFileItem_() .attributes(attributes) .leftGuideline(avatarSizeProvider.leftGuideline) - .izLocalFile(localFilesHelper.isLocalFile(messageContent.getFileUrl())) - .izDownloaded(session.fileService().isFileInCache(messageContent)) + .isLocalFile(localFilesHelper.isLocalFile(messageContent.getFileUrl())) + .isDownloaded(session.fileService().isFileInCache(messageContent)) .mxcUrl(mxcUrl) .contentUploadStateTrackerBinder(contentUploadStateTrackerBinder) .contentDownloadStateTrackerBinder(contentDownloadStateTrackerBinder) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageFileItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageFileItem.kt index 17d4a87ac6..dc39fbfdd7 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageFileItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageFileItem.kt @@ -51,9 +51,11 @@ abstract class MessageFileItem : AbsMessageItem() { // var clickListener: ClickListener? = null @EpoxyAttribute + @JvmField var isLocalFile = false @EpoxyAttribute + @JvmField var isDownloaded = false @EpoxyAttribute diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt index c44ddcd843..6e176a8fa2 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt @@ -48,9 +48,11 @@ abstract class MessageVoiceItem : AbsMessageItem() { var waveform: List = emptyList() @EpoxyAttribute + @JvmField var isLocalFile = false @EpoxyAttribute + @JvmField var isDownloaded = false @EpoxyAttribute From d54b465b30994b9a16de7aa2a690d83d81c9a70e Mon Sep 17 00:00:00 2001 From: ericdecanini Date: Fri, 18 Mar 2022 13:22:49 +0100 Subject: [PATCH 018/565] Uses messageVoiceItem for audio files --- .../timeline/factory/MessageItemFactory.kt | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt index e70da45216..9daec9a4c9 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -192,7 +192,7 @@ class MessageItemFactory @Inject constructor( if (messageContent.voiceMessageIndicator != null) { buildVoiceMessageItem(params, messageContent, informationData, highlight, attributes) } else { - buildAudioMessageItem(messageContent, informationData, highlight, attributes) + buildAudioMessageItem(params, messageContent, informationData, highlight, attributes) } } is MessageVerificationRequestContent -> buildVerificationRequestMessageItem(messageContent, informationData, highlight, callback, attributes) @@ -312,11 +312,12 @@ class MessageItemFactory @Inject constructor( .callback(callback) } - private fun buildAudioMessageItem(messageContent: MessageAudioContent, + private fun buildAudioMessageItem(params: TimelineItemFactoryParams, + messageContent: MessageAudioContent, @Suppress("UNUSED_PARAMETER") informationData: MessageInformationData, highlight: Boolean, - attributes: AbsMessageItem.Attributes): MessageFileItem? { + attributes: AbsMessageItem.Attributes): MessageVoiceItem? { val fileUrl = messageContent.getFileUrl()?.let { if (informationData.sentByMe && !informationData.sendState.isSent()) { it @@ -324,8 +325,18 @@ class MessageItemFactory @Inject constructor( it.takeIf { it.isMxcUrl() } } } ?: "" - return MessageFileItem_() + + val playbackControlButtonClickListener: ClickListener = object : ClickListener { + override fun invoke(view: View) { + params.callback?.onVoiceControlButtonClicked(informationData.eventId, messageContent) + } + } + + return MessageVoiceItem_() .attributes(attributes) + .duration(messageContent.audioInfo?.duration ?: 0) + .playbackControlButtonClickListener(playbackControlButtonClickListener) + .voiceMessagePlaybackTracker(voiceMessagePlaybackTracker) .isLocalFile(localFilesHelper.isLocalFile(fileUrl)) .isDownloaded(session.fileService().isFileInCache( fileUrl, @@ -338,8 +349,6 @@ class MessageItemFactory @Inject constructor( .contentDownloadStateTrackerBinder(contentDownloadStateTrackerBinder) .highlighted(highlight) .leftGuideline(avatarSizeProvider.leftGuideline) - .filename(messageContent.body) - .iconRes(R.drawable.ic_headphones) } private fun buildVoiceMessageItem(params: TimelineItemFactoryParams, From fab78c9a6eec4ee28b03e55b7b678e4f0593ceef Mon Sep 17 00:00:00 2001 From: ericdecanini Date: Sat, 19 Mar 2022 18:16:51 +0100 Subject: [PATCH 019/565] Refactors MessageAudioItem to work for both audio files and voice messages --- .../home/room/detail/TimelineFragment.kt | 23 ++++-- .../timeline/factory/MessageItemFactory.kt | 82 ++++++++----------- ...essageVoiceItem.kt => MessageAudioItem.kt} | 67 +++++++++------ .../detail/timeline/item/MessageFileItem.kt | 2 +- ...xml => item_timeline_event_audio_stub.xml} | 20 ++--- .../layout/item_timeline_event_file_stub.xml | 2 +- ...em_timeline_event_view_stubs_container.xml | 4 +- vector/src/main/res/values/strings.xml | 6 ++ 8 files changed, 106 insertions(+), 100 deletions(-) rename vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/{MessageVoiceItem.kt => MessageAudioItem.kt} (64%) rename vector/src/main/res/layout/{item_timeline_event_voice_stub.xml => item_timeline_event_audio_stub.xml} (82%) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt index c2ab12595f..a558a1cc87 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt @@ -163,7 +163,7 @@ import im.vector.app.features.home.room.detail.timeline.item.MessageFileItem import im.vector.app.features.home.room.detail.timeline.item.MessageImageVideoItem import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData import im.vector.app.features.home.room.detail.timeline.item.MessageTextItem -import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceItem +import im.vector.app.features.home.room.detail.timeline.item.MessageAudioItem import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptData import im.vector.app.features.home.room.detail.timeline.reactions.ViewReactionsBottomSheet import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever @@ -1163,6 +1163,7 @@ class TimelineFragment @Inject constructor( views.composerLayout.views.sendButton.contentDescription = getString(R.string.action_send) } + // TODO: Test this private fun renderSpecialMode(event: TimelineEvent, @DrawableRes iconRes: Int, @StringRes descriptionRes: Int, @@ -1175,13 +1176,17 @@ class TimelineFragment @Inject constructor( } val messageContent: MessageContent? = event.getLastMessageContent() - val nonFormattedBody = if (messageContent is MessageAudioContent && messageContent.voiceMessageIndicator != null) { - val formattedDuration = DateUtils.formatElapsedTime(((messageContent.audioInfo?.duration ?: 0) / 1000).toLong()) - getString(R.string.voice_message_reply_content, formattedDuration) - } else if (messageContent is MessagePollContent) { - messageContent.getBestPollCreationInfo()?.question?.getBestQuestion() - } else { - messageContent?.body ?: "" + val nonFormattedBody = when { + messageContent is MessageAudioContent && messageContent.voiceMessageIndicator != null -> { + val formattedDuration = DateUtils.formatElapsedTime(((messageContent.audioInfo?.duration ?: 0) / 1000).toLong()) + getString(R.string.voice_message_reply_content, formattedDuration) + } + messageContent is MessagePollContent -> { + messageContent.getBestPollCreationInfo()?.question?.getBestQuestion() + } + else -> { + messageContent?.body ?: "" + } } var formattedBody: CharSequence? = null if (messageContent is MessageTextContent && messageContent.format == MessageFormat.FORMAT_MATRIX_HTML) { @@ -1372,7 +1377,7 @@ class TimelineFragment @Inject constructor( } return when (model) { is MessageFileItem, - is MessageVoiceItem, + is MessageAudioItem, is MessageImageVideoItem, is MessageTextItem -> { return (model as AbsMessageItem).attributes.informationData.sendState == SendState.SYNCED diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt index 9daec9a4c9..71e546e5f0 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -43,6 +43,8 @@ import im.vector.app.features.home.room.detail.timeline.helper.MessageItemAttrib import im.vector.app.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider import im.vector.app.features.home.room.detail.timeline.helper.VoiceMessagePlaybackTracker import im.vector.app.features.home.room.detail.timeline.item.AbsMessageItem +import im.vector.app.features.home.room.detail.timeline.item.MessageAudioItem +import im.vector.app.features.home.room.detail.timeline.item.MessageAudioItem_ import im.vector.app.features.home.room.detail.timeline.item.MessageFileItem import im.vector.app.features.home.room.detail.timeline.item.MessageFileItem_ import im.vector.app.features.home.room.detail.timeline.item.MessageImageVideoItem @@ -52,8 +54,6 @@ import im.vector.app.features.home.room.detail.timeline.item.MessageLocationItem import im.vector.app.features.home.room.detail.timeline.item.MessageLocationItem_ import im.vector.app.features.home.room.detail.timeline.item.MessageTextItem import im.vector.app.features.home.room.detail.timeline.item.MessageTextItem_ -import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceItem -import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceItem_ import im.vector.app.features.home.room.detail.timeline.item.PollItem import im.vector.app.features.home.room.detail.timeline.item.PollItem_ import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState @@ -96,7 +96,6 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageType import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationRequestContent import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent import org.matrix.android.sdk.api.session.room.model.message.PollType -import org.matrix.android.sdk.api.session.room.model.message.getFileName import org.matrix.android.sdk.api.session.room.model.message.getFileUrl import org.matrix.android.sdk.api.session.room.model.message.getThumbnailUrl import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent @@ -314,36 +313,18 @@ class MessageItemFactory @Inject constructor( private fun buildAudioMessageItem(params: TimelineItemFactoryParams, messageContent: MessageAudioContent, - @Suppress("UNUSED_PARAMETER") informationData: MessageInformationData, highlight: Boolean, - attributes: AbsMessageItem.Attributes): MessageVoiceItem? { - val fileUrl = messageContent.getFileUrl()?.let { - if (informationData.sentByMe && !informationData.sendState.isSent()) { - it - } else { - it.takeIf { it.isMxcUrl() } - } - } ?: "" + attributes: AbsMessageItem.Attributes): MessageAudioItem { + val fileUrl = getAudioFileUrl(messageContent, informationData) + val playbackControlButtonClickListener = createOnPlaybackButtonClickListener(messageContent, informationData, params) - val playbackControlButtonClickListener: ClickListener = object : ClickListener { - override fun invoke(view: View) { - params.callback?.onVoiceControlButtonClicked(informationData.eventId, messageContent) - } - } - - return MessageVoiceItem_() + return MessageAudioItem_() .attributes(attributes) .duration(messageContent.audioInfo?.duration ?: 0) .playbackControlButtonClickListener(playbackControlButtonClickListener) .voiceMessagePlaybackTracker(voiceMessagePlaybackTracker) .isLocalFile(localFilesHelper.isLocalFile(fileUrl)) - .isDownloaded(session.fileService().isFileInCache( - fileUrl, - messageContent.getFileName(), - messageContent.mimeType, - messageContent.encryptedFileInfo?.toElementToDecrypt()) - ) .mxcUrl(fileUrl) .contentUploadStateTrackerBinder(contentUploadStateTrackerBinder) .contentDownloadStateTrackerBinder(contentDownloadStateTrackerBinder) @@ -351,39 +332,42 @@ class MessageItemFactory @Inject constructor( .leftGuideline(avatarSizeProvider.leftGuideline) } + private fun getAudioFileUrl( + messageContent: MessageAudioContent, + informationData: MessageInformationData, + ) = messageContent.getFileUrl()?.let { + if (informationData.sentByMe && !informationData.sendState.isSent()) { + it + } else { + it.takeIf { it.isMxcUrl() } + } + } ?: "" + + private fun createOnPlaybackButtonClickListener( + messageContent: MessageAudioContent, + informationData: MessageInformationData, + params: TimelineItemFactoryParams, + ) = object : ClickListener { + override fun invoke(view: View) { + params.callback?.onVoiceControlButtonClicked(informationData.eventId, messageContent) + } + } + private fun buildVoiceMessageItem(params: TimelineItemFactoryParams, messageContent: MessageAudioContent, - @Suppress("UNUSED_PARAMETER") informationData: MessageInformationData, highlight: Boolean, - attributes: AbsMessageItem.Attributes): MessageVoiceItem? { - val fileUrl = messageContent.getFileUrl()?.let { - if (informationData.sentByMe && !informationData.sendState.isSent()) { - it - } else { - it.takeIf { it.isMxcUrl() } - } - } ?: "" + attributes: AbsMessageItem.Attributes): MessageAudioItem { + val fileUrl = getAudioFileUrl(messageContent, informationData) + val playbackControlButtonClickListener = createOnPlaybackButtonClickListener(messageContent, informationData, params) - val playbackControlButtonClickListener: ClickListener = object : ClickListener { - override fun invoke(view: View) { - params.callback?.onVoiceControlButtonClicked(informationData.eventId, messageContent) - } - } - - return MessageVoiceItem_() + return MessageAudioItem_() .attributes(attributes) .duration(messageContent.audioWaveformInfo?.duration ?: 0) .waveform(messageContent.audioWaveformInfo?.waveform?.toFft().orEmpty()) .playbackControlButtonClickListener(playbackControlButtonClickListener) .voiceMessagePlaybackTracker(voiceMessagePlaybackTracker) .isLocalFile(localFilesHelper.isLocalFile(fileUrl)) - .isDownloaded(session.fileService().isFileInCache( - fileUrl, - messageContent.getFileName(), - messageContent.mimeType, - messageContent.encryptedFileInfo?.toElementToDecrypt()) - ) .mxcUrl(fileUrl) .contentUploadStateTrackerBinder(contentUploadStateTrackerBinder) .contentDownloadStateTrackerBinder(contentDownloadStateTrackerBinder) @@ -432,10 +416,8 @@ class MessageItemFactory @Inject constructor( } private fun buildFileMessageItem(messageContent: MessageFileContent, -// informationData: MessageInformationData, highlight: Boolean, -// callback: TimelineEventController.Callback?, - attributes: AbsMessageItem.Attributes): MessageFileItem? { + attributes: AbsMessageItem.Attributes): MessageFileItem { val mxcUrl = messageContent.getFileUrl() ?: "" return MessageFileItem_() .attributes(attributes) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageAudioItem.kt similarity index 64% rename from vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt rename to vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageAudioItem.kt index 6e176a8fa2..e2fac439f7 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageAudioItem.kt @@ -16,6 +16,7 @@ package im.vector.app.features.home.room.detail.timeline.item +import android.content.Context import android.content.res.ColorStateList import android.graphics.Color import android.text.format.DateUtils @@ -36,7 +37,7 @@ import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLay import im.vector.app.features.themes.ThemeUtils @EpoxyModelClass(layout = R.layout.item_timeline_event_base) -abstract class MessageVoiceItem : AbsMessageItem() { +abstract class MessageAudioItem : AbsMessageItem() { @EpoxyAttribute var mxcUrl: String = "" @@ -53,7 +54,7 @@ abstract class MessageVoiceItem : AbsMessageItem() { @EpoxyAttribute @JvmField - var isDownloaded = false + var isVoiceMessage = false @EpoxyAttribute lateinit var contentUploadStateTrackerBinder: ContentUploadStateTrackerBinder @@ -69,21 +70,21 @@ abstract class MessageVoiceItem : AbsMessageItem() { override fun bind(holder: Holder) { super.bind(holder) - renderSendState(holder.voiceLayout, null) + renderSendState(holder.audioLayout, null) if (!attributes.informationData.sendState.hasFailed()) { contentUploadStateTrackerBinder.bind(attributes.informationData.eventId, isLocalFile, holder.progressLayout) } else { - holder.voicePlaybackControlButton.setImageResource(R.drawable.ic_cross) - holder.voicePlaybackControlButton.contentDescription = holder.view.context.getString(R.string.error_voice_message_unable_to_play) + holder.audioPlaybackControlButton.setImageResource(R.drawable.ic_cross) + holder.audioPlaybackControlButton.contentDescription = getUnableToPlayContentDescription(holder.view.context) holder.progressLayout.isVisible = false } - holder.voicePlaybackWaveform.setOnLongClickListener(attributes.itemLongClickListener) + holder.audioPlaybackWaveform.setOnLongClickListener(attributes.itemLongClickListener) - holder.voicePlaybackWaveform.post { - holder.voicePlaybackWaveform.recreate() + holder.audioPlaybackWaveform.post { + holder.audioPlaybackWaveform.recreate() waveform.forEach { amplitude -> - holder.voicePlaybackWaveform.update(amplitude) + holder.audioPlaybackWaveform.update(amplitude) } } @@ -92,8 +93,8 @@ abstract class MessageVoiceItem : AbsMessageItem() { else ThemeUtils.getColor(holder.view.context, R.attr.vctr_content_quinary) - holder.voicePlaybackLayout.backgroundTintList = ColorStateList.valueOf(backgroundTint) - holder.voicePlaybackControlButton.setOnClickListener { playbackControlButtonClickListener?.invoke(it) } + holder.audioPlaybackLayout.backgroundTintList = ColorStateList.valueOf(backgroundTint) + holder.audioPlaybackControlButton.setOnClickListener { playbackControlButtonClickListener?.invoke(it) } voiceMessagePlaybackTracker.track(attributes.informationData.eventId, object : VoiceMessagePlaybackTracker.Listener { override fun onUpdate(state: VoiceMessagePlaybackTracker.Listener.State) { @@ -107,22 +108,34 @@ abstract class MessageVoiceItem : AbsMessageItem() { }) } + private fun getUnableToPlayContentDescription(context: Context) = context.getString( + if (isVoiceMessage) R.string.error_voice_message_unable_to_play else R.string.error_audio_message_unable_to_play + ) + private fun renderIdleState(holder: Holder) { - holder.voicePlaybackControlButton.setImageResource(R.drawable.ic_play_pause_play) - holder.voicePlaybackControlButton.contentDescription = holder.view.context.getString(R.string.a11y_play_voice_message) - holder.voicePlaybackTime.text = formatPlaybackTime(duration) + holder.audioPlaybackControlButton.setImageResource(R.drawable.ic_play_pause_play) + holder.audioPlaybackControlButton.contentDescription = getPlayMessageContentDescription(holder.view.context) + holder.audioPlaybackTime.text = formatPlaybackTime(duration) } + private fun getPlayMessageContentDescription(context: Context) = context.getString( + if (isVoiceMessage) R.string.a11y_play_voice_message else R.string.a11y_play_audio_message + ) + private fun renderPlayingState(holder: Holder, state: VoiceMessagePlaybackTracker.Listener.State.Playing) { - holder.voicePlaybackControlButton.setImageResource(R.drawable.ic_play_pause_pause) - holder.voicePlaybackControlButton.contentDescription = holder.view.context.getString(R.string.a11y_pause_voice_message) - holder.voicePlaybackTime.text = formatPlaybackTime(state.playbackTime) + holder.audioPlaybackControlButton.setImageResource(R.drawable.ic_play_pause_pause) + holder.audioPlaybackControlButton.contentDescription = getPauseMessageContentDescription(holder.view.context) + holder.audioPlaybackTime.text = formatPlaybackTime(state.playbackTime) } + private fun getPauseMessageContentDescription(context: Context) = context.getString( + if (isVoiceMessage) R.string.a11y_pause_voice_message else R.string.a11y_pause_audio_message + ) + private fun renderPausedState(holder: Holder, state: VoiceMessagePlaybackTracker.Listener.State.Paused) { - holder.voicePlaybackControlButton.setImageResource(R.drawable.ic_play_pause_play) - holder.voicePlaybackControlButton.contentDescription = holder.view.context.getString(R.string.a11y_play_voice_message) - holder.voicePlaybackTime.text = formatPlaybackTime(state.playbackTime) + holder.audioPlaybackControlButton.setImageResource(R.drawable.ic_play_pause_play) + holder.audioPlaybackControlButton.contentDescription = getPlayMessageContentDescription(holder.view.context) + holder.audioPlaybackTime.text = formatPlaybackTime(state.playbackTime) } private fun formatPlaybackTime(time: Int) = DateUtils.formatElapsedTime((time / 1000).toLong()) @@ -137,15 +150,15 @@ abstract class MessageVoiceItem : AbsMessageItem() { override fun getViewStubId() = STUB_ID class Holder : AbsMessageItem.Holder(STUB_ID) { - val voicePlaybackLayout by bind(R.id.voicePlaybackLayout) - val voiceLayout by bind(R.id.voiceLayout) - val voicePlaybackControlButton by bind(R.id.voicePlaybackControlButton) - val voicePlaybackTime by bind(R.id.voicePlaybackTime) - val voicePlaybackWaveform by bind(R.id.voicePlaybackWaveform) - val progressLayout by bind(R.id.messageFileUploadProgressLayout) + val audioPlaybackLayout by bind(R.id.audioPlaybackLayout) + val audioLayout by bind(R.id.audioLayout) + val audioPlaybackControlButton by bind(R.id.audioPlaybackControlButton) + val audioPlaybackTime by bind(R.id.audioPlaybackTime) + val audioPlaybackWaveform by bind(R.id.audioPlaybackWaveform) + val progressLayout by bind(R.id.audioFileUploadProgressLayout) } companion object { - private const val STUB_ID = R.id.messageContentVoiceStub + private const val STUB_ID = R.id.messageContentAudioStub } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageFileItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageFileItem.kt index dc39fbfdd7..dbd232bfaf 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageFileItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageFileItem.kt @@ -109,7 +109,7 @@ abstract class MessageFileItem : AbsMessageItem() { class Holder : AbsMessageItem.Holder(STUB_ID) { val mainLayout by bind(R.id.messageFileMainLayout) - val progressLayout by bind(R.id.messageFileUploadProgressLayout) + val progressLayout by bind(R.id.audioFileUploadProgressLayout) val fileLayout by bind(R.id.messageFileLayout) val fileImageView by bind(R.id.messageFileIconView) val fileImageWrapper by bind(R.id.messageFileImageView) diff --git a/vector/src/main/res/layout/item_timeline_event_voice_stub.xml b/vector/src/main/res/layout/item_timeline_event_audio_stub.xml similarity index 82% rename from vector/src/main/res/layout/item_timeline_event_voice_stub.xml rename to vector/src/main/res/layout/item_timeline_event_audio_stub.xml index a180afbf8e..e8b3b80073 100644 --- a/vector/src/main/res/layout/item_timeline_event_voice_stub.xml +++ b/vector/src/main/res/layout/item_timeline_event_audio_stub.xml @@ -2,7 +2,7 @@ Cannot reply or edit while voice message is active Voice Message (%1$s) + Play Audio Message + Pause Audio Message + Pause Audio Message + Cannot reply or edit while audio message is active + Audio Message (%1$s) + Anyone in %s will be able to find and join this room - no need to manually invite everyone. You’ll be able to change this in room settings anytime. Anyone in a parent space will be able to find and join this room - no need to manually invite everyone. You’ll be able to change this in room settings anytime. From 2e716cb8a043ccba3e706f88db98c66cfd9c2407 Mon Sep 17 00:00:00 2001 From: ericdecanini Date: Sat, 19 Mar 2022 18:26:37 +0100 Subject: [PATCH 020/565] Adapts special body text for audio messages --- .../app/features/home/room/detail/TimelineFragment.kt | 11 +++++++++++ vector/src/main/res/values/strings.xml | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt index a558a1cc87..8fad376a0b 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt @@ -1177,6 +1177,9 @@ class TimelineFragment @Inject constructor( val messageContent: MessageContent? = event.getLastMessageContent() val nonFormattedBody = when { + messageContent is MessageAudioContent -> { + getAudioContentBodyText(messageContent) + } messageContent is MessageAudioContent && messageContent.voiceMessageIndicator != null -> { val formattedDuration = DateUtils.formatElapsedTime(((messageContent.audioInfo?.duration ?: 0) / 1000).toLong()) getString(R.string.voice_message_reply_content, formattedDuration) @@ -1225,6 +1228,14 @@ class TimelineFragment @Inject constructor( focusComposerAndShowKeyboard() } + private fun getAudioContentBodyText(messageContent: MessageAudioContent): String { + val formattedDuration = DateUtils.formatElapsedTime(((messageContent.audioInfo?.duration ?: 0) / 1000).toLong()) + return if (messageContent.voiceMessageIndicator != null) + getString(R.string.voice_message_reply_content, formattedDuration) + else + getString(R.string.audio_message_reply_content, messageContent.body, formattedDuration) + } + override fun onResume() { super.onResume() notificationDrawerManager.setCurrentRoom(timelineArgs.roomId) diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 83b2bba5fb..0cc9795589 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -2862,7 +2862,7 @@ Pause Audio Message Pause Audio Message Cannot reply or edit while audio message is active - Audio Message (%1$s) + %1$s (%2$s) Anyone in %s will be able to find and join this room - no need to manually invite everyone. You’ll be able to change this in room settings anytime. Anyone in a parent space will be able to find and join this room - no need to manually invite everyone. You’ll be able to change this in room settings anytime. From 0479049476d7a292dbcef2fcf10e7344b9ab6aaf Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Mon, 21 Mar 2022 15:16:57 +0200 Subject: [PATCH 021/565] Permalink to a root thread message will navigate user within the thread timeline --- changelog.d/5567.misc | 1 + .../im/vector/app/features/permalink/PermalinkHandler.kt | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 changelog.d/5567.misc diff --git a/changelog.d/5567.misc b/changelog.d/5567.misc new file mode 100644 index 0000000000..4c0f5e0ffc --- /dev/null +++ b/changelog.d/5567.misc @@ -0,0 +1 @@ +Permalinks to root thread messages will now navigate you within the thread timeline \ No newline at end of file diff --git a/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandler.kt b/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandler.kt index fa7b5aa7bc..f840e9ea6f 100644 --- a/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandler.kt +++ b/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandler.kt @@ -38,6 +38,7 @@ import org.matrix.android.sdk.api.session.permalinks.PermalinkService import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.RoomType +import org.matrix.android.sdk.api.session.room.timeline.isRootThread import javax.inject.Inject class PermalinkHandler @Inject constructor(private val activeSessionHolder: ActiveSessionHolder, @@ -89,7 +90,13 @@ class PermalinkHandler @Inject constructor(private val activeSessionHolder: Acti val rootThreadEventId = permalinkData.eventId?.let { eventId -> val room = roomId?.let { session?.getRoom(it) } - room?.getTimelineEvent(eventId)?.root?.getRootThreadEventId() + + val rootThreadEventId = room?.getTimelineEvent(eventId)?.root?.getRootThreadEventId() + rootThreadEventId ?: if (room?.getTimelineEvent(eventId)?.isRootThread() == true) { + eventId + } else { + null + } } openRoom( navigationInterceptor, From 7a7d36d010ccfdf2a039b7ad28768bb1c8200e2d Mon Sep 17 00:00:00 2001 From: ericdecanini Date: Mon, 21 Mar 2022 16:23:19 +0100 Subject: [PATCH 022/565] Renames functions and variables --- .../home/room/detail/RoomDetailActivity.kt | 8 +++---- .../home/room/detail/TimelineFragment.kt | 8 +++---- .../detail/composer/VoiceMessageHelper.kt | 14 ++++++------ .../voice/VoiceMessageRecorderView.kt | 14 ++++++------ .../composer/voice/VoiceMessageViews.kt | 4 ++-- .../timeline/factory/MessageItemFactory.kt | 8 +++---- ...cker.kt => AudioMessagePlaybackTracker.kt} | 4 ++-- .../detail/timeline/item/MessageAudioItem.kt | 22 +++++++++---------- 8 files changed, 41 insertions(+), 41 deletions(-) rename vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/{VoiceMessagePlaybackTracker.kt => AudioMessagePlaybackTracker.kt} (97%) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailActivity.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailActivity.kt index aa4ee825dc..5784e6e264 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailActivity.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailActivity.kt @@ -38,7 +38,7 @@ import im.vector.app.databinding.ActivityRoomDetailBinding import im.vector.app.features.analytics.plan.MobileScreen import im.vector.app.features.home.room.breadcrumbs.BreadcrumbsFragment import im.vector.app.features.home.room.detail.arguments.TimelineArgs -import im.vector.app.features.home.room.detail.timeline.helper.VoiceMessagePlaybackTracker +import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker import im.vector.app.features.matrixto.MatrixToBottomSheet import im.vector.app.features.navigation.Navigator import im.vector.app.features.room.RequireActiveMembershipAction @@ -75,7 +75,7 @@ class RoomDetailActivity : } private var lastKnownPlayingOrRecordingState: Boolean? = null - private val playbackActivityListener = VoiceMessagePlaybackTracker.ActivityListener { isPlayingOrRecording -> + private val playbackActivityListener = AudioMessagePlaybackTracker.ActivityListener { isPlayingOrRecording -> if (lastKnownPlayingOrRecordingState == isPlayingOrRecording) return@ActivityListener when (isPlayingOrRecording) { true -> keepScreenOn() @@ -86,7 +86,7 @@ class RoomDetailActivity : override fun getCoordinatorLayout() = views.coordinatorLayout - @Inject lateinit var playbackTracker: VoiceMessagePlaybackTracker + @Inject lateinit var playbackTracker: AudioMessagePlaybackTracker private lateinit var sharedActionViewModel: RoomDetailSharedActionViewModel private val requireActiveMembershipViewModel: RequireActiveMembershipViewModel by viewModel() @@ -152,7 +152,7 @@ class RoomDetailActivity : override fun onDestroy() { supportFragmentManager.unregisterFragmentLifecycleCallbacks(fragmentLifecycleCallbacks) views.drawerLayout.removeDrawerListener(drawerListener) - playbackTracker.unTrackActivity(playbackActivityListener) + playbackTracker.untrackActivity(playbackActivityListener) super.onDestroy() } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt index 8fad376a0b..aa08ba2b93 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt @@ -156,7 +156,7 @@ import im.vector.app.features.home.room.detail.timeline.action.MessageActionsBot import im.vector.app.features.home.room.detail.timeline.action.MessageSharedActionViewModel import im.vector.app.features.home.room.detail.timeline.edithistory.ViewEditHistoryBottomSheet import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider -import im.vector.app.features.home.room.detail.timeline.helper.VoiceMessagePlaybackTracker +import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker import im.vector.app.features.home.room.detail.timeline.image.buildImageContentRendererData import im.vector.app.features.home.room.detail.timeline.item.AbsMessageItem import im.vector.app.features.home.room.detail.timeline.item.MessageFileItem @@ -259,7 +259,7 @@ class TimelineFragment @Inject constructor( private val roomDetailPendingActionStore: RoomDetailPendingActionStore, private val pillsPostProcessorFactory: PillsPostProcessor.Factory, private val callManager: WebRtcCallManager, - private val voiceMessagePlaybackTracker: VoiceMessagePlaybackTracker, + private val audioMessagePlaybackTracker: AudioMessagePlaybackTracker, private val clock: Clock ) : VectorBaseFragment(), @@ -726,7 +726,7 @@ class TimelineFragment @Inject constructor( } private fun setupVoiceMessageView() { - voiceMessagePlaybackTracker.track(VoiceMessagePlaybackTracker.RECORDING_ID, views.voiceMessageRecorderView) + audioMessagePlaybackTracker.track(AudioMessagePlaybackTracker.RECORDING_ID, views.voiceMessageRecorderView) views.voiceMessageRecorderView.callback = object : VoiceMessageRecorderView.Callback { override fun onVoiceRecordingStarted() { @@ -1260,7 +1260,7 @@ class TimelineFragment @Inject constructor( override fun onPause() { super.onPause() notificationDrawerManager.setCurrentRoom(null) - voiceMessagePlaybackTracker.untrack(VoiceMessagePlaybackTracker.RECORDING_ID) + audioMessagePlaybackTracker.untrack(AudioMessagePlaybackTracker.RECORDING_ID) if (withState(messageComposerViewModel) { it.isVoiceRecording } && requireActivity().isChangingConfigurations) { // we're rotating, maintain any active recordings diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageHelper.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageHelper.kt index 735d356476..bede7a3ce7 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageHelper.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageHelper.kt @@ -21,7 +21,7 @@ import android.media.AudioAttributes import android.media.MediaPlayer import androidx.core.content.FileProvider import im.vector.app.BuildConfig -import im.vector.app.features.home.room.detail.timeline.helper.VoiceMessagePlaybackTracker +import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker import im.vector.app.features.voice.VoiceFailure import im.vector.app.features.voice.VoiceRecorder import im.vector.app.features.voice.VoiceRecorderProvider @@ -42,7 +42,7 @@ import javax.inject.Inject */ class VoiceMessageHelper @Inject constructor( private val context: Context, - private val playbackTracker: VoiceMessagePlaybackTracker, + private val playbackTracker: AudioMessagePlaybackTracker, voiceRecorderProvider: VoiceRecorderProvider ) { private var mediaPlayer: MediaPlayer? = null @@ -58,7 +58,7 @@ class VoiceMessageHelper @Inject constructor( amplitudeList.clear() attachmentData.waveform?.let { amplitudeList.addAll(it) - playbackTracker.updateCurrentRecording(VoiceMessagePlaybackTracker.RECORDING_ID, amplitudeList) + playbackTracker.updateCurrentRecording(AudioMessagePlaybackTracker.RECORDING_ID, amplitudeList) } } @@ -127,14 +127,14 @@ class VoiceMessageHelper @Inject constructor( fun startOrPauseRecordingPlayback() { voiceRecorder.getCurrentRecord()?.let { - startOrPausePlayback(VoiceMessagePlaybackTracker.RECORDING_ID, it) + startOrPausePlayback(AudioMessagePlaybackTracker.RECORDING_ID, it) } } fun startOrPausePlayback(id: String, file: File) { stopPlayback() stopRecordingAmplitudes() - if (playbackTracker.getPlaybackState(id) is VoiceMessagePlaybackTracker.Listener.State.Playing) { + if (playbackTracker.getPlaybackState(id) is AudioMessagePlaybackTracker.Listener.State.Playing) { playbackTracker.pausePlayback(id) } else { startPlayback(id, file) @@ -169,7 +169,7 @@ class VoiceMessageHelper @Inject constructor( } fun stopPlayback() { - playbackTracker.stopPlayback(VoiceMessagePlaybackTracker.RECORDING_ID) + playbackTracker.stopPlayback(AudioMessagePlaybackTracker.RECORDING_ID) mediaPlayer?.stop() stopPlaybackTicker() } @@ -190,7 +190,7 @@ class VoiceMessageHelper @Inject constructor( try { val maxAmplitude = voiceRecorder.getMaxAmplitude() amplitudeList.add(maxAmplitude) - playbackTracker.updateCurrentRecording(VoiceMessagePlaybackTracker.RECORDING_ID, amplitudeList) + playbackTracker.updateCurrentRecording(AudioMessagePlaybackTracker.RECORDING_ID, amplitudeList) } catch (e: IllegalStateException) { Timber.e(e, "Cannot get max amplitude. Amplitude recording timer will be stopped.") stopRecordingAmplitudes() diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageRecorderView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageRecorderView.kt index 9a643796a9..a9f6e33d50 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageRecorderView.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageRecorderView.kt @@ -28,7 +28,7 @@ import im.vector.app.core.hardware.vibrate import im.vector.app.core.time.Clock import im.vector.app.core.utils.DimensionConverter import im.vector.app.databinding.ViewVoiceMessageRecorderBinding -import im.vector.app.features.home.room.detail.timeline.helper.VoiceMessagePlaybackTracker +import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker import im.vector.lib.core.utils.timer.CountUpTimer import javax.inject.Inject import kotlin.math.floor @@ -41,7 +41,7 @@ class VoiceMessageRecorderView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 -) : ConstraintLayout(context, attrs, defStyleAttr), VoiceMessagePlaybackTracker.Listener { +) : ConstraintLayout(context, attrs, defStyleAttr), AudioMessagePlaybackTracker.Listener { interface Callback { fun onVoiceRecordingStarted() @@ -207,16 +207,16 @@ class VoiceMessageRecorderView @JvmOverloads constructor( recordingTicker = null } - override fun onUpdate(state: VoiceMessagePlaybackTracker.Listener.State) { + override fun onUpdate(state: AudioMessagePlaybackTracker.Listener.State) { when (state) { - is VoiceMessagePlaybackTracker.Listener.State.Recording -> { + is AudioMessagePlaybackTracker.Listener.State.Recording -> { voiceMessageViews.renderRecordingWaveform(state.amplitudeList.toTypedArray()) } - is VoiceMessagePlaybackTracker.Listener.State.Playing -> { + is AudioMessagePlaybackTracker.Listener.State.Playing -> { voiceMessageViews.renderPlaying(state) } - is VoiceMessagePlaybackTracker.Listener.State.Paused, - is VoiceMessagePlaybackTracker.Listener.State.Idle -> { + is AudioMessagePlaybackTracker.Listener.State.Paused, + is AudioMessagePlaybackTracker.Listener.State.Idle -> { voiceMessageViews.renderIdle() } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageViews.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageViews.kt index 09284ea5fc..5070785569 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageViews.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageViews.kt @@ -36,7 +36,7 @@ import im.vector.app.core.utils.DimensionConverter import im.vector.app.databinding.ViewVoiceMessageRecorderBinding import im.vector.app.features.home.room.detail.composer.voice.VoiceMessageRecorderView.DraggingState import im.vector.app.features.home.room.detail.composer.voice.VoiceMessageRecorderView.RecordingUiState -import im.vector.app.features.home.room.detail.timeline.helper.VoiceMessagePlaybackTracker +import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker class VoiceMessageViews( private val resources: Resources, @@ -287,7 +287,7 @@ class VoiceMessageViews( views.voicePlaybackWaveform.post { views.voicePlaybackWaveform.recreate() } } - fun renderPlaying(state: VoiceMessagePlaybackTracker.Listener.State.Playing) { + fun renderPlaying(state: AudioMessagePlaybackTracker.Listener.State.Playing) { views.voicePlaybackControlButton.setImageResource(R.drawable.ic_play_pause_pause) views.voicePlaybackControlButton.contentDescription = resources.getString(R.string.a11y_pause_voice_message) val formattedTimerText = DateUtils.formatElapsedTime((state.playbackTime / 1000).toLong()) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt index 71e546e5f0..30e36d33d3 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -41,7 +41,7 @@ import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvid import im.vector.app.features.home.room.detail.timeline.helper.MessageInformationDataFactory import im.vector.app.features.home.room.detail.timeline.helper.MessageItemAttributesFactory import im.vector.app.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider -import im.vector.app.features.home.room.detail.timeline.helper.VoiceMessagePlaybackTracker +import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker import im.vector.app.features.home.room.detail.timeline.item.AbsMessageItem import im.vector.app.features.home.room.detail.timeline.item.MessageAudioItem import im.vector.app.features.home.room.detail.timeline.item.MessageAudioItem_ @@ -126,7 +126,7 @@ class MessageItemFactory @Inject constructor( private val lightweightSettingsStorage: LightweightSettingsStorage, private val spanUtils: SpanUtils, private val session: Session, - private val voiceMessagePlaybackTracker: VoiceMessagePlaybackTracker, + private val audioMessagePlaybackTracker: AudioMessagePlaybackTracker, private val locationPinProvider: LocationPinProvider, private val vectorPreferences: VectorPreferences, private val urlMapProvider: UrlMapProvider, @@ -323,7 +323,7 @@ class MessageItemFactory @Inject constructor( .attributes(attributes) .duration(messageContent.audioInfo?.duration ?: 0) .playbackControlButtonClickListener(playbackControlButtonClickListener) - .voiceMessagePlaybackTracker(voiceMessagePlaybackTracker) + .audioMessagePlaybackTracker(audioMessagePlaybackTracker) .isLocalFile(localFilesHelper.isLocalFile(fileUrl)) .mxcUrl(fileUrl) .contentUploadStateTrackerBinder(contentUploadStateTrackerBinder) @@ -366,7 +366,7 @@ class MessageItemFactory @Inject constructor( .duration(messageContent.audioWaveformInfo?.duration ?: 0) .waveform(messageContent.audioWaveformInfo?.waveform?.toFft().orEmpty()) .playbackControlButtonClickListener(playbackControlButtonClickListener) - .voiceMessagePlaybackTracker(voiceMessagePlaybackTracker) + .audioMessagePlaybackTracker(audioMessagePlaybackTracker) .isLocalFile(localFilesHelper.isLocalFile(fileUrl)) .mxcUrl(fileUrl) .contentUploadStateTrackerBinder(contentUploadStateTrackerBinder) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/VoiceMessagePlaybackTracker.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/AudioMessagePlaybackTracker.kt similarity index 97% rename from vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/VoiceMessagePlaybackTracker.kt rename to vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/AudioMessagePlaybackTracker.kt index c033af1ef5..774b0d797e 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/VoiceMessagePlaybackTracker.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/AudioMessagePlaybackTracker.kt @@ -22,7 +22,7 @@ import javax.inject.Inject import javax.inject.Singleton @Singleton -class VoiceMessagePlaybackTracker @Inject constructor() { +class AudioMessagePlaybackTracker @Inject constructor() { private val mainHandler = Handler(Looper.getMainLooper()) private val listeners = mutableMapOf() @@ -33,7 +33,7 @@ class VoiceMessagePlaybackTracker @Inject constructor() { activityListeners.add(listener) } - fun unTrackActivity(listener: ActivityListener) { + fun untrackActivity(listener: ActivityListener) { activityListeners.remove(listener) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageAudioItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageAudioItem.kt index e2fac439f7..29f6ab7d62 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageAudioItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageAudioItem.kt @@ -32,7 +32,7 @@ import im.vector.app.R import im.vector.app.core.epoxy.ClickListener import im.vector.app.features.home.room.detail.timeline.helper.ContentDownloadStateTrackerBinder import im.vector.app.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder -import im.vector.app.features.home.room.detail.timeline.helper.VoiceMessagePlaybackTracker +import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout import im.vector.app.features.themes.ThemeUtils @@ -66,7 +66,7 @@ abstract class MessageAudioItem : AbsMessageItem() { var playbackControlButtonClickListener: ClickListener? = null @EpoxyAttribute - lateinit var voiceMessagePlaybackTracker: VoiceMessagePlaybackTracker + lateinit var audioMessagePlaybackTracker: AudioMessagePlaybackTracker override fun bind(holder: Holder) { super.bind(holder) @@ -96,13 +96,13 @@ abstract class MessageAudioItem : AbsMessageItem() { holder.audioPlaybackLayout.backgroundTintList = ColorStateList.valueOf(backgroundTint) holder.audioPlaybackControlButton.setOnClickListener { playbackControlButtonClickListener?.invoke(it) } - voiceMessagePlaybackTracker.track(attributes.informationData.eventId, object : VoiceMessagePlaybackTracker.Listener { - override fun onUpdate(state: VoiceMessagePlaybackTracker.Listener.State) { + audioMessagePlaybackTracker.track(attributes.informationData.eventId, object : AudioMessagePlaybackTracker.Listener { + override fun onUpdate(state: AudioMessagePlaybackTracker.Listener.State) { when (state) { - is VoiceMessagePlaybackTracker.Listener.State.Idle -> renderIdleState(holder) - is VoiceMessagePlaybackTracker.Listener.State.Playing -> renderPlayingState(holder, state) - is VoiceMessagePlaybackTracker.Listener.State.Paused -> renderPausedState(holder, state) - is VoiceMessagePlaybackTracker.Listener.State.Recording -> Unit + is AudioMessagePlaybackTracker.Listener.State.Idle -> renderIdleState(holder) + is AudioMessagePlaybackTracker.Listener.State.Playing -> renderPlayingState(holder, state) + is AudioMessagePlaybackTracker.Listener.State.Paused -> renderPausedState(holder, state) + is AudioMessagePlaybackTracker.Listener.State.Recording -> Unit } } }) @@ -122,7 +122,7 @@ abstract class MessageAudioItem : AbsMessageItem() { if (isVoiceMessage) R.string.a11y_play_voice_message else R.string.a11y_play_audio_message ) - private fun renderPlayingState(holder: Holder, state: VoiceMessagePlaybackTracker.Listener.State.Playing) { + private fun renderPlayingState(holder: Holder, state: AudioMessagePlaybackTracker.Listener.State.Playing) { holder.audioPlaybackControlButton.setImageResource(R.drawable.ic_play_pause_pause) holder.audioPlaybackControlButton.contentDescription = getPauseMessageContentDescription(holder.view.context) holder.audioPlaybackTime.text = formatPlaybackTime(state.playbackTime) @@ -132,7 +132,7 @@ abstract class MessageAudioItem : AbsMessageItem() { if (isVoiceMessage) R.string.a11y_pause_voice_message else R.string.a11y_pause_audio_message ) - private fun renderPausedState(holder: Holder, state: VoiceMessagePlaybackTracker.Listener.State.Paused) { + private fun renderPausedState(holder: Holder, state: AudioMessagePlaybackTracker.Listener.State.Paused) { holder.audioPlaybackControlButton.setImageResource(R.drawable.ic_play_pause_play) holder.audioPlaybackControlButton.contentDescription = getPlayMessageContentDescription(holder.view.context) holder.audioPlaybackTime.text = formatPlaybackTime(state.playbackTime) @@ -144,7 +144,7 @@ abstract class MessageAudioItem : AbsMessageItem() { super.unbind(holder) contentUploadStateTrackerBinder.unbind(attributes.informationData.eventId) contentDownloadStateTrackerBinder.unbind(mxcUrl) - voiceMessagePlaybackTracker.untrack(attributes.informationData.eventId) + audioMessagePlaybackTracker.untrack(attributes.informationData.eventId) } override fun getViewStubId() = STUB_ID From ff26829d652c4db967986027a3f81316eb46c608 Mon Sep 17 00:00:00 2001 From: ericdecanini Date: Mon, 21 Mar 2022 18:42:07 +0100 Subject: [PATCH 023/565] Adds new audio timeline stub --- .../timeline/factory/MessageItemFactory.kt | 9 +- .../detail/timeline/item/MessageAudioItem.kt | 67 +++----- .../detail/timeline/item/MessageFileItem.kt | 10 +- .../detail/timeline/item/MessageVoiceItem.kt | 150 ++++++++++++++++++ .../layout/item_timeline_event_audio_stub.xml | 57 +++---- .../layout/item_timeline_event_file_stub.xml | 2 +- ...em_timeline_event_view_stubs_container.xml | 7 + .../layout/item_timeline_event_voice_stub.xml | 69 ++++++++ 8 files changed, 292 insertions(+), 79 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt create mode 100644 vector/src/main/res/layout/item_timeline_event_voice_stub.xml diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt index 30e36d33d3..b8d7af2d80 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -54,6 +54,8 @@ import im.vector.app.features.home.room.detail.timeline.item.MessageLocationItem import im.vector.app.features.home.room.detail.timeline.item.MessageLocationItem_ import im.vector.app.features.home.room.detail.timeline.item.MessageTextItem import im.vector.app.features.home.room.detail.timeline.item.MessageTextItem_ +import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceItem +import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceItem_ import im.vector.app.features.home.room.detail.timeline.item.PollItem import im.vector.app.features.home.room.detail.timeline.item.PollItem_ import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState @@ -321,6 +323,7 @@ class MessageItemFactory @Inject constructor( return MessageAudioItem_() .attributes(attributes) + .filename(messageContent.body) .duration(messageContent.audioInfo?.duration ?: 0) .playbackControlButtonClickListener(playbackControlButtonClickListener) .audioMessagePlaybackTracker(audioMessagePlaybackTracker) @@ -357,16 +360,16 @@ class MessageItemFactory @Inject constructor( messageContent: MessageAudioContent, informationData: MessageInformationData, highlight: Boolean, - attributes: AbsMessageItem.Attributes): MessageAudioItem { + attributes: AbsMessageItem.Attributes): MessageVoiceItem { val fileUrl = getAudioFileUrl(messageContent, informationData) val playbackControlButtonClickListener = createOnPlaybackButtonClickListener(messageContent, informationData, params) - return MessageAudioItem_() + return MessageVoiceItem_() .attributes(attributes) .duration(messageContent.audioWaveformInfo?.duration ?: 0) .waveform(messageContent.audioWaveformInfo?.waveform?.toFft().orEmpty()) .playbackControlButtonClickListener(playbackControlButtonClickListener) - .audioMessagePlaybackTracker(audioMessagePlaybackTracker) + .voiceMessagePlaybackTracker(audioMessagePlaybackTracker) .isLocalFile(localFilesHelper.isLocalFile(fileUrl)) .mxcUrl(fileUrl) .contentUploadStateTrackerBinder(contentUploadStateTrackerBinder) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageAudioItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageAudioItem.kt index 29f6ab7d62..101a837b3f 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageAudioItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageAudioItem.kt @@ -16,18 +16,15 @@ package im.vector.app.features.home.room.detail.timeline.item -import android.content.Context import android.content.res.ColorStateList import android.graphics.Color import android.text.format.DateUtils -import android.view.View import android.view.ViewGroup import android.widget.ImageButton import android.widget.TextView import androidx.core.view.isVisible import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass -import com.visualizer.amplitude.AudioRecordView import im.vector.app.R import im.vector.app.core.epoxy.ClickListener import im.vector.app.features.home.room.detail.timeline.helper.ContentDownloadStateTrackerBinder @@ -39,23 +36,19 @@ import im.vector.app.features.themes.ThemeUtils @EpoxyModelClass(layout = R.layout.item_timeline_event_base) abstract class MessageAudioItem : AbsMessageItem() { + @EpoxyAttribute + var filename: String = "" + @EpoxyAttribute var mxcUrl: String = "" @EpoxyAttribute var duration: Int = 0 - @EpoxyAttribute - var waveform: List = emptyList() - @EpoxyAttribute @JvmField var isLocalFile = false - @EpoxyAttribute - @JvmField - var isVoiceMessage = false - @EpoxyAttribute lateinit var contentUploadStateTrackerBinder: ContentUploadStateTrackerBinder @@ -70,32 +63,34 @@ abstract class MessageAudioItem : AbsMessageItem() { override fun bind(holder: Holder) { super.bind(holder) - renderSendState(holder.audioLayout, null) + renderSendState(holder.rootLayout, null) + bindUploadState(holder) + holder.filenameView.text = filename + applyLayoutTint(holder) + holder.audioPlaybackControlButton.setOnClickListener { playbackControlButtonClickListener?.invoke(it) } + renderStateBasedOnAudioPlayback(holder) + } + + private fun bindUploadState(holder: Holder) { if (!attributes.informationData.sendState.hasFailed()) { contentUploadStateTrackerBinder.bind(attributes.informationData.eventId, isLocalFile, holder.progressLayout) } else { holder.audioPlaybackControlButton.setImageResource(R.drawable.ic_cross) - holder.audioPlaybackControlButton.contentDescription = getUnableToPlayContentDescription(holder.view.context) + holder.audioPlaybackControlButton.contentDescription = holder.view.context.getString(R.string.error_audio_message_unable_to_play) holder.progressLayout.isVisible = false } + } - holder.audioPlaybackWaveform.setOnLongClickListener(attributes.itemLongClickListener) - - holder.audioPlaybackWaveform.post { - holder.audioPlaybackWaveform.recreate() - waveform.forEach { amplitude -> - holder.audioPlaybackWaveform.update(amplitude) - } - } - + private fun applyLayoutTint(holder: Holder) { val backgroundTint = if (attributes.informationData.messageLayout is TimelineMessageLayout.Bubble) Color.TRANSPARENT else ThemeUtils.getColor(holder.view.context, R.attr.vctr_content_quinary) - holder.audioPlaybackLayout.backgroundTintList = ColorStateList.valueOf(backgroundTint) - holder.audioPlaybackControlButton.setOnClickListener { playbackControlButtonClickListener?.invoke(it) } + holder.mainLayout.backgroundTintList = ColorStateList.valueOf(backgroundTint) + } + private fun renderStateBasedOnAudioPlayback(holder: Holder) { audioMessagePlaybackTracker.track(attributes.informationData.eventId, object : AudioMessagePlaybackTracker.Listener { override fun onUpdate(state: AudioMessagePlaybackTracker.Listener.State) { when (state) { @@ -108,33 +103,21 @@ abstract class MessageAudioItem : AbsMessageItem() { }) } - private fun getUnableToPlayContentDescription(context: Context) = context.getString( - if (isVoiceMessage) R.string.error_voice_message_unable_to_play else R.string.error_audio_message_unable_to_play - ) - private fun renderIdleState(holder: Holder) { holder.audioPlaybackControlButton.setImageResource(R.drawable.ic_play_pause_play) - holder.audioPlaybackControlButton.contentDescription = getPlayMessageContentDescription(holder.view.context) + holder.audioPlaybackControlButton.contentDescription = holder.view.context.getString(R.string.a11y_play_audio_message) holder.audioPlaybackTime.text = formatPlaybackTime(duration) } - private fun getPlayMessageContentDescription(context: Context) = context.getString( - if (isVoiceMessage) R.string.a11y_play_voice_message else R.string.a11y_play_audio_message - ) - private fun renderPlayingState(holder: Holder, state: AudioMessagePlaybackTracker.Listener.State.Playing) { holder.audioPlaybackControlButton.setImageResource(R.drawable.ic_play_pause_pause) - holder.audioPlaybackControlButton.contentDescription = getPauseMessageContentDescription(holder.view.context) + holder.audioPlaybackControlButton.contentDescription = holder.view.context.getString(R.string.a11y_pause_audio_message) holder.audioPlaybackTime.text = formatPlaybackTime(state.playbackTime) } - private fun getPauseMessageContentDescription(context: Context) = context.getString( - if (isVoiceMessage) R.string.a11y_pause_voice_message else R.string.a11y_pause_audio_message - ) - private fun renderPausedState(holder: Holder, state: AudioMessagePlaybackTracker.Listener.State.Paused) { holder.audioPlaybackControlButton.setImageResource(R.drawable.ic_play_pause_play) - holder.audioPlaybackControlButton.contentDescription = getPlayMessageContentDescription(holder.view.context) + holder.audioPlaybackControlButton.contentDescription = holder.view.context.getString(R.string.a11y_play_audio_message) holder.audioPlaybackTime.text = formatPlaybackTime(state.playbackTime) } @@ -150,12 +133,12 @@ abstract class MessageAudioItem : AbsMessageItem() { override fun getViewStubId() = STUB_ID class Holder : AbsMessageItem.Holder(STUB_ID) { - val audioPlaybackLayout by bind(R.id.audioPlaybackLayout) - val audioLayout by bind(R.id.audioLayout) + val rootLayout by bind(R.id.messageRootLayout) + val mainLayout by bind(R.id.messageMainInnerLayout) + val filenameView by bind(R.id.messageFilenameView) val audioPlaybackControlButton by bind(R.id.audioPlaybackControlButton) val audioPlaybackTime by bind(R.id.audioPlaybackTime) - val audioPlaybackWaveform by bind(R.id.audioPlaybackWaveform) - val progressLayout by bind(R.id.audioFileUploadProgressLayout) + val progressLayout by bind(R.id.messageFileUploadProgressLayout) } companion object { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageFileItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageFileItem.kt index dbd232bfaf..8a94f927f9 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageFileItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageFileItem.kt @@ -47,9 +47,6 @@ abstract class MessageFileItem : AbsMessageItem() { @DrawableRes var iconRes: Int = 0 -// @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) -// var clickListener: ClickListener? = null - @EpoxyAttribute @JvmField var isLocalFile = false @@ -67,13 +64,16 @@ abstract class MessageFileItem : AbsMessageItem() { override fun bind(holder: Holder) { super.bind(holder) renderSendState(holder.fileLayout, holder.filenameView) + if (!attributes.informationData.sendState.hasFailed()) { contentUploadStateTrackerBinder.bind(attributes.informationData.eventId, isLocalFile, holder.progressLayout) } else { holder.fileImageView.setImageResource(R.drawable.ic_cross) holder.progressLayout.isVisible = false } + holder.filenameView.text = filename + if (attributes.informationData.sendState.isSending()) { holder.fileImageView.setImageResource(iconRes) } else { @@ -85,7 +85,7 @@ abstract class MessageFileItem : AbsMessageItem() { holder.fileImageView.setImageResource(R.drawable.ic_download) } } -// holder.view.setOnClickListener(clickListener) + val backgroundTint = if (attributes.informationData.messageLayout is TimelineMessageLayout.Bubble) { Color.TRANSPARENT } else { @@ -109,7 +109,7 @@ abstract class MessageFileItem : AbsMessageItem() { class Holder : AbsMessageItem.Holder(STUB_ID) { val mainLayout by bind(R.id.messageFileMainLayout) - val progressLayout by bind(R.id.audioFileUploadProgressLayout) + val progressLayout by bind(R.id.messageFileUploadProgressLayout) val fileLayout by bind(R.id.messageFileLayout) val fileImageView by bind(R.id.messageFileIconView) val fileImageWrapper by bind(R.id.messageFileImageView) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt new file mode 100644 index 0000000000..06b622d1d6 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2021 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.features.home.room.detail.timeline.item + +import android.content.res.ColorStateList +import android.graphics.Color +import android.text.format.DateUtils +import android.view.View +import android.view.ViewGroup +import android.widget.ImageButton +import android.widget.TextView +import androidx.core.view.isVisible +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import com.visualizer.amplitude.AudioRecordView +import im.vector.app.R +import im.vector.app.core.epoxy.ClickListener +import im.vector.app.features.home.room.detail.timeline.helper.ContentDownloadStateTrackerBinder +import im.vector.app.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder +import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker +import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout +import im.vector.app.features.themes.ThemeUtils + +@EpoxyModelClass(layout = R.layout.item_timeline_event_base) +abstract class MessageVoiceItem : AbsMessageItem() { + + @EpoxyAttribute + var mxcUrl: String = "" + + @EpoxyAttribute + var duration: Int = 0 + + @EpoxyAttribute + var waveform: List = emptyList() + + @EpoxyAttribute + @JvmField + var isLocalFile = false + + @EpoxyAttribute + @JvmField + var isDownloaded = false + + @EpoxyAttribute + lateinit var contentUploadStateTrackerBinder: ContentUploadStateTrackerBinder + + @EpoxyAttribute + lateinit var contentDownloadStateTrackerBinder: ContentDownloadStateTrackerBinder + + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) + var playbackControlButtonClickListener: ClickListener? = null + + @EpoxyAttribute + lateinit var voiceMessagePlaybackTracker: AudioMessagePlaybackTracker + + override fun bind(holder: Holder) { + super.bind(holder) + renderSendState(holder.voiceLayout, null) + if (!attributes.informationData.sendState.hasFailed()) { + contentUploadStateTrackerBinder.bind(attributes.informationData.eventId, isLocalFile, holder.progressLayout) + } else { + holder.voicePlaybackControlButton.setImageResource(R.drawable.ic_cross) + holder.voicePlaybackControlButton.contentDescription = holder.view.context.getString(R.string.error_voice_message_unable_to_play) + holder.progressLayout.isVisible = false + } + + holder.voicePlaybackWaveform.setOnLongClickListener(attributes.itemLongClickListener) + + holder.voicePlaybackWaveform.post { + holder.voicePlaybackWaveform.recreate() + waveform.forEach { amplitude -> + holder.voicePlaybackWaveform.update(amplitude) + } + } + + val backgroundTint = if (attributes.informationData.messageLayout is TimelineMessageLayout.Bubble) { + Color.TRANSPARENT + } else { + ThemeUtils.getColor(holder.view.context, R.attr.vctr_content_quinary) + } + holder.voicePlaybackLayout.backgroundTintList = ColorStateList.valueOf(backgroundTint) + holder.voicePlaybackControlButton.setOnClickListener { playbackControlButtonClickListener?.invoke(it) } + + voiceMessagePlaybackTracker.track(attributes.informationData.eventId, object : AudioMessagePlaybackTracker.Listener { + override fun onUpdate(state: AudioMessagePlaybackTracker.Listener.State) { + when (state) { + is AudioMessagePlaybackTracker.Listener.State.Idle -> renderIdleState(holder) + is AudioMessagePlaybackTracker.Listener.State.Playing -> renderPlayingState(holder, state) + is AudioMessagePlaybackTracker.Listener.State.Paused -> renderPausedState(holder, state) + } + } + }) + } + + private fun renderIdleState(holder: Holder) { + holder.voicePlaybackControlButton.setImageResource(R.drawable.ic_play_pause_play) + holder.voicePlaybackControlButton.contentDescription = holder.view.context.getString(R.string.a11y_play_voice_message) + holder.voicePlaybackTime.text = formatPlaybackTime(duration) + } + + private fun renderPlayingState(holder: Holder, state: AudioMessagePlaybackTracker.Listener.State.Playing) { + holder.voicePlaybackControlButton.setImageResource(R.drawable.ic_play_pause_pause) + holder.voicePlaybackControlButton.contentDescription = holder.view.context.getString(R.string.a11y_pause_voice_message) + holder.voicePlaybackTime.text = formatPlaybackTime(state.playbackTime) + } + + private fun renderPausedState(holder: Holder, state: AudioMessagePlaybackTracker.Listener.State.Paused) { + holder.voicePlaybackControlButton.setImageResource(R.drawable.ic_play_pause_play) + holder.voicePlaybackControlButton.contentDescription = holder.view.context.getString(R.string.a11y_play_voice_message) + holder.voicePlaybackTime.text = formatPlaybackTime(state.playbackTime) + } + + private fun formatPlaybackTime(time: Int) = DateUtils.formatElapsedTime((time / 1000).toLong()) + + override fun unbind(holder: Holder) { + super.unbind(holder) + contentUploadStateTrackerBinder.unbind(attributes.informationData.eventId) + contentDownloadStateTrackerBinder.unbind(mxcUrl) + voiceMessagePlaybackTracker.untrack(attributes.informationData.eventId) + } + + override fun getViewStubId() = STUB_ID + + class Holder : AbsMessageItem.Holder(STUB_ID) { + val voicePlaybackLayout by bind(R.id.voicePlaybackLayout) + val voiceLayout by bind(R.id.voiceLayout) + val voicePlaybackControlButton by bind(R.id.voicePlaybackControlButton) + val voicePlaybackTime by bind(R.id.voicePlaybackTime) + val voicePlaybackWaveform by bind(R.id.voicePlaybackWaveform) + val progressLayout by bind(R.id.messageFileUploadProgressLayout) + } + + companion object { + private const val STUB_ID = R.id.messageContentVoiceStub + } +} diff --git a/vector/src/main/res/layout/item_timeline_event_audio_stub.xml b/vector/src/main/res/layout/item_timeline_event_audio_stub.xml index e8b3b80073..d0f4d4c85f 100644 --- a/vector/src/main/res/layout/item_timeline_event_audio_stub.xml +++ b/vector/src/main/res/layout/item_timeline_event_audio_stub.xml @@ -2,18 +2,18 @@ + tools:viewBindingIgnore="true"> + + - - + tools:text="0:23" /> - + \ No newline at end of file diff --git a/vector/src/main/res/layout/item_timeline_event_file_stub.xml b/vector/src/main/res/layout/item_timeline_event_file_stub.xml index 82f2c8e886..41e4a118a3 100644 --- a/vector/src/main/res/layout/item_timeline_event_file_stub.xml +++ b/vector/src/main/res/layout/item_timeline_event_file_stub.xml @@ -49,7 +49,7 @@ + + + + + + + + + + + + + + + + + From 47dddd706c660c071c8b55e29f2aeb613ef752f3 Mon Sep 17 00:00:00 2001 From: SpiritCroc Date: Mon, 21 Mar 2022 11:23:03 +0100 Subject: [PATCH 024/565] Only show HasUnread -> HasUnread updates for same readMarker --- .../app/features/home/room/detail/RoomDetailViewState.kt | 2 +- .../app/features/home/room/detail/TimelineViewModel.kt | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt index e2b97b0900..5d545ef4b5 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt @@ -36,7 +36,7 @@ sealed class UnreadState { object Unknown : UnreadState() object HasNoUnread : UnreadState() data class ReadMarkerNotLoaded(val readMarkerId: String) : UnreadState() - data class HasUnread(val firstUnreadEventId: String) : UnreadState() + data class HasUnread(val firstUnreadEventId: String, val readMarkerId: String) : UnreadState() } data class JitsiState( diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt index 78e3469a58..4caad1bb5e 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt @@ -1099,11 +1099,13 @@ class TimelineViewModel @AssistedInject constructor( computeUnreadState(timelineEvents, roomSummary) } // We don't want live update of unread so we skip when we already had a HasUnread or HasNoUnread - // However, we want to update an existing HasUnread, as we might get additional information during loading of events. + // However, we want to update an existing HasUnread, if the readMarkerId hasn't changed, + // as we might be loading new events to fill gaps in the timeline. .distinctUntilChanged { previous, current -> when { previous is UnreadState.Unknown || previous is UnreadState.ReadMarkerNotLoaded -> false - previous is UnreadState.HasUnread && current is UnreadState.HasUnread -> false + previous is UnreadState.HasUnread && current is UnreadState.HasUnread && + previous.readMarkerId == current.readMarkerId -> false current is UnreadState.HasUnread || current is UnreadState.HasNoUnread -> true else -> false } @@ -1132,7 +1134,7 @@ class TimelineViewModel @AssistedInject constructor( val eventId = timelineEvent.root.eventId ?: return UnreadState.Unknown val isFromMe = timelineEvent.root.senderId == session.myUserId if (!isFromMe) { - return UnreadState.HasUnread(eventId) + return UnreadState.HasUnread(eventId, readMarkerIdSnapshot) } } return UnreadState.HasNoUnread From c9946b6dd3d0e9998263b1122e0336df2fc88b4c Mon Sep 17 00:00:00 2001 From: ericdecanini Date: Mon, 21 Mar 2022 19:57:18 +0100 Subject: [PATCH 025/565] Code cleanup --- .../home/room/detail/TimelineFragment.kt | 27 +++++++------------ .../timeline/factory/MessageItemFactory.kt | 21 +++++++++------ .../detail/timeline/item/MessageAudioItem.kt | 8 +++--- .../detail/timeline/item/MessageVoiceItem.kt | 2 +- vector/src/main/res/values/strings.xml | 1 - 5 files changed, 28 insertions(+), 31 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt index 7f0c7105cb..cc34a35145 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt @@ -155,8 +155,8 @@ import im.vector.app.features.home.room.detail.timeline.action.EventSharedAction import im.vector.app.features.home.room.detail.timeline.action.MessageActionsBottomSheet import im.vector.app.features.home.room.detail.timeline.action.MessageSharedActionViewModel import im.vector.app.features.home.room.detail.timeline.edithistory.ViewEditHistoryBottomSheet -import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker +import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider import im.vector.app.features.home.room.detail.timeline.image.buildImageContentRendererData import im.vector.app.features.home.room.detail.timeline.item.AbsMessageItem import im.vector.app.features.home.room.detail.timeline.item.MessageFileItem @@ -164,6 +164,7 @@ import im.vector.app.features.home.room.detail.timeline.item.MessageImageVideoIt import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData import im.vector.app.features.home.room.detail.timeline.item.MessageTextItem import im.vector.app.features.home.room.detail.timeline.item.MessageAudioItem +import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceItem import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptData import im.vector.app.features.home.room.detail.timeline.reactions.ViewReactionsBottomSheet import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever @@ -1178,20 +1179,10 @@ class TimelineFragment @Inject constructor( } val messageContent: MessageContent? = event.getLastMessageContent() - val nonFormattedBody = when { - messageContent is MessageAudioContent -> { - getAudioContentBodyText(messageContent) - } - messageContent is MessageAudioContent && messageContent.voiceMessageIndicator != null -> { - val formattedDuration = DateUtils.formatElapsedTime(((messageContent.audioInfo?.duration ?: 0) / 1000).toLong()) - getString(R.string.voice_message_reply_content, formattedDuration) - } - messageContent is MessagePollContent -> { - messageContent.getBestPollCreationInfo()?.question?.getBestQuestion() - } - else -> { - messageContent?.body ?: "" - } + val nonFormattedBody = when (messageContent) { + is MessageAudioContent -> getAudioContentBodyText(messageContent) + is MessagePollContent -> messageContent.getBestPollCreationInfo()?.question?.getBestQuestion() + else -> messageContent?.body ?: "" } var formattedBody: CharSequence? = null if (messageContent is MessageTextContent && messageContent.format == MessageFormat.FORMAT_MATRIX_HTML) { @@ -1232,10 +1223,11 @@ class TimelineFragment @Inject constructor( private fun getAudioContentBodyText(messageContent: MessageAudioContent): String { val formattedDuration = DateUtils.formatElapsedTime(((messageContent.audioInfo?.duration ?: 0) / 1000).toLong()) - return if (messageContent.voiceMessageIndicator != null) + return if (messageContent.voiceMessageIndicator != null) { getString(R.string.voice_message_reply_content, formattedDuration) - else + } else { getString(R.string.audio_message_reply_content, messageContent.body, formattedDuration) + } } override fun onResume() { @@ -1391,6 +1383,7 @@ class TimelineFragment @Inject constructor( return when (model) { is MessageFileItem, is MessageAudioItem, + is MessageVoiceItem, is MessageImageVideoItem, is MessageTextItem -> { return (model as AbsMessageItem).attributes.informationData.sendState == SendState.SYNCED diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt index 89fd807240..1b352b93e1 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -34,6 +34,7 @@ import im.vector.app.core.resources.StringProvider import im.vector.app.core.utils.DimensionConverter import im.vector.app.core.utils.containsOnlyEmojis import im.vector.app.features.home.room.detail.timeline.TimelineEventController +import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker import im.vector.app.features.home.room.detail.timeline.helper.AvatarSizeProvider import im.vector.app.features.home.room.detail.timeline.helper.ContentDownloadStateTrackerBinder import im.vector.app.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder @@ -41,7 +42,6 @@ import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvid import im.vector.app.features.home.room.detail.timeline.helper.MessageInformationDataFactory import im.vector.app.features.home.room.detail.timeline.helper.MessageItemAttributesFactory import im.vector.app.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider -import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker import im.vector.app.features.home.room.detail.timeline.item.AbsMessageItem import im.vector.app.features.home.room.detail.timeline.item.MessageAudioItem import im.vector.app.features.home.room.detail.timeline.item.MessageAudioItem_ @@ -189,13 +189,7 @@ class MessageItemFactory @Inject constructor( is MessageNoticeContent -> buildNoticeMessageItem(messageContent, informationData, highlight, callback, attributes) is MessageVideoContent -> buildVideoMessageItem(messageContent, informationData, highlight, callback, attributes) is MessageFileContent -> buildFileMessageItem(messageContent, highlight, attributes) - is MessageAudioContent -> { - if (messageContent.voiceMessageIndicator != null) { - buildVoiceMessageItem(params, messageContent, informationData, highlight, attributes) - } else { - buildAudioMessageItem(params, messageContent, informationData, highlight, attributes) - } - } + is MessageAudioContent -> buildAudioContent(params, messageContent, informationData, highlight, attributes) is MessageVerificationRequestContent -> buildVerificationRequestMessageItem(messageContent, informationData, highlight, callback, attributes) is MessagePollContent -> buildPollItem(messageContent, informationData, highlight, callback, attributes) is MessageLocationContent -> { @@ -435,6 +429,17 @@ class MessageItemFactory @Inject constructor( .iconRes(R.drawable.ic_paperclip) } + private fun buildAudioContent(params: TimelineItemFactoryParams, + messageContent: MessageAudioContent, + informationData: MessageInformationData, + highlight: Boolean, + attributes: AbsMessageItem.Attributes) = + if (messageContent.voiceMessageIndicator != null) { + buildVoiceMessageItem(params, messageContent, informationData, highlight, attributes) + } else { + buildAudioMessageItem(params, messageContent, informationData, highlight, attributes) + } + private fun buildNotHandledMessageItem(messageContent: MessageContent, informationData: MessageInformationData, highlight: Boolean, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageAudioItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageAudioItem.kt index 101a837b3f..ac65262ba2 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageAudioItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageAudioItem.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 New Vector Ltd + * Copyright (c) 2022 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. @@ -82,11 +82,11 @@ abstract class MessageAudioItem : AbsMessageItem() { } private fun applyLayoutTint(holder: Holder) { - val backgroundTint = if (attributes.informationData.messageLayout is TimelineMessageLayout.Bubble) + val backgroundTint = if (attributes.informationData.messageLayout is TimelineMessageLayout.Bubble) { Color.TRANSPARENT - else + } else { ThemeUtils.getColor(holder.view.context, R.attr.vctr_content_quinary) - + } holder.mainLayout.backgroundTintList = ColorStateList.valueOf(backgroundTint) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt index 06b622d1d6..70d93e4263 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt @@ -29,9 +29,9 @@ import com.airbnb.epoxy.EpoxyModelClass import com.visualizer.amplitude.AudioRecordView import im.vector.app.R import im.vector.app.core.epoxy.ClickListener +import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker import im.vector.app.features.home.room.detail.timeline.helper.ContentDownloadStateTrackerBinder import im.vector.app.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder -import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout import im.vector.app.features.themes.ThemeUtils diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index b76aa565b5..46565bfda5 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -2861,7 +2861,6 @@ Play Audio Message Pause Audio Message Pause Audio Message - Cannot reply or edit while audio message is active %1$s (%2$s) Anyone in %s will be able to find and join this room - no need to manually invite everyone. You’ll be able to change this in room settings anytime. From 06468a43657109c79238756d074c12b4a8d04a6e Mon Sep 17 00:00:00 2001 From: ericdecanini Date: Mon, 21 Mar 2022 20:04:08 +0100 Subject: [PATCH 026/565] Adds changelog file --- changelog.d/5586.feature | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/5586.feature diff --git a/changelog.d/5586.feature b/changelog.d/5586.feature new file mode 100644 index 0000000000..17d7bfce86 --- /dev/null +++ b/changelog.d/5586.feature @@ -0,0 +1 @@ +Adds the ability for audio attachments to be played in the timeline From b035911d8f1936be15e1dc34419d512090c904a0 Mon Sep 17 00:00:00 2001 From: ericdecanini Date: Mon, 21 Mar 2022 20:05:47 +0100 Subject: [PATCH 027/565] Fixes import lint error --- .../im/vector/app/features/home/room/detail/TimelineFragment.kt | 2 +- .../features/home/room/detail/timeline/item/MessageAudioItem.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt index cc34a35145..ebb07a3a90 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt @@ -159,11 +159,11 @@ import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlayb import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider import im.vector.app.features.home.room.detail.timeline.image.buildImageContentRendererData import im.vector.app.features.home.room.detail.timeline.item.AbsMessageItem +import im.vector.app.features.home.room.detail.timeline.item.MessageAudioItem import im.vector.app.features.home.room.detail.timeline.item.MessageFileItem import im.vector.app.features.home.room.detail.timeline.item.MessageImageVideoItem import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData import im.vector.app.features.home.room.detail.timeline.item.MessageTextItem -import im.vector.app.features.home.room.detail.timeline.item.MessageAudioItem import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceItem import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptData import im.vector.app.features.home.room.detail.timeline.reactions.ViewReactionsBottomSheet diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageAudioItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageAudioItem.kt index ac65262ba2..c3ec93ced3 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageAudioItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageAudioItem.kt @@ -27,9 +27,9 @@ import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass import im.vector.app.R import im.vector.app.core.epoxy.ClickListener +import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker import im.vector.app.features.home.room.detail.timeline.helper.ContentDownloadStateTrackerBinder import im.vector.app.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder -import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout import im.vector.app.features.themes.ThemeUtils From 52699357dd709b7d89256ab254d71f5b52519ced Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Tue, 22 Mar 2022 12:27:11 +0200 Subject: [PATCH 028/565] Change text constant --- vector/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index aed977d687..6e5a2be47c 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -1546,7 +1546,7 @@ Edit Reply - Reply In Thread + Reply in thread View In Room Retry From 3c6dbd08437abc4e7cb0e896e6ae28bebf9847ad Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Tue, 22 Mar 2022 12:55:50 +0200 Subject: [PATCH 029/565] Reduce timeline menu thread icon padding --- library/ui-styles/src/main/res/values/dimens.xml | 1 + vector/src/main/res/layout/view_thread_notification_badge.xml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/library/ui-styles/src/main/res/values/dimens.xml b/library/ui-styles/src/main/res/values/dimens.xml index 600c73c878..8dc4edc07d 100644 --- a/library/ui-styles/src/main/res/values/dimens.xml +++ b/library/ui-styles/src/main/res/values/dimens.xml @@ -40,6 +40,7 @@ 24dp 48dp 48dp + 38dp 56dp diff --git a/vector/src/main/res/layout/view_thread_notification_badge.xml b/vector/src/main/res/layout/view_thread_notification_badge.xml index 81b3f7138e..00ba45ad08 100644 --- a/vector/src/main/res/layout/view_thread_notification_badge.xml +++ b/vector/src/main/res/layout/view_thread_notification_badge.xml @@ -2,7 +2,7 @@ Date: Tue, 22 Mar 2022 13:48:07 +0200 Subject: [PATCH 030/565] Increase thread summary icon size --- vector/src/main/res/layout/view_thread_room_summary.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vector/src/main/res/layout/view_thread_room_summary.xml b/vector/src/main/res/layout/view_thread_room_summary.xml index 0f184edef3..6eeb62974d 100644 --- a/vector/src/main/res/layout/view_thread_room_summary.xml +++ b/vector/src/main/res/layout/view_thread_room_summary.xml @@ -6,8 +6,8 @@ Date: Tue, 22 Mar 2022 16:03:49 +0200 Subject: [PATCH 031/565] Fix timeline thread summary padding --- vector/src/main/res/layout/item_timeline_event_base.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vector/src/main/res/layout/item_timeline_event_base.xml b/vector/src/main/res/layout/item_timeline_event_base.xml index bc02728f6e..7b491b2dfa 100644 --- a/vector/src/main/res/layout/item_timeline_event_base.xml +++ b/vector/src/main/res/layout/item_timeline_event_base.xml @@ -154,8 +154,8 @@ android:layout_below="@id/informationBottom" android:layout_marginEnd="32dp" android:layout_marginBottom="4dp" - android:paddingStart="13dp" - android:paddingEnd="13dp" + android:paddingStart="12dp" + android:paddingEnd="12dp" android:layout_toEndOf="@id/messageStartGuideline" android:background="@drawable/rounded_rect_shape_8" android:contentDescription="@string/room_threads_filter" From 1e3b859f48940a9251b9c21f115dec2895d481ff Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Tue, 22 Mar 2022 16:19:15 +0200 Subject: [PATCH 032/565] Fix timeline thread summary width --- vector/src/main/res/layout/item_timeline_event_base.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/vector/src/main/res/layout/item_timeline_event_base.xml b/vector/src/main/res/layout/item_timeline_event_base.xml index 7b491b2dfa..722928a874 100644 --- a/vector/src/main/res/layout/item_timeline_event_base.xml +++ b/vector/src/main/res/layout/item_timeline_event_base.xml @@ -34,11 +34,11 @@ android:layout_marginStart="8dp" android:layout_marginTop="4dp" android:layout_marginEnd="4dp" - android:textAlignment="viewStart" android:layout_toStartOf="@id/messageTimeView" android:layout_toEndOf="@id/messageStartGuideline" android:ellipsize="end" android:maxLines="1" + android:textAlignment="viewStart" android:textColor="?vctr_content_primary" android:textStyle="bold" tools:text="@sample/users.json/data/displayName" /> @@ -152,16 +152,16 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/informationBottom" - android:layout_marginEnd="32dp" + android:layout_marginEnd="40dp" android:layout_marginBottom="4dp" - android:paddingStart="12dp" - android:paddingEnd="12dp" android:layout_toEndOf="@id/messageStartGuideline" android:background="@drawable/rounded_rect_shape_8" android:contentDescription="@string/room_threads_filter" android:maxWidth="496dp" android:minWidth="144dp" + android:paddingStart="12dp" android:paddingTop="8dp" + android:paddingEnd="12dp" android:paddingBottom="8dp" android:visibility="gone" tools:visibility="visible"> From bc9a785a591fe04da4ea8615b2a3524cf84e5f5d Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Tue, 22 Mar 2022 17:08:49 +0200 Subject: [PATCH 033/565] Match timeline thread summary width with the actual text --- vector/src/main/res/layout/item_timeline_event_base.xml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/vector/src/main/res/layout/item_timeline_event_base.xml b/vector/src/main/res/layout/item_timeline_event_base.xml index 722928a874..182e9c04a4 100644 --- a/vector/src/main/res/layout/item_timeline_event_base.xml +++ b/vector/src/main/res/layout/item_timeline_event_base.xml @@ -152,8 +152,10 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/informationBottom" - android:layout_marginEnd="40dp" + android:layout_marginEnd="48dp" android:layout_marginBottom="4dp" + android:layout_marginTop="4dp" + android:layout_marginStart="8dp" android:layout_toEndOf="@id/messageStartGuideline" android:background="@drawable/rounded_rect_shape_8" android:contentDescription="@string/room_threads_filter" From 249db1820f22067e394a98e8ce3a929795799454 Mon Sep 17 00:00:00 2001 From: ericdecanini Date: Tue, 22 Mar 2022 16:22:40 +0100 Subject: [PATCH 034/565] Removes ic_headphones --- vector/src/main/res/drawable/ic_headphones.xml | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 vector/src/main/res/drawable/ic_headphones.xml diff --git a/vector/src/main/res/drawable/ic_headphones.xml b/vector/src/main/res/drawable/ic_headphones.xml deleted file mode 100644 index 86f3d8ab7f..0000000000 --- a/vector/src/main/res/drawable/ic_headphones.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - From b1c4ca7816124a22f4c896fed9d91d2435a9fcd4 Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Tue, 22 Mar 2022 18:03:49 +0200 Subject: [PATCH 035/565] Simplify thread timeline toolbar menu more --- .../home/room/detail/TimelineViewModel.kt | 6 ++- vector/src/main/res/menu/menu_timeline.xml | 41 +++++++------------ 2 files changed, 19 insertions(+), 28 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt index a9235b5699..cba11b4ed7 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt @@ -706,8 +706,10 @@ class TimelineViewModel @AssistedInject constructor( if (initialState.isThreadTimeline()) { when (itemId) { - R.id.menu_thread_timeline_more -> true - else -> false + R.id.menu_thread_timeline_view_in_room, + R.id.menu_thread_timeline_copy_link, + R.id.menu_thread_timeline_share -> true + else -> false } } else { when (itemId) { diff --git a/vector/src/main/res/menu/menu_timeline.xml b/vector/src/main/res/menu/menu_timeline.xml index 962c505e4e..d035ce38eb 100644 --- a/vector/src/main/res/menu/menu_timeline.xml +++ b/vector/src/main/res/menu/menu_timeline.xml @@ -66,35 +66,24 @@ tools:visible="true" /> + app:showAsAction="never" /> - - - - - - - - + + \ No newline at end of file From f8e7ba7355e70b2448ae38e3c9d5125def07a065 Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Tue, 22 Mar 2022 18:43:01 +0200 Subject: [PATCH 036/565] Format menu_timeline --- .../features/home/room/detail/TimelineFragment.kt | 5 +++++ vector/src/main/res/menu/menu_timeline.xml | 12 ++++++------ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt index b66897d8d1..3c271710dd 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt @@ -40,6 +40,7 @@ import android.widget.TextView import android.widget.Toast import androidx.annotation.DrawableRes import androidx.annotation.StringRes +import androidx.appcompat.view.menu.MenuBuilder import androidx.core.content.ContextCompat import androidx.core.graphics.drawable.DrawableCompat import androidx.core.net.toUri @@ -982,7 +983,11 @@ class TimelineFragment @Inject constructor( } } + @SuppressLint("RestrictedApi") override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + if (isThreadTimeLine()) { + if (menu is MenuBuilder) menu.setOptionalIconsVisible(true) + } super.onCreateOptionsMenu(menu, inflater) // We use a custom layout for this menu item, so we need to set a ClickListener menu.findItem(R.id.open_matrix_apps)?.let { menuItem -> diff --git a/vector/src/main/res/menu/menu_timeline.xml b/vector/src/main/res/menu/menu_timeline.xml index d035ce38eb..e362ec7483 100644 --- a/vector/src/main/res/menu/menu_timeline.xml +++ b/vector/src/main/res/menu/menu_timeline.xml @@ -41,12 +41,13 @@ android:id="@+id/menu_timeline_thread_list" android:title="@string/action_view_threads" android:visible="false" - app:iconTint="?colorPrimary" app:actionLayout="@layout/view_thread_notification_badge" + app:iconTint="?colorPrimary" app:showAsAction="always" tools:visible="true" /> - @@ -70,20 +71,19 @@ android:icon="@drawable/ic_thread_view_in_room_menu_item" android:title="@string/action_thread_view_in_room" app:iconTint="?vctr_content_secondary" - app:showAsAction="never" /> + app:showAsAction="withText" /> + app:showAsAction="withText" /> - + app:showAsAction="withText" /> \ No newline at end of file From 72bc613f347c4c6fe13b839725f6b5870b261705 Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Tue, 22 Mar 2022 18:54:59 +0200 Subject: [PATCH 037/565] Reduce thread toolbar avatar size --- .../src/main/res/layout/view_room_detail_thread_toolbar.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vector/src/main/res/layout/view_room_detail_thread_toolbar.xml b/vector/src/main/res/layout/view_room_detail_thread_toolbar.xml index e16912246e..c58a8cd837 100644 --- a/vector/src/main/res/layout/view_room_detail_thread_toolbar.xml +++ b/vector/src/main/res/layout/view_room_detail_thread_toolbar.xml @@ -26,8 +26,8 @@ Date: Tue, 22 Mar 2022 17:56:07 +0100 Subject: [PATCH 038/565] Renames call option to be more agnostic --- .../app/features/call/conference/RemoveJitsiWidgetView.kt | 2 +- .../app/features/home/room/detail/RoomDetailViewState.kt | 2 +- .../app/features/home/room/detail/TimelineViewModel.kt | 6 +++--- vector/src/main/res/menu/menu_timeline.xml | 6 +++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/call/conference/RemoveJitsiWidgetView.kt b/vector/src/main/java/im/vector/app/features/call/conference/RemoveJitsiWidgetView.kt index fd7fc31e6d..e7659fb3e6 100644 --- a/vector/src/main/java/im/vector/app/features/call/conference/RemoveJitsiWidgetView.kt +++ b/vector/src/main/java/im/vector/app/features/call/conference/RemoveJitsiWidgetView.kt @@ -88,7 +88,7 @@ import org.matrix.android.sdk.api.session.room.model.Membership fun render(roomDetailViewState: RoomDetailViewState) { val summary = roomDetailViewState.asyncRoomSummary() val newState = if (summary?.membership != Membership.JOIN || - roomDetailViewState.isWebRTCCallOptionAvailable() || + roomDetailViewState.isCallOptionAvailable() || !roomDetailViewState.isAllowedToManageWidgets || roomDetailViewState.jitsiState.widgetId == null) { State.Unmount diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt index 84e618a8fe..09b5211246 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt @@ -87,7 +87,7 @@ data class RoomDetailViewState( rootThreadEventId = args.threadTimelineArgs?.rootThreadEventId ) - fun isWebRTCCallOptionAvailable() = asyncRoomSummary.invoke()?.isDirect ?: true + fun isCallOptionAvailable() = asyncRoomSummary.invoke()?.isDirect ?: true fun isSearchAvailable() = asyncRoomSummary()?.isEncrypted == false diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt index a9235b5699..a726aea61f 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt @@ -714,10 +714,10 @@ class TimelineViewModel @AssistedInject constructor( R.id.timeline_setting -> true R.id.invite -> state.canInvite R.id.open_matrix_apps -> true - R.id.voice_call -> state.isWebRTCCallOptionAvailable() - R.id.video_call -> state.isWebRTCCallOptionAvailable() || state.jitsiState.confId == null || state.jitsiState.hasJoined + R.id.voice_call -> state.isCallOptionAvailable() + R.id.video_call -> state.isCallOptionAvailable() || state.jitsiState.confId == null || state.jitsiState.hasJoined // Show Join conference button only if there is an active conf id not joined. Otherwise fallback to default video disabled. ^ - R.id.join_conference -> !state.isWebRTCCallOptionAvailable() && state.jitsiState.confId != null && !state.jitsiState.hasJoined + R.id.join_conference -> !state.isCallOptionAvailable() && state.jitsiState.confId != null && !state.jitsiState.hasJoined R.id.search -> state.isSearchAvailable() R.id.menu_timeline_thread_list -> vectorPreferences.areThreadMessagesEnabled() R.id.dev_tools -> vectorPreferences.developerMode() diff --git a/vector/src/main/res/menu/menu_timeline.xml b/vector/src/main/res/menu/menu_timeline.xml index 962c505e4e..98d1beece3 100644 --- a/vector/src/main/res/menu/menu_timeline.xml +++ b/vector/src/main/res/menu/menu_timeline.xml @@ -25,7 +25,7 @@ android:title="@string/action_video_call" android:visible="false" app:iconTint="?colorPrimary" - app:showAsAction="always" + app:showAsAction="ifRoom" tools:visible="true" /> Date: Tue, 22 Mar 2022 16:42:45 +0000 Subject: [PATCH 039/565] Translated using Weblate (Czech) Currently translated at 100.0% (52 of 52 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/cs/ --- fastlane/metadata/android/cs-CZ/changelogs/40104040.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/cs-CZ/changelogs/40104040.txt diff --git a/fastlane/metadata/android/cs-CZ/changelogs/40104040.txt b/fastlane/metadata/android/cs-CZ/changelogs/40104040.txt new file mode 100644 index 0000000000..38a29f5a5d --- /dev/null +++ b/fastlane/metadata/android/cs-CZ/changelogs/40104040.txt @@ -0,0 +1,2 @@ +Hlavní změny v této verzi: vylepšení indikátoru psaní. Opravy různých chyb a vylepšení stability. +Úplný seznam změn: https://github.com/vector-im/element-android/releases/tag/v1.4.4 From 41de155fb586b9d6c74c8b7acbfcb6cec7530cd8 Mon Sep 17 00:00:00 2001 From: libexus Date: Tue, 22 Mar 2022 17:03:18 +0000 Subject: [PATCH 040/565] Translated using Weblate (German) Currently translated at 100.0% (52 of 52 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/de/ --- fastlane/metadata/android/de-DE/changelogs/40104040.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/de-DE/changelogs/40104040.txt diff --git a/fastlane/metadata/android/de-DE/changelogs/40104040.txt b/fastlane/metadata/android/de-DE/changelogs/40104040.txt new file mode 100644 index 0000000000..8813f7f3c3 --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/40104040.txt @@ -0,0 +1,2 @@ +Neuer Tippindikator und Bugfixes +Alle Änderungen: https://github.com/vector-im/element-android/releases/tag/v1.4.4 From 720d830f5fc24caa2f30e1fdc058b5576e553cfa Mon Sep 17 00:00:00 2001 From: lvre <7uu3qrbvm@relay.firefox.com> Date: Tue, 22 Mar 2022 17:26:01 +0000 Subject: [PATCH 041/565] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (52 of 52 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/pt_BR/ --- fastlane/metadata/android/pt-BR/changelogs/40104040.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/pt-BR/changelogs/40104040.txt diff --git a/fastlane/metadata/android/pt-BR/changelogs/40104040.txt b/fastlane/metadata/android/pt-BR/changelogs/40104040.txt new file mode 100644 index 0000000000..87a61ec102 --- /dev/null +++ b/fastlane/metadata/android/pt-BR/changelogs/40104040.txt @@ -0,0 +1,2 @@ +Principais mudanças nesta versão: atualizações de UI de indicador de digitação. Vários consertos de bugs e melhorias de estabilidade. +Changelog completo: https://github.com/vector-im/element-android/releases/tag/v1.4.4 From 17d4e6c74502111c8e638276f4c9ed3daac493d9 Mon Sep 17 00:00:00 2001 From: Denys Nykula Date: Tue, 22 Mar 2022 16:39:03 +0000 Subject: [PATCH 042/565] Translated using Weblate (Ukrainian) Currently translated at 100.0% (52 of 52 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/uk/ --- fastlane/metadata/android/uk/changelogs/40104040.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/uk/changelogs/40104040.txt diff --git a/fastlane/metadata/android/uk/changelogs/40104040.txt b/fastlane/metadata/android/uk/changelogs/40104040.txt new file mode 100644 index 0000000000..313eb56045 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/40104040.txt @@ -0,0 +1,2 @@ +Основні зміни в цій версії: оновлено індикатор набору. Виправлено різні вади й удосконалено стабільність. +Вичерпний журнал змін: https://github.com/vector-im/element-android/releases/tag/v1.4.4 From a8969b6dfa0617309c11d917aa0732fcbad49889 Mon Sep 17 00:00:00 2001 From: Denys Nykula Date: Tue, 22 Mar 2022 20:25:23 +0000 Subject: [PATCH 043/565] Translated using Weblate (Ukrainian) Currently translated at 100.0% (2171 of 2171 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/uk/ --- vector/src/main/res/values-uk/strings.xml | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/vector/src/main/res/values-uk/strings.xml b/vector/src/main/res/values-uk/strings.xml index 2f34fc66fb..9e46fdebce 100644 --- a/vector/src/main/res/values-uk/strings.xml +++ b/vector/src/main/res/values-uk/strings.xml @@ -424,7 +424,7 @@ %d змін членства - Список учасників + Учасники Усі повідомлення Додати ярлик на головний екран Надіслати наліпку @@ -1496,7 +1496,7 @@ Запросити електронним листом Проведіть пальцем, щоб скасувати Записати голосове повідомлення - Вийти з простору + Вийти Керувати кімнатами %s запрошує вас Вас запрошено @@ -2511,4 +2511,23 @@ Згорнути %1$s, %2$s та інші %1$s і %2$s + Зупинити + Місцеперебування поширюється наживо + Якщо бажаєте поширювати місцеперебування наживо, ${app_name} потребує доступу до місцеперебування протягом усієї фонової роботи застосунку. +\nМи отримуватимемо ваше місцеперебування лише протягом обраного вами терміну. + Дозвольте доступ + Поширити ці координати + Поширити ці координати + Маркер обраних координат на карті + Поширити місцеперебування наживо + Поширити місцеперебування наживо + Поширити моє поточне місцеперебування + Поширити моє поточне місцеперебування + Перейти до поточного місцеперебування + Наближаємось до випуску тредів у загальнодоступну бета-версію. +\n +\nГотуючись, маємо дещо змінити: треди, створені досі, буде показано як звичайні відповіді. +\n +\nЦе буде одноразовий перехід, бо треди — тепер частина специфікації Matrix. + Незабаром бета-версія тредів 🎉 \ No newline at end of file From 8a1d008b3c00965c255242886100f85a8fcd09b5 Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Wed, 23 Mar 2022 11:29:06 +0200 Subject: [PATCH 044/565] Show keyboard when user first reply in a thread --- .../vector/app/features/home/room/detail/TimelineFragment.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt index 3c271710dd..09d82840bd 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt @@ -1463,6 +1463,10 @@ class TimelineFragment @Inject constructor( views.composerLayout.views.composerEmojiButton.isVisible = vectorPreferences.showEmojiKeyboard() + if(isThreadTimeLine() && timelineArgs.threadTimelineArgs?.startsThread == true){ + // Show keyboard when the user started a thread + views.composerLayout.views.composerEditText.showKeyboard(andRequestFocus = true) + } views.composerLayout.callback = object : MessageComposerView.Callback { override fun onAddAttachment() { if (!::attachmentTypeSelector.isInitialized) { From 6568091f298cc993b3f6355b7c41ce94bc88947d Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Wed, 23 Mar 2022 12:29:07 +0200 Subject: [PATCH 045/565] Improve thread list item UI --- .../home/room/threads/list/model/ThreadListItem.kt | 3 +++ vector/src/main/res/layout/item_thread.xml | 10 +++++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/model/ThreadListItem.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/model/ThreadListItem.kt index 2364e86166..385bb226a1 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/model/ThreadListItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/model/ThreadListItem.kt @@ -33,6 +33,7 @@ import im.vector.app.core.extensions.setLeftDrawable import im.vector.app.core.utils.DimensionConverter import im.vector.app.features.displayname.getBestName import im.vector.app.features.home.AvatarRenderer +import im.vector.app.features.themes.ThemeUtils import org.matrix.android.sdk.api.session.threads.ThreadNotificationState import org.matrix.android.sdk.api.util.MatrixItem @@ -60,9 +61,11 @@ abstract class ThreadListItem : VectorEpoxyModel() { holder.dateTextView.text = date if (rootMessageDeleted) { holder.rootMessageTextView.text = holder.view.context.getString(R.string.event_redacted) + holder.rootMessageTextView.setTextColor(ThemeUtils.getColor(holder.view.context, R.attr.vctr_content_secondary)) holder.rootMessageTextView.setLeftDrawable(R.drawable.ic_trash_16, R.attr.vctr_content_tertiary) holder.rootMessageTextView.compoundDrawablePadding = DimensionConverter(holder.view.context.resources).dpToPx(10) } else { + holder.rootMessageTextView.setTextColor(ThemeUtils.getColor(holder.view.context, R.attr.vctr_content_primary)) holder.rootMessageTextView.text = rootMessage holder.rootMessageTextView.clearDrawables() } diff --git a/vector/src/main/res/layout/item_thread.xml b/vector/src/main/res/layout/item_thread.xml index 37186f031c..921f0663b1 100644 --- a/vector/src/main/res/layout/item_thread.xml +++ b/vector/src/main/res/layout/item_thread.xml @@ -73,8 +73,8 @@ style="@style/Widget.Vector.TextView.Body" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginTop="3dp" - android:layout_marginEnd="25dp" + android:layout_marginTop="2dp" + android:layout_marginEnd="28dp" android:ellipsize="end" android:maxLines="2" android:textColor="?vctr_content_primary" @@ -87,12 +87,12 @@ android:id="@+id/threadSummaryConstraintLayout" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginEnd="25dp" + android:layout_marginEnd="28dp" android:contentDescription="@string/room_threads_filter" android:maxWidth="496dp" android:minWidth="144dp" - android:paddingTop="10dp" - android:paddingBottom="12dp" + android:paddingTop="8dp" + android:paddingBottom="8dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="@id/threadSummaryTitleTextView" app:layout_constraintTop_toBottomOf="@id/threadSummaryRootMessageTextView" From d232c49d65706855e220fddd52f78fef3faf017b Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Wed, 23 Mar 2022 13:33:55 +0200 Subject: [PATCH 046/565] Remove filter toolbar on thread list while there no threads to display --- .../app/features/home/room/detail/TimelineFragment.kt | 2 +- .../room/threads/list/views/ThreadListFragment.kt | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt index 09d82840bd..872205d95a 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt @@ -1463,7 +1463,7 @@ class TimelineFragment @Inject constructor( views.composerLayout.views.composerEmojiButton.isVisible = vectorPreferences.showEmojiKeyboard() - if(isThreadTimeLine() && timelineArgs.threadTimelineArgs?.startsThread == true){ + if (isThreadTimeLine() && timelineArgs.threadTimelineArgs?.startsThread == true) { // Show keyboard when the user started a thread views.composerLayout.views.composerEditText.showKeyboard(andRequestFocus = true) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt index d5659efa49..d00fcc9eb7 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt @@ -18,6 +18,7 @@ package im.vector.app.features.home.room.threads.list.views import android.os.Bundle import android.view.LayoutInflater +import android.view.Menu import android.view.MenuItem import android.view.View import android.view.ViewGroup @@ -76,6 +77,15 @@ class ThreadListFragment @Inject constructor( } } + override fun onPrepareOptionsMenu(menu: Menu) { + withState(threadListViewModel) { state -> + when (threadListViewModel.canHomeserverUseThreading()) { + true -> menu.findItem(R.id.menu_thread_list_filter).isVisible = !state.threadSummaryList.invoke().isNullOrEmpty() + false -> menu.findItem(R.id.menu_thread_list_filter).isVisible = !state.rootThreadEventList.invoke().isNullOrEmpty() + } + } + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) initToolbar() @@ -102,6 +112,7 @@ class ThreadListFragment @Inject constructor( } override fun invalidate() = withState(threadListViewModel) { state -> + invalidateOptionsMenu() renderEmptyStateIfNeeded(state) threadListController.update(state) } From 82a6ea9d857d752804a909afb882c599ac6dd2cf Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Wed, 23 Mar 2022 13:40:32 +0200 Subject: [PATCH 047/565] Change thread list filtering radio buttons color --- .../home/room/threads/list/views/ThreadListBottomSheet.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListBottomSheet.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListBottomSheet.kt index 7ad4804e5b..07684a796e 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListBottomSheet.kt @@ -61,11 +61,11 @@ class ThreadListBottomSheet : VectorBaseBottomSheetDialogFragment Date: Wed, 23 Mar 2022 15:13:15 +0200 Subject: [PATCH 048/565] Thread list filtering minor UI changes --- vector/src/main/res/layout/bottom_sheet_thread_list.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vector/src/main/res/layout/bottom_sheet_thread_list.xml b/vector/src/main/res/layout/bottom_sheet_thread_list.xml index e736f30edc..4d1f1eb043 100644 --- a/vector/src/main/res/layout/bottom_sheet_thread_list.xml +++ b/vector/src/main/res/layout/bottom_sheet_thread_list.xml @@ -5,7 +5,7 @@ android:layout_height="match_parent" android:background="?colorSurface" android:orientation="vertical" - android:padding="8dp"> + android:padding="16dp"> Date: Wed, 23 Mar 2022 15:15:30 +0000 Subject: [PATCH 049/565] Only run sonar on nightly runs --- .github/workflows/nightly.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 7f789b4610..4affffe143 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -293,7 +293,7 @@ jobs: sonarqube: name: Sonarqube upload runs-on: macos-latest - if: always() + if: always() && github.event_name == "schedule" needs: - codecov-units steps: @@ -319,7 +319,7 @@ jobs: env: ORG_GRADLE_PROJECT_SONAR_LOGIN: ${{ secrets.SONAR_TOKEN }} -# Notify the channel about scheduled runs, do not notify for manually triggered runs +# Notify the channel about scheduled runs, or pushes to the release branches, do not notify for manually triggered runs notify: name: Notify matrix runs-on: ubuntu-latest From f7f115e4dc03d466d57d49c9ff957e66b6881790 Mon Sep 17 00:00:00 2001 From: Michael Kaye <1917473+michaelkaye@users.noreply.github.com> Date: Wed, 23 Mar 2022 15:19:30 +0000 Subject: [PATCH 050/565] Be clearer on which test run we're running & don't run sonarqube on release branches. --- .github/workflows/nightly.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 4affffe143..59f4169452 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -335,5 +335,5 @@ jobs: github_token: ${{ secrets.GITHUB_TOKEN }} matrix_access_token: ${{ secrets.ELEMENT_ANDROID_NOTIFICATION_ACCESS_TOKEN }} matrix_room_id: ${{ secrets.ELEMENT_ANDROID_INTERNAL_ROOM_ID }} - text_template: "Nightly test run: {{#each job_statuses }}{{#with this }}{{#if completed }} {{name}} {{conclusion}} at {{completed_at}}, {{/if}}{{/with}}{{/each}}" - html_template: "Nightly test run results: {{#each job_statuses }}{{#with this }}{{#if completed }}
{{icon conclusion}} {{name}} {{conclusion}} at {{completed_at}} [details]{{/if}}{{/with}}{{/each}}" + text_template: "{{#if ${{ github.event_name }} == 'schedule' }}Nightly test run{{else}}Test run (on ${{ github.ref }}){{/if }}: {{#each job_statuses }}{{#with this }}{{#if completed }} {{name}} {{conclusion}} at {{completed_at}}, {{/if}}{{/with}}{{/each}}" + html_template: "{{#if ${{ github.event_name }} == 'schedule' }}Nightly test run{{else}}Test run (on ${{ github.ref }}){{/if}}: {{#each job_statuses }}{{#with this }}{{#if completed }}
{{icon conclusion}} {{name}} {{conclusion}} at {{completed_at}} [details]{{/if}}{{/with}}{{/each}}" From 481274088ca6b028ea66fb8259f94c761c0dafd3 Mon Sep 17 00:00:00 2001 From: ravit Date: Thu, 24 Mar 2022 13:16:12 +0000 Subject: [PATCH 051/565] Translated using Weblate (Hebrew) Currently translated at 95.5% (2075 of 2171 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/he/ --- vector/src/main/res/values-iw/strings.xml | 62 +++++++++++++++++++++-- 1 file changed, 59 insertions(+), 3 deletions(-) diff --git a/vector/src/main/res/values-iw/strings.xml b/vector/src/main/res/values-iw/strings.xml index b70034888f..d5e8bf021e 100644 --- a/vector/src/main/res/values-iw/strings.xml +++ b/vector/src/main/res/values-iw/strings.xml @@ -1521,7 +1521,7 @@ זוהי ההתחלה של היסטוריית ההודעות הישירות שלך עם%s. זו תחילתה של השיחה הזו. זו ההתחלה ש ל%s. - הצתרפתם. + הצטרפתם. %s הצטרף. יצרת ותגדרת את החדר. %s יצר את החדר והגדיר אותו. @@ -2361,9 +2361,9 @@ \n ניתן לקרוא את כל התנאים %s. התוצאה הסופית מתבססת על הצבעה %1$d - + התוצאה הסופית מתבססת על הצבעות %1$d - + בחר אילו מרחבים יכולים לגשת לחדר זה. בבחירת מרחב החברים יוכלו למצוא ולהצטרף לחדר. חברים במרחב %s יכולים למצוא , לצפות ולהצטרף. @@ -2417,4 +2417,60 @@ חדר@ הודעות קבוצתיות הודעות ישירות מוצפנות + ההזמנה למרחב זה נשלחה מ%s אשר אינו משוייך לחשבונך + ההזמנה לחדר זה נשלחה מ%s אשר אינו משוייך לחשבונך + הודעה קולית (%1$s) + לא ניתן להשיב או לערוך במהלך הודעה קולית + לא ניתן להקליט את ההודעה הקולית + לא ניתן לנגן את ההודעה הקולית + לחץ על הקלטה כדי לעצור או להאזין + נשארו עוד %1$d שניות + החזק להקלטה, שחרר לשיחה + נגן הודעה קולית + השהה הודעה קולית + מחיקת הקלטה + מקליט הודעה קולית + עצור הקלטה + החלק לביטול + הקלט שיחה קולית + סליחה , אירעה שגיאה בזמן נסיון ההצטרפות לחדר:%s + שדרג לגרסת החדר המומלצת + חדר זה נמצא בשדרוג גרסה %s , לכן הוא לא יציב . + נדרשת הרשאה לשדרוג החדר + אחראי המרחב מעודכן אוטומטית + הזמן משתמשים אוטומטית + תשדרג את החדר מ%1$s ל%2$s. + שדרג חדר פרטי + שדרג חדר ציבורי + נדרש שדרוג + שדרוג + אנא המתן, זה יכול לקחת קצת זמן. + הצטרפו לחדר שהוחלף + + כניסה %d + שתי כניסות %d + %d כניסות + %d כניסות + + ההצפנה מוגדרת בצורה שגויה + שרשורים מתקרבים לבטא 🎉 + שלח את המדיה בגודלה המקורי + + שלח סרט וידאו בגודל המקורי + שלח שני סרטי וידאו בגודל המקורי + שלח סרטי וידאו בגודלם המקורי + שלח סרט וידאו בגודל המקורי + + לא יציב + יציב + גרסת ברירת מחדל + גרסאות חדר 👓 + המגבלה אינה ידועה. + שרת הבית שלך מקבל קבצים מצורפים (קבצים, מדיה וכו\') בגודל של עד %s. + מגבלת העלאת קובץ לשרת + גרסת שרת + שם שרת + ביטול צבע + שחזור הצפנה + אנא צור קשר עם מנהל המערכת לשחזור ההצפנה למצב תקין . \ No newline at end of file From 516e548fcd85c2419f4ba0257f1e34e41821523d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=AD=D0=B4=D1=83=D0=B0=D1=80=D0=B4=20=D0=93=D0=B5=D1=80?= =?UTF-8?q?=D0=B0?= Date: Thu, 24 Mar 2022 08:25:18 +0000 Subject: [PATCH 052/565] Translated using Weblate (Hebrew) Currently translated at 95.5% (2075 of 2171 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/he/ --- vector/src/main/res/values-iw/strings.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/vector/src/main/res/values-iw/strings.xml b/vector/src/main/res/values-iw/strings.xml index d5e8bf021e..da6f15ff34 100644 --- a/vector/src/main/res/values-iw/strings.xml +++ b/vector/src/main/res/values-iw/strings.xml @@ -2473,4 +2473,8 @@ ביטול צבע שחזור הצפנה אנא צור קשר עם מנהל המערכת לשחזור ההצפנה למצב תקין . + חדר ללא שם + חלק מהחדרים עשויים להיות מוסתרים כי הם פרטיים ואתה זקוק להזמנה. + חלק מהחדרים עשויים להיות מוסתרים כי הם פרטיים ואתה זקוק להזמנה. +\nאין לך הרשאה להוסיף חדרים. \ No newline at end of file From 3525d82733a76ee0c04392074f37538e89451ca6 Mon Sep 17 00:00:00 2001 From: Michael Kaye <1917473+michaelkaye@users.noreply.github.com> Date: Wed, 23 Mar 2022 15:23:12 +0000 Subject: [PATCH 053/565] Make test use quoted strings --- .github/workflows/nightly.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 59f4169452..b8f9f83f8e 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -335,5 +335,5 @@ jobs: github_token: ${{ secrets.GITHUB_TOKEN }} matrix_access_token: ${{ secrets.ELEMENT_ANDROID_NOTIFICATION_ACCESS_TOKEN }} matrix_room_id: ${{ secrets.ELEMENT_ANDROID_INTERNAL_ROOM_ID }} - text_template: "{{#if ${{ github.event_name }} == 'schedule' }}Nightly test run{{else}}Test run (on ${{ github.ref }}){{/if }}: {{#each job_statuses }}{{#with this }}{{#if completed }} {{name}} {{conclusion}} at {{completed_at}}, {{/if}}{{/with}}{{/each}}" - html_template: "{{#if ${{ github.event_name }} == 'schedule' }}Nightly test run{{else}}Test run (on ${{ github.ref }}){{/if}}: {{#each job_statuses }}{{#with this }}{{#if completed }}
{{icon conclusion}} {{name}} {{conclusion}} at {{completed_at}} [details]{{/if}}{{/with}}{{/each}}" + text_template: "{{#if '${{ github.event_name }}' == 'schedule' }}Nightly test run{{else}}Test run (on ${{ github.ref }}){{/if }}: {{#each job_statuses }}{{#with this }}{{#if completed }} {{name}} {{conclusion}} at {{completed_at}}, {{/if}}{{/with}}{{/each}}" + html_template: "{{#if '${{ github.event_name }}' == 'schedule' }}Nightly test run{{else}}Test run (on ${{ github.ref }}){{/if}}: {{#each job_statuses }}{{#with this }}{{#if completed }}
{{icon conclusion}} {{name}} {{conclusion}} at {{completed_at}} [details]{{/if}}{{/with}}{{/each}}" From 164ac0ee80cebea2fbc86cf5721eccb5bcd1d928 Mon Sep 17 00:00:00 2001 From: Michael Kaye <1917473+michaelkaye@users.noreply.github.com> Date: Thu, 24 Mar 2022 13:40:52 +0000 Subject: [PATCH 054/565] Update nightly.yml Clean up formatting of if --- .github/workflows/nightly.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index b8f9f83f8e..4efca05d36 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -336,4 +336,4 @@ jobs: matrix_access_token: ${{ secrets.ELEMENT_ANDROID_NOTIFICATION_ACCESS_TOKEN }} matrix_room_id: ${{ secrets.ELEMENT_ANDROID_INTERNAL_ROOM_ID }} text_template: "{{#if '${{ github.event_name }}' == 'schedule' }}Nightly test run{{else}}Test run (on ${{ github.ref }}){{/if }}: {{#each job_statuses }}{{#with this }}{{#if completed }} {{name}} {{conclusion}} at {{completed_at}}, {{/if}}{{/with}}{{/each}}" - html_template: "{{#if '${{ github.event_name }}' == 'schedule' }}Nightly test run{{else}}Test run (on ${{ github.ref }}){{/if}}: {{#each job_statuses }}{{#with this }}{{#if completed }}
{{icon conclusion}} {{name}} {{conclusion}} at {{completed_at}} [details]{{/if}}{{/with}}{{/each}}" + html_template: "{{#if '${{ github.event_name }}' == 'schedule' }}Nightly test run{{else}}Test run (on ${{ github.ref }}){{/if }}: {{#each job_statuses }}{{#with this }}{{#if completed }}
{{icon conclusion}} {{name}} {{conclusion}} at {{completed_at}} [details]{{/if}}{{/with}}{{/each}}" From 3182c60d132c69e7876ad8a36f262fb03ed2599f Mon Sep 17 00:00:00 2001 From: Michael Kaye <1917473+michaelkaye@users.noreply.github.com> Date: Thu, 24 Mar 2022 13:42:25 +0000 Subject: [PATCH 055/565] Quoe 'schedule' correctly. --- .github/workflows/nightly.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 4efca05d36..f4bcef2d4e 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -293,7 +293,7 @@ jobs: sonarqube: name: Sonarqube upload runs-on: macos-latest - if: always() && github.event_name == "schedule" + if: always() && github.event_name == 'schedule' needs: - codecov-units steps: From 3c7495bd605da1893f9a2055e511373ac3fdfb0a Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Thu, 24 Mar 2022 13:50:07 +0200 Subject: [PATCH 056/565] Thread redaction will now update the thread summary counter Root threads with 0 threads replies will become normal messages and removed from thread summaries --- .../api/session/events/model/UnsignedData.kt | 2 + .../database/helper/ThreadEventsHelper.kt | 51 +++++++++++++++---- .../room/prune/RedactionEventProcessor.kt | 38 ++++++++++++++ 3 files changed, 82 insertions(+), 9 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/UnsignedData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/UnsignedData.kt index dfe1db7b1c..630a2fb91a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/UnsignedData.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/UnsignedData.kt @@ -46,3 +46,5 @@ data class UnsignedData( @Json(name = "replaces_state") val replacesState: String? = null ) + +fun UnsignedData?.isRedacted() = this?.redactedEvent != null diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt index ee3008d40b..0e85057f23 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt @@ -16,9 +16,12 @@ package org.matrix.android.sdk.internal.database.helper +import com.squareup.moshi.JsonDataException import io.realm.Realm import io.realm.RealmQuery import io.realm.Sort +import org.matrix.android.sdk.api.session.events.model.UnsignedData +import org.matrix.android.sdk.api.session.events.model.isRedacted import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.threads.ThreadNotificationState import org.matrix.android.sdk.internal.database.mapper.asDomain @@ -33,6 +36,8 @@ import org.matrix.android.sdk.internal.database.query.findIncludingEvent import org.matrix.android.sdk.internal.database.query.findLastForwardChunkOfRoom import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.database.query.whereRoomId +import org.matrix.android.sdk.internal.di.MoshiProvider +import timber.log.Timber private typealias Summary = Pair? @@ -90,19 +95,18 @@ internal fun EventEntity.markEventAsRoot( /** * Count the number of threads for the provided root thread eventId, and finds the latest event message + * note: Redactions are handled by RedactionEventProcessor * @param rootThreadEventId The root eventId that will find the number of threads * @return A ThreadSummary containing the counted threads and the latest event message */ internal fun EventEntity.threadSummaryInThread(realm: Realm, rootThreadEventId: String, chunkEntity: ChunkEntity?): Summary { - // Number of messages - val messages = TimelineEventEntity - .whereRoomId(realm, roomId = roomId) - .equalTo(TimelineEventEntityFields.ROOT.ROOT_THREAD_EVENT_ID, rootThreadEventId) - .distinct(TimelineEventEntityFields.ROOT.EVENT_ID) - .count() - .toInt() + val numberOfThread = countThreads( + realm = realm, + roomId = roomId, + rootThreadEventId = rootThreadEventId + ) ?: return null - if (messages <= 0) return null + if (numberOfThread <= 0) return null // Find latest thread event, we know it exists var chunk = ChunkEntity.findLastForwardChunkOfRoom(realm, roomId) ?: chunkEntity ?: return null @@ -124,9 +128,38 @@ internal fun EventEntity.threadSummaryInThread(realm: Realm, rootThreadEventId: result ?: return null - return Summary(messages, result) + return Summary(numberOfThread, result) } +/** + * Counts the number of threads in the main timeline thread summary, + * with respect to redactions. + */ +internal fun countThreads(realm: Realm, roomId: String, rootThreadEventId: String): Int? = + TimelineEventEntity + .whereRoomId(realm, roomId = roomId) + .equalTo(TimelineEventEntityFields.ROOT.ROOT_THREAD_EVENT_ID, rootThreadEventId) + .distinct(TimelineEventEntityFields.ROOT.EVENT_ID) + .findAll() + ?.filterNot { timelineEvent -> + timelineEvent.root + ?.unsignedData + ?.takeIf { it.isNotBlank() } + ?.toUnsignedData() + .isRedacted() + }?.size + +/** + * Mapping string to UnsignedData using Moshi + */ +private fun String.toUnsignedData(): UnsignedData? = + try { + MoshiProvider.providesMoshi().adapter(UnsignedData::class.java).fromJson(this) + } catch (ex: JsonDataException) { + Timber.e(ex, "Failed to parse UnsignedData") + null + } + /** * Lets compare them in case user is moving forward in the timeline and we cannot know the * exact chunk sequence while currentChunk is not yet committed in the DB diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/prune/RedactionEventProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/prune/RedactionEventProcessor.kt index 4753e12157..4fcc47a8d4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/prune/RedactionEventProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/prune/RedactionEventProcessor.kt @@ -21,11 +21,14 @@ import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.LocalEcho import org.matrix.android.sdk.api.session.events.model.UnsignedData +import org.matrix.android.sdk.internal.database.helper.countThreads +import org.matrix.android.sdk.internal.database.helper.findRootThreadEvent import org.matrix.android.sdk.internal.database.mapper.ContentMapper import org.matrix.android.sdk.internal.database.mapper.EventMapper import org.matrix.android.sdk.internal.database.model.EventEntity import org.matrix.android.sdk.internal.database.model.EventInsertType import org.matrix.android.sdk.internal.database.model.TimelineEventEntity +import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntity import org.matrix.android.sdk.internal.database.query.findWithSenderMembershipEvent import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.MoshiProvider @@ -89,6 +92,8 @@ internal class RedactionEventProcessor @Inject constructor() : EventInsertLivePr eventToPrune.unsignedData = MoshiProvider.providesMoshi().adapter(UnsignedData::class.java).toJson(modified) eventToPrune.decryptionResultJson = null eventToPrune.decryptionErrorCode = null + + handleTimelineThreadSummaryIfNeeded(realm, eventToPrune, isLocalEcho) } // EventType.REACTION -> { // eventRelationsAggregationUpdater.handleReactionRedact(eventToPrune, realm, userId) @@ -104,6 +109,39 @@ internal class RedactionEventProcessor @Inject constructor() : EventInsertLivePr } } + /** + * Invalidates the number of threads in the main timeline thread summary, + * with respect to redactions. + */ + private fun handleTimelineThreadSummaryIfNeeded( + realm: Realm, + eventToPrune: EventEntity, + isLocalEcho: Boolean, + ) { + if (eventToPrune.isThread() && !isLocalEcho) { + val roomId = eventToPrune.roomId + val rootThreadEvent = eventToPrune.findRootThreadEvent() ?: return + val rootThreadEventId = eventToPrune.rootThreadEventId ?: return + + val numberOfThreads = countThreads( + realm = realm, + roomId = roomId, + rootThreadEventId = rootThreadEventId + ) ?: return + + rootThreadEvent.numberOfThreads = numberOfThreads + if (numberOfThreads == 0) { + // We should also clear the thread summary list + rootThreadEvent.isRootThread = false + rootThreadEvent.threadSummaryLatestMessage = null + ThreadSummaryEntity + .where(realm, roomId = roomId, rootThreadEventId) + .findFirst() + ?.deleteFromRealm() + } + } + } + private fun computeAllowedKeys(type: String): List { // Add filtered content, allowed keys in content depends on the event type return when (type) { From 806af4798a2e34f1a5deffe6ce7a98d1f2ec0ce4 Mon Sep 17 00:00:00 2001 From: ericdecanini Date: Thu, 24 Mar 2022 21:02:29 +0100 Subject: [PATCH 057/565] Fixes post merge errors --- .../features/home/room/detail/TimelineFragment.kt | 4 ++-- .../detail/timeline/factory/MessageItemFactory.kt | 2 +- .../room/detail/timeline/item/MessageVoiceItem.kt | 12 ++++++------ 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt index 587d3d3cc8..87327bbdc5 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt @@ -786,13 +786,13 @@ class TimelineFragment @Inject constructor( override fun onVoiceWaveformTouchedUp(percentage: Float, duration: Int) { messageComposerViewModel.handle( - MessageComposerAction.VoiceWaveformTouchedUp(VoiceMessagePlaybackTracker.RECORDING_ID, duration, percentage) + MessageComposerAction.VoiceWaveformTouchedUp(AudioMessagePlaybackTracker.RECORDING_ID, duration, percentage) ) } override fun onVoiceWaveformMoved(percentage: Float, duration: Int) { messageComposerViewModel.handle( - MessageComposerAction.VoiceWaveformTouchedUp(VoiceMessagePlaybackTracker.RECORDING_ID, duration, percentage) + MessageComposerAction.VoiceWaveformTouchedUp(AudioMessagePlaybackTracker.RECORDING_ID, duration, percentage) ) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt index 745f04b159..21050bf0cf 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -377,7 +377,7 @@ class MessageItemFactory @Inject constructor( .waveform(messageContent.audioWaveformInfo?.waveform?.toFft().orEmpty()) .playbackControlButtonClickListener(playbackControlButtonClickListener) .waveformTouchListener(waveformTouchListener) - .voiceMessagePlaybackTracker(audioMessagePlaybackTracker) + .audioMessagePlaybackTracker(audioMessagePlaybackTracker) .isLocalFile(localFilesHelper.isLocalFile(fileUrl)) .mxcUrl(fileUrl) .contentUploadStateTrackerBinder(contentUploadStateTrackerBinder) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt index f82504595d..d66b9445f5 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt @@ -75,7 +75,7 @@ abstract class MessageVoiceItem : AbsMessageItem() { var waveformTouchListener: WaveformTouchListener? = null @EpoxyAttribute - lateinit var voiceMessagePlaybackTracker: AudioMessagePlaybackTracker + lateinit var audioMessagePlaybackTracker: AudioMessagePlaybackTracker override fun bind(holder: Holder) { super.bind(holder) @@ -127,12 +127,12 @@ abstract class MessageVoiceItem : AbsMessageItem() { true } - voiceMessagePlaybackTracker.track(attributes.informationData.eventId, object : AudioMessagePlaybackTracker.Listener { + audioMessagePlaybackTracker.track(attributes.informationData.eventId, object : AudioMessagePlaybackTracker.Listener { override fun onUpdate(state: AudioMessagePlaybackTracker.Listener.State) { when (state) { - is AudioMessagePlaybackTracker.Listener.State.Idle -> renderIdleState(holder) - is AudioMessagePlaybackTracker.Listener.State.Playing -> renderPlayingState(holder, state) - is AudioMessagePlaybackTracker.Listener.State.Paused -> renderPausedState(holder, state) + is AudioMessagePlaybackTracker.Listener.State.Idle -> renderIdleState(holder, waveformColorIdle, waveformColorPlayed) + is AudioMessagePlaybackTracker.Listener.State.Playing -> renderPlayingState(holder, state, waveformColorIdle, waveformColorPlayed) + is AudioMessagePlaybackTracker.Listener.State.Paused -> renderPausedState(holder, state, waveformColorIdle, waveformColorPlayed) is AudioMessagePlaybackTracker.Listener.State.Recording -> Unit } } @@ -168,7 +168,7 @@ abstract class MessageVoiceItem : AbsMessageItem() { super.unbind(holder) contentUploadStateTrackerBinder.unbind(attributes.informationData.eventId) contentDownloadStateTrackerBinder.unbind(mxcUrl) - voiceMessagePlaybackTracker.untrack(attributes.informationData.eventId) + audioMessagePlaybackTracker.untrack(attributes.informationData.eventId) } override fun getViewStubId() = STUB_ID From 47415a8ef11f304111b5f9a9b628fcf6b8979d72 Mon Sep 17 00:00:00 2001 From: random Date: Wed, 23 Mar 2022 15:17:14 +0000 Subject: [PATCH 058/565] Translated using Weblate (Italian) Currently translated at 100.0% (2171 of 2171 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/it/ --- vector/src/main/res/values-it/strings.xml | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/vector/src/main/res/values-it/strings.xml b/vector/src/main/res/values-it/strings.xml index 492d8af526..d14aa3c671 100644 --- a/vector/src/main/res/values-it/strings.xml +++ b/vector/src/main/res/values-it/strings.xml @@ -494,7 +494,7 @@ Per segnalare un errore agita il dispositivo con rabbia Sicuro di voler fare una chiamata audio\? Sicuro di voler fare una videochiamata\? - Elenco dei membri + Membri %d utente %d utenti @@ -1948,7 +1948,7 @@ Sei stato invitato Gli Spazi sono un nuovo modo per raggruppare stanze e contatti. Aggiungi stanze e Spazi esistenti - Esci dallo Spazio + Esci Aggiungi stanze Guarda le stanze @@ -2410,4 +2410,23 @@ %1$s e %2$s %1$s, %2$s e altri + Ferma + Posizione in tempo reale attivata + Se vuoi condividere la tua posizione in tempo reale, ${app_name} richiede l\'accesso alla posizione costantemente quando l\'app è in secondo piano. +\nAccederemo alla tua posizione solo per la durata che scegli. + Permetti accesso + Condividi questa posizione + Condividi questa posizione + Condividi posizione in tempo reale + Condividi posizione in tempo reale + Condividi la mia posizione attuale + Condividi la mia posizione attuale + Ingrandisci alla posizione attuale + Puntina della posizione selezionata sulla mappa + Siamo vicini ad iniziare una beta pubblica delle conversazioni. +\n +\nMentre ci prepariamo, dobbiamo fare alcune modifiche: le conversazioni create prima di questo momento saranno mostrate come normali risposte. +\n +\nSarà una transizione una-tantum, dato che le conversazioni ora sono parte delle specifiche di Matrix. + I messaggi in conversazioni entrano in beta 🎉 \ No newline at end of file From ce28da3ae4d27cf58835445b6184a4349e412fc3 Mon Sep 17 00:00:00 2001 From: LinAGKar Date: Wed, 23 Mar 2022 21:24:30 +0000 Subject: [PATCH 059/565] Translated using Weblate (Swedish) Currently translated at 99.3% (2157 of 2171 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/sv/ --- vector/src/main/res/values-sv/strings.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vector/src/main/res/values-sv/strings.xml b/vector/src/main/res/values-sv/strings.xml index 69c25ebba3..11c38830f6 100644 --- a/vector/src/main/res/values-sv/strings.xml +++ b/vector/src/main/res/values-sv/strings.xml @@ -378,7 +378,7 @@ Ta bort Gå med Avslå - Lista medlemmar + Medlemmar Hoppa till oläst %d medlem @@ -2419,4 +2419,5 @@ %d server-ACL-ändring %d server-ACL-ändringar + Trådar närmar sig beta 🎉 \ No newline at end of file From f3deb3e16048da774b970493b8c0154f52db3a1c Mon Sep 17 00:00:00 2001 From: random Date: Wed, 23 Mar 2022 15:19:31 +0000 Subject: [PATCH 060/565] Translated using Weblate (Italian) Currently translated at 100.0% (52 of 52 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/it/ --- fastlane/metadata/android/it-IT/changelogs/40104040.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/it-IT/changelogs/40104040.txt diff --git a/fastlane/metadata/android/it-IT/changelogs/40104040.txt b/fastlane/metadata/android/it-IT/changelogs/40104040.txt new file mode 100644 index 0000000000..ef63e614cb --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/40104040.txt @@ -0,0 +1,2 @@ +Modifiche principali in questa versione: agg.mento indicatore scrittura. Correzioni errori e miglioramenti stabilità. +Cronologia completa: https://github.com/vector-im/element-android/releases/tag/v1.4.4 From 7352faa2d975010c80e931ca04f5fb84cef2c381 Mon Sep 17 00:00:00 2001 From: lvre <7uu3qrbvm@relay.firefox.com> Date: Tue, 22 Mar 2022 20:58:01 +0000 Subject: [PATCH 061/565] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (2171 of 2171 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/pt_BR/ --- vector/src/main/res/values-pt-rBR/strings.xml | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/vector/src/main/res/values-pt-rBR/strings.xml b/vector/src/main/res/values-pt-rBR/strings.xml index f2eaf8d993..d06937fb64 100644 --- a/vector/src/main/res/values-pt-rBR/strings.xml +++ b/vector/src/main/res/values-pt-rBR/strings.xml @@ -529,7 +529,7 @@ %d mudança de filiação %d mudanças de filiação - Listar membros + Membros %d mensagem notificada não-lida %d mensagens notificadas não-lidas @@ -2016,7 +2016,7 @@ Você é a/o única(o) admin deste espaço. Sair dele vai significar que ninguém tem controle sobre ele. Você não vai ser capaz de se rejuntar a menos que você seja re-convidada(o). Você é a única pessoa aqui. Se você sair, ninguém vai ser capaz de se juntar no futuro, incluindo você. - Sair de Espaço + Sair Adicionar salas Explorar salas @@ -2419,4 +2419,23 @@ %1$s, %2$s e outras(os) %1$s e %2$s + Localização ao vivo habilitada + Se você gostaria de compartilhar sua localização Ao Vivo, ${app_name} precisa de acesso de localização todo o tempo quando o app está no background. +\nNós vamos somente acessar sua localização pela duração que você escolher. + Compartilhar esta localização + Compartilhar esta localização + Compartilhar localização ao vivo + Compartilhar localização ao vivo + Compartilhar minha localização atual + Compartilhar minha localização atual + Zoom para localização atual + Pin de localização selecionada em mapa + Parar + Permitir acesso + Nós estamos ficando mais perto de lançar uma Beta pública para Threads. +\n +\nEnquanto nos preparamos para isso, nós precisamos fazer algumas mudanças: threads criadas antes deste ponto vão ser exibidas como respostas regulares. +\n +\nIsto vai ser uma transição única visto que Threads são agora parte da especificação Matrix. + Threads Aproximando-Se a Beta 🎉 \ No newline at end of file From 56fd8983c9c680783b197e082f50af0f74691ae2 Mon Sep 17 00:00:00 2001 From: Jozef Gaal Date: Tue, 22 Mar 2022 20:03:07 +0000 Subject: [PATCH 062/565] Translated using Weblate (Slovak) Currently translated at 100.0% (52 of 52 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/sk/ --- fastlane/metadata/android/sk/changelogs/40104040.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/sk/changelogs/40104040.txt diff --git a/fastlane/metadata/android/sk/changelogs/40104040.txt b/fastlane/metadata/android/sk/changelogs/40104040.txt new file mode 100644 index 0000000000..67f04df995 --- /dev/null +++ b/fastlane/metadata/android/sk/changelogs/40104040.txt @@ -0,0 +1,2 @@ +Hlavné zmeny v tejto verzii: aktualizácie používateľského rozhrania indikátora písania. Rôzne opravy chýb a vylepšenia stability. +Úplný zoznam zmien: https://github.com/vector-im/element-android/releases/tag/v1.4.4 From b9d10c9d6e03b7ad00e8e6dd601bc73969138146 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=AD=D0=B4=D1=83=D0=B0=D1=80=D0=B4=20=D0=93=D0=B5=D1=80?= =?UTF-8?q?=D0=B0?= Date: Thu, 24 Mar 2022 13:42:13 +0000 Subject: [PATCH 063/565] Translated using Weblate (Hebrew) Currently translated at 96.4% (2095 of 2171 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/he/ --- vector/src/main/res/values-iw/strings.xml | 47 +++++++++++++++++++++-- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/vector/src/main/res/values-iw/strings.xml b/vector/src/main/res/values-iw/strings.xml index da6f15ff34..fcc956ba35 100644 --- a/vector/src/main/res/values-iw/strings.xml +++ b/vector/src/main/res/values-iw/strings.xml @@ -2361,9 +2361,9 @@ \n ניתן לקרוא את כל התנאים %s. התוצאה הסופית מתבססת על הצבעה %1$d - - התוצאה הסופית מתבססת על הצבעות %1$d - + התוצאות הסופיות מתבססות על %1$d הצבעות + התוצאות הסופיות מתבססות על %1$d הצבעות + התוצאות הסופיות מתבססות על %1$d הצבעות בחר אילו מרחבים יכולים לגשת לחדר זה. בבחירת מרחב החברים יוכלו למצוא ולהצטרף לחדר. חברים במרחב %s יכולים למצוא , לצפות ולהצטרף. @@ -2477,4 +2477,45 @@ חלק מהחדרים עשויים להיות מוסתרים כי הם פרטיים ואתה זקוק להזמנה. חלק מהחדרים עשויים להיות מוסתרים כי הם פרטיים ואתה זקוק להזמנה. \nאין לך הרשאה להוסיף חדרים. + + הצבעה %1$d. הצביעו כדי לראות את התוצאות + הצבעות %1$d. הצביעו כדי לראות את התוצאות + הצבעות %1$d. הצביעו כדי לראות את התוצאות + הצבעות %1$d. הצביעו כדי לראות את התוצאות + + אין הצבעות + + מבוסס על %1$d הצבעה + מבוסס על %1$d הצבעות + מבוסס על %1$d הצבעות + מבוסס על %1$d הצבעות + + + %1$d הצבעה + %1$d הצבעות + %1$d הצבעות + %1$d הצבעות + + + נדרשת אפשרות %1$s לפחות + נדרשות %1$s אפשרויות לפחות + נדרשות %1$s אפשרויות לפחות + נדרשות %1$s אפשרויות לפחות + + %s בהגדרות כדי לקבל הזמנות ישירות ב-${app_name}. + אם תרצה לשתף את המיקום החי שלך, ${app_name} זקוק לגישה למיקום כל הזמן כשהאפליקציה נמצאת ברקע. +\nאנו ניגשים למיקום שלך רק למשך הזמן שתבחר. + אפשר גישה + שתף מיקום זה + שתף מיקום זה + שתף מיקום בזמן אמת + שתף מיקום בזמן אמת + שתף את המיקום הנוכחי + שתף את המיקום הנוכחי + זום למיקום הנוכחי + קיבוע של המיקום שנבחר במפה + עצור + מיקום בזמן אמת מופעל + שדרוג חדר הוא פעולה מתקדמת ומומלץ לרוב כאשר החדר אינו יציב עקב באגים, תכונות חסרות או פרצות אבטחה. +\nזה בדרך כלל משפיע רק על אופן עיבוד החדר בשרת. \ No newline at end of file From 604f38bfa3c024c3900a2b9bd32bc59e37ee7fbc Mon Sep 17 00:00:00 2001 From: ravit Date: Thu, 24 Mar 2022 13:23:39 +0000 Subject: [PATCH 064/565] Translated using Weblate (Hebrew) Currently translated at 96.4% (2095 of 2171 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/he/ --- vector/src/main/res/values-iw/strings.xml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/vector/src/main/res/values-iw/strings.xml b/vector/src/main/res/values-iw/strings.xml index fcc956ba35..639175efa8 100644 --- a/vector/src/main/res/values-iw/strings.xml +++ b/vector/src/main/res/values-iw/strings.xml @@ -2158,10 +2158,10 @@ צור משאל הוסף אפשרות אפשרות %1$d - צור אפשריות + צור אפשרויות שאלה או נושא - שאלה או נושא המשאל - צור משאל + שאלה או נושא הסקר + צור סקר אתחל את הישומון כדי שהשינויים יכנסו לתוקף. הצטרף בכל מקרה הצטרף למרחב @@ -2518,4 +2518,7 @@ מיקום בזמן אמת מופעל שדרוג חדר הוא פעולה מתקדמת ומומלץ לרוב כאשר החדר אינו יציב עקב באגים, תכונות חסרות או פרצות אבטחה. \nזה בדרך כלל משפיע רק על אופן עיבוד החדר בשרת. + כל מי שנמצא ב%s יוכל למצוא או להצטרף לחדר- אין צורך להזמין משתתפים. ניתן לשנות את הגדרת החדר בכל זמן. + אפשר LaTeX mathematics + שייך את הדואר האלקטרוני לחשבונך \ No newline at end of file From b2896dd2486943d73a7ea15b65eed3dc6b14eee8 Mon Sep 17 00:00:00 2001 From: Besnik Bleta Date: Tue, 22 Mar 2022 19:19:04 +0000 Subject: [PATCH 065/565] Translated using Weblate (Albanian) Currently translated at 100.0% (52 of 52 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/sq/ --- fastlane/metadata/android/sq/changelogs/40104040.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/sq/changelogs/40104040.txt diff --git a/fastlane/metadata/android/sq/changelogs/40104040.txt b/fastlane/metadata/android/sq/changelogs/40104040.txt new file mode 100644 index 0000000000..5cece335e3 --- /dev/null +++ b/fastlane/metadata/android/sq/changelogs/40104040.txt @@ -0,0 +1,2 @@ +Ndryshimet kryesore në këtë version: përditësime UI treguesi shtypjeje. Ndreqje të metash të ndryshme dhe përmirësime qëndrueshmërie. +Regjistër i plotë ndryshimesh: https://github.com/vector-im/element-android/releases/tag/v1.4.4 From 6537f7f69a93c7cc1f1335479937f66c3a425174 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Tue, 22 Mar 2022 20:40:00 +0000 Subject: [PATCH 066/565] Translated using Weblate (Estonian) Currently translated at 100.0% (52 of 52 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/et/ --- fastlane/metadata/android/et/changelogs/40104040.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/et/changelogs/40104040.txt diff --git a/fastlane/metadata/android/et/changelogs/40104040.txt b/fastlane/metadata/android/et/changelogs/40104040.txt new file mode 100644 index 0000000000..b4a0e059e3 --- /dev/null +++ b/fastlane/metadata/android/et/changelogs/40104040.txt @@ -0,0 +1,2 @@ +Põhilised muutused selles versioonis: kirjutusteatiste liidese uuendused ning pisiparandused ja stabiilsust parandavad kohendused. +Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases/tag/v1.4.4 From 237bb879552fe775a0c94875cae64d7e75f24530 Mon Sep 17 00:00:00 2001 From: Danial Behzadi Date: Wed, 23 Mar 2022 14:16:56 +0000 Subject: [PATCH 067/565] Translated using Weblate (Persian) Currently translated at 99.8% (2168 of 2171 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/fa/ --- vector/src/main/res/values-fa/strings.xml | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/vector/src/main/res/values-fa/strings.xml b/vector/src/main/res/values-fa/strings.xml index a3ee61013e..ab6bac5c1b 100644 --- a/vector/src/main/res/values-fa/strings.xml +++ b/vector/src/main/res/values-fa/strings.xml @@ -282,7 +282,7 @@ برداشتن پیوستن رد کردن - فهرست اعضا + اعضا در حال برقراری تماس… تماس پایان یافت تماس ویدئویی ورودی @@ -1957,7 +1957,7 @@ دعوت شده‌اید فضاها شیوه‌ای جدید برای گروه‌بندی اتاق‌ها و افراد است. افزودن فضا و اتاق‌های موجود - ترک فضا + ترک افزودن اتاق کاوش در اتاق‌ها @@ -2419,4 +2419,15 @@ نمایش کم‌تر %1$s، %2$s و دیگران %1$s و %2$s + توقّف + مکان زنده به کار افتاد + اجازهٔ دسترسی + هم‌رسانی این مکان + هم‌رسانی این مکان + هم‌رسانی مکان زنده + هم‌رسانی مکان زنده + هم‌رسانی مکان کنونیم + هم‌رسانی مکان کنونیم + بزرگ‌نمایی به مکان کنونی + سنجاق مکان گزیده روی نقشه \ No newline at end of file From 5e79c9367bcd1faee47dd8e634208510d7e0489a Mon Sep 17 00:00:00 2001 From: Linerly Date: Wed, 23 Mar 2022 11:16:08 +0000 Subject: [PATCH 068/565] Translated using Weblate (Indonesian) Currently translated at 100.0% (2171 of 2171 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/id/ --- vector/src/main/res/values-in/strings.xml | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/vector/src/main/res/values-in/strings.xml b/vector/src/main/res/values-in/strings.xml index 719b49fc4b..29831c1377 100644 --- a/vector/src/main/res/values-in/strings.xml +++ b/vector/src/main/res/values-in/strings.xml @@ -134,7 +134,7 @@ %d perubahan keanggotaan Panggilan - Daftar Anggota + Anggota Arahkan ke pesan yang belum dibaca %d anggota @@ -1727,7 +1727,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Anda tidak akan dapat bergabung lagi kecuali jika Anda diundang lagi. Anda orang satu-satunya di sini. Jika Anda tinggalkan, siapa saja tidak dapat bergabung di masa depan, termasuk Anda. Apakah Anda yakin untuk meninggalkan %s\? - Tinggalkan Space + Tinggalkan Tambahkan ruangan Jelajah ruangan @@ -2374,4 +2374,23 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. %1$s, %2$s dan lainnya %1$s dan %2$s + Berhenti + Lokasi langsung diaktifkan + Jika Anda ingin membagikan lokasi langsung Anda, ${app_name} membutuhkan akses ke lokasi selama aplikasinya di latar belakang. +\nKami hanya akan mengakses lokasi Anda untuk durasi yang Anda pilih. + Perbolehkan akses + Bagikan lokasi ini + Bagikan lokasi ini + Bagikan lokasi langsung + Bagikan lokasi langsung + Bagikan lokasi saya saat ini + Bagikan lokasi saya saat ini + Perbesar ke lokasi saat ini + Pin dari lokasi yang terpilih pada peta + Kami hampir dekat untuk meriliskan sebuah Beta publik untuk Utasan. +\n +\nSaat kami mempersiapkan, kami harus membuat beberapa perubahan: utasan dibuat sebelum titik ini akan ditampilkan sebagai balasan biasa. +\n +\nIni hanya transisi sekali, utasan sekarang sebagai bagian dari spesifikasi Matrix. + Utasan Mencapai Beta 🎉 \ No newline at end of file From 9317d42c49b14259f2066a3c970d18011e444fd0 Mon Sep 17 00:00:00 2001 From: Ihor Hordiichuk Date: Tue, 22 Mar 2022 22:35:40 +0000 Subject: [PATCH 069/565] Translated using Weblate (Ukrainian) Currently translated at 100.0% (2171 of 2171 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/uk/ --- vector/src/main/res/values-uk/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vector/src/main/res/values-uk/strings.xml b/vector/src/main/res/values-uk/strings.xml index 9e46fdebce..26d090e3ef 100644 --- a/vector/src/main/res/values-uk/strings.xml +++ b/vector/src/main/res/values-uk/strings.xml @@ -2526,8 +2526,8 @@ Перейти до поточного місцеперебування Наближаємось до випуску тредів у загальнодоступну бета-версію. \n -\nГотуючись, маємо дещо змінити: треди, створені досі, буде показано як звичайні відповіді. +\nГотуючись, маємо дещо змінити: раніше створені треди буде показано як звичайні відповіді. \n -\nЦе буде одноразовий перехід, бо треди — тепер частина специфікації Matrix. +\nЦе буде одноразовий перехід, оскільки треди — тепер частина специфікації Matrix. Незабаром бета-версія тредів 🎉 \ No newline at end of file From 0e91511db1d0e051e6c6a8f3e620544962177c63 Mon Sep 17 00:00:00 2001 From: Besnik Bleta Date: Tue, 22 Mar 2022 19:26:01 +0000 Subject: [PATCH 070/565] Translated using Weblate (Albanian) Currently translated at 99.3% (2157 of 2171 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/sq/ --- vector/src/main/res/values-sq/strings.xml | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/vector/src/main/res/values-sq/strings.xml b/vector/src/main/res/values-sq/strings.xml index 0fc5c7f6fb..fb7fb1448e 100644 --- a/vector/src/main/res/values-sq/strings.xml +++ b/vector/src/main/res/values-sq/strings.xml @@ -312,7 +312,7 @@ Hiqe Hyni Hidheni tej - Anëtarë liste + Anëtarë Hidhu te të palexuarit Dilni nga dhoma Jeni i sigurt se doni të dilni nga dhoma? @@ -1946,7 +1946,7 @@ Hapësirat janë mënyra për të grupuar dhoma dhe persona. Jeni ftuar Shtoni dhoma ekzistuese dhe hapësira - Braktiseni Hapësirën + Braktiseni Shtoni dhoma Eksploroni dhoma @@ -2408,4 +2408,21 @@ Njoftim dhome Shfaq më pak + Ndale + Vendndodhje Drejtpërsëdrejti e aktivizuar + Nëse do të donit të tregonit Vendndodhjen tuaj Drejtpërsëdrejti, ${app_name} lyp njohje të vendndodhjes gjithë kohën, kur aplikacioni gjenden në prapaskenë. +\nDo ta njohim vendndodhjen tuaj vetëm për kohëzgjatjen që zgjidhni ju. + Lejoni hyrje + Tregoje këtë vendndodhje + Tregoje këtë vendndodhje + Tregoje vendndodhjen time të tanishme + Tregoje vendndodhjen time të tanishme + “Zoom” te vendndodhja e tanishme + Piketë e vendndodhjes së përzgjedhur në hartë + Po i afrohemi hedhjes publike në qarkullim Beta për Rrjedha. +\n +\nTeksa përgatitemi për të, na duhet të bëjmë disa ndryshime: rrjedha të krijuara para kësaj pike do të shfaqen si përgjigje të rregullta. +\n +\nKy do të jetë tranzicion vetëm për një herë, meqë tanimë Rrjedhat janë pjesë e protokollit Matrix. + Rrjedhat Po i Afrohen Beta-s 🎉 \ No newline at end of file From df1cc6a14440af494c1f45d54af11c4fc1e52ca6 Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Wed, 23 Mar 2022 03:09:14 +0000 Subject: [PATCH 071/565] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (52 of 52 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/zh_Hant/ --- fastlane/metadata/android/zh-TW/changelogs/40104040.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/zh-TW/changelogs/40104040.txt diff --git a/fastlane/metadata/android/zh-TW/changelogs/40104040.txt b/fastlane/metadata/android/zh-TW/changelogs/40104040.txt new file mode 100644 index 0000000000..8949ec3486 --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/40104040.txt @@ -0,0 +1,2 @@ +此版本中的主要變動:輸入指示器使用者介面更新。許多臭蟲修復與穩定性改善。 +完整的變更紀錄:https://github.com/vector-im/element-android/releases/tag/v1.4.4 From c042e5f3cfff84f87c697bd376f432d8972e6832 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Wed, 23 Mar 2022 06:16:12 +0000 Subject: [PATCH 072/565] Translated using Weblate (Estonian) Currently translated at 99.9% (2169 of 2171 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/et/ --- vector/src/main/res/values-et/strings.xml | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/vector/src/main/res/values-et/strings.xml b/vector/src/main/res/values-et/strings.xml index 44025005d3..e320550323 100644 --- a/vector/src/main/res/values-et/strings.xml +++ b/vector/src/main/res/values-et/strings.xml @@ -682,7 +682,7 @@ SSL\'i viga: teise osapoole identiteeti ei õnnestu kontrollida. Väldi juhuslikke kõnesid Enne kõne algatamist küsi kinnitust - Osalejate loend + Osalejad %d osaleja %d osalejat @@ -1957,7 +1957,7 @@ Sa oled saanud kutse Kogukonnakeskused on uus võimalus siduda jututubasid ja inimesi. Lisa olemasolevaid jututubasid ja kogukonnakeskuseid - Lahku kogukonnakeskusest + Lahku kogukonnast Lisa jututuba Tutvu jututubadega @@ -2419,4 +2419,23 @@ %1$s, %2$s ning teised kasutajad %1$s ja %2$s + Lõpeta asukoha jagamine + Reaalajas asukoha jagamine on kasutusel + Kui sa soovid reaalajas oma asukohta jagada, siis ${app_name} vajab alati õigus asukohta tuvastada. Seda ka siis kui rakendus töötab taustal. +\nLigipääs asukohale on saadaval vaid sinu valitud ajavahemiku vältel. + Luba ligipääs asukohale + Jaga seda asukohta + Jaga seda asukohta + Jaga asukohta reaalajas + Jaga asukohta reaalajas + Jaga minu praegust asukohta + Jaga minu praegust asukohta + Suumi praeguse asukohani + Valitud asukoha marker kaardil + Me oleme valmis avaldama jutulõngade funktsionaalsuse beetaversiooni. +\n +\nValmistudes järgnevaks, me peame tegema natuke muudatusi: enne seda hetke loodud jutulõngad kuvatakse tavaliste vastustena. +\n +\nKuna jutulõngad on nüüd osa Matrix\'i spetsifikatsioonist, siis see on ühekordne muudatus. + Jutulõngad on peaaegu valmis 🎉 \ No newline at end of file From d526724eb11018eb8b4a34aaca46f65e7cf54ed5 Mon Sep 17 00:00:00 2001 From: Linerly Date: Wed, 23 Mar 2022 11:08:47 +0000 Subject: [PATCH 073/565] Translated using Weblate (Indonesian) Currently translated at 100.0% (52 of 52 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/id/ --- fastlane/metadata/android/id/changelogs/40104040.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/id/changelogs/40104040.txt diff --git a/fastlane/metadata/android/id/changelogs/40104040.txt b/fastlane/metadata/android/id/changelogs/40104040.txt new file mode 100644 index 0000000000..4f3ef77ed6 --- /dev/null +++ b/fastlane/metadata/android/id/changelogs/40104040.txt @@ -0,0 +1,2 @@ +Perubahan utama dalam versi ini: pembaruan UI indikator pengetikan. Beberapa perbaikan kutu dan perbaikan stabilitas. +Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.4.4 From 37b17932ca7a92359e9848193689ecd1b8014895 Mon Sep 17 00:00:00 2001 From: LinAGKar Date: Wed, 23 Mar 2022 21:23:43 +0000 Subject: [PATCH 074/565] Translated using Weblate (Swedish) Currently translated at 100.0% (52 of 52 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/sv/ --- fastlane/metadata/android/sv-SE/changelogs/40104040.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/sv-SE/changelogs/40104040.txt diff --git a/fastlane/metadata/android/sv-SE/changelogs/40104040.txt b/fastlane/metadata/android/sv-SE/changelogs/40104040.txt new file mode 100644 index 0000000000..0601b26873 --- /dev/null +++ b/fastlane/metadata/android/sv-SE/changelogs/40104040.txt @@ -0,0 +1,2 @@ +Huvudsakliga ändringar i den här versionen: gränssnittsuppdateringar för skrivindikator. Diverse buggfixar och stabilitetsförbättringar. +Full ändringslogg: https://github.com/vector-im/element-android/releases/tag/v1.4.4 From 345ce171ff99404f471dc87cc92d226a19ef87fc Mon Sep 17 00:00:00 2001 From: waclaw66 Date: Tue, 22 Mar 2022 18:55:58 +0000 Subject: [PATCH 075/565] Translated using Weblate (Czech) Currently translated at 100.0% (2171 of 2171 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/cs/ --- vector/src/main/res/values-cs/strings.xml | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/vector/src/main/res/values-cs/strings.xml b/vector/src/main/res/values-cs/strings.xml index c7e0f96433..55ead1f8fc 100644 --- a/vector/src/main/res/values-cs/strings.xml +++ b/vector/src/main/res/values-cs/strings.xml @@ -363,7 +363,7 @@ Vstoupit Prosím, spusťte ${app_name} na jiném zařízení, které může dešifrovat zprávu, aby poslalo klíče této relaci. Odmítnout - Zobrazit členy + Členové Přejít na nepřečtené %d člen @@ -1998,7 +1998,7 @@ Prostory Jste zváni Přidat existující místnosti a prostor - Opustit prostor + Opustit Přidat místnosti Prozkoumat místnosti @@ -2466,4 +2466,23 @@ Zobrazit méně %1$s, %2$s a další %1$s a %2$s + Zastavit + Poloha živě povolena + Pokud chcete sdílet svou polohu živě, ${název_aplikace} potřebuje přístup k poloze po celou dobu, kdy je aplikace na pozadí. +\nK vaší poloze budeme mít přístup pouze po dobu, kterou si zvolíte. + Povolit přístup + Sdílet tuto polohu + Sdílet tuto polohu + Sdílet polohu živě + Sdílet polohu živě + Sdílet moji současnou polohu + Sdílet moji současnou polohu + Přiblížit na současnou polohu + Připnout vybranou pozici na mapě + Blížíme se k vydání veřejné betaverze vláken. +\n +\nV rámci příprav na ni musíme provést několik změn: vlákna vytvořená před tímto okamžikem se budou zobrazovat jako běžné odpovědi. +\n +\nPůjde o jednorázový přechod, protože vlákna jsou nyní součástí Matrix specifikace. + Vlákna se blíží k betaverzi 🎉 \ No newline at end of file From 44f064c484101b2f91b853171ae18e4394047c30 Mon Sep 17 00:00:00 2001 From: Ultimator14 Date: Wed, 23 Mar 2022 23:12:34 +0000 Subject: [PATCH 076/565] Translated using Weblate (German) Currently translated at 99.2% (2154 of 2171 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/de/ --- vector/src/main/res/values-de/strings.xml | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/vector/src/main/res/values-de/strings.xml b/vector/src/main/res/values-de/strings.xml index 4c5131d452..866f05b3a6 100644 --- a/vector/src/main/res/values-de/strings.xml +++ b/vector/src/main/res/values-de/strings.xml @@ -123,8 +123,8 @@ Du hast das %1$s Widget entfernt %1$s hat das %2$s Widget modifiziert Du hast das %1$s Widget modifiziert - Admin - Moderation + Administrator + Moderator Standard Benutzerdefiniert (%1$d) Benutzerdefiniert @@ -1261,8 +1261,8 @@ Benutzerdefiniert Eingeladen Nutzer - Admin in %1$s - Moderation in %1$s + Administrator in %1$s + Moderator in %1$s Springen und als gelesen markieren ${app_name} kann keine Ereignisse vom Typ \'%1$s\' ${app_name} ist beim Verarbeiten des Ereignisinhalts mit der ID \'%1$s\' auf ein Problem gestoßen @@ -2418,4 +2418,14 @@ %d Server-ACL geändert %d Server-ACLs geändert + Beenden + Live-Standort aktiviert + Zugriff erlauben + Standort teilen + Standort teilen + Meinen Standort teilen + Meinen Standort teilen + Live-Standort teilen + Live-Standort teilen + Threads nähern sich der Beta 🎉 \ No newline at end of file From 9004d23f38cac4f9423b204769c3c6c761a4a470 Mon Sep 17 00:00:00 2001 From: Jozef Gaal Date: Tue, 22 Mar 2022 20:13:03 +0000 Subject: [PATCH 077/565] Translated using Weblate (Slovak) Currently translated at 100.0% (2171 of 2171 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/sk/ --- vector/src/main/res/values-sk/strings.xml | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/vector/src/main/res/values-sk/strings.xml b/vector/src/main/res/values-sk/strings.xml index 6650890ac3..bd3e86e40c 100644 --- a/vector/src/main/res/values-sk/strings.xml +++ b/vector/src/main/res/values-sk/strings.xml @@ -472,7 +472,7 @@ %d zmeny členstva %d zmien členstva - Zobraziť členov + Členovia 1 člen %d členovia @@ -1316,7 +1316,7 @@ Verejná miestnosť Kompresia obrázku… Vždy sa opýtať - Opustiť priestor + Opustiť Pridať miestnosti Preskúmať miestnosti Pripojiť sa k priestoru @@ -2466,4 +2466,23 @@ Opustiť miestnosť s daným id (alebo aktuálnu miestnosť, ak je prázdna) Varovná úroveň dôveryhodnosti Pripojiť + Zastaviť + Poloha v reálnom čase zapnutá + Ak chcete zdieľať svoju polohu v reálnom čase, ${app_name} potrebuje prístup k polohe po celý čas, kým je aplikácia na pozadí. +\nPrístup k vašej polohe bude len počas obdobia, ktoré si zvolíte. + Povoliť prístup + Zdieľať túto polohu + Zdieľať túto polohu + Zdieľať polohu v reálnom čase + Zdieľať polohu v reálnom čase + Zdieľať moju aktuálnu polohu + Zdieľať moju aktuálnu polohu + Priblížiť k aktuálnej polohe + Označiť vybranú lokalitu na mape + Blížime sa k vydaniu verejnej beta verzie pre vlákna. +\n +\nV rámci príprav na ňu musíme urobiť niekoľko zmien: vlákna vytvorené pred týmto bodom sa budú zobrazovať ako bežné odpovede. +\n +\nPôjde o jednorazový prechod, keďže vlákna sú teraz súčasťou špecifikácie Matrix. + Vlákna sa blížia k beta verzii 🎉 \ No newline at end of file From 2602c7afb541f1c524427bef0ab589d655e89717 Mon Sep 17 00:00:00 2001 From: Danial Behzadi Date: Wed, 23 Mar 2022 14:13:35 +0000 Subject: [PATCH 078/565] Translated using Weblate (Persian) Currently translated at 100.0% (52 of 52 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/fa/ --- fastlane/metadata/android/fa/changelogs/40104040.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/fa/changelogs/40104040.txt diff --git a/fastlane/metadata/android/fa/changelogs/40104040.txt b/fastlane/metadata/android/fa/changelogs/40104040.txt new file mode 100644 index 0000000000..6e8f4911ea --- /dev/null +++ b/fastlane/metadata/android/fa/changelogs/40104040.txt @@ -0,0 +1,2 @@ +تغییرات عمده در این نگارش: به‌روز رسانی‌های رابط کاربری نشانگر نوشتن. چندین رفع اشکال و بهبودهای پایداری‌. +گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases/tag/v1.4.4 From 57ad36175068e2b891f591f86a347fef20b5bf89 Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Wed, 23 Mar 2022 03:16:13 +0000 Subject: [PATCH 079/565] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (2171 of 2171 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/zh_Hant/ --- vector/src/main/res/values-zh-rTW/strings.xml | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/vector/src/main/res/values-zh-rTW/strings.xml b/vector/src/main/res/values-zh-rTW/strings.xml index 564a4ccf8a..781591ace7 100644 --- a/vector/src/main/res/values-zh-rTW/strings.xml +++ b/vector/src/main/res/values-zh-rTW/strings.xml @@ -326,7 +326,7 @@ 移除 加入 拒絕 - 列出成員 + 成員 跳到未讀 %d 個成員 @@ -1922,7 +1922,7 @@ 您被邀請了 空間是將聊天室與人們分組的新方式。 新增既有的聊天室與空間 - 離開空間 + 離開 新增聊天室 探索聊天室 @@ -2372,4 +2372,23 @@ %1$s 與 %2$s %1$s、%2$s 與其他人 + 停止 + 即時位置已啟用 + 若您想要分享您的即時位置,${app_name} 需要當應用程式在背景時隨時存取位置的權限。 +\n我們將只會在您選擇的期間內存取您的位置。 + 允許存取 + 分享此位置 + 分享此位置 + 分享即時位置 + 分享即時位置 + 分享我目前的位置 + 分享我目前的位置 + 縮放至目前位置 + 地圖上選定位置的圖釘 + 我們愈來愈接近將討論串釋出為公開測試版。 +\n +\n在我們為此做準備時,我們需要做出一些變動:先前建立的討論串將會顯示為一般回覆。 +\n +\n這會是一次性的過渡,因為討論串現在是 Matrix 規範的一部分了。 + 討論串接近測試版了 🎉 \ No newline at end of file From 129c6aa7fb193a091134fd81611ce82cb40f0370 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sveinn=20=C3=AD=20Felli?= Date: Wed, 23 Mar 2022 15:10:55 +0000 Subject: [PATCH 080/565] Translated using Weblate (Icelandic) Currently translated at 75.1% (1632 of 2171 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/is/ --- vector/src/main/res/values-is/strings.xml | 280 ++++++++++++++++++++-- 1 file changed, 257 insertions(+), 23 deletions(-) diff --git a/vector/src/main/res/values-is/strings.xml b/vector/src/main/res/values-is/strings.xml index ee34c9e314..6deeb7fac5 100644 --- a/vector/src/main/res/values-is/strings.xml +++ b/vector/src/main/res/values-is/strings.xml @@ -86,7 +86,7 @@ Lýstu vandamálinu þínu hér Taka þátt í spjallrás Notandanafn - Útskráning + Skrá út Leita Hefja raddsamtal Hefja myndsamtal @@ -107,7 +107,7 @@ Stórt Miðlungs Lítið - Samtal + Símtal Innhringing myndsamtals Innhringing raddsamtals Samtal í gangi… @@ -118,7 +118,7 @@ Fjarlægja Taka þátt Hafna - Listi yfir meðlimi + Meðlimir %d meðlimur %d meðlimir @@ -160,7 +160,7 @@ Tilkynningar Hunsaðir notendur Annað - Ítarlegt + Nánar Dulritun Tengiliðir á tæki Greiningar @@ -175,7 +175,7 @@ Tungumál notandaviðmóts Veldu tungumál Breyta lykilorði - eldra lykilorð + Núverandi lykilorð Nýtt lykilorð Mistókst að uppfæra lykilorð Lykilorðið þitt hefur verið uppfært @@ -191,12 +191,12 @@ Hver getur lesið ferilskráningu? Hver sem er Bannaðir notendur - Ítarlegt + Nánar Þema Afkóðunarvilla Heiti tækis Auðkenni setu - Dulritunarlykill tækis + Dulritunarlykill setu Flytja út Settu inn lykilsetningu Staðfestu lykilsetningu @@ -230,7 +230,7 @@ Hávært Dulrituð skilaboð Búa til - Heim + Upphafssíða Spjallrásir Ástæða: %1$s Auðkennismynd @@ -246,7 +246,7 @@ Inniheldur ekki gilt JSON Of margar beiðnir hafa verið sendar Samtal tengist… - Samtali lokið + Símtali lokið Boð um samtal Nota innbyggða myndavél Minnst á @@ -273,8 +273,8 @@ Fara í ólesið Banna Afbanna - Fela öll skilaboð frá þessum notanda - Sýna öll skilaboð frá þessum notanda + Hunsa + Hætta að hunsa Þú hefur ekki heimild til að senda skilaboð á þessa spjallrás. Gat ekki sannreynt auðkenni fjartengds þjóns. Bæta við á upphafsskjá @@ -298,7 +298,7 @@ Einungis meðlimir (síðan þeir skráðu sig) Innra auðkenni þessarar spjallrásar Veldu skrá yfir spjallrásir - Heiti heimaþjóns + Heiti þjóns %d ólesið tilkynnt skilaboð %d ólesin tilkynnt skilaboð @@ -521,7 +521,7 @@ %1$s tók %2$s úr banni. Ástæða: %3$s Þú fjarlægðir %1$s. Ástæða: %2$s %1$s fjarlægði %2$s. Ástæða: %3$s - Þú hafnaðir boðinu. Ástæða: %1$s + Þú hafnaðir boðinu. Ástæða: %2$s %1$s hafnaði boðinu. Ástæða: %2$s Þú hættir. Ástæða: %1$s %1$s hætti. Ástæða: %2$s @@ -562,7 +562,7 @@ %1$s fjarlægði %2$s viðmótshluta Þú bættir við %1$s viðmótshluta %1$s bætti við %2$s viðmótshluta - Þú samþykktir boð um að taka þátt í %1$s + Þú samþykktir boð um að taka þátt í %$s Þú afturkallaðir boðið til %1$s %1$s afturkallaði boðið til %2$s Þú afturkallaðir boð til %1$s um þátttöku í spjallrásinni @@ -746,7 +746,7 @@ Svæði eru ný leið til að hópa fólk og spjallrásir. Bæta við fyrirliggjandi svæðum Bæta við fyrirliggjandi spjallrásum - Yfirgefa svæði + Yfirgefa Bæta við spjallrásum Kanna spjallrásir Búa til svæði @@ -762,8 +762,8 @@ Almenningsspjallrás Eyða auðkennismynd Það kom upp villa við að fletta upp símanúmerinu - Sendir skilaboðið með snjókomu - Sendir skilaboðið með skrauti + Sendir skilaboðin með snjókomu + Sendir skilaboðin með skrauti Uppfærsla dulritunar tiltæk Sendir skilaboð sem óbreyttur texti án þess að túlka það sem markdown Dulritun ekki virk @@ -899,7 +899,7 @@ Notaðu lykilsetningu endurheimtu eða dulritunarlykil Sýsla með í öryggisafriti dulritunarlykla Nota öryggisafrit af lykli - Verja öryggisafrit + Varið öryggisafrit Eyða öryggisafriti Athuga ástand öryggisafrits Eyði öryggisafriti… @@ -972,8 +972,8 @@ Hver sem er getur fundið svæðið og tekið þátt Hver sem er getur fundið spjallrásina og tekið þátt Birta falda atburði í tímalínu - Hjálp og um - Rödd og myndband + Hjálp og um hugbúnaðinn + Tal og myndmerki Stillingar spjallrásar Umfjöllunarefni spjallrásar (valkvætt) Skipta um netkerfi @@ -1105,7 +1105,7 @@ NÁÐI ÞVÍ Stilla auðkennismynd Umfjöllunarefni - Nafn spjallrásar + Heiti spjallrásar Setja upp Ræsa myndavélina Stöðva myndavélina @@ -1385,13 +1385,13 @@ Taka notanda úr banni Banna notanda Fjarlægja notanda - Lækka niður um stig + Lækka í tign Ekkert svar Bíða Halda áfram Símtöl Alltaf spyrja - Aftan + Til baka Fram Heyrnartól Hátalari @@ -1540,4 +1540,238 @@ Engin breyting. %1$s gekk í hópinn Boðið þitt + Settu aftur inn öryggisfrasann þinn til að staðfesta hann. + Öryggisfrasi + Setja öryggisfrasa + Vista öryggislykilinn þinn + Nota öryggisfrasa + Nota öryggislykil + Yfirfarðu þennan tengil + Ef þú frumstillir allt + Næstum því búið! Bíð eftir staðfestingu… + Bæta við umræðuefni + Sannprófa þessa innskráningu + Skrá út úr þessari setu + Skilaboð við þennan notanda eru enda-í-enda dulrituð þannig að enginn annar getur lesið þau. + Skanna með þessu tæki + Skannaðu kóðann hinna + Skrá inn með Matrix-auðkenni + Skrá inn með Matrix-auðkenni + Samþykktu skilmálana til að halda áfram + Velja sérsniðinn heimaþjón + Velja Element Matrix þjónustur + Velja matrix.org + Þú gerðir þetta einungis aðgengilegt gegn boði. + %1$s gerði þetta einungis aðgengilegt gegn boði. + Þú gerðir spjallrás einungis aðgengilega gegn boði. + %1$s gerði spjallrás einungis aðgengilega gegn boði. + Þú gerðir spjallrásina opinbera fyrir hverja þá sem þekkja slóðina á hana. + %1$s gerði spjallrásina opinbera fyrir hverja þá sem þekkja slóðina á hana. + Ýttu lengi á spjallrás til að sjá fleiri valkosti + Útbúa nýtt beint samtal + Loka valmyndinni til að útbúa spjallrás… + Stöðva + Staðsetning í rauntíma virkjuð + Leyfa aðgang + Deila þessari staðsetningu + Deila þessari staðsetningu + Deila staðsetningu í rauntíma + Deila staðsetningu í rauntíma + Deila núverandi staðsetningu minni + Deila núverandi staðsetningu minni + Renna að núverandi staðsetningu + Pinni með valinni staðsetningu á landakorti + Get ekki tekið upp talskilaboð + Get ekki spilað þessi talskilaboð + Bjóða notendum sjálfvirkt + Merkja sem ekki-tillögu + Merkja sem tillögu + Þér er boðið + Uppgötvun (%s) + Aðeins á þessa spjallrás + Bjóða í %s + Bjóða með notandanafni eða tölvupóstfangi + Bjóða í %s + Þú ert ekki að hunsa neina notendur + Skrá teikn + Talskilaboð (%1$s) + Tek upp talskilaboð + Setja talskilaboð í bið + Renna til að hætta við + Taka upp talskilaboð + %s býður þér + Útbý svæði… + Þú getur breytt þessu síðar ef þarf + Mistókst að senda skilaboð + Efni atburðar + Breyta efni + Flytja lykil inn úr skrá + Ýti-tilkynningar eru óvirkar + Tókst ekki að taka notanda úr banni + Bannaður af %1$s + Afturkalla boð til %1$s\? + Afturkalla boð + Vista endurheimtulykil í + Riot heitir núna Element! + Varið öryggisafrit + Setja upp nýtt lykilorð notandaaðgangs… + Kross-undirritun + Virkja enda-í-enda dulritun… + Deildi staðsetningu sinni + Þú ert skráð/ur út + Þú ert skráð/ur út + Þetta notandanafn er þegar í notkun + Nýskrá inn í %1$s + Senda aftur + Settu hér inn símanúmer + Til baka í innskráningu + Staðfestingarpóstur var sendur til %1$s. + Athugaðu pósthólfið þitt + Þetta tölvupóstfang er ekki tengt við neinn notandaaðgang + Endurstilla lykilorð á %1$s + Þetta tölvupóstfang er ekki tengt við neinn notandaaðgang. + Premium-hýsing fyrir samtök/fyrirtæki + Halda áfram með SSO + Tengjast sérsniðnum þjóni + Tengjast Element Matrix þjónustum + Sérsniðnar og ítarlegar stillingar + Premium-hýsing fyrir samtök/fyrirtæki + Veldu netþjón + Þú ert við stjórnvölinn. + Stríðni + Þú gerðir engar breytingar + %1$s gerði engar breytingar + Fara út úr spjallrásinni + Fjarlægja úr litlum forgangi + Bæta í lítinn forgang + HUNSA NOTANDA + Tókst ekki að meðhöndla deiligögn + Villa kom upp þegar reynt var að sækja viðhengið. + Skráin er of stór til að senda hana inn. + + %d notandi las + %d notendur lásu + + %s las + %1$s og %2$s lásu + %1$s, %2$s og %3$s lásu + + %1$s, %2$s og %3$d til viðbótar lásu + %1$s, %2$s og %3$d til viðbótar lásu + + Hoppa neðst + Senda viðhengi + Auðkennisþjónninn er ekki með neina þjónustuskilmála + Settu inn slóð á auðkennisþjón + Samþykkir þú að senda þessar upplýsingar\? + Senda tölvupóstföng og símanúmer til %s + Gefa samþykki + Afturkalla samþykki mitt + Ef þú aftengist frá auðkennisþjóninum þínum, munu aðrir notendur ekki geta fundið þig og þú munt ekki geta boðið öðrum með símanúmeri eða tölvupósti. + Fela reglur fyrir auðkenningarþjón + Sýna reglur fyrir auðkenningarþjón + Skipta um auðkennisþjón + Opna uppgötvunarstillingar + Stilla auðkennisþjón + Vertu finnanlegur fyrir aðra + Þekktir notendur + Bý til spjallrás… + Bæta við með QR-kóða + Finnurðu ekki það sem þú leitar að\? + Sía samtöl… + Skráin %1$s hefur verið sótt! + Sendi smámynd (%1$s / %2$s) + Dulrita smámynd… + Lýstu tillögunni þinni hér + Skrifaðu tillöguna þína hér. + Settu inn tillögu + Fáðu aðstoð við að nota ${app_name} + Lagaleg atriði + session_name: + app_display_name: + push_key: + app_id: + Þú ert nú þegar að skoða þennan spjallþráð! + Þú ert nú þegar að skoða þessa spjallrás! + Útgáfa Matrix SDK + Þú ert ekki að nota neinn auðkennisþjón + %s vill sannreyna setuna þína + Beiðni um sannvottun + Setja upp varið öryggisafrit + Tapaðu aldrei dulrituðum skilaboðum + Verið er að öryggisafrita dulritunarlyklana þína. + Flytja út dulritunarlykla handvirkt + Tekur bann af notanda með uppgefið auðkenni + Þessi þjónn gefur ekki upp neina stefnu. + Stefna fyrir auðkenningarþjóninn þinn + Stefna fyrir heimaþjóninn þinn + Reglur ${app_name} + Við deilum ekki upplýsingum með utanaðkomandi aðilum + Við skráum ekki eða búum til snið með gögnum notendaaðganga + Veldu lit á LED, hljóð, titring… + Stilla þöglar tilkynningar + Stilla tilkynningar símtala + Stilla háværar tilkynningar + Hunsa bestun + ${app_name} er ekki háð bestun fyrir rafhlöðuendingu. + Gera takmarkanir óvirkar + Virkja keyrslu í ræsingu + Skráning á aðgangsteikni + Mistókst að ná FCM-teikni: +\n%1$s + Tókst að ná FCM-teikni: +\n%1$s + Firebase-teikn + Laga Play-þjónustur + ${app_name} notar Google Play þjónustur til að afhenda ýtitilkynningar en það lítur út fyrir að vera rangt stillt: +\n%1$s + Google Play Services APK er tiltækt og af nýjustu gerð. + Athugun á Play-þjónustum + Uppfæra einkaspjallrás + Uppfæra almenningsspjallrás + Yfirgefa allar spjallrásir og svæði + Bjóða með tölvupósti + Einkasvæði til að skipuleggja spjallrásirnar þínar + Hverjum ertu að vinna með\? + Efni atburðar + Ósvarað myndsímtal + Ósvarað símtal + Virkt myndsamtal + Virkt raddsamtal + Þú hafnaðir þessu símtali + QR-kóði var ekki skannaður! + Ógildur QR-kóði (ógild slóð)! + Deila með textaskilaboðum + Endursetja PIN-númer + Hleð inn tiltækum tungumálum… + Kóðinn minn + Deila kóðanum mínum + Skanna QR-kóða + Býð notendum… + Bæta við meðlimum + Sannreyna handvirkt með textaskilaboðum + Dulritað meðf ósannreyndu tæki + sendir snjókomu ❄️ + sendir skraut 🎉 + ${app_name} fyrir iOS +\n${app_name} fyrir Android + ${app_name} fyrir vefinn +\n${app_name} fyrir vinnutölvur + Nota skrá + Þú gekkst í hópinn. + Þú bjóst til og stilltir spjallrásina. + Haltu þessu öruggu + Skilaboð hér eru ekki enda-í-enda dulrituð. + Þú samþykktir + Þú hættir við + Forritarahamur + Hreinsa persónuleg gögn + Taktu þátt ókeypis ásamt milljónum annarra á stærsta almenningsþjóninum + sleppt þessari spurningu + Örugg skilaboð. + Gat ekki tengst við auðkennisþjón + Dulritunarlyklarnir þínir eru ekki öryggisafritaðir úr þessari setu. + Tekur stjórnunarréttindi af notanda með uppgefið auðkenni + Hættir að hunsa notanda, birtir skilaboð viðkomandi héðan í frá + Setja upp varið öryggisafrit \ No newline at end of file From 88197991e1210f839bc0df0bd17ca853bff21cd6 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Thu, 24 Mar 2022 12:44:57 +0000 Subject: [PATCH 081/565] extracting the direct login logic to its own use case along with viewmodel test case - will ensure we emit account sign in when going via direct login flow --- .../features/onboarding/DirectLoginUseCase.kt | 91 +++++++++++++++++++ .../onboarding/OnboardingViewModel.kt | 78 ++-------------- .../onboarding/OnboardingViewModelTest.kt | 31 ++++++- .../app/test/fakes/FakeDirectLoginUseCase.kt | 31 +++++++ 4 files changed, 159 insertions(+), 72 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/onboarding/DirectLoginUseCase.kt create mode 100644 vector/src/test/java/im/vector/app/test/fakes/FakeDirectLoginUseCase.kt diff --git a/vector/src/main/java/im/vector/app/features/onboarding/DirectLoginUseCase.kt b/vector/src/main/java/im/vector/app/features/onboarding/DirectLoginUseCase.kt new file mode 100644 index 0000000000..54ee1d3a52 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/onboarding/DirectLoginUseCase.kt @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2022 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.features.onboarding + +import android.net.Uri +import im.vector.app.R +import im.vector.app.core.resources.StringProvider +import im.vector.app.features.onboarding.OnboardingAction.LoginOrRegister +import org.matrix.android.sdk.api.MatrixPatterns.getDomain +import org.matrix.android.sdk.api.auth.AuthenticationService +import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig +import org.matrix.android.sdk.api.auth.wellknown.WellknownResult +import org.matrix.android.sdk.api.session.Session +import javax.inject.Inject + +class DirectLoginUseCase @Inject constructor( + private val authenticationService: AuthenticationService, + private val stringProvider: StringProvider, +) { + + suspend fun execute(action: LoginOrRegister, homeServerConnectionConfig: HomeServerConnectionConfig?): Result { + return fetchWellKnown(action.username, homeServerConnectionConfig) + .andThen { wellKnown -> createSessionFor(wellKnown, action, homeServerConnectionConfig) } + } + + private suspend fun fetchWellKnown(matrixId: String, config: HomeServerConnectionConfig?) = runCatching { + authenticationService.getWellKnownData(matrixId, config) + } + + private suspend fun createSessionFor(data: WellknownResult, action: LoginOrRegister, config: HomeServerConnectionConfig?) = when (data) { + is WellknownResult.Prompt -> loginDirect(action, data, config) + is WellknownResult.FailPrompt -> handleFailPrompt(data, action, config) + else -> onWellKnownError() + } + + private suspend fun handleFailPrompt(data: WellknownResult.FailPrompt, action: LoginOrRegister, config: HomeServerConnectionConfig?): Result { + // Relax on IS discovery if homeserver is valid + val isMissingInformationToLogin = data.homeServerUrl == null || data.wellKnown == null + return when { + isMissingInformationToLogin -> onWellKnownError() + else -> loginDirect(action, WellknownResult.Prompt(data.homeServerUrl!!, null, data.wellKnown!!), config) + } + } + + private suspend fun loginDirect(action: LoginOrRegister, wellKnownPrompt: WellknownResult.Prompt, config: HomeServerConnectionConfig?): Result { + val alteredHomeServerConnectionConfig = config?.updateWith(wellKnownPrompt) ?: fallbackConfig(action, wellKnownPrompt) + return runCatching { + authenticationService.directAuthentication( + alteredHomeServerConnectionConfig, + action.username, + action.password, + action.initialDeviceName + ) + } + } + + private fun HomeServerConnectionConfig.updateWith(wellKnownPrompt: WellknownResult.Prompt) = copy( + homeServerUriBase = Uri.parse(wellKnownPrompt.homeServerUrl), + identityServerUri = wellKnownPrompt.identityServerUrl?.let { Uri.parse(it) } + ) + + private fun fallbackConfig(action: LoginOrRegister, wellKnownPrompt: WellknownResult.Prompt) = HomeServerConnectionConfig( + homeServerUri = Uri.parse("https://${action.username.getDomain()}"), + homeServerUriBase = Uri.parse(wellKnownPrompt.homeServerUrl), + identityServerUri = wellKnownPrompt.identityServerUrl?.let { Uri.parse(it) } + ) + + private fun onWellKnownError() = Result.failure(Exception(stringProvider.getString(R.string.autodiscover_well_known_error))) +} + +@Suppress("UNCHECKED_CAST") // We're casting null failure results to R +private inline fun Result.andThen(block: (T) -> Result): Result { + return when (val result = getOrNull()) { + null -> this as Result + else -> block(result) + } +} diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt index 3fb52619da..6d959ef124 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt @@ -17,7 +17,6 @@ package im.vector.app.features.onboarding import android.content.Context -import android.net.Uri import com.airbnb.mvrx.MavericksViewModelFactory import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -45,7 +44,6 @@ import im.vector.app.features.login.SignMode import kotlinx.coroutines.Job import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.launch -import org.matrix.android.sdk.api.MatrixPatterns.getDomain import org.matrix.android.sdk.api.auth.AuthenticationService import org.matrix.android.sdk.api.auth.HomeServerHistoryService import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig @@ -55,9 +53,6 @@ import org.matrix.android.sdk.api.auth.registration.FlowResult import org.matrix.android.sdk.api.auth.registration.RegistrationResult import org.matrix.android.sdk.api.auth.registration.RegistrationWizard import org.matrix.android.sdk.api.auth.registration.Stage -import org.matrix.android.sdk.api.auth.wellknown.WellknownResult -import org.matrix.android.sdk.api.failure.Failure -import org.matrix.android.sdk.api.failure.MatrixIdFailure import org.matrix.android.sdk.api.session.Session import timber.log.Timber import java.util.UUID @@ -79,6 +74,7 @@ class OnboardingViewModel @AssistedInject constructor( private val analyticsTracker: AnalyticsTracker, private val uriFilenameResolver: UriFilenameResolver, private val registrationActionHandler: RegistrationActionHandler, + private val directLoginUseCase: DirectLoginUseCase, private val vectorOverrides: VectorOverrides ) : VectorViewModel(initialState) { @@ -470,74 +466,14 @@ class OnboardingViewModel @AssistedInject constructor( private fun handleDirectLogin(action: OnboardingAction.LoginOrRegister, homeServerConnectionConfig: HomeServerConnectionConfig?) { setState { copy(isLoading = true) } - currentJob = viewModelScope.launch { - val data = try { - authenticationService.getWellKnownData(action.username, homeServerConnectionConfig) - } catch (failure: Throwable) { - onDirectLoginError(failure) - return@launch - } - when (data) { - is WellknownResult.Prompt -> - directLoginOnWellknownSuccess(action, data, homeServerConnectionConfig) - is WellknownResult.FailPrompt -> - // Relax on IS discovery if homeserver is valid - if (data.homeServerUrl != null && data.wellKnown != null) { - directLoginOnWellknownSuccess(action, WellknownResult.Prompt(data.homeServerUrl!!, null, data.wellKnown!!), homeServerConnectionConfig) - } else { - onWellKnownError() + directLoginUseCase.execute(action, homeServerConnectionConfig).fold( + onSuccess = { onSessionCreated(it, isAccountCreated = false) }, + onFailure = { + setState { copy(isLoading = false) } + _viewEvents.post(OnboardingViewEvents.Failure(it)) } - else -> { - onWellKnownError() - } - } - } - } - - private fun onWellKnownError() { - setState { copy(isLoading = false) } - _viewEvents.post(OnboardingViewEvents.Failure(Exception(stringProvider.getString(R.string.autodiscover_well_known_error)))) - } - - private suspend fun directLoginOnWellknownSuccess(action: OnboardingAction.LoginOrRegister, - wellKnownPrompt: WellknownResult.Prompt, - homeServerConnectionConfig: HomeServerConnectionConfig?) { - val alteredHomeServerConnectionConfig = homeServerConnectionConfig - ?.copy( - homeServerUriBase = Uri.parse(wellKnownPrompt.homeServerUrl), - identityServerUri = wellKnownPrompt.identityServerUrl?.let { Uri.parse(it) } - ) - ?: HomeServerConnectionConfig( - homeServerUri = Uri.parse("https://${action.username.getDomain()}"), - homeServerUriBase = Uri.parse(wellKnownPrompt.homeServerUrl), - identityServerUri = wellKnownPrompt.identityServerUrl?.let { Uri.parse(it) } - ) - - val data = try { - authenticationService.directAuthentication( - alteredHomeServerConnectionConfig, - action.username, - action.password, - action.initialDeviceName) - } catch (failure: Throwable) { - onDirectLoginError(failure) - return - } - onSessionCreated(data, isAccountCreated = false) - } - - private fun onDirectLoginError(failure: Throwable) { - when (failure) { - is MatrixIdFailure.InvalidMatrixId, - is Failure.UnrecognizedCertificateFailure -> { - setState { copy(isLoading = false) } - // Display this error in a dialog - _viewEvents.post(OnboardingViewEvents.Failure(failure)) - } - else -> { - setState { copy(isLoading = false) } - } + ) } } diff --git a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt index df4e0de65e..8d4ee50f2f 100644 --- a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt @@ -24,6 +24,7 @@ import im.vector.app.test.fakes.FakeActiveSessionHolder import im.vector.app.test.fakes.FakeAnalyticsTracker import im.vector.app.test.fakes.FakeAuthenticationService import im.vector.app.test.fakes.FakeContext +import im.vector.app.test.fakes.FakeDirectLoginUseCase import im.vector.app.test.fakes.FakeHomeServerConnectionConfigFactory import im.vector.app.test.fakes.FakeHomeServerHistoryService import im.vector.app.test.fakes.FakeRegisterActionHandler @@ -44,6 +45,7 @@ import org.matrix.android.sdk.api.auth.registration.FlowResult import org.matrix.android.sdk.api.auth.registration.RegisterThreePid import org.matrix.android.sdk.api.auth.registration.RegistrationResult import org.matrix.android.sdk.api.auth.registration.Stage +import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities private const val A_DISPLAY_NAME = "a display name" @@ -55,6 +57,7 @@ private val A_RESULT_IGNORED_REGISTER_ACTION = RegisterAction.AddThreePid(Regist private val A_HOMESERVER_CAPABILITIES = aHomeServerCapabilities(canChangeDisplayName = true, canChangeAvatar = true) private val AN_IGNORED_FLOW_RESULT = FlowResult(missingStages = emptyList(), completedStages = emptyList()) private val ANY_CONTINUING_REGISTRATION_RESULT = RegistrationResult.FlowResponse(AN_IGNORED_FLOW_RESULT) +private val A_LOGIN_OR_REGISTER_ACTION = OnboardingAction.LoginOrRegister("@a-user:id.org", "a-password", "a-device-name") class OnboardingViewModelTest { @@ -69,6 +72,7 @@ class OnboardingViewModelTest { private val fakeActiveSessionHolder = FakeActiveSessionHolder(fakeSession) private val fakeAuthenticationService = FakeAuthenticationService() private val fakeRegisterActionHandler = FakeRegisterActionHandler() + private val fakeDirectLoginUseCase = FakeDirectLoginUseCase() lateinit var viewModel: OnboardingViewModel @@ -114,6 +118,26 @@ class OnboardingViewModelTest { .finish() } + @Test + fun `given has sign in with matrix id sign mode, when handling login or register action, then logs in directly`() = runTest { + val initialState = initialState.copy(signMode = SignMode.SignInWithMatrixId) + viewModel = createViewModel(initialState) + fakeDirectLoginUseCase.givenSuccessResult(A_LOGIN_OR_REGISTER_ACTION, config = null, result = fakeSession) + givenInitialisesSession(fakeSession) + val test = viewModel.test() + + viewModel.handle(A_LOGIN_OR_REGISTER_ACTION) + + test + .assertStatesChanges( + initialState, + { copy(isLoading = true) }, + { copy(isLoading = false) } + ) + .assertEvents(OnboardingViewEvents.OnAccountSignedIn) + .finish() + } + @Test fun `when handling SignUp then sets sign mode to sign up and starts registration`() = runTest { givenRegistrationResultFor(RegisterAction.StartRegistration, ANY_CONTINUING_REGISTRATION_RESULT) @@ -344,6 +368,7 @@ class OnboardingViewModelTest { FakeAnalyticsTracker(), fakeUriFilenameResolver.instance, fakeRegisterActionHandler.instance, + fakeDirectLoginUseCase.instance, FakeVectorOverrides() ) } @@ -384,7 +409,11 @@ class OnboardingViewModelTest { private fun givenSuccessfullyCreatesAccount(homeServerCapabilities: HomeServerCapabilities) { fakeSession.fakeHomeServerCapabilitiesService.givenCapabilities(homeServerCapabilities) - fakeActiveSessionHolder.expectSetsActiveSession(fakeSession) + givenInitialisesSession(fakeSession) + } + + private fun givenInitialisesSession(session: Session) { + fakeActiveSessionHolder.expectSetsActiveSession(session) fakeAuthenticationService.expectReset() fakeSession.expectStartsSyncing() } diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeDirectLoginUseCase.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeDirectLoginUseCase.kt new file mode 100644 index 0000000000..b3fba51354 --- /dev/null +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeDirectLoginUseCase.kt @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2022 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.test.fakes + +import im.vector.app.features.onboarding.DirectLoginUseCase +import im.vector.app.features.onboarding.OnboardingAction +import io.mockk.coEvery +import io.mockk.mockk +import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig + +class FakeDirectLoginUseCase { + val instance = mockk() + + fun givenSuccessResult(action: OnboardingAction.LoginOrRegister, config: HomeServerConnectionConfig?, result: FakeSession) { + coEvery { instance.execute(action, config) } returns Result.success(result) + } +} From 230c37597c67c33403d173ba1685928cbaa97af3 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Thu, 24 Mar 2022 15:41:53 +0000 Subject: [PATCH 082/565] adding happy path tests for the direct login use case --- .../android/sdk/internal/extensions/Result.kt | 8 +++ .../features/onboarding/DirectLoginUseCase.kt | 21 ++---- .../app/features/onboarding/UriFactory.kt | 27 +++++++ .../onboarding/DirectLoginUseCaseTest.kt | 71 +++++++++++++++++++ .../test/fakes/FakeAuthenticationService.kt | 11 +++ .../java/im/vector/app/test/fakes/FakeUri.kt | 15 +++- .../vector/app/test/fakes/FakeUriFactory.kt | 31 ++++++++ 7 files changed, 169 insertions(+), 15 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/onboarding/UriFactory.kt create mode 100644 vector/src/test/java/im/vector/app/features/onboarding/DirectLoginUseCaseTest.kt create mode 100644 vector/src/test/java/im/vector/app/test/fakes/FakeUriFactory.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/extensions/Result.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/extensions/Result.kt index 3734c5dc1d..12adf16cbc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/extensions/Result.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/extensions/Result.kt @@ -21,3 +21,11 @@ fun Result.foldToCallback(callback: MatrixCallback): Unit = fold( { callback.onSuccess(it) }, { callback.onFailure(it) } ) + +@Suppress("UNCHECKED_CAST") // We're casting null failure results to R +inline fun Result.andThen(block: (T) -> Result): Result { + return when (val result = getOrNull()) { + null -> this as Result + else -> block(result) + } +} diff --git a/vector/src/main/java/im/vector/app/features/onboarding/DirectLoginUseCase.kt b/vector/src/main/java/im/vector/app/features/onboarding/DirectLoginUseCase.kt index 54ee1d3a52..7ef4dfb609 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/DirectLoginUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/DirectLoginUseCase.kt @@ -16,7 +16,6 @@ package im.vector.app.features.onboarding -import android.net.Uri import im.vector.app.R import im.vector.app.core.resources.StringProvider import im.vector.app.features.onboarding.OnboardingAction.LoginOrRegister @@ -25,11 +24,13 @@ import org.matrix.android.sdk.api.auth.AuthenticationService import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig import org.matrix.android.sdk.api.auth.wellknown.WellknownResult import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.internal.extensions.andThen import javax.inject.Inject class DirectLoginUseCase @Inject constructor( private val authenticationService: AuthenticationService, private val stringProvider: StringProvider, + private val uriFactory: UriFactory ) { suspend fun execute(action: LoginOrRegister, homeServerConnectionConfig: HomeServerConnectionConfig?): Result { @@ -69,23 +70,15 @@ class DirectLoginUseCase @Inject constructor( } private fun HomeServerConnectionConfig.updateWith(wellKnownPrompt: WellknownResult.Prompt) = copy( - homeServerUriBase = Uri.parse(wellKnownPrompt.homeServerUrl), - identityServerUri = wellKnownPrompt.identityServerUrl?.let { Uri.parse(it) } + homeServerUriBase = uriFactory.parse(wellKnownPrompt.homeServerUrl), + identityServerUri = wellKnownPrompt.identityServerUrl?.let { uriFactory.parse(it) } ) private fun fallbackConfig(action: LoginOrRegister, wellKnownPrompt: WellknownResult.Prompt) = HomeServerConnectionConfig( - homeServerUri = Uri.parse("https://${action.username.getDomain()}"), - homeServerUriBase = Uri.parse(wellKnownPrompt.homeServerUrl), - identityServerUri = wellKnownPrompt.identityServerUrl?.let { Uri.parse(it) } + homeServerUri = uriFactory.parse("https://${action.username.getDomain()}"), + homeServerUriBase = uriFactory.parse(wellKnownPrompt.homeServerUrl), + identityServerUri = wellKnownPrompt.identityServerUrl?.let { uriFactory.parse(it) } ) private fun onWellKnownError() = Result.failure(Exception(stringProvider.getString(R.string.autodiscover_well_known_error))) } - -@Suppress("UNCHECKED_CAST") // We're casting null failure results to R -private inline fun Result.andThen(block: (T) -> Result): Result { - return when (val result = getOrNull()) { - null -> this as Result - else -> block(result) - } -} diff --git a/vector/src/main/java/im/vector/app/features/onboarding/UriFactory.kt b/vector/src/main/java/im/vector/app/features/onboarding/UriFactory.kt new file mode 100644 index 0000000000..f9e7a3458c --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/onboarding/UriFactory.kt @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2022 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.features.onboarding + +import android.net.Uri +import javax.inject.Inject + +class UriFactory @Inject constructor() { + + fun parse(value: String): Uri { + return Uri.parse(value) + } +} diff --git a/vector/src/test/java/im/vector/app/features/onboarding/DirectLoginUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/onboarding/DirectLoginUseCaseTest.kt new file mode 100644 index 0000000000..c7bfc9b73d --- /dev/null +++ b/vector/src/test/java/im/vector/app/features/onboarding/DirectLoginUseCaseTest.kt @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2022 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.features.onboarding + +import im.vector.app.test.fakes.FakeAuthenticationService +import im.vector.app.test.fakes.FakeSession +import im.vector.app.test.fakes.FakeStringProvider +import im.vector.app.test.fakes.FakeUri +import im.vector.app.test.fakes.FakeUriFactory +import kotlinx.coroutines.test.runTest +import org.amshove.kluent.shouldBeEqualTo +import org.junit.Test +import org.matrix.android.sdk.api.MatrixPatterns.getDomain +import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig +import org.matrix.android.sdk.api.auth.data.WellKnown +import org.matrix.android.sdk.api.auth.wellknown.WellknownResult + +private val A_LOGIN_OR_REGISTER_ACTION = OnboardingAction.LoginOrRegister("@a-user:id.org", "a-password", "a-device-name") +private val A_WELLKNOWN_SUCCESS_RESULT = WellknownResult.Prompt("https://homeserverurl.com", identityServerUrl = null, WellKnown()) +private val A_WELLKNOWN_FAILED_WITH_CONTENT_RESULT = WellknownResult.FailPrompt("https://homeserverurl.com", WellKnown()) +private val NO_HOMESERVER_CONFIG: HomeServerConnectionConfig? = null +private val A_FALLBACK_CONFIG: HomeServerConnectionConfig = HomeServerConnectionConfig( + homeServerUri = FakeUri("https://${A_LOGIN_OR_REGISTER_ACTION.username.getDomain()}").instance, + homeServerUriBase = FakeUri(A_WELLKNOWN_SUCCESS_RESULT.homeServerUrl).instance, + identityServerUri = null +) + +class DirectLoginUseCaseTest { + + private val fakeAuthenticationService = FakeAuthenticationService() + private val fakeStringProvider = FakeStringProvider() + private val fakeSession = FakeSession() + + private val useCase = DirectLoginUseCase(fakeAuthenticationService, fakeStringProvider.instance, FakeUriFactory().instance) + + @Test + fun `when logging in directly, then returns success with direct session result`() = runTest { + fakeAuthenticationService.givenWellKnown(A_LOGIN_OR_REGISTER_ACTION.username, config = NO_HOMESERVER_CONFIG, result = A_WELLKNOWN_SUCCESS_RESULT) + val (username, password, initialDeviceName) = A_LOGIN_OR_REGISTER_ACTION + fakeAuthenticationService.givenDirectAuthentication(A_FALLBACK_CONFIG, username, password, initialDeviceName, result = fakeSession) + + val result = useCase.execute(A_LOGIN_OR_REGISTER_ACTION, homeServerConnectionConfig = NO_HOMESERVER_CONFIG) + + result shouldBeEqualTo Result.success(fakeSession) + } + + @Test + fun `given wellknown fails but has content, when logging in directly, then returns success with direct session result`() = runTest { + fakeAuthenticationService.givenWellKnown(A_LOGIN_OR_REGISTER_ACTION.username, config = NO_HOMESERVER_CONFIG, result = A_WELLKNOWN_FAILED_WITH_CONTENT_RESULT) + val (username, password, initialDeviceName) = A_LOGIN_OR_REGISTER_ACTION + fakeAuthenticationService.givenDirectAuthentication(A_FALLBACK_CONFIG, username, password, initialDeviceName, result = fakeSession) + + val result = useCase.execute(A_LOGIN_OR_REGISTER_ACTION, homeServerConnectionConfig = NO_HOMESERVER_CONFIG) + + result shouldBeEqualTo Result.success(fakeSession) + } +} diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeAuthenticationService.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeAuthenticationService.kt index 10daf5de1e..a59116a737 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeAuthenticationService.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeAuthenticationService.kt @@ -16,11 +16,14 @@ package im.vector.app.test.fakes +import io.mockk.coEvery import io.mockk.coJustRun import io.mockk.every import io.mockk.mockk import org.matrix.android.sdk.api.auth.AuthenticationService +import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig import org.matrix.android.sdk.api.auth.registration.RegistrationWizard +import org.matrix.android.sdk.api.auth.wellknown.WellknownResult class FakeAuthenticationService : AuthenticationService by mockk() { @@ -35,4 +38,12 @@ class FakeAuthenticationService : AuthenticationService by mockk() { fun expectReset() { coJustRun { reset() } } + + fun givenWellKnown(matrixId: String, config: HomeServerConnectionConfig?, result: WellknownResult) { + coEvery { getWellKnownData(matrixId, config) } returns result + } + + fun givenDirectAuthentication(config: HomeServerConnectionConfig, matrixId: String, password: String, deviceName: String, result: FakeSession) { + coEvery { directAuthentication(config, matrixId, password, deviceName) } returns result + } } diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeUri.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeUri.kt index 99f2cf39aa..675401d72f 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeUri.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeUri.kt @@ -20,9 +20,14 @@ import android.net.Uri import io.mockk.every import io.mockk.mockk -class FakeUri { +class FakeUri(contentEquals: String? = null) { + val instance = mockk() + init { + contentEquals?.let { givenEquals(it) } + } + fun givenNonHierarchical() { givenContent(schema = "mail", path = null) } @@ -31,4 +36,12 @@ class FakeUri { every { instance.scheme } returns schema every { instance.path } returns path } + + @Suppress("ReplaceCallWithBinaryOperator") + fun givenEquals(content: String) { + every { instance.equals(any()) } answers { + it.invocation.args.first() == content + } + every { instance.hashCode() } answers { content.hashCode() } + } } diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeUriFactory.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeUriFactory.kt new file mode 100644 index 0000000000..90b615cb7c --- /dev/null +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeUriFactory.kt @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2022 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.test.fakes + +import im.vector.app.features.onboarding.UriFactory +import io.mockk.every +import io.mockk.mockk + +class FakeUriFactory { + + val instance = mockk().also { + every { it.parse(any()) } answers { + val input = it.invocation.args.first() as String + FakeUri().also { it.givenEquals(input) }.instance + } + } +} From cfb3aa8a221a370b9c7ba0526bd2e06b70060917 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Thu, 24 Mar 2022 16:23:03 +0000 Subject: [PATCH 083/565] adding direct login error path tests --- .../onboarding/DirectLoginUseCaseTest.kt | 40 ++++++++++++++++++- .../onboarding/OnboardingViewModelTest.kt | 20 ++++++++++ .../test/fakes/FakeAuthenticationService.kt | 8 ++++ .../app/test/fakes/FakeDirectLoginUseCase.kt | 4 ++ .../app/test/fakes/FakeStringProvider.kt | 2 + 5 files changed, 73 insertions(+), 1 deletion(-) diff --git a/vector/src/test/java/im/vector/app/features/onboarding/DirectLoginUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/onboarding/DirectLoginUseCaseTest.kt index c7bfc9b73d..5a3c323316 100644 --- a/vector/src/test/java/im/vector/app/features/onboarding/DirectLoginUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/onboarding/DirectLoginUseCaseTest.kt @@ -16,12 +16,15 @@ package im.vector.app.features.onboarding +import im.vector.app.R import im.vector.app.test.fakes.FakeAuthenticationService import im.vector.app.test.fakes.FakeSession import im.vector.app.test.fakes.FakeStringProvider import im.vector.app.test.fakes.FakeUri import im.vector.app.test.fakes.FakeUriFactory +import im.vector.app.test.fakes.toTestString import kotlinx.coroutines.test.runTest +import org.amshove.kluent.should import org.amshove.kluent.shouldBeEqualTo import org.junit.Test import org.matrix.android.sdk.api.MatrixPatterns.getDomain @@ -32,12 +35,14 @@ import org.matrix.android.sdk.api.auth.wellknown.WellknownResult private val A_LOGIN_OR_REGISTER_ACTION = OnboardingAction.LoginOrRegister("@a-user:id.org", "a-password", "a-device-name") private val A_WELLKNOWN_SUCCESS_RESULT = WellknownResult.Prompt("https://homeserverurl.com", identityServerUrl = null, WellKnown()) private val A_WELLKNOWN_FAILED_WITH_CONTENT_RESULT = WellknownResult.FailPrompt("https://homeserverurl.com", WellKnown()) +private val A_WELLKNOWN_FAILED_WITHOUT_CONTENT_RESULT = WellknownResult.FailPrompt(null, null) private val NO_HOMESERVER_CONFIG: HomeServerConnectionConfig? = null private val A_FALLBACK_CONFIG: HomeServerConnectionConfig = HomeServerConnectionConfig( homeServerUri = FakeUri("https://${A_LOGIN_OR_REGISTER_ACTION.username.getDomain()}").instance, homeServerUriBase = FakeUri(A_WELLKNOWN_SUCCESS_RESULT.homeServerUrl).instance, identityServerUri = null ) +private val AN_ERROR = RuntimeException() class DirectLoginUseCaseTest { @@ -59,7 +64,7 @@ class DirectLoginUseCaseTest { } @Test - fun `given wellknown fails but has content, when logging in directly, then returns success with direct session result`() = runTest { + fun `given wellknown fails with content, when logging in directly, then returns success with direct session result`() = runTest { fakeAuthenticationService.givenWellKnown(A_LOGIN_OR_REGISTER_ACTION.username, config = NO_HOMESERVER_CONFIG, result = A_WELLKNOWN_FAILED_WITH_CONTENT_RESULT) val (username, password, initialDeviceName) = A_LOGIN_OR_REGISTER_ACTION fakeAuthenticationService.givenDirectAuthentication(A_FALLBACK_CONFIG, username, password, initialDeviceName, result = fakeSession) @@ -68,4 +73,37 @@ class DirectLoginUseCaseTest { result shouldBeEqualTo Result.success(fakeSession) } + + @Test + fun `given wellknown fails without content, when logging in directly, then returns well known error`() = runTest { + fakeAuthenticationService.givenWellKnown(A_LOGIN_OR_REGISTER_ACTION.username, config = NO_HOMESERVER_CONFIG, result = A_WELLKNOWN_FAILED_WITHOUT_CONTENT_RESULT) + val (username, password, initialDeviceName) = A_LOGIN_OR_REGISTER_ACTION + fakeAuthenticationService.givenDirectAuthentication(A_FALLBACK_CONFIG, username, password, initialDeviceName, result = fakeSession) + + val result = useCase.execute(A_LOGIN_OR_REGISTER_ACTION, homeServerConnectionConfig = NO_HOMESERVER_CONFIG) + + result should { this.isFailure } + result should { this.exceptionOrNull() is Exception } + result should { this.exceptionOrNull()?.message == R.string.autodiscover_well_known_error.toTestString() } + } + + @Test + fun `given wellknown throws, when logging in directly, then returns failure result with original cause`() = runTest { + fakeAuthenticationService.givenWellKnownThrows(A_LOGIN_OR_REGISTER_ACTION.username, config = NO_HOMESERVER_CONFIG, cause = AN_ERROR) + + val result = useCase.execute(A_LOGIN_OR_REGISTER_ACTION, homeServerConnectionConfig = NO_HOMESERVER_CONFIG) + + result shouldBeEqualTo Result.failure(AN_ERROR) + } + + @Test + fun `given direct authentication throws, when logging in directly, then returns failure result with original cause`() = runTest { + fakeAuthenticationService.givenWellKnown(A_LOGIN_OR_REGISTER_ACTION.username, config = NO_HOMESERVER_CONFIG, result = A_WELLKNOWN_SUCCESS_RESULT) + val (username, password, initialDeviceName) = A_LOGIN_OR_REGISTER_ACTION + fakeAuthenticationService.givenDirectAuthenticationThrows(A_FALLBACK_CONFIG, username, password, initialDeviceName, cause = AN_ERROR) + + val result = useCase.execute(A_LOGIN_OR_REGISTER_ACTION, homeServerConnectionConfig = NO_HOMESERVER_CONFIG) + + result shouldBeEqualTo Result.failure(AN_ERROR) + } } diff --git a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt index 8d4ee50f2f..118bf689d2 100644 --- a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt @@ -138,6 +138,26 @@ class OnboardingViewModelTest { .finish() } + @Test + fun `given has sign in with matrix id sign mode, when handling login or register action fails, then emits error`() = runTest { + val initialState = initialState.copy(signMode = SignMode.SignInWithMatrixId) + viewModel = createViewModel(initialState) + fakeDirectLoginUseCase.givenFailureResult(A_LOGIN_OR_REGISTER_ACTION, config = null, cause = AN_ERROR) + givenInitialisesSession(fakeSession) + val test = viewModel.test() + + viewModel.handle(A_LOGIN_OR_REGISTER_ACTION) + + test + .assertStatesChanges( + initialState, + { copy(isLoading = true) }, + { copy(isLoading = false) } + ) + .assertEvents(OnboardingViewEvents.Failure(AN_ERROR)) + .finish() + } + @Test fun `when handling SignUp then sets sign mode to sign up and starts registration`() = runTest { givenRegistrationResultFor(RegisterAction.StartRegistration, ANY_CONTINUING_REGISTRATION_RESULT) diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeAuthenticationService.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeAuthenticationService.kt index a59116a737..9175fd3750 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeAuthenticationService.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeAuthenticationService.kt @@ -43,7 +43,15 @@ class FakeAuthenticationService : AuthenticationService by mockk() { coEvery { getWellKnownData(matrixId, config) } returns result } + fun givenWellKnownThrows(matrixId: String, config: HomeServerConnectionConfig?, cause: Throwable) { + coEvery { getWellKnownData(matrixId, config) } throws cause + } + fun givenDirectAuthentication(config: HomeServerConnectionConfig, matrixId: String, password: String, deviceName: String, result: FakeSession) { coEvery { directAuthentication(config, matrixId, password, deviceName) } returns result } + + fun givenDirectAuthenticationThrows(config: HomeServerConnectionConfig, matrixId: String, password: String, deviceName: String, cause: Throwable) { + coEvery { directAuthentication(config, matrixId, password, deviceName) } throws cause + } } diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeDirectLoginUseCase.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeDirectLoginUseCase.kt index b3fba51354..8a5c6b1cee 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeDirectLoginUseCase.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeDirectLoginUseCase.kt @@ -28,4 +28,8 @@ class FakeDirectLoginUseCase { fun givenSuccessResult(action: OnboardingAction.LoginOrRegister, config: HomeServerConnectionConfig?, result: FakeSession) { coEvery { instance.execute(action, config) } returns Result.success(result) } + + fun givenFailureResult(action: OnboardingAction.LoginOrRegister, config: HomeServerConnectionConfig?, cause: Throwable) { + coEvery { instance.execute(action, config) } returns Result.failure(cause) + } } diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeStringProvider.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeStringProvider.kt index f9001e3f8a..1a4f5cb85b 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeStringProvider.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeStringProvider.kt @@ -30,3 +30,5 @@ class FakeStringProvider { } } } + +fun Int.toTestString() = "test-$this" From 776cf2451621b6fb2beee7538544c08ede66f980 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Thu, 24 Mar 2022 16:24:17 +0000 Subject: [PATCH 084/565] adding changelog entry --- changelog.d/5628.misc | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/5628.misc diff --git a/changelog.d/5628.misc b/changelog.d/5628.misc new file mode 100644 index 0000000000..9c4894c164 --- /dev/null +++ b/changelog.d/5628.misc @@ -0,0 +1 @@ +Adds unit tests around the login with matrix id flow \ No newline at end of file From 313595e496d1b2673c534f6c9840048a975a4c10 Mon Sep 17 00:00:00 2001 From: ericdecanini Date: Fri, 25 Mar 2022 14:30:00 +0100 Subject: [PATCH 085/565] Fixes textview layout bounds in item_timeline_event_audio_stub --- .../main/res/layout/item_timeline_event_audio_stub.xml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/vector/src/main/res/layout/item_timeline_event_audio_stub.xml b/vector/src/main/res/layout/item_timeline_event_audio_stub.xml index d0f4d4c85f..a69afe4d9f 100644 --- a/vector/src/main/res/layout/item_timeline_event_audio_stub.xml +++ b/vector/src/main/res/layout/item_timeline_event_audio_stub.xml @@ -11,7 +11,7 @@ @@ -31,18 +31,17 @@ + tools:text="A filename here that is really long that I dont even wanna even like ugh can I keep talkin like this eventually its gotta end right get ready get your psyche up mob" /> Date: Fri, 25 Mar 2022 22:51:34 +0000 Subject: [PATCH 086/565] Align with web implementation --- .../sdk/api/session/LiveEventListener.kt | 8 +- .../algorithms/megolm/MXMegolmDecryption.kt | 2 +- .../internal/session/StreamEventsManager.kt | 30 +---- .../room/timeline/TokenChunkEventPersistor.kt | 2 +- .../sync/handler/room/RoomSyncHandler.kt | 1 - .../main/java/im/vector/app/AutoRageShaker.kt | 40 ++++--- .../main/java/im/vector/app/UISIDetector.kt | 105 ++++++------------ 7 files changed, 66 insertions(+), 122 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/LiveEventListener.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/LiveEventListener.kt index 6fda65953a..65e3e94d2d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/LiveEventListener.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/LiveEventListener.kt @@ -21,13 +21,9 @@ import org.matrix.android.sdk.api.util.JsonDict interface LiveEventListener { - fun onLiveEvent(roomId: String, event: Event) + fun onEventDecrypted(event: Event) - fun onPaginatedEvent(roomId: String, event: Event) - - fun onEventDecrypted(eventId: String, roomId: String, clearEvent: JsonDict) - - fun onEventDecryptionError(eventId: String, roomId: String, throwable: Throwable) + fun onEventDecryptionError(event: Event, throwable: Throwable) fun onLiveToDeviceEvent(event: Event) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt index e94daa0e76..22180dce86 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt @@ -113,7 +113,7 @@ internal class MXMegolmDecryption(private val userId: String, forwardingCurve25519KeyChain = olmDecryptionResult.forwardingCurve25519KeyChain .orEmpty() ).also { - liveEventManager.get().dispatchLiveEventDecrypted(event, it) + liveEventManager.get().dispatchLiveEventDecrypted(event) } } else { throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/StreamEventsManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/StreamEventsManager.kt index bb0ca11445..9876e265bf 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/StreamEventsManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/StreamEventsManager.kt @@ -42,36 +42,12 @@ internal class StreamEventsManager @Inject constructor() { listeners.remove(listener) } - fun dispatchLiveEventReceived(event: Event, roomId: String, initialSync: Boolean) { - Timber.v("## dispatchLiveEventReceived ${event.eventId}") - coroutineScope.launch { - if (!initialSync) { - listeners.forEach { - tryOrNull { - it.onLiveEvent(roomId, event) - } - } - } - } - } - - fun dispatchPaginatedEventReceived(event: Event, roomId: String) { - Timber.v("## dispatchPaginatedEventReceived ${event.eventId}") - coroutineScope.launch { - listeners.forEach { - tryOrNull { - it.onPaginatedEvent(roomId, event) - } - } - } - } - - fun dispatchLiveEventDecrypted(event: Event, result: MXEventDecryptionResult) { + fun dispatchLiveEventDecrypted(event: Event) { Timber.v("## dispatchLiveEventDecrypted ${event.eventId}") coroutineScope.launch { listeners.forEach { tryOrNull { - it.onEventDecrypted(event.eventId ?: "", event.roomId ?: "", result.clearEvent) + it.onEventDecrypted(event) } } } @@ -82,7 +58,7 @@ internal class StreamEventsManager @Inject constructor() { coroutineScope.launch { listeners.forEach { tryOrNull { - it.onEventDecryptionError(event.eventId ?: "", event.roomId ?: "", error) + it.onEventDecryptionError(event, error) } } } 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 63383a99b3..4e940bb445 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 @@ -186,7 +186,7 @@ internal class TokenChunkEventPersistor @Inject constructor( } roomMemberContentsByUser[event.stateKey] = contentToUse.toModel() } - liveEventManager.get().dispatchPaginatedEventReceived(event, roomId) + currentChunk.addTimelineEvent( roomId = roomId, eventEntity = eventEntity, 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 8fe85f0d31..1cb476d03a 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 @@ -382,7 +382,6 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle } eventIds.add(event.eventId) - liveEventService.get().dispatchLiveEventReceived(event, roomId, insertType == EventInsertType.INITIAL_SYNC) val isInitialSync = insertType == EventInsertType.INITIAL_SYNC diff --git a/vector/src/main/java/im/vector/app/AutoRageShaker.kt b/vector/src/main/java/im/vector/app/AutoRageShaker.kt index 43283254b1..836e44f57b 100644 --- a/vector/src/main/java/im/vector/app/AutoRageShaker.kt +++ b/vector/src/main/java/im/vector/app/AutoRageShaker.kt @@ -17,23 +17,31 @@ package im.vector.app import android.content.SharedPreferences +import androidx.lifecycle.asFlow import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.features.rageshake.BugReporter import im.vector.app.features.rageshake.ReportType +import im.vector.app.features.session.coroutineScope import im.vector.app.features.settings.VectorPreferences import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.cancellable import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.subscribe import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.toContent +import org.matrix.android.sdk.api.session.initsync.SyncStatusService +import org.matrix.android.sdk.api.session.sync.SyncState +import org.matrix.android.sdk.flow.flow import timber.log.Timber import javax.inject.Inject import javax.inject.Singleton @@ -62,10 +70,11 @@ class AutoRageShaker @Inject constructor( private val e2eDetectedFlow = MutableSharedFlow(replay = 0) private val matchingRSRequestFlow = MutableSharedFlow(replay = 0) - + var hasSynced = false + var preferenceEnabled = false fun initialize() { observeActiveSession() - enable(vectorPreferences.labsAutoReportUISI()) + preferenceEnabled = vectorPreferences.labsAutoReportUISI() // It's a singleton... vectorPreferences.subscribeToChanges(this) @@ -74,7 +83,7 @@ class AutoRageShaker @Inject constructor( e2eDetectedFlow .onEach { sendRageShake(it) - delay(2_000) + delay(60_000) } .catch { cause -> Timber.w(cause, "Failed to RS") @@ -84,7 +93,7 @@ class AutoRageShaker @Inject constructor( matchingRSRequestFlow .onEach { sendMatchingRageShake(it) - delay(2_000) + delay(60_000) } .catch { cause -> Timber.w(cause, "Failed to send matching rageshake") @@ -93,14 +102,7 @@ class AutoRageShaker @Inject constructor( } override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { - enable(vectorPreferences.labsAutoReportUISI()) - } - - var _enabled = false - fun enable(enabled: Boolean) { - if (enabled == _enabled) return - _enabled = enabled - detector.enabled = enabled + preferenceEnabled = vectorPreferences.labsAutoReportUISI() } private fun observeActiveSession() { @@ -115,7 +117,6 @@ class AutoRageShaker @Inject constructor( } fun decryptionErrorDetected(target: E2EMessageDetected) { - if (target.source == UISIEventSource.INITIAL_SYNC) return if (activeSessionHolder.getSafeActiveSession()?.sessionId != currentActiveSessionId) return val shouldSendRS = synchronized(alreadyReportedUisi) { val reportInfo = ReportInfo(target.roomId, target.sessionId) @@ -148,7 +149,6 @@ class AutoRageShaker @Inject constructor( append("\"room_id\": \"${target.roomId}\",") append("\"sender_key\": \"${target.senderKey}\",") append("\"device_id\": \"${target.senderDeviceId}\",") - append("\"source\": \"${target.source}\",") append("\"user_id\": \"${target.senderUserId}\",") append("\"session_id\": \"${target.sessionId}\"") append("}") @@ -245,6 +245,9 @@ class AutoRageShaker @Inject constructor( override val reciprocateToDeviceEventType: String get() = AUTO_RS_REQUEST + override val enabled: Boolean + get() = this@AutoRageShaker.preferenceEnabled && this@AutoRageShaker.hasSynced + override fun uisiDetected(source: E2EMessageDetected) { decryptionErrorDetected(source) } @@ -261,7 +264,14 @@ class AutoRageShaker @Inject constructor( return } this.currentActiveSessionId = sessionId - this.detector.enabled = _enabled + + hasSynced = session.hasAlreadySynced() + session.getSyncStatusLive() + .asFlow() + .onEach { + hasSynced = it !is SyncStatusService.Status.Progressing + } + .launchIn(session.coroutineScope) activeSessionIds.add(sessionId) session.addListener(this) session.addEventStreamListener(detector) diff --git a/vector/src/main/java/im/vector/app/UISIDetector.kt b/vector/src/main/java/im/vector/app/UISIDetector.kt index d6a4805e78..f7c6725bd8 100644 --- a/vector/src/main/java/im/vector/app/UISIDetector.kt +++ b/vector/src/main/java/im/vector/app/UISIDetector.kt @@ -16,6 +16,7 @@ package im.vector.app +import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.LiveEventListener import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.toModel @@ -26,23 +27,17 @@ import java.util.Timer import java.util.TimerTask import java.util.concurrent.Executors -enum class UISIEventSource { - INITIAL_SYNC, - INCREMENTAL_SYNC, - PAGINATION -} - data class E2EMessageDetected( val eventId: String, val roomId: String, val senderUserId: String, val senderDeviceId: String, val senderKey: String, - val sessionId: String, - val source: UISIEventSource) { + val sessionId: String + ) { companion object { - fun fromEvent(event: Event, roomId: String, source: UISIEventSource): E2EMessageDetected { + fun fromEvent(event: Event, roomId: String): E2EMessageDetected { val encryptedContent = event.content.toModel() return E2EMessageDetected( @@ -51,8 +46,7 @@ data class E2EMessageDetected( senderUserId = event.senderId ?: "", senderDeviceId = encryptedContent?.deviceId ?: "", senderKey = encryptedContent?.senderKey ?: "", - sessionId = encryptedContent?.sessionId ?: "", - source = source + sessionId = encryptedContent?.sessionId ?: "" ) } } @@ -61,6 +55,7 @@ data class E2EMessageDetected( class UISIDetector : LiveEventListener { interface UISIDetectorCallback { + val enabled: Boolean val reciprocateToDeviceEventType: String fun uisiDetected(source: E2EMessageDetected) fun uisiReciprocateRequest(source: Event) @@ -68,30 +63,16 @@ class UISIDetector : LiveEventListener { var callback: UISIDetectorCallback? = null - private val trackedEvents = mutableListOf>() + private val trackedEvents = mutableMapOf() private val executor = Executors.newSingleThreadExecutor() private val timer = Timer() private val timeoutMillis = 30_000L - var enabled = false + val enabled: Boolean get() = callback?.enabled.orFalse() - override fun onLiveEvent(roomId: String, event: Event) { - if (!enabled) return - if (!event.isEncrypted()) return - executor.execute { - handleEventReceived(E2EMessageDetected.fromEvent(event, roomId, UISIEventSource.INCREMENTAL_SYNC)) - } - } - - override fun onPaginatedEvent(roomId: String, event: Event) { - if (!enabled) return - if (!event.isEncrypted()) return - executor.execute { - handleEventReceived(E2EMessageDetected.fromEvent(event, roomId, UISIEventSource.PAGINATION)) - } - } - - override fun onEventDecrypted(eventId: String, roomId: String, clearEvent: JsonDict) { - if (!enabled) return + override fun onEventDecrypted(event: Event) { + val eventId = event.eventId + val roomId = event.roomId + if (!enabled || eventId == null || roomId == null) return executor.execute { unTrack(eventId, roomId) } @@ -104,57 +85,39 @@ class UISIDetector : LiveEventListener { } } - override fun onEventDecryptionError(eventId: String, roomId: String, throwable: Throwable) { - if (!enabled) return - executor.execute { - unTrack(eventId, roomId)?.let { - triggerUISI(it) - } -// if (throwable is MXCryptoError.OlmError) { -// if (throwable.olmException.message == "UNKNOWN_MESSAGE_INDEX") { -// unTrack(eventId, roomId)?.let { -// triggerUISI(it) -// } -// } -// } - } - } + override fun onEventDecryptionError(event: Event, throwable: Throwable) { + val eventId = event.eventId + val roomId = event.roomId + if (!enabled || eventId == null || roomId == null) return - private fun handleEventReceived(detectorEvent: E2EMessageDetected) { - if (!enabled) return - if (trackedEvents.any { it.first == detectorEvent }) { - Timber.w("## UISIDetector: Event ${detectorEvent.eventId} is already tracked") - } else { - // track it and start timer - val timeoutTask = object : TimerTask() { - override fun run() { - executor.execute { - unTrack(detectorEvent.eventId, detectorEvent.roomId) - Timber.v("## UISIDetector: Timeout on ${detectorEvent.eventId} ") - triggerUISI(detectorEvent) - } + val trackerId: String = trackerId(eventId, roomId) + if (trackedEvents.containsKey(trackerId)) { + Timber.w("## UISIDetector: Event $eventId is already tracked") + return + } + // track it and start timer + val timeoutTask = object : TimerTask() { + override fun run() { + executor.execute { + unTrack(eventId, roomId) + Timber.v("## UISIDetector: Timeout on $eventId") + triggerUISI(E2EMessageDetected.fromEvent(event, roomId)) } } - trackedEvents.add(detectorEvent to timeoutTask) - timer.schedule(timeoutTask, timeoutMillis) } + trackedEvents[trackerId] = timeoutTask + timer.schedule(timeoutTask, timeoutMillis) } + private fun trackerId(eventId: String, roomId: String): String = "$roomId-$eventId" + private fun triggerUISI(source: E2EMessageDetected) { if (!enabled) return Timber.i("## UISIDetector: Unable To Decrypt $source") callback?.uisiDetected(source) } - private fun unTrack(eventId: String, roomId: String): E2EMessageDetected? { - val index = trackedEvents.indexOfFirst { it.first.eventId == eventId && it.first.roomId == roomId } - return if (index != -1) { - trackedEvents.removeAt(index).let { - it.second.cancel() - it.first - } - } else { - null - } + private fun unTrack(eventId: String, roomId: String) { + trackedEvents.remove(trackerId(eventId, roomId))?.cancel() } } From 531b62f634ae2b4b728e7cefce74c97b0adacdda Mon Sep 17 00:00:00 2001 From: David Langley Date: Fri, 25 Mar 2022 23:00:47 +0000 Subject: [PATCH 087/565] Make properties private --- vector/src/main/java/im/vector/app/AutoRageShaker.kt | 4 ++-- vector/src/main/java/im/vector/app/UISIDetector.kt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/app/AutoRageShaker.kt b/vector/src/main/java/im/vector/app/AutoRageShaker.kt index 836e44f57b..b7578f8b89 100644 --- a/vector/src/main/java/im/vector/app/AutoRageShaker.kt +++ b/vector/src/main/java/im/vector/app/AutoRageShaker.kt @@ -70,8 +70,8 @@ class AutoRageShaker @Inject constructor( private val e2eDetectedFlow = MutableSharedFlow(replay = 0) private val matchingRSRequestFlow = MutableSharedFlow(replay = 0) - var hasSynced = false - var preferenceEnabled = false + private var hasSynced = false + private var preferenceEnabled = false fun initialize() { observeActiveSession() preferenceEnabled = vectorPreferences.labsAutoReportUISI() diff --git a/vector/src/main/java/im/vector/app/UISIDetector.kt b/vector/src/main/java/im/vector/app/UISIDetector.kt index f7c6725bd8..0dcc94516f 100644 --- a/vector/src/main/java/im/vector/app/UISIDetector.kt +++ b/vector/src/main/java/im/vector/app/UISIDetector.kt @@ -67,7 +67,7 @@ class UISIDetector : LiveEventListener { private val executor = Executors.newSingleThreadExecutor() private val timer = Timer() private val timeoutMillis = 30_000L - val enabled: Boolean get() = callback?.enabled.orFalse() + private val enabled: Boolean get() = callback?.enabled.orFalse() override fun onEventDecrypted(event: Event) { val eventId = event.eventId From f38bf2548f86efc679cb9bfd5e0b219e91701f0b Mon Sep 17 00:00:00 2001 From: David Langley Date: Fri, 25 Mar 2022 23:18:45 +0000 Subject: [PATCH 088/565] lint --- .../org/matrix/android/sdk/api/session/LiveEventListener.kt | 1 - .../android/sdk/internal/session/StreamEventsManager.kt | 1 - vector/src/main/java/im/vector/app/AutoRageShaker.kt | 5 ----- vector/src/main/java/im/vector/app/UISIDetector.kt | 1 - 4 files changed, 8 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/LiveEventListener.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/LiveEventListener.kt index 65e3e94d2d..def3ecbfca 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/LiveEventListener.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/LiveEventListener.kt @@ -17,7 +17,6 @@ package org.matrix.android.sdk.api.session import org.matrix.android.sdk.api.session.events.model.Event -import org.matrix.android.sdk.api.util.JsonDict interface LiveEventListener { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/StreamEventsManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/StreamEventsManager.kt index 9876e265bf..7c1ca1f630 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/StreamEventsManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/StreamEventsManager.kt @@ -23,7 +23,6 @@ import kotlinx.coroutines.launch import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.LiveEventListener import org.matrix.android.sdk.api.session.events.model.Event -import org.matrix.android.sdk.internal.crypto.MXEventDecryptionResult import timber.log.Timber import javax.inject.Inject diff --git a/vector/src/main/java/im/vector/app/AutoRageShaker.kt b/vector/src/main/java/im/vector/app/AutoRageShaker.kt index b7578f8b89..46390f30b8 100644 --- a/vector/src/main/java/im/vector/app/AutoRageShaker.kt +++ b/vector/src/main/java/im/vector/app/AutoRageShaker.kt @@ -25,23 +25,18 @@ import im.vector.app.features.session.coroutineScope import im.vector.app.features.settings.VectorPreferences import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.flow.cancellable import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.flow.subscribe import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.initsync.SyncStatusService -import org.matrix.android.sdk.api.session.sync.SyncState -import org.matrix.android.sdk.flow.flow import timber.log.Timber import javax.inject.Inject import javax.inject.Singleton diff --git a/vector/src/main/java/im/vector/app/UISIDetector.kt b/vector/src/main/java/im/vector/app/UISIDetector.kt index 0dcc94516f..2b6974eefb 100644 --- a/vector/src/main/java/im/vector/app/UISIDetector.kt +++ b/vector/src/main/java/im/vector/app/UISIDetector.kt @@ -20,7 +20,6 @@ import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.LiveEventListener import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.toModel -import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent import timber.log.Timber import java.util.Timer From 883c1816bc4787b1671a2582771d3b961062c40e Mon Sep 17 00:00:00 2001 From: LinAGKar Date: Sat, 26 Mar 2022 20:24:56 +0000 Subject: [PATCH 089/565] Translated using Weblate (Swedish) Currently translated at 100.0% (2171 of 2171 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/sv/ --- vector/src/main/res/values-sv/strings.xml | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/vector/src/main/res/values-sv/strings.xml b/vector/src/main/res/values-sv/strings.xml index 11c38830f6..2b4e3502f8 100644 --- a/vector/src/main/res/values-sv/strings.xml +++ b/vector/src/main/res/values-sv/strings.xml @@ -2012,7 +2012,7 @@ Du är inbjuden Utrymmen är ett nytt sätt att gruppera rum och personer. Lägg till existerande rum och utrymme - Lämna utrymme + Lämna Lägg till rum Utforska rum @@ -2420,4 +2420,22 @@ %d server-ACL-ändringar Trådar närmar sig beta 🎉 + Stoppa + Kontinuerligt plats aktiverad + Om du vill dela din plats kontinuerligt så behöver ${app_name} åtkomst till din plats hela tiden när appen är i bakgrunden. +\nVi kommer bara använda din plats under tiden du väljer. + Tillåt åtkomst + Dela den här platsen + Dela den här platsen + Dela plats kontinuerligt + Dela plats kontinuerligt + Dela min nuvarande plats + Dela min nuvarande plats + Zooma in till nuvarande plats + Nål för vald plats på kartan + Vi kommer närmare att släppa en offentlig beta för trådar. +\n +\nMedan vi förbereder den så behöver vi göra några ändringar: trådar skapade innan den här tidpunkten kommer att visas som vanliga svar. +\n +\nDet kommer att vara en engångshändelse, eftersom trådar nu är en del av Matrixspecifikationen. \ No newline at end of file From cc8fd0c9a73aab2f4062fd694ce1d81e34394bd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sveinn=20=C3=AD=20Felli?= Date: Sat, 26 Mar 2022 16:22:02 +0000 Subject: [PATCH 090/565] Translated using Weblate (Icelandic) Currently translated at 82.5% (1793 of 2171 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/is/ --- vector/src/main/res/values-is/strings.xml | 234 +++++++++++++++++++--- 1 file changed, 205 insertions(+), 29 deletions(-) diff --git a/vector/src/main/res/values-is/strings.xml b/vector/src/main/res/values-is/strings.xml index 6deeb7fac5..f48aa28e62 100644 --- a/vector/src/main/res/values-is/strings.xml +++ b/vector/src/main/res/values-is/strings.xml @@ -26,8 +26,8 @@ %1$s fjarlægði birtingarnafn sitt (sem var %2$s) %1$s breytti umræðuefninu í: %2$s %1$s breytti heiti spjallrásarinnar í: %2$s - %s hringdi myndsamtal. - %s hringdi raddsamtal. + %s hringdi myndsímtal. + %s hringdi raddsímtal. %s svaraði símtalinu. %s lauk símtalinu. %1$s fjarlægði heiti spjallrásar @@ -63,8 +63,8 @@ eða Bjóða Skrá út - Raddsamtal - Myndsamtal + Raddsímtal + Myndsímtal Merkja allt sem lesið Opna Loka @@ -88,8 +88,8 @@ Notandanafn Skrá út Leita - Hefja raddsamtal - Hefja myndsamtal + Hefja raddsímtal + Hefja myndsímtal Senda skrár Taka ljósmynd eða myndskeið Taka ljósmynd @@ -108,9 +108,9 @@ Miðlungs Lítið Símtal - Innhringing myndsamtals - Innhringing raddsamtals - Samtal í gangi… + Innhringing myndsímtals + Innhringing raddsímtals + Símtal í gangi… Upplýsingar NEI @@ -245,9 +245,9 @@ Skráðu inn gilda URL-lóð Inniheldur ekki gilt JSON Of margar beiðnir hafa verið sendar - Samtal tengist… + Símtal tengist… Símtali lokið - Boð um samtal + Boð um símtöl Nota innbyggða myndavél Minnst á Titra þegar minnst er á @@ -268,8 +268,8 @@ Tilraunir Kæra efni Slóð á heimaþjón - Ertu viss að þú viljir byrja raddsamtal? - Ertu viss að þú viljir byrja myndsamtal? + Ertu viss að þú viljir byrja raddsímtal\? + Ertu viss að þú viljir byrja myndsímtal\? Fara í ólesið Banna Afbanna @@ -430,8 +430,8 @@ Þú settir símtalið í bið %s setti símtalið í bið Raddsímtal við %s - Myndsamtal við %s - Myndsamtal í gangi… + Myndsímtal við %s + Myndsímtal í gangi… Ósvarað myndsímtal %d ósvöruð myndsímtöl @@ -521,7 +521,7 @@ %1$s tók %2$s úr banni. Ástæða: %3$s Þú fjarlægðir %1$s. Ástæða: %2$s %1$s fjarlægði %2$s. Ástæða: %3$s - Þú hafnaðir boðinu. Ástæða: %2$s + Þú hafnaðir boðinu. Ástæða: %1$s %1$s hafnaði boðinu. Ástæða: %2$s Þú hættir. Ástæða: %1$s %1$s hætti. Ástæða: %2$s @@ -562,7 +562,7 @@ %1$s fjarlægði %2$s viðmótshluta Þú bættir við %1$s viðmótshluta %1$s bætti við %2$s viðmótshluta - Þú samþykktir boð um að taka þátt í %$s + Þú samþykktir boð um að taka þátt í %1$s Þú afturkallaðir boðið til %1$s %1$s afturkallaði boðið til %2$s Þú afturkallaðir boð til %1$s um þátttöku í spjallrásinni @@ -602,10 +602,10 @@ Þú gerðir ferilskrá spjallrásar héðan í frá sýnilega fyrir %1$s Þú laukst símtalinu. Þú svaraðir símtalinu. - Þú sendir gögn til að setja upp samtalið. - %s sendi gögn til að setja upp samtalið. - Þú hringdir raddsamtal. - Þú hringdir myndsamtal. + Þú sendir gögn til að setja upp símtalið. + %s sendi gögn til að setja upp símtalið. + Þú hringdir raddsímtal. + Þú hringdir myndsímtal. Þú breyttir heiti spjallrásarinnar í: %1$s Þú breyttir auðkennismynd spjallrásarinnar %1$s breytti auðkennismynd spjallrásarinnar @@ -623,7 +623,7 @@ Opna emoji-tánmyndaval Skipta um auðkennismynd Opna viðmótshluta - Virkt samtal (%1$s) + Virkt símtal (%1$s) Talnaborð Nýtt PIN-númer Opna notkunarskilmála %s @@ -1080,14 +1080,14 @@ Notendur Flutningur Tengjast - Virkt samtal (%1$s) · + Virkt símtal (%1$s) · - Virkt samtal · - %1$d virk samtöl · + Virkt símtal · + %1$d virk símtöl · Ekkert svar - Innhringing myndsamtals - Innhringing raddsamtals + Innhringing myndsímtals + Innhringing raddsímtals Hringja til baka Þessu símtali er lokið Henda breytingum @@ -1736,8 +1736,8 @@ Efni atburðar Ósvarað myndsímtal Ósvarað símtal - Virkt myndsamtal - Virkt raddsamtal + Virkt myndsímtal + Virkt raddsímtal Þú hafnaðir þessu símtali QR-kóði var ekki skannaður! Ógildur QR-kóði (ógild slóð)! @@ -1774,4 +1774,180 @@ Tekur stjórnunarréttindi af notanda með uppgefið auðkenni Hættir að hunsa notanda, birtir skilaboð viðkomandi héðan í frá Setja upp varið öryggisafrit + Virkja að strjúka á tímalínu til að svara + Birta allan ferilinn í dulrituðum spjallrásum + Mistókst að senda umsögnina (%s) + Það tókst að senda umsögnina + Mistókst að senda tillöguna (%s) + Það tókst að senda tillöguna + Engar skráðar ýtigáttir + Engar ýtireglur skilgreindar + Ýtireglur (push rules) + Flytja e2e-lykla inn úr skránni \"%1$s\". + Hver sem er getur tekið þátt í þessari spjallrás + Ekki er ennþá búið að útbúa spjallrásina. Hætta við að búa hana til\? + Bjóddu með tölvupósti, finndu tengiliði og ýmislegt fleira… + Ljúka við að setja upp uppgötvun. + Þú ert núna ekki að nota neinn auðkennisþjón. Til að uppgötva og vera finnanleg/ur fyrir félaga þína í teyminu, skaltu bæta við auðkennisþjóni hér fyrir neðan. + ${app_name} krefst þess að þú setjir inn auðkennin þín til að framkvæma þessa aðgerð. + Endurauðkenning er nauðsynleg + Yfirfarðu hvar þú sért skráð/ur inn + Nota endurheimtulykil + Athuga öryggisafritunarlykil + Þetta er ekki gildur endurheimtulykill + Settu inn %s þinn til að halda áfram + + %d aðili sem þú þekkir hefur þegar tekið þátt + %d aðilar sem þú þekkir hafa þegar tekið þátt + + Tengillinn %1$s fer með þig yfir á annað vefsvæði: %2$s. +\n +\nErtu viss um að þú viljir halda áfram\? + Tengillinn er ekki rétt formaður + Finn ekki þessa spjallrás. Gakktu úr skugga um að hún sé til. + Get ekki opnað spjallrás þar sem þú ert í banni. + Krafist er PIN-númers í hvert skipti sem þú opnar ${app_name}. + Krafist er PIN-númers ef þú notar ekki ${app_name} í 2 mínútur. + Krefjast PIN-númers eftir 2 mínútur + Aðeins birta fjölda ólesinna skilaboða í einfaldri tilkynningu. + Birta nánari upplýsingar eins og heiti spjallrása og efni skilaboða. + PIN-númer er eina leiðin til að aflæsa ${app_name}. + Virkjaðu sérstök lífkenni tækisins, eins og fingrafaraskönnun og andlitakennsl. + Virkja lífkenni + Ef þú vilt endurstilla PIN-númerið þitt, geturðu ýtt á \'Gleymt PIN-númer\?\' og endurstillt það. + Verðu aðganginn með PIN-númeri og lífkennum. + Til að endurstilla PIN-númerið, þarftu að skrá þig inn aftur og útbúa nýtt. + Mistókst að fullgilda PIN-númer, ýttu á annað nýtt. + Veldu PIN-númer í öryggisskyni + Of margar villur, þú hefur verið skráð/ur út + Aðvörun! Síðasta tilraunin sem eftir er áður en útskráning fer fram! + + Rangur kóði, %d tilraun eftir + Rangur kóði, %d tilraunir eftir + + Yfirfarðu stillingarnar þínar til að virkja ýtitilkynningar + Leita að tengiliðum á Matrix + Tengiliðaskráin þín er tóm + Næ í tengiliðina þína… + Við iðum í skinninu eftir að tilkynbna að við höfum skipt um nafn! Forritið er að fullu uppfært og þú ert skráð/ur aftur inn á aðganginn þinn. + Bíð eftir ferli dulritunar + Þú hefur ekki aðgang að þessum skilaboðum því sendandinn hefur viljandi ekki sent dulritunarlyklana + Þú hefur ekki aðgang að þessum skilaboðum því setunni þinni er ekki treyst af sendandanum + Þú hefur ekki aðgang að þessum skilaboðum því sendandinn hefur lokað á þig + Vegna enda-í-enda dulritunar, gætirðu þurft að bíða eftir skilaboðum frá einhverjum þar sem þér hafa ekki verið sendir dulritunarlyklar á réttan hátt. + Bíð eftir þessum skilaboðum, þetta getur tekið smá tíma + Þú hefur ekki aðgang að þessum skilaboðum + Þér tókst að breyta stillingum spjallrásarinnar + Settu inn öryggisfrasa sem aðeins þú þekkir, þetta er notað til að verja leyndarmálin sem þú geymir á netþjóninum þínum. + Geymdu öryggislykilinn þinn á öruggum stað, eins og í lykilorðastýringu eða jafnvel í peningaskáp. + Settu inn leynilegan frasa eða setningu sem aðeins þú þekkir, og útbúðu lykil fyrir öryggisafrit. + Útbúðu öryggislykil til að geyma á öruggum stað, eins og í lykilorðastýringu eða jafnvel í peningaskáp. + Tryggðu þig gegn því að missa aðgang að dulrituðum skilaboðum og gögnum með því að taka öryggisafrit af dulritunarlyklunum á netþjóninum þinum. + Settu inn slóðina á auðkennisþjón + Aftengjast frá auðkennisþjóninum %s \? + + Boð voru send til %1$s og eins til viðbótar + Boð voru send til %1$s og %2$d til viðbótar + + Þetta er ekki gildur QR-kóði á Matrix + Boð voru send til %1$s og %2$s + Boð var sent til %1$s + Halló, talaðu við mig á ${app_name}: %s + Ekki tókst að setja upp kross-undirritun + Sannprófa gagnvirkt með táknmyndum + Sannprófa innskráningu + Það lítur út fyrir að heimaþjónninn þinn styðji ekki ennþá við notkun svæða + Villa kom upp þegar við áframsendingu símtals + %1$s Ýttu til að fara til baka + sinnum hafnað + Raddsímtali hafnað + Mynddsímtali lauk • %1$s + Raddsímtali lauk • %1$s + %1$s hafnaði þessu símtali + Það eru óvistaðar breytingar. Viltu henda þeim\? + Getur ekki sent sjálfum þér bein skilaboð! + Breyta fyrirliggjandi PIN-númeri þínu + Birta efni í tilkynningum + Stilla varnir + Verja aðgang + Búum til spjallrás fyrir hvern og einn þeirra. Þú getur bætt fleirum við síðar, þar með töldum þeim sem fyrir eru þegar. + Búum til spjallrás fyrir þá. Þú getur bætt fleirum við síðar. + Bættu við nánari atriðum svo fólk eigi auðveldara með að þekkja þetta. Þú getur breytt þessu hvenær sem er. + Bættu við nánari atriðum til að aðgreina þetta frá öðru. Þú getur breytt þessu hvenær sem er. + Ertu viss um að þú viljir eyða öllum ósendum skilaboðum úr þessari spjallrás\? + Viltu hætta við að senda skilaboðin\? + Eyða öllum misförnum skilaboðum + Stöðuatburður sendur! + Rangt sniðinn atburður + Vantar gerð skilaboða + Senda sérsniðinn stöðuatburð + Stöðuatburðir + Senda stöðuatburð + Forritunartól + Skoða leskvittanir + Skilaboð voru ekki send vegna villu + Loka emoji-tánmyndavali + Stig trausts er treyst + Stig trausts er aðvarandi + Sjálfgefið stig trausts + er með ósend drög + Sum skilaboð hafa ekki verið send + Kross-undirritun er virk +\nLyklum er ekki treyst + Kross-undirritun er virk +\nLyklum er treyst. +\nEinkalyklar eru ekki þekktir + Vantreyst innskráning + Villa kom upp við að sækja upplýsingar um traust + Taka samt þátt + Taka þátt í svæði + Taktu þátt í svæðinu mínu %1$s %2$s + Þau munu ekki vera hluti af %s + Þau munu geta kannað %s + Í augnablikinu ert þetta bara þú. %s verður enn betri með fleirum. + Bjóddu fólki inn á svæðið þitt + Í hvaða málum ertu að vinna\? + Gakktu úr skugga um að rétta fólkið hafi aðgang að %s. Þú getur boðið fleira fólki síðar. + Hverjir eru félagar í teyminu þínu\? + Hverjar eru umræðurnar sem þú vilt hafa í %s\? + Gefðu því nafn til að halda áfram. + Gakktu úr skugga um að rétta fólkið hafi aðgang að %s. + Til að ganga til liðs við fyrirliggjandi svæði þarftu boð. + Hvaða tegund af svæði viltu búa til\? + Ertu viss um að þú viljir ljúka þessari könnun\? Þú munt ekki geta endurheimt hana ef hún hefur einu sinni verið fjarlægð. + Þetta mun birta lokaniðurstöður könnunarinnar og koma í veg fyrir að fólk geti kosið. + %s í stillingunum til að fá boð beint í ${app_name}. + Hver sem er í yfirsvæði mun geta fundið og tekið þátt í þessari spjallrás - ekki er þörf á að bjóða öllum handvirkt. Þú munt geta breytt þessu í stillingum spjallrásarinnar hvenær sem er. + Hver sem er í %s mun geta fundið og tekið þátt í þessari spjallrás - ekki er þörf á að bjóða öllum handvirkt. Þú munt geta breytt þessu í stillingum spjallrásarinnar hvenær sem er. + Get ekki svarað eða breytt á meðan talskilaboð eru virk + Ýttu á upptökuna þína til að stöðva eða hlusta + Haltu niðri til að taka upp, slepptu til að senda + Því miður, villa kom upp við að reyna að taka þátt: %s + Uppfæra í þá útgáfu spjallrásar sem mælt er með + Uppfæra yfirsvæði sjálfvirkt + Þú munt uppfæra þessa spjallrás úr %1$s upp í %2$s. + Þetta svæði er ekki með neinar spjallrásir + Hafðu samband við stjórnanda heimaþjónsins þíns til að fá frekari upplýsingar + Ertu til í tilraunastarfsemi\? +\nÞú getur bætt fyrirliggjandi svæðum í annað svæði. + Allar spjallrásir sem þú ert í munu birtast á forsíðu. + Leitarðu að einhverjum sem ekki er í %s\? + Athugaðu: forritið verður endurræst + Virkja spjallþræði fyrir skilaboð + Tilkynna afkóðunarvillur sjálfvirkt. + Bættu svæði við eitthvað svæði sem þú stýrir. + Bæta við fyrirliggjandi spjallrásum og svæði + Veldu það sem á að yfirgefa + Yfirgefa tilteknar spjallrásir og svæði… + Ekki yfirgefa neinar spjallrásir eða svæði + Ertu viss um að þú viljir yfirgefa %s\? + Einkasvæði fyrir þig og félaga í teyminu þínu + Bæta við í uppgefið svæði + Gögn notandaaðgangs + Virkar setur + Óheimilt, vantar gild auðkenni sannvottunar + SSL-villa: auðkenni jafningjans hefur ekki verið sannreynt. + Næ ekki að tengjast heimaþjóni á slóðinni %s. Athugaðu slóðina eða veldu heimaþjón handvirkt.. + Yfirfarðu reglur þessa heimaþjóns: + Senda feril beiðna um deilingu lykla \ No newline at end of file From ee9c8d862674a1e413bead1e7f39bf148e25b227 Mon Sep 17 00:00:00 2001 From: bmarty Date: Mon, 28 Mar 2022 00:03:23 +0000 Subject: [PATCH 091/565] Sync analytics plan --- .../app/features/analytics/plan/ViewRoom.kt | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/vector/src/main/java/im/vector/app/features/analytics/plan/ViewRoom.kt b/vector/src/main/java/im/vector/app/features/analytics/plan/ViewRoom.kt index a73ca5a9b3..5ad1beaee5 100644 --- a/vector/src/main/java/im/vector/app/features/analytics/plan/ViewRoom.kt +++ b/vector/src/main/java/im/vector/app/features/analytics/plan/ViewRoom.kt @@ -63,6 +63,49 @@ data class ViewRoom( */ MessageUser, + /** + * Room switched due to user interacting with a file search result. + */ + MobileFileSearch, + + /** + * Room accessed via interacting with the incall screen. + */ + MobileInCall, + + /** + * Room accessed via interacting with direct chat item in the room + * contact detail screen. + */ + MobileRoomMemberDetail, + + /** + * Room switched due to user interacting with a room search result. + */ + MobileRoomSearch, + + /** + * Room accessed via interacting with direct chat item in the search + * contact detail screen. + */ + MobileSearchContactDetail, + + /** + * Room accessed via interacting with direct chat item in the space + * contact detail screen. + */ + MobileSpaceMemberDetail, + + /** + * Space accessed via interacting with the space menu. + */ + MobileSpaceMenu, + + /** + * Space accessed via interacting with a space settings menu item. + */ + MobileSpaceSettings, + /** * Room accessed via a push/desktop notification. */ From 8aaaf80262a26ca7b02852386a11cb88ca685d37 Mon Sep 17 00:00:00 2001 From: ericdecanini Date: Mon, 28 Mar 2022 10:47:59 +0200 Subject: [PATCH 092/565] Fixes lint error --- .../home/room/detail/timeline/factory/MessageItemFactory.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt index 5770a457b8..adb5cfdda6 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -492,8 +492,7 @@ class MessageItemFactory @Inject constructor( informationData: MessageInformationData, highlight: Boolean, callback: TimelineEventController.Callback?, - attributes: AbsMessageItem.Attributes) - : MessageTextItem? { + attributes: AbsMessageItem.Attributes): MessageTextItem? { // For compatibility reason we should display the body return buildMessageTextItem(messageContent.body, false, informationData, highlight, callback, attributes) } From 931c0e9826244100b76cb536988c9743551e794c Mon Sep 17 00:00:00 2001 From: ericdecanini Date: Mon, 28 Mar 2022 11:01:31 +0200 Subject: [PATCH 093/565] Improves accessibility talkback on MessageAudioItem --- .../room/detail/timeline/item/MessageAudioItem.kt | 12 ++++++++---- vector/src/main/res/values/strings.xml | 6 +++--- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageAudioItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageAudioItem.kt index c3ec93ced3..bf248a27e8 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageAudioItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageAudioItem.kt @@ -76,7 +76,8 @@ abstract class MessageAudioItem : AbsMessageItem() { contentUploadStateTrackerBinder.bind(attributes.informationData.eventId, isLocalFile, holder.progressLayout) } else { holder.audioPlaybackControlButton.setImageResource(R.drawable.ic_cross) - holder.audioPlaybackControlButton.contentDescription = holder.view.context.getString(R.string.error_audio_message_unable_to_play) + holder.audioPlaybackControlButton.contentDescription = + holder.view.context.getString(R.string.error_audio_message_unable_to_play, filename) holder.progressLayout.isVisible = false } } @@ -105,19 +106,22 @@ abstract class MessageAudioItem : AbsMessageItem() { private fun renderIdleState(holder: Holder) { holder.audioPlaybackControlButton.setImageResource(R.drawable.ic_play_pause_play) - holder.audioPlaybackControlButton.contentDescription = holder.view.context.getString(R.string.a11y_play_audio_message) + holder.audioPlaybackControlButton.contentDescription = + holder.view.context.getString(R.string.a11y_play_audio_message, filename) holder.audioPlaybackTime.text = formatPlaybackTime(duration) } private fun renderPlayingState(holder: Holder, state: AudioMessagePlaybackTracker.Listener.State.Playing) { holder.audioPlaybackControlButton.setImageResource(R.drawable.ic_play_pause_pause) - holder.audioPlaybackControlButton.contentDescription = holder.view.context.getString(R.string.a11y_pause_audio_message) + holder.audioPlaybackControlButton.contentDescription = + holder.view.context.getString(R.string.a11y_pause_audio_message, filename) holder.audioPlaybackTime.text = formatPlaybackTime(state.playbackTime) } private fun renderPausedState(holder: Holder, state: AudioMessagePlaybackTracker.Listener.State.Paused) { holder.audioPlaybackControlButton.setImageResource(R.drawable.ic_play_pause_play) - holder.audioPlaybackControlButton.contentDescription = holder.view.context.getString(R.string.a11y_play_audio_message) + holder.audioPlaybackControlButton.contentDescription = + holder.view.context.getString(R.string.a11y_play_audio_message, filename) holder.audioPlaybackTime.text = formatPlaybackTime(state.playbackTime) } diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index d646b73aa0..90f6c5f7b1 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -2860,9 +2860,9 @@ Cannot reply or edit while voice message is active Voice Message (%1$s) - Play Audio Message - Pause Audio Message - Pause Audio Message + Play %1$s + Pause %1$s + Unable to play %1$s %1$s (%2$s) Anyone in %s will be able to find and join this room - no need to manually invite everyone. You’ll be able to change this in room settings anytime. From 4fe86503879379237231dd4d676b328d9971380a Mon Sep 17 00:00:00 2001 From: David Langley Date: Mon, 28 Mar 2022 10:07:29 +0100 Subject: [PATCH 094/565] Add changelog --- changelog.d/5596.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/5596.bugfix diff --git a/changelog.d/5596.bugfix b/changelog.d/5596.bugfix new file mode 100644 index 0000000000..f51794c352 --- /dev/null +++ b/changelog.d/5596.bugfix @@ -0,0 +1 @@ +Align auto-reporting of decryption errors implementation with web client. \ No newline at end of file From bb19987314c5794b0fa13e691facbeac7cc970b2 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 28 Mar 2022 11:09:15 +0200 Subject: [PATCH 095/565] `is Loading` has been replaced by `Uninitialized, is Loading` in `when` statements, which is not strictly equivalent This commit revert those changes. --- .../app/features/createdirect/CreateDirectRoomActivity.kt | 3 +-- .../app/features/discovery/DiscoverySettingsController.kt | 4 ++-- .../app/features/home/room/detail/search/SearchFragment.kt | 3 +-- .../main/java/im/vector/app/features/login/LoginFragment.kt | 5 +---- .../roomprofile/uploads/files/RoomUploadsFilesFragment.kt | 3 +-- .../roomprofile/uploads/media/RoomUploadsMediaFragment.kt | 3 +-- .../features/settings/devtools/AccountDataEpoxyController.kt | 3 +-- .../settings/threepids/ThreePidsSettingsController.kt | 3 +-- 8 files changed, 9 insertions(+), 18 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt index 0d36c7c7cc..0053ab0fc6 100644 --- a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt +++ b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt @@ -28,7 +28,6 @@ import com.airbnb.mvrx.Async import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Loading import com.airbnb.mvrx.Success -import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.viewModel import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint @@ -167,10 +166,10 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() { private fun renderCreateAndInviteState(state: Async) { when (state) { - Uninitialized, is Loading -> renderCreationLoading() is Success -> renderCreationSuccess(state()) is Fail -> renderCreationFailure(state.error) + else -> Unit } } diff --git a/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsController.kt b/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsController.kt index b338f367e3..e7d74e3d38 100644 --- a/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsController.kt +++ b/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsController.kt @@ -50,7 +50,6 @@ class DiscoverySettingsController @Inject constructor( override fun buildModels(data: DiscoverySettingsState) { when (data.identityServer) { - Uninitialized, is Loading -> { loadingItem { id("identityServerLoading") @@ -71,6 +70,7 @@ class DiscoverySettingsController @Inject constructor( buildMsisdnSection(data.phoneNumbersList) } } + else -> Unit } } @@ -356,7 +356,6 @@ class DiscoverySettingsController @Inject constructor( colorProvider(host.colorProvider) stringProvider(host.stringProvider) when (pidInfo.isShared) { - Uninitialized, is Loading -> { buttonIndeterminate(true) } @@ -390,6 +389,7 @@ class DiscoverySettingsController @Inject constructor( } null -> Unit } + else -> Unit } } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchFragment.kt index fbcf29d863..b3543ae579 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchFragment.kt @@ -26,7 +26,6 @@ import androidx.recyclerview.widget.LinearLayoutManager import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Loading import com.airbnb.mvrx.Success -import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.args import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState @@ -89,7 +88,6 @@ class SearchFragment @Inject constructor( override fun invalidate() = withState(searchViewModel) { state -> if (state.searchResult.isNullOrEmpty()) { when (state.asyncSearchRequest) { - Uninitialized, is Loading -> { views.stateView.state = StateView.State.Loading } @@ -101,6 +99,7 @@ class SearchFragment @Inject constructor( title = getString(R.string.search_no_results), image = ContextCompat.getDrawable(requireContext(), R.drawable.ic_search_no_results)) } + else -> Unit } } else { controller.setData(state) diff --git a/vector/src/main/java/im/vector/app/features/login/LoginFragment.kt b/vector/src/main/java/im/vector/app/features/login/LoginFragment.kt index 22f8792078..df613eeae2 100644 --- a/vector/src/main/java/im/vector/app/features/login/LoginFragment.kt +++ b/vector/src/main/java/im/vector/app/features/login/LoginFragment.kt @@ -28,8 +28,6 @@ import androidx.core.view.isVisible import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.Success -import com.airbnb.mvrx.Uninitialized import im.vector.app.R import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.extensions.hidePassword @@ -269,7 +267,6 @@ class LoginFragment @Inject constructor() : AbstractSSOLoginFragment { // Ensure password is hidden views.passwordField.hidePassword() @@ -292,7 +289,7 @@ class LoginFragment @Inject constructor() : AbstractSSOLoginFragment Unit + else -> Unit } when (state.asyncRegistration) { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/files/RoomUploadsFilesFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/files/RoomUploadsFilesFragment.kt index 953838aecd..4b5d44e886 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/files/RoomUploadsFilesFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/files/RoomUploadsFilesFragment.kt @@ -24,7 +24,6 @@ import androidx.core.content.ContextCompat import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Loading import com.airbnb.mvrx.Success -import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.withState import im.vector.app.R @@ -92,7 +91,6 @@ class RoomUploadsFilesFragment @Inject constructor( override fun invalidate() = withState(uploadsViewModel) { state -> if (state.fileEvents.isEmpty()) { when (state.asyncEventsRequest) { - Uninitialized, is Loading -> { views.genericStateViewListStateView.state = StateView.State.Loading } @@ -110,6 +108,7 @@ class RoomUploadsFilesFragment @Inject constructor( ) } } + else -> Unit } } else { views.genericStateViewListStateView.state = StateView.State.Content diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/media/RoomUploadsMediaFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/media/RoomUploadsMediaFragment.kt index 2f33f8403c..59b66603df 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/media/RoomUploadsMediaFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/media/RoomUploadsMediaFragment.kt @@ -29,7 +29,6 @@ import androidx.recyclerview.widget.GridLayoutManager import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Loading import com.airbnb.mvrx.Success -import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.withState import com.google.android.material.appbar.AppBarLayout @@ -189,7 +188,6 @@ class RoomUploadsMediaFragment @Inject constructor( override fun invalidate() = withState(uploadsViewModel) { state -> if (state.mediaEvents.isEmpty()) { when (state.asyncEventsRequest) { - Uninitialized, is Loading -> { views.genericStateViewListStateView.state = StateView.State.Loading } @@ -207,6 +205,7 @@ class RoomUploadsMediaFragment @Inject constructor( ) } } + else -> Unit } } else { views.genericStateViewListStateView.state = StateView.State.Content diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataEpoxyController.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataEpoxyController.kt index 4748aeb45e..e840ab2266 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataEpoxyController.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataEpoxyController.kt @@ -21,7 +21,6 @@ import com.airbnb.epoxy.TypedEpoxyController import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Loading import com.airbnb.mvrx.Success -import com.airbnb.mvrx.Uninitialized import im.vector.app.R import im.vector.app.core.epoxy.loadingItem import im.vector.app.core.resources.StringProvider @@ -46,7 +45,6 @@ class AccountDataEpoxyController @Inject constructor( if (data == null) return val host = this when (data.accountData) { - Uninitialized, is Loading -> { loadingItem { id("loading") @@ -82,6 +80,7 @@ class AccountDataEpoxyController @Inject constructor( } } } + else -> Unit } } } diff --git a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsController.kt b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsController.kt index 61d93b6f5f..7a4033fb82 100644 --- a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsController.kt +++ b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsController.kt @@ -21,7 +21,6 @@ import com.airbnb.epoxy.TypedEpoxyController import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Loading import com.airbnb.mvrx.Success -import com.airbnb.mvrx.Uninitialized import im.vector.app.R import im.vector.app.core.epoxy.loadingItem import im.vector.app.core.epoxy.noResultItem @@ -78,7 +77,6 @@ class ThreePidsSettingsController @Inject constructor( } when (data.threePids) { - Uninitialized, is Loading -> { loadingItem { id("loading") @@ -95,6 +93,7 @@ class ThreePidsSettingsController @Inject constructor( val dataList = data.threePids.invoke() buildThreePids(dataList, data) } + else -> Unit } } From 58cc3931b91cd107ea54b426eace75ebd19ecb31 Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Mon, 28 Mar 2022 13:53:44 +0300 Subject: [PATCH 096/565] Add beta feedback for threads --- .../main/res/drawable/bg_shadow_divider.xml | 9 +++ .../threads/list/views/ThreadListFragment.kt | 11 ++++ .../features/rageshake/BugReportActivity.kt | 9 +++ .../app/features/rageshake/BugReporter.kt | 10 +-- .../app/features/rageshake/ReportType.kt | 3 +- .../main/res/layout/fragment_thread_list.xml | 66 +++++++++++++++++-- vector/src/main/res/values/strings.xml | 3 + 7 files changed, 100 insertions(+), 11 deletions(-) create mode 100644 library/ui-styles/src/main/res/drawable/bg_shadow_divider.xml diff --git a/library/ui-styles/src/main/res/drawable/bg_shadow_divider.xml b/library/ui-styles/src/main/res/drawable/bg_shadow_divider.xml new file mode 100644 index 0000000000..9d0ef632ec --- /dev/null +++ b/library/ui-styles/src/main/res/drawable/bg_shadow_divider.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt index d5659efa49..dc16680334 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt @@ -39,6 +39,8 @@ import im.vector.app.features.home.room.threads.arguments.ThreadTimelineArgs import im.vector.app.features.home.room.threads.list.viewmodel.ThreadListController import im.vector.app.features.home.room.threads.list.viewmodel.ThreadListViewModel import im.vector.app.features.home.room.threads.list.viewmodel.ThreadListViewState +import im.vector.app.features.rageshake.BugReporter +import im.vector.app.features.rageshake.ReportType import org.matrix.android.sdk.api.session.room.threads.model.ThreadSummary import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.util.MatrixItem @@ -46,6 +48,7 @@ import javax.inject.Inject class ThreadListFragment @Inject constructor( private val avatarRenderer: AvatarRenderer, + private val bugReporter: BugReporter, private val threadListController: ThreadListController, val threadListViewModelFactory: ThreadListViewModel.Factory ) : VectorBaseFragment(), @@ -80,6 +83,7 @@ class ThreadListFragment @Inject constructor( super.onViewCreated(view, savedInstanceState) initToolbar() initTextConstants() + initBetaFeedback() views.threadListRecyclerView.configureWith(threadListController, TimelineItemAnimator(), hasFixedSize = false) threadListController.listener = this } @@ -101,6 +105,13 @@ class ThreadListFragment @Inject constructor( resources.getString(R.string.reply_in_thread)) } + private fun initBetaFeedback() { + views.threadsFeedBackConstraintLayout.isVisible = resources.getBoolean(R.bool.feature_threads_beta_feedback_enabled) + views.threadFeedbackDivider.isVisible = resources.getBoolean(R.bool.feature_threads_beta_feedback_enabled) + views.threadsFeedBackConstraintLayout.debouncedClicks { + bugReporter.openBugReportScreen(requireActivity(), reportType = ReportType.THREADS_BETA_FEEDBACK) + } + } override fun invalidate() = withState(threadListViewModel) { state -> renderEmptyStateIfNeeded(state) threadListController.update(state) diff --git a/vector/src/main/java/im/vector/app/features/rageshake/BugReportActivity.kt b/vector/src/main/java/im/vector/app/features/rageshake/BugReportActivity.kt index 2d4bc704a4..701d04f3c9 100755 --- a/vector/src/main/java/im/vector/app/features/rageshake/BugReportActivity.kt +++ b/vector/src/main/java/im/vector/app/features/rageshake/BugReportActivity.kt @@ -85,6 +85,15 @@ class BugReportActivity : VectorBaseActivity() { hideBugReportOptions() } + ReportType.THREADS_BETA_FEEDBACK -> { + supportActionBar?.setTitle(R.string.send_feedback_threads_title) + + views.bugReportFirstText.setText(R.string.send_feedback_threads_info) + views.bugReportTextInputLayout.hint = getString(R.string.feedback) + views.bugReportButtonContactMe.isVisible = true + + hideBugReportOptions() + } else -> { // other types not supported here } diff --git a/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt b/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt index 6434ba60f2..4a62d62e34 100755 --- a/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt +++ b/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt @@ -255,11 +255,12 @@ class BugReporter @Inject constructor( if (!mIsCancelled) { val text = when (reportType) { - ReportType.BUG_REPORT -> "[Element] $bugDescription" - ReportType.SUGGESTION -> "[Element] [Suggestion] $bugDescription" - ReportType.SPACE_BETA_FEEDBACK -> "[Element] [spaces-feedback] $bugDescription" + ReportType.BUG_REPORT -> "[Element] $bugDescription" + ReportType.SUGGESTION -> "[Element] [Suggestion] $bugDescription" + ReportType.SPACE_BETA_FEEDBACK -> "[Element] [spaces-feedback] $bugDescription" ReportType.AUTO_UISI_SENDER, - ReportType.AUTO_UISI -> bugDescription + ReportType.AUTO_UISI -> bugDescription + ReportType.THREADS_BETA_FEEDBACK -> "[Element] [threads-feedback] $bugDescription" } // build the multi part request @@ -350,6 +351,7 @@ class BugReporter @Inject constructor( builder.addFormDataPart("label", "android") builder.addFormDataPart("label", "uisi-sender") } + ReportType.THREADS_BETA_FEEDBACK -> builder.addFormDataPart("label", "threads-feedback") } if (getCrashFile().exists()) { diff --git a/vector/src/main/java/im/vector/app/features/rageshake/ReportType.kt b/vector/src/main/java/im/vector/app/features/rageshake/ReportType.kt index f9dc628914..f75420ea55 100644 --- a/vector/src/main/java/im/vector/app/features/rageshake/ReportType.kt +++ b/vector/src/main/java/im/vector/app/features/rageshake/ReportType.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 New Vector Ltd + * Copyright (c) 2022 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. @@ -20,6 +20,7 @@ enum class ReportType { BUG_REPORT, SUGGESTION, SPACE_BETA_FEEDBACK, + THREADS_BETA_FEEDBACK, AUTO_UISI, AUTO_UISI_SENDER, } diff --git a/vector/src/main/res/layout/fragment_thread_list.xml b/vector/src/main/res/layout/fragment_thread_list.xml index 7e7c79f8c3..026432035f 100644 --- a/vector/src/main/res/layout/fragment_thread_list.xml +++ b/vector/src/main/res/layout/fragment_thread_list.xml @@ -30,7 +30,7 @@ android:layout_width="0dp" android:layout_height="0dp" android:background="?android:colorBackground" - app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintBottom_toTopOf="@id/threadsFeedBackConstraintLayout" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/threadListAppBarLayout" @@ -75,13 +75,13 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginBottom="20dp" + android:gravity="center" + android:text="@string/thread_list_empty_title" android:textColor="?vctr_content_primary" app:layout_constraintBottom_toTopOf="@id/threadListEmptySubtitleTextView" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - android:gravity="center" - app:layout_constraintTop_toBottomOf="@id/threadListEmptyImageView" - android:text="@string/thread_list_empty_title" /> + app:layout_constraintTop_toBottomOf="@id/threadListEmptyImageView" /> + app:layout_constraintTop_toBottomOf="@id/threadListEmptyTitleTextView" /> + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 551e5961ec..d227326ceb 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -1648,6 +1648,9 @@ Thanks, your feedback has been successfully sent The feedback failed to be sent (%s) Give Feedback + Give Feedback on threads + Threads Beta feedback + Threads are a work in progress with new, exciting upcoming features, such as improved notifications. We’d love to hear your feedback! Show hidden events in timeline "Show complete history in encrypted rooms" From a2e2cdc2f38b099e23a0537448c74241337a089c Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Mon, 28 Mar 2022 13:54:31 +0300 Subject: [PATCH 097/565] Add feature specific configurations --- vector-config/src/main/res/values/config-features.xml | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100755 vector-config/src/main/res/values/config-features.xml diff --git a/vector-config/src/main/res/values/config-features.xml b/vector-config/src/main/res/values/config-features.xml new file mode 100755 index 0000000000..0a6025b37d --- /dev/null +++ b/vector-config/src/main/res/values/config-features.xml @@ -0,0 +1,11 @@ + + + + + + false + + From 9eccb9eaa084b1a59d4922277dcef50df3f96dfe Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Mon, 28 Mar 2022 13:59:59 +0300 Subject: [PATCH 098/565] Enable threads beta feedback --- vector-config/src/main/res/values/config-features.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector-config/src/main/res/values/config-features.xml b/vector-config/src/main/res/values/config-features.xml index 0a6025b37d..1a1a62446f 100755 --- a/vector-config/src/main/res/values/config-features.xml +++ b/vector-config/src/main/res/values/config-features.xml @@ -6,6 +6,6 @@ to the users. --> - false + true From b996e0eac0e3d136c44eab9464ef146b9f3a7cc7 Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Mon, 28 Mar 2022 14:09:45 +0300 Subject: [PATCH 099/565] Add changelog --- changelog.d/5647.feature | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/5647.feature diff --git a/changelog.d/5647.feature b/changelog.d/5647.feature new file mode 100644 index 0000000000..e4192300a1 --- /dev/null +++ b/changelog.d/5647.feature @@ -0,0 +1 @@ +Users will be able to provide feedback for threads \ No newline at end of file From 3ba2419e9b8eb7cfc809a6f2144d93cd0525b46e Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Mon, 28 Mar 2022 14:15:15 +0300 Subject: [PATCH 100/565] Replace hardcoded string --- vector/src/main/res/layout/fragment_thread_list.xml | 2 +- vector/src/main/res/values/strings.xml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/vector/src/main/res/layout/fragment_thread_list.xml b/vector/src/main/res/layout/fragment_thread_list.xml index 026432035f..c0041c0d75 100644 --- a/vector/src/main/res/layout/fragment_thread_list.xml +++ b/vector/src/main/res/layout/fragment_thread_list.xml @@ -144,7 +144,7 @@ android:paddingTop="3dp" android:paddingEnd="10dp" android:paddingBottom="3dp" - android:text="BETA" + android:text="@string/beta" android:textColor="@color/palette_white" app:layout_constraintBottom_toBottomOf="@id/threadsBetaFeedbackButton" app:layout_constraintEnd_toStartOf="@id/threadsBetaFeedbackButton" diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index d227326ceb..3b5f1e6aca 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -1651,6 +1651,7 @@ Give Feedback on threads Threads Beta feedback Threads are a work in progress with new, exciting upcoming features, such as improved notifications. We’d love to hear your feedback! + BETA Show hidden events in timeline "Show complete history in encrypted rooms" From 32cf3feab8d0303c8662c2fc28e85f670052e36f Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Mon, 28 Mar 2022 14:45:40 +0300 Subject: [PATCH 101/565] Create beacon content class. --- .../sdk/api/session/room/model/BeaconInfo.kt | 33 +++++++++++++++++ .../room/model/LiveLocationBeaconContent.kt | 37 +++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/BeaconInfo.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/LiveLocationBeaconContent.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/BeaconInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/BeaconInfo.kt new file mode 100644 index 0000000000..2f9eeacd65 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/BeaconInfo.kt @@ -0,0 +1,33 @@ +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * 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 org.matrix.android.sdk.api.session.room.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class BeaconInfo( + @Json(name = "description") val description: String? = null, + /** + * Beacon should be considered as inactive after this timeout as milliseconds. + */ + @Json(name = "timeout") val timeout: Long? = null, + /** + * Should be set true to start sharing beacon. + */ + @Json(name = "live") val isLive: Boolean? = null +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/LiveLocationBeaconContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/LiveLocationBeaconContent.kt new file mode 100644 index 0000000000..b21296b4aa --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/LiveLocationBeaconContent.kt @@ -0,0 +1,37 @@ +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * 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 org.matrix.android.sdk.api.session.room.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.api.session.room.model.message.LocationAsset + +@JsonClass(generateAdapter = true) +data class LiveLocationBeaconContent( + /** + * Indicates user's intent to share ephemeral location. + */ + @Json(name = "m.beacon_info") val beaconInfo: BeaconInfo? = null, + /** + * Beacon creation timestamp. + */ + @Json(name = "m.ts") val ts: Long? = null, + /** + * Live location asset type. + */ + @Json(name = "m.asset") val locationAsset: LocationAsset = LocationAsset(type = "m.self.live") +) From fa56a5efa976bdeb532bcee61f3a44dfc614b556 Mon Sep 17 00:00:00 2001 From: ericdecanini Date: Mon, 28 Mar 2022 13:52:47 +0200 Subject: [PATCH 102/565] Fixes playback button state not tracking after onPause --- .../app/features/home/room/detail/TimelineFragment.kt | 2 +- .../room/detail/composer/MessageComposerViewModel.kt | 1 - .../timeline/helper/AudioMessagePlaybackTracker.kt | 6 ++++++ .../home/room/detail/timeline/item/MessageAudioItem.kt | 10 +++++++++- 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt index 703696173e..a5c1b8a544 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt @@ -1265,7 +1265,7 @@ class TimelineFragment @Inject constructor( override fun onPause() { super.onPause() notificationDrawerManager.setCurrentRoom(null) - audioMessagePlaybackTracker.untrack(AudioMessagePlaybackTracker.RECORDING_ID) + audioMessagePlaybackTracker.pauseAllPlaybacks() if (withState(messageComposerViewModel) { it.isVoiceRecording } && requireActivity().isChangingConfigurations) { // we're rotating, maintain any active recordings diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt index 976489eec3..eb7d87c371 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt @@ -881,7 +881,6 @@ class MessageComposerViewModel @AssistedInject constructor( private fun handleEntersBackground(composerText: String) { // Always stop all voice actions. It may be playing in timeline or active recording val playingAudioContent = voiceMessageHelper.stopAllVoiceActions(deleteRecord = false) - voiceMessageHelper.clearTracker() val isVoiceRecording = com.airbnb.mvrx.withState(this) { it.isVoiceRecording } if (isVoiceRecording) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/AudioMessagePlaybackTracker.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/AudioMessagePlaybackTracker.kt index 7404735e28..546b76bebd 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/AudioMessagePlaybackTracker.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/AudioMessagePlaybackTracker.kt @@ -50,6 +50,12 @@ class AudioMessagePlaybackTracker @Inject constructor() { listeners.remove(id) } + fun pauseAllPlaybacks() { + listeners.keys.forEach { key -> + pausePlayback(key) + } + } + fun makeAllPlaybacksIdle() { listeners.keys.forEach { key -> setState(key, Listener.State.Idle) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageAudioItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageAudioItem.kt index bf248a27e8..243aa8c6a4 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageAudioItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageAudioItem.kt @@ -18,6 +18,7 @@ package im.vector.app.features.home.room.detail.timeline.item import android.content.res.ColorStateList import android.graphics.Color +import android.graphics.Paint import android.text.format.DateUtils import android.view.ViewGroup import android.widget.ImageButton @@ -27,6 +28,7 @@ import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass import im.vector.app.R import im.vector.app.core.epoxy.ClickListener +import im.vector.app.core.epoxy.onClick import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker import im.vector.app.features.home.room.detail.timeline.helper.ContentDownloadStateTrackerBinder import im.vector.app.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder @@ -64,8 +66,8 @@ abstract class MessageAudioItem : AbsMessageItem() { override fun bind(holder: Holder) { super.bind(holder) renderSendState(holder.rootLayout, null) + bindFilenameViewAttributes(holder) bindUploadState(holder) - holder.filenameView.text = filename applyLayoutTint(holder) holder.audioPlaybackControlButton.setOnClickListener { playbackControlButtonClickListener?.invoke(it) } renderStateBasedOnAudioPlayback(holder) @@ -91,6 +93,12 @@ abstract class MessageAudioItem : AbsMessageItem() { holder.mainLayout.backgroundTintList = ColorStateList.valueOf(backgroundTint) } + private fun bindFilenameViewAttributes(holder: Holder) { + holder.filenameView.text = filename + holder.filenameView.onClick(attributes.itemClickListener) + holder.filenameView.paintFlags = (holder.filenameView.paintFlags or Paint.UNDERLINE_TEXT_FLAG) + } + private fun renderStateBasedOnAudioPlayback(holder: Holder) { audioMessagePlaybackTracker.track(attributes.informationData.eventId, object : AudioMessagePlaybackTracker.Listener { override fun onUpdate(state: AudioMessagePlaybackTracker.Listener.State) { From 152c92101750d7e2fd5b8ec16d8b863d9dd9372c Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Mon, 28 Mar 2022 16:23:51 +0300 Subject: [PATCH 103/565] Send beacon info. --- .../sdk/api/session/events/model/EventType.kt | 1 + .../location/LocationSharingService.kt | 33 +++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt index 22fb9bcbe2..460b9ae1d2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt @@ -49,6 +49,7 @@ object EventType { const val STATE_ROOM_JOIN_RULES = "m.room.join_rules" const val STATE_ROOM_GUEST_ACCESS = "m.room.guest_access" const val STATE_ROOM_POWER_LEVELS = "m.room.power_levels" + const val STATE_ROOM_BEACON_INFO = "m.beacon_info" const val STATE_SPACE_CHILD = "m.space.child" diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingService.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingService.kt index a2a68e4188..347443dc4e 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingService.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingService.kt @@ -22,7 +22,14 @@ import android.os.Parcelable import dagger.hilt.android.AndroidEntryPoint import im.vector.app.core.services.VectorService import im.vector.app.features.notifications.NotificationUtils +import im.vector.app.features.session.coroutineScope +import kotlinx.coroutines.launch import kotlinx.parcelize.Parcelize +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.toContent +import org.matrix.android.sdk.api.session.room.model.BeaconInfo +import org.matrix.android.sdk.api.session.room.model.LiveLocationBeaconContent import timber.log.Timber import java.util.Timer import java.util.TimerTask @@ -40,6 +47,7 @@ class LocationSharingService : VectorService(), LocationTracker.Callback { @Inject lateinit var notificationUtils: NotificationUtils @Inject lateinit var locationTracker: LocationTracker + @Inject lateinit var session: Session private var roomArgsList = mutableListOf() private var timers = mutableListOf() @@ -67,11 +75,36 @@ class LocationSharingService : VectorService(), LocationTracker.Callback { // Schedule a timer to stop sharing scheduleTimer(roomArgs.roomId, roomArgs.durationMillis) + + // Send beacon info state event + session.coroutineScope.launch { + sendBeaconInfo(roomArgs) + } } return START_STICKY } + private suspend fun sendBeaconInfo(roomArgs: RoomArgs) { + val beaconContent = LiveLocationBeaconContent( + beaconInfo = BeaconInfo( + timeout = roomArgs.durationMillis, + isLive = true + ), + ts = System.currentTimeMillis() + ).toContent() + + // This format is not yet finalized + val stateKey = session.myUserId + session + .getRoom(roomArgs.roomId) + ?.sendStateEvent( + eventType = EventType.STATE_ROOM_BEACON_INFO, + stateKey = stateKey, + body = beaconContent + ) + } + private fun scheduleTimer(roomId: String, durationMillis: Long) { Timer() .apply { From 6952552e5ebc7e46a7c8a8870c562da33ce6a749 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Mon, 28 Mar 2022 16:29:24 +0300 Subject: [PATCH 104/565] Changelog added. --- changelog.d/5651.feature | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/5651.feature diff --git a/changelog.d/5651.feature b/changelog.d/5651.feature new file mode 100644 index 0000000000..c633c01944 --- /dev/null +++ b/changelog.d/5651.feature @@ -0,0 +1 @@ +Send beacon info state event when live location sharing started \ No newline at end of file From 5499854ec0ff883ed3ee8594e32350d76c83c6ea Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 22 Mar 2022 14:05:33 +0100 Subject: [PATCH 105/565] Set up org.owasp.dependencycheck See https://github.com/jeremylong/DependencyCheck --- build.gradle | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 31416a0440..fee8af8822 100644 --- a/build.gradle +++ b/build.gradle @@ -21,7 +21,7 @@ buildscript { classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.3' classpath 'com.google.android.gms:oss-licenses-plugin:0.10.5' classpath "com.likethesalad.android:stem-plugin:2.0.0" - + classpath 'org.owasp:dependency-check-gradle:7.0.0' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } @@ -32,6 +32,9 @@ plugins { id "org.jlleitschuh.gradle.ktlint" version "10.2.1" } +// https://github.com/jeremylong/DependencyCheck +apply plugin: 'org.owasp.dependencycheck' + allprojects { apply plugin: "org.jlleitschuh.gradle.ktlint" From 2b3951fe0483f1ad43e6357e6ca039129fe7f994 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 28 Mar 2022 16:17:27 +0200 Subject: [PATCH 106/565] Force ktlint to version 0.45.1. The ktlint plugin is using 0.42.1 --- build.gradle | 2 ++ dependencies_groups.gradle | 2 ++ 2 files changed, 4 insertions(+) diff --git a/build.gradle b/build.gradle index fee8af8822..b9792511d6 100644 --- a/build.gradle +++ b/build.gradle @@ -90,6 +90,8 @@ allprojects { // See https://github.com/JLLeitschuh/ktlint-gradle#configuration ktlint { + // See https://github.com/pinterest/ktlint/releases/ + version = "0.45.1" android = true ignoreFailures = false enableExperimentalRules = true diff --git a/dependencies_groups.gradle b/dependencies_groups.gradle index 45883f506d..6f155a0149 100644 --- a/dependencies_groups.gradle +++ b/dependencies_groups.gradle @@ -39,6 +39,7 @@ ext.groups = [ regex: [ ], group: [ + 'ch.qos.logback', 'com.adevinta.android', 'com.airbnb.android', 'com.almworks.sqlite4java', @@ -113,6 +114,7 @@ ext.groups = [ 'info.picocli', 'io.arrow-kt', 'io.github.detekt.sarif4k', + 'io.github.microutils', 'io.github.reactivecircus.flowbinding', 'io.grpc', 'io.jsonwebtoken', From 4c406158716173308c4a8b662515ec6bef51b35e Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 28 Mar 2022 16:31:30 +0200 Subject: [PATCH 107/565] Remove Flair Fragment (not used, and part of group, which will be removed) --- .../features/settings/VectorPreferences.kt | 3 - .../settings/VectorSettingsFlairFragment.kt | 170 ------------------ vector/src/main/res/values/strings.xml | 3 +- .../main/res/xml/vector_settings_flair.xml | 8 - 4 files changed, 2 insertions(+), 182 deletions(-) delete mode 100644 vector/src/main/java/im/vector/app/features/settings/VectorSettingsFlairFragment.kt delete mode 100644 vector/src/main/res/xml/vector_settings_flair.xml diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt index 8d93edc0ec..8fce936428 100755 --- a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt @@ -111,9 +111,6 @@ class VectorPreferences @Inject constructor(private val context: Context) { private const val SETTINGS_PIN_UNREAD_MESSAGES_PREFERENCE_KEY = "SETTINGS_PIN_UNREAD_MESSAGES_PREFERENCE_KEY" private const val SETTINGS_PIN_MISSED_NOTIFICATIONS_PREFERENCE_KEY = "SETTINGS_PIN_MISSED_NOTIFICATIONS_PREFERENCE_KEY" - // flair - const val SETTINGS_GROUPS_FLAIR_KEY = "SETTINGS_GROUPS_FLAIR_KEY" - // notifications const val SETTINGS_ENABLE_ALL_NOTIF_PREFERENCE_KEY = "SETTINGS_ENABLE_ALL_NOTIF_PREFERENCE_KEY" const val SETTINGS_ENABLE_THIS_DEVICE_PREFERENCE_KEY = "SETTINGS_ENABLE_THIS_DEVICE_PREFERENCE_KEY" diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsFlairFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsFlairFragment.kt deleted file mode 100644 index ec65e7d004..0000000000 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsFlairFragment.kt +++ /dev/null @@ -1,170 +0,0 @@ -/* - * 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.app.features.settings - -import androidx.preference.PreferenceCategory -import im.vector.app.R -import im.vector.app.core.preference.ProgressBarPreference - -class VectorSettingsFlairFragment : VectorSettingsBaseFragment() { - - override var titleRes = R.string.settings_flair - override val preferenceXmlRes = R.xml.vector_settings_flair - - // current publicised group list - private var mPublicisedGroups: MutableSet? = null - - // Group Flairs - private val mGroupsFlairCategory by lazy { - findPreference(VectorPreferences.SETTINGS_GROUPS_FLAIR_KEY)!! - } - - override fun bindPref() { - // Flair - refreshGroupFlairsList() - } - - // ============================================================================================================== - // Group flairs management - // ============================================================================================================== - - /** - * Force the refresh of the devices list.

- * The devices list is the list of the devices where the user as looged in. - * It can be any mobile device, as any browser. - */ - private fun refreshGroupFlairsList() { - // display a spinner while refreshing - if (0 == mGroupsFlairCategory.preferenceCount) { - activity?.let { - val preference = ProgressBarPreference(it) - mGroupsFlairCategory.addPreference(preference) - } - } - - /* - TODO - session.groupsManager.getUserPublicisedGroups(session.myUserId, true, object : MatrixCallback> { - override fun onSuccess(publicisedGroups: Set) { - // clear everything - mGroupsFlairCategory.removeAll() - - if (publicisedGroups.isEmpty()) { - val vectorGroupPreference = Preference(activity) - vectorGroupPreference.title = resources.getString(R.string.settings_without_flair) - mGroupsFlairCategory.addPreference(vectorGroupPreference) - } else { - buildGroupsList(publicisedGroups) - } - } - - override fun onNetworkError(e: Exception) { - // NOP - } - - override fun onMatrixError(e: MatrixError) { - // NOP - } - - override fun onUnexpectedError(e: Exception) { - // NOP - } - }) - */ - } - - /** - * Build the groups list. - * - * @param publicisedGroups the publicised groups list. - */ - private fun buildGroupsList(publicisedGroups: Set) { - var isNewList = true - - mPublicisedGroups?.let { - if (it.size == publicisedGroups.size) { - isNewList = !it.containsAll(publicisedGroups) - } - } - - if (isNewList) { - /* - TODO - val joinedGroups = ArrayList(session.groupsManager.joinedGroups) - Collections.sort(joinedGroups, Group.mGroupsComparator) - - mPublicisedGroups = publicisedGroups.toMutableSet() - - for ((prefIndex, group) in joinedGroups.withIndex()) { - val vectorGroupPreference = VectorGroupPreference(activity!!) - vectorGroupPreference.key = DEVICES_PREFERENCE_KEY_BASE + prefIndex - - vectorGroupPreference.setGroup(group, session) - vectorGroupPreference.title = group.displayName - vectorGroupPreference.summary = group.groupId - - vectorGroupPreference.isChecked = publicisedGroups.contains(group.groupId) - mGroupsFlairCategory.addPreference(vectorGroupPreference) - - vectorGroupPreference.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue -> - if (newValue is Boolean) { - /* - * if mPublicisedGroup is null somehow, then - * we cant check it contains groupId or not - * so set isFlaired to false - */ - val isFlaired = mPublicisedGroups?.contains(group.groupId) ?: false - - if (newValue != isFlaired) { - displayLoadingView() - session.groupsManager.updateGroupPublicity(group.groupId, newValue, object : MatrixCallback { - override fun onSuccess(info: Void?) { - hideLoadingView() - if (newValue) { - mPublicisedGroups?.add(group.groupId) - } else { - mPublicisedGroups?.remove(group.groupId) - } - } - - private fun onError() { - hideLoadingView() - // restore default value - vectorGroupPreference.isChecked = publicisedGroups.contains(group.groupId) - } - - override fun onNetworkError(e: Exception) { - onError() - } - - override fun onMatrixError(e: MatrixError) { - onError() - } - - override fun onUnexpectedError(e: Exception) { - onError() - } - }) - } - } - true - } - } - */ - } - } -} diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index a276e07b1e..e444a3b0bb 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -1041,7 +1041,8 @@ Choose Play shutter sound - Flair + + Flair 3 days diff --git a/vector/src/main/res/xml/vector_settings_flair.xml b/vector/src/main/res/xml/vector_settings_flair.xml deleted file mode 100644 index ac7ae83d24..0000000000 --- a/vector/src/main/res/xml/vector_settings_flair.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - \ No newline at end of file From ebee66cfafd6e9a0b31b5e992d22011f30271309 Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 28 Mar 2022 16:37:24 +0200 Subject: [PATCH 108/565] Update versions to 1.4.10 --- matrix-sdk-android/build.gradle | 2 +- vector/build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index 1e2eda166f..25cfaeb45e 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -31,7 +31,7 @@ android { // that the app's state is completely cleared between tests. testInstrumentationRunnerArguments clearPackageData: 'true' - buildConfigField "String", "SDK_VERSION", "\"1.4.8\"" + buildConfigField "String", "SDK_VERSION", "\"1.4.10\"" buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\"" buildConfigField "String", "GIT_SDK_REVISION_UNIX_DATE", "\"${gitRevisionUnixDate()}\"" diff --git a/vector/build.gradle b/vector/build.gradle index 9f8471bc18..f5984ff0c6 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -18,7 +18,7 @@ ext.versionMinor = 4 // Note: even values are reserved for regular release, odd values for hotfix release. // When creating a hotfix, you should decrease the value, since the current value // is the value for the next regular release. -ext.versionPatch = 8 +ext.versionPatch = 10 static def getGitTimestamp() { def cmd = 'git show -s --format=%ct' From ff1fb63bf69d0dfb27a45bad54ce49774bdf745d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 28 Mar 2022 16:35:03 +0200 Subject: [PATCH 109/565] Fix some ktlint issues, ignore some others --- build.gradle | 11 +++++++++- .../crypto/gossiping/WithHeldTests.kt | 2 +- .../sdk/internal/crypto/CryptoModule.kt | 3 +-- .../crypto/model/MXUsersDevicesMap.kt | 2 +- .../sdk/internal/crypto/tools/HkdfSha256.kt | 2 +- .../DefaultVerificationService.kt | 16 +++++++-------- .../session/cache/DefaultCacheService.kt | 6 +++--- .../user/accountdata/DirectChatsHelper.kt | 5 +++-- .../widgets/DefaultWidgetPostAPIMediator.kt | 8 ++++---- .../app/features/debug/TestLinkifyActivity.kt | 4 ++-- .../vector/app/features/badge/BadgeProxy.kt | 4 ++-- .../createdirect/CreateDirectRoomViewModel.kt | 9 +++++---- .../restore/KeysBackupRestoreActivity.kt | 2 +- .../home/room/detail/TimelineFragment.kt | 2 +- .../action/MessageActionsViewModel.kt | 20 +++++++++---------- .../reactions/ViewReactionsViewModel.kt | 8 ++++---- .../invite/InviteUsersToRoomViewModel.kt | 11 +++++----- .../app/features/login/LoginViewModel.kt | 2 +- .../app/features/login2/LoginViewModel2.kt | 2 +- .../NotificationBroadcastReceiver.kt | 2 +- .../RoomPreviewNoPreviewFragment.kt | 16 +++++---------- .../settings/VectorSettingsGeneralFragment.kt | 2 +- .../VectorSettingsPreferencesFragment.kt | 2 +- 23 files changed, 72 insertions(+), 69 deletions(-) diff --git a/build.gradle b/build.gradle index b9792511d6..8ac546ac54 100644 --- a/build.gradle +++ b/build.gradle @@ -101,7 +101,16 @@ allprojects { "spacing-between-declarations-with-comments", "no-multi-spaces", "experimental:spacing-between-declarations-with-annotations", - "experimental:annotation" + "experimental:annotation", + // - Missing newline after "(" + // - Missing newline before ")" + "wrapping", + // - Unnecessary trailing comma before ")" + "experimental:trailing-comma", + // - A block comment in between other elements on the same line is disallowed + "experimental:comment-wrapping", + // - A KDoc comment after any other element on the same line must be separated by a new line + "experimental:kdoc-wrapping", ] } } diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt index 65c65660b5..e8f6eea460 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt @@ -138,7 +138,7 @@ class WithHeldTests : InstrumentedTest { @Test @Ignore("This test will be ignored until it is fixed") - fun test_WithHeldNoOlm() { + fun test_WithHeldNoOlm() { val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val aliceSession = testData.firstSession val bobSession = testData.secondSession!! diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt index 3130a6382f..2265526484 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt @@ -137,8 +137,7 @@ internal abstract class CryptoModule { @JvmStatic @Provides @CryptoDatabase - fun providesClearCacheTask(@CryptoDatabase - realmConfiguration: RealmConfiguration): ClearCacheTask { + fun providesClearCacheTask(@CryptoDatabase realmConfiguration: RealmConfiguration): ClearCacheTask { return RealmClearCacheTask(realmConfiguration) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXUsersDevicesMap.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXUsersDevicesMap.kt index 662541428e..bdb00dce8e 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXUsersDevicesMap.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXUsersDevicesMap.kt @@ -130,7 +130,7 @@ inline fun MXUsersDevicesMap.forEach(action: (String, String, T) -> Unit) } } -internal fun MXUsersDevicesMap.toDebugString() = +internal fun MXUsersDevicesMap.toDebugString() = map.entries.joinToString { "${it.key} [${it.value.keys.joinToString { it }}]" } internal fun MXUsersDevicesMap.toDebugCount() = diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tools/HkdfSha256.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tools/HkdfSha256.kt index 6839ccd326..04ce0d8500 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tools/HkdfSha256.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tools/HkdfSha256.kt @@ -70,7 +70,7 @@ object HkdfSha256 { T(2) = HMAC-Hash(PRK, T(1) | info | 0x02) T(3) = HMAC-Hash(PRK, T(2) | info | 0x03) ... - */ + */ val n = ceil(outputLength.toDouble() / HASH_LEN.toDouble()).toInt() var stepHash = ByteArray(0) // T(0) empty string (zero length) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationService.kt index 388ecb9659..bd623575fa 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationService.kt @@ -364,14 +364,14 @@ internal class DefaultVerificationService @Inject constructor( dispatchRequestAdded(pendingVerificationRequest) /* - * After the m.key.verification.ready event is sent, either party can send an m.key.verification.start event - * to begin the verification. - * If both parties send an m.key.verification.start event, and they both specify the same verification method, - * then the event sent by the user whose user ID is the smallest is used, and the other m.key.verification.start - * event is ignored. - * In the case of a single user verifying two of their devices, the device ID is compared instead. - * If both parties send an m.key.verification.start event, but they specify different verification methods, - * the verification should be cancelled with a code of m.unexpected_message. + * After the m.key.verification.ready event is sent, either party can send an m.key.verification.start event + * to begin the verification. + * If both parties send an m.key.verification.start event, and they both specify the same verification method, + * then the event sent by the user whose user ID is the smallest is used, and the other m.key.verification.start + * event is ignored. + * In the case of a single user verifying two of their devices, the device ID is compared instead. + * If both parties send an m.key.verification.start event, but they specify different verification methods, + * the verification should be cancelled with a code of m.unexpected_message. */ } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/cache/DefaultCacheService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/cache/DefaultCacheService.kt index 6d0cd37e1f..93b0dba13e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/cache/DefaultCacheService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/cache/DefaultCacheService.kt @@ -21,9 +21,9 @@ import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.task.TaskExecutor import javax.inject.Inject -internal class DefaultCacheService @Inject constructor(@SessionDatabase - private val clearCacheTask: ClearCacheTask, - private val taskExecutor: TaskExecutor +internal class DefaultCacheService @Inject constructor( + @SessionDatabase private val clearCacheTask: ClearCacheTask, + private val taskExecutor: TaskExecutor ) : CacheService { override suspend fun clearCache() { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DirectChatsHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DirectChatsHelper.kt index c7b125b5d6..c4fbdc75ab 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DirectChatsHelper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DirectChatsHelper.kt @@ -24,8 +24,9 @@ import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.session.sync.model.accountdata.DirectMessagesContent import javax.inject.Inject -internal class DirectChatsHelper @Inject constructor(@SessionDatabase - private val realmConfiguration: RealmConfiguration) { +internal class DirectChatsHelper @Inject constructor( + @SessionDatabase private val realmConfiguration: RealmConfiguration +) { /** * @return a map of userId <-> list of roomId diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetPostAPIMediator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetPostAPIMediator.kt index 07f7c7cb86..1fa5e5f771 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetPostAPIMediator.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetPostAPIMediator.kt @@ -88,10 +88,10 @@ internal class DefaultWidgetPostAPIMediator @Inject constructor(private val mosh } /* - * ********************************************************************************************* - * Message sending methods - * ********************************************************************************************* - */ + * ********************************************************************************************* + * Message sending methods + * ********************************************************************************************* + */ /** * Send a boolean response diff --git a/vector/src/debug/java/im/vector/app/features/debug/TestLinkifyActivity.kt b/vector/src/debug/java/im/vector/app/features/debug/TestLinkifyActivity.kt index 88e55d6760..59c60e0e15 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/TestLinkifyActivity.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/TestLinkifyActivity.kt @@ -93,7 +93,7 @@ class TestLinkifyActivity : AppCompatActivity() { .show() } }) - */ + */ } subViews.testLinkifyCustomText.apply { @@ -108,7 +108,7 @@ class TestLinkifyActivity : AppCompatActivity() { .show() } }) - */ + */ // TODO Call VectorLinkify.addLinks(text) } diff --git a/vector/src/main/java/im/vector/app/features/badge/BadgeProxy.kt b/vector/src/main/java/im/vector/app/features/badge/BadgeProxy.kt index 3df33b0c9b..fb597d1ef9 100644 --- a/vector/src/main/java/im/vector/app/features/badge/BadgeProxy.kt +++ b/vector/src/main/java/im/vector/app/features/badge/BadgeProxy.kt @@ -90,7 +90,7 @@ object BadgeProxy { } } } - */ + */ } /** @@ -124,6 +124,6 @@ object BadgeProxy { Timber.v("## updateBadgeCount(): badge update count=$unreadRoomsCount") updateBadgeCount(aContext, unreadRoomsCount) } - */ + */ } } diff --git a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewModel.kt b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewModel.kt index d3011496d2..9ce8e68dab 100644 --- a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewModel.kt @@ -38,10 +38,11 @@ import org.matrix.android.sdk.api.session.permalinks.PermalinkParser import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams import org.matrix.android.sdk.api.session.user.model.User -class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted - initialState: CreateDirectRoomViewState, - private val rawService: RawService, - val session: Session) : +class CreateDirectRoomViewModel @AssistedInject constructor( + @Assisted initialState: CreateDirectRoomViewState, + private val rawService: RawService, + val session: Session +) : VectorViewModel(initialState) { @AssistedFactory diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreActivity.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreActivity.kt index 6e5d7f5fab..a4f6587be4 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreActivity.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreActivity.kt @@ -52,7 +52,7 @@ class KeysBackupRestoreActivity : SimpleFragmentActivity() { super.onBackPressed() } - @Inject lateinit var activeSessionHolder: ActiveSessionHolder + @Inject lateinit var activeSessionHolder: ActiveSessionHolder override fun initUiAndData() { super.initUiAndData() diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt index 9c754e042c..81b037e016 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt @@ -1669,7 +1669,7 @@ class TimelineFragment @Inject constructor( is MessageComposerViewEvents.SlashCommandNotSupportedInThreads -> { displayCommandError(getString(R.string.command_not_supported_in_threads, sendMessageResult.command.command)) } - } // + } lockSendButton = false } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt index bd4e93b25d..aaaecb0a13 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt @@ -69,16 +69,16 @@ import org.matrix.android.sdk.flow.unwrap /** * Information related to an event and used to display preview in contextual bottom sheet. */ -class MessageActionsViewModel @AssistedInject constructor(@Assisted - private val initialState: MessageActionState, - private val eventHtmlRenderer: Lazy, - private val htmlCompressor: VectorHtmlCompressor, - private val session: Session, - private val noticeEventFormatter: NoticeEventFormatter, - private val errorFormatter: ErrorFormatter, - private val stringProvider: StringProvider, - private val pillsPostProcessorFactory: PillsPostProcessor.Factory, - private val vectorPreferences: VectorPreferences +class MessageActionsViewModel @AssistedInject constructor( + @Assisted private val initialState: MessageActionState, + private val eventHtmlRenderer: Lazy, + private val htmlCompressor: VectorHtmlCompressor, + private val session: Session, + private val noticeEventFormatter: NoticeEventFormatter, + private val errorFormatter: ErrorFormatter, + private val stringProvider: StringProvider, + private val pillsPostProcessorFactory: PillsPostProcessor.Factory, + private val vectorPreferences: VectorPreferences ) : VectorViewModel(initialState) { private val informationData = initialState.informationData diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reactions/ViewReactionsViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reactions/ViewReactionsViewModel.kt index 25d6f907b5..29b8c207df 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reactions/ViewReactionsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reactions/ViewReactionsViewModel.kt @@ -56,10 +56,10 @@ data class ReactionInfo( /** * Used to display the list of members that reacted to a given event */ -class ViewReactionsViewModel @AssistedInject constructor(@Assisted - initialState: DisplayReactionsViewState, - session: Session, - private val dateFormatter: VectorDateFormatter +class ViewReactionsViewModel @AssistedInject constructor( + @Assisted initialState: DisplayReactionsViewState, + session: Session, + private val dateFormatter: VectorDateFormatter ) : VectorViewModel(initialState) { private val roomId = initialState.roomId diff --git a/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomViewModel.kt b/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomViewModel.kt index 891194040e..42f08c334e 100644 --- a/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomViewModel.kt @@ -28,16 +28,15 @@ import im.vector.app.core.resources.StringProvider import im.vector.app.features.userdirectory.PendingSelection import kotlinx.coroutines.flow.asFlow import kotlinx.coroutines.flow.catch -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session -class InviteUsersToRoomViewModel @AssistedInject constructor(@Assisted - initialState: InviteUsersToRoomViewState, - session: Session, - val stringProvider: StringProvider) : - VectorViewModel(initialState) { +class InviteUsersToRoomViewModel @AssistedInject constructor( + @Assisted initialState: InviteUsersToRoomViewState, + session: Session, + val stringProvider: StringProvider +) : VectorViewModel(initialState) { private val room = session.getRoom(initialState.roomId)!! diff --git a/vector/src/main/java/im/vector/app/features/login/LoginViewModel.kt b/vector/src/main/java/im/vector/app/features/login/LoginViewModel.kt index 246c3ad464..73f5c064e7 100644 --- a/vector/src/main/java/im/vector/app/features/login/LoginViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/login/LoginViewModel.kt @@ -275,7 +275,7 @@ class LoginViewModel @AssistedInject constructor( code = MatrixError.FORBIDDEN, message = "Registration is disabled" ), 403)) - */ + */ } catch (failure: Throwable) { if (failure !is CancellationException) { _viewEvents.post(LoginViewEvents.Failure(failure)) diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginViewModel2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginViewModel2.kt index 8125c6e089..62f0007104 100644 --- a/vector/src/main/java/im/vector/app/features/login2/LoginViewModel2.kt +++ b/vector/src/main/java/im/vector/app/features/login2/LoginViewModel2.kt @@ -771,7 +771,7 @@ class LoginViewModel2 @AssistedInject constructor( ), httpCode = 403 ) - */ + */ LoginViewEvents2.OpenSignUpChooseUsernameScreen } catch (throwable: Throwable) { diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationBroadcastReceiver.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationBroadcastReceiver.kt index 01c1117ab2..505f4cc4a0 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationBroadcastReceiver.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationBroadcastReceiver.kt @@ -202,7 +202,7 @@ class NotificationBroadcastReceiver : BroadcastReceiver() { VectorApp.getInstance().notificationDrawerManager.refreshNotificationDrawer(null) } }) - */ + */ } private fun getReplyMessage(intent: Intent?): String? { diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewNoPreviewFragment.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewNoPreviewFragment.kt index 6d0195fae3..90f1a4785d 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewNoPreviewFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewNoPreviewFragment.kt @@ -110,8 +110,7 @@ class RoomPreviewNoPreviewFragment @Inject constructor( PeekingState.FOUND -> { // show join buttons views.roomPreviewNoPreviewJoin.isVisible = true - renderState(bestName, state.matrixItem(), state.roomTopic - /**, state.roomType*/) + renderState(bestName, state.matrixItem(), state.roomTopic) if (state.fromEmailInvite != null && !state.isEmailBoundToAccount) { views.roomPreviewNoPreviewLabel.text = span { @@ -152,15 +151,13 @@ class RoomPreviewNoPreviewFragment @Inject constructor( views.roomPreviewNoPreviewJoin.isVisible = true views.roomPreviewNoPreviewLabel.isVisible = true views.roomPreviewNoPreviewLabel.setText(R.string.room_preview_no_preview_join) - renderState(bestName, state.matrixItem().takeIf { state.roomAlias != null }, state.roomTopic - /**, state.roomType*/) + renderState(bestName, state.matrixItem().takeIf { state.roomAlias != null }, state.roomTopic) } else -> { views.roomPreviewNoPreviewJoin.isVisible = false views.roomPreviewNoPreviewLabel.isVisible = true views.roomPreviewNoPreviewLabel.setText(R.string.room_preview_not_found) - renderState(bestName, null, state.roomTopic - /**, state.roomType*/) + renderState(bestName, null, state.roomTopic) } } } @@ -168,16 +165,13 @@ class RoomPreviewNoPreviewFragment @Inject constructor( // Render with initial state, no peeking views.roomPreviewPeekingProgress.isVisible = false views.roomPreviewNoPreviewJoin.isVisible = true - renderState(bestName, state.matrixItem(), state.roomTopic - /**, state.roomType*/) + renderState(bestName, state.matrixItem(), state.roomTopic) views.roomPreviewNoPreviewLabel.isVisible = false } } } - private fun renderState(roomName: String, matrixItem: MatrixItem?, topic: String? - /**, roomType: String?*/ - ) { + private fun renderState(roomName: String, matrixItem: MatrixItem?, topic: String?) { // Toolbar if (matrixItem != null) { views.roomPreviewNoPreviewToolbarAvatar.isVisible = true diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt index ffb9fc4af4..db6b4002a0 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt @@ -359,7 +359,7 @@ class VectorSettingsGeneralFragment @Inject constructor( startActivityForResult(intent, REQUEST_PHONEBOOK_COUNTRY) true } - */ + */ } // ============================================================================================================== diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPreferencesFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPreferencesFragment.kt index 50e32ae453..fa020c8d26 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPreferencesFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPreferencesFragment.kt @@ -117,7 +117,7 @@ class VectorSettingsPreferencesFragment @Inject constructor( false } } - */ + */ // update keep medias period findPreference(VectorPreferences.SETTINGS_MEDIA_SAVING_PERIOD_KEY)!!.let { From b4885629af493cd5fbe726d0e81dc01cd7d7cb82 Mon Sep 17 00:00:00 2001 From: David Langley Date: Mon, 28 Mar 2022 17:23:05 +0100 Subject: [PATCH 110/565] Keep live event/pagination listeners. --- changelog.d/5639.sdk | 1 + .../sdk/api/session/LiveEventListener.kt | 7 ++++- .../algorithms/megolm/MXMegolmDecryption.kt | 2 +- .../internal/session/StreamEventsManager.kt | 29 +++++++++++++++++-- .../room/timeline/TokenChunkEventPersistor.kt | 2 +- .../sync/handler/room/RoomSyncHandler.kt | 1 + .../main/java/im/vector/app/UISIDetector.kt | 7 ++++- 7 files changed, 43 insertions(+), 6 deletions(-) create mode 100644 changelog.d/5639.sdk diff --git a/changelog.d/5639.sdk b/changelog.d/5639.sdk new file mode 100644 index 0000000000..96b495f809 --- /dev/null +++ b/changelog.d/5639.sdk @@ -0,0 +1 @@ +Include original event in decryption live decryption listeners. \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/LiveEventListener.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/LiveEventListener.kt index def3ecbfca..b4b283c86a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/LiveEventListener.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/LiveEventListener.kt @@ -17,10 +17,15 @@ package org.matrix.android.sdk.api.session import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.util.JsonDict interface LiveEventListener { - fun onEventDecrypted(event: Event) + fun onLiveEvent(roomId: String, event: Event) + + fun onPaginatedEvent(roomId: String, event: Event) + + fun onEventDecrypted(event: Event, clearEvent: JsonDict) fun onEventDecryptionError(event: Event, throwable: Throwable) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt index 22180dce86..e94daa0e76 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt @@ -113,7 +113,7 @@ internal class MXMegolmDecryption(private val userId: String, forwardingCurve25519KeyChain = olmDecryptionResult.forwardingCurve25519KeyChain .orEmpty() ).also { - liveEventManager.get().dispatchLiveEventDecrypted(event) + liveEventManager.get().dispatchLiveEventDecrypted(event, it) } } else { throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/StreamEventsManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/StreamEventsManager.kt index 7c1ca1f630..8ae9772714 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/StreamEventsManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/StreamEventsManager.kt @@ -23,6 +23,7 @@ import kotlinx.coroutines.launch import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.LiveEventListener import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.internal.crypto.MXEventDecryptionResult import timber.log.Timber import javax.inject.Inject @@ -41,12 +42,36 @@ internal class StreamEventsManager @Inject constructor() { listeners.remove(listener) } - fun dispatchLiveEventDecrypted(event: Event) { + fun dispatchLiveEventReceived(event: Event, roomId: String, initialSync: Boolean) { + Timber.v("## dispatchLiveEventReceived ${event.eventId}") + coroutineScope.launch { + if (!initialSync) { + listeners.forEach { + tryOrNull { + it.onLiveEvent(roomId, event) + } + } + } + } + } + + fun dispatchPaginatedEventReceived(event: Event, roomId: String) { + Timber.v("## dispatchPaginatedEventReceived ${event.eventId}") + coroutineScope.launch { + listeners.forEach { + tryOrNull { + it.onPaginatedEvent(roomId, event) + } + } + } + } + + fun dispatchLiveEventDecrypted(event: Event, result: MXEventDecryptionResult) { Timber.v("## dispatchLiveEventDecrypted ${event.eventId}") coroutineScope.launch { listeners.forEach { tryOrNull { - it.onEventDecrypted(event) + it.onEventDecrypted(event, result.clearEvent) } } } 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 4e940bb445..63383a99b3 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 @@ -186,7 +186,7 @@ internal class TokenChunkEventPersistor @Inject constructor( } roomMemberContentsByUser[event.stateKey] = contentToUse.toModel() } - + liveEventManager.get().dispatchPaginatedEventReceived(event, roomId) currentChunk.addTimelineEvent( roomId = roomId, eventEntity = eventEntity, 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 1cb476d03a..8fe85f0d31 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 @@ -382,6 +382,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle } eventIds.add(event.eventId) + liveEventService.get().dispatchLiveEventReceived(event, roomId, insertType == EventInsertType.INITIAL_SYNC) val isInitialSync = insertType == EventInsertType.INITIAL_SYNC diff --git a/vector/src/main/java/im/vector/app/UISIDetector.kt b/vector/src/main/java/im/vector/app/UISIDetector.kt index 2b6974eefb..bb76a96b92 100644 --- a/vector/src/main/java/im/vector/app/UISIDetector.kt +++ b/vector/src/main/java/im/vector/app/UISIDetector.kt @@ -20,6 +20,7 @@ import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.LiveEventListener import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent import timber.log.Timber import java.util.Timer @@ -68,7 +69,7 @@ class UISIDetector : LiveEventListener { private val timeoutMillis = 30_000L private val enabled: Boolean get() = callback?.enabled.orFalse() - override fun onEventDecrypted(event: Event) { + override fun onEventDecrypted(event: Event, clearEvent: JsonDict) { val eventId = event.eventId val roomId = event.roomId if (!enabled || eventId == null || roomId == null) return @@ -108,6 +109,10 @@ class UISIDetector : LiveEventListener { timer.schedule(timeoutTask, timeoutMillis) } + override fun onLiveEvent(roomId: String, event: Event) { } + + override fun onPaginatedEvent(roomId: String, event: Event) { } + private fun trackerId(eventId: String, roomId: String): String = "$roomId-$eventId" private fun triggerUISI(source: E2EMessageDetected) { From 3ae4303ecd4d28b8194bfe16025866957ee5ecb6 Mon Sep 17 00:00:00 2001 From: David Langley Date: Mon, 28 Mar 2022 17:28:33 +0100 Subject: [PATCH 111/565] Fix changelog wording --- changelog.d/5639.sdk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.d/5639.sdk b/changelog.d/5639.sdk index 96b495f809..1ff8b4c053 100644 --- a/changelog.d/5639.sdk +++ b/changelog.d/5639.sdk @@ -1 +1 @@ -Include original event in decryption live decryption listeners. \ No newline at end of file +Include original event in live decryption listeners. \ No newline at end of file From a7cd03d578087159758787a55f2f63df24801c6f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Mar 2022 23:04:32 +0000 Subject: [PATCH 112/565] Bump peter-evans/create-pull-request from 3 to 4 Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 3 to 4. - [Release notes](https://github.com/peter-evans/create-pull-request/releases) - [Commits](https://github.com/peter-evans/create-pull-request/compare/v3...v4) --- updated-dependencies: - dependency-name: peter-evans/create-pull-request dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/sync-from-external-sources.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/sync-from-external-sources.yml b/.github/workflows/sync-from-external-sources.yml index d390c47696..796d915ea6 100644 --- a/.github/workflows/sync-from-external-sources.yml +++ b/.github/workflows/sync-from-external-sources.yml @@ -23,7 +23,7 @@ jobs: - name: Run Emoji script run: ./tools/import_emojis.py - name: Create Pull Request for Emojis - uses: peter-evans/create-pull-request@v3 + uses: peter-evans/create-pull-request@v4 with: commit-message: Sync Emojis title: Sync Emojis @@ -49,7 +49,7 @@ jobs: - name: Run SAS String script run: ./tools/import_sas_strings.py - name: Create Pull Request for SAS Strings - uses: peter-evans/create-pull-request@v3 + uses: peter-evans/create-pull-request@v4 with: commit-message: Sync SAS Strings title: Sync SAS Strings @@ -68,7 +68,7 @@ jobs: - name: Run analytics import script run: ./tools/import_analytic_plan.sh - name: Create Pull Request for analytics plan - uses: peter-evans/create-pull-request@v3 + uses: peter-evans/create-pull-request@v4 with: commit-message: Sync analytics plan title: Sync analytics plan From 01bb49d9632ab1ad21c335288f979140f81dc123 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Mar 2022 23:04:35 +0000 Subject: [PATCH 113/565] Bump peter-evans/find-comment from 1 to 2 Bumps [peter-evans/find-comment](https://github.com/peter-evans/find-comment) from 1 to 2. - [Release notes](https://github.com/peter-evans/find-comment/releases) - [Commits](https://github.com/peter-evans/find-comment/compare/v1...v2) --- updated-dependencies: - dependency-name: peter-evans/find-comment dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/nightly.yml | 2 +- .github/workflows/quality.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 7f789b4610..f035244810 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -174,7 +174,7 @@ jobs: # package: class PermalinkParserTest - name: Find Comment if: always() && github.event_name == 'pull_request' - uses: peter-evans/find-comment@v1 + uses: peter-evans/find-comment@v2 id: fc with: issue-number: ${{ github.event.pull_request.number }} diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index d427d65b7f..b08d92ae56 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -59,7 +59,7 @@ jobs: fi - name: Find Comment if: always() && github.event_name == 'pull_request' - uses: peter-evans/find-comment@v1 + uses: peter-evans/find-comment@v2 id: fc with: issue-number: ${{ github.event.pull_request.number }} From 6269a3357b7e36702650dbe470d38ae36eaa36cc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Mar 2022 23:04:38 +0000 Subject: [PATCH 114/565] Bump peter-evans/create-or-update-comment from 1 to 2 Bumps [peter-evans/create-or-update-comment](https://github.com/peter-evans/create-or-update-comment) from 1 to 2. - [Release notes](https://github.com/peter-evans/create-or-update-comment/releases) - [Commits](https://github.com/peter-evans/create-or-update-comment/compare/v1...v2) --- updated-dependencies: - dependency-name: peter-evans/create-or-update-comment dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/nightly.yml | 2 +- .github/workflows/quality.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 7f789b4610..9ce77edd0e 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -182,7 +182,7 @@ jobs: body-includes: Integration Tests Results - name: Publish results to PR if: always() && github.event_name == 'pull_request' - uses: peter-evans/create-or-update-comment@v1 + uses: peter-evans/create-or-update-comment@v2 with: comment-id: ${{ steps.fc.outputs.comment-id }} issue-number: ${{ github.event.pull_request.number }} diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index d427d65b7f..00db85b01d 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -67,7 +67,7 @@ jobs: body-includes: Ktlint Results - name: Add comment if needed if: always() && github.event_name == 'pull_request' && steps.ktlint-results.outputs.add_comment == 'true' - uses: peter-evans/create-or-update-comment@v1 + uses: peter-evans/create-or-update-comment@v2 with: comment-id: ${{ steps.fc.outputs.comment-id }} issue-number: ${{ github.event.pull_request.number }} From 2a4182ea8421e6179b75acfb20133ef096b416e5 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Tue, 29 Mar 2022 11:53:44 +0300 Subject: [PATCH 115/565] Code review fixes. --- .../room/model/LiveLocationBeaconContent.kt | 2 +- .../location/LocationSharingService.kt | 19 +++++++++++++------ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/LiveLocationBeaconContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/LiveLocationBeaconContent.kt index b21296b4aa..1c80984d2d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/LiveLocationBeaconContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/LiveLocationBeaconContent.kt @@ -29,7 +29,7 @@ data class LiveLocationBeaconContent( /** * Beacon creation timestamp. */ - @Json(name = "m.ts") val ts: Long? = null, + @Json(name = "m.ts") val timestampAsMillisecond: Long? = null, /** * Live location asset type. */ diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingService.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingService.kt index 347443dc4e..0a330b497b 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingService.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingService.kt @@ -20,7 +20,9 @@ import android.content.Intent import android.os.IBinder import android.os.Parcelable import dagger.hilt.android.AndroidEntryPoint +import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.services.VectorService +import im.vector.app.core.time.Clock import im.vector.app.features.notifications.NotificationUtils import im.vector.app.features.session.coroutineScope import kotlinx.coroutines.launch @@ -47,7 +49,8 @@ class LocationSharingService : VectorService(), LocationTracker.Callback { @Inject lateinit var notificationUtils: NotificationUtils @Inject lateinit var locationTracker: LocationTracker - @Inject lateinit var session: Session + @Inject lateinit var activeSessionHolder: ActiveSessionHolder + @Inject lateinit var clock: Clock private var roomArgsList = mutableListOf() private var timers = mutableListOf() @@ -77,21 +80,25 @@ class LocationSharingService : VectorService(), LocationTracker.Callback { scheduleTimer(roomArgs.roomId, roomArgs.durationMillis) // Send beacon info state event - session.coroutineScope.launch { - sendBeaconInfo(roomArgs) - } + activeSessionHolder + .getSafeActiveSession() + ?.let { session -> + session.coroutineScope.launch { + sendBeaconInfo(session, roomArgs) + } + } } return START_STICKY } - private suspend fun sendBeaconInfo(roomArgs: RoomArgs) { + private suspend fun sendBeaconInfo(session: Session, roomArgs: RoomArgs) { val beaconContent = LiveLocationBeaconContent( beaconInfo = BeaconInfo( timeout = roomArgs.durationMillis, isLive = true ), - ts = System.currentTimeMillis() + timestampAsMillisecond = clock.epochMillis() ).toContent() // This format is not yet finalized From 2938fa92c0a6359c94e738d1842d6bbcf7271056 Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Tue, 29 Mar 2022 11:58:11 +0300 Subject: [PATCH 116/565] Rename countThreads method --- .../sdk/internal/database/helper/ThreadEventsHelper.kt | 4 ++-- .../internal/session/room/prune/RedactionEventProcessor.kt | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt index 0e85057f23..811c520b8b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt @@ -100,7 +100,7 @@ internal fun EventEntity.markEventAsRoot( * @return A ThreadSummary containing the counted threads and the latest event message */ internal fun EventEntity.threadSummaryInThread(realm: Realm, rootThreadEventId: String, chunkEntity: ChunkEntity?): Summary { - val numberOfThread = countThreads( + val numberOfThread = countThreadReplies( realm = realm, roomId = roomId, rootThreadEventId = rootThreadEventId @@ -135,7 +135,7 @@ internal fun EventEntity.threadSummaryInThread(realm: Realm, rootThreadEventId: * Counts the number of threads in the main timeline thread summary, * with respect to redactions. */ -internal fun countThreads(realm: Realm, roomId: String, rootThreadEventId: String): Int? = +internal fun countThreadReplies(realm: Realm, roomId: String, rootThreadEventId: String): Int? = TimelineEventEntity .whereRoomId(realm, roomId = roomId) .equalTo(TimelineEventEntityFields.ROOT.ROOT_THREAD_EVENT_ID, rootThreadEventId) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/prune/RedactionEventProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/prune/RedactionEventProcessor.kt index 4fcc47a8d4..6558a138b0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/prune/RedactionEventProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/prune/RedactionEventProcessor.kt @@ -21,7 +21,7 @@ import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.LocalEcho import org.matrix.android.sdk.api.session.events.model.UnsignedData -import org.matrix.android.sdk.internal.database.helper.countThreads +import org.matrix.android.sdk.internal.database.helper.countThreadReplies import org.matrix.android.sdk.internal.database.helper.findRootThreadEvent import org.matrix.android.sdk.internal.database.mapper.ContentMapper import org.matrix.android.sdk.internal.database.mapper.EventMapper @@ -123,7 +123,7 @@ internal class RedactionEventProcessor @Inject constructor() : EventInsertLivePr val rootThreadEvent = eventToPrune.findRootThreadEvent() ?: return val rootThreadEventId = eventToPrune.rootThreadEventId ?: return - val numberOfThreads = countThreads( + val numberOfThreads = countThreadReplies( realm = realm, roomId = roomId, rootThreadEventId = rootThreadEventId From 365c03e76365a2669ed9698cd67ce3e144eea98f Mon Sep 17 00:00:00 2001 From: SpiritCroc Date: Tue, 29 Mar 2022 12:45:23 +0200 Subject: [PATCH 117/565] Load timeline without initial eventId if not found --- changelog.d/5659.bugfix | 1 + .../session/room/timeline/DefaultTimeline.kt | 18 +++++++++++++++++- .../room/timeline/LoadTimelineStrategy.kt | 6 ++++++ 3 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 changelog.d/5659.bugfix diff --git a/changelog.d/5659.bugfix b/changelog.d/5659.bugfix new file mode 100644 index 0000000000..eec39a7738 --- /dev/null +++ b/changelog.d/5659.bugfix @@ -0,0 +1 @@ +Fix endless loading if the event from a permalink is not found diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt index 8c2b4d2bbe..99b9683c31 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt @@ -223,7 +223,15 @@ internal class DefaultTimeline(private val roomId: String, updateState(direction) { it.copy(loading = true) } - val loadMoreResult = strategy.loadMore(count, direction, fetchOnServerIfNeeded) + val loadMoreResult = try { + strategy.loadMore(count, direction, fetchOnServerIfNeeded) + } catch (throwable: Throwable) { + // Timeline could not be loaded with a (likely) permanent issue, such as the + // server now knowing the initialEventId, so we want to show an error message + // and possibly restart without initialEventId. + onTimelineFailure(throwable) + return false + } Timber.v("$baseLogMessage: result $loadMoreResult") val hasMoreToLoad = loadMoreResult != LoadMoreResult.REACHED_END updateState(direction) { @@ -342,6 +350,14 @@ internal class DefaultTimeline(private val roomId: String, } } + private fun onTimelineFailure(throwable: Throwable) { + timelineScope.launch(coroutineDispatchers.main) { + listeners.forEach { + tryOrNull { it.onTimelineFailure(throwable) } + } + } + } + private fun buildStrategy(mode: LoadTimelineStrategy.Mode): LoadTimelineStrategy { return LoadTimelineStrategy( roomId = roomId, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt index a9e7b3bcdc..bb565a8d6b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt @@ -24,6 +24,8 @@ import io.realm.RealmResults import io.realm.kotlin.createObject import kotlinx.coroutines.CompletableDeferred import org.matrix.android.sdk.api.extensions.orFalse +import org.matrix.android.sdk.api.failure.Failure +import org.matrix.android.sdk.api.failure.MatrixError import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.room.timeline.Timeline import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent @@ -194,6 +196,10 @@ internal class LoadTimelineStrategy( getContextLatch?.await() getContextLatch = null } catch (failure: Throwable) { + if (failure is Failure.ServerError && failure.error.code == MatrixError.M_NOT_FOUND) { + // This failure is likely permanent, so handle in DefaultTimeline to restart without eventId + throw failure + } return LoadMoreResult.FAILURE } } From f58f3ad6d9c563322940ee3352273a204b2df603 Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Tue, 29 Mar 2022 14:28:57 +0300 Subject: [PATCH 118/565] Refactoring --- .../database/helper/ThreadEventsHelper.kt | 24 +++++++++---------- .../internal/database/model/EventEntity.kt | 1 + .../room/prune/RedactionEventProcessor.kt | 10 ++++---- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt index 811c520b8b..04cf5b78af 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt @@ -53,14 +53,14 @@ internal fun Map.updateThreadSummaryIfNeeded( for ((rootThreadEventId, eventEntity) in this) { eventEntity.threadSummaryInThread(eventEntity.realm, rootThreadEventId, chunkEntity)?.let { threadSummary -> - val numberOfMessages = threadSummary.first + val inThreadMessages = threadSummary.first val latestEventInThread = threadSummary.second // If this is a thread message, find its root event if exists val rootThreadEvent = if (eventEntity.isThread()) eventEntity.findRootThreadEvent() else eventEntity rootThreadEvent?.markEventAsRoot( - threadsCounted = numberOfMessages, + inThreadMessages = inThreadMessages, latestMessageTimelineEventEntity = latestEventInThread ) } @@ -86,10 +86,10 @@ internal fun EventEntity.findRootThreadEvent(): EventEntity? = * Mark or update the current event a root thread event */ internal fun EventEntity.markEventAsRoot( - threadsCounted: Int, + inThreadMessages: Int, latestMessageTimelineEventEntity: TimelineEventEntity?) { isRootThread = true - numberOfThreads = threadsCounted + numberOfThreads = inThreadMessages threadSummaryLatestMessage = latestMessageTimelineEventEntity } @@ -100,13 +100,13 @@ internal fun EventEntity.markEventAsRoot( * @return A ThreadSummary containing the counted threads and the latest event message */ internal fun EventEntity.threadSummaryInThread(realm: Realm, rootThreadEventId: String, chunkEntity: ChunkEntity?): Summary { - val numberOfThread = countThreadReplies( + val inThreadMessages = countInThreadMessages( realm = realm, roomId = roomId, rootThreadEventId = rootThreadEventId - ) ?: return null + ) - if (numberOfThread <= 0) return null + if (inThreadMessages <= 0) return null // Find latest thread event, we know it exists var chunk = ChunkEntity.findLastForwardChunkOfRoom(realm, roomId) ?: chunkEntity ?: return null @@ -128,26 +128,26 @@ internal fun EventEntity.threadSummaryInThread(realm: Realm, rootThreadEventId: result ?: return null - return Summary(numberOfThread, result) + return Summary(inThreadMessages, result) } /** - * Counts the number of threads in the main timeline thread summary, + * Counts the number of thread replies in the main timeline thread summary, * with respect to redactions. */ -internal fun countThreadReplies(realm: Realm, roomId: String, rootThreadEventId: String): Int? = +internal fun countInThreadMessages(realm: Realm, roomId: String, rootThreadEventId: String): Int = TimelineEventEntity .whereRoomId(realm, roomId = roomId) .equalTo(TimelineEventEntityFields.ROOT.ROOT_THREAD_EVENT_ID, rootThreadEventId) .distinct(TimelineEventEntityFields.ROOT.EVENT_ID) .findAll() - ?.filterNot { timelineEvent -> + .filterNot { timelineEvent -> timelineEvent.root ?.unsignedData ?.takeIf { it.isNotBlank() } ?.toUnsignedData() .isRedacted() - }?.size + }.size /** * Mapping string to UnsignedData using Moshi diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventEntity.kt index b7158ba9cd..09be98aa96 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventEntity.kt @@ -44,6 +44,7 @@ internal open class EventEntity(@Index var eventId: String = "", // Thread related, no need to create a new Entity for performance @Index var isRootThread: Boolean = false, @Index var rootThreadEventId: String? = null, + // Number messages within the thread var numberOfThreads: Int = 0, var threadSummaryLatestMessage: TimelineEventEntity? = null ) : RealmObject() { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/prune/RedactionEventProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/prune/RedactionEventProcessor.kt index 6558a138b0..b19b8d4a6b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/prune/RedactionEventProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/prune/RedactionEventProcessor.kt @@ -21,7 +21,7 @@ import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.LocalEcho import org.matrix.android.sdk.api.session.events.model.UnsignedData -import org.matrix.android.sdk.internal.database.helper.countThreadReplies +import org.matrix.android.sdk.internal.database.helper.countInThreadMessages import org.matrix.android.sdk.internal.database.helper.findRootThreadEvent import org.matrix.android.sdk.internal.database.mapper.ContentMapper import org.matrix.android.sdk.internal.database.mapper.EventMapper @@ -123,14 +123,14 @@ internal class RedactionEventProcessor @Inject constructor() : EventInsertLivePr val rootThreadEvent = eventToPrune.findRootThreadEvent() ?: return val rootThreadEventId = eventToPrune.rootThreadEventId ?: return - val numberOfThreads = countThreadReplies( + val inThreadMessages = countInThreadMessages( realm = realm, roomId = roomId, rootThreadEventId = rootThreadEventId - ) ?: return + ) - rootThreadEvent.numberOfThreads = numberOfThreads - if (numberOfThreads == 0) { + rootThreadEvent.numberOfThreads = inThreadMessages + if (inThreadMessages == 0) { // We should also clear the thread summary list rootThreadEvent.isRootThread = false rootThreadEvent.threadSummaryLatestMessage = null From 33d197a4298f0f4e2345363abca13b47fb6c896d Mon Sep 17 00:00:00 2001 From: Szimszon Date: Mon, 28 Mar 2022 06:16:58 +0000 Subject: [PATCH 119/565] Translated using Weblate (Hungarian) Currently translated at 100.0% (2171 of 2171 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/hu/ --- vector/src/main/res/values-hu/strings.xml | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/vector/src/main/res/values-hu/strings.xml b/vector/src/main/res/values-hu/strings.xml index f189a4198f..f13a97d216 100644 --- a/vector/src/main/res/values-hu/strings.xml +++ b/vector/src/main/res/values-hu/strings.xml @@ -457,7 +457,7 @@ Vedd figyelembe, hogy az alkalmazás újraindul ami sok időt vehet igénybe."< %d tagság változás %d tagság változás
- Tagok listázása + Tagok %d tag %d tag @@ -1785,7 +1785,7 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze Ön az egyetlen adminisztrátora a térnek. Ha kilép, senki nem tudja irányítani. Amíg nem hívnak meg újra nem tudsz újracsatlakozni. Csak te van itt. Ha kilépsz, akkor a jövőben senki nem tud majd ide belépni, beleértve téged is. - Tér elhagyása + Elhagyás Szobák hozzáadása Szobák felderítése @@ -2419,4 +2419,23 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze %1$s, %2$s és mások %1$s és %2$s + Ha folyamatosan meg akarod osztani a földrajzi pozíciódat ahhoz a(z) ${app_name} alkalmazásnak hozzá kell férnie a helyadatokhoz akkor is ha a háttérben fut. +\nCsak az általad megadott idő intervallumban férünk hozzá a helyadatokhoz. + Egyre közelebb kerülünk ahhoz, hogy publikus béta állapotba kerüljenek az üzenetszálak. +\n +\nAz előkészületekkel be kell vezetnünk pár változtatást: az eddig a pontig készített üzenetszálak hagyományos válaszokként fognak megjelenni. +\n +\nEz egy egyszeri változtatás ahogy az üzenetszálak ezentúl a Matrix specifikáció részét képezik. + Állj + Folyamatos pozíció megosztás engedélyezve + Hozzáférés engedélyezése + Ennek a földrajzi helynek a megosztása + Ennek a földrajzi helynek a megosztása + Pozícióm folyamatos megosztása + Pozícióm folyamatos megosztása + Jelenlegi pozícióm megosztása + Jelenlegi pozícióm megosztása + Ugrás a jelenlegi pozícióra + Kiválasztott hely rögzítése a térképen + Üzenetszálak lassan béta állapotba kerülnek 🎉 \ No newline at end of file From ea96718af533484652c85d2a2f765c36c2cc3193 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Mon, 28 Mar 2022 11:40:21 +0000 Subject: [PATCH 120/565] Translated using Weblate (French) Currently translated at 99.2% (2155 of 2171 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/fr/ --- vector/src/main/res/values-fr/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vector/src/main/res/values-fr/strings.xml b/vector/src/main/res/values-fr/strings.xml index e1b3b0043c..c9230ec49e 100644 --- a/vector/src/main/res/values-fr/strings.xml +++ b/vector/src/main/res/values-fr/strings.xml @@ -1580,8 +1580,8 @@ Appel échoué Les messages de ce salon sont chiffrés de bout en bout. Vous avez créé et configuré ce salon. - Le code est requis à l’ouverture d’${app_name}. - Le code est demandé après 2 minutes d\'inutilisation d’${app_name}. + Le code est requis à l’ouverture de ${app_name}. + Le code est demandé après 2 minutes d\'inutilisation de ${app_name}. Demander le code après 2 minutes Afficher uniquement le numéro de messages non-lus dans une simple notification. Afficher les détails comme les noms des salons et le contenu du message. From 7999bd75233d22e73bab2d9dd5c40b056c7e0aaf Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Tue, 29 Mar 2022 15:34:50 +0300 Subject: [PATCH 121/565] Show a banner in timeline while location sharing service is running. --- .../home/room/detail/RoomDetailViewEvents.kt | 3 + .../home/room/detail/TimelineFragment.kt | 10 +++ .../home/room/detail/TimelineViewModel.kt | 16 ++++- .../location/LocationSharingService.kt | 11 +++- .../LocationSharingServiceConnection.kt | 63 +++++++++++++++++++ 5 files changed, 100 insertions(+), 3 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/location/LocationSharingServiceConnection.kt diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewEvents.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewEvents.kt index d08a27324c..a7819d3ddf 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewEvents.kt @@ -82,4 +82,7 @@ sealed class RoomDetailViewEvents : VectorViewEvents { data class StartChatEffect(val type: ChatEffect) : RoomDetailViewEvents() object StopChatEffects : RoomDetailViewEvents() object RoomReplacementStarted : RoomDetailViewEvents() + + object ShowLocationSharingIndicator : RoomDetailViewEvents() + object HideLocationSharingIndicator : RoomDetailViewEvents() } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt index 9c754e042c..00ddd8dcbd 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt @@ -482,6 +482,8 @@ class TimelineFragment @Inject constructor( RoomDetailViewEvents.StopChatEffects -> handleStopChatEffects() is RoomDetailViewEvents.DisplayAndAcceptCall -> acceptIncomingCall(it) RoomDetailViewEvents.RoomReplacementStarted -> handleRoomReplacement() + RoomDetailViewEvents.ShowLocationSharingIndicator -> handleShowLocationSharingIndicator() + RoomDetailViewEvents.HideLocationSharingIndicator -> handleHideLocationSharingIndicator() } } @@ -616,6 +618,14 @@ class TimelineFragment @Inject constructor( ) } + private fun handleShowLocationSharingIndicator() { + views.locationLiveStatusIndicator.isVisible = true + } + + private fun handleHideLocationSharingIndicator() { + views.locationLiveStatusIndicator.isVisible = false + } + private fun displayErrorMessage(error: RoomDetailViewEvents.Failure) { if (error.showInDialog) displayErrorDialog(error.throwable) else showErrorInSnackbar(error.throwable) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt index 6933adc758..024c42e503 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt @@ -52,6 +52,7 @@ import im.vector.app.features.home.room.detail.sticker.StickerPickerActionHandle import im.vector.app.features.home.room.detail.timeline.factory.TimelineFactory import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever import im.vector.app.features.home.room.typing.TypingHelper +import im.vector.app.features.location.LocationSharingServiceConnection import im.vector.app.features.notifications.NotificationDrawerManager import im.vector.app.features.powerlevel.PowerLevelsFlowFactory import im.vector.app.features.session.coroutineScope @@ -124,10 +125,11 @@ class TimelineViewModel @AssistedInject constructor( private val activeConferenceHolder: JitsiActiveConferenceHolder, private val decryptionFailureTracker: DecryptionFailureTracker, private val notificationDrawerManager: NotificationDrawerManager, + private val locationSharingServiceConnection: LocationSharingServiceConnection, timelineFactory: TimelineFactory, appStateHandler: AppStateHandler ) : VectorViewModel(initialState), - Timeline.Listener, ChatEffectManager.Delegate, CallProtocolsChecker.Listener { + Timeline.Listener, ChatEffectManager.Delegate, CallProtocolsChecker.Listener, LocationSharingServiceConnection.Callback { private val room = session.getRoom(initialState.roomId)!! private val eventId = initialState.eventId @@ -219,6 +221,9 @@ class TimelineViewModel @AssistedInject constructor( // Threads initThreads() + + // Observe location service lifecycle to be able to warn the user + locationSharingServiceConnection.bind(this) } /** @@ -1218,6 +1223,14 @@ class TimelineViewModel @AssistedInject constructor( _viewEvents.post(RoomDetailViewEvents.OnNewTimelineEvents(eventIds)) } + override fun onLocationServiceRunning() { + _viewEvents.post(RoomDetailViewEvents.ShowLocationSharingIndicator) + } + + override fun onLocationServiceStopped() { + _viewEvents.post(RoomDetailViewEvents.HideLocationSharingIndicator) + } + override fun onCleared() { timeline.dispose() timeline.removeAllListeners() @@ -1231,6 +1244,7 @@ class TimelineViewModel @AssistedInject constructor( // we should also mark it as read here, for the scenario that the user // is already in the thread timeline markThreadTimelineAsReadLocal() + locationSharingServiceConnection.unbind() super.onCleared() } } diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingService.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingService.kt index a2a68e4188..bcbda9ecf4 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingService.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingService.kt @@ -17,6 +17,7 @@ package im.vector.app.features.location import android.content.Intent +import android.os.Binder import android.os.IBinder import android.os.Parcelable import dagger.hilt.android.AndroidEntryPoint @@ -41,6 +42,8 @@ class LocationSharingService : VectorService(), LocationTracker.Callback { @Inject lateinit var notificationUtils: NotificationUtils @Inject lateinit var locationTracker: LocationTracker + private val binder = LocalBinder() + private var roomArgsList = mutableListOf() private var timers = mutableListOf() @@ -120,8 +123,12 @@ class LocationSharingService : VectorService(), LocationTracker.Callback { destroyMe() } - override fun onBind(intent: Intent?): IBinder? { - return null + override fun onBind(intent: Intent?): IBinder { + return binder + } + + inner class LocalBinder : Binder() { + fun getService(): LocationSharingService = this@LocationSharingService } companion object { diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingServiceConnection.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingServiceConnection.kt new file mode 100644 index 0000000000..9af6b1539a --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingServiceConnection.kt @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2022 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.features.location + +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.ServiceConnection +import android.os.IBinder +import javax.inject.Inject + +class LocationSharingServiceConnection @Inject constructor( + private val context: Context +) : ServiceConnection { + + interface Callback { + fun onLocationServiceRunning() + fun onLocationServiceStopped() + } + + private var callback: Callback? = null + private var isBound = false + + fun bind(callback: Callback) { + this.callback = callback + + if (isBound) { + callback.onLocationServiceRunning() + } else { + Intent(context, LocationSharingService::class.java).also { intent -> + context.bindService(intent, this, 0) + } + } + } + + fun unbind() { + callback = null + } + + override fun onServiceConnected(className: ComponentName, binder: IBinder) { + isBound = true + callback?.onLocationServiceRunning() + } + + override fun onServiceDisconnected(className: ComponentName) { + isBound = false + callback?.onLocationServiceStopped() + } +} From 0f7d6a19469cd364e4539f72b5c05929c514c84c Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Tue, 29 Mar 2022 16:26:47 +0300 Subject: [PATCH 122/565] Add loader to thread list --- .../list/viewmodel/ThreadListViewModel.kt | 8 ++++++ .../list/viewmodel/ThreadListViewState.kt | 1 + .../threads/list/views/ThreadListFragment.kt | 5 ++++ .../main/res/layout/fragment_thread_list.xml | 26 ++++++++++++++----- 4 files changed, 34 insertions(+), 6 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewModel.kt index 7f18d172e4..da09683b63 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewModel.kt @@ -113,7 +113,15 @@ class ThreadListViewModel @AssistedInject constructor(@Assisted val initialState private fun fetchThreadList() { viewModelScope.launch { + isLoading(true) room?.fetchThreadSummaries() + isLoading(false) + } + } + + private fun isLoading(show: Boolean) { + setState { + copy(isLoading = show) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewState.kt index e08f70030b..2328da0b8a 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewState.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewState.kt @@ -27,6 +27,7 @@ data class ThreadListViewState( val threadSummaryList: Async> = Uninitialized, val rootThreadEventList: Async> = Uninitialized, val shouldFilterThreads: Boolean = false, + val isLoading: Boolean = false, val roomId: String ) : MavericksState { constructor(args: ThreadListArgs) : this(roomId = args.roomId) diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt index d5659efa49..ab9ca4d2f2 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt @@ -104,6 +104,11 @@ class ThreadListFragment @Inject constructor( override fun invalidate() = withState(threadListViewModel) { state -> renderEmptyStateIfNeeded(state) threadListController.update(state) + renderLoaderIfNeeded(state) + } + + private fun renderLoaderIfNeeded(state: ThreadListViewState) { + views.threadListProgressBar.isVisible = state.isLoading } private fun renderToolbar() { diff --git a/vector/src/main/res/layout/fragment_thread_list.xml b/vector/src/main/res/layout/fragment_thread_list.xml index 7e7c79f8c3..f0f7dff611 100644 --- a/vector/src/main/res/layout/fragment_thread_list.xml +++ b/vector/src/main/res/layout/fragment_thread_list.xml @@ -37,6 +37,21 @@ tools:listitem="@layout/item_thread" tools:visibility="gone" /> + + + + app:layout_constraintTop_toBottomOf="@id/threadListEmptyImageView" /> + app:layout_constraintTop_toBottomOf="@id/threadListEmptyTitleTextView" /> Date: Tue, 29 Mar 2022 13:26:41 +0200 Subject: [PATCH 123/565] Add link to the changelog --- docs/jitsi.md | 2 ++ tools/jitsi/build_jisti_libs.sh | 2 ++ 2 files changed, 4 insertions(+) diff --git a/docs/jitsi.md b/docs/jitsi.md index 55cedaedb1..1b4e0a37b4 100644 --- a/docs/jitsi.md +++ b/docs/jitsi.md @@ -18,6 +18,8 @@ The generated maven repository is then host in the project https://github.com/ve Update the script `./tools/jitsi/build_jisti_libs.sh` with the tag of the project `https://github.com/jitsi/jitsi-meet`. +Latest tag can be found from this page: https://github.com/jitsi/jitsi-meet-release-notes/blob/master/CHANGELOG-MOBILE-SDKS.md + Currently we are building the version with the tag `android-sdk-3.10.0`. ### Run the build script diff --git a/tools/jitsi/build_jisti_libs.sh b/tools/jitsi/build_jisti_libs.sh index e352575775..25002fd379 100755 --- a/tools/jitsi/build_jisti_libs.sh +++ b/tools/jitsi/build_jisti_libs.sh @@ -25,6 +25,8 @@ cd jitsi-meet # This is commit after version 2.2.2, which does not compile # git checkout 5a934c071a5cbe64de275a25d0ed62d8193cdd03 +# Changelog: https://github.com/jitsi/jitsi-meet-release-notes/blob/master/CHANGELOG-MOBILE-SDKS.md + # Version android-sdk-3.10.0, commit 99e56e229dfa3c490096e37c3e5b76d2a3f23e32 git checkout android-sdk-3.10.0 From cc5e8f35a73eedf1d55c5635310697e91dafc038 Mon Sep 17 00:00:00 2001 From: ericdecanini Date: Tue, 29 Mar 2022 16:25:01 +0200 Subject: [PATCH 124/565] Improves code formatting --- .../app/features/home/room/detail/TimelineFragment.kt | 2 +- .../home/room/detail/timeline/item/MessageAudioItem.kt | 6 +++--- .../home/room/detail/timeline/item/MessageVoiceItem.kt | 4 ---- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt index a5c1b8a544..db86b5a55d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt @@ -1193,7 +1193,7 @@ class TimelineFragment @Inject constructor( val nonFormattedBody = when (messageContent) { is MessageAudioContent -> getAudioContentBodyText(messageContent) is MessagePollContent -> messageContent.getBestPollCreationInfo()?.question?.getBestQuestion() - else -> messageContent?.body ?: "" + else -> messageContent?.body.orEmpty() } var formattedBody: CharSequence? = null if (messageContent is MessageTextContent && messageContent.format == MessageFormat.FORMAT_MATRIX_HTML) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageAudioItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageAudioItem.kt index 243aa8c6a4..7748ccb03f 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageAudioItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageAudioItem.kt @@ -74,13 +74,13 @@ abstract class MessageAudioItem : AbsMessageItem() { } private fun bindUploadState(holder: Holder) { - if (!attributes.informationData.sendState.hasFailed()) { - contentUploadStateTrackerBinder.bind(attributes.informationData.eventId, isLocalFile, holder.progressLayout) - } else { + if (attributes.informationData.sendState.hasFailed()) { holder.audioPlaybackControlButton.setImageResource(R.drawable.ic_cross) holder.audioPlaybackControlButton.contentDescription = holder.view.context.getString(R.string.error_audio_message_unable_to_play, filename) holder.progressLayout.isVisible = false + } else { + contentUploadStateTrackerBinder.bind(attributes.informationData.eventId, isLocalFile, holder.progressLayout) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt index d66b9445f5..8da4a3dcd8 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt @@ -58,10 +58,6 @@ abstract class MessageVoiceItem : AbsMessageItem() { @JvmField var isLocalFile = false - @EpoxyAttribute - @JvmField - var isDownloaded = false - @EpoxyAttribute lateinit var contentUploadStateTrackerBinder: ContentUploadStateTrackerBinder From 0d2d1339e09da8dc9c9b4e414aa415368f0b0f15 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 29 Mar 2022 16:01:46 +0200 Subject: [PATCH 125/565] Bump Jitsi lib from 3.10.0 to 5.0.1 --- build.gradle | 2 +- dependencies_groups.gradle | 3 +++ tools/jitsi/build_jisti_libs.sh | 3 +-- vector/build.gradle | 4 ++-- vector/src/main/AndroidManifest.xml | 2 +- 5 files changed, 8 insertions(+), 6 deletions(-) diff --git a/build.gradle b/build.gradle index 8ac546ac54..8b527ccf83 100644 --- a/build.gradle +++ b/build.gradle @@ -54,7 +54,7 @@ allprojects { } // Jitsi repo maven { - url "https://github.com/vector-im/jitsi_libre_maven/raw/main/android-sdk-3.10.0" + url "https://github.com/vector-im/jitsi_libre_maven/raw/main/android-sdk-5.0.1" // Note: to test Jitsi release you can use a local file like this: // url "file:///Users/bmarty/workspaces/jitsi_libre_maven/android-sdk-3.10.0" content { diff --git a/dependencies_groups.gradle b/dependencies_groups.gradle index 6f155a0149..9e70a1de42 100644 --- a/dependencies_groups.gradle +++ b/dependencies_groups.gradle @@ -7,6 +7,7 @@ ext.groups = [ 'com.github.chrisbanes', 'com.github.hyuwah', 'com.github.jetradarmobile', + 'com.github.MatrixFrog', 'com.github.tapadoo', 'com.github.vector-im', 'com.github.yalantis', @@ -49,10 +50,12 @@ ext.groups = [ 'com.beust', 'com.davemorrissey.labs', 'com.dropbox.core', + 'com.facebook.fbjni', 'com.facebook.fresco', 'com.facebook.infer.annotation', 'com.facebook.soloader', 'com.facebook.stetho', + 'com.facebook.yoga', 'com.fasterxml', 'com.fasterxml.jackson', 'com.fasterxml.jackson.core', diff --git a/tools/jitsi/build_jisti_libs.sh b/tools/jitsi/build_jisti_libs.sh index 25002fd379..4220dd1551 100755 --- a/tools/jitsi/build_jisti_libs.sh +++ b/tools/jitsi/build_jisti_libs.sh @@ -27,8 +27,7 @@ cd jitsi-meet # Changelog: https://github.com/jitsi/jitsi-meet-release-notes/blob/master/CHANGELOG-MOBILE-SDKS.md -# Version android-sdk-3.10.0, commit 99e56e229dfa3c490096e37c3e5b76d2a3f23e32 -git checkout android-sdk-3.10.0 +git checkout android-sdk-5.0.1 echo echo "##################################################" diff --git a/vector/build.gradle b/vector/build.gradle index 9f8471bc18..1ab9dd8251 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -470,10 +470,10 @@ dependencies { // WebRTC // org.webrtc:google-webrtc is for development purposes only // implementation 'org.webrtc:google-webrtc:1.0.+' - implementation('com.facebook.react:react-native-webrtc:1.92.1-jitsi-9093212@aar') + implementation('com.facebook.react:react-native-webrtc:1.94.2-jitsi-10226524@aar') // Jitsi - implementation('org.jitsi.react:jitsi-meet-sdk:3.10.0') { + implementation('org.jitsi.react:jitsi-meet-sdk:5.0.1') { exclude group: 'com.google.firebase' exclude group: 'com.google.android.gms' exclude group: 'com.android.installreferrer' diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml index 1ffe8a2be8..eada664216 100644 --- a/vector/src/main/AndroidManifest.xml +++ b/vector/src/main/AndroidManifest.xml @@ -49,7 +49,7 @@ - + From 6adf4878f7cda2bc3560bbea9ecc8f135fe74f33 Mon Sep 17 00:00:00 2001 From: ericdecanini Date: Tue, 29 Mar 2022 17:40:00 +0200 Subject: [PATCH 126/565] Fixes bugs related to audio message duration being set incorrectly between activity states --- .../app/features/home/room/detail/TimelineFragment.kt | 1 + .../detail/timeline/helper/AudioMessagePlaybackTracker.kt | 8 +++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt index db86b5a55d..4907f99d25 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt @@ -900,6 +900,7 @@ class TimelineFragment @Inject constructor( } override fun onDestroyView() { + audioMessagePlaybackTracker.makeAllPlaybacksIdle() lazyLoadedViews.unBind() timelineEventController.callback = null timelineEventController.removeModelBuildListener(modelBuildListener) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/AudioMessagePlaybackTracker.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/AudioMessagePlaybackTracker.kt index 546b76bebd..fb000d7b70 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/AudioMessagePlaybackTracker.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/AudioMessagePlaybackTracker.kt @@ -93,9 +93,11 @@ class AudioMessagePlaybackTracker @Inject constructor() { } fun pausePlayback(id: String) { - val currentPlaybackTime = getPlaybackTime(id) - val currentPercentage = getPercentage(id) - setState(id, Listener.State.Paused(currentPlaybackTime, currentPercentage)) + if (getPlaybackState(id) is Listener.State.Playing) { + val currentPlaybackTime = getPlaybackTime(id) + val currentPercentage = getPercentage(id) + setState(id, Listener.State.Paused(currentPlaybackTime, currentPercentage)) + } } fun stopPlayback(id: String) { From 587948c1b97337370523b3d358116ed07d0ae0b2 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 29 Mar 2022 18:24:44 +0200 Subject: [PATCH 127/565] Bump Jitsi lib from 5.0.1 to 5.0.2 https://github.com/jitsi/jitsi-meet-release-notes/blob/master/CHANGELOG-MOBILE-SDKS.md#502-2022-03-29 --- build.gradle | 2 +- tools/jitsi/build_jisti_libs.sh | 5 ++++- vector/build.gradle | 4 ++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/build.gradle b/build.gradle index 8b527ccf83..6ad0663c6d 100644 --- a/build.gradle +++ b/build.gradle @@ -54,7 +54,7 @@ allprojects { } // Jitsi repo maven { - url "https://github.com/vector-im/jitsi_libre_maven/raw/main/android-sdk-5.0.1" + url "https://github.com/vector-im/jitsi_libre_maven/raw/main/android-sdk-5.0.2" // Note: to test Jitsi release you can use a local file like this: // url "file:///Users/bmarty/workspaces/jitsi_libre_maven/android-sdk-3.10.0" content { diff --git a/tools/jitsi/build_jisti_libs.sh b/tools/jitsi/build_jisti_libs.sh index 4220dd1551..445dc5e0fe 100755 --- a/tools/jitsi/build_jisti_libs.sh +++ b/tools/jitsi/build_jisti_libs.sh @@ -17,6 +17,9 @@ cd .. rm -rf jitsi-meet git clone https://github.com/jitsi/jitsi-meet +# Android SDK +export ANDROID_SDK_ROOT=~/Library/Android/sdk + # We want a libre build! export LIBRE_BUILD=true @@ -27,7 +30,7 @@ cd jitsi-meet # Changelog: https://github.com/jitsi/jitsi-meet-release-notes/blob/master/CHANGELOG-MOBILE-SDKS.md -git checkout android-sdk-5.0.1 +git checkout android-sdk-5.0.2 echo echo "##################################################" diff --git a/vector/build.gradle b/vector/build.gradle index 1ab9dd8251..8ccec79902 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -470,10 +470,10 @@ dependencies { // WebRTC // org.webrtc:google-webrtc is for development purposes only // implementation 'org.webrtc:google-webrtc:1.0.+' - implementation('com.facebook.react:react-native-webrtc:1.94.2-jitsi-10226524@aar') + implementation('com.facebook.react:react-native-webrtc:1.94.2-jitsi-10227332@aar') // Jitsi - implementation('org.jitsi.react:jitsi-meet-sdk:5.0.1') { + implementation('org.jitsi.react:jitsi-meet-sdk:5.0.2') { exclude group: 'com.google.firebase' exclude group: 'com.google.android.gms' exclude group: 'com.android.installreferrer' From b9b5cab7722a8b648b80b62fffc19cf27b92c375 Mon Sep 17 00:00:00 2001 From: cketti Date: Tue, 29 Mar 2022 16:12:12 +0200 Subject: [PATCH 128/565] Use truncate mode to replace the contents of existing files `ContentResolver.openOutputStream(Uri)` does not truncate existing files. If the amount of data written is smaller than the file size, you end up with new data at the beginning of the file followed by old data at the end of the file. --- changelog.d/5663.bugfix | 1 + .../im/vector/app/features/crypto/keys/KeysExporter.kt | 2 +- .../keysbackup/setup/KeysBackupSetupStep3Fragment.kt | 2 +- .../crypto/recover/BootstrapSaveRecoveryKeyFragment.kt | 2 +- .../app/features/settings/devtools/KeyRequestsFragment.kt | 2 +- .../vector/app/features/crypto/keys/KeysExporterTest.kt | 8 ++++---- .../src/test/java/im/vector/app/test/fakes/FakeContext.kt | 8 ++++---- 7 files changed, 13 insertions(+), 12 deletions(-) create mode 100644 changelog.d/5663.bugfix diff --git a/changelog.d/5663.bugfix b/changelog.d/5663.bugfix new file mode 100644 index 0000000000..5086407e8e --- /dev/null +++ b/changelog.d/5663.bugfix @@ -0,0 +1 @@ +Fixed key export when overwriting existing files diff --git a/vector/src/main/java/im/vector/app/features/crypto/keys/KeysExporter.kt b/vector/src/main/java/im/vector/app/features/crypto/keys/KeysExporter.kt index 3db67df8e1..27d8d4842a 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/keys/KeysExporter.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/keys/KeysExporter.kt @@ -34,7 +34,7 @@ class KeysExporter @Inject constructor( suspend fun export(password: String, uri: Uri) { withContext(dispatchers.io) { val data = session.cryptoService().exportRoomKeys(password) - context.contentResolver.openOutputStream(uri) + context.contentResolver.openOutputStream(uri, "wt") ?.use { it.write(data) } ?: throw IllegalStateException("Unable to open file for writing") verifyExportedKeysOutputFileSize(uri, expectedSize = data.size.toLong()) diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupStep3Fragment.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupStep3Fragment.kt index c1cd87b4c8..42ff4ac183 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupStep3Fragment.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupStep3Fragment.kt @@ -165,7 +165,7 @@ class KeysBackupSetupStep3Fragment @Inject constructor() : VectorBaseFragment os.write(data.toByteArray()) os.flush() diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSaveRecoveryKeyFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSaveRecoveryKeyFragment.kt index 8a41c7ce4d..746ed48c94 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSaveRecoveryKeyFragment.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSaveRecoveryKeyFragment.kt @@ -81,7 +81,7 @@ class BootstrapSaveRecoveryKeyFragment @Inject constructor( val uri = activityResult.data?.data ?: return@registerStartForActivityResult lifecycleScope.launch(Dispatchers.IO) { try { - sharedViewModel.handle(BootstrapActions.SaveKeyToUri(requireContext().contentResolver!!.openOutputStream(uri)!!)) + sharedViewModel.handle(BootstrapActions.SaveKeyToUri(requireContext().contentResolver!!.openOutputStream(uri, "wt")!!)) } catch (failure: Throwable) { sharedViewModel.handle(BootstrapActions.SaveReqFailed) } diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestsFragment.kt index db2d07feef..6748fec1bc 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestsFragment.kt @@ -106,7 +106,7 @@ class KeyRequestsFragment @Inject constructor() : VectorBaseFragment { tryOrNull { - requireContext().contentResolver?.openOutputStream(it.uri) + requireContext().contentResolver?.openOutputStream(it.uri, "wt") ?.use { os -> os.write(it.raw.toByteArray()) } } } diff --git a/vector/src/test/java/im/vector/app/features/crypto/keys/KeysExporterTest.kt b/vector/src/test/java/im/vector/app/features/crypto/keys/KeysExporterTest.kt index 5d0317592d..3bec3fad88 100644 --- a/vector/src/test/java/im/vector/app/features/crypto/keys/KeysExporterTest.kt +++ b/vector/src/test/java/im/vector/app/features/crypto/keys/KeysExporterTest.kt @@ -53,7 +53,7 @@ class KeysExporterTest { @Test fun `when exporting then writes exported keys to context output stream`() { givenFileDescriptorWithSize(size = A_ROOM_KEYS_EXPORT.size.toLong()) - val outputStream = context.givenOutputStreamFor(A_URI) + val outputStream = context.givenOutputStreamFor(A_URI, mode = "wt") runTest { keysExporter.export(A_PASSWORD, A_URI) } @@ -63,7 +63,7 @@ class KeysExporterTest { @Test fun `given different file size returned for export when exporting then throws UnexpectedExportKeysFileSizeException`() { givenFileDescriptorWithSize(size = 110) - context.givenOutputStreamFor(A_URI) + context.givenOutputStreamFor(A_URI, mode = "wt") assertFailsWith { runTest { keysExporter.export(A_PASSWORD, A_URI) } @@ -72,7 +72,7 @@ class KeysExporterTest { @Test fun `given output stream is unavailable for exporting to when exporting then throws IllegalStateException`() { - context.givenMissingOutputStreamFor(A_URI) + context.givenMissingOutputStreamFor(A_URI, mode = "wt") assertFailsWith(message = "Unable to open file for writing") { runTest { keysExporter.export(A_PASSWORD, A_URI) } @@ -82,7 +82,7 @@ class KeysExporterTest { @Test fun `given exported file is missing after export when exporting then throws IllegalStateException`() { context.givenFileDescriptor(A_URI, mode = "r") { null } - context.givenOutputStreamFor(A_URI) + context.givenOutputStreamFor(A_URI, mode = "wt") assertFailsWith(message = "Exported file not found") { runTest { keysExporter.export(A_PASSWORD, A_URI) } diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeContext.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeContext.kt index 8382e54253..de1a7956b8 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeContext.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeContext.kt @@ -39,13 +39,13 @@ class FakeContext( every { contentResolver.openFileDescriptor(uri, mode, null) } returns fileDescriptor } - fun givenOutputStreamFor(uri: Uri): OutputStream { + fun givenOutputStreamFor(uri: Uri, mode: String): OutputStream { val outputStream = mockk(relaxed = true) - every { contentResolver.openOutputStream(uri) } returns outputStream + every { contentResolver.openOutputStream(uri, mode) } returns outputStream return outputStream } - fun givenMissingOutputStreamFor(uri: Uri) { - every { contentResolver.openOutputStream(uri) } returns null + fun givenMissingOutputStreamFor(uri: Uri, mode: String) { + every { contentResolver.openOutputStream(uri, mode) } returns null } } From 963b2dfa572e384ee510ea9add03e651452224c6 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 29 Mar 2022 19:38:10 +0200 Subject: [PATCH 129/565] Ignore false positive on static analysis tools Until 2023-01-01Z ! --- build.gradle | 7 +++++++ tools/dependencycheck/suppressions.xml | 17 +++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 tools/dependencycheck/suppressions.xml diff --git a/build.gradle b/build.gradle index 6ad0663c6d..a5e1242afa 100644 --- a/build.gradle +++ b/build.gradle @@ -35,6 +35,13 @@ plugins { // https://github.com/jeremylong/DependencyCheck apply plugin: 'org.owasp.dependencycheck' +dependencyCheck { + // See https://jeremylong.github.io/DependencyCheck/general/suppression.html + suppressionFiles = [ + "./tools/dependencycheck/suppressions.xml" + ] +} + allprojects { apply plugin: "org.jlleitschuh.gradle.ktlint" diff --git a/tools/dependencycheck/suppressions.xml b/tools/dependencycheck/suppressions.xml new file mode 100644 index 0000000000..758b1a87f3 --- /dev/null +++ b/tools/dependencycheck/suppressions.xml @@ -0,0 +1,17 @@ + + + + + ^pkg:maven/com\.pinterest\.ktlint/ktlint\-reporter\-checkstyle@.*$ + CVE-2019-10782 + + + + ^pkg:maven/com\.pinterest\.ktlint/ktlint\-reporter\-checkstyle@.*$ + CVE-2019-9658 + + From 8ad4f20d990c079bdee9e0f41c3d93a043a511fe Mon Sep 17 00:00:00 2001 From: ericdecanini Date: Tue, 29 Mar 2022 19:54:23 +0200 Subject: [PATCH 130/565] Fixes bug where audio can be played before waveform is ready --- .../features/home/room/detail/timeline/item/MessageVoiceItem.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt index 8da4a3dcd8..02937574f2 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt @@ -94,11 +94,11 @@ abstract class MessageVoiceItem : AbsMessageItem() { ThemeUtils.getColor(holder.view.context, R.attr.vctr_content_quinary) } holder.voicePlaybackLayout.backgroundTintList = ColorStateList.valueOf(backgroundTint) - holder.voicePlaybackControlButton.setOnClickListener { playbackControlButtonClickListener?.invoke(it) } } private fun onWaveformViewReady(holder: Holder) { holder.voicePlaybackWaveform.setOnLongClickListener(attributes.itemLongClickListener) + holder.voicePlaybackControlButton.setOnClickListener { playbackControlButtonClickListener?.invoke(it) } val waveformColorIdle = ThemeUtils.getColor(holder.view.context, R.attr.vctr_content_quaternary) val waveformColorPlayed = ThemeUtils.getColor(holder.view.context, R.attr.vctr_content_secondary) From f4f5beff152ebcfe623aa84ae9155bf16c733cf9 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 30 Mar 2022 09:54:02 +0200 Subject: [PATCH 131/565] Add changelogs --- changelog.d/5654.feature | 1 + changelog.d/5654.misc | 1 + 2 files changed, 2 insertions(+) create mode 100644 changelog.d/5654.feature create mode 100644 changelog.d/5654.misc diff --git a/changelog.d/5654.feature b/changelog.d/5654.feature new file mode 100644 index 0000000000..52a41ef37a --- /dev/null +++ b/changelog.d/5654.feature @@ -0,0 +1 @@ +Update Jitsi lib from 3.10.0 to 5.0.2 \ No newline at end of file diff --git a/changelog.d/5654.misc b/changelog.d/5654.misc new file mode 100644 index 0000000000..26e2ed5a1c --- /dev/null +++ b/changelog.d/5654.misc @@ -0,0 +1 @@ +Setup the plugin org.owasp.dependencycheck \ No newline at end of file From 9d3d574d2833afc4cb9f7dc6b564fbb3b49cd5a0 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Wed, 30 Mar 2022 09:09:46 +0100 Subject: [PATCH 132/565] FTUE - Finalising personalisation journey (#5519) * enabling the personalisation flow and promoting the strings for translation * delegating the fake vector features to the static defaults, the fake exists for allowing overrides when needed * incorporating the personalisation screens into the UI test signup * adding changelog entry * removing unused import * putting the personalisation UI test flow behind the feature flag so that we can keep it disabled * disabling the personalisation flow, we'll batch enable with other parts of the FTUE flow * enabling the personalisation feature for registration unit tests which expect it to be enabled --- changelog.d/5519.wip | 1 + .../im/vector/app/ui/robot/OnboardingRobot.kt | 17 ++++++++++++++++- vector/src/main/res/values/donottranslate.xml | 18 ------------------ vector/src/main/res/values/strings.xml | 19 +++++++++++++++++++ .../onboarding/OnboardingViewModelTest.kt | 9 ++++++--- .../app/test/fakes/FakeVectorFeatures.kt | 14 ++++++++------ 6 files changed, 50 insertions(+), 28 deletions(-) create mode 100644 changelog.d/5519.wip diff --git a/changelog.d/5519.wip b/changelog.d/5519.wip new file mode 100644 index 0000000000..c5a6112ad9 --- /dev/null +++ b/changelog.d/5519.wip @@ -0,0 +1 @@ +Finalising FTUE onboarding account creation personalization steps but keeping feature disabled until other parts are complete \ No newline at end of file diff --git a/vector/src/androidTest/java/im/vector/app/ui/robot/OnboardingRobot.kt b/vector/src/androidTest/java/im/vector/app/ui/robot/OnboardingRobot.kt index d051488ad7..97e3b281c0 100644 --- a/vector/src/androidTest/java/im/vector/app/ui/robot/OnboardingRobot.kt +++ b/vector/src/androidTest/java/im/vector/app/ui/robot/OnboardingRobot.kt @@ -29,6 +29,7 @@ import com.adevinta.android.barista.interaction.BaristaClickInteractions.clickOn import com.adevinta.android.barista.interaction.BaristaEditTextInteractions.writeTo import im.vector.app.R import im.vector.app.espresso.tools.waitUntilViewVisible +import im.vector.app.features.DefaultVectorFeatures import im.vector.app.waitForView class OnboardingRobot { @@ -57,7 +58,21 @@ class OnboardingRobot { fun createAccount(userId: String, password: String = "password", homeServerUrl: String = "http://10.0.2.2:8080") { initSession(true, userId, password, homeServerUrl) waitUntilViewVisible(withText(R.string.ftue_account_created_congratulations_title)) - clickOn(R.string.ftue_account_created_take_me_home) + if (DefaultVectorFeatures().isOnboardingPersonalizeEnabled()) { + clickOn(R.string.ftue_account_created_personalize) + + waitUntilViewVisible(withText(R.string.ftue_display_name_title)) + writeTo(R.id.displayNameInput, "UI automation") + clickOn(R.string.ftue_personalize_submit) + + waitUntilViewVisible(withText(R.string.ftue_profile_picture_title)) + clickOn(R.string.ftue_personalize_skip_this_step) + + waitUntilViewVisible(withText(R.string.ftue_personalize_complete_title)) + clickOn(R.string.ftue_personalize_lets_go) + } else { + clickOn(R.string.ftue_account_created_take_me_home) + } } fun login(userId: String, password: String = "password", homeServerUrl: String = "http://10.0.2.2:8080") { diff --git a/vector/src/main/res/values/donottranslate.xml b/vector/src/main/res/values/donottranslate.xml index d8e06459c8..2895d72a98 100755 --- a/vector/src/main/res/values/donottranslate.xml +++ b/vector/src/main/res/values/donottranslate.xml @@ -10,22 +10,4 @@ Cut the slack from teams. - Personalize profile - Take me home - Congratulations! - Your account %s has been created. - - Choose a display name - This will be shown when you send messages. - Display Name - You can change this later - - Add a profile picture - You can change this anytime. - Let\'s go - You\'re all set! - Your preferences have been saved. - - Save and continue - Skip this step diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index a276e07b1e..59eb6e2911 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -1863,6 +1863,25 @@ Looking to join an existing server? Connect to server + Personalize profile + Take me home + Congratulations! + Your account %s has been created. + + Choose a display name + This will be shown when you send messages. + Display Name + You can change this later + + Add a profile picture + You can change this anytime. + Let\'s go + You\'re all set! + Your preferences have been saved. + + Save and continue + Skip this step + It\'s your conversation. Own it. Chat with people directly or in groups Keep conversations private with encryption diff --git a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt index 118bf689d2..a682d025b8 100644 --- a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt @@ -73,6 +73,7 @@ class OnboardingViewModelTest { private val fakeAuthenticationService = FakeAuthenticationService() private val fakeRegisterActionHandler = FakeRegisterActionHandler() private val fakeDirectLoginUseCase = FakeDirectLoginUseCase() + private val fakeVectorFeatures = FakeVectorFeatures() lateinit var viewModel: OnboardingViewModel @@ -224,7 +225,8 @@ class OnboardingViewModelTest { } @Test - fun `when registering account, then updates state and emits account created event`() = runTest { + fun `given personalisation enabled, when registering account, then updates state and emits account created event`() = runTest { + fakeVectorFeatures.givenPersonalisationEnabled() givenRegistrationResultFor(A_LOADABLE_REGISTER_ACTION, RegistrationResult.Success(fakeSession)) givenSuccessfullyCreatesAccount(A_HOMESERVER_CAPABILITIES) val test = viewModel.test() @@ -242,7 +244,8 @@ class OnboardingViewModelTest { } @Test - fun `given registration has started and has dummy step to do, when handling action, then ignores other steps and executes dummy`() = runTest { + fun `given personalisation enabled and registration has started and has dummy step to do, when handling action, then ignores other steps and executes dummy`() = runTest { + fakeVectorFeatures.givenPersonalisationEnabled() givenSuccessfulRegistrationForStartAndDummySteps(missingStages = listOf(Stage.Dummy(mandatory = true))) val test = viewModel.test() @@ -384,7 +387,7 @@ class OnboardingViewModelTest { ReAuthHelper(), FakeStringProvider().instance, FakeHomeServerHistoryService(), - FakeVectorFeatures(), + fakeVectorFeatures, FakeAnalyticsTracker(), fakeUriFilenameResolver.instance, fakeRegisterActionHandler.instance, diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeVectorFeatures.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeVectorFeatures.kt index b6e06bcdda..aeabcce7cd 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeVectorFeatures.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeVectorFeatures.kt @@ -16,12 +16,14 @@ package im.vector.app.test.fakes +import im.vector.app.features.DefaultVectorFeatures import im.vector.app.features.VectorFeatures +import io.mockk.every +import io.mockk.spyk -class FakeVectorFeatures : VectorFeatures { - override fun onboardingVariant() = VectorFeatures.OnboardingVariant.FTUE_AUTH - override fun isOnboardingAlreadyHaveAccountSplashEnabled() = true - override fun isOnboardingSplashCarouselEnabled() = true - override fun isOnboardingUseCaseEnabled() = true - override fun isOnboardingPersonalizeEnabled() = true +class FakeVectorFeatures : VectorFeatures by spyk() { + + fun givenPersonalisationEnabled() { + every { isOnboardingPersonalizeEnabled() } returns true + } } From 93876737e7569b1ee3942a72b5d97a845e4f380e Mon Sep 17 00:00:00 2001 From: Maxime Naturel Date: Fri, 18 Mar 2022 16:57:43 +0100 Subject: [PATCH 133/565] Adding forceEnableLiveLocationSharing field in VectorOverride interface --- .../features/debug/features/DebugVectorOverrides.kt | 11 +++++++++++ .../im/vector/app/features/DefaultVectorOverrides.kt | 2 ++ 2 files changed, 13 insertions(+) diff --git a/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorOverrides.kt b/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorOverrides.kt index 5e16182f3c..4bd19d9740 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorOverrides.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorOverrides.kt @@ -33,6 +33,7 @@ private val keyForceDialPadDisplay = booleanPreferencesKey("force_dial_pad_displ private val keyForceLoginFallback = booleanPreferencesKey("force_login_fallback") private val forceCanChangeDisplayName = booleanPreferencesKey("force_can_change_display_name") private val forceCanChangeAvatar = booleanPreferencesKey("force_can_change_avatar") +private val keyForceEnableLiveLocationSharing = booleanPreferencesKey("force_enable_live_location_sharing") class DebugVectorOverrides(private val context: Context) : VectorOverrides { @@ -51,6 +52,10 @@ class DebugVectorOverrides(private val context: Context) : VectorOverrides { ) } + override val forceEnableLiveLocationSharing = context.dataStore.data.map { preferences -> + preferences[keyForceEnableLiveLocationSharing].orFalse() + } + suspend fun setForceDialPadDisplay(force: Boolean) { context.dataStore.edit { settings -> settings[keyForceDialPadDisplay] = force @@ -76,4 +81,10 @@ class DebugVectorOverrides(private val context: Context) : VectorOverrides { } } } + + suspend fun setForceEnableLiveLocationSharing(force: Boolean) { + context.dataStore.edit { settings -> + settings[keyForceEnableLiveLocationSharing] = force + } + } } diff --git a/vector/src/main/java/im/vector/app/features/DefaultVectorOverrides.kt b/vector/src/main/java/im/vector/app/features/DefaultVectorOverrides.kt index daa0d9e0bd..d055244148 100644 --- a/vector/src/main/java/im/vector/app/features/DefaultVectorOverrides.kt +++ b/vector/src/main/java/im/vector/app/features/DefaultVectorOverrides.kt @@ -23,6 +23,7 @@ interface VectorOverrides { val forceDialPad: Flow val forceLoginFallback: Flow val forceHomeserverCapabilities: Flow? + val forceEnableLiveLocationSharing: Flow } data class HomeserverCapabilitiesOverride( @@ -34,4 +35,5 @@ class DefaultVectorOverrides : VectorOverrides { override val forceDialPad = flowOf(false) override val forceLoginFallback = flowOf(false) override val forceHomeserverCapabilities: Flow? = null + override val forceEnableLiveLocationSharing = flowOf(false) } From 424f70bc58f8fcc3d4dfa6708c166c24c042d605 Mon Sep 17 00:00:00 2001 From: Maxime Naturel Date: Fri, 18 Mar 2022 17:27:47 +0100 Subject: [PATCH 134/565] Adding new override setting in the debug private settings --- .../debug/settings/DebugPrivateSettingsFragment.kt | 4 ++++ .../settings/DebugPrivateSettingsViewActions.kt | 1 + .../settings/DebugPrivateSettingsViewModel.kt | 14 ++++++++++++-- .../settings/DebugPrivateSettingsViewState.kt | 3 ++- .../res/layout/fragment_debug_private_settings.xml | 6 ++++++ 5 files changed, 25 insertions(+), 3 deletions(-) diff --git a/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsFragment.kt b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsFragment.kt index 38253fe7c2..735b4079c0 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsFragment.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsFragment.kt @@ -46,6 +46,9 @@ class DebugPrivateSettingsFragment : VectorBaseFragment viewModel.handle(DebugPrivateSettingsViewActions.SetForceLoginFallbackEnabled(isChecked)) } + views.forceEnableLiveLocationSharing.setOnCheckedChangeListener { _, isChecked -> + viewModel.handle(DebugPrivateSettingsViewActions.SetEnableLiveLocationSharing(isChecked)) + } } override fun invalidate() = withState(viewModel) { @@ -57,5 +60,6 @@ class DebugPrivateSettingsFragment : VectorBaseFragment handleSetForceLoginFallbackEnabled(action) is SetDisplayNameCapabilityOverride -> handSetDisplayNameCapabilityOverride(action) is SetAvatarCapabilityOverride -> handSetAvatarCapabilityOverride(action) + is DebugPrivateSettingsViewActions.SetEnableLiveLocationSharing -> handleSetEnableLiveLocationSharingOverride(action) } } @@ -85,17 +89,23 @@ class DebugPrivateSettingsViewModel @AssistedInject constructor( } } - private fun handSetDisplayNameCapabilityOverride(action: SetDisplayNameCapabilityOverride) { + private fun handleSetDisplayNameCapabilityOverride(action: SetDisplayNameCapabilityOverride) { viewModelScope.launch { val forceDisplayName = action.option.toBoolean() debugVectorOverrides.setHomeserverCapabilities { copy(canChangeDisplayName = forceDisplayName) } } } - private fun handSetAvatarCapabilityOverride(action: SetAvatarCapabilityOverride) { + private fun handleSetAvatarCapabilityOverride(action: SetAvatarCapabilityOverride) { viewModelScope.launch { val forceAvatar = action.option.toBoolean() debugVectorOverrides.setHomeserverCapabilities { copy(canChangeAvatar = forceAvatar) } } } + + private fun handleSetEnableLiveLocationSharingOverride(action: DebugPrivateSettingsViewActions.SetEnableLiveLocationSharing) { + viewModelScope.launch { + debugVectorOverrides.setForceEnableLiveLocationSharing(action.force) + } + } } diff --git a/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewState.kt b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewState.kt index 749b11a744..2eff1575c7 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewState.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewState.kt @@ -22,7 +22,8 @@ import im.vector.app.features.debug.settings.OverrideDropdownView.OverrideDropdo data class DebugPrivateSettingsViewState( val dialPadVisible: Boolean = false, val forceLoginFallback: Boolean = false, - val homeserverCapabilityOverrides: HomeserverCapabilityOverrides = HomeserverCapabilityOverrides() + val homeserverCapabilityOverrides: HomeserverCapabilityOverrides = HomeserverCapabilityOverrides(), + val forceEnableLiveLocationSharing: Boolean = false ) : MavericksState data class HomeserverCapabilityOverrides( diff --git a/vector/src/debug/res/layout/fragment_debug_private_settings.xml b/vector/src/debug/res/layout/fragment_debug_private_settings.xml index c42ad68dce..3e89ac9120 100644 --- a/vector/src/debug/res/layout/fragment_debug_private_settings.xml +++ b/vector/src/debug/res/layout/fragment_debug_private_settings.xml @@ -49,6 +49,12 @@ android:layout_marginEnd="16dp" android:layout_marginBottom="4dp" /> + + From cfce144b61fd64fa57de91e80cf1f894606a93ab Mon Sep 17 00:00:00 2001 From: Maxime Naturel Date: Fri, 18 Mar 2022 17:28:18 +0100 Subject: [PATCH 135/565] Using the override setting in the LocationSharing screen --- .../app/features/location/LocationSharingFragment.kt | 3 +-- .../features/location/LocationSharingViewModel.kt | 12 +++++++++++- .../features/location/LocationSharingViewState.kt | 5 ++++- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt index b779b50c8b..ea350b00c3 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt @@ -29,7 +29,6 @@ import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.mapbox.mapboxsdk.maps.MapView -import im.vector.app.BuildConfig import im.vector.app.R import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.utils.PERMISSIONS_FOR_BACKGROUND_LOCATION_SHARING @@ -243,7 +242,7 @@ class LocationSharingFragment @Inject constructor( // first, update the options view val options: Set = when (state.areTargetAndUserLocationEqual) { true -> { - if (BuildConfig.ENABLE_LIVE_LOCATION_SHARING) { + if (state.isLiveLocationSharingEnabled) { setOf(LocationSharingOption.USER_CURRENT, LocationSharingOption.USER_LIVE) } else { setOf(LocationSharingOption.USER_CURRENT) diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt index dfa936dcaa..186c5c46b3 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt @@ -21,9 +21,11 @@ import com.airbnb.mvrx.MavericksViewModelFactory import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject +import im.vector.app.BuildConfig import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.platform.VectorViewModel +import im.vector.app.features.VectorOverrides import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvider import im.vector.app.features.location.domain.usecase.CompareLocationsUseCase import kotlinx.coroutines.flow.MutableSharedFlow @@ -48,7 +50,8 @@ class LocationSharingViewModel @AssistedInject constructor( private val locationTracker: LocationTracker, private val locationPinProvider: LocationPinProvider, private val session: Session, - private val compareLocationsUseCase: CompareLocationsUseCase + private val compareLocationsUseCase: CompareLocationsUseCase, + private val vectorOverrides: VectorOverrides ) : VectorViewModel(initialState), LocationTracker.Callback { private val room = session.getRoom(initialState.roomId)!! @@ -68,6 +71,7 @@ class LocationSharingViewModel @AssistedInject constructor( setUserItem() updatePin() compareTargetAndUserLocation() + observeVectorOverrides() } private fun setUserItem() { @@ -109,6 +113,12 @@ class LocationSharingViewModel @AssistedInject constructor( ?.let { userLocation -> compareLocationsUseCase.execute(userLocation, targetLocation) } } + private fun observeVectorOverrides() { + vectorOverrides.forceEnableLiveLocationSharing.setOnEach { forceLiveLocation -> + copy(isLiveLocationSharingEnabled = forceLiveLocation || BuildConfig.ENABLE_LIVE_LOCATION_SHARING) + } + } + override fun onCleared() { super.onCleared() locationTracker.removeCallback(this) diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt index ee5ba402e2..64039b00c4 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt @@ -19,6 +19,7 @@ package im.vector.app.features.location import android.graphics.drawable.Drawable import androidx.annotation.StringRes import com.airbnb.mvrx.MavericksState +import im.vector.app.BuildConfig import im.vector.app.R import org.matrix.android.sdk.api.extensions.orTrue import org.matrix.android.sdk.api.util.MatrixItem @@ -31,6 +32,7 @@ enum class LocationSharingMode(@StringRes val titleRes: Int) { data class LocationSharingViewState( val roomId: String, val mode: LocationSharingMode, + val isLiveLocationSharingEnabled: Boolean, val userItem: MatrixItem.UserItem? = null, val areTargetAndUserLocationEqual: Boolean? = null, val lastKnownUserLocation: LocationData? = null, @@ -39,7 +41,8 @@ data class LocationSharingViewState( constructor(locationSharingArgs: LocationSharingArgs) : this( roomId = locationSharingArgs.roomId, - mode = locationSharingArgs.mode + mode = locationSharingArgs.mode, + isLiveLocationSharingEnabled = BuildConfig.ENABLE_LIVE_LOCATION_SHARING ) } From e92a05abe7774792101bb551b434e0294b571eef Mon Sep 17 00:00:00 2001 From: Maxime Naturel Date: Fri, 18 Mar 2022 17:30:11 +0100 Subject: [PATCH 136/565] Adding changelog entry --- changelog.d/5581.misc | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/5581.misc diff --git a/changelog.d/5581.misc b/changelog.d/5581.misc new file mode 100644 index 0000000000..3191c5eae8 --- /dev/null +++ b/changelog.d/5581.misc @@ -0,0 +1 @@ +Live location sharing: adding way to override feature activation in debug From 78b2ccb2b50dfcdf8a350f7a8d5626577ff56aac Mon Sep 17 00:00:00 2001 From: Maxime Naturel Date: Mon, 28 Mar 2022 17:57:04 +0200 Subject: [PATCH 137/565] Using VectorFeatures instead of VectorOverrides --- .../debug/features/DebugFeaturesStateFactory.kt | 5 +++++ .../features/debug/features/DebugVectorFeatures.kt | 4 ++++ .../java/im/vector/app/features/VectorFeatures.kt | 2 ++ .../features/location/LocationSharingViewModel.kt | 13 ++++++------- 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/vector/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt b/vector/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt index 8702c8d966..60024d4c19 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt @@ -54,6 +54,11 @@ class DebugFeaturesStateFactory @Inject constructor( key = DebugFeatureKeys.onboardingPersonalize, factory = VectorFeatures::isOnboardingPersonalizeEnabled ), + createBooleanFeature( + label = "Live location sharing", + key = DebugFeatureKeys.liveLocationSharing, + factory = VectorFeatures::isLiveLocationEnabled + ), )) } diff --git a/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt b/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt index f93e3d96fb..ec3de1408a 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt @@ -54,6 +54,9 @@ class DebugVectorFeatures( override fun isOnboardingPersonalizeEnabled(): Boolean = read(DebugFeatureKeys.onboardingPersonalize) ?: vectorFeatures.isOnboardingPersonalizeEnabled() + override fun isLiveLocationEnabled(): Boolean = read(DebugFeatureKeys.liveLocationSharing) + ?: vectorFeatures.isLiveLocationEnabled() + fun override(value: T?, key: Preferences.Key) = updatePreferences { if (value == null) { it.remove(key) @@ -106,4 +109,5 @@ object DebugFeatureKeys { val onboardingSplashCarousel = booleanPreferencesKey("onboarding-splash-carousel") val onboardingUseCase = booleanPreferencesKey("onbboarding-splash-carousel") val onboardingPersonalize = booleanPreferencesKey("onbboarding-personalize") + val liveLocationSharing = booleanPreferencesKey("live-location-sharing") } diff --git a/vector/src/main/java/im/vector/app/features/VectorFeatures.kt b/vector/src/main/java/im/vector/app/features/VectorFeatures.kt index a19b3d9026..52ad9242f4 100644 --- a/vector/src/main/java/im/vector/app/features/VectorFeatures.kt +++ b/vector/src/main/java/im/vector/app/features/VectorFeatures.kt @@ -25,6 +25,7 @@ interface VectorFeatures { fun isOnboardingSplashCarouselEnabled(): Boolean fun isOnboardingUseCaseEnabled(): Boolean fun isOnboardingPersonalizeEnabled(): Boolean + fun isLiveLocationEnabled(): Boolean enum class OnboardingVariant { LEGACY, @@ -39,4 +40,5 @@ class DefaultVectorFeatures : VectorFeatures { override fun isOnboardingSplashCarouselEnabled() = true override fun isOnboardingUseCaseEnabled() = true override fun isOnboardingPersonalizeEnabled() = false + override fun isLiveLocationEnabled(): Boolean = BuildConfig.ENABLE_LIVE_LOCATION_SHARING } diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt index 186c5c46b3..37ccd344b3 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt @@ -21,11 +21,10 @@ import com.airbnb.mvrx.MavericksViewModelFactory import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject -import im.vector.app.BuildConfig import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.platform.VectorViewModel -import im.vector.app.features.VectorOverrides +import im.vector.app.features.VectorFeatures import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvider import im.vector.app.features.location.domain.usecase.CompareLocationsUseCase import kotlinx.coroutines.flow.MutableSharedFlow @@ -51,7 +50,7 @@ class LocationSharingViewModel @AssistedInject constructor( private val locationPinProvider: LocationPinProvider, private val session: Session, private val compareLocationsUseCase: CompareLocationsUseCase, - private val vectorOverrides: VectorOverrides + private val vectorFeatures: VectorFeatures, ) : VectorViewModel(initialState), LocationTracker.Callback { private val room = session.getRoom(initialState.roomId)!! @@ -71,7 +70,7 @@ class LocationSharingViewModel @AssistedInject constructor( setUserItem() updatePin() compareTargetAndUserLocation() - observeVectorOverrides() + checkVectorFeatures() } private fun setUserItem() { @@ -113,9 +112,9 @@ class LocationSharingViewModel @AssistedInject constructor( ?.let { userLocation -> compareLocationsUseCase.execute(userLocation, targetLocation) } } - private fun observeVectorOverrides() { - vectorOverrides.forceEnableLiveLocationSharing.setOnEach { forceLiveLocation -> - copy(isLiveLocationSharingEnabled = forceLiveLocation || BuildConfig.ENABLE_LIVE_LOCATION_SHARING) + private fun checkVectorFeatures() { + setState { + copy(isLiveLocationSharingEnabled = vectorFeatures.isLiveLocationEnabled()) } } From 90c53b9dd57d195ac13c759c542b81f74b9fafff Mon Sep 17 00:00:00 2001 From: Maxime Naturel Date: Mon, 28 Mar 2022 18:02:26 +0200 Subject: [PATCH 138/565] Remove non necessary DebugOverrides --- .../features/debug/features/DebugVectorOverrides.kt | 11 ----------- .../debug/settings/DebugPrivateSettingsFragment.kt | 4 ---- .../debug/settings/DebugPrivateSettingsViewActions.kt | 1 - .../debug/settings/DebugPrivateSettingsViewModel.kt | 10 ---------- .../debug/settings/DebugPrivateSettingsViewState.kt | 3 +-- .../res/layout/fragment_debug_private_settings.xml | 6 ------ .../im/vector/app/features/DefaultVectorOverrides.kt | 2 -- 7 files changed, 1 insertion(+), 36 deletions(-) diff --git a/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorOverrides.kt b/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorOverrides.kt index 4bd19d9740..5e16182f3c 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorOverrides.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorOverrides.kt @@ -33,7 +33,6 @@ private val keyForceDialPadDisplay = booleanPreferencesKey("force_dial_pad_displ private val keyForceLoginFallback = booleanPreferencesKey("force_login_fallback") private val forceCanChangeDisplayName = booleanPreferencesKey("force_can_change_display_name") private val forceCanChangeAvatar = booleanPreferencesKey("force_can_change_avatar") -private val keyForceEnableLiveLocationSharing = booleanPreferencesKey("force_enable_live_location_sharing") class DebugVectorOverrides(private val context: Context) : VectorOverrides { @@ -52,10 +51,6 @@ class DebugVectorOverrides(private val context: Context) : VectorOverrides { ) } - override val forceEnableLiveLocationSharing = context.dataStore.data.map { preferences -> - preferences[keyForceEnableLiveLocationSharing].orFalse() - } - suspend fun setForceDialPadDisplay(force: Boolean) { context.dataStore.edit { settings -> settings[keyForceDialPadDisplay] = force @@ -81,10 +76,4 @@ class DebugVectorOverrides(private val context: Context) : VectorOverrides { } } } - - suspend fun setForceEnableLiveLocationSharing(force: Boolean) { - context.dataStore.edit { settings -> - settings[keyForceEnableLiveLocationSharing] = force - } - } } diff --git a/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsFragment.kt b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsFragment.kt index 735b4079c0..38253fe7c2 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsFragment.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsFragment.kt @@ -46,9 +46,6 @@ class DebugPrivateSettingsFragment : VectorBaseFragment viewModel.handle(DebugPrivateSettingsViewActions.SetForceLoginFallbackEnabled(isChecked)) } - views.forceEnableLiveLocationSharing.setOnCheckedChangeListener { _, isChecked -> - viewModel.handle(DebugPrivateSettingsViewActions.SetEnableLiveLocationSharing(isChecked)) - } } override fun invalidate() = withState(viewModel) { @@ -60,6 +57,5 @@ class DebugPrivateSettingsFragment : VectorBaseFragment handleSetForceLoginFallbackEnabled(action) is SetDisplayNameCapabilityOverride -> handSetDisplayNameCapabilityOverride(action) is SetAvatarCapabilityOverride -> handSetAvatarCapabilityOverride(action) - is DebugPrivateSettingsViewActions.SetEnableLiveLocationSharing -> handleSetEnableLiveLocationSharingOverride(action) } } @@ -102,10 +98,4 @@ class DebugPrivateSettingsViewModel @AssistedInject constructor( debugVectorOverrides.setHomeserverCapabilities { copy(canChangeAvatar = forceAvatar) } } } - - private fun handleSetEnableLiveLocationSharingOverride(action: DebugPrivateSettingsViewActions.SetEnableLiveLocationSharing) { - viewModelScope.launch { - debugVectorOverrides.setForceEnableLiveLocationSharing(action.force) - } - } } diff --git a/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewState.kt b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewState.kt index 2eff1575c7..749b11a744 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewState.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewState.kt @@ -22,8 +22,7 @@ import im.vector.app.features.debug.settings.OverrideDropdownView.OverrideDropdo data class DebugPrivateSettingsViewState( val dialPadVisible: Boolean = false, val forceLoginFallback: Boolean = false, - val homeserverCapabilityOverrides: HomeserverCapabilityOverrides = HomeserverCapabilityOverrides(), - val forceEnableLiveLocationSharing: Boolean = false + val homeserverCapabilityOverrides: HomeserverCapabilityOverrides = HomeserverCapabilityOverrides() ) : MavericksState data class HomeserverCapabilityOverrides( diff --git a/vector/src/debug/res/layout/fragment_debug_private_settings.xml b/vector/src/debug/res/layout/fragment_debug_private_settings.xml index 3e89ac9120..c42ad68dce 100644 --- a/vector/src/debug/res/layout/fragment_debug_private_settings.xml +++ b/vector/src/debug/res/layout/fragment_debug_private_settings.xml @@ -49,12 +49,6 @@ android:layout_marginEnd="16dp" android:layout_marginBottom="4dp" /> - - diff --git a/vector/src/main/java/im/vector/app/features/DefaultVectorOverrides.kt b/vector/src/main/java/im/vector/app/features/DefaultVectorOverrides.kt index d055244148..daa0d9e0bd 100644 --- a/vector/src/main/java/im/vector/app/features/DefaultVectorOverrides.kt +++ b/vector/src/main/java/im/vector/app/features/DefaultVectorOverrides.kt @@ -23,7 +23,6 @@ interface VectorOverrides { val forceDialPad: Flow val forceLoginFallback: Flow val forceHomeserverCapabilities: Flow? - val forceEnableLiveLocationSharing: Flow } data class HomeserverCapabilitiesOverride( @@ -35,5 +34,4 @@ class DefaultVectorOverrides : VectorOverrides { override val forceDialPad = flowOf(false) override val forceLoginFallback = flowOf(false) override val forceHomeserverCapabilities: Flow? = null - override val forceEnableLiveLocationSharing = flowOf(false) } From 2b41096518269db2f90f60d2c35aaccb8789bf16 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Tue, 29 Mar 2022 16:16:53 +0200 Subject: [PATCH 139/565] Fixing wrong method name calls --- .../features/debug/settings/DebugPrivateSettingsViewModel.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewModel.kt b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewModel.kt index 8db030c13d..1d77d031af 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewModel.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewModel.kt @@ -68,8 +68,8 @@ class DebugPrivateSettingsViewModel @AssistedInject constructor( when (action) { is DebugPrivateSettingsViewActions.SetDialPadVisibility -> handleSetDialPadVisibility(action) is DebugPrivateSettingsViewActions.SetForceLoginFallbackEnabled -> handleSetForceLoginFallbackEnabled(action) - is SetDisplayNameCapabilityOverride -> handSetDisplayNameCapabilityOverride(action) - is SetAvatarCapabilityOverride -> handSetAvatarCapabilityOverride(action) + is SetDisplayNameCapabilityOverride -> handleSetDisplayNameCapabilityOverride(action) + is SetAvatarCapabilityOverride -> handleSetAvatarCapabilityOverride(action) } } From f4ef4c2e617ac0b6e2cba813955a5695428570bd Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 30 Mar 2022 09:33:43 +0200 Subject: [PATCH 140/565] Fixing attempt of unit tests --- .../vector/app/features/onboarding/OnboardingViewModelTest.kt | 2 ++ .../test/java/im/vector/app/test/fakes/FakeVectorFeatures.kt | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt index a682d025b8..a4983ba54a 100644 --- a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt @@ -227,6 +227,7 @@ class OnboardingViewModelTest { @Test fun `given personalisation enabled, when registering account, then updates state and emits account created event`() = runTest { fakeVectorFeatures.givenPersonalisationEnabled() + fakeVectorFeatures.givenLiveLocationSharingEnabled() givenRegistrationResultFor(A_LOADABLE_REGISTER_ACTION, RegistrationResult.Success(fakeSession)) givenSuccessfullyCreatesAccount(A_HOMESERVER_CAPABILITIES) val test = viewModel.test() @@ -246,6 +247,7 @@ class OnboardingViewModelTest { @Test fun `given personalisation enabled and registration has started and has dummy step to do, when handling action, then ignores other steps and executes dummy`() = runTest { fakeVectorFeatures.givenPersonalisationEnabled() + fakeVectorFeatures.givenLiveLocationSharingEnabled() givenSuccessfulRegistrationForStartAndDummySteps(missingStages = listOf(Stage.Dummy(mandatory = true))) val test = viewModel.test() diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeVectorFeatures.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeVectorFeatures.kt index aeabcce7cd..680dc520ee 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeVectorFeatures.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeVectorFeatures.kt @@ -26,4 +26,8 @@ class FakeVectorFeatures : VectorFeatures by spyk() { fun givenPersonalisationEnabled() { every { isOnboardingPersonalizeEnabled() } returns true } + + fun givenLiveLocationSharingEnabled() { + every { isLiveLocationEnabled() } returns true + } } From 9e3dc4c10f489f8d2406396a70796e71fd1994fd Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 30 Mar 2022 10:59:47 +0200 Subject: [PATCH 141/565] Fixing unit tests --- .../vector/app/features/onboarding/OnboardingViewModelTest.kt | 2 -- .../test/java/im/vector/app/test/fakes/FakeVectorFeatures.kt | 4 ---- 2 files changed, 6 deletions(-) diff --git a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt index a4983ba54a..a682d025b8 100644 --- a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt @@ -227,7 +227,6 @@ class OnboardingViewModelTest { @Test fun `given personalisation enabled, when registering account, then updates state and emits account created event`() = runTest { fakeVectorFeatures.givenPersonalisationEnabled() - fakeVectorFeatures.givenLiveLocationSharingEnabled() givenRegistrationResultFor(A_LOADABLE_REGISTER_ACTION, RegistrationResult.Success(fakeSession)) givenSuccessfullyCreatesAccount(A_HOMESERVER_CAPABILITIES) val test = viewModel.test() @@ -247,7 +246,6 @@ class OnboardingViewModelTest { @Test fun `given personalisation enabled and registration has started and has dummy step to do, when handling action, then ignores other steps and executes dummy`() = runTest { fakeVectorFeatures.givenPersonalisationEnabled() - fakeVectorFeatures.givenLiveLocationSharingEnabled() givenSuccessfulRegistrationForStartAndDummySteps(missingStages = listOf(Stage.Dummy(mandatory = true))) val test = viewModel.test() diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeVectorFeatures.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeVectorFeatures.kt index 680dc520ee..aeabcce7cd 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeVectorFeatures.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeVectorFeatures.kt @@ -26,8 +26,4 @@ class FakeVectorFeatures : VectorFeatures by spyk() { fun givenPersonalisationEnabled() { every { isOnboardingPersonalizeEnabled() } returns true } - - fun givenLiveLocationSharingEnabled() { - every { isLiveLocationEnabled() } returns true - } } From 0d59a317882c4ebf3166b7a64b966966ff29be16 Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Wed, 30 Mar 2022 12:32:08 +0300 Subject: [PATCH 142/565] Add changelog --- changelog.d/5562.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/5562.bugfix diff --git a/changelog.d/5562.bugfix b/changelog.d/5562.bugfix new file mode 100644 index 0000000000..02148e58fa --- /dev/null +++ b/changelog.d/5562.bugfix @@ -0,0 +1 @@ +Add loader in thread list \ No newline at end of file From 29c7ea11bd799ab1021131c88048bbcb654e5de3 Mon Sep 17 00:00:00 2001 From: cketti Date: Wed, 30 Mar 2022 15:38:40 +0200 Subject: [PATCH 143/565] Create extension function `Context.safeOpenOutputStream` --- .../main/java/im/vector/app/core/extensions/Context.kt | 9 +++++++++ .../im/vector/app/features/crypto/keys/KeysExporter.kt | 3 ++- .../keysbackup/setup/KeysBackupSetupStep3Fragment.kt | 3 ++- .../crypto/recover/BootstrapSaveRecoveryKeyFragment.kt | 3 ++- .../features/settings/devtools/KeyRequestsFragment.kt | 3 ++- .../vector/app/features/crypto/keys/KeysExporterTest.kt | 8 ++++---- .../test/java/im/vector/app/test/fakes/FakeContext.kt | 8 ++++---- 7 files changed, 25 insertions(+), 12 deletions(-) diff --git a/vector/src/main/java/im/vector/app/core/extensions/Context.kt b/vector/src/main/java/im/vector/app/core/extensions/Context.kt index b1e24c9502..0f785e43a3 100644 --- a/vector/src/main/java/im/vector/app/core/extensions/Context.kt +++ b/vector/src/main/java/im/vector/app/core/extensions/Context.kt @@ -18,6 +18,7 @@ package im.vector.app.core.extensions import android.content.Context import android.graphics.drawable.Drawable +import android.net.Uri import android.text.Spannable import android.text.SpannableString import android.text.style.ImageSpan @@ -31,6 +32,7 @@ import androidx.datastore.preferences.core.Preferences import dagger.hilt.EntryPoints import im.vector.app.core.datastore.dataStoreProvider import im.vector.app.core.di.SingletonEntryPoint +import java.io.OutputStream import kotlin.math.roundToInt fun Context.singletonEntryPoint(): SingletonEntryPoint { @@ -68,3 +70,10 @@ private fun Float.toAndroidAlpha(): Int { } val Context.dataStoreProvider: (String) -> DataStore by dataStoreProvider() + +/** + * Open Uri in truncate mode to make sure we don't partially overwrite content when we get passed a Uri to an existing file. + */ +fun Context.safeOpenOutputStream(uri: Uri): OutputStream? { + return contentResolver.openOutputStream(uri, "wt") +} diff --git a/vector/src/main/java/im/vector/app/features/crypto/keys/KeysExporter.kt b/vector/src/main/java/im/vector/app/features/crypto/keys/KeysExporter.kt index 27d8d4842a..f40f126d2c 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/keys/KeysExporter.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/keys/KeysExporter.kt @@ -19,6 +19,7 @@ package im.vector.app.features.crypto.keys import android.content.Context import android.net.Uri import im.vector.app.core.dispatchers.CoroutineDispatchers +import im.vector.app.core.extensions.safeOpenOutputStream import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.session.Session import javax.inject.Inject @@ -34,7 +35,7 @@ class KeysExporter @Inject constructor( suspend fun export(password: String, uri: Uri) { withContext(dispatchers.io) { val data = session.cryptoService().exportRoomKeys(password) - context.contentResolver.openOutputStream(uri, "wt") + context.safeOpenOutputStream(uri) ?.use { it.write(data) } ?: throw IllegalStateException("Unable to open file for writing") verifyExportedKeysOutputFileSize(uri, expectedSize = data.size.toLong()) diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupStep3Fragment.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupStep3Fragment.kt index 42ff4ac183..e5d7ade3ce 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupStep3Fragment.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupStep3Fragment.kt @@ -30,6 +30,7 @@ import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.dialog.MaterialAlertDialogBuilder import im.vector.app.R import im.vector.app.core.extensions.registerStartForActivityResult +import im.vector.app.core.extensions.safeOpenOutputStream import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.utils.LiveEvent import im.vector.app.core.utils.copyToClipboard @@ -165,7 +166,7 @@ class KeysBackupSetupStep3Fragment @Inject constructor() : VectorBaseFragment os.write(data.toByteArray()) os.flush() diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSaveRecoveryKeyFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSaveRecoveryKeyFragment.kt index 746ed48c94..efabae2f3a 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSaveRecoveryKeyFragment.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSaveRecoveryKeyFragment.kt @@ -29,6 +29,7 @@ import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.withState import im.vector.app.R import im.vector.app.core.extensions.registerStartForActivityResult +import im.vector.app.core.extensions.safeOpenOutputStream import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.resources.ColorProvider import im.vector.app.core.utils.startSharePlainTextIntent @@ -81,7 +82,7 @@ class BootstrapSaveRecoveryKeyFragment @Inject constructor( val uri = activityResult.data?.data ?: return@registerStartForActivityResult lifecycleScope.launch(Dispatchers.IO) { try { - sharedViewModel.handle(BootstrapActions.SaveKeyToUri(requireContext().contentResolver!!.openOutputStream(uri, "wt")!!)) + sharedViewModel.handle(BootstrapActions.SaveKeyToUri(requireContext().safeOpenOutputStream(uri)!!)) } catch (failure: Throwable) { sharedViewModel.handle(BootstrapActions.SaveReqFailed) } diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestsFragment.kt index 6748fec1bc..cef68c01c1 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestsFragment.kt @@ -34,6 +34,7 @@ import com.airbnb.mvrx.withState import com.google.android.material.tabs.TabLayoutMediator import im.vector.app.R import im.vector.app.core.extensions.registerStartForActivityResult +import im.vector.app.core.extensions.safeOpenOutputStream import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.utils.selectTxtFileToWrite import im.vector.app.databinding.FragmentDevtoolKeyrequestsBinding @@ -106,7 +107,7 @@ class KeyRequestsFragment @Inject constructor() : VectorBaseFragment { tryOrNull { - requireContext().contentResolver?.openOutputStream(it.uri, "wt") + requireContext().safeOpenOutputStream(it.uri) ?.use { os -> os.write(it.raw.toByteArray()) } } } diff --git a/vector/src/test/java/im/vector/app/features/crypto/keys/KeysExporterTest.kt b/vector/src/test/java/im/vector/app/features/crypto/keys/KeysExporterTest.kt index 3bec3fad88..3cd797a7b1 100644 --- a/vector/src/test/java/im/vector/app/features/crypto/keys/KeysExporterTest.kt +++ b/vector/src/test/java/im/vector/app/features/crypto/keys/KeysExporterTest.kt @@ -53,7 +53,7 @@ class KeysExporterTest { @Test fun `when exporting then writes exported keys to context output stream`() { givenFileDescriptorWithSize(size = A_ROOM_KEYS_EXPORT.size.toLong()) - val outputStream = context.givenOutputStreamFor(A_URI, mode = "wt") + val outputStream = context.givenSafeOutputStreamFor(A_URI) runTest { keysExporter.export(A_PASSWORD, A_URI) } @@ -63,7 +63,7 @@ class KeysExporterTest { @Test fun `given different file size returned for export when exporting then throws UnexpectedExportKeysFileSizeException`() { givenFileDescriptorWithSize(size = 110) - context.givenOutputStreamFor(A_URI, mode = "wt") + context.givenSafeOutputStreamFor(A_URI) assertFailsWith { runTest { keysExporter.export(A_PASSWORD, A_URI) } @@ -72,7 +72,7 @@ class KeysExporterTest { @Test fun `given output stream is unavailable for exporting to when exporting then throws IllegalStateException`() { - context.givenMissingOutputStreamFor(A_URI, mode = "wt") + context.givenMissingSafeOutputStreamFor(A_URI) assertFailsWith(message = "Unable to open file for writing") { runTest { keysExporter.export(A_PASSWORD, A_URI) } @@ -82,7 +82,7 @@ class KeysExporterTest { @Test fun `given exported file is missing after export when exporting then throws IllegalStateException`() { context.givenFileDescriptor(A_URI, mode = "r") { null } - context.givenOutputStreamFor(A_URI, mode = "wt") + context.givenSafeOutputStreamFor(A_URI) assertFailsWith(message = "Exported file not found") { runTest { keysExporter.export(A_PASSWORD, A_URI) } diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeContext.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeContext.kt index de1a7956b8..2a50c34ca3 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeContext.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeContext.kt @@ -39,13 +39,13 @@ class FakeContext( every { contentResolver.openFileDescriptor(uri, mode, null) } returns fileDescriptor } - fun givenOutputStreamFor(uri: Uri, mode: String): OutputStream { + fun givenSafeOutputStreamFor(uri: Uri): OutputStream { val outputStream = mockk(relaxed = true) - every { contentResolver.openOutputStream(uri, mode) } returns outputStream + every { contentResolver.openOutputStream(uri, "wt") } returns outputStream return outputStream } - fun givenMissingOutputStreamFor(uri: Uri, mode: String) { - every { contentResolver.openOutputStream(uri, mode) } returns null + fun givenMissingSafeOutputStreamFor(uri: Uri) { + every { contentResolver.openOutputStream(uri, "wt") } returns null } } From c44c637fbaa5be5329d480baa291a0a144272025 Mon Sep 17 00:00:00 2001 From: SpiritCroc Date: Wed, 30 Mar 2022 17:35:51 +0200 Subject: [PATCH 144/565] Also give up loading event for M_FORBIDDEN --- .../sdk/internal/session/room/timeline/LoadTimelineStrategy.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt index bb565a8d6b..42973797f3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt @@ -196,7 +196,7 @@ internal class LoadTimelineStrategy( getContextLatch?.await() getContextLatch = null } catch (failure: Throwable) { - if (failure is Failure.ServerError && failure.error.code == MatrixError.M_NOT_FOUND) { + if (failure is Failure.ServerError && failure.error.code in listOf(MatrixError.M_NOT_FOUND, MatrixError.M_FORBIDDEN)) { // This failure is likely permanent, so handle in DefaultTimeline to restart without eventId throw failure } From 558a72e58fbb80dd3cab00824189858bf6adb55a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 31 Mar 2022 09:58:15 +0200 Subject: [PATCH 145/565] Create a documentation about PR (#5401) Create documentation about our PR process --- CONTRIBUTING.md | 4 +- docs/pull_request.md | 236 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 239 insertions(+), 1 deletion(-) create mode 100644 docs/pull_request.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2512052953..f3739be08d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,7 @@ Please read https://github.com/matrix-org/synapse/blob/master/CONTRIBUTING.md -Android support can be found in this [![Element Android Matrix room #element-android:matrix.org](https://img.shields.io/matrix/element-android:matrix.org.svg?label=%23element-android:matrix.org&logo=matrix&server_fqdn=matrix.org)](https://matrix.to/#/#element-android:matrix.org) room. +Element Android support can be found in this room: [![Element Android Matrix room #element-android:matrix.org](https://img.shields.io/matrix/element-android:matrix.org.svg?label=%23element-android:matrix.org&logo=matrix&server_fqdn=matrix.org)](https://matrix.to/#/#element-android:matrix.org). # Specific rules for Matrix Android projects @@ -44,6 +44,8 @@ If you want to fix an issue in other languages, or add a missing translation, or ## I want to submit a PR to fix an issue +Please have a look in the [dedicated documentation](./docs/pull_request.md) about pull request. + Please check if a corresponding issue exists. If yes, please let us know in a comment that you're working on it. If an issue does not exist yet, it may be relevant to open a new issue and let us know that you're implementing it. diff --git a/docs/pull_request.md b/docs/pull_request.md new file mode 100644 index 0000000000..b4dd0bd209 --- /dev/null +++ b/docs/pull_request.md @@ -0,0 +1,236 @@ +# Pull requests + +## Introduction + +This document gives some clue about how to efficiently manage Pull Requests (PR). This document is a first draft and may be improved later. + +## Who should read this document? + +Every pull request reviewers, but also probably every ones who submit PRs. + +## Submitting PR + +### Who can submit pull requests? + +Basically every one who wants to contribute to the project! But there are some rules to follow. + +#### Humans + +People with write access to the project can directly clone the project, push their branches and create PR. + +External contributors must first fork the project and create PR to the mainline from there. + +##### Draft PR? + +Draft PR can be created when the submitter does not expect the PR to be reviewed and merged yet. It can be useful to publicly show the work, or to do a self-review first. + +Draft PR can also be created when it depends on other un-merged PR. + +In any case, it is better to explicitly declare in the description why the PR is a draft PR. + +Also, draft PR should not stay indefinitely in this state. It may be removed if it is the case and the submitter does not update it after a few days. + +##### PR Review Assignment + +We use automatic assignment for PR reviews. A PR is automatically routed by GitHub to a team member using the round robin algorithm. The process is the following: + +- The PR creator assigns the [element-android](https://github.com/orgs/vector-im/teams/element-android) team as a reviewer. They can skip this process and assign directly a specific member if they think they should take a look at it. +- GitHub automatically assigns one reviewer. If the chosen reviewer is not available (holiday, etc.), remove them and set again the team, GitHub will select another reviewer. +- The reviewer gets a notification to make the review: they review the code following the good practice (see the rest of this document). +- After making their own review, if they feel not confident enough, they can ask another person for a full review, or they can tag someone within a PR comment to check specific lines. + +For PRs coming from the community, the issue wrangler can assign either the team [element-android](https://github.com/orgs/vector-im/teams/element-android) or any member directly. + +##### PR review time + +As a PR submitter, you deserve a quick review. As a reviewer, you should do your best to unblock others. + +Some tips to achieve it: + +- Set up your GH notifications correctly +- Check your pulls page: [https://github.com/pulls](https://github.com/pulls) +- Check your pending assigned PRs before starting or resuming your day to day tasks + +It is hard to define a deadline for a review. It depends on the PR size and the complexity. Let's start with a goal of 24h (working day!) for a PR smaller than 500 lines. If bigger, the submitter and the reviewer should discuss. + +After this time, the submitter can ping the reviewer to get a status of the review. + +##### Re-request PR review + +Once all the remarks have been handled, it's possible to re-request a review from the (same) reviewer to let them know that the PR has been updated the PR is ready to be reviewed again. Use the double arrow next to the reviewer name to do that. + +##### When create split PR? + +To implement big new feature, it may be efficient to split the work into several smaller and scoped PRs. They will be easier to review, and they can be merged on `develop` faster. + +Big PR can take time, and there is a risk of future merge conflict. + +Feature flag can be used to avoid half implemented feature to be available in the application. + +That said, splitting into several PRs should not have the side effect to have more review to do, for instance if some code is added, then finally removed. + +##### Avoid fixing other unrelated issue in a big PR + +Each PR should focus on a single task. If other issues may be fixed when working in the area of it, it's preferable to open a dedicated PR. + +It will have the advantage to be reviewed and merged faster, and not interfere with the main PR. + +It's also applicable for code rework (such as renaming for instance), or code formatting. Sometimes, it is more efficient to extract that work to a dedicated PR, and rebase your branch once this "rework" PR has been merged. + +#### Bots + +Some bots can create PR, but they still have to be reviewed by the team + +##### Dependabot + +Dependabot is a tool which maintain all our external dependencies up to date. A dedicated PR is created for each new available release for one of our external dependency.Dependabot + +To review such PR, you have to + - **IMPORTANT** check the diff files (as always). + - Check the release note. Some existing bugs in Element project may be fixed by the upgrade + - Make sure that the CI is happy + - If the code does not compile (API break for instance), you have to checkout the branch and push new commits + - Do some smoke test, depending of the library which has been upgraded + +For some reason dependabot sometimes does not upgrade some dependencies. In this case, and when detected, the upgrade has to be done manually. + +##### Gradle wrapper + +`Update Gradle Wrapper` is a tool which can create PR to upgrade our gradle.properties file. +Review such PR is the same recipe than for PR from Dependabot + +##### Sync analytics plan + +This tools imports any update in the analytics plan. See instruction in the PR itself to handle it. +More info can be found in the file [analytics.md] + +## Reviewing PR + +### Who can review pull requests? + +As an open source project, every one can review each others PR. Of course an approval from internal developer is mandatory for a PR to be merged. +But comment in PR from the community are always appreciated! + +### What to have in mind when reviewing a PR + +1. User experience: is the UX and UI correct? You will probably be the second person to test the new thing, the first one is the developer. +2. Developer experience: does the code look nice and decoupled? No big functions, new classes added to the right module, etc. +3. Code maintenance. A bit similar to point 2. Tricky code must be documented for instance +4. Fork consideration. Will configuration of forks be easy? Some documentation may help in some cases. +5. We are building long term products. "Quick and dirty" code must be avoided. +6. The PR includes new tests for the added code, updated test for the existing code +7. All PRs from external contributors **MUST** include a sign-off. It's in the checklist, and sometimes it's checked by the submitter, but there is actually no sign-off. In this case, ask nicely for a sign-off and request changes (do not approve the PR, even if everything else is fine). + +### Rules + +#### Check the form + +##### PR title + +PR title should describe in one line what's brought by the PR. Reviewer can edit the title if it's not clear enough, or to add suffix like `[BLOCKED]` or similar. Fixing typo is also a good practice, since GitHub search is quite not efficient, so the words must be spelled without any issue. Adding suffix will help when viewing the PR list. + +It's free form, but prefix tags could also be used to help understand what's in the PR. + +Examples of prefixes: +- `[Refacto]` +- `[Feature]` +- `[Bugfix]` +- etc. + +Also, it's still possible to add labels to the PRs, such as `A-` or `T-` labels, even if this is not a string requirement. We prefer to spend time to add labels on issues. + +##### PR description + +PR description should follow the PR template, and at least provide some context about the code change. + +##### File change + +1. Code should follow the guidelines +2. Code should be formatted correctly +3. XML attribute must be sorted +4. New code is added at the correct location +5. New classes are added to the correct location +6. Naming is correct. Naming is really important, it's considered part of the documentation +7. Architecture is followed. For instance, the logic is in the ViewModel and not in the Fragment +8. There is at least one file for the changelog. Exception if the PR fixes something which has not been released yet. Changelog content should target their audience: `.sdk` extension are mainly targeted for developers, other extensions are targeted for users and forks maintainers. It should generally describe visual change rather than give technical details. More details can be found [here](../CONTRIBUTING.md#changelog). +9. PR includes tests. allScreensTest when applicable, and unit tests +10. Avoid over complicating things. Keep it simple (KISS)! +11. PR contains only the expected change. Sometimes, the diff is showing changes that are already on `develop`. This is not good, submitter has to fix that up. + +##### Check the commit + +Commit message must be short, one line and valuable. "WIP" is not a good commit message. Commit message can contain issue number, starting with `#`. GitHub will add some link between the issue and such commit, which can be useful. It's possible to change a commit message at any time (may require a force push). + +Commit messages can contain extra lines with more details, links, etc. But keep in mind that those lines are quite less visible than the first line. + +Also commit history should be nice. Having commits like "Adding temporary code" then later "Removing temporary code" is not good. The branch has to be rebased and those commit have to be dropped. + +PR merger could decide to squash and merge if commit history is not good. + +Commit like "Code review fixes" is good when reviewing the PR, since new changes can be reviewed easily, but is less valuable when looking at git history. To avoid this, PR submitter should always push new commits after a review (no commit amend with force push), and when the PR is approved decide to interactive rebase the PR to improve the git history and reduce noise. + +##### Check the substance + +1. Test the changes! +2. Test the nominal case and the edge cases +3. Run the sanity test for critical PR + +##### Make a dedicated meeting to review the PR + +Sometimes a big PR can be hard to review. Setting up a call with the PR submitter can speed up the communication, rather than putting comments and questions in GitHub comments. It has the inconvenience of making the discussion non-public, consider including a summary of the main points of the "offline" conversation in the PR. + +### What happen to the issue(s)? + +The issue(s) should be referenced in the PR description using keywords like `Closes` of `Fixes` followed by the issue number. + +Example: +> Closes #1 + +Note that you have to repeat the keyword in case of a list of issue + +> Closes #1, Closes #2, etc. + +When PR will be merged, such referenced issue will be automatically closed. +It is up to the person who has merged the PR to go to the (closed) issue(s) and to add a comment to inform in which version the issue fix will be available. Use the current version of `develop` branch. + +> Closed in Element Android v1.x.y + +### Merge conflict + +It's up to the submitter to handle merge conflict. Sometimes, they can be fixed directly from GitHub, sometimes this is not possible. The branch can be rebased on `develop`, or the `develop` branch can be merged on the branch, it's up to the submitter to decide what is best. +Keep in mind that Github Actions are not run in case of conflict. + +### When and who can merge PR + +PR can be merged by the submitter, if and only if at least one approval from another developer is done. Approval from all people added as reviewer is also a good thing to have. Approval from design team may be mandatory, but is not sufficient to merge a PR. + +PR can also be merged by the reviewer, to reduce the time the PR is open. But only if the PR is not in draft and the change are quite small, or behind a feature flag. + +Dangerous PR should not be merged just before a release. Dangerous PR are PR that could break the app. Update of Realm library, rework in the chunk of Events management in the SDK, etc. + +We prefer to merge such PR after a release so that it can be tested during several days by the team before behind included in a release candidate. + +PR from bots will always be merged by the reviewer, right after approving the changes, or in case of critical changes, right after a release. + +#### Merge type + +Generally we use "Create a merge commit", which has the advantage to keep the branch visible. + +If git history is noisy (code added, then removed, etc.), it's possible to use "Squash and merge". But the branch will not be visible anymore, a commit will be added on top of develop. Git commit message can (and probably must) be edited from the GitHub web app. It's better if the submitter do the work to cleanup the git history by using a git interactive rebase of their branch. + +### Resolve conversation + +Generally we do not close conversation added during PR review and update by clicking on "Resolve conversation" +If the submitter or the reviewer do so, it will more difficult for further readers to see again the content. They will have to open the conversation to see it again. it's a waste of time. + +When remarks are handled, a small comment like "done" is enough, commit hash can also be added to the conversation. + +Exception: for big PRs with lots of conversations, using "Resolve conversation" may help to see the remaining remarks. + +Also "Resolve conversation" should probably be hit by the creator of the conversation. + +## Responsibility + +PR submitter is responsible of the incoming change. PR reviewers who approved the PR take a part of responsibility on the code which will land to develop, and then be used by our users, and the user of our forks. + +That said, bug may still be merged on `develop`, this is still acceptable of course. In this case, please make sure an issue is created and correctly labelled. Ideally, such issues should be fixed before the next release candidate, i.e. with a higher priority. But as we release the application every 10 working days, it can be hard to fix every bugs. That's why PR should be fully tested and reviewed before being merge and we should never comment code review remark with "will be handled later", or similar comments. \ No newline at end of file From 167fcb53664c284507d9373312ad42ef4e491523 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Thu, 31 Mar 2022 11:25:30 +0200 Subject: [PATCH 146/565] Directly use VectorFeatures inside Fragment --- .../app/features/location/LocationSharingFragment.kt | 6 ++++-- .../app/features/location/LocationSharingViewModel.kt | 9 --------- .../app/features/location/LocationSharingViewState.kt | 3 --- 3 files changed, 4 insertions(+), 14 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt index ea350b00c3..ab3bf9b933 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt @@ -36,6 +36,7 @@ import im.vector.app.core.utils.PERMISSIONS_FOR_FOREGROUND_LOCATION_SHARING import im.vector.app.core.utils.checkPermissions import im.vector.app.core.utils.registerForPermissionsResult import im.vector.app.databinding.FragmentLocationSharingBinding +import im.vector.app.features.VectorFeatures import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider import im.vector.app.features.location.option.LocationSharingOption @@ -49,7 +50,8 @@ import javax.inject.Inject class LocationSharingFragment @Inject constructor( private val urlMapProvider: UrlMapProvider, private val avatarRenderer: AvatarRenderer, - private val matrixItemColorProvider: MatrixItemColorProvider + private val matrixItemColorProvider: MatrixItemColorProvider, + private val vectorFeatures: VectorFeatures, ) : VectorBaseFragment(), LocationTargetChangeListener { private val viewModel: LocationSharingViewModel by fragmentViewModel() @@ -242,7 +244,7 @@ class LocationSharingFragment @Inject constructor( // first, update the options view val options: Set = when (state.areTargetAndUserLocationEqual) { true -> { - if (state.isLiveLocationSharingEnabled) { + if (vectorFeatures.isLiveLocationEnabled()) { setOf(LocationSharingOption.USER_CURRENT, LocationSharingOption.USER_LIVE) } else { setOf(LocationSharingOption.USER_CURRENT) diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt index 37ccd344b3..5f538dad67 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt @@ -24,7 +24,6 @@ import dagger.assisted.AssistedInject import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.platform.VectorViewModel -import im.vector.app.features.VectorFeatures import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvider import im.vector.app.features.location.domain.usecase.CompareLocationsUseCase import kotlinx.coroutines.flow.MutableSharedFlow @@ -50,7 +49,6 @@ class LocationSharingViewModel @AssistedInject constructor( private val locationPinProvider: LocationPinProvider, private val session: Session, private val compareLocationsUseCase: CompareLocationsUseCase, - private val vectorFeatures: VectorFeatures, ) : VectorViewModel(initialState), LocationTracker.Callback { private val room = session.getRoom(initialState.roomId)!! @@ -70,7 +68,6 @@ class LocationSharingViewModel @AssistedInject constructor( setUserItem() updatePin() compareTargetAndUserLocation() - checkVectorFeatures() } private fun setUserItem() { @@ -112,12 +109,6 @@ class LocationSharingViewModel @AssistedInject constructor( ?.let { userLocation -> compareLocationsUseCase.execute(userLocation, targetLocation) } } - private fun checkVectorFeatures() { - setState { - copy(isLiveLocationSharingEnabled = vectorFeatures.isLiveLocationEnabled()) - } - } - override fun onCleared() { super.onCleared() locationTracker.removeCallback(this) diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt index 64039b00c4..64f324bc1b 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt @@ -19,7 +19,6 @@ package im.vector.app.features.location import android.graphics.drawable.Drawable import androidx.annotation.StringRes import com.airbnb.mvrx.MavericksState -import im.vector.app.BuildConfig import im.vector.app.R import org.matrix.android.sdk.api.extensions.orTrue import org.matrix.android.sdk.api.util.MatrixItem @@ -32,7 +31,6 @@ enum class LocationSharingMode(@StringRes val titleRes: Int) { data class LocationSharingViewState( val roomId: String, val mode: LocationSharingMode, - val isLiveLocationSharingEnabled: Boolean, val userItem: MatrixItem.UserItem? = null, val areTargetAndUserLocationEqual: Boolean? = null, val lastKnownUserLocation: LocationData? = null, @@ -42,7 +40,6 @@ data class LocationSharingViewState( constructor(locationSharingArgs: LocationSharingArgs) : this( roomId = locationSharingArgs.roomId, mode = locationSharingArgs.mode, - isLiveLocationSharingEnabled = BuildConfig.ENABLE_LIVE_LOCATION_SHARING ) } From fbbadc81385efb9bdaf734bef55d6185454a3f93 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Thu, 31 Mar 2022 13:45:46 +0300 Subject: [PATCH 147/565] Rebind location service when the previous sharing is stopped. --- .../vector/app/features/home/room/detail/TimelineViewModel.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt index 024c42e503..dc1df9a96e 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt @@ -1229,6 +1229,8 @@ class TimelineViewModel @AssistedInject constructor( override fun onLocationServiceStopped() { _viewEvents.post(RoomDetailViewEvents.HideLocationSharingIndicator) + // Bind again in case user decides to share live location without leaving the room + locationSharingServiceConnection.bind(this) } override fun onCleared() { From 20cdf2bef1a66eeb184b5ca8823fb09aa46d2c39 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Thu, 31 Mar 2022 13:47:18 +0300 Subject: [PATCH 148/565] Changelog added. --- changelog.d/5660.feature | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/5660.feature diff --git a/changelog.d/5660.feature b/changelog.d/5660.feature new file mode 100644 index 0000000000..a914762d50 --- /dev/null +++ b/changelog.d/5660.feature @@ -0,0 +1 @@ +Show a banner in timeline while location sharing service is running \ No newline at end of file From f8a909b014ba92f9073fa2e2391cfeb217315cb0 Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Thu, 31 Mar 2022 14:07:49 +0300 Subject: [PATCH 149/565] Enhance naming --- .../room/threads/list/viewmodel/ThreadListViewModel.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewModel.kt index da09683b63..8840131f38 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewModel.kt @@ -113,15 +113,15 @@ class ThreadListViewModel @AssistedInject constructor(@Assisted val initialState private fun fetchThreadList() { viewModelScope.launch { - isLoading(true) + setLoading(true) room?.fetchThreadSummaries() - isLoading(false) + setLoading(false) } } - private fun isLoading(show: Boolean) { + private fun setLoading(isLoading: Boolean) { setState { - copy(isLoading = show) + copy(isLoading = isLoading) } } From 21541642ba8eaa748bbd6273ac9789c9251a0560 Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Thu, 31 Mar 2022 14:35:37 +0300 Subject: [PATCH 150/565] Exclude NegativeMargin from linter --- vector/src/main/res/layout/fragment_thread_list.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/vector/src/main/res/layout/fragment_thread_list.xml b/vector/src/main/res/layout/fragment_thread_list.xml index f0f7dff611..7918aaf178 100644 --- a/vector/src/main/res/layout/fragment_thread_list.xml +++ b/vector/src/main/res/layout/fragment_thread_list.xml @@ -46,6 +46,7 @@ android:layout_gravity="center_vertical" android:indeterminate="true" android:visibility="gone" + tools:ignore="NegativeMargin" android:layout_marginTop="-6dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" From d448c62caddf7bcdb045e8e75a6c7516857379f5 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 30 Mar 2022 13:37:24 +0200 Subject: [PATCH 151/565] Adding changelog entry --- changelog.d/5667.feature | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/5667.feature diff --git a/changelog.d/5667.feature b/changelog.d/5667.feature new file mode 100644 index 0000000000..38e750112e --- /dev/null +++ b/changelog.d/5667.feature @@ -0,0 +1 @@ +Location sharing: adding possibility to choose duration of live sharing \ No newline at end of file From f34225506a64ba354766cf9c032f20900495712f Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 30 Mar 2022 13:46:08 +0200 Subject: [PATCH 152/565] Adding strings resources --- vector/src/main/res/values/strings.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 96c9541da7..be143e99bd 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -2959,6 +2959,10 @@ Share live location Share this location Share this location + Share your live location for + 15 minutes + 1 hour + 8 hours Allow access If you’d like to share your Live location, ${app_name} needs location access all the time when the app is in the background.\nWe will only access your location for the duration that you choose. ${app_name} could not access your location From d0a255819a2b5bca3ffa1638a08cba00f50a6fae Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 30 Mar 2022 14:34:56 +0200 Subject: [PATCH 153/565] Creating BottomSheet to choose the live duration --- .../location/LocationSharingFragment.kt | 7 ++- .../duration/ChooseLiveDurationBottomSheet.kt | 48 +++++++++++++++++++ ...et_choose_live_location_share_duration.xml | 18 +++++++ 3 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/location/live/duration/ChooseLiveDurationBottomSheet.kt create mode 100644 vector/src/main/res/layout/bottom_sheet_choose_live_location_share_duration.xml diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt index ab3bf9b933..62104aa8eb 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt @@ -39,6 +39,7 @@ import im.vector.app.databinding.FragmentLocationSharingBinding import im.vector.app.features.VectorFeatures import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider +import im.vector.app.features.location.live.duration.ChooseLiveDurationBottomSheet import im.vector.app.features.location.option.LocationSharingOption import org.matrix.android.sdk.api.util.MatrixItem import java.lang.ref.WeakReference @@ -236,8 +237,10 @@ class LocationSharingFragment @Inject constructor( private fun startLiveLocationSharing() { // TODO. Get duration from user - val duration = 30 * 1000L - viewModel.handle(LocationSharingAction.StartLiveLocationSharing(duration)) + ChooseLiveDurationBottomSheet.newInstance() + .show(requireActivity().supportFragmentManager, "DISPLAY_CHOOSE_DURATION_OPTIONS") + //val duration = 30 * 1000L + //viewModel.handle(LocationSharingAction.StartLiveLocationSharing(duration)) } private fun updateMap(state: LocationSharingViewState) { diff --git a/vector/src/main/java/im/vector/app/features/location/live/duration/ChooseLiveDurationBottomSheet.kt b/vector/src/main/java/im/vector/app/features/location/live/duration/ChooseLiveDurationBottomSheet.kt new file mode 100644 index 0000000000..2801d5f0a6 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/location/live/duration/ChooseLiveDurationBottomSheet.kt @@ -0,0 +1,48 @@ +/* + * 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.app.features.location.live.duration + +import android.view.LayoutInflater +import android.view.ViewGroup +import dagger.hilt.android.AndroidEntryPoint +import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment +import im.vector.app.databinding.BottomSheetChooseLiveLocationShareDurationBinding +import im.vector.app.features.home.room.detail.timeline.action.MessageSharedActionViewModel + +/** + * Bottom sheet displaying list of options to choose the duration of the live sharing. + */ +@AndroidEntryPoint +class ChooseLiveDurationBottomSheet : + VectorBaseBottomSheetDialogFragment() { + + // TODO create interface callback to set the chosen duration + // TODO show same UI as in Figma + // TODO handle choice of user + + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): BottomSheetChooseLiveLocationShareDurationBinding { + return BottomSheetChooseLiveLocationShareDurationBinding.inflate(inflater, container, false) + } + + // we are not using state for this one as it's static, so no need to override invalidate() + + companion object { + fun newInstance(): ChooseLiveDurationBottomSheet { + return ChooseLiveDurationBottomSheet() + } + } +} diff --git a/vector/src/main/res/layout/bottom_sheet_choose_live_location_share_duration.xml b/vector/src/main/res/layout/bottom_sheet_choose_live_location_share_duration.xml new file mode 100644 index 0000000000..675a63144f --- /dev/null +++ b/vector/src/main/res/layout/bottom_sheet_choose_live_location_share_duration.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file From 4da11bbdc0ae0beceede01a1010728e796628d28 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 30 Mar 2022 14:41:12 +0200 Subject: [PATCH 154/565] Renaming duration parameter to precise the time unit --- .../vector/app/features/location/LocationSharingAction.kt | 2 +- .../vector/app/features/location/LocationSharingFragment.kt | 2 +- .../app/features/location/LocationSharingViewEvents.kt | 2 +- .../app/features/location/LocationSharingViewModel.kt | 6 +++--- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingAction.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingAction.kt index 4025fbefa8..d86d97e6b0 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingAction.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingAction.kt @@ -23,5 +23,5 @@ sealed class LocationSharingAction : VectorViewModelAction { data class PinnedLocationSharing(val locationData: LocationData?) : LocationSharingAction() data class LocationTargetChange(val locationData: LocationData) : LocationSharingAction() object ZoomToUserLocation : LocationSharingAction() - data class StartLiveLocationSharing(val duration: Long) : LocationSharingAction() + data class StartLiveLocationSharing(val durationMillis: Long) : LocationSharingAction() } diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt index 62104aa8eb..c4574d69db 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt @@ -182,7 +182,7 @@ class LocationSharingFragment @Inject constructor( } private fun handleStartLiveLocationService(event: LocationSharingViewEvents.StartLiveLocationService) { - val args = LocationSharingService.RoomArgs(event.sessionId, event.roomId, event.duration) + val args = LocationSharingService.RoomArgs(event.sessionId, event.roomId, event.durationMillis) Intent(requireContext(), LocationSharingService::class.java) .putExtra(LocationSharingService.EXTRA_ROOM_ARGS, args) diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewEvents.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewEvents.kt index b25a4988b0..1116003e41 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewEvents.kt @@ -22,5 +22,5 @@ sealed class LocationSharingViewEvents : VectorViewEvents { object Close : LocationSharingViewEvents() object LocationNotAvailableError : LocationSharingViewEvents() data class ZoomToUserLocation(val userLocation: LocationData) : LocationSharingViewEvents() - data class StartLiveLocationService(val sessionId: String, val roomId: String, val duration: Long) : LocationSharingViewEvents() + data class StartLiveLocationService(val sessionId: String, val roomId: String, val durationMillis: Long) : LocationSharingViewEvents() } diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt index 5f538dad67..e67d5d0abb 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt @@ -120,7 +120,7 @@ class LocationSharingViewModel @AssistedInject constructor( is LocationSharingAction.PinnedLocationSharing -> handlePinnedLocationSharingAction(action) is LocationSharingAction.LocationTargetChange -> handleLocationTargetChangeAction(action) LocationSharingAction.ZoomToUserLocation -> handleZoomToUserLocationAction() - is LocationSharingAction.StartLiveLocationSharing -> handleStartLiveLocationSharingAction(action.duration) + is LocationSharingAction.StartLiveLocationSharing -> handleStartLiveLocationSharingAction(action.durationMillis) } } @@ -158,11 +158,11 @@ class LocationSharingViewModel @AssistedInject constructor( } } - private fun handleStartLiveLocationSharingAction(duration: Long) { + private fun handleStartLiveLocationSharingAction(durationMillis: Long) { _viewEvents.post(LocationSharingViewEvents.StartLiveLocationService( sessionId = session.sessionId, roomId = room.roomId, - duration = duration + durationMillis = durationMillis )) } From 5abc1965364d4e3cc8240fbe4dce30b924a4c106 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 30 Mar 2022 14:53:32 +0200 Subject: [PATCH 155/565] Callback interface for the choice of the duration --- .../location/LocationSharingFragment.kt | 13 ++++++++----- .../duration/ChooseLiveDurationBottomSheet.kt | 18 +++++++++++++++--- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt index c4574d69db..3fdae43312 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt @@ -53,7 +53,9 @@ class LocationSharingFragment @Inject constructor( private val avatarRenderer: AvatarRenderer, private val matrixItemColorProvider: MatrixItemColorProvider, private val vectorFeatures: VectorFeatures, -) : VectorBaseFragment(), LocationTargetChangeListener { +) : VectorBaseFragment(), + LocationTargetChangeListener, + ChooseLiveDurationBottomSheet.DurationChoiceListener { private val viewModel: LocationSharingViewModel by fragmentViewModel() @@ -236,11 +238,12 @@ class LocationSharingFragment @Inject constructor( } private fun startLiveLocationSharing() { - // TODO. Get duration from user - ChooseLiveDurationBottomSheet.newInstance() + ChooseLiveDurationBottomSheet.newInstance(this) .show(requireActivity().supportFragmentManager, "DISPLAY_CHOOSE_DURATION_OPTIONS") - //val duration = 30 * 1000L - //viewModel.handle(LocationSharingAction.StartLiveLocationSharing(duration)) + } + + override fun onDurationChoice(durationMillis: Long) { + viewModel.handle(LocationSharingAction.StartLiveLocationSharing(durationMillis)) } private fun updateMap(state: LocationSharingViewState) { diff --git a/vector/src/main/java/im/vector/app/features/location/live/duration/ChooseLiveDurationBottomSheet.kt b/vector/src/main/java/im/vector/app/features/location/live/duration/ChooseLiveDurationBottomSheet.kt index 2801d5f0a6..59c9faddda 100644 --- a/vector/src/main/java/im/vector/app/features/location/live/duration/ChooseLiveDurationBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/location/live/duration/ChooseLiveDurationBottomSheet.kt @@ -30,19 +30,31 @@ import im.vector.app.features.home.room.detail.timeline.action.MessageSharedActi class ChooseLiveDurationBottomSheet : VectorBaseBottomSheetDialogFragment() { - // TODO create interface callback to set the chosen duration // TODO show same UI as in Figma // TODO handle choice of user + var durationChoiceListener: DurationChoiceListener? = null + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): BottomSheetChooseLiveLocationShareDurationBinding { return BottomSheetChooseLiveLocationShareDurationBinding.inflate(inflater, container, false) } + override fun onDestroyView() { + durationChoiceListener = null + super.onDestroyView() + } + // we are not using state for this one as it's static, so no need to override invalidate() companion object { - fun newInstance(): ChooseLiveDurationBottomSheet { - return ChooseLiveDurationBottomSheet() + fun newInstance(durationChoiceListener: DurationChoiceListener): ChooseLiveDurationBottomSheet { + val bottomSheet = ChooseLiveDurationBottomSheet() + bottomSheet.durationChoiceListener = durationChoiceListener + return bottomSheet } } + + interface DurationChoiceListener { + fun onDurationChoice(durationMillis: Long) + } } From c18a9230e552ce7f480e59ae02f31785672c8adb Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 30 Mar 2022 16:11:25 +0200 Subject: [PATCH 156/565] UI to select the duration --- .../duration/ChooseLiveDurationBottomSheet.kt | 44 ++++++++++++++- .../drawable/divider_horizontal_system.xml | 5 ++ ...et_choose_live_location_share_duration.xml | 56 +++++++++++++++++-- 3 files changed, 96 insertions(+), 9 deletions(-) create mode 100644 vector/src/main/res/drawable/divider_horizontal_system.xml diff --git a/vector/src/main/java/im/vector/app/features/location/live/duration/ChooseLiveDurationBottomSheet.kt b/vector/src/main/java/im/vector/app/features/location/live/duration/ChooseLiveDurationBottomSheet.kt index 59c9faddda..f0bee558cc 100644 --- a/vector/src/main/java/im/vector/app/features/location/live/duration/ChooseLiveDurationBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/location/live/duration/ChooseLiveDurationBottomSheet.kt @@ -16,12 +16,29 @@ package im.vector.app.features.location.live.duration +import android.os.Bundle import android.view.LayoutInflater +import android.view.View import android.view.ViewGroup import dagger.hilt.android.AndroidEntryPoint +import im.vector.app.R import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment import im.vector.app.databinding.BottomSheetChooseLiveLocationShareDurationBinding -import im.vector.app.features.home.room.detail.timeline.action.MessageSharedActionViewModel + +/** + * 15 minutes. + */ +private const val DURATION_IN_MS_OPTION_1 = 15 * 60_000L + +/** + * 1 hour. + */ +private const val DURATION_IN_MS_OPTION_2 = 60 * 60_000L + +/** + * 8 hours. + */ +private const val DURATION_IN_MS_OPTION_3 = 8 * 60 * 60_000L /** * Bottom sheet displaying list of options to choose the duration of the live sharing. @@ -30,8 +47,7 @@ import im.vector.app.features.home.room.detail.timeline.action.MessageSharedActi class ChooseLiveDurationBottomSheet : VectorBaseBottomSheetDialogFragment() { - // TODO show same UI as in Figma - // TODO handle choice of user + // TODO fix text color problem of button in dqrk mode var durationChoiceListener: DurationChoiceListener? = null @@ -39,6 +55,11 @@ class ChooseLiveDurationBottomSheet : return BottomSheetChooseLiveLocationShareDurationBinding.inflate(inflater, container, false) } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + initConfirmButton() + } + override fun onDestroyView() { durationChoiceListener = null super.onDestroyView() @@ -46,6 +67,23 @@ class ChooseLiveDurationBottomSheet : // we are not using state for this one as it's static, so no need to override invalidate() + private fun initConfirmButton() { + views.liveLocShareChooseDurationConfirm.setOnClickListener { + val currentChoice = getCurrentChoice() + durationChoiceListener?.onDurationChoice(currentChoice) + dismiss() + } + } + + private fun getCurrentChoice(): Long { + return when (views.liveLocShareChooseDurationOptions.checkedRadioButtonId) { + R.id.liveLocShareChooseDurationOption1 -> DURATION_IN_MS_OPTION_1 + R.id.liveLocShareChooseDurationOption2 -> DURATION_IN_MS_OPTION_2 + R.id.liveLocShareChooseDurationOption3 -> DURATION_IN_MS_OPTION_3 + else -> DURATION_IN_MS_OPTION_1 + } + } + companion object { fun newInstance(durationChoiceListener: DurationChoiceListener): ChooseLiveDurationBottomSheet { val bottomSheet = ChooseLiveDurationBottomSheet() diff --git a/vector/src/main/res/drawable/divider_horizontal_system.xml b/vector/src/main/res/drawable/divider_horizontal_system.xml new file mode 100644 index 0000000000..df847a4feb --- /dev/null +++ b/vector/src/main/res/drawable/divider_horizontal_system.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/vector/src/main/res/layout/bottom_sheet_choose_live_location_share_duration.xml b/vector/src/main/res/layout/bottom_sheet_choose_live_location_share_duration.xml index 675a63144f..3336c0bac0 100644 --- a/vector/src/main/res/layout/bottom_sheet_choose_live_location_share_duration.xml +++ b/vector/src/main/res/layout/bottom_sheet_choose_live_location_share_duration.xml @@ -2,17 +2,61 @@ + android:paddingHorizontal="15dp" + android:paddingVertical="24dp" + android:text="@string/location_share_live_select_duration_title" + android:textColor="?vctr_content_primary" /> + + + + + + + + + + +