From 7aaebd493be4c2f3996214089910da0210a4a723 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Radics?= Date: Sun, 3 Jan 2021 18:47:53 +0100 Subject: [PATCH 001/632] [issue-2610] implement setting to override nick color MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - allow changing the nick color by clicking the dispay-name in the room member detail page. - the ovirride-color can be specified as a hex string (#rrggbb) or as palette index (2) - entering an invalid color code or leaving the field blank reverts to the default hash-based nick color - the setting is stored in `account_data` as `im.vector.setting.override_colors` - future improvements / notes: - replace the text-based color entry with a proper color picker dialog - make the feature more discoverable - the color change listener is now in AppStateHandler, not sure if this is the best place - implement override color support in element-web / element-desktop, too Signed-off-by: Péter Radics --- CHANGES.md | 1 + .../accountdata/UserAccountDataTypes.kt | 1 + .../java/im/vector/app/AppStateHandler.kt | 22 +++++++++- .../helper/MatrixItemColorProvider.kt | 40 ++++++++++++++++- .../RoomMemberProfileFragment.kt | 44 ++++++++++++++++++- vector/src/main/res/values/strings.xml | 2 + 6 files changed, 107 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index a028ef6f1e..579124ce45 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,7 @@ Changes in Element 1.0.14 (2020-XX-XX) =================================================== Features ✨: + - Allow changing nick colors (#2610) - Enable url previews for notices (#2562) Improvements 🙌: diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/UserAccountDataTypes.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/UserAccountDataTypes.kt index 69b15ff7d4..91167d896f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/UserAccountDataTypes.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/UserAccountDataTypes.kt @@ -27,4 +27,5 @@ object UserAccountDataTypes { const val TYPE_ALLOWED_WIDGETS = "im.vector.setting.allowed_widgets" const val TYPE_IDENTITY_SERVER = "m.identity_server" const val TYPE_ACCEPTED_TERMS = "m.accepted_terms" + const val TYPE_OVERRIDE_COLORS = "im.vector.setting.override_colors" } diff --git a/vector/src/main/java/im/vector/app/AppStateHandler.kt b/vector/src/main/java/im/vector/app/AppStateHandler.kt index 1e92f7bc67..b2791c0978 100644 --- a/vector/src/main/java/im/vector/app/AppStateHandler.kt +++ b/vector/src/main/java/im/vector/app/AppStateHandler.kt @@ -23,12 +23,15 @@ import arrow.core.Option import im.vector.app.features.grouplist.ALL_COMMUNITIES_GROUP_ID import im.vector.app.features.grouplist.SelectedGroupDataSource import im.vector.app.features.home.HomeRoomListDataSource +import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider import im.vector.app.features.home.room.list.ChronologicalRoomComparator import io.reactivex.Observable import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.CompositeDisposable import io.reactivex.functions.BiFunction import io.reactivex.rxkotlin.addTo +import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes +import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.group.model.GroupSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams @@ -46,13 +49,15 @@ class AppStateHandler @Inject constructor( private val sessionDataSource: ActiveSessionDataSource, private val homeRoomListDataSource: HomeRoomListDataSource, private val selectedGroupDataSource: SelectedGroupDataSource, - private val chronologicalRoomComparator: ChronologicalRoomComparator) : LifecycleObserver { + private val chronologicalRoomComparator: ChronologicalRoomComparator, + private val matrixItemColorProvider: MatrixItemColorProvider) : LifecycleObserver { private val compositeDisposable = CompositeDisposable() @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) fun entersForeground() { observeRoomsAndGroup() + observeUserAccountData() } @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) @@ -93,4 +98,19 @@ class AppStateHandler @Inject constructor( } .addTo(compositeDisposable) } + + private fun observeUserAccountData() { + sessionDataSource.observe() + .observeOn(AndroidSchedulers.mainThread()) + .switchMap { + it.orNull()?.rx()?.liveAccountData(setOf(UserAccountDataTypes.TYPE_OVERRIDE_COLORS)) + ?: Observable.just(emptyList()) + } + .distinctUntilChanged() + .subscribe { + val overrideColorSpecs = it?.first()?.content?.toModel>() + matrixItemColorProvider.setOverrideColors(overrideColorSpecs) + } + .addTo(compositeDisposable) + } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MatrixItemColorProvider.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MatrixItemColorProvider.kt index 6a590206cb..dcbfb13dcc 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MatrixItemColorProvider.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MatrixItemColorProvider.kt @@ -44,6 +44,39 @@ class MatrixItemColorProvider @Inject constructor( } } + fun setOverrideColors(overrideColors: Map?) { + overrideColors?.forEach() { + setOverrideColor(it.key, it.value) + } + } + + fun setOverrideColor(id: String, colorSpec: String?) : Boolean { + val color = parseUserColorSpec(colorSpec) + if (color == null) { + cache.remove(id) + return false + } else { + cache.put(id, color) + return true + } + } + + @ColorInt + private fun parseUserColorSpec(colorText: String?): Int? { + if (colorText.isNullOrBlank()) { + return null + } + try { + if (colorText.first() == '#') { + return (colorText.substring(1).toLong(radix = 16) or 0xff000000L).toInt() + } else { + return colorProvider.getColor(getUserColorByIndex(colorText.toInt())) + } + } catch (e: Throwable) { + return null + } + } + companion object { @ColorRes @VisibleForTesting @@ -52,7 +85,12 @@ class MatrixItemColorProvider @Inject constructor( userId?.toList()?.map { chr -> hash = (hash shl 5) - hash + chr.toInt() } - return when (abs(hash) % 8) { + return getUserColorByIndex(abs(hash)) + } + + @ColorRes + private fun getUserColorByIndex(index: Int): Int { + return when (index % 8) { 1 -> R.color.riotx_username_2 2 -> R.color.riotx_username_3 3 -> R.color.riotx_username_4 diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt index 08a251834e..3c0ff15fb8 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt @@ -43,6 +43,7 @@ import im.vector.app.core.extensions.setTextOrHide import im.vector.app.core.platform.StateView import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.utils.startSharePlainTextIntent +import im.vector.app.databinding.DialogBaseEditTextBinding import im.vector.app.databinding.DialogShareQrCodeBinding import im.vector.app.databinding.FragmentMatrixProfileBinding import im.vector.app.databinding.ViewStubRoomMemberProfileHeaderBinding @@ -50,9 +51,11 @@ import im.vector.app.features.crypto.verification.VerificationBottomSheet import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.room.detail.RoomDetailPendingAction import im.vector.app.features.home.room.detail.RoomDetailPendingActionStore +import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider import im.vector.app.features.roommemberprofile.devices.DeviceListBottomSheet import im.vector.app.features.roommemberprofile.powerlevel.EditPowerLevelDialogs import kotlinx.parcelize.Parcelize +import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes import org.matrix.android.sdk.api.session.room.powerlevels.Role import org.matrix.android.sdk.api.util.MatrixItem import javax.inject.Inject @@ -67,7 +70,8 @@ class RoomMemberProfileFragment @Inject constructor( val viewModelFactory: RoomMemberProfileViewModel.Factory, private val roomMemberProfileController: RoomMemberProfileController, private val avatarRenderer: AvatarRenderer, - private val roomDetailPendingActionStore: RoomDetailPendingActionStore + private val roomDetailPendingActionStore: RoomDetailPendingActionStore, + private val matrixItemColorProvider: MatrixItemColorProvider ) : VectorBaseFragment(), RoomMemberProfileController.Callback { @@ -199,6 +203,7 @@ class RoomMemberProfileFragment @Inject constructor( headerViews.memberProfileIdView.text = userMatrixItem.id val bestName = userMatrixItem.getBestName() headerViews.memberProfileNameView.text = bestName + headerViews.memberProfileNameView.setTextColor(matrixItemColorProvider.getColor(userMatrixItem)) views.matrixProfileToolbarTitleView.text = bestName avatarRenderer.render(userMatrixItem, headerViews.memberProfileAvatarView) avatarRenderer.render(userMatrixItem, views.matrixProfileToolbarAvatarImageView) @@ -237,6 +242,9 @@ class RoomMemberProfileFragment @Inject constructor( headerViews.memberProfileAvatarView.setOnClickListener { view -> onAvatarClicked(view, userMatrixItem) } + headerViews.memberProfileNameView.setOnClickListener { _ -> + onProfileNameClicked(userMatrixItem) + } views.matrixProfileToolbarAvatarImageView.setOnClickListener { view -> onAvatarClicked(view, userMatrixItem) } @@ -323,6 +331,40 @@ class RoomMemberProfileFragment @Inject constructor( navigator.openBigImageViewer(requireActivity(), view, userMatrixItem) } + private fun onProfileNameClicked(userMatrixItem: MatrixItem) { + val inflater = requireActivity().layoutInflater + val layout = inflater.inflate(R.layout.dialog_base_edit_text, null) + val views = DialogBaseEditTextBinding.bind(layout) + val session = injector().activeSessionHolder().getActiveSession() + val overrideColorsSetting = session.getAccountDataEvent(UserAccountDataTypes.TYPE_OVERRIDE_COLORS) + val overrideColorSpecs = overrideColorsSetting?.content?.toMap().orEmpty() + val overrideColorSpec = overrideColorSpecs[userMatrixItem.id]?.toString() + views.editText.setText(overrideColorSpec) + views.editText.hint = "#000000" + + AlertDialog.Builder(requireActivity()) + .setTitle(R.string.room_member_override_color) + .setView(layout) + .setPositiveButton(R.string.ok) { _, _ -> + val newOverrideColorSpec = views.editText.text.toString() + if (newOverrideColorSpec != overrideColorSpec) { + val newOverrideColorSpecs = overrideColorSpecs.toMutableMap() + if (matrixItemColorProvider.setOverrideColor(userMatrixItem.id, newOverrideColorSpec)) { + newOverrideColorSpecs[userMatrixItem.id] = newOverrideColorSpec + } else { + newOverrideColorSpecs.remove(userMatrixItem.id) + } + session.updateAccountData( + type = UserAccountDataTypes.TYPE_OVERRIDE_COLORS, + content = newOverrideColorSpecs + ) + headerViews.memberProfileNameView.setTextColor(matrixItemColorProvider.getColor(userMatrixItem)) + } + } + .setNegativeButton(R.string.cancel, null) + .show() + } + override fun onEditPowerLevel(currentRole: Role) { EditPowerLevelDialogs.showChoice(requireActivity(), currentRole) { newPowerLevel -> viewModel.handle(RoomMemberProfileAction.SetPowerLevel(currentRole.value, newPowerLevel, true)) diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 76541460d2..61bc5dec94 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -2241,6 +2241,8 @@ Leave "Leaving the room…" + Override color + Admins Moderators Custom From 889a9a17983a4241a285d3d12aeee1e6fb7a4d56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Radics?= Date: Sun, 3 Jan 2021 21:42:33 +0100 Subject: [PATCH 002/632] [issue-2610] fix NoSuchElement error --- vector/src/main/java/im/vector/app/AppStateHandler.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/AppStateHandler.kt b/vector/src/main/java/im/vector/app/AppStateHandler.kt index b2791c0978..303433db67 100644 --- a/vector/src/main/java/im/vector/app/AppStateHandler.kt +++ b/vector/src/main/java/im/vector/app/AppStateHandler.kt @@ -108,7 +108,7 @@ class AppStateHandler @Inject constructor( } .distinctUntilChanged() .subscribe { - val overrideColorSpecs = it?.first()?.content?.toModel>() + val overrideColorSpecs = it?.firstOrNull()?.content?.toModel>() matrixItemColorProvider.setOverrideColors(overrideColorSpecs) } .addTo(compositeDisposable) From 0e400dca24a69636059bf23b03068cfe40197709 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Radics?= Date: Wed, 3 Feb 2021 20:04:24 +0100 Subject: [PATCH 003/632] [issue-2610] Add Override Color menu item under More... --- gradle/wrapper/gradle-wrapper.properties | 2 +- .../RoomMemberProfileController.kt | 13 +++++++++++-- .../RoomMemberProfileFragment.kt | 15 ++++++++------- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 247e2b90ad..73f88221b1 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ #Fri Jan 15 11:30:47 CET 2021 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=a7ca23b3ccf265680f2bfd35f1f00b1424f4466292c7337c85d46c9641b3f053 +#distributionSha256Sum=a7ca23b3ccf265680f2bfd35f1f00b1424f4466292c7337c85d46c9641b3f053 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-6.8-all.zip diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileController.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileController.kt index e29c197ab8..9c2d2ded70 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileController.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileController.kt @@ -46,6 +46,7 @@ class RoomMemberProfileController @Inject constructor( fun onShowDeviceList() fun onShowDeviceListNoCrossSigning() fun onOpenDmClicked() + fun onOverrideColorClicked() fun onJumpToReadReceiptClicked() fun onMentionClicked() fun onEditPowerLevel(currentRole: Role) @@ -180,11 +181,19 @@ class RoomMemberProfileController @Inject constructor( private fun buildMoreSection(state: RoomMemberProfileViewState) { // More + buildProfileSection(stringProvider.getString(R.string.room_profile_section_more)) + + buildProfileAction( + id = "overrideColor", + editable = false, + title = stringProvider.getString(R.string.room_member_override_color), + dividerColor = dividerColor, + action = { callback?.onOverrideColorClicked() } + ) + if (!state.isMine) { val membership = state.asyncMembership() ?: return - buildProfileSection(stringProvider.getString(R.string.room_profile_section_more)) - buildProfileAction( id = "direct", editable = false, diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt index 92cdf305fe..2099b706eb 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt @@ -243,7 +243,7 @@ class RoomMemberProfileFragment @Inject constructor( onAvatarClicked(view, userMatrixItem) } headerViews.memberProfileNameView.setOnClickListener { _ -> - onProfileNameClicked(userMatrixItem) + onOverrideColorClicked() } views.matrixProfileToolbarAvatarImageView.setOnClickListener { view -> onAvatarClicked(view, userMatrixItem) @@ -331,14 +331,15 @@ class RoomMemberProfileFragment @Inject constructor( navigator.openBigImageViewer(requireActivity(), view, userMatrixItem) } - private fun onProfileNameClicked(userMatrixItem: MatrixItem) { + override fun onOverrideColorClicked(): Unit = withState(viewModel) { state -> val inflater = requireActivity().layoutInflater val layout = inflater.inflate(R.layout.dialog_base_edit_text, null) val views = DialogBaseEditTextBinding.bind(layout) val session = injector().activeSessionHolder().getActiveSession() val overrideColorsSetting = session.getAccountDataEvent(UserAccountDataTypes.TYPE_OVERRIDE_COLORS) val overrideColorSpecs = overrideColorsSetting?.content?.toMap().orEmpty() - val overrideColorSpec = overrideColorSpecs[userMatrixItem.id]?.toString() + val userId = state.userId; + val overrideColorSpec = overrideColorSpecs[userId]?.toString() views.editText.setText(overrideColorSpec) views.editText.hint = "#000000" @@ -349,16 +350,16 @@ class RoomMemberProfileFragment @Inject constructor( val newOverrideColorSpec = views.editText.text.toString() if (newOverrideColorSpec != overrideColorSpec) { val newOverrideColorSpecs = overrideColorSpecs.toMutableMap() - if (matrixItemColorProvider.setOverrideColor(userMatrixItem.id, newOverrideColorSpec)) { - newOverrideColorSpecs[userMatrixItem.id] = newOverrideColorSpec + if (matrixItemColorProvider.setOverrideColor(userId, newOverrideColorSpec)) { + newOverrideColorSpecs[userId] = newOverrideColorSpec } else { - newOverrideColorSpecs.remove(userMatrixItem.id) + newOverrideColorSpecs.remove(userId) } session.updateAccountData( type = UserAccountDataTypes.TYPE_OVERRIDE_COLORS, content = newOverrideColorSpecs ) - headerViews.memberProfileNameView.setTextColor(matrixItemColorProvider.getColor(userMatrixItem)) + invalidate() } } .setNegativeButton(R.string.cancel, null) From f7d8127fa669dc1fe4d4ac5b195b0a4cd19779f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Radics?= Date: Wed, 3 Feb 2021 21:04:47 +0100 Subject: [PATCH 004/632] [issue-2610] remove extra semicolon --- .../app/features/roommemberprofile/RoomMemberProfileFragment.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt index 2099b706eb..a60b7368fa 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt @@ -338,7 +338,7 @@ class RoomMemberProfileFragment @Inject constructor( val session = injector().activeSessionHolder().getActiveSession() val overrideColorsSetting = session.getAccountDataEvent(UserAccountDataTypes.TYPE_OVERRIDE_COLORS) val overrideColorSpecs = overrideColorsSetting?.content?.toMap().orEmpty() - val userId = state.userId; + val userId = state.userId val overrideColorSpec = overrideColorSpecs[userId]?.toString() views.editText.setText(overrideColorSpec) views.editText.hint = "#000000" From cc15f9b1298d0da8c39afa71b7e19fc5d58d8f31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Radics?= Date: Sat, 6 Feb 2021 09:34:31 +0100 Subject: [PATCH 005/632] [issue-2610] remove click handler from display-name Remove the click handler that opens the override color dialog from the display-name part on the member profile page, as this is not really discoverable and we have a proper menu item for it now. --- .../features/roommemberprofile/RoomMemberProfileFragment.kt | 3 --- 1 file changed, 3 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt index a60b7368fa..e50645ae01 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt @@ -242,9 +242,6 @@ class RoomMemberProfileFragment @Inject constructor( headerViews.memberProfileAvatarView.setOnClickListener { view -> onAvatarClicked(view, userMatrixItem) } - headerViews.memberProfileNameView.setOnClickListener { _ -> - onOverrideColorClicked() - } views.matrixProfileToolbarAvatarImageView.setOnClickListener { view -> onAvatarClicked(view, userMatrixItem) } From db97046f082a0417f2195b0dd0ffcef724352569 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Radics?= Date: Sat, 13 Feb 2021 13:30:31 +0100 Subject: [PATCH 006/632] [issue-2610] change menu text to "Override nick color" --- .../features/roommemberprofile/RoomMemberProfileController.kt | 2 +- .../app/features/roommemberprofile/RoomMemberProfileFragment.kt | 2 +- vector/src/main/res/values/strings.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileController.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileController.kt index 2a01e45f93..ba114e2e05 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileController.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileController.kt @@ -184,7 +184,7 @@ class RoomMemberProfileController @Inject constructor( buildProfileAction( id = "overrideColor", editable = false, - title = stringProvider.getString(R.string.room_member_override_color), + title = stringProvider.getString(R.string.room_member_override_nick_color), dividerColor = dividerColor, action = { callback?.onOverrideColorClicked() } ) diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt index e50645ae01..04ee40668e 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt @@ -341,7 +341,7 @@ class RoomMemberProfileFragment @Inject constructor( views.editText.hint = "#000000" AlertDialog.Builder(requireActivity()) - .setTitle(R.string.room_member_override_color) + .setTitle(R.string.room_member_override_nick_color) .setView(layout) .setPositiveButton(R.string.ok) { _, _ -> val newOverrideColorSpec = views.editText.text.toString() diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 2b98efc765..085c999713 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -2273,7 +2273,7 @@ Leave "Leaving the room…" - Override color + Override nick color Admins Moderators From 9238037067c9131391c79ad8cd99e8b52ee75831 Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 7 Sep 2021 19:28:20 +0200 Subject: [PATCH 007/632] Timeline: try new strategy for handling chunks (no merging) --- .../sdk/api/session/room/timeline/Timeline.kt | 15 + .../sdk/internal/database/DatabaseCleaner.kt | 20 +- .../SessionRealmConfigurationFactory.kt | 1 + .../database/helper/ChunkEntityHelper.kt | 2 +- .../internal/database/model/ChunkEntity.kt | 4 +- .../session/room/timeline/DefaultTimeline.kt | 873 +++++------------- .../room/timeline/DefaultTimelineService.kt | 7 +- .../{TimelineState.kt => LoadMoreResult.kt} | 13 +- .../room/timeline/LoadTimelineStrategy.kt | 198 ++++ .../room/timeline/SendingEventsDataSource.kt | 84 ++ .../session/room/timeline/TimelineChunk.kt | 362 ++++++++ .../room/timeline/TokenChunkEventPersistor.kt | 79 +- .../session/room/timeline/UIEchoManager.kt | 10 +- .../timeline/TimelineEventController.kt | 15 +- 14 files changed, 929 insertions(+), 754 deletions(-) rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/{TimelineState.kt => LoadMoreResult.kt} (70%) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/SendingEventsDataSource.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/Timeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/Timeline.kt index 06c88db831..dff45c0d94 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/Timeline.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/Timeline.kt @@ -95,6 +95,8 @@ interface Timeline { */ fun getTimelineEventWithId(eventId: String?): TimelineEvent? + fun getPaginationState(direction: Direction): PaginationState + interface Listener { /** * Call when the timeline has been updated through pagination or sync. @@ -112,8 +114,21 @@ interface Timeline { * Called when new events come through the sync */ fun onNewTimelineEvents(eventIds: List) + + fun onStateUpdated() { + //NOOP + } } + /** + * Pagination state + */ + data class PaginationState( + val hasMoreToLoad: Boolean = true, + val loading: Boolean = false, + val inError: Boolean = false + ) + /** * This is used to paginate in one or another direction. */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/DatabaseCleaner.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/DatabaseCleaner.kt index ee58880eb8..09fbb2bfa0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/DatabaseCleaner.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/DatabaseCleaner.kt @@ -16,22 +16,13 @@ package org.matrix.android.sdk.internal.database -import io.realm.Realm import io.realm.RealmConfiguration import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.internal.database.helper.nextDisplayIndex -import org.matrix.android.sdk.internal.database.model.ChunkEntity -import org.matrix.android.sdk.internal.database.model.ChunkEntityFields -import org.matrix.android.sdk.internal.database.model.EventEntity -import org.matrix.android.sdk.internal.database.model.RoomEntity -import org.matrix.android.sdk.internal.database.model.TimelineEventEntity -import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields -import org.matrix.android.sdk.internal.database.model.deleteOnCascade -import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.api.session.SessionLifecycleObserver -import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection +import org.matrix.android.sdk.internal.database.model.RoomEntity +import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.task.TaskExecutor import timber.log.Timber import javax.inject.Inject @@ -53,11 +44,12 @@ internal class DatabaseCleaner @Inject constructor(@SessionDatabase private val awaitTransaction(realmConfiguration) { realm -> val allRooms = realm.where(RoomEntity::class.java).findAll() Timber.v("There are ${allRooms.size} rooms in this session") - cleanUp(realm, MAX_NUMBER_OF_EVENTS_IN_DB / 2L) + //cleanUp(realm, MAX_NUMBER_OF_EVENTS_IN_DB / 2L) } } } + /* private fun cleanUp(realm: Realm, threshold: Long) { val numberOfEvents = realm.where(EventEntity::class.java).findAll().size val numberOfTimelineEvents = realm.where(TimelineEventEntity::class.java).findAll().size @@ -75,7 +67,7 @@ internal class DatabaseCleaner @Inject constructor(@SessionDatabase private val val thresholdDisplayIndex = maxDisplayIndex - threshold val eventsToRemove = chunk.timelineEvents.where().lessThan(TimelineEventEntityFields.DISPLAY_INDEX, thresholdDisplayIndex).findAll() Timber.v("There are ${eventsToRemove.size} events to clean in chunk: ${chunk.identifier()} from room ${chunk.room?.first()?.roomId}") - chunk.numberOfTimelineEvents = chunk.numberOfTimelineEvents - eventsToRemove.size + //chunk.numberOfTimelineEvents = chunk.numberOfTimelineEvents - eventsToRemove.size eventsToRemove.forEach { val canDeleteRoot = it.root?.stateKey == null it.deleteOnCascade(canDeleteRoot) @@ -86,4 +78,6 @@ internal class DatabaseCleaner @Inject constructor(@SessionDatabase private val cleanUp(realm, (threshold / 1.5).toLong()) } } + + */ } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/SessionRealmConfigurationFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/SessionRealmConfigurationFactory.kt index 1771c5b202..6aeb3936a9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/SessionRealmConfigurationFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/SessionRealmConfigurationFactory.kt @@ -72,6 +72,7 @@ internal class SessionRealmConfigurationFactory @Inject constructor( .modules(SessionRealmModule()) .schemaVersion(RealmSessionStoreMigration.SESSION_STORE_SCHEMA_VERSION) .migration(RealmSessionStoreMigration) + .deleteRealmIfMigrationNeeded() .build() // Try creating a realm instance and if it succeeds we can clear the flag 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 e262b40419..5e4e5ff3b6 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 @@ -110,7 +110,7 @@ internal fun ChunkEntity.addTimelineEvent(roomId: String, true } } - numberOfTimelineEvents++ + //numberOfTimelineEvents++ timelineEvents.add(timelineEventEntity) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/ChunkEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/ChunkEntity.kt index 68533a3c19..8b301d15fa 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/ChunkEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/ChunkEntity.kt @@ -27,9 +27,11 @@ import org.matrix.android.sdk.internal.extensions.clearWith internal open class ChunkEntity(@Index var prevToken: String? = null, // Because of gaps we can have several chunks with nextToken == null @Index var nextToken: String? = null, + var prevChunk: ChunkEntity? = null, + var nextChunk: ChunkEntity? = null, var stateEvents: RealmList = RealmList(), var timelineEvents: RealmList = RealmList(), - var numberOfTimelineEvents: Long = 0, + //var numberOfTimelineEvents: Long = 0, // Only one chunk will have isLastForward == true @Index var isLastForward: Boolean = false, @Index var isLastBackward: Boolean = false 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 8cc5d943b7..141d33ad0e 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 @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Matrix.org Foundation C.I.C. + * 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. @@ -16,168 +16,257 @@ package org.matrix.android.sdk.internal.session.room.timeline -import io.realm.OrderedCollectionChangeSet -import io.realm.OrderedRealmCollectionChangeListener import io.realm.Realm import io.realm.RealmConfiguration -import io.realm.RealmQuery -import io.realm.RealmResults -import io.realm.Sort -import org.matrix.android.sdk.api.MatrixCallback -import org.matrix.android.sdk.api.extensions.orFalse +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.android.asCoroutineDispatcher +import kotlinx.coroutines.cancelChildren +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import okhttp3.internal.closeQuietly import org.matrix.android.sdk.api.extensions.tryOrNull -import org.matrix.android.sdk.api.session.events.model.EventType -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 -import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings -import org.matrix.android.sdk.api.util.CancelableBag -import org.matrix.android.sdk.internal.database.RealmSessionProvider import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper -import org.matrix.android.sdk.internal.database.model.ChunkEntity -import org.matrix.android.sdk.internal.database.model.RoomEntity -import org.matrix.android.sdk.internal.database.model.TimelineEventEntity -import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields -import org.matrix.android.sdk.internal.database.query.findAllInRoomWithSendStates -import org.matrix.android.sdk.internal.database.query.where -import org.matrix.android.sdk.internal.database.query.whereRoomId import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask import org.matrix.android.sdk.internal.session.sync.ReadReceiptHandler -import org.matrix.android.sdk.internal.task.TaskExecutor -import org.matrix.android.sdk.internal.task.configureWith -import org.matrix.android.sdk.internal.util.Debouncer +import org.matrix.android.sdk.internal.task.SemaphoreCoroutineSequencer import org.matrix.android.sdk.internal.util.createBackgroundHandler -import org.matrix.android.sdk.internal.util.createUIHandler import timber.log.Timber -import java.util.Collections import java.util.UUID import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicReference -import kotlin.math.max -private const val MIN_FETCHING_COUNT = 30 - -internal class DefaultTimeline( - private val roomId: String, - private var initialEventId: String? = null, - private val realmConfiguration: RealmConfiguration, - private val taskExecutor: TaskExecutor, - private val contextOfEventTask: GetContextOfEventTask, - private val fetchTokenAndPaginateTask: FetchTokenAndPaginateTask, - private val paginationTask: PaginationTask, - private val timelineEventMapper: TimelineEventMapper, - private val settings: TimelineSettings, - private val timelineInput: TimelineInput, - private val eventDecryptor: TimelineEventDecryptor, - private val realmSessionProvider: RealmSessionProvider, - private val loadRoomMembersTask: LoadRoomMembersTask, - private val readReceiptHandler: ReadReceiptHandler -) : Timeline, - TimelineInput.Listener, - UIEchoManager.Listener { +class DefaultTimeline internal constructor(private val roomId: String, + private val initialEventId: String?, + private val realmConfiguration: RealmConfiguration, + private val loadRoomMembersTask: LoadRoomMembersTask, + private val readReceiptHandler: ReadReceiptHandler, + paginationTask: PaginationTask, + getEventTask: GetContextOfEventTask, + fetchTokenAndPaginateTask: FetchTokenAndPaginateTask, + timelineEventMapper: TimelineEventMapper, + timelineInput: TimelineInput, + eventDecryptor: TimelineEventDecryptor) : Timeline { companion object { - val BACKGROUND_HANDLER = createBackgroundHandler("TIMELINE_DB_THREAD") + val BACKGROUND_HANDLER = createBackgroundHandler("SimpleTimeline_Thread") } - private val listeners = CopyOnWriteArrayList() - private val isStarted = AtomicBoolean(false) - private val isReady = AtomicBoolean(false) - private val mainHandler = createUIHandler() - private val backgroundRealm = AtomicReference() - private val cancelableBag = CancelableBag() - private val debouncer = Debouncer(mainHandler) - - private lateinit var timelineEvents: RealmResults - private lateinit var sendingEvents: RealmResults - - private var prevDisplayIndex: Int? = null - private var nextDisplayIndex: Int? = null - - private val uiEchoManager = UIEchoManager(settings, this) - - private val builtEvents = Collections.synchronizedList(ArrayList()) - private val builtEventsIdMap = Collections.synchronizedMap(HashMap()) - private val backwardsState = AtomicReference(TimelineState()) - private val forwardsState = AtomicReference(TimelineState()) - override val timelineID = UUID.randomUUID().toString() - override val isLive - get() = !hasMoreToLoad(Timeline.Direction.FORWARDS) + private val listeners = CopyOnWriteArrayList() + private val isStarted = AtomicBoolean(false) + private val forwardState = AtomicReference(Timeline.PaginationState()) + private val backwardState = AtomicReference(Timeline.PaginationState()) - private val eventsChangeListener = OrderedRealmCollectionChangeListener> { results, changeSet -> - if (!results.isLoaded || !results.isValid) { - return@OrderedRealmCollectionChangeListener - } - Timber.v("## SendEvent: [${System.currentTimeMillis()}] DB update for room $roomId") - handleUpdates(results, changeSet) + private val backgroundRealm = AtomicReference() + private val timelineDispatcher = BACKGROUND_HANDLER.asCoroutineDispatcher() + private val timelineScope = CoroutineScope(SupervisorJob() + timelineDispatcher) + private val sequencer = SemaphoreCoroutineSequencer() + + private val strategyDependencies = LoadTimelineStrategy.Dependencies( + eventDecryptor = eventDecryptor, + paginationTask = paginationTask, + fetchTokenAndPaginateTask = fetchTokenAndPaginateTask, + timelineInput = timelineInput, + timelineEventMapper = timelineEventMapper, + realm = backgroundRealm, + getContextOfEventTask = getEventTask, + onEventsUpdated = this::postSnapshot + ) + private var strategy: LoadTimelineStrategy = buildStrategy(LoadTimelineStrategy.Mode.Default) + + override val isLive: Boolean + get() = !getPaginationState(Timeline.Direction.FORWARDS).hasMoreToLoad + + override fun addListener(listener: Timeline.Listener): Boolean { + listeners.add(listener) + postSnapshot() + return true } - // Public methods ****************************************************************************** + override fun removeListener(listener: Timeline.Listener): Boolean { + return listeners.remove(listener) + } + + override fun removeAllListeners() { + listeners.clear() + } + + override fun start() { + timelineScope.launch { + loadRoomMemberIfNeeded() + } + timelineScope.launch { + sequencer.post { + if (isStarted.compareAndSet(false, true)) { + val realm = Realm.getInstance(realmConfiguration) + ensureReadReceiptAreLoaded(realm) + backgroundRealm.set(realm) + openAround(initialEventId) + strategy.onStart() + } + } + } + } + + override fun dispose() { + timelineScope.coroutineContext.cancelChildren() + timelineScope.launch { + sequencer.post { + if (isStarted.compareAndSet(true, false)) { + strategy.onStop() + backgroundRealm.get().closeQuietly() + } + } + } + } + + override fun restartWithEventId(eventId: String?) { + timelineScope.launch { + openAround(eventId) + } + } + + override fun hasMoreToLoad(direction: Timeline.Direction): Boolean { + return getPaginationState(direction).hasMoreToLoad + } override fun paginate(direction: Timeline.Direction, count: Int) { - BACKGROUND_HANDLER.post { - if (!canPaginate(direction)) { - return@post - } - Timber.v("Paginate $direction of $count items") - val startDisplayIndex = if (direction == Timeline.Direction.BACKWARDS) prevDisplayIndex else nextDisplayIndex - val shouldPostSnapshot = paginateInternal(startDisplayIndex, direction, count) - if (shouldPostSnapshot) { - postSnapshot() - } + timelineScope.launch { + loadMore(count.toLong(), direction) } } override fun pendingEventCount(): Int { - return realmSessionProvider.withRealm { - RoomEntity.where(it, roomId).findFirst()?.sendingTimelineEvents?.count() ?: 0 - } + return 0 } override fun failedToDeliverEventCount(): Int { - return realmSessionProvider.withRealm { - TimelineEventEntity.findAllInRoomWithSendStates(it, roomId, SendState.HAS_FAILED_STATES).count() + return 0 + } + + override fun getIndexOfEvent(eventId: String?): Int? { + if (eventId == null) return null + return strategy.getBuiltEventIndex(eventId) + } + + override fun getTimelineEventAtIndex(index: Int): TimelineEvent? { + return null + } + + override fun getTimelineEventWithId(eventId: String?): TimelineEvent? { + if (eventId == null) return null + return strategy.getBuiltEvent(eventId) + } + + override fun getPaginationState(direction: Timeline.Direction): Timeline.PaginationState { + return if (direction == Timeline.Direction.BACKWARDS) { + backwardState + } else { + forwardState + }.get() + } + + private suspend fun loadMore(count: Long, direction: Timeline.Direction) = withContext(timelineDispatcher) { + val baseLogMessage = "loadMore(count: $count, direction: $direction, roomId: $roomId)" + Timber.v("$baseLogMessage started") + if (!isStarted.get()) { + throw IllegalStateException("You should call start before using timeline") + } + val currentState = getPaginationState(direction) + if (!currentState.hasMoreToLoad) { + Timber.v("$baseLogMessage : nothing more to load") + return@withContext + } + if (currentState.loading) { + Timber.v("$baseLogMessage : already loading") + return@withContext + } + updateState(direction) { + it.copy(loading = true) + } + val loadMoreResult = strategy.loadMore(count, direction) + val hasMoreToLoad = loadMoreResult != LoadMoreResult.REACHED_END + updateState(direction) { + it.copy(loading = false, hasMoreToLoad = hasMoreToLoad) + } + postSnapshot() + } + + private suspend fun openAround(eventId: String?) = withContext(timelineDispatcher) { + val baseLogMessage = "openAround(eventId: $eventId)" + Timber.v("$baseLogMessage started") + if (!isStarted.get()) { + throw IllegalStateException("You should call start before using timeline") + } + strategy.onStop() + strategy = if (eventId == null) { + buildStrategy(LoadTimelineStrategy.Mode.Default) + } else { + buildStrategy(LoadTimelineStrategy.Mode.Permalink(eventId)) + } + updateState(Timeline.Direction.FORWARDS) { + it.copy(loading = false, hasMoreToLoad = eventId != null) + } + updateState(Timeline.Direction.BACKWARDS) { + it.copy(loading = false, hasMoreToLoad = true) + } + strategy.onStart() + postSnapshot() + } + + private fun postSnapshot() { + timelineScope.launch { + val snapshot = strategy.buildSnapshot() + withContext(Dispatchers.Main) { + listeners.forEach { + tryOrNull { it.onTimelineUpdated(snapshot) } + } + } } } - override fun start() { - if (isStarted.compareAndSet(false, true)) { - Timber.v("Start timeline for roomId: $roomId and eventId: $initialEventId") - timelineInput.listeners.add(this) - BACKGROUND_HANDLER.post { - eventDecryptor.start() - val realm = Realm.getInstance(realmConfiguration) - backgroundRealm.set(realm) - - val roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst() - ?: throw IllegalStateException("Can't open a timeline without a room") - - // We don't want to filter here because some sending events that are not displayed - // are still used for ui echo (relation like reaction) - sendingEvents = roomEntity.sendingTimelineEvents.where()/*.filterEventsWithSettings()*/.findAll() - sendingEvents.addChangeListener { events -> - uiEchoManager.onSentEventsInDatabase(events.map { it.eventId }) - postSnapshot() - } - - timelineEvents = buildEventQuery(realm).sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING).findAll() - timelineEvents.addChangeListener(eventsChangeListener) - handleInitialLoad() - loadRoomMembersTask - .configureWith(LoadRoomMembersTask.Params(roomId)) - .executeBy(taskExecutor) - - // Ensure ReadReceipt from init sync are loaded - ensureReadReceiptAreLoaded(realm) - - isReady.set(true) + private suspend fun updateState(direction: Timeline.Direction, update: (Timeline.PaginationState) -> Timeline.PaginationState) { + val stateReference = when (direction) { + Timeline.Direction.FORWARDS -> forwardState + Timeline.Direction.BACKWARDS -> backwardState + } + val currentValue = stateReference.get() + val newValue = update(currentValue) + stateReference.set(newValue) + withContext(Dispatchers.Main) { + listeners.forEach { + tryOrNull { it.onStateUpdated() } } } } + private fun buildStrategy(mode: LoadTimelineStrategy.Mode): LoadTimelineStrategy { + return LoadTimelineStrategy( + roomId = roomId, + timelineId = timelineID, + mode = mode, + dependencies = strategyDependencies + ) + } + + private suspend fun loadRoomMemberIfNeeded() { + val loadRoomMembersParam = LoadRoomMembersTask.Params(roomId) + try { + loadRoomMembersTask.execute(loadRoomMembersParam) + } catch (failure: Throwable) { + Timber.v("Failed to load room members. Retry in 10s.") + delay(10_000L) + loadRoomMemberIfNeeded() + } + } + private fun ensureReadReceiptAreLoaded(realm: Realm) { readReceiptHandler.getContentFromInitSync(roomId) ?.also { @@ -190,542 +279,4 @@ internal class DefaultTimeline( } } } - - override fun dispose() { - if (isStarted.compareAndSet(true, false)) { - isReady.set(false) - timelineInput.listeners.remove(this) - Timber.v("Dispose timeline for roomId: $roomId and eventId: $initialEventId") - cancelableBag.cancel() - BACKGROUND_HANDLER.removeCallbacksAndMessages(null) - BACKGROUND_HANDLER.post { - if (this::sendingEvents.isInitialized) { - sendingEvents.removeAllChangeListeners() - } - if (this::timelineEvents.isInitialized) { - timelineEvents.removeAllChangeListeners() - } - clearAllValues() - backgroundRealm.getAndSet(null).also { - it?.close() - } - eventDecryptor.destroy() - } - } - } - - override fun restartWithEventId(eventId: String?) { - dispose() - initialEventId = eventId - start() - postSnapshot() - } - - override fun getTimelineEventAtIndex(index: Int): TimelineEvent? { - return builtEvents.getOrNull(index) - } - - override fun getIndexOfEvent(eventId: String?): Int? { - return builtEventsIdMap[eventId] - } - - override fun getTimelineEventWithId(eventId: String?): TimelineEvent? { - return builtEventsIdMap[eventId]?.let { - getTimelineEventAtIndex(it) - } - } - - override fun hasMoreToLoad(direction: Timeline.Direction): Boolean { - return hasMoreInCache(direction) || !hasReachedEnd(direction) - } - - override fun addListener(listener: Timeline.Listener): Boolean { - if (listeners.contains(listener)) { - return false - } - return listeners.add(listener).also { - postSnapshot() - } - } - - override fun removeListener(listener: Timeline.Listener): Boolean { - return listeners.remove(listener) - } - - override fun removeAllListeners() { - listeners.clear() - } - - override fun onNewTimelineEvents(roomId: String, eventIds: List) { - if (isLive && this.roomId == roomId) { - listeners.forEach { - it.onNewTimelineEvents(eventIds) - } - } - } - - override fun onLocalEchoCreated(roomId: String, timelineEvent: TimelineEvent) { - if (roomId != this.roomId || !isLive) return - uiEchoManager.onLocalEchoCreated(timelineEvent) - listeners.forEach { - tryOrNull { - it.onNewTimelineEvents(listOf(timelineEvent.eventId)) - } - } - postSnapshot() - } - - override fun onLocalEchoUpdated(roomId: String, eventId: String, sendState: SendState) { - if (roomId != this.roomId || !isLive) return - if (uiEchoManager.onSendStateUpdated(eventId, sendState)) { - postSnapshot() - } - } - - override fun rebuildEvent(eventId: String, builder: (TimelineEvent) -> TimelineEvent?): Boolean { - return tryOrNull { - builtEventsIdMap[eventId]?.let { builtIndex -> - // Update the relation of existing event - builtEvents[builtIndex]?.let { te -> - val rebuiltEvent = builder(te) - // If rebuilt event is filtered its returned as null and should be removed. - if (rebuiltEvent == null) { - builtEventsIdMap.remove(eventId) - builtEventsIdMap.entries.filter { it.value > builtIndex }.forEach { it.setValue(it.value - 1) } - builtEvents.removeAt(builtIndex) - } else { - builtEvents[builtIndex] = rebuiltEvent - } - true - } - } - } ?: false - } - -// Private methods ***************************************************************************** - - private fun hasMoreInCache(direction: Timeline.Direction) = getState(direction).hasMoreInCache - - private fun hasReachedEnd(direction: Timeline.Direction) = getState(direction).hasReachedEnd - - private fun updateLoadingStates(results: RealmResults) { - val lastCacheEvent = results.lastOrNull() - val firstCacheEvent = results.firstOrNull() - val chunkEntity = getLiveChunk() - - updateState(Timeline.Direction.FORWARDS) { - it.copy( - hasMoreInCache = !builtEventsIdMap.containsKey(firstCacheEvent?.eventId), - hasReachedEnd = chunkEntity?.isLastForward ?: false - ) - } - updateState(Timeline.Direction.BACKWARDS) { - it.copy( - hasMoreInCache = !builtEventsIdMap.containsKey(lastCacheEvent?.eventId), - hasReachedEnd = chunkEntity?.isLastBackward ?: false || lastCacheEvent?.root?.type == EventType.STATE_ROOM_CREATE - ) - } - } - - /** - * This has to be called on TimelineThread as it accesses realm live results - * @return true if createSnapshot should be posted - */ - private fun paginateInternal(startDisplayIndex: Int?, - direction: Timeline.Direction, - count: Int): Boolean { - if (count == 0) { - return false - } - updateState(direction) { it.copy(requestedPaginationCount = count, isPaginating = true) } - val builtCount = buildTimelineEvents(startDisplayIndex, direction, count.toLong()) - val shouldFetchMore = builtCount < count && !hasReachedEnd(direction) - if (shouldFetchMore) { - val newRequestedCount = count - builtCount - updateState(direction) { it.copy(requestedPaginationCount = newRequestedCount) } - val fetchingCount = max(MIN_FETCHING_COUNT, newRequestedCount) - executePaginationTask(direction, fetchingCount) - } else { - updateState(direction) { it.copy(isPaginating = false, requestedPaginationCount = 0) } - } - return !shouldFetchMore - } - - private fun createSnapshot(): List { - return buildSendingEvents() + builtEvents.toList() - } - - private fun buildSendingEvents(): List { - val builtSendingEvents = mutableListOf() - if (hasReachedEnd(Timeline.Direction.FORWARDS) && !hasMoreInCache(Timeline.Direction.FORWARDS)) { - uiEchoManager.getInMemorySendingEvents() - .updateWithUiEchoInto(builtSendingEvents) - sendingEvents - .filter { timelineEvent -> - builtSendingEvents.none { it.eventId == timelineEvent.eventId } - } - .map { timelineEventMapper.map(it) } - .updateWithUiEchoInto(builtSendingEvents) - } - return builtSendingEvents - } - - private fun List.updateWithUiEchoInto(target: MutableList) { - target.addAll( - // Get most up to date send state (in memory) - map { uiEchoManager.updateSentStateWithUiEcho(it) } - ) - } - - private fun canPaginate(direction: Timeline.Direction): Boolean { - return isReady.get() && !getState(direction).isPaginating && hasMoreToLoad(direction) - } - - private fun getState(direction: Timeline.Direction): TimelineState { - return when (direction) { - Timeline.Direction.FORWARDS -> forwardsState.get() - Timeline.Direction.BACKWARDS -> backwardsState.get() - } - } - - private fun updateState(direction: Timeline.Direction, update: (TimelineState) -> TimelineState) { - val stateReference = when (direction) { - Timeline.Direction.FORWARDS -> forwardsState - Timeline.Direction.BACKWARDS -> backwardsState - } - val currentValue = stateReference.get() - val newValue = update(currentValue) - stateReference.set(newValue) - } - - /** - * This has to be called on TimelineThread as it accesses realm live results - */ - private fun handleInitialLoad() { - var shouldFetchInitialEvent = false - val currentInitialEventId = initialEventId - val initialDisplayIndex = if (currentInitialEventId == null) { - timelineEvents.firstOrNull()?.displayIndex - } else { - val initialEvent = timelineEvents.where() - .equalTo(TimelineEventEntityFields.EVENT_ID, initialEventId) - .findFirst() - - shouldFetchInitialEvent = initialEvent == null - initialEvent?.displayIndex - } - prevDisplayIndex = initialDisplayIndex - nextDisplayIndex = initialDisplayIndex - if (currentInitialEventId != null && shouldFetchInitialEvent) { - fetchEvent(currentInitialEventId) - } else { - val count = timelineEvents.size.coerceAtMost(settings.initialSize) - if (initialEventId == null) { - paginateInternal(initialDisplayIndex, Timeline.Direction.BACKWARDS, count) - } else { - paginateInternal(initialDisplayIndex, Timeline.Direction.FORWARDS, (count / 2).coerceAtLeast(1)) - paginateInternal(initialDisplayIndex?.minus(1), Timeline.Direction.BACKWARDS, (count / 2).coerceAtLeast(1)) - } - } - postSnapshot() - } - - /** - * This has to be called on TimelineThread as it accesses realm live results - */ - private fun handleUpdates(results: RealmResults, changeSet: OrderedCollectionChangeSet) { - // If changeSet has deletion we are having a gap, so we clear everything - if (changeSet.deletionRanges.isNotEmpty()) { - clearAllValues() - } - var postSnapshot = false - changeSet.insertionRanges.forEach { range -> - val (startDisplayIndex, direction) = if (range.startIndex == 0) { - Pair(results[range.length - 1]!!.displayIndex, Timeline.Direction.FORWARDS) - } else { - Pair(results[range.startIndex]!!.displayIndex, Timeline.Direction.BACKWARDS) - } - val state = getState(direction) - if (state.isPaginating) { - // We are getting new items from pagination - postSnapshot = paginateInternal(startDisplayIndex, direction, state.requestedPaginationCount) - } else { - // We are getting new items from sync - buildTimelineEvents(startDisplayIndex, direction, range.length.toLong()) - postSnapshot = true - } - } - changeSet.changes.forEach { index -> - val eventEntity = results[index] - eventEntity?.eventId?.let { eventId -> - postSnapshot = rebuildEvent(eventId) { - buildTimelineEvent(eventEntity) - } || postSnapshot - } - } - if (postSnapshot) { - postSnapshot() - } - } - - /** - * This has to be called on TimelineThread as it accesses realm live results - */ - private fun executePaginationTask(direction: Timeline.Direction, limit: Int) { - val currentChunk = getLiveChunk() - val token = if (direction == Timeline.Direction.BACKWARDS) currentChunk?.prevToken else currentChunk?.nextToken - if (token == null) { - if (direction == Timeline.Direction.BACKWARDS - || (direction == Timeline.Direction.FORWARDS && currentChunk?.hasBeenALastForwardChunk().orFalse())) { - // We are in the case where event exists, but we do not know the token. - // Fetch (again) the last event to get a token - val lastKnownEventId = if (direction == Timeline.Direction.FORWARDS) { - timelineEvents.firstOrNull()?.eventId - } else { - timelineEvents.lastOrNull()?.eventId - } - if (lastKnownEventId == null) { - updateState(direction) { it.copy(isPaginating = false, requestedPaginationCount = 0) } - } else { - val params = FetchTokenAndPaginateTask.Params( - roomId = roomId, - limit = limit, - direction = direction.toPaginationDirection(), - lastKnownEventId = lastKnownEventId - ) - cancelableBag += fetchTokenAndPaginateTask - .configureWith(params) { - this.callback = createPaginationCallback(limit, direction) - } - .executeBy(taskExecutor) - } - } else { - updateState(direction) { it.copy(isPaginating = false, requestedPaginationCount = 0) } - } - } else { - val params = PaginationTask.Params( - roomId = roomId, - from = token, - direction = direction.toPaginationDirection(), - limit = limit - ) - Timber.v("Should fetch $limit items $direction") - cancelableBag += paginationTask - .configureWith(params) { - this.callback = createPaginationCallback(limit, direction) - } - .executeBy(taskExecutor) - } - } - - // For debug purpose only - private fun dumpAndLogChunks() { - val liveChunk = getLiveChunk() - Timber.w("Live chunk: $liveChunk") - - Realm.getInstance(realmConfiguration).use { realm -> - ChunkEntity.where(realm, roomId).findAll() - .also { Timber.w("Found ${it.size} chunks") } - .forEach { - Timber.w("") - Timber.w("ChunkEntity: $it") - Timber.w("prevToken: ${it.prevToken}") - Timber.w("nextToken: ${it.nextToken}") - Timber.w("isLastBackward: ${it.isLastBackward}") - Timber.w("isLastForward: ${it.isLastForward}") - it.timelineEvents.forEach { tle -> - Timber.w(" TLE: ${tle.root?.content}") - } - } - } - } - - /** - * This has to be called on TimelineThread as it accesses realm live results - */ - private fun getTokenLive(direction: Timeline.Direction): String? { - val chunkEntity = getLiveChunk() ?: return null - return if (direction == Timeline.Direction.BACKWARDS) chunkEntity.prevToken else chunkEntity.nextToken - } - - /** - * This has to be called on TimelineThread as it accesses realm live results - * Return the current Chunk - */ - private fun getLiveChunk(): ChunkEntity? { - return timelineEvents.firstOrNull()?.chunk?.firstOrNull() - } - - /** - * This has to be called on TimelineThread as it accesses realm live results - * @return the number of items who have been added - */ - private fun buildTimelineEvents(startDisplayIndex: Int?, - direction: Timeline.Direction, - count: Long): Int { - if (count < 1 || startDisplayIndex == null) { - return 0 - } - val start = System.currentTimeMillis() - val offsetResults = getOffsetResults(startDisplayIndex, direction, count) - if (offsetResults.isEmpty()) { - return 0 - } - val offsetIndex = offsetResults.last()!!.displayIndex - if (direction == Timeline.Direction.BACKWARDS) { - prevDisplayIndex = offsetIndex - 1 - } else { - nextDisplayIndex = offsetIndex + 1 - } - offsetResults.forEach { eventEntity -> - - val timelineEvent = buildTimelineEvent(eventEntity) - val transactionId = timelineEvent.root.unsignedData?.transactionId - uiEchoManager.onSyncedEvent(transactionId) - - if (timelineEvent.isEncrypted() - && timelineEvent.root.mxDecryptionResult == null) { - timelineEvent.root.eventId?.also { eventDecryptor.requestDecryption(TimelineEventDecryptor.DecryptionRequest(timelineEvent.root, timelineID)) } - } - - val position = if (direction == Timeline.Direction.FORWARDS) 0 else builtEvents.size - builtEvents.add(position, timelineEvent) - // Need to shift :/ - builtEventsIdMap.entries.filter { it.value >= position }.forEach { it.setValue(it.value + 1) } - builtEventsIdMap[eventEntity.eventId] = position - } - val time = System.currentTimeMillis() - start - Timber.v("Built ${offsetResults.size} items from db in $time ms") - // For the case where wo reach the lastForward chunk - updateLoadingStates(timelineEvents) - return offsetResults.size - } - - private fun buildTimelineEvent(eventEntity: TimelineEventEntity): TimelineEvent { - return timelineEventMapper.map( - timelineEventEntity = eventEntity, - buildReadReceipts = settings.buildReadReceipts - ).let { timelineEvent -> - // eventually enhance with ui echo? - uiEchoManager.decorateEventWithReactionUiEcho(timelineEvent) ?: timelineEvent - } - } - - /** - * This has to be called on TimelineThread as it accesses realm live results - */ - private fun getOffsetResults(startDisplayIndex: Int, - direction: Timeline.Direction, - count: Long): RealmResults { - val offsetQuery = timelineEvents.where() - if (direction == Timeline.Direction.BACKWARDS) { - offsetQuery - .sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING) - .lessThanOrEqualTo(TimelineEventEntityFields.DISPLAY_INDEX, startDisplayIndex) - } else { - offsetQuery - .sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.ASCENDING) - .greaterThanOrEqualTo(TimelineEventEntityFields.DISPLAY_INDEX, startDisplayIndex) - } - return offsetQuery - .limit(count) - .findAll() - } - - private fun buildEventQuery(realm: Realm): RealmQuery { - return if (initialEventId == null) { - TimelineEventEntity - .whereRoomId(realm, roomId = roomId) - .equalTo(TimelineEventEntityFields.CHUNK.IS_LAST_FORWARD, true) - } else { - TimelineEventEntity - .whereRoomId(realm, roomId = roomId) - .`in`("${TimelineEventEntityFields.CHUNK.TIMELINE_EVENTS}.${TimelineEventEntityFields.EVENT_ID}", arrayOf(initialEventId)) - } - } - - private fun fetchEvent(eventId: String) { - val params = GetContextOfEventTask.Params(roomId, eventId) - cancelableBag += contextOfEventTask.configureWith(params) { - callback = object : MatrixCallback { - override fun onSuccess(data: TokenChunkEventPersistor.Result) { - postSnapshot() - } - - override fun onFailure(failure: Throwable) { - postFailure(failure) - } - } - } - .executeBy(taskExecutor) - } - - private fun postSnapshot() { - BACKGROUND_HANDLER.post { - if (isReady.get().not()) { - return@post - } - updateLoadingStates(timelineEvents) - val snapshot = createSnapshot() - val runnable = Runnable { - listeners.forEach { - it.onTimelineUpdated(snapshot) - } - } - debouncer.debounce("post_snapshot", runnable, 1) - } - } - - private fun postFailure(throwable: Throwable) { - if (isReady.get().not()) { - return - } - val runnable = Runnable { - listeners.forEach { - it.onTimelineFailure(throwable) - } - } - mainHandler.post(runnable) - } - - private fun clearAllValues() { - prevDisplayIndex = null - nextDisplayIndex = null - builtEvents.clear() - builtEventsIdMap.clear() - backwardsState.set(TimelineState()) - forwardsState.set(TimelineState()) - } - - private fun createPaginationCallback(limit: Int, direction: Timeline.Direction): MatrixCallback { - return object : MatrixCallback { - override fun onSuccess(data: TokenChunkEventPersistor.Result) { - when (data) { - TokenChunkEventPersistor.Result.SUCCESS -> { - Timber.v("Success fetching $limit items $direction from pagination request") - } - TokenChunkEventPersistor.Result.REACHED_END -> { - postSnapshot() - } - TokenChunkEventPersistor.Result.SHOULD_FETCH_MORE -> - // Database won't be updated, so we force pagination request - BACKGROUND_HANDLER.post { - executePaginationTask(direction, limit) - } - } - } - - override fun onFailure(failure: Throwable) { - updateState(direction) { it.copy(isPaginating = false, requestedPaginationCount = 0) } - postSnapshot() - Timber.v("Failure fetching $limit items $direction from pagination request") - } - } - } - - // Extension methods *************************************************************************** - - private fun Timeline.Direction.toPaginationDirection(): PaginationDirection { - return if (this == Timeline.Direction.BACKWARDS) PaginationDirection.BACKWARDS else PaginationDirection.FORWARDS - } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt index 8de36d0427..9c64dce388 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt @@ -65,17 +65,14 @@ internal class DefaultTimelineService @AssistedInject constructor( roomId = roomId, initialEventId = eventId, realmConfiguration = monarchy.realmConfiguration, - taskExecutor = taskExecutor, - contextOfEventTask = contextOfEventTask, paginationTask = paginationTask, timelineEventMapper = timelineEventMapper, - settings = settings, timelineInput = timelineInput, eventDecryptor = eventDecryptor, fetchTokenAndPaginateTask = fetchTokenAndPaginateTask, - realmSessionProvider = realmSessionProvider, loadRoomMembersTask = loadRoomMembersTask, - readReceiptHandler = readReceiptHandler + readReceiptHandler = readReceiptHandler, + getEventTask = contextOfEventTask ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineState.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadMoreResult.kt similarity index 70% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineState.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadMoreResult.kt index 0143d9bab3..8b0cf0715b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineState.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadMoreResult.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 The Matrix.org Foundation C.I.C. + * 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. @@ -16,9 +16,8 @@ package org.matrix.android.sdk.internal.session.room.timeline -internal data class TimelineState( - val hasReachedEnd: Boolean = false, - val hasMoreInCache: Boolean = true, - val isPaginating: Boolean = false, - val requestedPaginationCount: Int = 0 -) +internal enum class LoadMoreResult { + REACHED_END, + SUCCESS, + FAILURE +} 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 new file mode 100644 index 0000000000..ce1ef7f186 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt @@ -0,0 +1,198 @@ +/* + * 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 org.matrix.android.sdk.internal.session.room.timeline + +import io.realm.OrderedCollectionChangeSet +import io.realm.OrderedRealmCollectionChangeListener +import io.realm.Realm +import io.realm.RealmResults +import org.matrix.android.sdk.api.extensions.orFalse +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 +import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings +import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper +import org.matrix.android.sdk.internal.database.model.ChunkEntity +import org.matrix.android.sdk.internal.database.model.ChunkEntityFields +import org.matrix.android.sdk.internal.database.query.findAllIncludingEvents +import org.matrix.android.sdk.internal.database.query.where +import java.util.concurrent.atomic.AtomicReference + +internal class LoadTimelineStrategy( + private val roomId: String, + private val timelineId: String, + private val mode: Mode, + private val dependencies: Dependencies) { + + sealed class Mode { + object Default : Mode() + data class Permalink(val originEventId: String) : Mode() + + fun originEventId(): String? { + return if (this is Permalink) { + originEventId + } else { + null + } + } + } + + data class Dependencies( + val realm: AtomicReference, + val eventDecryptor: TimelineEventDecryptor, + val paginationTask: PaginationTask, + val fetchTokenAndPaginateTask: FetchTokenAndPaginateTask, + val getContextOfEventTask: GetContextOfEventTask, + val timelineInput: TimelineInput, + val timelineEventMapper: TimelineEventMapper, + val onEventsUpdated: () -> Unit + ) + + private var chunkEntity: RealmResults? = null + private var timelineChunk: TimelineChunk? = null + + private val chunkEntityListener = OrderedRealmCollectionChangeListener { _: RealmResults, changeSet: OrderedCollectionChangeSet -> + val shouldRebuildChunk = changeSet.insertions.isNotEmpty() + if (shouldRebuildChunk) { + timelineChunk?.close(closeNext = true, closePrev = true) + timelineChunk = chunkEntity?.createTimelineChunk() + dependencies.onEventsUpdated() + } + } + + private val uiEchoManagerListener = object : UIEchoManager.Listener { + override fun rebuildEvent(eventId: String, builder: (TimelineEvent) -> TimelineEvent?): Boolean { + return timelineChunk?.rebuildEvent(eventId, builder, searchInNext = true, searchInPrev = true).orFalse() + } + } + + private val timelineInputListener = object : TimelineInput.Listener { + override fun onLocalEchoCreated(roomId: String, timelineEvent: TimelineEvent) { + if (roomId != this@LoadTimelineStrategy.roomId) { + return + } + if (uiEchoManager.onLocalEchoCreated(timelineEvent)) { + dependencies.onEventsUpdated() + } + } + + override fun onLocalEchoUpdated(roomId: String, eventId: String, sendState: SendState) { + if (roomId != this@LoadTimelineStrategy.roomId) { + return + } + if (uiEchoManager.onSendStateUpdated(eventId, sendState)) { + dependencies.onEventsUpdated() + } + } + } + + private val uiEchoManager = UIEchoManager(TimelineSettings(10), uiEchoManagerListener) + private val sendingEventsDataSource: SendingEventsDataSource = RealmSendingEventsDataSource( + roomId = roomId, + realm = dependencies.realm, + uiEchoManager = uiEchoManager, + timelineEventMapper = dependencies.timelineEventMapper, + onEventsUpdated = dependencies.onEventsUpdated + ) + + suspend fun onStart() { + dependencies.eventDecryptor.start() + dependencies.timelineInput.listeners.add(timelineInputListener) + val realm = dependencies.realm.get() + sendingEventsDataSource.start() + chunkEntity = getChunkEntity(realm).also { + it.addChangeListener(chunkEntityListener) + timelineChunk = it.createTimelineChunk() + } + if(mode is Mode.Default){ + loadMore(10, Timeline.Direction.BACKWARDS) + } + } + + fun onStop() { + dependencies.eventDecryptor.destroy() + dependencies.timelineInput.listeners.remove(timelineInputListener) + chunkEntity?.removeChangeListener(chunkEntityListener) + sendingEventsDataSource.stop() + timelineChunk?.close(closeNext = true, closePrev = true) + chunkEntity = null + timelineChunk = null + } + + suspend fun loadMore(count: Long, direction: Timeline.Direction): LoadMoreResult { + return if (mode is Mode.Permalink && timelineChunk == null) { + val params = GetContextOfEventTask.Params(roomId, mode.originEventId) + try { + dependencies.getContextOfEventTask.execute(params) + LoadMoreResult.SUCCESS + } catch (failure: Throwable) { + LoadMoreResult.FAILURE + } + } else { + timelineChunk?.loadMore(count, direction) ?: LoadMoreResult.FAILURE + } + } + + fun getBuiltEventIndex(eventId: String): Int? { + return timelineChunk?.getBuiltEventIndex(eventId, searchInNext = true, searchInPrev = true) + } + + fun getBuiltEvent(eventId: String): TimelineEvent? { + return timelineChunk?.getBuiltEvent(eventId, searchInNext = true, searchInPrev = true) + } + + fun buildSnapshot(): List { + return buildSendingEvents() + timelineChunk?.builtItems(includesNext = true, includesPrev = true).orEmpty() + } + + private fun buildSendingEvents(): List { + return if (timelineChunk?.hasReachedLastForward().orFalse()) { + sendingEventsDataSource.buildSendingEvents() + } else { + emptyList() + } + } + + private fun getChunkEntity(realm: Realm): RealmResults { + return if (mode is Mode.Permalink) { + ChunkEntity.findAllIncludingEvents(realm, listOf(mode.originEventId)) + } else { + ChunkEntity.where(realm, roomId) + .equalTo(ChunkEntityFields.IS_LAST_FORWARD, true) + .findAll() + } + } + + private fun RealmResults.createTimelineChunk(): TimelineChunk? { + return firstOrNull()?.let { + return TimelineChunk( + chunkEntity = it, + roomId = roomId, + timelineId = timelineId, + eventDecryptor = dependencies.eventDecryptor, + paginationTask = dependencies.paginationTask, + fetchTokenAndPaginateTask = dependencies.fetchTokenAndPaginateTask, + timelineEventMapper = dependencies.timelineEventMapper, + uiEchoManager = uiEchoManager, + initialEventId = mode.originEventId(), + onBuiltEvents = dependencies.onEventsUpdated + ) + } + } +} + + diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/SendingEventsDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/SendingEventsDataSource.kt new file mode 100644 index 0000000000..58298609a0 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/SendingEventsDataSource.kt @@ -0,0 +1,84 @@ +/* + * 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 org.matrix.android.sdk.internal.session.room.timeline + +import io.realm.Realm +import io.realm.RealmChangeListener +import io.realm.RealmList +import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent +import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper +import org.matrix.android.sdk.internal.database.model.RoomEntity +import org.matrix.android.sdk.internal.database.model.TimelineEventEntity +import org.matrix.android.sdk.internal.database.query.where +import java.util.concurrent.atomic.AtomicReference + +internal interface SendingEventsDataSource { + fun start() + fun stop() + fun buildSendingEvents(): List +} + +internal class RealmSendingEventsDataSource( + private val roomId: String, + private val realm: AtomicReference, + private val uiEchoManager: UIEchoManager, + private val timelineEventMapper: TimelineEventMapper, + private val onEventsUpdated: () -> Unit +) : SendingEventsDataSource { + + private var roomEntity: RoomEntity? = null + private var sendingTimelineEvents: RealmList? = null + + private val sendingTimelineEventsListener = RealmChangeListener> { events -> + uiEchoManager.onSentEventsInDatabase(events.map { it.eventId }) + onEventsUpdated() + } + + override fun start() { + val safeRealm = realm.get() + roomEntity = RoomEntity.where(safeRealm, roomId = roomId).findFirst() + sendingTimelineEvents = roomEntity?.sendingTimelineEvents + sendingTimelineEvents?.addChangeListener(sendingTimelineEventsListener) + } + + override fun stop() { + sendingTimelineEvents?.removeChangeListener(sendingTimelineEventsListener) + sendingTimelineEvents = null + roomEntity = null + } + + override fun buildSendingEvents(): List { + val builtSendingEvents = mutableListOf() + uiEchoManager.getInMemorySendingEvents() + .addWithUiEcho(builtSendingEvents) + sendingTimelineEvents?.freeze() + ?.filter { timelineEvent -> + builtSendingEvents.none { it.eventId == timelineEvent.eventId } + } + ?.map { + timelineEventMapper.map(it) + }?.addWithUiEcho(builtSendingEvents) + + return builtSendingEvents + } + + private fun List.addWithUiEcho(target: MutableList) { + target.addAll( + map { uiEchoManager.updateSentStateWithUiEcho(it) } + ) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt new file mode 100644 index 0000000000..6c337b3663 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt @@ -0,0 +1,362 @@ +/* + * 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 org.matrix.android.sdk.internal.session.room.timeline + +import io.realm.OrderedCollectionChangeSet +import io.realm.OrderedRealmCollectionChangeListener +import io.realm.RealmObjectChangeListener +import io.realm.RealmQuery +import io.realm.RealmResults +import io.realm.Sort +import org.matrix.android.sdk.api.extensions.orFalse +import org.matrix.android.sdk.api.extensions.tryOrNull +import org.matrix.android.sdk.api.session.room.timeline.Timeline +import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent +import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper +import org.matrix.android.sdk.internal.database.model.ChunkEntity +import org.matrix.android.sdk.internal.database.model.TimelineEventEntity +import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields +import timber.log.Timber +import java.util.Collections + +/** + * This is the value used to fetch on server. It's better to make constant as otherwise we can have weird chunks with disparate and small chunk of data. + */ +private const val PAGINATION_COUNT = 50 + +internal class TimelineChunk constructor(private val chunkEntity: ChunkEntity, + private val roomId: String, + private val timelineId: String, + private val eventDecryptor: TimelineEventDecryptor, + private val paginationTask: PaginationTask, + private val fetchTokenAndPaginateTask: FetchTokenAndPaginateTask, + private val timelineEventMapper: TimelineEventMapper, + private val uiEchoManager: UIEchoManager? = null, + private val initialEventId: String?, + private val onBuiltEvents: () -> Unit) { + + private val chunkObjectListener = RealmObjectChangeListener { _, changeSet -> + Timber.v("on chunk (${chunkEntity.identifier()}) changed: ${changeSet?.changedFields?.joinToString(",")}") + } + + private val timelineEventCollectionListener = OrderedRealmCollectionChangeListener { results: RealmResults, changeSet: OrderedCollectionChangeSet -> + val frozenResults = results.freeze() + Timber.v("on timeline event changed: $changeSet") + handleChangeSet(frozenResults, changeSet) + } + + private var timelineEventEntities: RealmResults = chunkEntity.sortedTimelineEvents() + private val builtEvents: MutableList = Collections.synchronizedList(ArrayList()) + private val builtEventsIndexes: MutableMap = Collections.synchronizedMap(HashMap()) + + private var nextChunk: TimelineChunk? = null + private var prevChunk: TimelineChunk? = null + + init { + timelineEventEntities.addChangeListener(timelineEventCollectionListener) + chunkEntity.addChangeListener(chunkObjectListener) + } + + fun hasReachedLastForward(): Boolean { + return if (chunkEntity.isLastForward) { + true + } else { + nextChunk?.hasReachedLastForward().orFalse() + } + } + + fun builtItems(includesNext: Boolean, includesPrev: Boolean): List { + val deepBuiltItems = ArrayList(builtEvents.size) + if (includesNext) { + val nextEvents = nextChunk?.builtItems(includesNext = true, includesPrev = false).orEmpty() + deepBuiltItems.addAll(nextEvents) + } + deepBuiltItems.addAll(builtEvents) + if (includesPrev) { + val prevEvents = prevChunk?.builtItems(includesNext = false, includesPrev = true).orEmpty() + deepBuiltItems.addAll(prevEvents) + } + return deepBuiltItems + } + + suspend fun loadMore(count: Long, direction: Timeline.Direction): LoadMoreResult { + val loadFromDbCount = loadFromDb(count, direction) + val offsetCount = count - loadFromDbCount + // We have built the right amount of data + if (offsetCount == 0L) { + onBuiltEvents() + return LoadMoreResult.SUCCESS + } + return if (direction == Timeline.Direction.FORWARDS) { + val nextChunkEntity = chunkEntity.nextChunk + if (nextChunkEntity == null) { + val token = chunkEntity.nextToken ?: return LoadMoreResult.REACHED_END // TODO handle previous live chunk + try { + fetchFromServer(token, direction) + } catch (failure: Throwable) { + Timber.v("Failed to fetch from server: $failure") + LoadMoreResult.FAILURE + } + } else { + // otherwise we delegate to the next chunk + if (nextChunk == null) { + nextChunk = createTimelineChunk(nextChunkEntity) + } + nextChunk?.loadMore(offsetCount, direction) ?: LoadMoreResult.FAILURE + } + } else { + val prevChunkEntity = chunkEntity.prevChunk + if (prevChunkEntity == null) { + val token = chunkEntity.prevToken ?: return LoadMoreResult.REACHED_END + try { + fetchFromServer(token, direction) + } catch (failure: Throwable) { + Timber.v("Failed to fetch from server: $failure") + LoadMoreResult.FAILURE + } + } else { + // otherwise we delegate to the prev chunk + if (prevChunk == null) { + prevChunk = createTimelineChunk(prevChunkEntity) + } + prevChunk?.loadMore(offsetCount, direction) ?: LoadMoreResult.FAILURE + } + } + } + + private fun loadFromDb(count: Long, direction: Timeline.Direction): Long { + val displayIndex = getNextDisplayIndex(direction) ?: return 0 + val baseQuery = timelineEventEntities.where() + val timelineEvents = baseQuery.offsets(direction, count, displayIndex).findAll().orEmpty() + if (timelineEvents.isEmpty()) return 0 + if (direction == Timeline.Direction.FORWARDS) { + builtEventsIndexes.entries.forEach { it.setValue(it.value + timelineEvents.size) } + } + timelineEvents + .mapIndexed { index, timelineEventEntity -> + val timelineEvent = timelineEventEntity.buildAndDecryptIfNeeded() + if (direction == Timeline.Direction.FORWARDS) { + builtEventsIndexes[timelineEvent.eventId] = index + builtEvents.add(index, timelineEvent) + } else { + builtEventsIndexes[timelineEvent.eventId] = builtEvents.size + builtEvents.add(timelineEvent) + } + } + return timelineEvents.size.toLong() + } + + private fun TimelineEventEntity.buildAndDecryptIfNeeded(): TimelineEvent { + val timelineEvent = buildTimelineEvent(this) + val transactionId = timelineEvent.root.unsignedData?.transactionId + uiEchoManager?.onSyncedEvent(transactionId) + if (timelineEvent.isEncrypted() + && timelineEvent.root.mxDecryptionResult == null) { + timelineEvent.root.eventId?.also { eventDecryptor.requestDecryption(TimelineEventDecryptor.DecryptionRequest(timelineEvent.root, timelineId)) } + } + return timelineEvent + } + + private fun buildTimelineEvent(eventEntity: TimelineEventEntity) = timelineEventMapper.map( + timelineEventEntity = eventEntity + ).let { + // eventually enhance with ui echo? + (uiEchoManager?.decorateEventWithReactionUiEcho(it) ?: it) + } + + private fun createTimelineChunk(chunkEntity: ChunkEntity): TimelineChunk { + return TimelineChunk( + chunkEntity = chunkEntity, + timelineId = timelineId, + eventDecryptor = eventDecryptor, + roomId = roomId, + paginationTask = paginationTask, + fetchTokenAndPaginateTask = fetchTokenAndPaginateTask, + timelineEventMapper = timelineEventMapper, + uiEchoManager = uiEchoManager, + initialEventId = null, + onBuiltEvents = onBuiltEvents + ) + } + + private suspend fun fetchFromServer(token: String, direction: Timeline.Direction): LoadMoreResult { + val paginationParams = PaginationTask.Params(roomId, token, direction.toPaginationDirection(), PAGINATION_COUNT) + val paginationResult = paginationTask.execute(paginationParams) + return when (paginationResult) { + TokenChunkEventPersistor.Result.REACHED_END -> LoadMoreResult.REACHED_END + TokenChunkEventPersistor.Result.SHOULD_FETCH_MORE, + TokenChunkEventPersistor.Result.SUCCESS -> LoadMoreResult.SUCCESS + } + } + + fun getBuiltEventIndex(eventId: String, searchInNext: Boolean, searchInPrev: Boolean): Int? { + val builtEventIndex = builtEventsIndexes[eventId] + if (builtEventIndex != null) { + return getOffsetIndex() + builtEventIndex + } + if (searchInNext) { + val nextBuiltEventIndex = nextChunk?.getBuiltEventIndex(eventId, searchInNext = true, searchInPrev = false) + if (nextBuiltEventIndex != null) { + return nextBuiltEventIndex + } + } + if (searchInPrev) { + val prevBuiltEventIndex = prevChunk?.getBuiltEventIndex(eventId, searchInNext = false, searchInPrev = true) + if (prevBuiltEventIndex != null) { + return prevBuiltEventIndex + } + } + return null + } + + fun getBuiltEvent(eventId: String, searchInNext: Boolean, searchInPrev: Boolean): TimelineEvent? { + val builtEventIndex = builtEventsIndexes[eventId] + if (builtEventIndex != null) { + return builtEvents.getOrNull(builtEventIndex) + } + if (searchInNext) { + val nextBuiltEvent = nextChunk?.getBuiltEvent(eventId, searchInNext = true, searchInPrev = false) + if (nextBuiltEvent != null) { + return nextBuiltEvent + } + } + if (searchInPrev) { + val prevBuiltEvent = prevChunk?.getBuiltEvent(eventId, searchInNext = false, searchInPrev = true) + if (prevBuiltEvent != null) { + return prevBuiltEvent + } + } + return null + } + + private fun getOffsetIndex(): Int { + var offset = 0 + var currentNextChunk = nextChunk + while (currentNextChunk != null) { + offset += currentNextChunk.builtEvents.size + currentNextChunk = currentNextChunk.nextChunk + } + return offset + } + + private fun handleChangeSet(frozenResults: RealmResults, changeSet: OrderedCollectionChangeSet) { + val insertions = changeSet.insertionRanges + for (range in insertions) { + val newItems = frozenResults + .subList(range.startIndex, range.startIndex + range.length) + .map { it.buildAndDecryptIfNeeded() } + builtEventsIndexes.entries.filter { it.value >= range.startIndex }.forEach { it.setValue(it.value + range.length) } + newItems.mapIndexed { index, timelineEvent -> + val correctedIndex = range.startIndex + index + builtEvents.add(correctedIndex, timelineEvent) + builtEventsIndexes[timelineEvent.eventId] = correctedIndex + } + } + val modifications = changeSet.changeRanges + for (range in modifications) { + for (modificationIndex in (range.startIndex until range.startIndex + range.length)) { + val updatedEntity = frozenResults[modificationIndex] ?: continue + try { + builtEvents[modificationIndex] = updatedEntity.buildAndDecryptIfNeeded() + } catch (failure: Throwable) { + Timber.v("Fail to update items at index: $modificationIndex") + } + } + } + if (insertions.isNotEmpty() || modifications.isNotEmpty()) { + onBuiltEvents() + } + } + + fun rebuildEvent(eventId: String, builder: (TimelineEvent) -> TimelineEvent?, searchInNext: Boolean, searchInPrev: Boolean): Boolean { + return tryOrNull { + val builtIndex = getBuiltEventIndex(eventId, searchInNext = false, searchInPrev = false) + if (builtIndex == null) { + val foundInPrev = searchInPrev && prevChunk?.rebuildEvent(eventId, builder, searchInNext = false, searchInPrev = true).orFalse() + if (foundInPrev) { + return true + } + if (searchInNext) { + return prevChunk?.rebuildEvent(eventId, builder, searchInPrev = false, searchInNext = true).orFalse() + } + return false + } + // Update the relation of existing event + builtEvents.getOrNull(builtIndex)?.let { te -> + val rebuiltEvent = builder(te) + builtEvents[builtIndex] = rebuiltEvent!! + true + } + } + ?: false + } + + fun close(closeNext: Boolean, closePrev: Boolean) { + if (closeNext) { + nextChunk?.close(closeNext = true, closePrev = false) + } + if (closePrev) { + prevChunk?.close(closeNext = false, closePrev = true) + } + nextChunk = null + prevChunk = null + chunkEntity.removeChangeListener(chunkObjectListener) + timelineEventEntities.removeChangeListener(timelineEventCollectionListener) + } + + private fun getNextDisplayIndex(direction: Timeline.Direction): Int? { + val frozenTimelineEvents = timelineEventEntities.freeze() + if (frozenTimelineEvents.isEmpty()) { + return null + } + return if (builtEvents.isEmpty()) { + if (initialEventId != null) { + frozenTimelineEvents.where().equalTo(TimelineEventEntityFields.EVENT_ID, initialEventId).findFirst()?.displayIndex + } else if (direction == Timeline.Direction.BACKWARDS) { + frozenTimelineEvents.first()?.displayIndex + } else { + frozenTimelineEvents.last()?.displayIndex + } + } else if (direction == Timeline.Direction.FORWARDS) { + builtEvents.first().displayIndex + 1 + } else { + builtEvents.last().displayIndex - 1 + } + } +} + +private fun RealmQuery.offsets( + direction: Timeline.Direction, + count: Long, + startDisplayIndex: Int +): RealmQuery { + sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING) + if (direction == Timeline.Direction.BACKWARDS) { + lessThanOrEqualTo(TimelineEventEntityFields.DISPLAY_INDEX, startDisplayIndex) + } else { + greaterThanOrEqualTo(TimelineEventEntityFields.DISPLAY_INDEX, startDisplayIndex) + } + return limit(count) +} + +private fun Timeline.Direction.toPaginationDirection(): PaginationDirection { + return if (this == Timeline.Direction.BACKWARDS) PaginationDirection.BACKWARDS else PaginationDirection.FORWARDS +} + +private fun ChunkEntity.sortedTimelineEvents(): RealmResults { + return timelineEvents.sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING) +} 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 a7cba2fe99..13704a5168 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 @@ -25,22 +25,16 @@ import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.internal.database.helper.addIfNecessary import org.matrix.android.sdk.internal.database.helper.addStateEvent import org.matrix.android.sdk.internal.database.helper.addTimelineEvent -import org.matrix.android.sdk.internal.database.helper.merge import org.matrix.android.sdk.internal.database.mapper.toEntity import org.matrix.android.sdk.internal.database.model.ChunkEntity import org.matrix.android.sdk.internal.database.model.EventInsertType import org.matrix.android.sdk.internal.database.model.RoomEntity -import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity -import org.matrix.android.sdk.internal.database.model.deleteOnCascade +import org.matrix.android.sdk.internal.database.model.TimelineEventEntity 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.findAllIncludingEvents -import org.matrix.android.sdk.internal.database.query.findLastForwardChunkOfRoom -import org.matrix.android.sdk.internal.database.query.getOrCreate import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.SessionDatabase -import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryEventsHelper import org.matrix.android.sdk.internal.util.awaitTransaction import timber.log.Timber import javax.inject.Inject @@ -136,21 +130,21 @@ internal class TokenChunkEventPersistor @Inject constructor(@SessionDatabase pri prevToken = receivedChunk.end } + val existingChunk = ChunkEntity.find(realm, roomId, prevToken = prevToken, nextToken = nextToken) + if (existingChunk != null) { + Timber.v("This chunk is already in the db, returns") + return@awaitTransaction + } val prevChunk = ChunkEntity.find(realm, roomId, nextToken = prevToken) val nextChunk = ChunkEntity.find(realm, roomId, prevToken = nextToken) - - // The current chunk is the one we will keep all along the merge processChanges. - // We try to look for a chunk next to the token, - // otherwise we create a whole new one which is unlinked (not live) - val currentChunk = if (direction == PaginationDirection.FORWARDS) { - prevChunk?.apply { this.nextToken = nextToken } - } else { - nextChunk?.apply { this.prevToken = prevToken } + val currentChunk = ChunkEntity.create(realm, prevToken = prevToken, nextToken = nextToken).apply { + this.nextChunk = nextChunk + this.prevChunk = prevChunk } - ?: ChunkEntity.create(realm, prevToken, nextToken) - - if (receivedChunk.events.isNullOrEmpty() && !receivedChunk.hasMore()) { - handleReachEnd(realm, roomId, direction, currentChunk) + nextChunk?.prevChunk = currentChunk + prevChunk?.nextChunk = currentChunk + if (receivedChunk.events.isEmpty() && !receivedChunk.hasMore()) { + handleReachEnd(roomId, direction, currentChunk) } else { handlePagination(realm, roomId, direction, receivedChunk, currentChunk) } @@ -166,17 +160,10 @@ internal class TokenChunkEventPersistor @Inject constructor(@SessionDatabase pri } } - private fun handleReachEnd(realm: Realm, roomId: String, direction: PaginationDirection, currentChunk: ChunkEntity) { + private fun handleReachEnd(roomId: String, direction: PaginationDirection, currentChunk: ChunkEntity) { Timber.v("Reach end of $roomId") if (direction == PaginationDirection.FORWARDS) { - val currentLastForwardChunk = ChunkEntity.findLastForwardChunkOfRoom(realm, roomId) - if (currentChunk != currentLastForwardChunk) { - currentChunk.isLastForward = true - currentLastForwardChunk?.deleteOnCascade(deleteStateEvents = false, canDeleteRoot = false) - RoomSummaryEntity.where(realm, roomId).findFirst()?.apply { - latestPreviewableEvent = RoomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId) - } - } + Timber.v("We should keep the lastForward chunk unique, the one from sync") } else { currentChunk.isLastBackward = true } @@ -209,6 +196,21 @@ internal class TokenChunkEventPersistor @Inject constructor(@SessionDatabase pri if (event.eventId == null || event.senderId == null) { return@forEach } + //We check for the timeline event with this id + val eventId = event.eventId + val existingTimelineEvent = TimelineEventEntity.where(realm, roomId, eventId).findFirst() + // If it exists, we want to skip here + val existingChunk = existingTimelineEvent?.chunk?.firstOrNull() + if (existingChunk != null) { + if (direction == PaginationDirection.BACKWARDS) { + currentChunk.prevChunk = existingChunk + existingChunk.nextChunk = currentChunk + } else if (direction == PaginationDirection.FORWARDS) { + currentChunk.nextChunk = existingChunk + existingChunk.prevChunk = currentChunk + } + return@forEach + } val ageLocalTs = event.unsignedData?.age?.let { now - it } eventIds.add(event.eventId) val eventEntity = event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, EventInsertType.PAGINATION) @@ -220,29 +222,8 @@ internal class TokenChunkEventPersistor @Inject constructor(@SessionDatabase pri } roomMemberContentsByUser[event.stateKey] = contentToUse.toModel() } - currentChunk.addTimelineEvent(roomId, eventEntity, direction, roomMemberContentsByUser) } - // Find all the chunks which contain at least one event from the list of eventIds - val chunks = ChunkEntity.findAllIncludingEvents(realm, eventIds) - Timber.d("Found ${chunks.size} chunks containing at least one of the eventIds") - val chunksToDelete = ArrayList() - chunks.forEach { - if (it != currentChunk) { - Timber.d("Merge $it") - currentChunk.merge(roomId, it, direction) - chunksToDelete.add(it) - } - } - chunksToDelete.forEach { - it.deleteOnCascade(deleteStateEvents = false, canDeleteRoot = false) - } - val roomSummaryEntity = RoomSummaryEntity.getOrCreate(realm, roomId) - val shouldUpdateSummary = roomSummaryEntity.latestPreviewableEvent == null - || (chunksToDelete.isNotEmpty() && currentChunk.isLastForward && direction == PaginationDirection.FORWARDS) - if (shouldUpdateSummary) { - roomSummaryEntity.latestPreviewableEvent = RoomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId) - } if (currentChunk.isValid) { RoomEntity.where(realm, roomId).findFirst()?.addIfNecessary(currentChunk) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/UIEchoManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/UIEchoManager.kt index 4804fbd731..25ff17dd70 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/UIEchoManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/UIEchoManager.kt @@ -70,13 +70,12 @@ internal class UIEchoManager( return existingState != sendState } - fun onLocalEchoCreated(timelineEvent: TimelineEvent) { - // Manage some ui echos (do it before filter because actual event could be filtered out) + fun onLocalEchoCreated(timelineEvent: TimelineEvent): Boolean { when (timelineEvent.root.getClearType()) { EventType.REDACTION -> { } EventType.REACTION -> { - val content = timelineEvent.root.content?.toModel() + val content: ReactionContent? = timelineEvent.root.content?.toModel() if (RelationType.ANNOTATION == content?.relatesTo?.type) { val reaction = content.relatesTo.key val relatedEventID = content.relatesTo.eventId @@ -96,11 +95,12 @@ internal class UIEchoManager( } Timber.v("On local echo created: ${timelineEvent.eventId}") inMemorySendingEvents.add(0, timelineEvent) + return true } - fun decorateEventWithReactionUiEcho(timelineEvent: TimelineEvent): TimelineEvent? { + fun decorateEventWithReactionUiEcho(timelineEvent: TimelineEvent): TimelineEvent { val relatedEventID = timelineEvent.eventId - val contents = inMemoryReactions[relatedEventID] ?: return null + val contents = inMemoryReactions[relatedEventID] ?: return timelineEvent var existingAnnotationSummary = timelineEvent.annotations ?: EventAnnotationsSummary( relatedEventID diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt index 8be319f2a8..e272ee86c9 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt @@ -39,13 +39,13 @@ import im.vector.app.features.home.room.detail.timeline.factory.MergedHeaderItem import im.vector.app.features.home.room.detail.timeline.factory.ReadReceiptsItemFactory import im.vector.app.features.home.room.detail.timeline.factory.TimelineItemFactory import im.vector.app.features.home.room.detail.timeline.factory.TimelineItemFactoryParams -import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventsGroups 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.TimelineControllerInterceptorHelper import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventDiffUtilCallback import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventVisibilityHelper import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventVisibilityStateChangedListener +import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventsGroups import im.vector.app.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider import im.vector.app.features.home.room.detail.timeline.item.BasedMergedItem import im.vector.app.features.home.room.detail.timeline.item.DaySeparatorItem @@ -244,17 +244,8 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec interceptorHelper.intercept(models, partialState.unreadState, timeline, callback) } - fun update(viewState: RoomDetailViewState) = synchronized(modelCache) { + fun update(viewState: RoomDetailViewState) { val newPartialState = PartialState(viewState) - if (partialState.highlightedEventId != newPartialState.highlightedEventId) { - // Clear cache to force a refresh - for (i in 0 until modelCache.size) { - if (modelCache[i]?.eventId == viewState.highlightedEventId - || modelCache[i]?.eventId == partialState.highlightedEventId) { - modelCache[i] = null - } - } - } if (newPartialState != partialState) { partialState = newPartialState requestModelBuild() @@ -394,7 +385,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec it.id(event.localId) it.setOnVisibilityStateChanged(TimelineEventVisibilityStateChangedListener(callback, event)) } - val isCacheable = eventModel is ItemWithEvents && eventModel.isCacheable() + val isCacheable = eventModel is ItemWithEvents && eventModel.isCacheable() && !params.isHighlighted return CacheItemData( localId = event.localId, eventId = event.root.eventId, From 4f145e365e721830e466128937d0dca7c6e1948d Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 8 Sep 2021 15:33:40 +0200 Subject: [PATCH 008/632] Timeline: small on fixes on new implementation --- .../internal/session/room/timeline/DefaultTimeline.kt | 1 - .../room/detail/timeline/TimelineEventController.kt | 11 ++++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) 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 141d33ad0e..b102ab9fd7 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 @@ -109,7 +109,6 @@ class DefaultTimeline internal constructor(private val roomId: String, ensureReadReceiptAreLoaded(realm) backgroundRealm.set(realm) openAround(initialEventId) - strategy.onStart() } } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt index e272ee86c9..923404ac5e 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt @@ -58,6 +58,7 @@ import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever import im.vector.app.features.media.ImageContentRenderer import im.vector.app.features.media.VideoContentRenderer import im.vector.app.features.settings.VectorPreferences +import org.matrix.android.sdk.api.extensions.orFalse 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.toModel @@ -355,7 +356,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec timelineEventVisibilityHelper.shouldShowEvent(it, partialState.highlightedEventId) } // Should be build if not cached or if model should be refreshed - if (modelCache[position] == null || modelCache[position]?.isCacheable == false) { + if (modelCache[position] == null || modelCache[position]?.isCacheable(partialState).orFalse()) { val timelineEventsGroup = timelineEventsGroups.getOrNull(event) val params = TimelineItemFactoryParams( event = event, @@ -537,6 +538,10 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec val eventModel: EpoxyModel<*>? = null, val mergedHeaderModel: BasedMergedItem<*>? = null, val formattedDayModel: DaySeparatorItem? = null, - val isCacheable: Boolean = true - ) + private val isCacheable: Boolean = true + ) { + fun isCacheable(partialState: PartialState): Boolean { + return isCacheable || partialState.highlightedEventId == eventId + } + } } From 94a69503949d16a8790cbc14090ebfc1af00041a Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 8 Sep 2021 18:00:54 +0200 Subject: [PATCH 009/632] Timeline rework: continue branching things. --- .../sdk/api/session/room/timeline/Timeline.kt | 4 +-- .../session/room/timeline/DefaultTimeline.kt | 36 ++++++++++++------- .../session/room/timeline/LoadMoreResult.kt | 2 +- .../room/timeline/LoadTimelineStrategy.kt | 18 +++++++--- .../room/timeline/SendingEventsDataSource.kt | 2 +- .../session/room/timeline/TimelineChunk.kt | 2 +- .../session/room/timeline/UIEchoManager.kt | 5 +-- 7 files changed, 41 insertions(+), 28 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/Timeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/Timeline.kt index dff45c0d94..ae202da979 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/Timeline.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/Timeline.kt @@ -115,9 +115,7 @@ interface Timeline { */ fun onNewTimelineEvents(eventIds: List) - fun onStateUpdated() { - //NOOP - } + fun onStateUpdated() = Unit } /** 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 b102ab9fd7..ca86771f9b 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 New Vector Ltd + * Copyright (c) 2021 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. @@ -41,17 +41,17 @@ import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicReference -class DefaultTimeline internal constructor(private val roomId: String, - private val initialEventId: String?, - private val realmConfiguration: RealmConfiguration, - private val loadRoomMembersTask: LoadRoomMembersTask, - private val readReceiptHandler: ReadReceiptHandler, - paginationTask: PaginationTask, - getEventTask: GetContextOfEventTask, - fetchTokenAndPaginateTask: FetchTokenAndPaginateTask, - timelineEventMapper: TimelineEventMapper, - timelineInput: TimelineInput, - eventDecryptor: TimelineEventDecryptor) : Timeline { +internal class DefaultTimeline internal constructor(private val roomId: String, + private val initialEventId: String?, + private val realmConfiguration: RealmConfiguration, + private val loadRoomMembersTask: LoadRoomMembersTask, + private val readReceiptHandler: ReadReceiptHandler, + paginationTask: PaginationTask, + getEventTask: GetContextOfEventTask, + fetchTokenAndPaginateTask: FetchTokenAndPaginateTask, + timelineEventMapper: TimelineEventMapper, + timelineInput: TimelineInput, + eventDecryptor: TimelineEventDecryptor) : Timeline { companion object { val BACKGROUND_HANDLER = createBackgroundHandler("SimpleTimeline_Thread") @@ -77,8 +77,10 @@ class DefaultTimeline internal constructor(private val roomId: String, timelineEventMapper = timelineEventMapper, realm = backgroundRealm, getContextOfEventTask = getEventTask, - onEventsUpdated = this::postSnapshot + onEventsUpdated = this::postSnapshot, + onNewTimelineEvents = this::onNewTimelineEvents ) + private var strategy: LoadTimelineStrategy = buildStrategy(LoadTimelineStrategy.Mode.Default) override val isLive: Boolean @@ -231,6 +233,14 @@ class DefaultTimeline internal constructor(private val roomId: String, } } + private fun onNewTimelineEvents(eventIds: List) { + timelineScope.launch(Dispatchers.Main) { + listeners.forEach { + tryOrNull { it.onNewTimelineEvents(eventIds) } + } + } + } + private suspend fun updateState(direction: Timeline.Direction, update: (Timeline.PaginationState) -> Timeline.PaginationState) { val stateReference = when (direction) { Timeline.Direction.FORWARDS -> forwardState diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadMoreResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadMoreResult.kt index 8b0cf0715b..c419e8325e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadMoreResult.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadMoreResult.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 New Vector Ltd + * Copyright (c) 2021 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. 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 ce1ef7f186..0c8a1ae903 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 New Vector Ltd + * Copyright (c) 2021 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. @@ -24,7 +24,6 @@ import org.matrix.android.sdk.api.extensions.orFalse 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 -import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper import org.matrix.android.sdk.internal.database.model.ChunkEntity import org.matrix.android.sdk.internal.database.model.ChunkEntityFields @@ -59,7 +58,8 @@ internal class LoadTimelineStrategy( val getContextOfEventTask: GetContextOfEventTask, val timelineInput: TimelineInput, val timelineEventMapper: TimelineEventMapper, - val onEventsUpdated: () -> Unit + val onEventsUpdated: () -> Unit, + val onNewTimelineEvents: (List) -> Unit ) private var chunkEntity: RealmResults? = null @@ -86,6 +86,7 @@ internal class LoadTimelineStrategy( return } if (uiEchoManager.onLocalEchoCreated(timelineEvent)) { + dependencies.onNewTimelineEvents(listOf(timelineEvent.eventId)) dependencies.onEventsUpdated() } } @@ -98,9 +99,16 @@ internal class LoadTimelineStrategy( dependencies.onEventsUpdated() } } + + override fun onNewTimelineEvents(roomId: String, eventIds: List) { + super.onNewTimelineEvents(roomId, eventIds) + if (mode == Mode.Default && roomId == this@LoadTimelineStrategy.roomId) { + dependencies.onNewTimelineEvents(eventIds) + } + } } - private val uiEchoManager = UIEchoManager(TimelineSettings(10), uiEchoManagerListener) + private val uiEchoManager = UIEchoManager(uiEchoManagerListener) private val sendingEventsDataSource: SendingEventsDataSource = RealmSendingEventsDataSource( roomId = roomId, realm = dependencies.realm, @@ -118,7 +126,7 @@ internal class LoadTimelineStrategy( it.addChangeListener(chunkEntityListener) timelineChunk = it.createTimelineChunk() } - if(mode is Mode.Default){ + if (mode is Mode.Default) { loadMore(10, Timeline.Direction.BACKWARDS) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/SendingEventsDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/SendingEventsDataSource.kt index 58298609a0..c6d10a23fb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/SendingEventsDataSource.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/SendingEventsDataSource.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 New Vector Ltd + * Copyright (c) 2021 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. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt index 6c337b3663..c9f19f0ddd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 New Vector Ltd + * Copyright (c) 2021 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. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/UIEchoManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/UIEchoManager.kt index 25ff17dd70..5b2cf001f3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/UIEchoManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/UIEchoManager.kt @@ -28,10 +28,7 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings import timber.log.Timber import java.util.Collections -internal class UIEchoManager( - private val settings: TimelineSettings, - private val listener: Listener -) { +internal class UIEchoManager(private val listener: Listener) { interface Listener { fun rebuildEvent(eventId: String, builder: (TimelineEvent) -> TimelineEvent?): Boolean From da75642b92e9c7f6d5616d7b2126e6cbf3a74231 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 17 Sep 2021 15:13:38 +0200 Subject: [PATCH 010/632] Timeline: add some logs and fix epoxy cache --- .../session/room/timeline/TimelineInput.kt | 5 +++-- .../timeline/TimelineEventController.kt | 22 ++++++++++++++----- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineInput.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineInput.kt index cdc85ea722..a953db0704 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineInput.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineInput.kt @@ -23,6 +23,9 @@ import javax.inject.Inject @SessionScope internal class TimelineInput @Inject constructor() { + + val listeners = mutableSetOf() + fun onLocalEchoCreated(roomId: String, timelineEvent: TimelineEvent) { listeners.toSet().forEach { it.onLocalEchoCreated(roomId, timelineEvent) } } @@ -35,8 +38,6 @@ internal class TimelineInput @Inject constructor() { listeners.toSet().forEach { it.onNewTimelineEvents(roomId, eventIds) } } - val listeners = mutableSetOf() - internal interface Listener { fun onLocalEchoCreated(roomId: String, timelineEvent: TimelineEvent) = Unit fun onLocalEchoUpdated(roomId: String, eventId: String, sendState: SendState) = Unit diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt index 923404ac5e..377e5e06df 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt @@ -58,7 +58,6 @@ import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever import im.vector.app.features.media.ImageContentRenderer import im.vector.app.features.media.VideoContentRenderer import im.vector.app.features.settings.VectorPreferences -import org.matrix.android.sdk.api.extensions.orFalse 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.toModel @@ -71,7 +70,9 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageImageInfoCon import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent import org.matrix.android.sdk.api.session.room.timeline.Timeline import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent +import timber.log.Timber import javax.inject.Inject +import kotlin.system.measureTimeMillis class TimelineEventController @Inject constructor(private val dateFormatter: VectorDateFormatter, private val vectorPreferences: VectorPreferences, @@ -321,7 +322,10 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec } private fun getModels(): List> { - buildCacheItemsIfNeeded() + val timeForBuilding = measureTimeMillis { + buildCacheItemsIfNeeded() + } + Timber.v("Time for building cache items: $timeForBuilding ms") return modelCache .map { cacheItemData -> val eventModel = if (cacheItemData == null || mergedHeaderItemFactory.isCollapsed(cacheItemData.localId)) { @@ -346,7 +350,11 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec if (modelCache.isEmpty()) { return } - preprocessReverseEvents() + val preprocessEventsTiming = measureTimeMillis { + preprocessReverseEvents() + } + Timber.v("Preprocess events took $preprocessEventsTiming ms") + var numberOfEventsToBuild = 0 val lastSentEventWithoutReadReceipts = searchLastSentEventWithoutReadReceipts(receiptsByEvent) (0 until modelCache.size).forEach { position -> val event = currentSnapshot[position] @@ -356,7 +364,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec timelineEventVisibilityHelper.shouldShowEvent(it, partialState.highlightedEventId) } // Should be build if not cached or if model should be refreshed - if (modelCache[position] == null || modelCache[position]?.isCacheable(partialState).orFalse()) { + if (modelCache[position] == null || modelCache[position]?.isCacheable(partialState) == false) { val timelineEventsGroup = timelineEventsGroups.getOrNull(event) val params = TimelineItemFactoryParams( event = event, @@ -369,11 +377,13 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec eventsGroup = timelineEventsGroup ) modelCache[position] = buildCacheItem(params) + numberOfEventsToBuild++ } val itemCachedData = modelCache[position] ?: return@forEach // Then update with additional models if needed modelCache[position] = itemCachedData.enrichWithModels(event, nextEvent, position, receiptsByEvent) } + Timber.v("Number of events to rebuild: $numberOfEventsToBuild on ${modelCache.size} total events") } private fun buildCacheItem(params: TimelineItemFactoryParams): CacheItemData { @@ -386,7 +396,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec it.id(event.localId) it.setOnVisibilityStateChanged(TimelineEventVisibilityStateChangedListener(callback, event)) } - val isCacheable = eventModel is ItemWithEvents && eventModel.isCacheable() && !params.isHighlighted + val isCacheable = (eventModel !is ItemWithEvents || eventModel.isCacheable()) && !params.isHighlighted return CacheItemData( localId = event.localId, eventId = event.root.eventId, @@ -541,7 +551,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec private val isCacheable: Boolean = true ) { fun isCacheable(partialState: PartialState): Boolean { - return isCacheable || partialState.highlightedEventId == eventId + return isCacheable && partialState.highlightedEventId != eventId } } } From 2283030c9b67dd0518e08f29da4e5558e12bbf3f Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 17 Sep 2021 17:51:40 +0200 Subject: [PATCH 011/632] Timeline rework: handle lastForwardChunk --- .../session/room/timeline/TimelineChunk.kt | 44 +++++++++++-------- 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt index c9f19f0ddd..45c74fa446 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt @@ -104,13 +104,9 @@ internal class TimelineChunk constructor(private val chunkEntity: ChunkEntity, return if (direction == Timeline.Direction.FORWARDS) { val nextChunkEntity = chunkEntity.nextChunk if (nextChunkEntity == null) { - val token = chunkEntity.nextToken ?: return LoadMoreResult.REACHED_END // TODO handle previous live chunk - try { - fetchFromServer(token, direction) - } catch (failure: Throwable) { - Timber.v("Failed to fetch from server: $failure") - LoadMoreResult.FAILURE - } + // Fetch next chunk from server if not in the db + val token = chunkEntity.nextToken + fetchFromServer(token, direction) } else { // otherwise we delegate to the next chunk if (nextChunk == null) { @@ -121,13 +117,9 @@ internal class TimelineChunk constructor(private val chunkEntity: ChunkEntity, } else { val prevChunkEntity = chunkEntity.prevChunk if (prevChunkEntity == null) { - val token = chunkEntity.prevToken ?: return LoadMoreResult.REACHED_END - try { - fetchFromServer(token, direction) - } catch (failure: Throwable) { - Timber.v("Failed to fetch from server: $failure") - LoadMoreResult.FAILURE - } + // Fetch prev chunk from server if not in the db + val token = chunkEntity.prevToken + fetchFromServer(token, direction) } else { // otherwise we delegate to the prev chunk if (prevChunk == null) { @@ -193,10 +185,26 @@ internal class TimelineChunk constructor(private val chunkEntity: ChunkEntity, ) } - private suspend fun fetchFromServer(token: String, direction: Timeline.Direction): LoadMoreResult { - val paginationParams = PaginationTask.Params(roomId, token, direction.toPaginationDirection(), PAGINATION_COUNT) - val paginationResult = paginationTask.execute(paginationParams) - return when (paginationResult) { + private suspend fun fetchFromServer(token: String?, direction: Timeline.Direction): LoadMoreResult { + val paginationResult = try { + if (token == null) { + if (direction == Timeline.Direction.BACKWARDS || !chunkEntity.hasBeenALastForwardChunk()) return LoadMoreResult.REACHED_END + val lastKnownEventId = chunkEntity.sortedTimelineEvents().firstOrNull()?.eventId ?: return LoadMoreResult.FAILURE + val taskParams = FetchTokenAndPaginateTask.Params(roomId, lastKnownEventId, direction.toPaginationDirection(), PAGINATION_COUNT) + fetchTokenAndPaginateTask.execute(taskParams) + } else { + val taskParams = PaginationTask.Params(roomId, token, direction.toPaginationDirection(), PAGINATION_COUNT) + paginationTask.execute(taskParams) + } + } catch (failure: Throwable) { + Timber.e("Failed to fetch from server: $failure", failure) + return LoadMoreResult.FAILURE + } + return paginationResult.toLoadMoreResult() + } + + private fun TokenChunkEventPersistor.Result.toLoadMoreResult(): LoadMoreResult { + return when (this) { TokenChunkEventPersistor.Result.REACHED_END -> LoadMoreResult.REACHED_END TokenChunkEventPersistor.Result.SHOULD_FETCH_MORE, TokenChunkEventPersistor.Result.SUCCESS -> LoadMoreResult.SUCCESS From 7f9c1916477d63c38f9bf8b0c19aebeefa27757d Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 17 Sep 2021 18:01:26 +0200 Subject: [PATCH 012/632] Timeline rework: add db migration --- .../database/RealmSessionStoreMigration.kt | 24 ++++++++++++++++++- .../SessionRealmConfigurationFactory.kt | 1 - 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt index aa96ca5e1a..e1d27647a6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt @@ -25,6 +25,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent import org.matrix.android.sdk.api.session.room.model.VersioningState import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent import org.matrix.android.sdk.api.session.room.model.tag.RoomTag +import org.matrix.android.sdk.internal.database.model.ChunkEntityFields import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntityFields import org.matrix.android.sdk.internal.database.model.EditAggregatedSummaryEntityFields import org.matrix.android.sdk.internal.database.model.EditionOfEventFields @@ -47,7 +48,7 @@ import timber.log.Timber internal object RealmSessionStoreMigration : RealmMigration { - const val SESSION_STORE_SCHEMA_VERSION = 17L + const val SESSION_STORE_SCHEMA_VERSION = 18L override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { Timber.v("Migrating Realm Session from $oldVersion to $newVersion") @@ -69,6 +70,7 @@ internal object RealmSessionStoreMigration : RealmMigration { if (oldVersion <= 14) migrateTo15(realm) if (oldVersion <= 15) migrateTo16(realm) if (oldVersion <= 16) migrateTo17(realm) + if (oldVersion <= 17) migrateTo18(realm) } private fun migrateTo1(realm: DynamicRealm) { @@ -338,4 +340,24 @@ internal object RealmSessionStoreMigration : RealmMigration { realm.schema.get("EventInsertEntity") ?.addField(EventInsertEntityFields.CAN_BE_PROCESSED, Boolean::class.java) } + + private fun migrateTo18(realm: DynamicRealm) { + Timber.d("Step 17 -> 18") + realm.schema.get("ChunkEntity")?.apply { + removeField("numberOfTimelineEvents") + var cleanOldChunks = false + if (!hasField(ChunkEntityFields.NEXT_CHUNK.`$`)) { + cleanOldChunks = true + addRealmObjectField(ChunkEntityFields.NEXT_CHUNK.`$`, this) + } + if (!hasField(ChunkEntityFields.PREV_CHUNK.`$`)) { + cleanOldChunks = true + addRealmObjectField(ChunkEntityFields.PREV_CHUNK.`$`, this) + } + if (cleanOldChunks) { + val chunkEntities = realm.where("ChunkEntity").equalTo(ChunkEntityFields.IS_LAST_FORWARD, false).findAll() + chunkEntities.deleteAllFromRealm() + } + } + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/SessionRealmConfigurationFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/SessionRealmConfigurationFactory.kt index 6aeb3936a9..1771c5b202 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/SessionRealmConfigurationFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/SessionRealmConfigurationFactory.kt @@ -72,7 +72,6 @@ internal class SessionRealmConfigurationFactory @Inject constructor( .modules(SessionRealmModule()) .schemaVersion(RealmSessionStoreMigration.SESSION_STORE_SCHEMA_VERSION) .migration(RealmSessionStoreMigration) - .deleteRealmIfMigrationNeeded() .build() // Try creating a realm instance and if it succeeds we can clear the flag From b370f84e08feafb0391556f0e70b15f72ddfe089 Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 20 Sep 2021 18:33:26 +0200 Subject: [PATCH 013/632] Timeline rework: add some comments and fix pagination when having overlapping events --- .../room/timeline/LoadTimelineStrategy.kt | 7 + .../session/room/timeline/TimelineChunk.kt | 161 +++++++++--------- .../room/timeline/TokenChunkEventPersistor.kt | 124 ++++---------- 3 files changed, 121 insertions(+), 171 deletions(-) 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 0c8a1ae903..3d8eb89315 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 @@ -31,6 +31,13 @@ import org.matrix.android.sdk.internal.database.query.findAllIncludingEvents import org.matrix.android.sdk.internal.database.query.where import java.util.concurrent.atomic.AtomicReference +/** + * This class is responsible for keeping an instance of chunkEntity and timelineChunk according to the strategy. + * There is 2 different mode: Default and Permalink. + * In Default, we will query for the live chunk (isLastForward = true). + * In Permalink, we will query for the chunk including the eventId we are looking for. + * Once we got a ChunkEntity we wrap it with TimelineChunk class so we dispatch any methods for loading data. + */ internal class LoadTimelineStrategy( private val roomId: String, private val timelineId: String, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt index 45c74fa446..3e46681358 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt @@ -38,6 +38,11 @@ import java.util.Collections */ private const val PAGINATION_COUNT = 50 +/** + * This is a wrapper around a ChunkEntity in the database. + * It does mainly listen to the db timeline events. + * It also triggers pagination to the server when needed, or dispatch to the prev or next chunk if any. + */ internal class TimelineChunk constructor(private val chunkEntity: ChunkEntity, private val roomId: String, private val timelineId: String, @@ -56,7 +61,7 @@ internal class TimelineChunk constructor(private val chunkEntity: ChunkEntity, private val timelineEventCollectionListener = OrderedRealmCollectionChangeListener { results: RealmResults, changeSet: OrderedCollectionChangeSet -> val frozenResults = results.freeze() Timber.v("on timeline event changed: $changeSet") - handleChangeSet(frozenResults, changeSet) + handleDatabaseChangeSet(frozenResults, changeSet) } private var timelineEventEntities: RealmResults = chunkEntity.sortedTimelineEvents() @@ -130,6 +135,82 @@ internal class TimelineChunk constructor(private val chunkEntity: ChunkEntity, } } + fun getBuiltEventIndex(eventId: String, searchInNext: Boolean, searchInPrev: Boolean): Int? { + val builtEventIndex = builtEventsIndexes[eventId] + if (builtEventIndex != null) { + return getOffsetIndex() + builtEventIndex + } + if (searchInNext) { + val nextBuiltEventIndex = nextChunk?.getBuiltEventIndex(eventId, searchInNext = true, searchInPrev = false) + if (nextBuiltEventIndex != null) { + return nextBuiltEventIndex + } + } + if (searchInPrev) { + val prevBuiltEventIndex = prevChunk?.getBuiltEventIndex(eventId, searchInNext = false, searchInPrev = true) + if (prevBuiltEventIndex != null) { + return prevBuiltEventIndex + } + } + return null + } + + fun getBuiltEvent(eventId: String, searchInNext: Boolean, searchInPrev: Boolean): TimelineEvent? { + val builtEventIndex = builtEventsIndexes[eventId] + if (builtEventIndex != null) { + return builtEvents.getOrNull(builtEventIndex) + } + if (searchInNext) { + val nextBuiltEvent = nextChunk?.getBuiltEvent(eventId, searchInNext = true, searchInPrev = false) + if (nextBuiltEvent != null) { + return nextBuiltEvent + } + } + if (searchInPrev) { + val prevBuiltEvent = prevChunk?.getBuiltEvent(eventId, searchInNext = false, searchInPrev = true) + if (prevBuiltEvent != null) { + return prevBuiltEvent + } + } + return null + } + + fun rebuildEvent(eventId: String, builder: (TimelineEvent) -> TimelineEvent?, searchInNext: Boolean, searchInPrev: Boolean): Boolean { + return tryOrNull { + val builtIndex = getBuiltEventIndex(eventId, searchInNext = false, searchInPrev = false) + if (builtIndex == null) { + val foundInPrev = searchInPrev && prevChunk?.rebuildEvent(eventId, builder, searchInNext = false, searchInPrev = true).orFalse() + if (foundInPrev) { + return true + } + if (searchInNext) { + return prevChunk?.rebuildEvent(eventId, builder, searchInPrev = false, searchInNext = true).orFalse() + } + return false + } + // Update the relation of existing event + builtEvents.getOrNull(builtIndex)?.let { te -> + val rebuiltEvent = builder(te) + builtEvents[builtIndex] = rebuiltEvent!! + true + } + } + ?: false + } + + fun close(closeNext: Boolean, closePrev: Boolean) { + if (closeNext) { + nextChunk?.close(closeNext = true, closePrev = false) + } + if (closePrev) { + prevChunk?.close(closeNext = false, closePrev = true) + } + nextChunk = null + prevChunk = null + chunkEntity.removeChangeListener(chunkObjectListener) + timelineEventEntities.removeChangeListener(timelineEventCollectionListener) + } + private fun loadFromDb(count: Long, direction: Timeline.Direction): Long { val displayIndex = getNextDisplayIndex(direction) ?: return 0 val baseQuery = timelineEventEntities.where() @@ -211,46 +292,6 @@ internal class TimelineChunk constructor(private val chunkEntity: ChunkEntity, } } - fun getBuiltEventIndex(eventId: String, searchInNext: Boolean, searchInPrev: Boolean): Int? { - val builtEventIndex = builtEventsIndexes[eventId] - if (builtEventIndex != null) { - return getOffsetIndex() + builtEventIndex - } - if (searchInNext) { - val nextBuiltEventIndex = nextChunk?.getBuiltEventIndex(eventId, searchInNext = true, searchInPrev = false) - if (nextBuiltEventIndex != null) { - return nextBuiltEventIndex - } - } - if (searchInPrev) { - val prevBuiltEventIndex = prevChunk?.getBuiltEventIndex(eventId, searchInNext = false, searchInPrev = true) - if (prevBuiltEventIndex != null) { - return prevBuiltEventIndex - } - } - return null - } - - fun getBuiltEvent(eventId: String, searchInNext: Boolean, searchInPrev: Boolean): TimelineEvent? { - val builtEventIndex = builtEventsIndexes[eventId] - if (builtEventIndex != null) { - return builtEvents.getOrNull(builtEventIndex) - } - if (searchInNext) { - val nextBuiltEvent = nextChunk?.getBuiltEvent(eventId, searchInNext = true, searchInPrev = false) - if (nextBuiltEvent != null) { - return nextBuiltEvent - } - } - if (searchInPrev) { - val prevBuiltEvent = prevChunk?.getBuiltEvent(eventId, searchInNext = false, searchInPrev = true) - if (prevBuiltEvent != null) { - return prevBuiltEvent - } - } - return null - } - private fun getOffsetIndex(): Int { var offset = 0 var currentNextChunk = nextChunk @@ -261,7 +302,7 @@ internal class TimelineChunk constructor(private val chunkEntity: ChunkEntity, return offset } - private fun handleChangeSet(frozenResults: RealmResults, changeSet: OrderedCollectionChangeSet) { + private fun handleDatabaseChangeSet(frozenResults: RealmResults, changeSet: OrderedCollectionChangeSet) { val insertions = changeSet.insertionRanges for (range in insertions) { val newItems = frozenResults @@ -290,42 +331,6 @@ internal class TimelineChunk constructor(private val chunkEntity: ChunkEntity, } } - fun rebuildEvent(eventId: String, builder: (TimelineEvent) -> TimelineEvent?, searchInNext: Boolean, searchInPrev: Boolean): Boolean { - return tryOrNull { - val builtIndex = getBuiltEventIndex(eventId, searchInNext = false, searchInPrev = false) - if (builtIndex == null) { - val foundInPrev = searchInPrev && prevChunk?.rebuildEvent(eventId, builder, searchInNext = false, searchInPrev = true).orFalse() - if (foundInPrev) { - return true - } - if (searchInNext) { - return prevChunk?.rebuildEvent(eventId, builder, searchInPrev = false, searchInNext = true).orFalse() - } - return false - } - // Update the relation of existing event - builtEvents.getOrNull(builtIndex)?.let { te -> - val rebuiltEvent = builder(te) - builtEvents[builtIndex] = rebuiltEvent!! - true - } - } - ?: false - } - - fun close(closeNext: Boolean, closePrev: Boolean) { - if (closeNext) { - nextChunk?.close(closeNext = true, closePrev = false) - } - if (closePrev) { - prevChunk?.close(closeNext = false, closePrev = true) - } - nextChunk = null - prevChunk = null - chunkEntity.removeChangeListener(chunkObjectListener) - timelineEventEntities.removeChangeListener(timelineEventCollectionListener) - } - private fun getNextDisplayIndex(direction: Timeline.Direction): Int? { val frozenTimelineEvents = timelineEventEntities.freeze() if (frozenTimelineEvents.isEmpty()) { 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 13704a5168..43c2c37f84 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 @@ -40,73 +40,10 @@ import timber.log.Timber import javax.inject.Inject /** - * Insert Chunk in DB, and eventually merge with existing chunk event + * Insert Chunk in DB, and eventually link next and previous chunk in db. */ internal class TokenChunkEventPersistor @Inject constructor(@SessionDatabase private val monarchy: Monarchy) { - /** - *
-     * ========================================================================================================
-     * | Backward case                                                                                        |
-     * ========================================================================================================
-     *
-     *                               *--------------------------*        *--------------------------*
-     *                               | startToken1              |        | startToken1              |
-     *                               *--------------------------*        *--------------------------*
-     *                               |                          |        |                          |
-     *                               |                          |        |                          |
-     *                               |  receivedChunk backward  |        |                          |
-     *                               |         Events           |        |                          |
-     *                               |                          |        |                          |
-     *                               |                          |        |                          |
-     *                               |                          |        |                          |
-     * *--------------------------*  *--------------------------*        |                          |
-     * | startToken0              |  | endToken1                |   =>   |       Merged chunk       |
-     * *--------------------------*  *--------------------------*        |          Events          |
-     * |                          |                                      |                          |
-     * |                          |                                      |                          |
-     * |      Current Chunk       |                                      |                          |
-     * |         Events           |                                      |                          |
-     * |                          |                                      |                          |
-     * |                          |                                      |                          |
-     * |                          |                                      |                          |
-     * *--------------------------*                                      *--------------------------*
-     * | endToken0                |                                      | endToken0                |
-     * *--------------------------*                                      *--------------------------*
-     *
-     *
-     * ========================================================================================================
-     * | Forward case                                                                                         |
-     * ========================================================================================================
-     *
-     * *--------------------------*                                      *--------------------------*
-     * | startToken0              |                                      | startToken0              |
-     * *--------------------------*                                      *--------------------------*
-     * |                          |                                      |                          |
-     * |                          |                                      |                          |
-     * |      Current Chunk       |                                      |                          |
-     * |         Events           |                                      |                          |
-     * |                          |                                      |                          |
-     * |                          |                                      |                          |
-     * |                          |                                      |                          |
-     * *--------------------------*  *--------------------------*        |                          |
-     * | endToken0                |  | startToken1              |   =>   |       Merged chunk       |
-     * *--------------------------*  *--------------------------*        |          Events          |
-     *                               |                          |        |                          |
-     *                               |                          |        |                          |
-     *                               |  receivedChunk forward   |        |                          |
-     *                               |         Events           |        |                          |
-     *                               |                          |        |                          |
-     *                               |                          |        |                          |
-     *                               |                          |        |                          |
-     *                               *--------------------------*        *--------------------------*
-     *                               | endToken1                |        | endToken1                |
-     *                               *--------------------------*        *--------------------------*
-     *
-     * ========================================================================================================
-     * 
- */ - enum class Result { SHOULD_FETCH_MORE, REACHED_END, @@ -191,38 +128,39 @@ internal class TokenChunkEventPersistor @Inject constructor(@SessionDatabase pri roomMemberContentsByUser[stateEvent.stateKey] = stateEvent.content.toModel() } } - val eventIds = ArrayList(eventList.size) - eventList.forEach { event -> - if (event.eventId == null || event.senderId == null) { - return@forEach - } - //We check for the timeline event with this id - val eventId = event.eventId - val existingTimelineEvent = TimelineEventEntity.where(realm, roomId, eventId).findFirst() - // If it exists, we want to skip here - val existingChunk = existingTimelineEvent?.chunk?.firstOrNull() - if (existingChunk != null) { - if (direction == PaginationDirection.BACKWARDS) { - currentChunk.prevChunk = existingChunk - existingChunk.nextChunk = currentChunk - } else if (direction == PaginationDirection.FORWARDS) { - currentChunk.nextChunk = existingChunk - existingChunk.prevChunk = currentChunk + run processTimelineEvents@ { + eventList.forEach { event -> + if (event.eventId == null || event.senderId == null) { + return@forEach } - return@forEach - } - val ageLocalTs = event.unsignedData?.age?.let { now - it } - eventIds.add(event.eventId) - val eventEntity = event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, EventInsertType.PAGINATION) - if (event.type == EventType.STATE_ROOM_MEMBER && event.stateKey != null) { - val contentToUse = if (direction == PaginationDirection.BACKWARDS) { - event.prevContent - } else { - event.content + //We check for the timeline event with this id + val eventId = event.eventId + val existingTimelineEvent = TimelineEventEntity.where(realm, roomId, eventId).findFirst() + // If it exists, we want to stop here, just link the prevChunk + val existingChunk = existingTimelineEvent?.chunk?.firstOrNull() + if (existingChunk != null) { + if (direction == PaginationDirection.BACKWARDS) { + currentChunk.prevChunk = existingChunk + existingChunk.nextChunk = currentChunk + } else if (direction == PaginationDirection.FORWARDS) { + currentChunk.nextChunk = existingChunk + existingChunk.prevChunk = currentChunk + } + // Stop processing here + return@processTimelineEvents } - roomMemberContentsByUser[event.stateKey] = contentToUse.toModel() + val ageLocalTs = event.unsignedData?.age?.let { now - it } + val eventEntity = event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, EventInsertType.PAGINATION) + if (event.type == EventType.STATE_ROOM_MEMBER && event.stateKey != null) { + val contentToUse = if (direction == PaginationDirection.BACKWARDS) { + event.prevContent + } else { + event.content + } + roomMemberContentsByUser[event.stateKey] = contentToUse.toModel() + } + currentChunk.addTimelineEvent(roomId, eventEntity, direction, roomMemberContentsByUser) } - currentChunk.addTimelineEvent(roomId, eventEntity, direction, roomMemberContentsByUser) } if (currentChunk.isValid) { RoomEntity.where(realm, roomId).findFirst()?.addIfNecessary(currentChunk) From cd1da7348f795dcb7637c57a3b1b30b16c8e1231 Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 20 Sep 2021 18:33:43 +0200 Subject: [PATCH 014/632] Timeline rework: make sure migration doesn't crash --- .../sdk/internal/database/RealmSessionStoreMigration.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt index e1d27647a6..5aa6914647 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt @@ -344,7 +344,9 @@ internal object RealmSessionStoreMigration : RealmMigration { private fun migrateTo18(realm: DynamicRealm) { Timber.d("Step 17 -> 18") realm.schema.get("ChunkEntity")?.apply { - removeField("numberOfTimelineEvents") + if (hasField("numberOfTimelineEvents")) { + removeField("numberOfTimelineEvents") + } var cleanOldChunks = false if (!hasField(ChunkEntityFields.NEXT_CHUNK.`$`)) { cleanOldChunks = true From 78b870c5580aa483f98f84c70ecb0753a1d1d941 Mon Sep 17 00:00:00 2001 From: Nick Hu Date: Fri, 18 Sep 2020 15:22:10 +0100 Subject: [PATCH 015/632] Downgrade markwon version and enable LaTeX rendering for messages --- dependencies.gradle | 4 +++- vector/build.gradle | 2 ++ .../detail/timeline/factory/MessageItemFactory.kt | 1 + .../home/room/detail/timeline/item/MessageTextItem.kt | 7 +++++++ .../im/vector/app/features/html/EventHtmlRenderer.kt | 11 +++++++++++ 5 files changed, 24 insertions(+), 1 deletion(-) diff --git a/dependencies.gradle b/dependencies.gradle index a4e2c60387..58133c3eef 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -14,7 +14,7 @@ def kotlinCoroutines = "1.5.1" def dagger = "2.38.1" def retrofit = "2.9.0" def arrow = "0.8.2" -def markwon = "4.6.2" +def markwon = "4.3.1" def moshi = "1.12.0" def lifecycle = "2.2.0" def rxBinding = "3.1.0" @@ -90,6 +90,8 @@ ext.libs = [ ], markwon : [ 'core' : "io.noties.markwon:core:$markwon", + 'extLatex' : "io.noties.markwon:ext-latex:$markwon", + 'inlineParser' : "io.noties.markwon:inline-parser:$markwon", 'html' : "io.noties.markwon:html:$markwon" ], airbnb : [ diff --git a/vector/build.gradle b/vector/build.gradle index d60f928f2c..9d25914e8e 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -390,6 +390,8 @@ dependencies { implementation libs.google.material implementation 'me.gujun.android:span:1.7' implementation libs.markwon.core + implementation libs.markwon.extLatex + implementation libs.markwon.inlineParser implementation libs.markwon.html implementation 'com.googlecode.htmlcompressor:htmlcompressor:1.5.2' implementation 'me.saket:better-link-movement-method:2.2.0' 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 287cd014e9..fd675d90fa 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 @@ -484,6 +484,7 @@ class MessageItemFactory @Inject constructor( } .useBigFont(linkifiedBody.length <= MAX_NUMBER_OF_EMOJI_FOR_BIG_FONT * 2 && containsOnlyEmojis(linkifiedBody.toString())) .canUseTextFuture(canUseTextFuture) + .markwonPlugins(htmlRenderer.get().plugins) .searchForPills(isFormatted) .previewUrlRetriever(callback?.getPreviewUrlRetriever()) .imageContentRenderer(imageContentRenderer) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt index c6dce5a9ce..a53e80362d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt @@ -16,6 +16,7 @@ package im.vector.app.features.home.room.detail.timeline.item +import android.text.Spanned import android.text.method.MovementMethod import androidx.appcompat.widget.AppCompatTextView import androidx.core.text.PrecomputedTextCompat @@ -31,6 +32,7 @@ import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlUiState import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlView import im.vector.app.features.media.ImageContentRenderer +import io.noties.markwon.MarkwonPlugin @EpoxyModelClass(layout = R.layout.item_timeline_event_base) abstract class MessageTextItem : AbsMessageItem() { @@ -59,6 +61,9 @@ abstract class MessageTextItem : AbsMessageItem() { @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var movementMethod: MovementMethod? = null + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) + var markwonPlugins: (List)? = null + private val previewUrlViewUpdater = PreviewUrlViewUpdater() override fun bind(holder: Holder) { @@ -92,6 +97,7 @@ abstract class MessageTextItem : AbsMessageItem() { } else { null } + markwonPlugins?.forEach { plugin -> plugin.beforeSetText(holder.messageView, message as Spanned) } super.bind(holder) holder.messageView.movementMethod = movementMethod @@ -104,6 +110,7 @@ abstract class MessageTextItem : AbsMessageItem() { } else { holder.messageView.text = message } + markwonPlugins?.forEach { plugin -> plugin.afterSetText(holder.messageView) } } override fun unbind(holder: Holder) { diff --git a/vector/src/main/java/im/vector/app/features/html/EventHtmlRenderer.kt b/vector/src/main/java/im/vector/app/features/html/EventHtmlRenderer.kt index d3b3be6a3e..cc7c999067 100644 --- a/vector/src/main/java/im/vector/app/features/html/EventHtmlRenderer.kt +++ b/vector/src/main/java/im/vector/app/features/html/EventHtmlRenderer.kt @@ -21,7 +21,11 @@ import android.text.Spannable import androidx.core.text.toSpannable import im.vector.app.core.resources.ColorProvider import io.noties.markwon.Markwon +import io.noties.markwon.MarkwonPlugin +import io.noties.markwon.ext.latex.JLatexMathPlugin +import io.noties.markwon.ext.latex.JLatexMathTheme import io.noties.markwon.html.HtmlPlugin +import io.noties.markwon.inlineparser.MarkwonInlineParserPlugin import org.commonmark.node.Node import timber.log.Timber import javax.inject.Inject @@ -37,8 +41,15 @@ class EventHtmlRenderer @Inject constructor(htmlConfigure: MatrixHtmlPluginConfi private val markwon = Markwon.builder(context) .usePlugin(HtmlPlugin.create(htmlConfigure)) + .usePlugin(MarkwonInlineParserPlugin.create()) + .usePlugin(JLatexMathPlugin.create(44F) { builder -> + builder.inlinesEnabled(true) + builder.theme().inlinePadding(JLatexMathTheme.Padding.symmetric(24, 8)) + }) .build() + val plugins: List = markwon.plugins + fun parse(text: String): Node { return markwon.parse(text) } From 20821fbe802a527c950fe333d649894820a395f7 Mon Sep 17 00:00:00 2001 From: Nick Hu Date: Fri, 18 Sep 2020 15:23:21 +0100 Subject: [PATCH 016/632] Render maths with respect to `data-mx-maths` (https://github.com/matrix-org/matrix-doc/pull/2191) Firstly, this implements a commonmark-java plugin which is solely used to parse LaTeX input in the composer box, so that they can be rendered into `fallback` and `
fallback
` for inline and display maths respectively in the sent message. Secondly, received messages of this form are pre-processed by a simple regex into a form which markwon (which performs the rendering) expects. --- .../commonmark/ext/maths/DisplayMaths.java | 32 +++++++++ .../org/commonmark/ext/maths/InlineMaths.java | 55 ++++++++++++++++ .../commonmark/ext/maths/MathsExtension.java | 51 ++++++++++++++ .../DollarMathsDelimiterProcessor.java | 66 +++++++++++++++++++ .../maths/internal/MathsHtmlNodeRenderer.java | 50 ++++++++++++++ .../ext/maths/internal/MathsNodeRenderer.java | 35 ++++++++++ .../sdk/internal/session/room/RoomModule.kt | 6 +- .../session/room/send/MarkdownParser.kt | 2 +- .../app/features/html/EventHtmlRenderer.kt | 8 +++ 9 files changed, 303 insertions(+), 2 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/commonmark/ext/maths/DisplayMaths.java create mode 100644 matrix-sdk-android/src/main/java/org/commonmark/ext/maths/InlineMaths.java create mode 100644 matrix-sdk-android/src/main/java/org/commonmark/ext/maths/MathsExtension.java create mode 100644 matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/DollarMathsDelimiterProcessor.java create mode 100644 matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/MathsHtmlNodeRenderer.java create mode 100644 matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/MathsNodeRenderer.java diff --git a/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/DisplayMaths.java b/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/DisplayMaths.java new file mode 100644 index 0000000000..3cf574b824 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/DisplayMaths.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.commonmark.ext.maths; + +import org.commonmark.node.CustomBlock; + +public class DisplayMaths extends CustomBlock { + public enum DisplayDelimiter { + DOUBLE_DOLLAR, + SQUARE_BRACKET_ESCAPED + }; + + private DisplayDelimiter delimiter; + + public DisplayMaths(DisplayDelimiter delimiter) { + this.delimiter = delimiter; + } +} diff --git a/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/InlineMaths.java b/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/InlineMaths.java new file mode 100644 index 0000000000..982570a58d --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/InlineMaths.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.commonmark.ext.maths; + +import org.commonmark.node.CustomNode; +import org.commonmark.node.Delimited; + +public class InlineMaths extends CustomNode implements Delimited { + public enum InlineDelimiter { + SINGLE_DOLLAR, + ROUND_BRACKET_ESCAPED + }; + + private InlineDelimiter delimiter; + + public InlineMaths(InlineDelimiter delimiter) { + this.delimiter = delimiter; + } + + @Override + public String getOpeningDelimiter() { + switch (delimiter) { + case SINGLE_DOLLAR: + return "$"; + case ROUND_BRACKET_ESCAPED: + return "\\("; + } + return null; + } + + @Override + public String getClosingDelimiter() { + switch (delimiter) { + case SINGLE_DOLLAR: + return "$"; + case ROUND_BRACKET_ESCAPED: + return "\\)"; + } + return null; + } +} diff --git a/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/MathsExtension.java b/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/MathsExtension.java new file mode 100644 index 0000000000..31706b3f2d --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/MathsExtension.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.commonmark.ext.maths; + +import org.commonmark.Extension; +import org.commonmark.ext.maths.internal.DollarMathsDelimiterProcessor; +import org.commonmark.ext.maths.internal.MathsHtmlNodeRenderer; +import org.commonmark.parser.Parser; +import org.commonmark.renderer.NodeRenderer; +import org.commonmark.renderer.html.HtmlNodeRendererContext; +import org.commonmark.renderer.html.HtmlNodeRendererFactory; +import org.commonmark.renderer.html.HtmlRenderer; + +public class MathsExtension implements Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension { + + private MathsExtension() { + } + + public static Extension create() { + return new MathsExtension(); + } + + @Override + public void extend(Parser.Builder parserBuilder) { + parserBuilder.customDelimiterProcessor(new DollarMathsDelimiterProcessor()); + } + + @Override + public void extend(HtmlRenderer.Builder rendererBuilder) { + rendererBuilder.nodeRendererFactory(new HtmlNodeRendererFactory() { + @Override + public NodeRenderer create(HtmlNodeRendererContext context) { + return new MathsHtmlNodeRenderer(context); + } + }); + } +} diff --git a/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/DollarMathsDelimiterProcessor.java b/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/DollarMathsDelimiterProcessor.java new file mode 100644 index 0000000000..809c070f29 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/DollarMathsDelimiterProcessor.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.commonmark.ext.maths.internal; + +import org.commonmark.ext.maths.DisplayMaths; +import org.commonmark.ext.maths.InlineMaths; +import org.commonmark.node.Node; +import org.commonmark.node.Text; +import org.commonmark.parser.delimiter.DelimiterProcessor; +import org.commonmark.parser.delimiter.DelimiterRun; + +public class DollarMathsDelimiterProcessor implements DelimiterProcessor { + @Override + public char getOpeningCharacter() { + return '$'; + } + + @Override + public char getClosingCharacter() { + return '$'; + } + + @Override + public int getMinLength() { + return 1; + } + + @Override + public int getDelimiterUse(DelimiterRun opener, DelimiterRun closer) { + if (opener.length() == 1 && closer.length() == 1) + return 1; // inline + else if (opener.length() == 2 && closer.length() == 2) + return 2; // display + else + return 0; + } + + @Override + public void process(Text opener, Text closer, int delimiterUse) { + Node maths = delimiterUse == 1 ? new InlineMaths(InlineMaths.InlineDelimiter.SINGLE_DOLLAR) : + new DisplayMaths(DisplayMaths.DisplayDelimiter.DOUBLE_DOLLAR); + + Node tmp = opener.getNext(); + while (tmp != null && tmp != closer) { + Node next = tmp.getNext(); + maths.appendChild(tmp); + tmp = next; + } + + opener.insertAfter(maths); + } +} diff --git a/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/MathsHtmlNodeRenderer.java b/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/MathsHtmlNodeRenderer.java new file mode 100644 index 0000000000..a84129cd28 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/MathsHtmlNodeRenderer.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.commonmark.ext.maths.internal; + +import org.commonmark.ext.maths.DisplayMaths; +import org.commonmark.node.Node; +import org.commonmark.node.Text; +import org.commonmark.renderer.html.HtmlNodeRendererContext; +import org.commonmark.renderer.html.HtmlWriter; + +import java.util.Collections; +import java.util.Map; + +public class MathsHtmlNodeRenderer extends MathsNodeRenderer { + private final HtmlNodeRendererContext context; + private final HtmlWriter html; + + public MathsHtmlNodeRenderer(HtmlNodeRendererContext context) { + this.context = context; + this.html = context.getWriter(); + } + + @Override + public void render(Node node) { + boolean display = node.getClass() == DisplayMaths.class; + Node contents = node.getFirstChild(); // should be the only child + String latex = ((Text) contents).getLiteral(); + Map attributes = context.extendAttributes(node, display ? "div" : "span", Collections.singletonMap("data-mx-maths", + latex)); + html.tag(display ? "div" : "span", attributes); + html.tag("code"); + context.render(contents); + html.tag("/code"); + html.tag(display ? "/div" : "/span"); + } +} diff --git a/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/MathsNodeRenderer.java b/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/MathsNodeRenderer.java new file mode 100644 index 0000000000..4d2a35f33d --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/MathsNodeRenderer.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.commonmark.ext.maths.internal; + +import org.commonmark.ext.maths.DisplayMaths; +import org.commonmark.ext.maths.InlineMaths; +import org.commonmark.node.Node; +import org.commonmark.renderer.NodeRenderer; + +import java.util.HashSet; +import java.util.Set; + +abstract class MathsNodeRenderer implements NodeRenderer { + @Override + public Set> getNodeTypes() { + final Set> types = new HashSet>(); + types.add(InlineMaths.class); + types.add(DisplayMaths.class); + return types; + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt index dbd0ae6f06..105c8ad03e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt @@ -19,6 +19,8 @@ package org.matrix.android.sdk.internal.session.room import dagger.Binds import dagger.Module import dagger.Provides +import org.commonmark.Extension +import org.commonmark.ext.maths.MathsExtension import org.commonmark.parser.Parser import org.commonmark.renderer.html.HtmlRenderer import org.matrix.android.sdk.api.session.file.FileService @@ -104,6 +106,7 @@ internal abstract class RoomModule { @Module companion object { + private val extensions : List = listOf(MathsExtension.create()) @Provides @JvmStatic @SessionScope @@ -121,7 +124,7 @@ internal abstract class RoomModule { @Provides @JvmStatic fun providesParser(): Parser { - return Parser.builder().build() + return Parser.builder().extensions(extensions).build() } @Provides @@ -129,6 +132,7 @@ internal abstract class RoomModule { fun providesHtmlRenderer(): HtmlRenderer { return HtmlRenderer .builder() + .extensions(extensions) .softbreak("
") .build() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/MarkdownParser.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/MarkdownParser.kt index c99d482300..1ac95154f8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/MarkdownParser.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/MarkdownParser.kt @@ -32,7 +32,7 @@ internal class MarkdownParser @Inject constructor( private val textPillsUtils: TextPillsUtils ) { - private val mdSpecialChars = "[`_\\-*>.\\[\\]#~]".toRegex() + private val mdSpecialChars = "[`_\\-*>.\\[\\]#~$]".toRegex() fun parse(text: CharSequence): TextContent { val source = textPillsUtils.processSpecialSpansToMarkdown(text) ?: text.toString() diff --git a/vector/src/main/java/im/vector/app/features/html/EventHtmlRenderer.kt b/vector/src/main/java/im/vector/app/features/html/EventHtmlRenderer.kt index cc7c999067..e2fb385ae5 100644 --- a/vector/src/main/java/im/vector/app/features/html/EventHtmlRenderer.kt +++ b/vector/src/main/java/im/vector/app/features/html/EventHtmlRenderer.kt @@ -20,6 +20,7 @@ import android.content.Context import android.text.Spannable import androidx.core.text.toSpannable import im.vector.app.core.resources.ColorProvider +import io.noties.markwon.AbstractMarkwonPlugin import io.noties.markwon.Markwon import io.noties.markwon.MarkwonPlugin import io.noties.markwon.ext.latex.JLatexMathPlugin @@ -41,6 +42,13 @@ class EventHtmlRenderer @Inject constructor(htmlConfigure: MatrixHtmlPluginConfi private val markwon = Markwon.builder(context) .usePlugin(HtmlPlugin.create(htmlConfigure)) + .usePlugin(object : AbstractMarkwonPlugin() { // Markwon expects maths to be in a specific format: https://noties.io/Markwon/docs/v4/ext-latex + override fun processMarkdown(markdown: String): String { + return markdown + .replace(Regex(""".*?""")) { matchResult -> "$$" + matchResult.groupValues[1] + "$$" } + .replace(Regex(""".*?""")) { matchResult -> "\n$$\n" + matchResult.groupValues[1] + "\n$$\n" } + } + }) .usePlugin(MarkwonInlineParserPlugin.create()) .usePlugin(JLatexMathPlugin.create(44F) { builder -> builder.inlinesEnabled(true) From 4c45a69129dcd523d441d4b04ca46847b1a8f946 Mon Sep 17 00:00:00 2001 From: Nick Hu Date: Wed, 8 Sep 2021 16:06:31 +0100 Subject: [PATCH 017/632] Migrate commonmark extension to kotlin --- .../{DisplayMaths.java => DisplayMaths.kt} | 20 ++---- .../org/commonmark/ext/maths/InlineMaths.java | 55 ---------------- .../org/commonmark/ext/maths/InlineMaths.kt | 39 +++++++++++ .../commonmark/ext/maths/MathsExtension.java | 51 -------------- .../commonmark/ext/maths/MathsExtension.kt | 38 +++++++++++ .../DollarMathsDelimiterProcessor.java | 66 ------------------- .../internal/DollarMathsDelimiterProcessor.kt | 53 +++++++++++++++ .../maths/internal/MathsHtmlNodeRenderer.java | 50 -------------- .../maths/internal/MathsHtmlNodeRenderer.kt | 39 +++++++++++ ...NodeRenderer.java => MathsNodeRenderer.kt} | 31 ++++----- 10 files changed, 189 insertions(+), 253 deletions(-) rename matrix-sdk-android/src/main/java/org/commonmark/ext/maths/{DisplayMaths.java => DisplayMaths.kt} (63%) delete mode 100644 matrix-sdk-android/src/main/java/org/commonmark/ext/maths/InlineMaths.java create mode 100644 matrix-sdk-android/src/main/java/org/commonmark/ext/maths/InlineMaths.kt delete mode 100644 matrix-sdk-android/src/main/java/org/commonmark/ext/maths/MathsExtension.java create mode 100644 matrix-sdk-android/src/main/java/org/commonmark/ext/maths/MathsExtension.kt delete mode 100644 matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/DollarMathsDelimiterProcessor.java create mode 100644 matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/DollarMathsDelimiterProcessor.kt delete mode 100644 matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/MathsHtmlNodeRenderer.java create mode 100644 matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/MathsHtmlNodeRenderer.kt rename matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/{MathsNodeRenderer.java => MathsNodeRenderer.kt} (51%) diff --git a/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/DisplayMaths.java b/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/DisplayMaths.kt similarity index 63% rename from matrix-sdk-android/src/main/java/org/commonmark/ext/maths/DisplayMaths.java rename to matrix-sdk-android/src/main/java/org/commonmark/ext/maths/DisplayMaths.kt index 3cf574b824..bf6a89a26a 100644 --- a/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/DisplayMaths.java +++ b/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/DisplayMaths.kt @@ -13,20 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package org.commonmark.ext.maths -package org.commonmark.ext.maths; +import org.commonmark.node.CustomBlock -import org.commonmark.node.CustomBlock; - -public class DisplayMaths extends CustomBlock { - public enum DisplayDelimiter { - DOUBLE_DOLLAR, - SQUARE_BRACKET_ESCAPED - }; - - private DisplayDelimiter delimiter; - - public DisplayMaths(DisplayDelimiter delimiter) { - this.delimiter = delimiter; +class DisplayMaths(private val delimiter: DisplayDelimiter) : CustomBlock() { + enum class DisplayDelimiter { + DOUBLE_DOLLAR, SQUARE_BRACKET_ESCAPED } -} +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/InlineMaths.java b/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/InlineMaths.java deleted file mode 100644 index 982570a58d..0000000000 --- a/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/InlineMaths.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (c) 2020 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.commonmark.ext.maths; - -import org.commonmark.node.CustomNode; -import org.commonmark.node.Delimited; - -public class InlineMaths extends CustomNode implements Delimited { - public enum InlineDelimiter { - SINGLE_DOLLAR, - ROUND_BRACKET_ESCAPED - }; - - private InlineDelimiter delimiter; - - public InlineMaths(InlineDelimiter delimiter) { - this.delimiter = delimiter; - } - - @Override - public String getOpeningDelimiter() { - switch (delimiter) { - case SINGLE_DOLLAR: - return "$"; - case ROUND_BRACKET_ESCAPED: - return "\\("; - } - return null; - } - - @Override - public String getClosingDelimiter() { - switch (delimiter) { - case SINGLE_DOLLAR: - return "$"; - case ROUND_BRACKET_ESCAPED: - return "\\)"; - } - return null; - } -} diff --git a/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/InlineMaths.kt b/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/InlineMaths.kt new file mode 100644 index 0000000000..cb24949317 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/InlineMaths.kt @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.commonmark.ext.maths + +import org.commonmark.node.CustomNode +import org.commonmark.node.Delimited + +class InlineMaths(private val delimiter: InlineDelimiter) : CustomNode(), Delimited { + enum class InlineDelimiter { + SINGLE_DOLLAR, ROUND_BRACKET_ESCAPED + } + + override fun getOpeningDelimiter(): String { + return when (delimiter) { + InlineDelimiter.SINGLE_DOLLAR -> "$" + InlineDelimiter.ROUND_BRACKET_ESCAPED -> "\\(" + } + } + + override fun getClosingDelimiter(): String { + return when (delimiter) { + InlineDelimiter.SINGLE_DOLLAR -> "$" + InlineDelimiter.ROUND_BRACKET_ESCAPED -> "\\)" + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/MathsExtension.java b/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/MathsExtension.java deleted file mode 100644 index 31706b3f2d..0000000000 --- a/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/MathsExtension.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (c) 2020 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.commonmark.ext.maths; - -import org.commonmark.Extension; -import org.commonmark.ext.maths.internal.DollarMathsDelimiterProcessor; -import org.commonmark.ext.maths.internal.MathsHtmlNodeRenderer; -import org.commonmark.parser.Parser; -import org.commonmark.renderer.NodeRenderer; -import org.commonmark.renderer.html.HtmlNodeRendererContext; -import org.commonmark.renderer.html.HtmlNodeRendererFactory; -import org.commonmark.renderer.html.HtmlRenderer; - -public class MathsExtension implements Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension { - - private MathsExtension() { - } - - public static Extension create() { - return new MathsExtension(); - } - - @Override - public void extend(Parser.Builder parserBuilder) { - parserBuilder.customDelimiterProcessor(new DollarMathsDelimiterProcessor()); - } - - @Override - public void extend(HtmlRenderer.Builder rendererBuilder) { - rendererBuilder.nodeRendererFactory(new HtmlNodeRendererFactory() { - @Override - public NodeRenderer create(HtmlNodeRendererContext context) { - return new MathsHtmlNodeRenderer(context); - } - }); - } -} diff --git a/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/MathsExtension.kt b/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/MathsExtension.kt new file mode 100644 index 0000000000..4a5d4e440e --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/MathsExtension.kt @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.commonmark.ext.maths + +import org.commonmark.Extension +import org.commonmark.ext.maths.internal.DollarMathsDelimiterProcessor +import org.commonmark.ext.maths.internal.MathsHtmlNodeRenderer +import org.commonmark.parser.Parser +import org.commonmark.renderer.html.HtmlRenderer + +class MathsExtension private constructor() : Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension { + override fun extend(parserBuilder: Parser.Builder) { + parserBuilder.customDelimiterProcessor(DollarMathsDelimiterProcessor()) + } + + override fun extend(rendererBuilder: HtmlRenderer.Builder) { + rendererBuilder.nodeRendererFactory { context -> MathsHtmlNodeRenderer(context) } + } + + companion object { + fun create(): Extension { + return MathsExtension() + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/DollarMathsDelimiterProcessor.java b/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/DollarMathsDelimiterProcessor.java deleted file mode 100644 index 809c070f29..0000000000 --- a/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/DollarMathsDelimiterProcessor.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (c) 2020 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.commonmark.ext.maths.internal; - -import org.commonmark.ext.maths.DisplayMaths; -import org.commonmark.ext.maths.InlineMaths; -import org.commonmark.node.Node; -import org.commonmark.node.Text; -import org.commonmark.parser.delimiter.DelimiterProcessor; -import org.commonmark.parser.delimiter.DelimiterRun; - -public class DollarMathsDelimiterProcessor implements DelimiterProcessor { - @Override - public char getOpeningCharacter() { - return '$'; - } - - @Override - public char getClosingCharacter() { - return '$'; - } - - @Override - public int getMinLength() { - return 1; - } - - @Override - public int getDelimiterUse(DelimiterRun opener, DelimiterRun closer) { - if (opener.length() == 1 && closer.length() == 1) - return 1; // inline - else if (opener.length() == 2 && closer.length() == 2) - return 2; // display - else - return 0; - } - - @Override - public void process(Text opener, Text closer, int delimiterUse) { - Node maths = delimiterUse == 1 ? new InlineMaths(InlineMaths.InlineDelimiter.SINGLE_DOLLAR) : - new DisplayMaths(DisplayMaths.DisplayDelimiter.DOUBLE_DOLLAR); - - Node tmp = opener.getNext(); - while (tmp != null && tmp != closer) { - Node next = tmp.getNext(); - maths.appendChild(tmp); - tmp = next; - } - - opener.insertAfter(maths); - } -} diff --git a/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/DollarMathsDelimiterProcessor.kt b/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/DollarMathsDelimiterProcessor.kt new file mode 100644 index 0000000000..a3daa26361 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/DollarMathsDelimiterProcessor.kt @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.commonmark.ext.maths.internal + +import org.commonmark.ext.maths.DisplayMaths +import org.commonmark.ext.maths.InlineMaths +import org.commonmark.node.Text +import org.commonmark.parser.delimiter.DelimiterProcessor +import org.commonmark.parser.delimiter.DelimiterRun + +class DollarMathsDelimiterProcessor : DelimiterProcessor { + override fun getOpeningCharacter(): Char { + return '$' + } + + override fun getClosingCharacter(): Char { + return '$' + } + + override fun getMinLength(): Int { + return 1 + } + + override fun getDelimiterUse(opener: DelimiterRun, closer: DelimiterRun): Int { + return if (opener.length() == 1 && closer.length() == 1) 1 // inline + else if (opener.length() == 2 && closer.length() == 2) 2 // display + else 0 + } + + override fun process(opener: Text, closer: Text, delimiterUse: Int) { + val maths = if (delimiterUse == 1) InlineMaths(InlineMaths.InlineDelimiter.SINGLE_DOLLAR) else DisplayMaths(DisplayMaths.DisplayDelimiter.DOUBLE_DOLLAR) + var tmp = opener.next + while (tmp != null && tmp !== closer) { + val next = tmp.next + maths.appendChild(tmp) + tmp = next + } + opener.insertAfter(maths) + } +} diff --git a/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/MathsHtmlNodeRenderer.java b/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/MathsHtmlNodeRenderer.java deleted file mode 100644 index a84129cd28..0000000000 --- a/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/MathsHtmlNodeRenderer.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (c) 2020 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.commonmark.ext.maths.internal; - -import org.commonmark.ext.maths.DisplayMaths; -import org.commonmark.node.Node; -import org.commonmark.node.Text; -import org.commonmark.renderer.html.HtmlNodeRendererContext; -import org.commonmark.renderer.html.HtmlWriter; - -import java.util.Collections; -import java.util.Map; - -public class MathsHtmlNodeRenderer extends MathsNodeRenderer { - private final HtmlNodeRendererContext context; - private final HtmlWriter html; - - public MathsHtmlNodeRenderer(HtmlNodeRendererContext context) { - this.context = context; - this.html = context.getWriter(); - } - - @Override - public void render(Node node) { - boolean display = node.getClass() == DisplayMaths.class; - Node contents = node.getFirstChild(); // should be the only child - String latex = ((Text) contents).getLiteral(); - Map attributes = context.extendAttributes(node, display ? "div" : "span", Collections.singletonMap("data-mx-maths", - latex)); - html.tag(display ? "div" : "span", attributes); - html.tag("code"); - context.render(contents); - html.tag("/code"); - html.tag(display ? "/div" : "/span"); - } -} diff --git a/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/MathsHtmlNodeRenderer.kt b/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/MathsHtmlNodeRenderer.kt new file mode 100644 index 0000000000..f46a21afd8 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/MathsHtmlNodeRenderer.kt @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.commonmark.ext.maths.internal + +import org.commonmark.ext.maths.DisplayMaths +import org.commonmark.node.Node +import org.commonmark.node.Text +import org.commonmark.renderer.html.HtmlNodeRendererContext +import org.commonmark.renderer.html.HtmlWriter +import java.util.Collections + +class MathsHtmlNodeRenderer(private val context: HtmlNodeRendererContext) : MathsNodeRenderer() { + private val html: HtmlWriter = context.writer + override fun render(node: Node) { + val display = node.javaClass == DisplayMaths::class.java + val contents = node.firstChild // should be the only child + val latex = (contents as Text).literal + val attributes = context.extendAttributes(node, if (display) "div" else "span", Collections.singletonMap("data-mx-maths", + latex)) + html.tag(if (display) "div" else "span", attributes) + html.tag("code") + context.render(contents) + html.tag("/code") + html.tag(if (display) "/div" else "/span") + } +} diff --git a/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/MathsNodeRenderer.java b/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/MathsNodeRenderer.kt similarity index 51% rename from matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/MathsNodeRenderer.java rename to matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/MathsNodeRenderer.kt index 4d2a35f33d..f6edd5385a 100644 --- a/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/MathsNodeRenderer.java +++ b/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/MathsNodeRenderer.kt @@ -13,23 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package org.commonmark.ext.maths.internal -package org.commonmark.ext.maths.internal; +import org.commonmark.ext.maths.InlineMaths +import org.commonmark.ext.maths.DisplayMaths +import org.commonmark.ext.maths.internal.MathsNodeRenderer +import org.commonmark.node.Node +import org.commonmark.renderer.NodeRenderer +import java.util.HashSet -import org.commonmark.ext.maths.DisplayMaths; -import org.commonmark.ext.maths.InlineMaths; -import org.commonmark.node.Node; -import org.commonmark.renderer.NodeRenderer; - -import java.util.HashSet; -import java.util.Set; - -abstract class MathsNodeRenderer implements NodeRenderer { - @Override - public Set> getNodeTypes() { - final Set> types = new HashSet>(); - types.add(InlineMaths.class); - types.add(DisplayMaths.class); - return types; +abstract class MathsNodeRenderer : NodeRenderer { + override fun getNodeTypes(): Set> { + val types: MutableSet> = HashSet() + types.add(InlineMaths::class.java) + types.add(DisplayMaths::class.java) + return types } -} +} \ No newline at end of file From 2262cd4b6d08bc5644a45e557b00fb51caf717f5 Mon Sep 17 00:00:00 2001 From: Nick Hu Date: Wed, 8 Sep 2021 17:01:12 +0100 Subject: [PATCH 018/632] Gate LaTeX maths behind labs flag --- .../app/features/html/EventHtmlRenderer.kt | 37 +++++++++++-------- .../features/settings/VectorPreferences.kt | 5 +++ vector/src/main/res/values/strings.xml | 1 + .../src/main/res/xml/vector_settings_labs.xml | 5 +++ 4 files changed, 33 insertions(+), 15 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/html/EventHtmlRenderer.kt b/vector/src/main/java/im/vector/app/features/html/EventHtmlRenderer.kt index e2fb385ae5..5c950b43e9 100644 --- a/vector/src/main/java/im/vector/app/features/html/EventHtmlRenderer.kt +++ b/vector/src/main/java/im/vector/app/features/html/EventHtmlRenderer.kt @@ -20,6 +20,7 @@ import android.content.Context import android.text.Spannable import androidx.core.text.toSpannable import im.vector.app.core.resources.ColorProvider +import im.vector.app.features.settings.VectorPreferences import io.noties.markwon.AbstractMarkwonPlugin import io.noties.markwon.Markwon import io.noties.markwon.MarkwonPlugin @@ -34,27 +35,33 @@ import javax.inject.Singleton @Singleton class EventHtmlRenderer @Inject constructor(htmlConfigure: MatrixHtmlPluginConfigure, - context: Context) { + context: Context, + private val vectorPreferences: VectorPreferences) { interface PostProcessor { fun afterRender(renderedText: Spannable) } - private val markwon = Markwon.builder(context) + private val builder = Markwon.builder(context) .usePlugin(HtmlPlugin.create(htmlConfigure)) - .usePlugin(object : AbstractMarkwonPlugin() { // Markwon expects maths to be in a specific format: https://noties.io/Markwon/docs/v4/ext-latex - override fun processMarkdown(markdown: String): String { - return markdown - .replace(Regex(""".*?""")) { matchResult -> "$$" + matchResult.groupValues[1] + "$$" } - .replace(Regex(""".*?""")) { matchResult -> "\n$$\n" + matchResult.groupValues[1] + "\n$$\n" } - } - }) - .usePlugin(MarkwonInlineParserPlugin.create()) - .usePlugin(JLatexMathPlugin.create(44F) { builder -> - builder.inlinesEnabled(true) - builder.theme().inlinePadding(JLatexMathTheme.Padding.symmetric(24, 8)) - }) - .build() + + private val markwon = if (vectorPreferences.latexMathsIsEnabled()) { + builder + .usePlugin(object : AbstractMarkwonPlugin() { // Markwon expects maths to be in a specific format: https://noties.io/Markwon/docs/v4/ext-latex + override fun processMarkdown(markdown: String): String { + return markdown + .replace(Regex(""".*?""")) { matchResult -> "$$" + matchResult.groupValues[1] + "$$" } + .replace(Regex(""".*?""")) { matchResult -> "\n$$\n" + matchResult.groupValues[1] + "\n$$\n" } + } + }) + .usePlugin(MarkwonInlineParserPlugin.create()) + .usePlugin(JLatexMathPlugin.create(44F) { builder -> + builder.inlinesEnabled(true) + builder.theme().inlinePadding(JLatexMathTheme.Padding.symmetric(24, 8)) + }) + } else { + builder + }.build() val plugins: List = markwon.plugins 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 0b7b495f48..1e673e7b4f 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 @@ -100,6 +100,7 @@ class VectorPreferences @Inject constructor(private val context: Context) { private const val SETTINGS_SEND_MESSAGE_WITH_ENTER = "SETTINGS_SEND_MESSAGE_WITH_ENTER" private const val SETTINGS_ENABLE_CHAT_EFFECTS = "SETTINGS_ENABLE_CHAT_EFFECTS" private const val SETTINGS_SHOW_EMOJI_KEYBOARD = "SETTINGS_SHOW_EMOJI_KEYBOARD" + private const val SETTINGS_LABS_ENABLE_LATEX_MATHS = "SETTINGS_LABS_ENABLE_LATEX_MATHS" // Room directory private const val SETTINGS_ROOM_DIRECTORY_SHOW_ALL_PUBLIC_ROOMS = "SETTINGS_ROOM_DIRECTORY_SHOW_ALL_PUBLIC_ROOMS" @@ -337,6 +338,10 @@ class VectorPreferences @Inject constructor(private val context: Context) { return defaultPrefs.getBoolean(SETTINGS_LABS_UNREAD_NOTIFICATIONS_AS_TAB, false) } + fun latexMathsIsEnabled(): Boolean { + return defaultPrefs.getBoolean(SETTINGS_LABS_ENABLE_LATEX_MATHS, false) + } + fun failFast(): Boolean { return BuildConfig.DEBUG || (developerMode() && defaultPrefs.getBoolean(SETTINGS_DEVELOPER_MODE_FAIL_FAST_PREFERENCE_KEY, false)) } diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 453b5ba432..f96320175a 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -3595,4 +3595,5 @@ Link this email with your account %s in Settings to receive invites directly in Element. + Enable LaTeX mathematics diff --git a/vector/src/main/res/xml/vector_settings_labs.xml b/vector/src/main/res/xml/vector_settings_labs.xml index 6260f65fd9..e7f06bc3b3 100644 --- a/vector/src/main/res/xml/vector_settings_labs.xml +++ b/vector/src/main/res/xml/vector_settings_labs.xml @@ -50,6 +50,11 @@ android:key="SETTINGS_LABS_USE_RESTRICTED_JOIN_RULE" android:title="@string/labs_use_restricted_join_rule" android:summary="@string/labs_use_restricted_join_rule_desc"/> + + From cf1a93839f6fc1554d91b2a6d4098459c1e6b623 Mon Sep 17 00:00:00 2001 From: Nick Hu Date: Wed, 8 Sep 2021 23:13:53 +0100 Subject: [PATCH 019/632] Use PrecomputedFutureTextSetterCompat --- .../main/java/im/vector/app/features/html/EventHtmlRenderer.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/html/EventHtmlRenderer.kt b/vector/src/main/java/im/vector/app/features/html/EventHtmlRenderer.kt index 5c950b43e9..35da5ca4d1 100644 --- a/vector/src/main/java/im/vector/app/features/html/EventHtmlRenderer.kt +++ b/vector/src/main/java/im/vector/app/features/html/EventHtmlRenderer.kt @@ -24,6 +24,7 @@ import im.vector.app.features.settings.VectorPreferences import io.noties.markwon.AbstractMarkwonPlugin import io.noties.markwon.Markwon import io.noties.markwon.MarkwonPlugin +import io.noties.markwon.PrecomputedFutureTextSetterCompat import io.noties.markwon.ext.latex.JLatexMathPlugin import io.noties.markwon.ext.latex.JLatexMathTheme import io.noties.markwon.html.HtmlPlugin @@ -61,7 +62,7 @@ class EventHtmlRenderer @Inject constructor(htmlConfigure: MatrixHtmlPluginConfi }) } else { builder - }.build() + }.textSetter(PrecomputedFutureTextSetterCompat.create()).build() val plugins: List = markwon.plugins From 0a498bee38bc60c3344077ecab8247cbfb531431 Mon Sep 17 00:00:00 2001 From: Nick Hu Date: Fri, 24 Sep 2021 12:43:41 +0100 Subject: [PATCH 020/632] Fix lints and add changelog.d entry --- changelog.d/2133.feature | 1 + .../main/java/org/commonmark/ext/maths/DisplayMaths.kt | 4 ++-- .../src/main/java/org/commonmark/ext/maths/InlineMaths.kt | 2 +- .../main/java/org/commonmark/ext/maths/MathsExtension.kt | 2 +- .../ext/maths/internal/DollarMathsDelimiterProcessor.kt | 2 +- .../ext/maths/internal/MathsHtmlNodeRenderer.kt | 2 +- .../commonmark/ext/maths/internal/MathsNodeRenderer.kt | 4 ++-- .../java/im/vector/app/features/html/EventHtmlRenderer.kt | 8 ++++++-- 8 files changed, 15 insertions(+), 10 deletions(-) create mode 100644 changelog.d/2133.feature diff --git a/changelog.d/2133.feature b/changelog.d/2133.feature new file mode 100644 index 0000000000..5649ca4cc6 --- /dev/null +++ b/changelog.d/2133.feature @@ -0,0 +1 @@ +Add labs support for rendering LaTeX maths (MSC2191) diff --git a/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/DisplayMaths.kt b/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/DisplayMaths.kt index bf6a89a26a..a69649640f 100644 --- a/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/DisplayMaths.kt +++ b/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/DisplayMaths.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 New Vector Ltd + * Copyright (c) 2021 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. @@ -21,4 +21,4 @@ class DisplayMaths(private val delimiter: DisplayDelimiter) : CustomBlock() { enum class DisplayDelimiter { DOUBLE_DOLLAR, SQUARE_BRACKET_ESCAPED } -} \ No newline at end of file +} diff --git a/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/InlineMaths.kt b/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/InlineMaths.kt index cb24949317..799626045d 100644 --- a/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/InlineMaths.kt +++ b/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/InlineMaths.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 New Vector Ltd + * Copyright (c) 2021 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. diff --git a/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/MathsExtension.kt b/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/MathsExtension.kt index 4a5d4e440e..18c0fc4284 100644 --- a/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/MathsExtension.kt +++ b/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/MathsExtension.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 New Vector Ltd + * Copyright (c) 2021 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. diff --git a/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/DollarMathsDelimiterProcessor.kt b/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/DollarMathsDelimiterProcessor.kt index a3daa26361..55b27a21bc 100644 --- a/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/DollarMathsDelimiterProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/DollarMathsDelimiterProcessor.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 New Vector Ltd + * Copyright (c) 2021 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. diff --git a/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/MathsHtmlNodeRenderer.kt b/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/MathsHtmlNodeRenderer.kt index f46a21afd8..94652ed7ad 100644 --- a/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/MathsHtmlNodeRenderer.kt +++ b/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/MathsHtmlNodeRenderer.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 New Vector Ltd + * Copyright (c) 2021 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. diff --git a/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/MathsNodeRenderer.kt b/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/MathsNodeRenderer.kt index f6edd5385a..d5ce47abeb 100644 --- a/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/MathsNodeRenderer.kt +++ b/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/MathsNodeRenderer.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 New Vector Ltd + * Copyright (c) 2021 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. @@ -29,4 +29,4 @@ abstract class MathsNodeRenderer : NodeRenderer { types.add(DisplayMaths::class.java) return types } -} \ No newline at end of file +} diff --git a/vector/src/main/java/im/vector/app/features/html/EventHtmlRenderer.kt b/vector/src/main/java/im/vector/app/features/html/EventHtmlRenderer.kt index 35da5ca4d1..2d832b60cc 100644 --- a/vector/src/main/java/im/vector/app/features/html/EventHtmlRenderer.kt +++ b/vector/src/main/java/im/vector/app/features/html/EventHtmlRenderer.kt @@ -51,8 +51,12 @@ class EventHtmlRenderer @Inject constructor(htmlConfigure: MatrixHtmlPluginConfi .usePlugin(object : AbstractMarkwonPlugin() { // Markwon expects maths to be in a specific format: https://noties.io/Markwon/docs/v4/ext-latex override fun processMarkdown(markdown: String): String { return markdown - .replace(Regex(""".*?""")) { matchResult -> "$$" + matchResult.groupValues[1] + "$$" } - .replace(Regex(""".*?""")) { matchResult -> "\n$$\n" + matchResult.groupValues[1] + "\n$$\n" } + .replace(Regex(""".*?""")) { + matchResult -> "$$" + matchResult.groupValues[1] + "$$" + } + .replace(Regex(""".*?""")) { + matchResult -> "\n$$\n" + matchResult.groupValues[1] + "\n$$\n" + } } }) .usePlugin(MarkwonInlineParserPlugin.create()) From d42a2e69aeb5161fdc770f18144d22c7f71547f8 Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 3 Nov 2021 11:44:01 +0100 Subject: [PATCH 021/632] Timeline: don't remove annotations and read receipts when deleting timeline event --- .../android/sdk/internal/database/model/TimelineEventEntity.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/TimelineEventEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/TimelineEventEntity.kt index 30bbde70c2..185f0e2dcc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/TimelineEventEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/TimelineEventEntity.kt @@ -46,7 +46,5 @@ internal fun TimelineEventEntity.deleteOnCascade(canDeleteRoot: Boolean) { if (canDeleteRoot) { root?.deleteFromRealm() } - annotations?.deleteOnCascade() - readReceipts?.deleteOnCascade() deleteFromRealm() } From ce5ccd4dab5a18364526fcf7559b84f6473c0cb1 Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 3 Nov 2021 11:44:20 +0100 Subject: [PATCH 022/632] Timeline: remove useless methods --- .../sdk/api/session/room/timeline/Timeline.kt | 20 ------------------- .../session/room/timeline/DefaultTimeline.kt | 19 +----------------- .../detail/timeline/merged/MergedTimelines.kt | 18 ----------------- 3 files changed, 1 insertion(+), 56 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/Timeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/Timeline.kt index ae202da979..3ba2aadd38 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/Timeline.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/Timeline.kt @@ -70,31 +70,11 @@ interface Timeline { */ fun paginate(direction: Direction, count: Int) - /** - * Returns the number of sending events - */ - fun pendingEventCount(): Int - - /** - * Returns the number of failed sending events. - */ - fun failedToDeliverEventCount(): Int - /** * Returns the index of a built event or null. */ fun getIndexOfEvent(eventId: String?): Int? - /** - * Returns the built [TimelineEvent] at index or null - */ - fun getTimelineEventAtIndex(index: Int): TimelineEvent? - - /** - * Returns the built [TimelineEvent] with eventId or null - */ - fun getTimelineEventWithId(eventId: String?): TimelineEvent? - fun getPaginationState(direction: Direction): PaginationState interface Listener { 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 b4e839ae21..857075ffde 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 @@ -54,7 +54,7 @@ internal class DefaultTimeline internal constructor(private val roomId: String, eventDecryptor: TimelineEventDecryptor) : Timeline { companion object { - val BACKGROUND_HANDLER = createBackgroundHandler("SimpleTimeline_Thread") + val BACKGROUND_HANDLER = createBackgroundHandler("DefaultTimeline_Thread") } override val timelineID = UUID.randomUUID().toString() @@ -144,28 +144,11 @@ internal class DefaultTimeline internal constructor(private val roomId: String, } } - override fun pendingEventCount(): Int { - return 0 - } - - override fun failedToDeliverEventCount(): Int { - return 0 - } - override fun getIndexOfEvent(eventId: String?): Int? { if (eventId == null) return null return strategy.getBuiltEventIndex(eventId) } - override fun getTimelineEventAtIndex(index: Int): TimelineEvent? { - return null - } - - override fun getTimelineEventWithId(eventId: String?): TimelineEvent? { - if (eventId == null) return null - return strategy.getBuiltEvent(eventId) - } - override fun getPaginationState(direction: Timeline.Direction): Timeline.PaginationState { return if (direction == Timeline.Direction.BACKWARDS) { backwardState diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/merged/MergedTimelines.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/merged/MergedTimelines.kt index 0d5dbc5a8e..535b733dc2 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/merged/MergedTimelines.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/merged/MergedTimelines.kt @@ -133,28 +133,10 @@ class MergedTimelines( secondaryTimeline.paginate(direction, count) } - override fun pendingEventCount(): Int { - return mainTimeline.pendingEventCount() + secondaryTimeline.pendingEventCount() - } - - override fun failedToDeliverEventCount(): Int { - return mainTimeline.pendingEventCount() + secondaryTimeline.pendingEventCount() - } - - override fun getTimelineEventAtIndex(index: Int): TimelineEvent? { - return mergedEvents.getOrNull(index) - } - override fun getIndexOfEvent(eventId: String?): Int? { return positionsMapping[eventId] } - override fun getTimelineEventWithId(eventId: String?): TimelineEvent? { - return positionsMapping[eventId]?.let { - getTimelineEventAtIndex(it) - } - } - private fun processTimelineUpdates(isInit: KMutableProperty0, eventsRef: MutableList, newData: List) { coroutineScope.launch(Dispatchers.Default) { processingSemaphore.withPermit { From 52d0da705316328499bc33abd2b3c6f917faf45c Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 3 Nov 2021 19:02:44 +0100 Subject: [PATCH 023/632] Timeline: remove previous lastForward chunk --- .../session/sync/handler/room/RoomSyncHandler.kt | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) 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 8c4af81c99..4a3a3617ca 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 @@ -344,15 +344,17 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle syncLocalTimestampMillis: Long, aggregator: SyncResponsePostTreatmentAggregator): ChunkEntity { val lastChunk = ChunkEntity.findLastForwardChunkOfRoom(realm, roomEntity.roomId) + if (isLimited && lastChunk != null) { + lastChunk.deleteOnCascade(deleteStateEvents = true, canDeleteRoot = true) + } val chunkEntity = if (!isLimited && lastChunk != null) { lastChunk } else { - realm.createObject().apply { this.prevToken = prevToken } + realm.createObject().apply { + this.prevToken = prevToken + this.isLastForward = true + } } - // Only one chunk has isLastForward set to true - lastChunk?.isLastForward = false - chunkEntity.isLastForward = true - val eventIds = ArrayList(eventList.size) val roomMemberContentsByUser = HashMap() From a1fdd31b6800f570999f286ed379afa26d837faa Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 3 Nov 2021 19:02:58 +0100 Subject: [PATCH 024/632] Timeline: just some renaming + constant --- .../session/room/timeline/DefaultTimeline.kt | 5 ++--- .../session/room/timeline/LoadTimelineStrategy.kt | 15 +++++++++------ 2 files changed, 11 insertions(+), 9 deletions(-) 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 857075ffde..e4173e90f8 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 @@ -29,7 +29,6 @@ import kotlinx.coroutines.withContext import okhttp3.internal.closeQuietly import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.room.timeline.Timeline -import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask import org.matrix.android.sdk.internal.session.sync.handler.room.ReadReceiptHandler @@ -81,7 +80,7 @@ internal class DefaultTimeline internal constructor(private val roomId: String, onNewTimelineEvents = this::onNewTimelineEvents ) - private var strategy: LoadTimelineStrategy = buildStrategy(LoadTimelineStrategy.Mode.Default) + private var strategy: LoadTimelineStrategy = buildStrategy(LoadTimelineStrategy.Mode.Live) override val isLive: Boolean get() = !getPaginationState(Timeline.Direction.FORWARDS).hasMoreToLoad @@ -191,7 +190,7 @@ internal class DefaultTimeline internal constructor(private val roomId: String, } strategy.onStop() strategy = if (eventId == null) { - buildStrategy(LoadTimelineStrategy.Mode.Default) + buildStrategy(LoadTimelineStrategy.Mode.Live) } else { buildStrategy(LoadTimelineStrategy.Mode.Permalink(eventId)) } 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 3d8eb89315..975ef4e56d 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 @@ -33,11 +33,14 @@ import java.util.concurrent.atomic.AtomicReference /** * This class is responsible for keeping an instance of chunkEntity and timelineChunk according to the strategy. - * There is 2 different mode: Default and Permalink. - * In Default, we will query for the live chunk (isLastForward = true). + * There is 2 different mode: Live and Permalink. + * In Live, we will query for the live chunk (isLastForward = true). * In Permalink, we will query for the chunk including the eventId we are looking for. * Once we got a ChunkEntity we wrap it with TimelineChunk class so we dispatch any methods for loading data. */ + +private const val INITIAL_LOAD_COUNT = 30L + internal class LoadTimelineStrategy( private val roomId: String, private val timelineId: String, @@ -45,7 +48,7 @@ internal class LoadTimelineStrategy( private val dependencies: Dependencies) { sealed class Mode { - object Default : Mode() + object Live : Mode() data class Permalink(val originEventId: String) : Mode() fun originEventId(): String? { @@ -109,7 +112,7 @@ internal class LoadTimelineStrategy( override fun onNewTimelineEvents(roomId: String, eventIds: List) { super.onNewTimelineEvents(roomId, eventIds) - if (mode == Mode.Default && roomId == this@LoadTimelineStrategy.roomId) { + if (mode == Mode.Live && roomId == this@LoadTimelineStrategy.roomId) { dependencies.onNewTimelineEvents(eventIds) } } @@ -133,8 +136,8 @@ internal class LoadTimelineStrategy( it.addChangeListener(chunkEntityListener) timelineChunk = it.createTimelineChunk() } - if (mode is Mode.Default) { - loadMore(10, Timeline.Direction.BACKWARDS) + if (mode is Mode.Live) { + loadMore(INITIAL_LOAD_COUNT, Timeline.Direction.BACKWARDS) } } From 92a37f15d4b4def2de216049552181a1374da9b8 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 4 Nov 2021 13:11:45 +0100 Subject: [PATCH 025/632] Timeline: fix hasReachedEnd --- .../session/room/timeline/LoadTimelineStrategy.kt | 9 ++++++--- .../sdk/internal/session/room/timeline/TimelineChunk.kt | 9 ++++++++- 2 files changed, 14 insertions(+), 4 deletions(-) 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 975ef4e56d..3f4d751f6d 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 @@ -111,8 +111,7 @@ internal class LoadTimelineStrategy( } override fun onNewTimelineEvents(roomId: String, eventIds: List) { - super.onNewTimelineEvents(roomId, eventIds) - if (mode == Mode.Live && roomId == this@LoadTimelineStrategy.roomId) { + if (roomId == this@LoadTimelineStrategy.roomId && hasReachedLastForward()) { dependencies.onNewTimelineEvents(eventIds) } } @@ -178,7 +177,7 @@ internal class LoadTimelineStrategy( } private fun buildSendingEvents(): List { - return if (timelineChunk?.hasReachedLastForward().orFalse()) { + return if (hasReachedLastForward()) { sendingEventsDataSource.buildSendingEvents() } else { emptyList() @@ -195,6 +194,10 @@ internal class LoadTimelineStrategy( } } + private fun hasReachedLastForward(): Boolean{ + return timelineChunk?.hasReachedLastForward().orFalse() + } + private fun RealmResults.createTimelineChunk(): TimelineChunk? { return firstOrNull()?.let { return TimelineChunk( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt index 3e46681358..fcea10250b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt @@ -28,10 +28,12 @@ import org.matrix.android.sdk.api.session.room.timeline.Timeline import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper import org.matrix.android.sdk.internal.database.model.ChunkEntity +import org.matrix.android.sdk.internal.database.model.ChunkEntityFields import org.matrix.android.sdk.internal.database.model.TimelineEventEntity import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields import timber.log.Timber import java.util.Collections +import java.util.concurrent.atomic.AtomicBoolean /** * This is the value used to fetch on server. It's better to make constant as otherwise we can have weird chunks with disparate and small chunk of data. @@ -54,8 +56,13 @@ internal class TimelineChunk constructor(private val chunkEntity: ChunkEntity, private val initialEventId: String?, private val onBuiltEvents: () -> Unit) { + private val isLastForward = AtomicBoolean(chunkEntity.isLastForward) + private val chunkObjectListener = RealmObjectChangeListener { _, changeSet -> Timber.v("on chunk (${chunkEntity.identifier()}) changed: ${changeSet?.changedFields?.joinToString(",")}") + if(changeSet?.isFieldChanged(ChunkEntityFields.IS_LAST_FORWARD).orFalse()){ + isLastForward.set(chunkEntity.isLastForward) + } } private val timelineEventCollectionListener = OrderedRealmCollectionChangeListener { results: RealmResults, changeSet: OrderedCollectionChangeSet -> @@ -77,7 +84,7 @@ internal class TimelineChunk constructor(private val chunkEntity: ChunkEntity, } fun hasReachedLastForward(): Boolean { - return if (chunkEntity.isLastForward) { + return if (isLastForward.get()) { true } else { nextChunk?.hasReachedLastForward().orFalse() From 52df50a6865289421e56e6c6a6c768ce0e50bba5 Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 10 Nov 2021 19:17:34 +0100 Subject: [PATCH 026/632] Timeline: continue trying to make Read marker/receipts working --- .../session/room/timeline/TimelineEvent.kt | 4 +++ .../database/helper/ChunkEntityHelper.kt | 26 +++++++++++++++++++ .../helper/TimelineEventEntityHelper.kt | 10 +++++++ .../internal/database/query/ReadQueries.kt | 24 ++++++++--------- .../session/room/read/DefaultReadService.kt | 1 - .../home/room/detail/RoomDetailViewModel.kt | 11 +++++--- 6 files changed, 60 insertions(+), 16 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt index 4a6462477d..b9c7c812ae 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt @@ -44,6 +44,10 @@ data class TimelineEvent( */ val localId: Long, val eventId: String, + /** + * This display index is the position in the current chunk. + * It's not unique on the timeline as it's reset on each chunk. + */ val displayIndex: Int, val senderInfo: SenderInfo, val annotations: EventAnnotationsSummary? = null, 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 482f4ad842..136734c02e 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 @@ -191,3 +191,29 @@ internal fun ChunkEntity.nextDisplayIndex(direction: PaginationDirection): Int { } } } + +internal fun ChunkEntity.doesNextChunksVerifyCondition(linkCondition: (ChunkEntity) -> Boolean): Boolean { + var nextChunkToCheck = this.nextChunk + while (nextChunkToCheck != null) { + if (linkCondition(nextChunkToCheck)) { + return true + } + nextChunkToCheck = nextChunkToCheck.nextChunk + } + return false +} + +internal fun ChunkEntity.isMoreRecentThan(chunkToCheck: ChunkEntity): Boolean { + if (this.isLastForward) return true + if (chunkToCheck.isLastForward) return false + // Check if the chunk to check is linked to this one + if(chunkToCheck.doesNextChunksVerifyCondition { it == this }){ + return true + } + // Otherwise check if this chunk is linked to last forward + if(this.doesNextChunksVerifyCondition { it.isLastForward }){ + return true + } + // We don't know, so we assume it's false + return false +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/TimelineEventEntityHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/TimelineEventEntityHelper.kt index 3993e8e799..1d2cbcad51 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/TimelineEventEntityHelper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/TimelineEventEntityHelper.kt @@ -28,3 +28,13 @@ internal fun TimelineEventEntity.Companion.nextId(realm: Realm): Long { currentIdNum.toLong() + 1 } } + +internal fun TimelineEventEntity.isMoreRecentThan(eventToCheck: TimelineEventEntity): Boolean { + val currentChunk = this.chunk?.first() ?: return false + val chunkToCheck = eventToCheck.chunk?.firstOrNull() ?: return false + return if (currentChunk == chunkToCheck) { + this.displayIndex >= eventToCheck.displayIndex + } else { + currentChunk.isMoreRecentThan(chunkToCheck) + } +} 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 60096777d9..c8879b009e 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 @@ -18,6 +18,8 @@ package org.matrix.android.sdk.internal.database.query import io.realm.Realm import io.realm.RealmConfiguration import org.matrix.android.sdk.api.session.events.model.LocalEcho +import org.matrix.android.sdk.internal.database.helper.doesNextChunksVerifyCondition +import org.matrix.android.sdk.internal.database.helper.isMoreRecentThan import org.matrix.android.sdk.internal.database.model.ChunkEntity import org.matrix.android.sdk.internal.database.model.ReadMarkerEntity import org.matrix.android.sdk.internal.database.model.ReadReceiptEntity @@ -33,28 +35,26 @@ internal fun isEventRead(realmConfiguration: RealmConfiguration, if (LocalEcho.isLocalEchoId(eventId)) { return true } + // If we don't know if the event has been read, we assume it's not var isEventRead = false Realm.getInstance(realmConfiguration).use { realm -> - val liveChunk = ChunkEntity.findLastForwardChunkOfRoom(realm, roomId) ?: return@use - val eventToCheck = liveChunk.timelineEvents.find(eventId) + val latestEvent = TimelineEventEntity.latestEvent(realm, roomId, true) + // If latest event is from you we are sure the event is read + if (latestEvent?.root?.sender == userId) { + return true + } + val eventToCheck = TimelineEventEntity.where(realm, roomId, eventId).findFirst() isEventRead = when { - eventToCheck == null -> hasReadMissingEvent( - realm = realm, - latestChunkEntity = liveChunk, - roomId = roomId, - userId = userId, - eventId = eventId - ) + eventToCheck == null -> false eventToCheck.root?.sender == userId -> true else -> { val readReceipt = ReadReceiptEntity.where(realm, roomId, userId).findFirst() ?: return@use - val readReceiptIndex = liveChunk.timelineEvents.find(readReceipt.eventId)?.displayIndex ?: Int.MIN_VALUE - eventToCheck.displayIndex <= readReceiptIndex + val readReceiptEvent = TimelineEventEntity.where(realm, roomId, readReceipt.eventId).findFirst() ?: return@use + readReceiptEvent.isMoreRecentThan(eventToCheck) } } } - return isEventRead } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/DefaultReadService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/DefaultReadService.kt index 28f55a01ee..7e42e4f9c2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/DefaultReadService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/DefaultReadService.kt @@ -39,7 +39,6 @@ import org.matrix.android.sdk.internal.task.TaskExecutor internal class DefaultReadService @AssistedInject constructor( @Assisted private val roomId: String, @SessionDatabase private val monarchy: Monarchy, - private val taskExecutor: TaskExecutor, private val setReadMarkersTask: SetReadMarkersTask, private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper, @UserId private val userId: String diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index 03bde7d4cc..4918618683 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -794,7 +794,6 @@ class RoomDetailViewModel @AssistedInject constructor( } private fun handleNavigateToEvent(action: RoomDetailAction.NavigateToEvent) { - stopTrackingUnreadMessages() val targetEventId: String = action.eventId val indexOfEvent = timeline.getIndexOfEvent(targetEventId) if (indexOfEvent == null) { @@ -868,12 +867,12 @@ class RoomDetailViewModel @AssistedInject constructor( .buffer(1, TimeUnit.SECONDS) .filter { it.isNotEmpty() } .subscribeBy(onNext = { actions -> - val bufferedMostRecentDisplayedEvent = actions.maxByOrNull { it.event.displayIndex }?.event ?: return@subscribeBy + val bufferedMostRecentDisplayedEvent = actions.minByOrNull { it.event.indexOfEvent() }?.event ?: return@subscribeBy val globalMostRecentDisplayedEvent = mostRecentDisplayedEvent if (trackUnreadMessages.get()) { if (globalMostRecentDisplayedEvent == null) { mostRecentDisplayedEvent = bufferedMostRecentDisplayedEvent - } else if (bufferedMostRecentDisplayedEvent.displayIndex > globalMostRecentDisplayedEvent.displayIndex) { + } else if (bufferedMostRecentDisplayedEvent.indexOfEvent() < globalMostRecentDisplayedEvent.indexOfEvent()) { mostRecentDisplayedEvent = bufferedMostRecentDisplayedEvent } } @@ -886,6 +885,12 @@ class RoomDetailViewModel @AssistedInject constructor( .disposeOnClear() } + /** + * Returns the index of event in the timeline. + * Returns Int.MAX_VALUE if not found + */ + private fun TimelineEvent.indexOfEvent(): Int = timeline.getIndexOfEvent(eventId) ?: Int.MAX_VALUE + private fun handleMarkAllAsRead() { setState { copy(unreadState = UnreadState.HasNoUnread) } viewModelScope.launch { From 8c0b2a6704da7b74e7edc9f299e5388748ed7a04 Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 16 Nov 2021 18:14:11 +0100 Subject: [PATCH 027/632] Timeline: fix double link issue when server is messed up... --- .../room/timeline/TokenChunkEventPersistor.kt | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 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 43c2c37f84..54d7412166 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 @@ -128,7 +128,7 @@ internal class TokenChunkEventPersistor @Inject constructor(@SessionDatabase pri roomMemberContentsByUser[stateEvent.stateKey] = stateEvent.content.toModel() } } - run processTimelineEvents@ { + run processTimelineEvents@{ eventList.forEach { event -> if (event.eventId == null || event.senderId == null) { return@forEach @@ -139,12 +139,23 @@ internal class TokenChunkEventPersistor @Inject constructor(@SessionDatabase pri // If it exists, we want to stop here, just link the prevChunk val existingChunk = existingTimelineEvent?.chunk?.firstOrNull() if (existingChunk != null) { - if (direction == PaginationDirection.BACKWARDS) { - currentChunk.prevChunk = existingChunk - existingChunk.nextChunk = currentChunk - } else if (direction == PaginationDirection.FORWARDS) { - currentChunk.nextChunk = existingChunk - existingChunk.prevChunk = currentChunk + when (direction) { + PaginationDirection.BACKWARDS -> { + if (currentChunk.nextChunk == existingChunk) { + Timber.w("Avoid double link, shouldn't happen in an ideal world") + } else { + currentChunk.prevChunk = existingChunk + existingChunk.nextChunk = currentChunk + } + } + PaginationDirection.FORWARDS -> { + if (currentChunk.prevChunk == existingChunk) { + Timber.w("Avoid double link, shouldn't happen in an ideal world") + } else { + currentChunk.nextChunk = existingChunk + existingChunk.prevChunk = currentChunk + } + } } // Stop processing here return@processTimelineEvents From e562d7684ad25b3286f35c79a8d1ec31cf42a1b4 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 18 Nov 2021 11:03:13 +0100 Subject: [PATCH 028/632] Timeline: re-add usage of TimelineSettings --- .../internal/session/room/timeline/DefaultTimeline.kt | 3 +++ .../session/room/timeline/DefaultTimelineService.kt | 1 + .../session/room/timeline/LoadTimelineStrategy.kt | 7 ++++--- .../sdk/internal/session/room/timeline/TimelineChunk.kt | 9 ++++++++- 4 files changed, 16 insertions(+), 4 deletions(-) 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 e4173e90f8..82684f8c7e 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 @@ -29,6 +29,7 @@ import kotlinx.coroutines.withContext import okhttp3.internal.closeQuietly import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.room.timeline.Timeline +import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask import org.matrix.android.sdk.internal.session.sync.handler.room.ReadReceiptHandler @@ -42,6 +43,7 @@ import java.util.concurrent.atomic.AtomicReference internal class DefaultTimeline internal constructor(private val roomId: String, private val initialEventId: String?, + private val settings: TimelineSettings, private val realmConfiguration: RealmConfiguration, private val loadRoomMembersTask: LoadRoomMembersTask, private val readReceiptHandler: ReadReceiptHandler, @@ -70,6 +72,7 @@ internal class DefaultTimeline internal constructor(private val roomId: String, private val strategyDependencies = LoadTimelineStrategy.Dependencies( eventDecryptor = eventDecryptor, + timelineSettings = settings, paginationTask = paginationTask, fetchTokenAndPaginateTask = fetchTokenAndPaginateTask, timelineInput = timelineInput, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt index a898510654..31a6e627ec 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt @@ -64,6 +64,7 @@ internal class DefaultTimelineService @AssistedInject constructor( return DefaultTimeline( roomId = roomId, initialEventId = eventId, + settings = settings, realmConfiguration = monarchy.realmConfiguration, paginationTask = paginationTask, timelineEventMapper = timelineEventMapper, 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 3f4d751f6d..72613e5c6b 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,7 @@ import org.matrix.android.sdk.api.extensions.orFalse 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 +import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper import org.matrix.android.sdk.internal.database.model.ChunkEntity import org.matrix.android.sdk.internal.database.model.ChunkEntityFields @@ -39,8 +40,6 @@ import java.util.concurrent.atomic.AtomicReference * Once we got a ChunkEntity we wrap it with TimelineChunk class so we dispatch any methods for loading data. */ -private const val INITIAL_LOAD_COUNT = 30L - internal class LoadTimelineStrategy( private val roomId: String, private val timelineId: String, @@ -61,6 +60,7 @@ internal class LoadTimelineStrategy( } data class Dependencies( + val timelineSettings: TimelineSettings, val realm: AtomicReference, val eventDecryptor: TimelineEventDecryptor, val paginationTask: PaginationTask, @@ -136,7 +136,7 @@ internal class LoadTimelineStrategy( timelineChunk = it.createTimelineChunk() } if (mode is Mode.Live) { - loadMore(INITIAL_LOAD_COUNT, Timeline.Direction.BACKWARDS) + loadMore(dependencies.timelineSettings.initialSize.toLong(), Timeline.Direction.BACKWARDS) } } @@ -202,6 +202,7 @@ internal class LoadTimelineStrategy( return firstOrNull()?.let { return TimelineChunk( chunkEntity = it, + timelineSettings = dependencies.timelineSettings, roomId = roomId, timelineId = timelineId, eventDecryptor = dependencies.eventDecryptor, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt index fcea10250b..7b45d4cdc5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt @@ -26,6 +26,7 @@ import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.room.timeline.Timeline import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent +import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper import org.matrix.android.sdk.internal.database.model.ChunkEntity import org.matrix.android.sdk.internal.database.model.ChunkEntityFields @@ -46,6 +47,7 @@ private const val PAGINATION_COUNT = 50 * It also triggers pagination to the server when needed, or dispatch to the prev or next chunk if any. */ internal class TimelineChunk constructor(private val chunkEntity: ChunkEntity, + private val timelineSettings: TimelineSettings, private val roomId: String, private val timelineId: String, private val eventDecryptor: TimelineEventDecryptor, @@ -59,6 +61,9 @@ internal class TimelineChunk constructor(private val chunkEntity: ChunkEntity, private val isLastForward = AtomicBoolean(chunkEntity.isLastForward) private val chunkObjectListener = RealmObjectChangeListener { _, changeSet -> + if(changeSet?.isDeleted.orFalse()){ + return@RealmObjectChangeListener + } Timber.v("on chunk (${chunkEntity.identifier()}) changed: ${changeSet?.changedFields?.joinToString(",")}") if(changeSet?.isFieldChanged(ChunkEntityFields.IS_LAST_FORWARD).orFalse()){ isLastForward.set(chunkEntity.isLastForward) @@ -252,7 +257,8 @@ internal class TimelineChunk constructor(private val chunkEntity: ChunkEntity, } private fun buildTimelineEvent(eventEntity: TimelineEventEntity) = timelineEventMapper.map( - timelineEventEntity = eventEntity + timelineEventEntity = eventEntity, + buildReadReceipts = timelineSettings.buildReadReceipts ).let { // eventually enhance with ui echo? (uiEchoManager?.decorateEventWithReactionUiEcho(it) ?: it) @@ -261,6 +267,7 @@ internal class TimelineChunk constructor(private val chunkEntity: ChunkEntity, private fun createTimelineChunk(chunkEntity: ChunkEntity): TimelineChunk { return TimelineChunk( chunkEntity = chunkEntity, + timelineSettings = timelineSettings, timelineId = timelineId, eventDecryptor = eventDecryptor, roomId = roomId, From 2b3de840f127adbd97a70be2dddbc42bd35c8989 Mon Sep 17 00:00:00 2001 From: David Langley Date: Mon, 22 Nov 2021 17:02:12 +0000 Subject: [PATCH 029/632] Force markdown parse on replies and update quote to use markdown parser. --- .../sdk/api/session/room/send/SendService.kt | 10 ++++ .../session/room/send/DefaultSendService.kt | 6 +++ .../room/send/LocalEchoEventFactory.kt | 54 ++++++++++++++++--- .../session/room/send/MarkdownParser.kt | 10 +++- .../detail/composer/TextComposerViewModel.kt | 18 +------ 5 files changed, 73 insertions(+), 25 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt index a2b38b6606..9c41f0d344 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt @@ -56,6 +56,16 @@ interface SendService { */ fun sendFormattedTextMessage(text: String, formattedText: String, msgType: String = MessageType.MSGTYPE_TEXT): Cancelable + /** + * Method to quote an events content. + * @param quotedEvent The event to which we will quote it's content. + * @param text the text message to send + * @param msgType the message type: MessageType.MSGTYPE_TEXT (default) or MessageType.MSGTYPE_EMOTE + * @param autoMarkdown If true, the SDK will generate a formatted HTML message from the body text if markdown syntax is present + * @return a [Cancelable] + */ + fun sendQuotedTextMessage(quotedEvent: TimelineEvent, text: String, autoMarkdown: Boolean = false): Cancelable + /** * Method to send a media asynchronously. * @param attachment the media to send diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt index 77aadef6bd..47cc1fc29a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt @@ -97,6 +97,12 @@ internal class DefaultSendService @AssistedInject constructor( .let { sendEvent(it) } } + override fun sendQuotedTextMessage(quotedEvent: TimelineEvent, text: String, autoMarkdown: Boolean): Cancelable { + return localEchoEventFactory.createQuotedTextEvent(roomId, quotedEvent, text, autoMarkdown) + .also { createLocalEcho(it) } + .let { sendEvent(it) } + } + override fun sendPoll(question: String, options: List): Cancelable { return localEchoEventFactory.createPollEvent(roomId, question, options) .also { createLocalEcho(it) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt index 5cb9687518..8c7f96d1fe 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt @@ -20,6 +20,7 @@ import android.content.Context import android.graphics.Bitmap import android.media.MediaMetadataRetriever import androidx.exifinterface.media.ExifInterface +import okhttp3.internal.format import org.matrix.android.sdk.api.session.content.ContentAttachmentData import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.events.model.Event @@ -88,9 +89,9 @@ internal class LocalEchoEventFactory @Inject constructor( return createMessageEvent(roomId, content) } - private fun createTextContent(text: CharSequence, autoMarkdown: Boolean): TextContent { + private fun createTextContent(text: CharSequence, autoMarkdown: Boolean, forceMarkdownParse: Boolean = false): TextContent { if (autoMarkdown) { - return markdownParser.parse(text) + return markdownParser.parse(text, force = forceMarkdownParse) } else { // Try to detect pills textPillsUtils.processSpecialSpansToHtml(text)?.let { @@ -175,13 +176,17 @@ internal class LocalEchoEventFactory @Inject constructor( val userLink = originalEvent.root.senderId?.let { permalinkFactory.createPermalink(it, false) } ?: "" val body = bodyForReply(originalEvent.getLastMessageContent(), originalEvent.isReply()) + // As we always supply formatted body for replies we should force the MarkdownParser to produce html. + val newBodyFormatted = createTextContent(newBodyText, newBodyAutoMarkdown, forceMarkdownParse = true).takeFormatted() + // Body of the original message may not have formatted version, so may also have to convert to html. + val bodyFormatted = body.formattedText ?: createTextContent(body.text, newBodyAutoMarkdown, forceMarkdownParse = true).takeFormatted() val replyFormatted = REPLY_PATTERN.format( permalink, userLink, originalEvent.senderInfo.disambiguatedDisplayName, // Remove inner mx_reply tags if any - body.takeFormatted().replace(MX_REPLY_REGEX, ""), - createTextContent(newBodyText, newBodyAutoMarkdown).takeFormatted() + bodyFormatted.replace(MX_REPLY_REGEX, ""), + newBodyFormatted ) // // > <@alice:example.org> This is the original body @@ -361,13 +366,18 @@ internal class LocalEchoEventFactory @Inject constructor( val userLink = permalinkFactory.createPermalink(userId, false) ?: return null val body = bodyForReply(eventReplied.getLastMessageContent(), eventReplied.isReply()) + + // As we always supply formatted body for replies we should force the MarkdownParser to produce html. + val replyTextFormatted = createTextContent(replyText, autoMarkdown, forceMarkdownParse = true).takeFormatted() + // Body of the original message may not have formatted version, so may also have to convert to html. + val bodyFormatted = body.formattedText ?: createTextContent(body.text, autoMarkdown, forceMarkdownParse = true).takeFormatted() val replyFormatted = REPLY_PATTERN.format( permalink, userLink, userId, // Remove inner mx_reply tags if any - body.takeFormatted().replace(MX_REPLY_REGEX, ""), - createTextContent(replyText, autoMarkdown).takeFormatted() + bodyFormatted.replace(MX_REPLY_REGEX, ""), + replyTextFormatted ) // // > <@alice:example.org> This is the original body @@ -467,6 +477,38 @@ internal class LocalEchoEventFactory @Inject constructor( localEchoRepository.createLocalEcho(event) } + fun createQuotedTextEvent( + roomId: String, + quotedEvent: TimelineEvent, + text: String, + autoMarkdown: Boolean + ): Event { + val messageContent = quotedEvent.getLastMessageContent() + val textMsg = messageContent?.body + val quoteText = legacyRiotQuoteText(textMsg, text) + return createFormattedTextEvent(roomId, createTextContent(quoteText, autoMarkdown), MessageType.MSGTYPE_TEXT) + } + + private fun legacyRiotQuoteText(quotedText: String?, myText: String): String { + val messageParagraphs = quotedText?.split("\n\n".toRegex())?.dropLastWhile { it.isEmpty() }?.toTypedArray() + return buildString { + if (messageParagraphs != null) { + for (i in messageParagraphs.indices) { + if (messageParagraphs[i].isNotBlank()) { + append("> ") + append(messageParagraphs[i]) + } + + if (i != messageParagraphs.lastIndex) { + append("\n\n") + } + } + } + append("\n\n") + append(myText) + } + } + companion object { // //
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/MarkdownParser.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/MarkdownParser.kt index c99d482300..2df668bf40 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/MarkdownParser.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/MarkdownParser.kt @@ -34,11 +34,17 @@ internal class MarkdownParser @Inject constructor( private val mdSpecialChars = "[`_\\-*>.\\[\\]#~]".toRegex() - fun parse(text: CharSequence): TextContent { + /** + * Parses some input text and produces html. + * @param text An input CharSequence to be parsed. + * @param force Skips the check for detecting if the input contains markdown and always converts to html. + * @return TextContent containing the plain text and the formatted html if generated. + */ + fun parse(text: CharSequence, force: Boolean = false): TextContent { val source = textPillsUtils.processSpecialSpansToMarkdown(text) ?: text.toString() // If no special char are detected, just return plain text - if (source.contains(mdSpecialChars).not()) { + if (!force && source.contains(mdSpecialChars).not()) { return TextContent(source) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewModel.kt index 66d49f9819..55a7eba0de 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewModel.kt @@ -395,23 +395,7 @@ class TextComposerViewModel @AssistedInject constructor( popDraft() } is SendMode.QUOTE -> { - val messageContent = state.sendMode.timelineEvent.getLastMessageContent() - val textMsg = messageContent?.body - - val finalText = legacyRiotQuoteText(textMsg, action.text.toString()) - - // TODO check for pills? - - // TODO Refactor this, just temporary for quotes - val parser = Parser.builder().build() - val document = parser.parse(finalText) - val renderer = HtmlRenderer.builder().build() - val htmlText = renderer.render(document) - if (finalText == htmlText) { - room.sendTextMessage(finalText) - } else { - room.sendFormattedTextMessage(finalText, htmlText) - } + room.sendQuotedTextMessage(state.sendMode.timelineEvent, action.text.toString(), action.autoMarkdown) _viewEvents.post(TextComposerViewEvents.MessageSent) popDraft() } From 0efe4ef16a7507e7e60928bb6e544c78c2fff0b1 Mon Sep 17 00:00:00 2001 From: David Langley Date: Mon, 22 Nov 2021 17:03:47 +0000 Subject: [PATCH 030/632] Create 4540.bugfix --- changelog.d/4540.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/4540.bugfix diff --git a/changelog.d/4540.bugfix b/changelog.d/4540.bugfix new file mode 100644 index 0000000000..9c962f91f7 --- /dev/null +++ b/changelog.d/4540.bugfix @@ -0,0 +1 @@ +Fix message replies/quotes to respect newlines. \ No newline at end of file From c4cf7fa069167716a95d48248c9599a3d4c2f893 Mon Sep 17 00:00:00 2001 From: David Langley Date: Mon, 22 Nov 2021 17:15:15 +0000 Subject: [PATCH 031/632] lint --- .../features/home/room/detail/composer/TextComposerViewModel.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewModel.kt index 55a7eba0de..a56fe72b24 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewModel.kt @@ -36,8 +36,6 @@ import im.vector.app.features.session.coroutineScope import im.vector.app.features.settings.VectorPreferences import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -import org.commonmark.parser.Parser -import org.commonmark.renderer.html.HtmlRenderer import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.EventType From 03961fe9334406d86b843bb834e03c247a8c507a Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 2 Dec 2021 20:42:29 +0100 Subject: [PATCH 032/632] Timeline: update when loading states changed --- .../android/sdk/api/session/room/timeline/Timeline.kt | 2 +- .../home/room/detail/timeline/TimelineEventController.kt | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/Timeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/Timeline.kt index 3ba2aadd38..c8b14b8f57 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/Timeline.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/Timeline.kt @@ -95,7 +95,7 @@ interface Timeline { */ fun onNewTimelineEvents(eventIds: List) - fun onStateUpdated() = Unit + fun onStateUpdated(direction: Direction, state: PaginationState) = Unit } /** diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt index e5c1cd5f73..706e42f715 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt @@ -309,6 +309,14 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec // no-op, already handled } + override fun onStateUpdated(direction: Timeline.Direction, state: Timeline.PaginationState) { + if(!state.hasMoreToLoad) { + backgroundHandler.post { + requestModelBuild() + } + } + } + private fun submitSnapshot(newSnapshot: List) { backgroundHandler.post { inSubmitList = true From 76eddef840c320110cac1dc7613ce494507400c7 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 2 Dec 2021 20:42:54 +0100 Subject: [PATCH 033/632] Timeline: avoid notifying when decryption error is same as previous --- .../sdk/internal/database/model/EventEntity.kt | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) 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 836fc4efaf..ce2d1efc1d 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 @@ -40,8 +40,6 @@ internal open class EventEntity(@Index var eventId: String = "", var unsignedData: String? = null, var redacts: String? = null, var decryptionResultJson: String? = null, - var decryptionErrorCode: String? = null, - var decryptionErrorReason: String? = null, var ageLocalTs: Long? = null ) : RealmObject() { @@ -55,6 +53,16 @@ internal open class EventEntity(@Index var eventId: String = "", sendStateStr = value.name } + var decryptionErrorCode: String? = null + set(value) { + if (value != field) field = value + } + + var decryptionErrorReason: String? = null + set(value) { + if (value != field) field = value + } + companion object fun setDecryptionResult(result: MXEventDecryptionResult, clearEvent: JsonDict? = null) { From 014da84ba6a341e9d5088da737eb21b3a687bd99 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 3 Dec 2021 12:14:35 +0100 Subject: [PATCH 034/632] Timeline: try to optimise a bit the loading --- .../session/room/timeline/DefaultTimeline.kt | 21 ++--- .../room/timeline/LoadTimelineStrategy.kt | 5 +- .../session/room/timeline/TimelineChunk.kt | 92 +++++++++++++------ .../timeline/TimelineEventController.kt | 6 +- 4 files changed, 79 insertions(+), 45 deletions(-) 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 4332255a8b..37c0f6a44b 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 @@ -18,7 +18,6 @@ package org.matrix.android.sdk.internal.session.room.timeline import io.realm.Realm import io.realm.RealmConfiguration -import io.realm.RealmResults import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob @@ -26,13 +25,11 @@ import kotlinx.coroutines.android.asCoroutineDispatcher import kotlinx.coroutines.cancelChildren import kotlinx.coroutines.delay import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext import okhttp3.internal.closeQuietly import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.room.timeline.Timeline import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings -import org.matrix.android.sdk.internal.database.mapper.EventMapper import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask import org.matrix.android.sdk.internal.session.sync.handler.room.ReadReceiptHandler @@ -47,16 +44,16 @@ import java.util.concurrent.atomic.AtomicReference internal class DefaultTimeline internal constructor(private val roomId: String, private val initialEventId: String?, - private val settings: TimelineSettings, private val realmConfiguration: RealmConfiguration, private val loadRoomMembersTask: LoadRoomMembersTask, private val readReceiptHandler: ReadReceiptHandler, + settings: TimelineSettings, paginationTask: PaginationTask, getEventTask: GetContextOfEventTask, fetchTokenAndPaginateTask: FetchTokenAndPaginateTask, timelineEventMapper: TimelineEventMapper, timelineInput: TimelineInput, - private val threadsAwarenessHandler: ThreadsAwarenessHandler, + threadsAwarenessHandler: ThreadsAwarenessHandler, eventDecryptor: TimelineEventDecryptor) : Timeline { companion object { @@ -76,6 +73,7 @@ internal class DefaultTimeline internal constructor(private val roomId: String, private val sequencer = SemaphoreCoroutineSequencer() private val strategyDependencies = LoadTimelineStrategy.Dependencies( + timelineScope = timelineScope, eventDecryptor = eventDecryptor, timelineSettings = settings, paginationTask = paginationTask, @@ -139,6 +137,7 @@ internal class DefaultTimeline internal constructor(private val roomId: String, override fun restartWithEventId(eventId: String?) { timelineScope.launch { openAround(eventId) + postSnapshot() } } @@ -165,7 +164,7 @@ internal class DefaultTimeline internal constructor(private val roomId: String, }.get() } - private suspend fun loadMore(count: Long, direction: Timeline.Direction) = withContext(timelineDispatcher) { + private suspend fun loadMore(count: Long, direction: Timeline.Direction) { val baseLogMessage = "loadMore(count: $count, direction: $direction, roomId: $roomId)" Timber.v("$baseLogMessage started") if (!isStarted.get()) { @@ -174,21 +173,21 @@ internal class DefaultTimeline internal constructor(private val roomId: String, val currentState = getPaginationState(direction) if (!currentState.hasMoreToLoad) { Timber.v("$baseLogMessage : nothing more to load") - return@withContext + return } if (currentState.loading) { Timber.v("$baseLogMessage : already loading") - return@withContext + return } updateState(direction) { it.copy(loading = true) } val loadMoreResult = strategy.loadMore(count, direction) + Timber.v("$baseLogMessage: result $loadMoreResult") val hasMoreToLoad = loadMoreResult != LoadMoreResult.REACHED_END updateState(direction) { it.copy(loading = false, hasMoreToLoad = hasMoreToLoad) } - postSnapshot() } private suspend fun openAround(eventId: String?) = withContext(timelineDispatcher) { @@ -210,12 +209,12 @@ internal class DefaultTimeline internal constructor(private val roomId: String, it.copy(loading = false, hasMoreToLoad = true) } strategy.onStart() - postSnapshot() } private fun postSnapshot() { timelineScope.launch { val snapshot = strategy.buildSnapshot() + Timber.v("Post snapshot of ${snapshot.size} items") withContext(Dispatchers.Main) { listeners.forEach { tryOrNull { it.onTimelineUpdated(snapshot) } @@ -242,7 +241,7 @@ internal class DefaultTimeline internal constructor(private val roomId: String, stateReference.set(newValue) withContext(Dispatchers.Main) { listeners.forEach { - tryOrNull { it.onStateUpdated() } + tryOrNull { it.onStateUpdated(direction, newValue) } } } } 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 d258dabad9..ea1b26dbdf 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 @@ -20,6 +20,7 @@ import io.realm.OrderedCollectionChangeSet import io.realm.OrderedRealmCollectionChangeListener import io.realm.Realm import io.realm.RealmResults +import kotlinx.coroutines.CoroutineScope import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.room.timeline.Timeline @@ -62,6 +63,7 @@ internal class LoadTimelineStrategy( data class Dependencies( val timelineSettings: TimelineSettings, + val timelineScope: CoroutineScope, val realm: AtomicReference, val eventDecryptor: TimelineEventDecryptor, val paginationTask: PaginationTask, @@ -214,7 +216,8 @@ internal class LoadTimelineStrategy( uiEchoManager = uiEchoManager, threadsAwarenessHandler = dependencies.threadsAwarenessHandler, initialEventId = mode.originEventId(), - onBuiltEvents = dependencies.onEventsUpdated + onBuiltEvents = dependencies.onEventsUpdated, + timelineScope = dependencies.timelineScope ) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt index 447c4a3aa0..aa2e62fd3a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt @@ -22,7 +22,8 @@ import io.realm.RealmObjectChangeListener import io.realm.RealmQuery import io.realm.RealmResults import io.realm.Sort -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.room.timeline.Timeline @@ -50,6 +51,7 @@ private const val PAGINATION_COUNT = 50 * It also triggers pagination to the server when needed, or dispatch to the prev or next chunk if any. */ internal class TimelineChunk constructor(private val chunkEntity: ChunkEntity, + private val timelineScope: CoroutineScope, private val timelineSettings: TimelineSettings, private val roomId: String, private val timelineId: String, @@ -65,18 +67,30 @@ internal class TimelineChunk constructor(private val chunkEntity: ChunkEntity, private val isLastForward = AtomicBoolean(chunkEntity.isLastForward) private val chunkObjectListener = RealmObjectChangeListener { _, changeSet -> - if(changeSet?.isDeleted.orFalse()){ + if (changeSet == null) return@RealmObjectChangeListener + if (changeSet.isDeleted.orFalse()) { return@RealmObjectChangeListener } - Timber.v("on chunk (${chunkEntity.identifier()}) changed: ${changeSet?.changedFields?.joinToString(",")}") - if(changeSet?.isFieldChanged(ChunkEntityFields.IS_LAST_FORWARD).orFalse()){ + Timber.v("on chunk (${chunkEntity.identifier()}) changed: ${changeSet.changedFields?.joinToString(",")}") + if (changeSet.isFieldChanged(ChunkEntityFields.IS_LAST_FORWARD)) { isLastForward.set(chunkEntity.isLastForward) } + if (changeSet.isFieldChanged(ChunkEntityFields.NEXT_CHUNK.`$`)) { + nextChunk = createTimelineChunk(chunkEntity.nextChunk) + timelineScope.launch { + nextChunk?.loadMore(PAGINATION_COUNT.toLong(), Timeline.Direction.FORWARDS) + } + } + if (changeSet.isFieldChanged(ChunkEntityFields.PREV_CHUNK.`$`)) { + prevChunk = createTimelineChunk(chunkEntity.prevChunk) + timelineScope.launch { + prevChunk?.loadMore(PAGINATION_COUNT.toLong(), Timeline.Direction.BACKWARDS) + } + } } private val timelineEventCollectionListener = OrderedRealmCollectionChangeListener { results: RealmResults, changeSet: OrderedCollectionChangeSet -> val frozenResults = results.freeze() - Timber.v("on timeline event changed: $changeSet") handleDatabaseChangeSet(frozenResults, changeSet) } @@ -116,18 +130,22 @@ internal class TimelineChunk constructor(private val chunkEntity: ChunkEntity, suspend fun loadMore(count: Long, direction: Timeline.Direction): LoadMoreResult { val loadFromDbCount = loadFromDb(count, direction) + Timber.v("Has loaded $loadFromDbCount items from db") val offsetCount = count - loadFromDbCount // We have built the right amount of data - if (offsetCount == 0L) { - onBuiltEvents() - return LoadMoreResult.SUCCESS + return if (offsetCount == 0L) { + LoadMoreResult.SUCCESS + } else { + delegateLoadMore(offsetCount, direction) } + } + + private suspend fun delegateLoadMore(offsetCount: Long, direction: Timeline.Direction): LoadMoreResult { return if (direction == Timeline.Direction.FORWARDS) { val nextChunkEntity = chunkEntity.nextChunk if (nextChunkEntity == null) { // Fetch next chunk from server if not in the db - val token = chunkEntity.nextToken - fetchFromServer(token, direction) + fetchFromServer(chunkEntity.nextToken, direction) } else { // otherwise we delegate to the next chunk if (nextChunk == null) { @@ -139,8 +157,7 @@ internal class TimelineChunk constructor(private val chunkEntity: ChunkEntity, val prevChunkEntity = chunkEntity.prevChunk if (prevChunkEntity == null) { // Fetch prev chunk from server if not in the db - val token = chunkEntity.prevToken - fetchFromServer(token, direction) + fetchFromServer(chunkEntity.prevToken, direction) } else { // otherwise we delegate to the prev chunk if (prevChunk == null) { @@ -227,6 +244,9 @@ internal class TimelineChunk constructor(private val chunkEntity: ChunkEntity, timelineEventEntities.removeChangeListener(timelineEventCollectionListener) } + /** + * This method tries to read events from the current chunk. + */ private suspend fun loadFromDb(count: Long, direction: Timeline.Direction): Long { val displayIndex = getNextDisplayIndex(direction) ?: return 0 val baseQuery = timelineEventEntities.where() @@ -247,6 +267,7 @@ internal class TimelineChunk constructor(private val chunkEntity: ChunkEntity, builtEvents.add(timelineEvent) } } + onBuiltEvents() return timelineEvents.size.toLong() } @@ -264,7 +285,6 @@ internal class TimelineChunk constructor(private val chunkEntity: ChunkEntity, threadsAwarenessHandler.fetchRootThreadEventsIfNeeded(eventEntityList) } - private fun TimelineEventEntity.buildAndDecryptIfNeeded(): TimelineEvent { val timelineEvent = buildTimelineEvent(this) val transactionId = timelineEvent.root.unsignedData?.transactionId @@ -284,23 +304,11 @@ internal class TimelineChunk constructor(private val chunkEntity: ChunkEntity, (uiEchoManager?.decorateEventWithReactionUiEcho(it) ?: it) } - private fun createTimelineChunk(chunkEntity: ChunkEntity): TimelineChunk { - return TimelineChunk( - chunkEntity = chunkEntity, - timelineSettings = timelineSettings, - timelineId = timelineId, - eventDecryptor = eventDecryptor, - roomId = roomId, - paginationTask = paginationTask, - fetchTokenAndPaginateTask = fetchTokenAndPaginateTask, - timelineEventMapper = timelineEventMapper, - uiEchoManager = uiEchoManager, - threadsAwarenessHandler = threadsAwarenessHandler, - initialEventId = null, - onBuiltEvents = onBuiltEvents - ) - } - + /** + * Will try to fetch a new chunk on the home server. + * It will take care to update the database by inserting new events and linking new chunk + * with this one. + */ private suspend fun fetchFromServer(token: String?, direction: Timeline.Direction): LoadMoreResult { val paginationResult = try { if (token == null) { @@ -309,6 +317,7 @@ internal class TimelineChunk constructor(private val chunkEntity: ChunkEntity, val taskParams = FetchTokenAndPaginateTask.Params(roomId, lastKnownEventId, direction.toPaginationDirection(), PAGINATION_COUNT) fetchTokenAndPaginateTask.execute(taskParams) } else { + Timber.v("Fetch more events on server") val taskParams = PaginationTask.Params(roomId, token, direction.toPaginationDirection(), PAGINATION_COUNT) paginationTask.execute(taskParams) } @@ -337,6 +346,10 @@ internal class TimelineChunk constructor(private val chunkEntity: ChunkEntity, return offset } + /** + * This method is responsible for managing insertions and updates of events on this chunk. + * + */ private fun handleDatabaseChangeSet(frozenResults: RealmResults, changeSet: OrderedCollectionChangeSet) { val insertions = changeSet.insertionRanges for (range in insertions) { @@ -385,6 +398,25 @@ internal class TimelineChunk constructor(private val chunkEntity: ChunkEntity, builtEvents.last().displayIndex - 1 } } + + private fun createTimelineChunk(chunkEntity: ChunkEntity?): TimelineChunk? { + if (chunkEntity == null) return null + return TimelineChunk( + chunkEntity = chunkEntity, + timelineScope = timelineScope, + timelineSettings = timelineSettings, + timelineId = timelineId, + eventDecryptor = eventDecryptor, + roomId = roomId, + paginationTask = paginationTask, + fetchTokenAndPaginateTask = fetchTokenAndPaginateTask, + timelineEventMapper = timelineEventMapper, + uiEchoManager = uiEchoManager, + threadsAwarenessHandler = threadsAwarenessHandler, + initialEventId = null, + onBuiltEvents = this.onBuiltEvents + ) + } } private fun RealmQuery.offsets( diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt index 706e42f715..78463fd0c1 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt @@ -310,9 +310,9 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec } override fun onStateUpdated(direction: Timeline.Direction, state: Timeline.PaginationState) { - if(!state.hasMoreToLoad) { + if (!state.hasMoreToLoad) { backgroundHandler.post { - requestModelBuild() + requestDelayedModelBuild(0) } } } @@ -324,7 +324,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec currentSnapshot = newSnapshot val diffResult = DiffUtil.calculateDiff(diffCallback) diffResult.dispatchUpdatesTo(listUpdateCallback) - requestModelBuild() + requestDelayedModelBuild(0) inSubmitList = false } } From 7fa4bf182ab07b2fd34c6db7881a5a999f231fdf Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 3 Dec 2021 12:15:04 +0100 Subject: [PATCH 035/632] Timeline: get off main thread when waiting for positionOrReadMarker --- .../home/room/detail/RoomDetailFragment.kt | 37 ++++++++++--------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index 08a2e6cd9c..dddd8ce03f 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -184,11 +184,13 @@ import im.vector.app.features.widgets.WidgetActivity import im.vector.app.features.widgets.WidgetArgs import im.vector.app.features.widgets.WidgetKind import im.vector.app.features.widgets.permissions.RoomWidgetPermissionBottomSheet +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import kotlinx.parcelize.Parcelize import nl.dionsegijn.konfetti.models.Shape import nl.dionsegijn.konfetti.models.Size @@ -1298,27 +1300,28 @@ class RoomDetailFragment @Inject constructor( private fun updateJumpToReadMarkerViewVisibility() { viewLifecycleOwner.lifecycleScope.launchWhenResumed { - withState(roomDetailViewModel) { - val showJumpToUnreadBanner = when (it.unreadState) { - UnreadState.Unknown, - UnreadState.HasNoUnread -> false - is UnreadState.ReadMarkerNotLoaded -> true - is UnreadState.HasUnread -> { - if (it.canShowJumpToReadMarker) { - val lastVisibleItem = layoutManager.findLastCompletelyVisibleItemPosition() - val positionOfReadMarker = timelineEventController.getPositionOfReadMarker() - if (positionOfReadMarker == null) { - false - } else { - positionOfReadMarker > lastVisibleItem - } - } else { - false + val state = roomDetailViewModel.awaitState() + val showJumpToUnreadBanner = when (state.unreadState) { + UnreadState.Unknown, + UnreadState.HasNoUnread -> false + is UnreadState.ReadMarkerNotLoaded -> true + is UnreadState.HasUnread -> { + if (state.canShowJumpToReadMarker) { + val lastVisibleItem = layoutManager.findLastCompletelyVisibleItemPosition() + val positionOfReadMarker = withContext(Dispatchers.Default) { + timelineEventController.getPositionOfReadMarker() } + if (positionOfReadMarker == null) { + false + } else { + positionOfReadMarker > lastVisibleItem + } + } else { + false } } - views.jumpToReadMarkerView.isVisible = showJumpToUnreadBanner } + views.jumpToReadMarkerView.isVisible = showJumpToUnreadBanner } } From 3217277bc45a5da6964d75a42a7cadfe663821cc Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 3 Dec 2021 18:39:04 +0100 Subject: [PATCH 036/632] Timeline: check for create event to hide loader --- .../sdk/api/session/room/timeline/Timeline.kt | 9 +++++--- .../timeline/TimelineEventController.kt | 23 +++++-------------- 2 files changed, 12 insertions(+), 20 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/Timeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/Timeline.kt index c8b14b8f57..548c639231 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/Timeline.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/Timeline.kt @@ -83,18 +83,21 @@ interface Timeline { * The latest event is the first in the list * @param snapshot the most up to date snapshot */ - fun onTimelineUpdated(snapshot: List) + fun onTimelineUpdated(snapshot: List) = Unit /** * Called whenever an error we can't recover from occurred */ - fun onTimelineFailure(throwable: Throwable) + fun onTimelineFailure(throwable: Throwable) = Unit /** * Called when new events come through the sync */ - fun onNewTimelineEvents(eventIds: List) + fun onNewTimelineEvents(eventIds: List) = Unit + /** + * Called when the pagination state has changed in one direction + */ fun onStateUpdated(direction: Direction, state: PaginationState) = Unit } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt index 78463fd0c1..ac6de2ebb0 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt @@ -174,6 +174,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec private var inSubmitList: Boolean = false private var hasReachedInvite: Boolean = false private var hasUTD: Boolean = false + private var hasReachedCreateEvent: Boolean = false private var positionOfReadMarker: Int? = null private var partialState: PartialState = PartialState() @@ -286,7 +287,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec return } // Avoid displaying two loaders if there is no elements between them - val showBackwardsLoader = !showingForwardLoader || timelineModels.isNotEmpty() + val showBackwardsLoader = (!showingForwardLoader || timelineModels.isNotEmpty()) && !hasReachedCreateEvent // We can hide the loader but still add the item to controller so it can trigger backwards pagination LoadingItem_() .id("backward_loading_item_$timestamp") @@ -301,22 +302,6 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec submitSnapshot(snapshot) } - override fun onTimelineFailure(throwable: Throwable) { - // no-op, already handled - } - - override fun onNewTimelineEvents(eventIds: List) { - // no-op, already handled - } - - override fun onStateUpdated(direction: Timeline.Direction, state: Timeline.PaginationState) { - if (!state.hasMoreToLoad) { - backgroundHandler.post { - requestDelayedModelBuild(0) - } - } - } - private fun submitSnapshot(newSnapshot: List) { backgroundHandler.post { inSubmitList = true @@ -471,10 +456,14 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec private fun preprocessReverseEvents() { receiptsByEvent.clear() timelineEventsGroups.clear() + hasReachedCreateEvent = false val itr = currentSnapshot.listIterator(currentSnapshot.size) var lastShownEventId: String? = null while (itr.hasPrevious()) { val event = itr.previous() + if (!hasReachedCreateEvent && event.root.type == EventType.STATE_ROOM_CREATE) { + hasReachedCreateEvent = true + } timelineEventsGroups.addOrIgnore(event) val currentReadReceipts = ArrayList(event.readReceipts).filter { it.user.userId != session.myUserId From 29a4fd1e419e3d6b3fab63f168388271a3a1b258 Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 7 Dec 2021 21:13:41 +0100 Subject: [PATCH 037/632] Timeline: make 3 integration tests passing (also add some suspend method on the timeline) --- .../android/sdk/common/CommonTestHelper.kt | 69 +++---- .../TimelineBackToPreviousLastForwardTest.kt | 183 ------------------ .../timeline/TimelineForwardPaginationTest.kt | 55 ++---- .../TimelinePreviousLastForwardTest.kt | 4 +- .../TimelineSimpleBackPaginationTest.kt | 105 ++++++++++ .../sdk/session/room/timeline/TimelineTest.kt | 84 -------- .../sdk/api/session/room/timeline/Timeline.kt | 15 ++ .../session/room/timeline/DefaultTimeline.kt | 60 ++++-- .../room/timeline/LoadTimelineStrategy.kt | 36 ++-- .../session/room/timeline/TimelineChunk.kt | 135 ++++++++----- .../timeline/TimelineEventController.kt | 11 +- 11 files changed, 331 insertions(+), 426 deletions(-) delete mode 100644 matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineBackToPreviousLastForwardTest.kt create mode 100644 matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineSimpleBackPaginationTest.kt delete mode 100644 matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineTest.kt diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt index 8e21828562..3cb699378f 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt @@ -145,36 +145,9 @@ class CommonTestHelper(context: Context) { * @param nbOfMessages the number of time the message will be sent */ fun sendTextMessage(room: Room, message: String, nbOfMessages: Int, timeout: Long = TestConstants.timeOutMillis): List { - val sentEvents = ArrayList(nbOfMessages) val timeline = room.createTimeline(null, TimelineSettings(10)) timeline.start() - waitWithLatch(timeout + 1_000L * nbOfMessages) { latch -> - val timelineListener = object : Timeline.Listener { - override fun onTimelineFailure(throwable: Throwable) { - } - - override fun onNewTimelineEvents(eventIds: List) { - // noop - } - - override fun onTimelineUpdated(snapshot: List) { - val newMessages = snapshot - .filter { it.root.sendState == SendState.SYNCED } - .filter { it.root.getClearType() == EventType.MESSAGE } - .filter { it.root.getClearContent().toModel()?.body?.startsWith(message) == true } - - Timber.v("New synced message size: ${newMessages.size}") - if (newMessages.size == nbOfMessages) { - sentEvents.addAll(newMessages) - // Remove listener now, if not at the next update sendEvents could change - timeline.removeListener(this) - latch.countDown() - } - } - } - timeline.addListener(timelineListener) - sendTextMessagesBatched(room, message, nbOfMessages) - } + val sentEvents = sendTextMessagesBatched(timeline, room, message, nbOfMessages, timeout) timeline.dispose() // Check that all events has been created assertEquals("Message number do not match $sentEvents", nbOfMessages.toLong(), sentEvents.size.toLong()) @@ -182,9 +155,10 @@ class CommonTestHelper(context: Context) { } /** - * Will send nb of messages provided by count parameter but waits a bit every 10 messages to avoid gap in sync + * Will send nb of messages provided by count parameter but waits every 10 messages to avoid gap in sync */ - private fun sendTextMessagesBatched(room: Room, message: String, count: Int) { + private fun sendTextMessagesBatched(timeline: Timeline, room: Room, message: String, count: Int, timeout: Long): List { + val sentEvents = ArrayList(count) (1 until count + 1) .map { "$message #$it" } .chunked(10) @@ -192,8 +166,34 @@ class CommonTestHelper(context: Context) { batchedMessages.forEach { formattedMessage -> room.sendTextMessage(formattedMessage) } - Thread.sleep(1_000L) + waitWithLatch(timeout) { latch -> + val timelineListener = object : Timeline.Listener { + + override fun onTimelineUpdated(snapshot: List) { + val allSentMessages = snapshot + .filter { it.root.sendState == SendState.SYNCED } + .filter { it.root.getClearType() == EventType.MESSAGE } + .filter { it.root.getClearContent().toModel()?.body?.startsWith(message) == true } + + val hasSyncedAllBatchedMessages = allSentMessages + .map { + it.root.getClearContent().toModel()?.body + } + .containsAll(batchedMessages) + + if (allSentMessages.size == count) { + sentEvents.addAll(allSentMessages) + } + if (hasSyncedAllBatchedMessages) { + timeline.removeListener(this) + latch.countDown() + } + } + } + timeline.addListener(timelineListener) + } } + return sentEvents } // PRIVATE METHODS ***************************************************************************** @@ -332,13 +332,6 @@ class CommonTestHelper(context: Context) { fun createEventListener(latch: CountDownLatch, predicate: (List) -> Boolean): Timeline.Listener { return object : Timeline.Listener { - override fun onTimelineFailure(throwable: Throwable) { - // noop - } - - override fun onNewTimelineEvents(eventIds: List) { - // noop - } override fun onTimelineUpdated(snapshot: List) { if (predicate(snapshot)) { diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineBackToPreviousLastForwardTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineBackToPreviousLastForwardTest.kt deleted file mode 100644 index 7628f287c9..0000000000 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineBackToPreviousLastForwardTest.kt +++ /dev/null @@ -1,183 +0,0 @@ -/* - * 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.session.room.timeline - -import org.amshove.kluent.shouldBeFalse -import org.amshove.kluent.shouldBeTrue -import org.junit.Assert.assertTrue -import org.junit.FixMethodOrder -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.JUnit4 -import org.junit.runners.MethodSorters -import org.matrix.android.sdk.InstrumentedTest -import org.matrix.android.sdk.api.extensions.orFalse -import org.matrix.android.sdk.api.session.events.model.EventType -import org.matrix.android.sdk.api.session.events.model.toModel -import org.matrix.android.sdk.api.session.room.model.message.MessageContent -import org.matrix.android.sdk.api.session.room.timeline.Timeline -import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings -import org.matrix.android.sdk.common.CommonTestHelper -import org.matrix.android.sdk.common.CryptoTestHelper -import org.matrix.android.sdk.common.checkSendOrder -import timber.log.Timber -import java.util.concurrent.CountDownLatch - -@RunWith(JUnit4::class) -@FixMethodOrder(MethodSorters.JVM) -class TimelineBackToPreviousLastForwardTest : InstrumentedTest { - - private val commonTestHelper = CommonTestHelper(context()) - private val cryptoTestHelper = CryptoTestHelper(commonTestHelper) - - /** - * This test ensure that if we have a chunk in the timeline which is due to a sync, and we click to permalink of an - * even contained in a previous lastForward chunk, we will be able to go back to the live - */ - @Test - fun backToPreviousLastForwardTest() { - val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(false) - - val aliceSession = cryptoTestData.firstSession - val bobSession = cryptoTestData.secondSession!! - val aliceRoomId = cryptoTestData.roomId - - aliceSession.cryptoService().setWarnOnUnknownDevices(false) - bobSession.cryptoService().setWarnOnUnknownDevices(false) - - val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!! - val roomFromBobPOV = bobSession.getRoom(aliceRoomId)!! - - val bobTimeline = roomFromBobPOV.createTimeline(null, TimelineSettings(30)) - bobTimeline.start() - - var roomCreationEventId: String? = null - - run { - val lock = CountDownLatch(1) - val eventsListener = commonTestHelper.createEventListener(lock) { snapshot -> - Timber.e("Bob timeline updated: with ${snapshot.size} events:") - snapshot.forEach { - Timber.w(" event ${it.root}") - } - - roomCreationEventId = snapshot.lastOrNull()?.root?.eventId - // Ok, we have the 8 first messages of the initial sync (room creation and bob join event) - snapshot.size == 8 - } - - bobTimeline.addListener(eventsListener) - commonTestHelper.await(lock) - bobTimeline.removeAllListeners() - - bobTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeFalse() - bobTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeFalse() - } - - // Bob stop to sync - bobSession.stopSync() - - val messageRoot = "First messages from Alice" - - // Alice sends 30 messages - commonTestHelper.sendTextMessage( - roomFromAlicePOV, - messageRoot, - 30) - - // Bob start to sync - bobSession.startSync(true) - - run { - val lock = CountDownLatch(1) - val eventsListener = commonTestHelper.createEventListener(lock) { snapshot -> - Timber.e("Bob timeline updated: with ${snapshot.size} events:") - snapshot.forEach { - Timber.w(" event ${it.root}") - } - - // Ok, we have the 10 last messages from Alice. - snapshot.size == 10 && - snapshot.all { it.root.content.toModel()?.body?.startsWith(messageRoot).orFalse() } - } - - bobTimeline.addListener(eventsListener) - commonTestHelper.await(lock) - bobTimeline.removeAllListeners() - - bobTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeTrue() - bobTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeFalse() - } - - // Bob navigate to the first event (room creation event), so inside the previous last forward chunk - run { - val lock = CountDownLatch(1) - val eventsListener = commonTestHelper.createEventListener(lock) { snapshot -> - Timber.e("Bob timeline updated: with ${snapshot.size} events:") - snapshot.forEach { - Timber.w(" event ${it.root}") - } - - // The event is in db, so it is fetch and auto pagination occurs, half of the number of events we have for this chunk (?) - snapshot.size == 4 - } - - bobTimeline.addListener(eventsListener) - - // Restart the timeline to the first sent event, which is already in the database, so pagination should start automatically - assertTrue(roomFromBobPOV.getTimeLineEvent(roomCreationEventId!!) != null) - - bobTimeline.restartWithEventId(roomCreationEventId) - - commonTestHelper.await(lock) - bobTimeline.removeAllListeners() - - bobTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeTrue() - bobTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeFalse() - } - - // Bob scroll to the future - run { - val lock = CountDownLatch(1) - val eventsListener = commonTestHelper.createEventListener(lock) { snapshot -> - Timber.e("Bob timeline updated: with ${snapshot.size} events:") - snapshot.forEach { - Timber.w(" event ${it.root}") - } - - // Bob can see the first event of the room (so Back pagination has worked) - snapshot.lastOrNull()?.root?.getClearType() == EventType.STATE_ROOM_CREATE && - // 8 for room creation item, and 30 for the forward pagination - snapshot.size == 38 && - snapshot.checkSendOrder(messageRoot, 30, 0) - } - - bobTimeline.addListener(eventsListener) - - bobTimeline.paginate(Timeline.Direction.FORWARDS, 50) - - commonTestHelper.await(lock) - bobTimeline.removeAllListeners() - - bobTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeFalse() - bobTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeFalse() - } - bobTimeline.dispose() - - cryptoTestData.cleanUp(commonTestHelper) - } -} diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineForwardPaginationTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineForwardPaginationTest.kt index bc9722c922..05a43de0ac 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineForwardPaginationTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineForwardPaginationTest.kt @@ -16,6 +16,8 @@ package org.matrix.android.sdk.session.room.timeline +import kotlinx.coroutines.runBlocking +import org.amshove.kluent.internal.assertEquals import org.amshove.kluent.shouldBeFalse import org.amshove.kluent.shouldBeTrue import org.junit.FixMethodOrder @@ -123,54 +125,29 @@ class TimelineForwardPaginationTest : InstrumentedTest { // Alice paginates BACKWARD and FORWARD of 50 events each // Then she can only navigate FORWARD run { - val lock = CountDownLatch(1) - val aliceEventsListener = commonTestHelper.createEventListener(lock) { snapshot -> - Timber.e("Alice timeline updated: with ${snapshot.size} events:") - snapshot.forEach { - Timber.w(" event ${it.root.content}") - } - - // Alice can see the first event of the room (so Back pagination has worked) - snapshot.lastOrNull()?.root?.getClearType() == EventType.STATE_ROOM_CREATE && - // 6 for room creation item (backward pagination), 1 for the context, and 50 for the forward pagination - snapshot.size == 57 // 6 + 1 + 50 + val snapshot = runBlocking { + aliceTimeline.awaitPaginate(Timeline.Direction.BACKWARDS, 50) + aliceTimeline.awaitPaginate(Timeline.Direction.FORWARDS, 50) } - - aliceTimeline.addListener(aliceEventsListener) - - // Restart the timeline to the first sent event - // We ask to load event backward and forward - aliceTimeline.paginate(Timeline.Direction.BACKWARDS, 50) - aliceTimeline.paginate(Timeline.Direction.FORWARDS, 50) - - commonTestHelper.await(lock) - aliceTimeline.removeAllListeners() - aliceTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeTrue() aliceTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeFalse() + + assertEquals(EventType.STATE_ROOM_CREATE, snapshot.lastOrNull()?.root?.getClearType()) + // 6 for room creation item (backward pagination), 1 for the context, and 50 for the forward pagination + // 6 + 1 + 50 + assertEquals(57, snapshot.size) } // Alice paginates once again FORWARD for 50 events // All the timeline is retrieved, she cannot paginate anymore in both direction run { - val lock = CountDownLatch(1) - val aliceEventsListener = commonTestHelper.createEventListener(lock) { snapshot -> - Timber.e("Alice timeline updated: with ${snapshot.size} events:") - snapshot.forEach { - Timber.w(" event ${it.root.content}") - } - // 6 for room creation item (backward pagination),and numberOfMessagesToSend (all the message of the room) - snapshot.size == 6 + numberOfMessagesToSend && - snapshot.checkSendOrder(message, numberOfMessagesToSend, 0) - } - - aliceTimeline.addListener(aliceEventsListener) - // Ask for a forward pagination - aliceTimeline.paginate(Timeline.Direction.FORWARDS, 50) - - commonTestHelper.await(lock) - aliceTimeline.removeAllListeners() + val snapshot = runBlocking { + aliceTimeline.awaitPaginate(Timeline.Direction.FORWARDS, 50) + } + // 6 for room creation item (backward pagination),and numberOfMessagesToSend (all the message of the room) + snapshot.size == 6 + numberOfMessagesToSend && + snapshot.checkSendOrder(message, numberOfMessagesToSend, 0) // The timeline is fully loaded aliceTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeFalse() diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelinePreviousLastForwardTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelinePreviousLastForwardTest.kt index e865fe17da..c6fdec150d 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelinePreviousLastForwardTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelinePreviousLastForwardTest.kt @@ -168,10 +168,8 @@ class TimelinePreviousLastForwardTest : InstrumentedTest { bobTimeline.addListener(eventsListener) - // Restart the timeline to the first sent event, and paginate in both direction + // Restart the timeline to the first sent event bobTimeline.restartWithEventId(firstMessageFromAliceId) - bobTimeline.paginate(Timeline.Direction.BACKWARDS, 50) - bobTimeline.paginate(Timeline.Direction.FORWARDS, 50) commonTestHelper.await(lock) bobTimeline.removeAllListeners() diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineSimpleBackPaginationTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineSimpleBackPaginationTest.kt new file mode 100644 index 0000000000..bec6886fb1 --- /dev/null +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineSimpleBackPaginationTest.kt @@ -0,0 +1,105 @@ +/* + * 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.session.room.timeline + +import kotlinx.coroutines.runBlocking +import org.amshove.kluent.internal.assertEquals +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.junit.runners.MethodSorters +import org.matrix.android.sdk.InstrumentedTest +import org.matrix.android.sdk.api.extensions.orFalse +import org.matrix.android.sdk.api.session.events.model.isTextMessage +import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent +import org.matrix.android.sdk.api.session.room.timeline.Timeline +import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings +import org.matrix.android.sdk.common.CommonTestHelper +import org.matrix.android.sdk.common.CryptoTestHelper +import org.matrix.android.sdk.common.TestConstants + +@RunWith(JUnit4::class) +@FixMethodOrder(MethodSorters.JVM) +class TimelineSimpleBackPaginationTest : InstrumentedTest { + + private val commonTestHelper = CommonTestHelper(context()) + private val cryptoTestHelper = CryptoTestHelper(commonTestHelper) + + @Test + fun timeline_backPaginate_shouldReachEndOfTimeline() { + + val numberOfMessagesToSent = 200 + + val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(false) + + val aliceSession = cryptoTestData.firstSession + val bobSession = cryptoTestData.secondSession!! + val roomId = cryptoTestData.roomId + + aliceSession.cryptoService().setWarnOnUnknownDevices(false) + bobSession.cryptoService().setWarnOnUnknownDevices(false) + + val roomFromAlicePOV = aliceSession.getRoom(roomId)!! + val roomFromBobPOV = bobSession.getRoom(roomId)!! + + // Alice sends X messages + val message = "Message from Alice" + commonTestHelper.sendTextMessage( + roomFromAlicePOV, + message, + numberOfMessagesToSent) + + val bobTimeline = roomFromBobPOV.createTimeline(null, TimelineSettings(30)) + bobTimeline.start() + + commonTestHelper.waitWithLatch(timeout = TestConstants.timeOutMillis * 10) { + + val listener = object : Timeline.Listener { + + override fun onStateUpdated(direction: Timeline.Direction, state: Timeline.PaginationState) { + if (direction == Timeline.Direction.FORWARDS) + return + if (state.hasMoreToLoad && !state.loading) { + bobTimeline.paginate(Timeline.Direction.BACKWARDS, 30) + } else if (!state.hasMoreToLoad) { + bobTimeline.removeListener(this) + it.countDown() + } + } + } + bobTimeline.addListener(listener) + bobTimeline.paginate(Timeline.Direction.BACKWARDS, 30) + } + assertEquals(false, bobTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS)) + assertEquals(false, bobTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS)) + + val onlySentEvents = runBlocking { + bobTimeline.awaitSnapshot() + } + .filter { + it.root.isTextMessage() + }.filter { + (it.root.content.toModel())?.body?.startsWith(message).orFalse() + } + assertEquals(numberOfMessagesToSent, onlySentEvents.size) + + bobTimeline.dispose() + cryptoTestData.cleanUp(commonTestHelper) + } +} diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineTest.kt deleted file mode 100644 index 9be0a5d5af..0000000000 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineTest.kt +++ /dev/null @@ -1,84 +0,0 @@ -/* - * 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.session.room.timeline - -import com.zhuinden.monarchy.Monarchy -import org.matrix.android.sdk.InstrumentedTest - -internal class TimelineTest : InstrumentedTest { - - companion object { - private const val ROOM_ID = "roomId" - } - - private lateinit var monarchy: Monarchy - -// @Before -// fun setup() { -// Timber.plant(Timber.DebugTree()) -// Realm.init(context()) -// val testConfiguration = RealmConfiguration.Builder().name("test-realm") -// .modules(SessionRealmModule()).build() -// -// Realm.deleteRealm(testConfiguration) -// monarchy = Monarchy.Builder().setRealmConfiguration(testConfiguration).build() -// RoomDataHelper.fakeInitialSync(monarchy, ROOM_ID) -// } -// -// private fun createTimeline(initialEventId: String? = null): Timeline { -// val taskExecutor = TaskExecutor(testCoroutineDispatchers) -// val tokenChunkEventPersistor = TokenChunkEventPersistor(monarchy) -// val paginationTask = FakePaginationTask @Inject constructor(tokenChunkEventPersistor) -// val getContextOfEventTask = FakeGetContextOfEventTask @Inject constructor(tokenChunkEventPersistor) -// val roomMemberExtractor = SenderRoomMemberExtractor(ROOM_ID) -// val timelineEventFactory = TimelineEventFactory(roomMemberExtractor, EventRelationExtractor()) -// return DefaultTimeline( -// ROOM_ID, -// initialEventId, -// monarchy.realmConfiguration, -// taskExecutor, -// getContextOfEventTask, -// timelineEventFactory, -// paginationTask, -// null) -// } -// -// @Test -// fun backPaginate_shouldLoadMoreEvents_whenPaginateIsCalled() { -// val timeline = createTimeline() -// timeline.start() -// val paginationCount = 30 -// var initialLoad = 0 -// val latch = CountDownLatch(2) -// var timelineEvents: List = emptyList() -// timeline.listener = object : Timeline.Listener { -// override fun onTimelineUpdated(snapshot: List) { -// if (snapshot.isNotEmpty()) { -// if (initialLoad == 0) { -// initialLoad = snapshot.size -// } -// timelineEvents = snapshot -// latch.countDown() -// timeline.paginate(Timeline.Direction.BACKWARDS, paginationCount) -// } -// } -// } -// latch.await() -// timelineEvents.size shouldBeEqualTo initialLoad + paginationCount -// timeline.dispose() -// } -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/Timeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/Timeline.kt index 548c639231..443b1a8f13 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/Timeline.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/Timeline.kt @@ -70,13 +70,28 @@ interface Timeline { */ fun paginate(direction: Direction, count: Int) + /** + * This is the same than the regular paginate method but waits for the results instead + * of relying on the timeline listener. + * Note that it will still trigger onTimelineUpdated internally. + */ + suspend fun awaitPaginate(direction: Direction, count: Int): List + /** * Returns the index of a built event or null. */ fun getIndexOfEvent(eventId: String?): Int? + /** + * Returns the current pagination state for the direction. + */ fun getPaginationState(direction: Direction): PaginationState + /** + * Returns a snapshot of the timeline in his current state. + */ + suspend fun awaitSnapshot(): List + interface Listener { /** * Call when the timeline has been updated through pagination or sync. 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 37c0f6a44b..5e9efecc67 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 @@ -29,6 +29,7 @@ import kotlinx.coroutines.withContext import okhttp3.internal.closeQuietly import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.room.timeline.Timeline +import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask @@ -47,7 +48,7 @@ internal class DefaultTimeline internal constructor(private val roomId: String, private val realmConfiguration: RealmConfiguration, private val loadRoomMembersTask: LoadRoomMembersTask, private val readReceiptHandler: ReadReceiptHandler, - settings: TimelineSettings, + private val settings: TimelineSettings, paginationTask: PaginationTask, getEventTask: GetContextOfEventTask, fetchTokenAndPaginateTask: FetchTokenAndPaginateTask, @@ -83,6 +84,7 @@ internal class DefaultTimeline internal constructor(private val roomId: String, realm = backgroundRealm, getContextOfEventTask = getEventTask, threadsAwarenessHandler = threadsAwarenessHandler, + onLimitedTimeline = this::onLimitedTimeline, onEventsUpdated = this::postSnapshot, onNewTimelineEvents = this::onNewTimelineEvents ) @@ -108,7 +110,7 @@ internal class DefaultTimeline internal constructor(private val roomId: String, override fun start() { timelineScope.launch { - loadRoomMemberIfNeeded() + loadRoomMembersIfNeeded() } timelineScope.launch { sequencer.post { @@ -147,10 +149,21 @@ internal class DefaultTimeline internal constructor(private val roomId: String, override fun paginate(direction: Timeline.Direction, count: Int) { timelineScope.launch { - loadMore(count.toLong(), direction) + loadMore(count, direction, fetchOnServerIfNeeded = true) } } + override suspend fun awaitPaginate(direction: Timeline.Direction, count: Int): List { + withContext(timelineDispatcher) { + loadMore(count, direction, fetchOnServerIfNeeded = true) + } + return awaitSnapshot() + } + + override suspend fun awaitSnapshot(): List = withContext(timelineDispatcher) { + strategy.buildSnapshot() + } + override fun getIndexOfEvent(eventId: String?): Int? { if (eventId == null) return null return strategy.getBuiltEventIndex(eventId) @@ -164,8 +177,8 @@ internal class DefaultTimeline internal constructor(private val roomId: String, }.get() } - private suspend fun loadMore(count: Long, direction: Timeline.Direction) { - val baseLogMessage = "loadMore(count: $count, direction: $direction, roomId: $roomId)" + private suspend fun loadMore(count: Int, direction: Timeline.Direction, fetchOnServerIfNeeded: Boolean) { + val baseLogMessage = "loadMore(count: $count, direction: $direction, roomId: $roomId, fetchOnServer: $fetchOnServerIfNeeded)" Timber.v("$baseLogMessage started") if (!isStarted.get()) { throw IllegalStateException("You should call start before using timeline") @@ -182,7 +195,7 @@ internal class DefaultTimeline internal constructor(private val roomId: String, updateState(direction) { it.copy(loading = true) } - val loadMoreResult = strategy.loadMore(count, direction) + val loadMoreResult = strategy.loadMore(count, direction, fetchOnServerIfNeeded) Timber.v("$baseLogMessage: result $loadMoreResult") val hasMoreToLoad = loadMoreResult != LoadMoreResult.REACHED_END updateState(direction) { @@ -202,13 +215,29 @@ internal class DefaultTimeline internal constructor(private val roomId: String, } else { buildStrategy(LoadTimelineStrategy.Mode.Permalink(eventId)) } + initPaginationStates(eventId) + strategy.onStart() + loadMore( + count = strategyDependencies.timelineSettings.initialSize, + direction = Timeline.Direction.BACKWARDS, + fetchOnServerIfNeeded = false + ) + } + + private suspend fun initPaginationStates(eventId: String?) { updateState(Timeline.Direction.FORWARDS) { it.copy(loading = false, hasMoreToLoad = eventId != null) } updateState(Timeline.Direction.BACKWARDS) { it.copy(loading = false, hasMoreToLoad = true) } - strategy.onStart() + } + + private fun onLimitedTimeline() { + timelineScope.launch { + initPaginationStates(null) + loadMore(settings.initialSize, Timeline.Direction.BACKWARDS, false) + } } private fun postSnapshot() { @@ -239,10 +268,15 @@ internal class DefaultTimeline internal constructor(private val roomId: String, val currentValue = stateReference.get() val newValue = update(currentValue) stateReference.set(newValue) - withContext(Dispatchers.Main) { - listeners.forEach { - tryOrNull { it.onStateUpdated(direction, newValue) } - } + if (newValue != currentValue) { + postPaginationState(direction, newValue) + } + } + + private suspend fun postPaginationState(direction: Timeline.Direction, state: Timeline.PaginationState) = withContext(Dispatchers.Main) { + Timber.v("Post $direction pagination state: $state ") + listeners.forEach { + tryOrNull { it.onStateUpdated(direction, state) } } } @@ -255,14 +289,14 @@ internal class DefaultTimeline internal constructor(private val roomId: String, ) } - private suspend fun loadRoomMemberIfNeeded() { + private suspend fun loadRoomMembersIfNeeded() { val loadRoomMembersParam = LoadRoomMembersTask.Params(roomId) try { loadRoomMembersTask.execute(loadRoomMembersParam) } catch (failure: Throwable) { Timber.v("Failed to load room members. Retry in 10s.") delay(10_000L) - loadRoomMemberIfNeeded() + loadRoomMembersIfNeeded() } } 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 ea1b26dbdf..fb0971ad47 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 @@ -20,7 +20,9 @@ import io.realm.OrderedCollectionChangeSet import io.realm.OrderedRealmCollectionChangeListener import io.realm.Realm import io.realm.RealmResults +import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.room.timeline.Timeline @@ -73,18 +75,27 @@ internal class LoadTimelineStrategy( val timelineEventMapper: TimelineEventMapper, val threadsAwarenessHandler: ThreadsAwarenessHandler, val onEventsUpdated: () -> Unit, + val onLimitedTimeline: () -> Unit, val onNewTimelineEvents: (List) -> Unit ) + private var getContextLatch: CompletableDeferred? = null private var chunkEntity: RealmResults? = null private var timelineChunk: TimelineChunk? = null private val chunkEntityListener = OrderedRealmCollectionChangeListener { _: RealmResults, changeSet: OrderedCollectionChangeSet -> + // Can be call either when you open a permalink on an unknown event + // or when there is a gap in the timeline. val shouldRebuildChunk = changeSet.insertions.isNotEmpty() if (shouldRebuildChunk) { timelineChunk?.close(closeNext = true, closePrev = true) timelineChunk = chunkEntity?.createTimelineChunk() - dependencies.onEventsUpdated() + // If we are waiting for a result of get context, post completion + getContextLatch?.complete(Unit) + // If we have a gap, just tell the timeline about it. + if (timelineChunk?.hasReachedLastForward().orFalse()) { + dependencies.onLimitedTimeline() + } } } @@ -95,6 +106,7 @@ internal class LoadTimelineStrategy( } private val timelineInputListener = object : TimelineInput.Listener { + override fun onLocalEchoCreated(roomId: String, timelineEvent: TimelineEvent) { if (roomId != this@LoadTimelineStrategy.roomId) { return @@ -130,7 +142,7 @@ internal class LoadTimelineStrategy( onEventsUpdated = dependencies.onEventsUpdated ) - suspend fun onStart() { + fun onStart() { dependencies.eventDecryptor.start() dependencies.timelineInput.listeners.add(timelineInputListener) val realm = dependencies.realm.get() @@ -139,9 +151,6 @@ internal class LoadTimelineStrategy( it.addChangeListener(chunkEntityListener) timelineChunk = it.createTimelineChunk() } - if (mode is Mode.Live) { - loadMore(dependencies.timelineSettings.initialSize.toLong(), Timeline.Direction.BACKWARDS) - } } fun onStop() { @@ -150,22 +159,25 @@ internal class LoadTimelineStrategy( chunkEntity?.removeChangeListener(chunkEntityListener) sendingEventsDataSource.stop() timelineChunk?.close(closeNext = true, closePrev = true) + getContextLatch?.cancel() chunkEntity = null timelineChunk = null } - suspend fun loadMore(count: Long, direction: Timeline.Direction): LoadMoreResult { - return if (mode is Mode.Permalink && timelineChunk == null) { + suspend fun loadMore(count: Int, direction: Timeline.Direction, fetchOnServerIfNeeded: Boolean = true): LoadMoreResult { + if (mode is Mode.Permalink && timelineChunk == null) { val params = GetContextOfEventTask.Params(roomId, mode.originEventId) try { + getContextLatch = CompletableDeferred() dependencies.getContextOfEventTask.execute(params) - LoadMoreResult.SUCCESS + // waits for the query to be fulfilled + getContextLatch?.await() + getContextLatch = null } catch (failure: Throwable) { - LoadMoreResult.FAILURE + return LoadMoreResult.FAILURE } - } else { - timelineChunk?.loadMore(count, direction) ?: LoadMoreResult.FAILURE } + return timelineChunk?.loadMore(count, direction, fetchOnServerIfNeeded) ?: LoadMoreResult.FAILURE } fun getBuiltEventIndex(eventId: String): Int? { @@ -198,7 +210,7 @@ internal class LoadTimelineStrategy( } } - private fun hasReachedLastForward(): Boolean{ + private fun hasReachedLastForward(): Boolean { return timelineChunk?.hasReachedLastForward().orFalse() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt index aa2e62fd3a..78bc02f9c7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt @@ -22,10 +22,11 @@ import io.realm.RealmObjectChangeListener import io.realm.RealmQuery import io.realm.RealmResults import io.realm.Sort +import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.tryOrNull +import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.room.timeline.Timeline import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings @@ -40,11 +41,6 @@ import timber.log.Timber import java.util.Collections import java.util.concurrent.atomic.AtomicBoolean -/** - * This is the value used to fetch on server. It's better to make constant as otherwise we can have weird chunks with disparate and small chunk of data. - */ -private const val PAGINATION_COUNT = 50 - /** * This is a wrapper around a ChunkEntity in the database. * It does mainly listen to the db timeline events. @@ -65,6 +61,9 @@ internal class TimelineChunk constructor(private val chunkEntity: ChunkEntity, private val onBuiltEvents: () -> Unit) { private val isLastForward = AtomicBoolean(chunkEntity.isLastForward) + private val isLastBackward = AtomicBoolean(chunkEntity.isLastBackward) + private var prevChunkLatch: CompletableDeferred? = null + private var nextChunkLatch: CompletableDeferred? = null private val chunkObjectListener = RealmObjectChangeListener { _, changeSet -> if (changeSet == null) return@RealmObjectChangeListener @@ -75,17 +74,16 @@ internal class TimelineChunk constructor(private val chunkEntity: ChunkEntity, if (changeSet.isFieldChanged(ChunkEntityFields.IS_LAST_FORWARD)) { isLastForward.set(chunkEntity.isLastForward) } + if (changeSet.isFieldChanged(ChunkEntityFields.IS_LAST_BACKWARD)) { + isLastBackward.set(chunkEntity.isLastBackward) + } if (changeSet.isFieldChanged(ChunkEntityFields.NEXT_CHUNK.`$`)) { nextChunk = createTimelineChunk(chunkEntity.nextChunk) - timelineScope.launch { - nextChunk?.loadMore(PAGINATION_COUNT.toLong(), Timeline.Direction.FORWARDS) - } + nextChunkLatch?.complete(Unit) } if (changeSet.isFieldChanged(ChunkEntityFields.PREV_CHUNK.`$`)) { prevChunk = createTimelineChunk(chunkEntity.prevChunk) - timelineScope.launch { - prevChunk?.loadMore(PAGINATION_COUNT.toLong(), Timeline.Direction.BACKWARDS) - } + prevChunkLatch?.complete(Unit) } } @@ -128,42 +126,63 @@ internal class TimelineChunk constructor(private val chunkEntity: ChunkEntity, return deepBuiltItems } - suspend fun loadMore(count: Long, direction: Timeline.Direction): LoadMoreResult { - val loadFromDbCount = loadFromDb(count, direction) - Timber.v("Has loaded $loadFromDbCount items from db") + /** + * This will take care of loading and building events of this chunk for the given direction and count. + * If @param fetchFromServerIfNeeded is true, it will try to fetch more events on server to get the right amount of data. + * This method will also post a snapshot as soon the data is built from db to avoid waiting for server response. + */ + suspend fun loadMore(count: Int, direction: Timeline.Direction, fetchOnServerIfNeeded: Boolean = true): LoadMoreResult { + if (direction == Timeline.Direction.FORWARDS && nextChunk != null) { + return nextChunk?.loadMore(count, direction, fetchOnServerIfNeeded) ?: LoadMoreResult.FAILURE + } else if (direction == Timeline.Direction.BACKWARDS && prevChunk != null) { + return prevChunk?.loadMore(count, direction, fetchOnServerIfNeeded) ?: LoadMoreResult.FAILURE + } + val loadFromDbCount = loadFromStorage(count, direction) + Timber.v("Has loaded $loadFromDbCount items from storage") val offsetCount = count - loadFromDbCount - // We have built the right amount of data - return if (offsetCount == 0L) { + return if (direction == Timeline.Direction.FORWARDS && isLastForward.get()) { + LoadMoreResult.REACHED_END + } else if (direction == Timeline.Direction.BACKWARDS && isLastBackward.get()) { + LoadMoreResult.REACHED_END + } else if (offsetCount == 0) { LoadMoreResult.SUCCESS } else { - delegateLoadMore(offsetCount, direction) + delegateLoadMore(fetchOnServerIfNeeded, offsetCount, direction) } } - private suspend fun delegateLoadMore(offsetCount: Long, direction: Timeline.Direction): LoadMoreResult { + private suspend fun delegateLoadMore(fetchFromServerIfNeeded: Boolean, offsetCount: Int, direction: Timeline.Direction): LoadMoreResult { return if (direction == Timeline.Direction.FORWARDS) { val nextChunkEntity = chunkEntity.nextChunk - if (nextChunkEntity == null) { - // Fetch next chunk from server if not in the db - fetchFromServer(chunkEntity.nextToken, direction) - } else { - // otherwise we delegate to the next chunk - if (nextChunk == null) { - nextChunk = createTimelineChunk(nextChunkEntity) + when { + nextChunkEntity != null -> { + if (nextChunk == null) { + nextChunk = createTimelineChunk(nextChunkEntity) + } + nextChunk?.loadMore(offsetCount, direction, fetchFromServerIfNeeded) ?: LoadMoreResult.FAILURE + } + fetchFromServerIfNeeded -> { + fetchFromServer(offsetCount, chunkEntity.nextToken, direction) + } + else -> { + LoadMoreResult.SUCCESS } - nextChunk?.loadMore(offsetCount, direction) ?: LoadMoreResult.FAILURE } } else { val prevChunkEntity = chunkEntity.prevChunk - if (prevChunkEntity == null) { - // Fetch prev chunk from server if not in the db - fetchFromServer(chunkEntity.prevToken, direction) - } else { - // otherwise we delegate to the prev chunk - if (prevChunk == null) { - prevChunk = createTimelineChunk(prevChunkEntity) + when { + prevChunkEntity != null -> { + if (prevChunk == null) { + prevChunk = createTimelineChunk(prevChunkEntity) + } + prevChunk?.loadMore(offsetCount, direction, fetchFromServerIfNeeded) ?: LoadMoreResult.FAILURE + } + fetchFromServerIfNeeded -> { + fetchFromServer(offsetCount, chunkEntity.prevToken, direction) + } + else -> { + LoadMoreResult.SUCCESS } - prevChunk?.loadMore(offsetCount, direction) ?: LoadMoreResult.FAILURE } } } @@ -239,7 +258,9 @@ internal class TimelineChunk constructor(private val chunkEntity: ChunkEntity, prevChunk?.close(closeNext = false, closePrev = true) } nextChunk = null + nextChunkLatch?.cancel() prevChunk = null + prevChunkLatch?.cancel() chunkEntity.removeChangeListener(chunkObjectListener) timelineEventEntities.removeChangeListener(timelineEventCollectionListener) } @@ -247,7 +268,7 @@ internal class TimelineChunk constructor(private val chunkEntity: ChunkEntity, /** * This method tries to read events from the current chunk. */ - private suspend fun loadFromDb(count: Long, direction: Timeline.Direction): Long { + private suspend fun loadFromStorage(count: Int, direction: Timeline.Direction): Int { val displayIndex = getNextDisplayIndex(direction) ?: return 0 val baseQuery = timelineEventEntities.where() val timelineEvents = baseQuery.offsets(direction, count, displayIndex).findAll().orEmpty() @@ -259,6 +280,9 @@ internal class TimelineChunk constructor(private val chunkEntity: ChunkEntity, timelineEvents .mapIndexed { index, timelineEventEntity -> val timelineEvent = timelineEventEntity.buildAndDecryptIfNeeded() + if (timelineEvent.root.type == EventType.STATE_ROOM_CREATE) { + isLastBackward.set(true) + } if (direction == Timeline.Direction.FORWARDS) { builtEventsIndexes[timelineEvent.eventId] = index builtEvents.add(index, timelineEvent) @@ -268,7 +292,7 @@ internal class TimelineChunk constructor(private val chunkEntity: ChunkEntity, } } onBuiltEvents() - return timelineEvents.size.toLong() + return timelineEvents.size } /** @@ -309,23 +333,35 @@ internal class TimelineChunk constructor(private val chunkEntity: ChunkEntity, * It will take care to update the database by inserting new events and linking new chunk * with this one. */ - private suspend fun fetchFromServer(token: String?, direction: Timeline.Direction): LoadMoreResult { - val paginationResult = try { + private suspend fun fetchFromServer(count: Int, token: String?, direction: Timeline.Direction): LoadMoreResult { + val latch = if (direction == Timeline.Direction.FORWARDS) { + nextChunkLatch = CompletableDeferred() + nextChunkLatch + } else { + prevChunkLatch = CompletableDeferred() + prevChunkLatch + } + val loadMoreResult = try { if (token == null) { if (direction == Timeline.Direction.BACKWARDS || !chunkEntity.hasBeenALastForwardChunk()) return LoadMoreResult.REACHED_END val lastKnownEventId = chunkEntity.sortedTimelineEvents().firstOrNull()?.eventId ?: return LoadMoreResult.FAILURE - val taskParams = FetchTokenAndPaginateTask.Params(roomId, lastKnownEventId, direction.toPaginationDirection(), PAGINATION_COUNT) - fetchTokenAndPaginateTask.execute(taskParams) + val taskParams = FetchTokenAndPaginateTask.Params(roomId, lastKnownEventId, direction.toPaginationDirection(), count) + fetchTokenAndPaginateTask.execute(taskParams).toLoadMoreResult() } else { - Timber.v("Fetch more events on server") - val taskParams = PaginationTask.Params(roomId, token, direction.toPaginationDirection(), PAGINATION_COUNT) - paginationTask.execute(taskParams) + Timber.v("Fetch $count more events on server") + val taskParams = PaginationTask.Params(roomId, token, direction.toPaginationDirection(), count) + paginationTask.execute(taskParams).toLoadMoreResult() } } catch (failure: Throwable) { Timber.e("Failed to fetch from server: $failure", failure) - return LoadMoreResult.FAILURE + LoadMoreResult.FAILURE + } + return if (loadMoreResult == LoadMoreResult.SUCCESS) { + latch?.await() + loadMore(count, direction, fetchOnServerIfNeeded = false) + } else { + loadMoreResult } - return paginationResult.toLoadMoreResult() } private fun TokenChunkEventPersistor.Result.toLoadMoreResult(): LoadMoreResult { @@ -358,6 +394,9 @@ internal class TimelineChunk constructor(private val chunkEntity: ChunkEntity, .map { it.buildAndDecryptIfNeeded() } builtEventsIndexes.entries.filter { it.value >= range.startIndex }.forEach { it.setValue(it.value + range.length) } newItems.mapIndexed { index, timelineEvent -> + if (timelineEvent.root.type == EventType.STATE_ROOM_CREATE) { + isLastBackward.set(true) + } val correctedIndex = range.startIndex + index builtEvents.add(correctedIndex, timelineEvent) builtEventsIndexes[timelineEvent.eventId] = correctedIndex @@ -421,7 +460,7 @@ internal class TimelineChunk constructor(private val chunkEntity: ChunkEntity, private fun RealmQuery.offsets( direction: Timeline.Direction, - count: Long, + count: Int, startDisplayIndex: Int ): RealmQuery { sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING) @@ -430,7 +469,7 @@ private fun RealmQuery.offsets( } else { greaterThanOrEqualTo(TimelineEventEntityFields.DISPLAY_INDEX, startDisplayIndex) } - return limit(count) + return limit(count.toLong()) } private fun Timeline.Direction.toPaginationDirection(): PaginationDirection { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt index ac6de2ebb0..2fd2f4cf87 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt @@ -174,7 +174,6 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec private var inSubmitList: Boolean = false private var hasReachedInvite: Boolean = false private var hasUTD: Boolean = false - private var hasReachedCreateEvent: Boolean = false private var positionOfReadMarker: Int? = null private var partialState: PartialState = PartialState() @@ -287,7 +286,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec return } // Avoid displaying two loaders if there is no elements between them - val showBackwardsLoader = (!showingForwardLoader || timelineModels.isNotEmpty()) && !hasReachedCreateEvent + val showBackwardsLoader = !showingForwardLoader || timelineModels.isNotEmpty() // We can hide the loader but still add the item to controller so it can trigger backwards pagination LoadingItem_() .id("backward_loading_item_$timestamp") @@ -302,6 +301,10 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec submitSnapshot(snapshot) } + override fun onStateUpdated(direction: Timeline.Direction, state: Timeline.PaginationState) { + requestDelayedModelBuild(0) + } + private fun submitSnapshot(newSnapshot: List) { backgroundHandler.post { inSubmitList = true @@ -456,14 +459,10 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec private fun preprocessReverseEvents() { receiptsByEvent.clear() timelineEventsGroups.clear() - hasReachedCreateEvent = false val itr = currentSnapshot.listIterator(currentSnapshot.size) var lastShownEventId: String? = null while (itr.hasPrevious()) { val event = itr.previous() - if (!hasReachedCreateEvent && event.root.type == EventType.STATE_ROOM_CREATE) { - hasReachedCreateEvent = true - } timelineEventsGroups.addOrIgnore(event) val currentReadReceipts = ArrayList(event.readReceipts).filter { it.user.userId != session.myUserId From b53433e61bc455ed2e7ee19c699eed9428a2871b Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 8 Dec 2021 11:07:07 +0100 Subject: [PATCH 038/632] Timeline: some clean up --- .../session/room/timeline/DefaultTimeline.kt | 9 +++--- .../room/timeline/LoadTimelineStrategy.kt | 6 +--- .../session/room/timeline/TimelineChunk.kt | 29 +++++++++---------- 3 files changed, 18 insertions(+), 26 deletions(-) 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 5e9efecc67..a2398bd750 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 @@ -74,18 +74,17 @@ internal class DefaultTimeline internal constructor(private val roomId: String, private val sequencer = SemaphoreCoroutineSequencer() private val strategyDependencies = LoadTimelineStrategy.Dependencies( - timelineScope = timelineScope, - eventDecryptor = eventDecryptor, timelineSettings = settings, + realm = backgroundRealm, + eventDecryptor = eventDecryptor, paginationTask = paginationTask, fetchTokenAndPaginateTask = fetchTokenAndPaginateTask, + getContextOfEventTask = getEventTask, timelineInput = timelineInput, timelineEventMapper = timelineEventMapper, - realm = backgroundRealm, - getContextOfEventTask = getEventTask, threadsAwarenessHandler = threadsAwarenessHandler, - onLimitedTimeline = this::onLimitedTimeline, onEventsUpdated = this::postSnapshot, + onLimitedTimeline = this::onLimitedTimeline, onNewTimelineEvents = this::onNewTimelineEvents ) 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 fb0971ad47..d51c2c28cb 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 @@ -21,8 +21,6 @@ import io.realm.OrderedRealmCollectionChangeListener import io.realm.Realm import io.realm.RealmResults import kotlinx.coroutines.CompletableDeferred -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.room.timeline.Timeline @@ -65,7 +63,6 @@ internal class LoadTimelineStrategy( data class Dependencies( val timelineSettings: TimelineSettings, - val timelineScope: CoroutineScope, val realm: AtomicReference, val eventDecryptor: TimelineEventDecryptor, val paginationTask: PaginationTask, @@ -228,8 +225,7 @@ internal class LoadTimelineStrategy( uiEchoManager = uiEchoManager, threadsAwarenessHandler = dependencies.threadsAwarenessHandler, initialEventId = mode.originEventId(), - onBuiltEvents = dependencies.onEventsUpdated, - timelineScope = dependencies.timelineScope + onBuiltEvents = dependencies.onEventsUpdated ) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt index 78bc02f9c7..9686e49987 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt @@ -23,7 +23,6 @@ import io.realm.RealmQuery import io.realm.RealmResults import io.realm.Sort import kotlinx.coroutines.CompletableDeferred -import kotlinx.coroutines.CoroutineScope import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.events.model.EventType @@ -46,19 +45,18 @@ import java.util.concurrent.atomic.AtomicBoolean * It does mainly listen to the db timeline events. * It also triggers pagination to the server when needed, or dispatch to the prev or next chunk if any. */ -internal class TimelineChunk constructor(private val chunkEntity: ChunkEntity, - private val timelineScope: CoroutineScope, - private val timelineSettings: TimelineSettings, - private val roomId: String, - private val timelineId: String, - private val eventDecryptor: TimelineEventDecryptor, - private val paginationTask: PaginationTask, - private val fetchTokenAndPaginateTask: FetchTokenAndPaginateTask, - private val timelineEventMapper: TimelineEventMapper, - private val uiEchoManager: UIEchoManager? = null, - private val threadsAwarenessHandler: ThreadsAwarenessHandler, - private val initialEventId: String?, - private val onBuiltEvents: () -> Unit) { +internal class TimelineChunk(private val chunkEntity: ChunkEntity, + private val timelineSettings: TimelineSettings, + private val roomId: String, + private val timelineId: String, + private val eventDecryptor: TimelineEventDecryptor, + private val paginationTask: PaginationTask, + private val fetchTokenAndPaginateTask: FetchTokenAndPaginateTask, + private val timelineEventMapper: TimelineEventMapper, + private val uiEchoManager: UIEchoManager? = null, + private val threadsAwarenessHandler: ThreadsAwarenessHandler, + private val initialEventId: String?, + private val onBuiltEvents: () -> Unit) { private val isLastForward = AtomicBoolean(chunkEntity.isLastForward) private val isLastBackward = AtomicBoolean(chunkEntity.isLastBackward) @@ -442,11 +440,10 @@ internal class TimelineChunk constructor(private val chunkEntity: ChunkEntity, if (chunkEntity == null) return null return TimelineChunk( chunkEntity = chunkEntity, - timelineScope = timelineScope, timelineSettings = timelineSettings, + roomId = roomId, timelineId = timelineId, eventDecryptor = eventDecryptor, - roomId = roomId, paginationTask = paginationTask, fetchTokenAndPaginateTask = fetchTokenAndPaginateTask, timelineEventMapper = timelineEventMapper, From 1e2e9e1070a13f852fc674460bf45d6ad54d385e Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 9 Dec 2021 12:26:40 +0100 Subject: [PATCH 039/632] Timeline: change a bit when postPagination is triggered --- .../TimelineSimpleBackPaginationTest.kt | 2 +- .../sdk/api/session/room/timeline/Timeline.kt | 3 +- .../session/room/timeline/DefaultTimeline.kt | 84 ++++++++++++++----- .../room/timeline/LoadTimelineStrategy.kt | 6 +- .../room/timeline/SendingEventsDataSource.kt | 8 +- .../session/room/timeline/TimelineChunk.kt | 12 +-- 6 files changed, 77 insertions(+), 38 deletions(-) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineSimpleBackPaginationTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineSimpleBackPaginationTest.kt index bec6886fb1..d87a7269f6 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineSimpleBackPaginationTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineSimpleBackPaginationTest.kt @@ -90,7 +90,7 @@ class TimelineSimpleBackPaginationTest : InstrumentedTest { assertEquals(false, bobTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS)) val onlySentEvents = runBlocking { - bobTimeline.awaitSnapshot() + bobTimeline.getSnapshot() } .filter { it.root.isTextMessage() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/Timeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/Timeline.kt index 443b1a8f13..241e5f3b9b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/Timeline.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/Timeline.kt @@ -73,7 +73,6 @@ interface Timeline { /** * This is the same than the regular paginate method but waits for the results instead * of relying on the timeline listener. - * Note that it will still trigger onTimelineUpdated internally. */ suspend fun awaitPaginate(direction: Direction, count: Int): List @@ -90,7 +89,7 @@ interface Timeline { /** * Returns a snapshot of the timeline in his current state. */ - suspend fun awaitSnapshot(): List + fun getSnapshot(): List interface Listener { /** 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 a2398bd750..31ed8868d1 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 @@ -24,6 +24,10 @@ import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.android.asCoroutineDispatcher import kotlinx.coroutines.cancelChildren import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.sample import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import okhttp3.internal.closeQuietly @@ -72,6 +76,7 @@ internal class DefaultTimeline internal constructor(private val roomId: String, private val timelineDispatcher = BACKGROUND_HANDLER.asCoroutineDispatcher() private val timelineScope = CoroutineScope(SupervisorJob() + timelineDispatcher) private val sequencer = SemaphoreCoroutineSequencer() + private val postSnapshotSignalFlow = MutableSharedFlow(0) private val strategyDependencies = LoadTimelineStrategy.Dependencies( timelineSettings = settings, @@ -83,7 +88,7 @@ internal class DefaultTimeline internal constructor(private val roomId: String, timelineInput = timelineInput, timelineEventMapper = timelineEventMapper, threadsAwarenessHandler = threadsAwarenessHandler, - onEventsUpdated = this::postSnapshot, + onEventsUpdated = this::sendSignalToPostSnapshot, onLimitedTimeline = this::onLimitedTimeline, onNewTimelineEvents = this::onNewTimelineEvents ) @@ -95,7 +100,12 @@ internal class DefaultTimeline internal constructor(private val roomId: String, override fun addListener(listener: Timeline.Listener): Boolean { listeners.add(listener) - postSnapshot() + timelineScope.launch { + val snapshot = strategy.buildSnapshot() + withContext(Dispatchers.Main) { + tryOrNull { listener.onTimelineUpdated(snapshot) } + } + } return true } @@ -117,7 +127,9 @@ internal class DefaultTimeline internal constructor(private val roomId: String, val realm = Realm.getInstance(realmConfiguration) ensureReadReceiptAreLoaded(realm) backgroundRealm.set(realm) + listenToPostSnapshotSignals() openAround(initialEventId) + postSnapshot() } } } @@ -148,7 +160,10 @@ internal class DefaultTimeline internal constructor(private val roomId: String, override fun paginate(direction: Timeline.Direction, count: Int) { timelineScope.launch { - loadMore(count, direction, fetchOnServerIfNeeded = true) + val postSnapshot = loadMore(count, direction, fetchOnServerIfNeeded = true) + if (postSnapshot) { + postSnapshot() + } } } @@ -156,11 +171,11 @@ internal class DefaultTimeline internal constructor(private val roomId: String, withContext(timelineDispatcher) { loadMore(count, direction, fetchOnServerIfNeeded = true) } - return awaitSnapshot() + return getSnapshot() } - override suspend fun awaitSnapshot(): List = withContext(timelineDispatcher) { - strategy.buildSnapshot() + override fun getSnapshot(): List { + return strategy.buildSnapshot() } override fun getIndexOfEvent(eventId: String?): Int? { @@ -176,7 +191,7 @@ internal class DefaultTimeline internal constructor(private val roomId: String, }.get() } - private suspend fun loadMore(count: Int, direction: Timeline.Direction, fetchOnServerIfNeeded: Boolean) { + private suspend fun loadMore(count: Int, direction: Timeline.Direction, fetchOnServerIfNeeded: Boolean): Boolean { val baseLogMessage = "loadMore(count: $count, direction: $direction, roomId: $roomId, fetchOnServer: $fetchOnServerIfNeeded)" Timber.v("$baseLogMessage started") if (!isStarted.get()) { @@ -185,11 +200,11 @@ internal class DefaultTimeline internal constructor(private val roomId: String, val currentState = getPaginationState(direction) if (!currentState.hasMoreToLoad) { Timber.v("$baseLogMessage : nothing more to load") - return + return false } if (currentState.loading) { Timber.v("$baseLogMessage : already loading") - return + return false } updateState(direction) { it.copy(loading = true) @@ -200,6 +215,7 @@ internal class DefaultTimeline internal constructor(private val roomId: String, updateState(direction) { it.copy(loading = false, hasMoreToLoad = hasMoreToLoad) } + return true } private suspend fun openAround(eventId: String?) = withContext(timelineDispatcher) { @@ -221,9 +237,10 @@ internal class DefaultTimeline internal constructor(private val roomId: String, direction = Timeline.Direction.BACKWARDS, fetchOnServerIfNeeded = false ) + Timber.v("$baseLogMessage finished") } - private suspend fun initPaginationStates(eventId: String?) { + private fun initPaginationStates(eventId: String?) { updateState(Timeline.Direction.FORWARDS) { it.copy(loading = false, hasMoreToLoad = eventId != null) } @@ -232,21 +249,40 @@ internal class DefaultTimeline internal constructor(private val roomId: String, } } + private fun sendSignalToPostSnapshot(withThrottling: Boolean) { + timelineScope.launch { + if (withThrottling) { + postSnapshotSignalFlow.emit(Unit) + } else { + postSnapshot() + } + } + } + + @Suppress("EXPERIMENTAL_API_USAGE") + private fun listenToPostSnapshotSignals() { + postSnapshotSignalFlow + .sample(150) + .onEach { + postSnapshot() + } + .launchIn(timelineScope) + } + private fun onLimitedTimeline() { timelineScope.launch { initPaginationStates(null) loadMore(settings.initialSize, Timeline.Direction.BACKWARDS, false) + postSnapshot() } } - private fun postSnapshot() { - timelineScope.launch { - val snapshot = strategy.buildSnapshot() - Timber.v("Post snapshot of ${snapshot.size} items") - withContext(Dispatchers.Main) { - listeners.forEach { - tryOrNull { it.onTimelineUpdated(snapshot) } - } + private suspend fun postSnapshot() { + val snapshot = strategy.buildSnapshot() + Timber.v("Post snapshot of ${snapshot.size} events") + withContext(Dispatchers.Main) { + listeners.forEach { + tryOrNull { it.onTimelineUpdated(snapshot) } } } } @@ -259,7 +295,7 @@ internal class DefaultTimeline internal constructor(private val roomId: String, } } - private suspend fun updateState(direction: Timeline.Direction, update: (Timeline.PaginationState) -> Timeline.PaginationState) { + private fun updateState(direction: Timeline.Direction, update: (Timeline.PaginationState) -> Timeline.PaginationState) { val stateReference = when (direction) { Timeline.Direction.FORWARDS -> forwardState Timeline.Direction.BACKWARDS -> backwardState @@ -272,10 +308,12 @@ internal class DefaultTimeline internal constructor(private val roomId: String, } } - private suspend fun postPaginationState(direction: Timeline.Direction, state: Timeline.PaginationState) = withContext(Dispatchers.Main) { - Timber.v("Post $direction pagination state: $state ") - listeners.forEach { - tryOrNull { it.onStateUpdated(direction, state) } + private fun postPaginationState(direction: Timeline.Direction, state: Timeline.PaginationState) { + timelineScope.launch(Dispatchers.Main) { + Timber.v("Post $direction pagination state: $state ") + listeners.forEach { + tryOrNull { it.onStateUpdated(direction, state) } + } } } 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 d51c2c28cb..0111996345 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 @@ -71,7 +71,7 @@ internal class LoadTimelineStrategy( val timelineInput: TimelineInput, val timelineEventMapper: TimelineEventMapper, val threadsAwarenessHandler: ThreadsAwarenessHandler, - val onEventsUpdated: () -> Unit, + val onEventsUpdated: (Boolean) -> Unit, val onLimitedTimeline: () -> Unit, val onNewTimelineEvents: (List) -> Unit ) @@ -110,7 +110,7 @@ internal class LoadTimelineStrategy( } if (uiEchoManager.onLocalEchoCreated(timelineEvent)) { dependencies.onNewTimelineEvents(listOf(timelineEvent.eventId)) - dependencies.onEventsUpdated() + dependencies.onEventsUpdated(false) } } @@ -119,7 +119,7 @@ internal class LoadTimelineStrategy( return } if (uiEchoManager.onSendStateUpdated(eventId, sendState)) { - dependencies.onEventsUpdated() + dependencies.onEventsUpdated(false) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/SendingEventsDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/SendingEventsDataSource.kt index c6d10a23fb..a98de1c595 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/SendingEventsDataSource.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/SendingEventsDataSource.kt @@ -37,15 +37,17 @@ internal class RealmSendingEventsDataSource( private val realm: AtomicReference, private val uiEchoManager: UIEchoManager, private val timelineEventMapper: TimelineEventMapper, - private val onEventsUpdated: () -> Unit + private val onEventsUpdated: (Boolean) -> Unit ) : SendingEventsDataSource { private var roomEntity: RoomEntity? = null private var sendingTimelineEvents: RealmList? = null + private var frozenSendingTimelineEvents: RealmList? = null private val sendingTimelineEventsListener = RealmChangeListener> { events -> uiEchoManager.onSentEventsInDatabase(events.map { it.eventId }) - onEventsUpdated() + frozenSendingTimelineEvents = sendingTimelineEvents?.freeze() + onEventsUpdated(false) } override fun start() { @@ -65,7 +67,7 @@ internal class RealmSendingEventsDataSource( val builtSendingEvents = mutableListOf() uiEchoManager.getInMemorySendingEvents() .addWithUiEcho(builtSendingEvents) - sendingTimelineEvents?.freeze() + frozenSendingTimelineEvents ?.filter { timelineEvent -> builtSendingEvents.none { it.eventId == timelineEvent.eventId } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt index 9686e49987..b3a8808ba7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt @@ -56,7 +56,7 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity, private val uiEchoManager: UIEchoManager? = null, private val threadsAwarenessHandler: ThreadsAwarenessHandler, private val initialEventId: String?, - private val onBuiltEvents: () -> Unit) { + private val onBuiltEvents: (Boolean) -> Unit) { private val isLastForward = AtomicBoolean(chunkEntity.isLastForward) private val isLastBackward = AtomicBoolean(chunkEntity.isLastBackward) @@ -86,6 +86,7 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity, } private val timelineEventCollectionListener = OrderedRealmCollectionChangeListener { results: RealmResults, changeSet: OrderedCollectionChangeSet -> + Timber.v("on timeline events chunk update") val frozenResults = results.freeze() handleDatabaseChangeSet(frozenResults, changeSet) } @@ -135,9 +136,9 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity, } else if (direction == Timeline.Direction.BACKWARDS && prevChunk != null) { return prevChunk?.loadMore(count, direction, fetchOnServerIfNeeded) ?: LoadMoreResult.FAILURE } - val loadFromDbCount = loadFromStorage(count, direction) - Timber.v("Has loaded $loadFromDbCount items from storage") - val offsetCount = count - loadFromDbCount + val loadFromStorageCount = loadFromStorage(count, direction) + Timber.v("Has loaded $loadFromStorageCount items from storage in $direction") + val offsetCount = count - loadFromStorageCount return if (direction == Timeline.Direction.FORWARDS && isLastForward.get()) { LoadMoreResult.REACHED_END } else if (direction == Timeline.Direction.BACKWARDS && isLastBackward.get()) { @@ -289,7 +290,6 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity, builtEvents.add(timelineEvent) } } - onBuiltEvents() return timelineEvents.size } @@ -412,7 +412,7 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity, } } if (insertions.isNotEmpty() || modifications.isNotEmpty()) { - onBuiltEvents() + onBuiltEvents(true) } } From c830d4992437158c71b8f2340f54066093c1dead Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 9 Dec 2021 12:27:36 +0100 Subject: [PATCH 040/632] Timeline: remove onStateUpdated in controller --- .../home/room/detail/timeline/TimelineEventController.kt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt index 2fd2f4cf87..241ccb7428 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt @@ -301,10 +301,6 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec submitSnapshot(snapshot) } - override fun onStateUpdated(direction: Timeline.Direction, state: Timeline.PaginationState) { - requestDelayedModelBuild(0) - } - private fun submitSnapshot(newSnapshot: List) { backgroundHandler.post { inSubmitList = true From 31ba912d6e021c86d7638bb51b80b1021ee9bb04 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 9 Dec 2021 12:28:13 +0100 Subject: [PATCH 041/632] Timeline: url preview listen to Flow in ViewModel --- .../home/room/detail/RoomDetailViewModel.kt | 35 +++++++++++++------ 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index 18acc4ff14..3ac3ed5ed4 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -160,6 +160,7 @@ class RoomDetailViewModel @AssistedInject constructor( observeMyRoomMember() observeActiveRoomWidgets() observePowerLevel() + setupPreviewUrlObservers() room.getRoomSummaryLive() viewModelScope.launch(Dispatchers.IO) { tryOrNull { room.markAsRead(ReadService.MarkAsReadParams.READ_RECEIPT) } @@ -263,6 +264,30 @@ class RoomDetailViewModel @AssistedInject constructor( } } + private fun setupPreviewUrlObservers() { + if (!vectorPreferences.showUrlPreviews()) { + return + } + combine( + timelineEvents, + room.flow().liveRoomSummary() + .unwrap() + .map { it.isEncrypted } + .distinctUntilChanged() + ) { snapshot, isRoomEncrypted -> + if (isRoomEncrypted) { + return@combine + } + withContext(Dispatchers.Default) { + Timber.v("On new timeline events for urlpreview on ${Thread.currentThread()}") + snapshot.forEach { + previewUrlRetriever.getPreviewUrl(it) + } + } + } + .launchIn(viewModelScope) + } + fun getOtherUserIds() = room.roomSummary()?.otherMemberIds override fun handle(action: RoomDetailAction) { @@ -1031,16 +1056,6 @@ class RoomDetailViewModel @AssistedInject constructor( // tryEmit doesn't work with SharedFlow without cache timelineEvents.emit(snapshot) } - // PreviewUrl - if (vectorPreferences.showUrlPreviews()) { - withState { state -> - snapshot - .takeIf { state.asyncRoomSummary.invoke()?.isEncrypted == false } - ?.forEach { - previewUrlRetriever.getPreviewUrl(it) - } - } - } } override fun onTimelineFailure(throwable: Throwable) { From faebf95e1c2fc81fcbea0ca37733877759c07765 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 9 Dec 2021 12:28:53 +0100 Subject: [PATCH 042/632] Timeline: remove LifecycleOwner on EpoxyModel as it's not used (and takes some time uselessly) --- .../main/java/im/vector/app/core/epoxy/VectorEpoxyModel.kt | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/vector/src/main/java/im/vector/app/core/epoxy/VectorEpoxyModel.kt b/vector/src/main/java/im/vector/app/core/epoxy/VectorEpoxyModel.kt index fcb5a473a4..e9dd72a6f2 100644 --- a/vector/src/main/java/im/vector/app/core/epoxy/VectorEpoxyModel.kt +++ b/vector/src/main/java/im/vector/app/core/epoxy/VectorEpoxyModel.kt @@ -30,24 +30,19 @@ import kotlinx.coroutines.cancelChildren /** * EpoxyModelWithHolder which can listen to visibility state change */ -abstract class VectorEpoxyModel : EpoxyModelWithHolder(), LifecycleOwner { +abstract class VectorEpoxyModel : EpoxyModelWithHolder(){ protected val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main) - private val lifecycleRegistry: LifecycleRegistry = LifecycleRegistry(this) - - override fun getLifecycle() = lifecycleRegistry private var onModelVisibilityStateChangedListener: OnVisibilityStateChangedListener? = null @CallSuper override fun bind(holder: H) { super.bind(holder) - lifecycleRegistry.currentState = Lifecycle.State.STARTED } @CallSuper override fun unbind(holder: H) { - lifecycleRegistry.currentState = Lifecycle.State.DESTROYED coroutineScope.coroutineContext.cancelChildren() super.unbind(holder) } From bf287d1827beb60f65b25668f404012b8676f20d Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 9 Dec 2021 12:33:56 +0100 Subject: [PATCH 043/632] Timeline: clean up --- .../TimelineSimpleBackPaginationTest.kt | 5 ++--- .../sdk/internal/database/DatabaseCleaner.kt | 2 +- .../database/RealmSessionStoreMigration.kt | 1 - .../database/helper/ChunkEntityHelper.kt | 6 +++--- .../internal/database/model/ChunkEntity.kt | 2 +- .../internal/database/query/ReadQueries.kt | 1 - .../session/room/read/DefaultReadService.kt | 1 - .../session/room/timeline/DefaultTimeline.kt | 11 ++++++----- .../room/timeline/DefaultTimelineService.kt | 5 ++++- .../room/timeline/LoadTimelineStrategy.kt | 2 -- .../session/room/timeline/TimelineChunk.kt | 19 ++++++++++--------- .../room/timeline/TokenChunkEventPersistor.kt | 2 +- .../session/room/timeline/UIEchoManager.kt | 1 - .../vector/app/core/epoxy/VectorEpoxyModel.kt | 5 +---- 14 files changed, 29 insertions(+), 34 deletions(-) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineSimpleBackPaginationTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineSimpleBackPaginationTest.kt index d87a7269f6..b75df9b5a2 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineSimpleBackPaginationTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineSimpleBackPaginationTest.kt @@ -43,7 +43,6 @@ class TimelineSimpleBackPaginationTest : InstrumentedTest { @Test fun timeline_backPaginate_shouldReachEndOfTimeline() { - val numberOfMessagesToSent = 200 val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(false) @@ -69,12 +68,12 @@ class TimelineSimpleBackPaginationTest : InstrumentedTest { bobTimeline.start() commonTestHelper.waitWithLatch(timeout = TestConstants.timeOutMillis * 10) { - val listener = object : Timeline.Listener { override fun onStateUpdated(direction: Timeline.Direction, state: Timeline.PaginationState) { - if (direction == Timeline.Direction.FORWARDS) + if (direction == Timeline.Direction.FORWARDS) { return + } if (state.hasMoreToLoad && !state.loading) { bobTimeline.paginate(Timeline.Direction.BACKWARDS, 30) } else if (!state.hasMoreToLoad) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/DatabaseCleaner.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/DatabaseCleaner.kt index 09fbb2bfa0..6d567600ad 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/DatabaseCleaner.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/DatabaseCleaner.kt @@ -44,7 +44,7 @@ internal class DatabaseCleaner @Inject constructor(@SessionDatabase private val awaitTransaction(realmConfiguration) { realm -> val allRooms = realm.where(RoomEntity::class.java).findAll() Timber.v("There are ${allRooms.size} rooms in this session") - //cleanUp(realm, MAX_NUMBER_OF_EVENTS_IN_DB / 2L) + // cleanUp(realm, MAX_NUMBER_OF_EVENTS_IN_DB / 2L) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt index 9934c2570b..508af250c2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt @@ -414,5 +414,4 @@ internal class RealmSessionStoreMigration @Inject constructor( } } } - } 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 136734c02e..c21bf74d93 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 @@ -110,7 +110,7 @@ internal fun ChunkEntity.addTimelineEvent(roomId: String, true } } - //numberOfTimelineEvents++ + // numberOfTimelineEvents++ timelineEvents.add(timelineEventEntity) } @@ -207,11 +207,11 @@ internal fun ChunkEntity.isMoreRecentThan(chunkToCheck: ChunkEntity): Boolean { if (this.isLastForward) return true if (chunkToCheck.isLastForward) return false // Check if the chunk to check is linked to this one - if(chunkToCheck.doesNextChunksVerifyCondition { it == this }){ + if (chunkToCheck.doesNextChunksVerifyCondition { it == this }) { return true } // Otherwise check if this chunk is linked to last forward - if(this.doesNextChunksVerifyCondition { it.isLastForward }){ + if (this.doesNextChunksVerifyCondition { it.isLastForward }) { return true } // We don't know, so we assume it's false diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/ChunkEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/ChunkEntity.kt index 8b301d15fa..82b7517181 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/ChunkEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/ChunkEntity.kt @@ -31,7 +31,7 @@ internal open class ChunkEntity(@Index var prevToken: String? = null, var nextChunk: ChunkEntity? = null, var stateEvents: RealmList = RealmList(), var timelineEvents: RealmList = RealmList(), - //var numberOfTimelineEvents: Long = 0, + // var numberOfTimelineEvents: Long = 0, // Only one chunk will have isLastForward == true @Index var isLastForward: Boolean = false, @Index var isLastBackward: Boolean = false 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 c8879b009e..c9c96b9cc1 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 @@ -18,7 +18,6 @@ package org.matrix.android.sdk.internal.database.query import io.realm.Realm import io.realm.RealmConfiguration import org.matrix.android.sdk.api.session.events.model.LocalEcho -import org.matrix.android.sdk.internal.database.helper.doesNextChunksVerifyCondition import org.matrix.android.sdk.internal.database.helper.isMoreRecentThan import org.matrix.android.sdk.internal.database.model.ChunkEntity import org.matrix.android.sdk.internal.database.model.ReadMarkerEntity diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/DefaultReadService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/DefaultReadService.kt index 7e42e4f9c2..b30c66c82e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/DefaultReadService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/DefaultReadService.kt @@ -34,7 +34,6 @@ import org.matrix.android.sdk.internal.database.query.isEventRead 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 -import org.matrix.android.sdk.internal.task.TaskExecutor internal class DefaultReadService @AssistedInject constructor( @Assisted private val roomId: String, 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 31ed8868d1..bb15bcb9ca 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 @@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.session.room.timeline import io.realm.Realm import io.realm.RealmConfiguration import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.android.asCoroutineDispatcher import kotlinx.coroutines.cancelChildren @@ -31,6 +30,7 @@ import kotlinx.coroutines.flow.sample import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import okhttp3.internal.closeQuietly +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.room.timeline.Timeline import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent @@ -53,6 +53,7 @@ internal class DefaultTimeline internal constructor(private val roomId: String, private val loadRoomMembersTask: LoadRoomMembersTask, private val readReceiptHandler: ReadReceiptHandler, private val settings: TimelineSettings, + private val coroutineDispatchers: MatrixCoroutineDispatchers, paginationTask: PaginationTask, getEventTask: GetContextOfEventTask, fetchTokenAndPaginateTask: FetchTokenAndPaginateTask, @@ -102,7 +103,7 @@ internal class DefaultTimeline internal constructor(private val roomId: String, listeners.add(listener) timelineScope.launch { val snapshot = strategy.buildSnapshot() - withContext(Dispatchers.Main) { + withContext(coroutineDispatchers.main) { tryOrNull { listener.onTimelineUpdated(snapshot) } } } @@ -280,7 +281,7 @@ internal class DefaultTimeline internal constructor(private val roomId: String, private suspend fun postSnapshot() { val snapshot = strategy.buildSnapshot() Timber.v("Post snapshot of ${snapshot.size} events") - withContext(Dispatchers.Main) { + withContext(coroutineDispatchers.main) { listeners.forEach { tryOrNull { it.onTimelineUpdated(snapshot) } } @@ -288,7 +289,7 @@ internal class DefaultTimeline internal constructor(private val roomId: String, } private fun onNewTimelineEvents(eventIds: List) { - timelineScope.launch(Dispatchers.Main) { + timelineScope.launch(coroutineDispatchers.main) { listeners.forEach { tryOrNull { it.onNewTimelineEvents(eventIds) } } @@ -309,7 +310,7 @@ internal class DefaultTimeline internal constructor(private val roomId: String, } private fun postPaginationState(direction: Timeline.Direction, state: Timeline.PaginationState) { - timelineScope.launch(Dispatchers.Main) { + timelineScope.launch(coroutineDispatchers.main) { Timber.v("Post $direction pagination state: $state ") listeners.forEach { tryOrNull { it.onStateUpdated(direction, state) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt index ab3f574130..126374b430 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt @@ -23,6 +23,7 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import io.realm.Sort import io.realm.kotlin.where +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.session.events.model.isImageMessage import org.matrix.android.sdk.api.session.events.model.isVideoMessage import org.matrix.android.sdk.api.session.room.timeline.Timeline @@ -54,7 +55,8 @@ internal class DefaultTimelineService @AssistedInject constructor( private val timelineEventMapper: TimelineEventMapper, private val loadRoomMembersTask: LoadRoomMembersTask, private val threadsAwarenessHandler: ThreadsAwarenessHandler, - private val readReceiptHandler: ReadReceiptHandler + private val readReceiptHandler: ReadReceiptHandler, + private val coroutineDispatchers: MatrixCoroutineDispatchers ) : TimelineService { @AssistedFactory @@ -68,6 +70,7 @@ internal class DefaultTimelineService @AssistedInject constructor( initialEventId = eventId, settings = settings, realmConfiguration = monarchy.realmConfiguration, + coroutineDispatchers = coroutineDispatchers, paginationTask = paginationTask, timelineEventMapper = timelineEventMapper, timelineInput = timelineInput, 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 0111996345..2e71a8099f 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 @@ -230,5 +230,3 @@ internal class LoadTimelineStrategy( } } } - - diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt index b3a8808ba7..14cba2a4b8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt @@ -85,11 +85,12 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity, } } - private val timelineEventCollectionListener = OrderedRealmCollectionChangeListener { results: RealmResults, changeSet: OrderedCollectionChangeSet -> - Timber.v("on timeline events chunk update") - val frozenResults = results.freeze() - handleDatabaseChangeSet(frozenResults, changeSet) - } + private val timelineEventsChangeListener = + OrderedRealmCollectionChangeListener { results: RealmResults, changeSet: OrderedCollectionChangeSet -> + Timber.v("on timeline events chunk update") + val frozenResults = results.freeze() + handleDatabaseChangeSet(frozenResults, changeSet) + } private var timelineEventEntities: RealmResults = chunkEntity.sortedTimelineEvents() private val builtEvents: MutableList = Collections.synchronizedList(ArrayList()) @@ -99,7 +100,7 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity, private var prevChunk: TimelineChunk? = null init { - timelineEventEntities.addChangeListener(timelineEventCollectionListener) + timelineEventEntities.addChangeListener(timelineEventsChangeListener) chunkEntity.addChangeListener(chunkObjectListener) } @@ -261,7 +262,7 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity, prevChunk = null prevChunkLatch?.cancel() chunkEntity.removeChangeListener(chunkObjectListener) - timelineEventEntities.removeChangeListener(timelineEventCollectionListener) + timelineEventEntities.removeChangeListener(timelineEventsChangeListener) } /** @@ -311,8 +312,8 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity, val timelineEvent = buildTimelineEvent(this) val transactionId = timelineEvent.root.unsignedData?.transactionId uiEchoManager?.onSyncedEvent(transactionId) - if (timelineEvent.isEncrypted() - && timelineEvent.root.mxDecryptionResult == null) { + if (timelineEvent.isEncrypted() && + timelineEvent.root.mxDecryptionResult == null) { timelineEvent.root.eventId?.also { eventDecryptor.requestDecryption(TimelineEventDecryptor.DecryptionRequest(timelineEvent.root, timelineId)) } } return timelineEvent 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 54d7412166..4625155c0a 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 @@ -133,7 +133,7 @@ internal class TokenChunkEventPersistor @Inject constructor(@SessionDatabase pri if (event.eventId == null || event.senderId == null) { return@forEach } - //We check for the timeline event with this id + // We check for the timeline event with this id val eventId = event.eventId val existingTimelineEvent = TimelineEventEntity.where(realm, roomId, eventId).findFirst() // If it exists, we want to stop here, just link the prevChunk diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/UIEchoManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/UIEchoManager.kt index 5b2cf001f3..16d36c0cd9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/UIEchoManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/UIEchoManager.kt @@ -24,7 +24,6 @@ import org.matrix.android.sdk.api.session.room.model.ReactionAggregatedSummary import org.matrix.android.sdk.api.session.room.model.relation.ReactionContent import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent -import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings import timber.log.Timber import java.util.Collections diff --git a/vector/src/main/java/im/vector/app/core/epoxy/VectorEpoxyModel.kt b/vector/src/main/java/im/vector/app/core/epoxy/VectorEpoxyModel.kt index e9dd72a6f2..6142748bf4 100644 --- a/vector/src/main/java/im/vector/app/core/epoxy/VectorEpoxyModel.kt +++ b/vector/src/main/java/im/vector/app/core/epoxy/VectorEpoxyModel.kt @@ -17,9 +17,6 @@ package im.vector.app.core.epoxy import androidx.annotation.CallSuper -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.LifecycleRegistry import com.airbnb.epoxy.EpoxyModelWithHolder import com.airbnb.epoxy.VisibilityState import kotlinx.coroutines.CoroutineScope @@ -30,7 +27,7 @@ import kotlinx.coroutines.cancelChildren /** * EpoxyModelWithHolder which can listen to visibility state change */ -abstract class VectorEpoxyModel : EpoxyModelWithHolder(){ +abstract class VectorEpoxyModel : EpoxyModelWithHolder() { protected val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main) From a3ad8c5e2e5dc4c886540f09e645b6bb10668020 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Wed, 8 Dec 2021 11:29:26 +0000 Subject: [PATCH 044/632] removing non accessible softlogout2 activity - there's no manifest entry - the implementation looks like it delegates back to login 1, will look to add back as part of the FTUE changes --- .../features/navigation/DefaultNavigator.kt | 6 +- .../signout/soft/SoftLogoutActivity2.kt | 113 ------------------ 2 files changed, 1 insertion(+), 118 deletions(-) delete mode 100644 vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutActivity2.kt diff --git a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt index eacd8523cf..befc62d2ce 100644 --- a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt +++ b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt @@ -80,7 +80,6 @@ import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.VectorSettingsActivity import im.vector.app.features.share.SharedData import im.vector.app.features.signout.soft.SoftLogoutActivity -import im.vector.app.features.signout.soft.SoftLogoutActivity2 import im.vector.app.features.spaces.InviteRoomSpaceChooserBottomSheet import im.vector.app.features.spaces.SpaceExploreActivity import im.vector.app.features.spaces.SpacePreviewActivity @@ -128,10 +127,7 @@ class DefaultNavigator @Inject constructor( } override fun softLogout(context: Context) { - val intent = when (features.loginVersion()) { - VectorFeatures.LoginVersion.V1 -> SoftLogoutActivity.newIntent(context) - VectorFeatures.LoginVersion.V2 -> SoftLogoutActivity2.newIntent(context) - } + val intent = SoftLogoutActivity.newIntent(context) context.startActivity(intent) } diff --git a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutActivity2.kt b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutActivity2.kt deleted file mode 100644 index 8489b2baef..0000000000 --- a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutActivity2.kt +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright 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.signout.soft - -import android.content.Context -import android.content.Intent -import androidx.core.view.isVisible -import androidx.fragment.app.FragmentManager -import com.airbnb.mvrx.Success -import com.airbnb.mvrx.viewModel -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import dagger.hilt.android.AndroidEntryPoint -import im.vector.app.R -import im.vector.app.core.error.ErrorFormatter -import im.vector.app.core.extensions.replaceFragment -import im.vector.app.features.MainActivity -import im.vector.app.features.MainActivityArgs -import im.vector.app.features.login2.LoginActivity2 -import org.matrix.android.sdk.api.failure.GlobalError -import org.matrix.android.sdk.api.session.Session -import timber.log.Timber -import javax.inject.Inject - -/** - * In this screen, the user is viewing a message informing that he has been logged out - * Extends LoginActivity to get the login with SSO and forget password functionality for (nearly) free - * - * This is just a copy of SoftLogoutActivity2, which extends LoginActivity2 - */ -@AndroidEntryPoint -class SoftLogoutActivity2 : LoginActivity2() { - - private val softLogoutViewModel: SoftLogoutViewModel by viewModel() - - @Inject lateinit var session: Session - @Inject lateinit var errorFormatter: ErrorFormatter - - override fun initUiAndData() { - super.initUiAndData() - - softLogoutViewModel.onEach { - updateWithState(it) - } - - softLogoutViewModel.observeViewEvents { handleSoftLogoutViewEvents(it) } - } - - private fun handleSoftLogoutViewEvents(softLogoutViewEvents: SoftLogoutViewEvents) { - when (softLogoutViewEvents) { - is SoftLogoutViewEvents.Failure -> - showError(errorFormatter.toHumanReadable(softLogoutViewEvents.throwable)) - is SoftLogoutViewEvents.ErrorNotSameUser -> { - // Pop the backstack - supportFragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE) - - // And inform the user - showError(getString( - R.string.soft_logout_sso_not_same_user_error, - softLogoutViewEvents.currentUserId, - softLogoutViewEvents.newUserId) - ) - } - is SoftLogoutViewEvents.ClearData -> { - MainActivity.restartApp(this, MainActivityArgs(clearCredentials = true)) - } - } - } - - private fun showError(message: String) { - MaterialAlertDialogBuilder(this) - .setTitle(R.string.dialog_title_error) - .setMessage(message) - .setPositiveButton(R.string.ok, null) - .show() - } - - override fun addFirstFragment() { - replaceFragment(views.loginFragmentContainer, SoftLogoutFragment::class.java) - } - - private fun updateWithState(softLogoutViewState: SoftLogoutViewState) { - if (softLogoutViewState.asyncLoginAction is Success) { - MainActivity.restartApp(this, MainActivityArgs()) - } - - views.loginLoading.isVisible = softLogoutViewState.isLoading() - } - - companion object { - fun newIntent(context: Context): Intent { - return Intent(context, SoftLogoutActivity2::class.java) - } - } - - override fun handleInvalidToken(globalError: GlobalError.InvalidToken) { - // No op here - Timber.w("Ignoring invalid token global error") - } -} From 01d4a48b8b71820517ece88abb49969dbb7a6274 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Wed, 8 Dec 2021 11:31:01 +0000 Subject: [PATCH 045/632] adding ability to lazily create viewmodels - helpful when multiple view models are injected but not all are needed --- .../app/core/extensions/MavericksViewModel.kt | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 vector/src/main/java/im/vector/app/core/extensions/MavericksViewModel.kt diff --git a/vector/src/main/java/im/vector/app/core/extensions/MavericksViewModel.kt b/vector/src/main/java/im/vector/app/core/extensions/MavericksViewModel.kt new file mode 100644 index 0000000000..6120a84d7c --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/extensions/MavericksViewModel.kt @@ -0,0 +1,35 @@ +/* + * 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.core.extensions + +import androidx.activity.ComponentActivity +import com.airbnb.mvrx.ActivityViewModelContext +import com.airbnb.mvrx.Mavericks +import com.airbnb.mvrx.MavericksState +import com.airbnb.mvrx.MavericksViewModel +import com.airbnb.mvrx.MavericksViewModelProvider + +inline fun , reified S : MavericksState> ComponentActivity.lazyViewModel(): Lazy { + return lazy(mode = LazyThreadSafetyMode.NONE) { + MavericksViewModelProvider.get( + viewModelClass = VM::class.java, + stateClass = S::class.java, + viewModelContext = ActivityViewModelContext(this, intent.extras?.get(Mavericks.KEY_ARG)), + key = VM::class.java.name + ) + } +} From dae2e9988ffda803fcec0853f3f690bd4ae523f9 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Wed, 8 Dec 2021 11:33:35 +0000 Subject: [PATCH 046/632] moving the sso redirect parameter to the sso redirect activity for sharing --- .../im/vector/app/features/login/AbstractSSOLoginFragment.kt | 2 +- .../main/java/im/vector/app/features/login/LoginActivity.kt | 3 --- .../main/java/im/vector/app/features/login/LoginFragment.kt | 2 +- .../app/features/login/LoginSignUpSignInSelectionFragment.kt | 4 ++-- .../vector/app/features/login/SSORedirectRouterActivity.kt | 5 +++++ .../vector/app/features/login2/AbstractSSOLoginFragment2.kt | 3 ++- .../app/features/login2/LoginFragmentSignupUsername2.kt | 3 ++- .../im/vector/app/features/login2/LoginFragmentToAny2.kt | 3 ++- .../im/vector/app/features/login2/LoginSsoOnlyFragment2.kt | 3 ++- 9 files changed, 17 insertions(+), 11 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/login/AbstractSSOLoginFragment.kt b/vector/src/main/java/im/vector/app/features/login/AbstractSSOLoginFragment.kt index 8663b7c73f..b18df6c9cf 100644 --- a/vector/src/main/java/im/vector/app/features/login/AbstractSSOLoginFragment.kt +++ b/vector/src/main/java/im/vector/app/features/login/AbstractSSOLoginFragment.kt @@ -88,7 +88,7 @@ abstract class AbstractSSOLoginFragment : AbstractLoginFragmen if (state.loginMode.hasSso() && state.loginMode.ssoIdentityProviders().isNullOrEmpty()) { // in this case we can prefetch (not other cases for privacy concerns) loginViewModel.getSsoUrl( - redirectUrl = LoginActivity.VECTOR_REDIRECT_URL, + redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, deviceId = state.deviceId, providerId = null ) diff --git a/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt b/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt index c46dca27b3..5ab08ffff7 100644 --- a/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt +++ b/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt @@ -356,9 +356,6 @@ open class LoginActivity : VectorBaseActivity(), ToolbarCo private const val EXTRA_CONFIG = "EXTRA_CONFIG" - // Note that the domain can be displayed to the user for confirmation that he trusts it. So use a human readable string - const val VECTOR_REDIRECT_URL = "element://connect" - fun newIntent(context: Context, loginConfig: LoginConfig?): Intent { return Intent(context, LoginActivity::class.java).apply { putExtra(EXTRA_CONFIG, loginConfig) 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 9ca8a1dbec..da61d95997 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 @@ -200,7 +200,7 @@ class LoginFragment @Inject constructor() : AbstractSSOLoginFragment if (state.loginMode is LoginMode.Sso) { loginViewModel.getSsoUrl( - redirectUrl = LoginActivity.VECTOR_REDIRECT_URL, + redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, deviceId = state.deviceId, providerId = null ) diff --git a/vector/src/main/java/im/vector/app/features/login/SSORedirectRouterActivity.kt b/vector/src/main/java/im/vector/app/features/login/SSORedirectRouterActivity.kt index 29f8559362..19c549fd45 100644 --- a/vector/src/main/java/im/vector/app/features/login/SSORedirectRouterActivity.kt +++ b/vector/src/main/java/im/vector/app/features/login/SSORedirectRouterActivity.kt @@ -32,4 +32,9 @@ class SSORedirectRouterActivity : AppCompatActivity() { navigator.loginSSORedirect(this, intent.data) finish() } + + companion object { + // Note that the domain can be displayed to the user for confirmation that he trusts it. So use a human readable string + const val VECTOR_REDIRECT_URL = "element://connect" + } } diff --git a/vector/src/main/java/im/vector/app/features/login2/AbstractSSOLoginFragment2.kt b/vector/src/main/java/im/vector/app/features/login2/AbstractSSOLoginFragment2.kt index 43f301d9b4..8bc531b25d 100644 --- a/vector/src/main/java/im/vector/app/features/login2/AbstractSSOLoginFragment2.kt +++ b/vector/src/main/java/im/vector/app/features/login2/AbstractSSOLoginFragment2.kt @@ -24,6 +24,7 @@ import androidx.browser.customtabs.CustomTabsSession import androidx.viewbinding.ViewBinding import com.airbnb.mvrx.withState import im.vector.app.core.utils.openUrlInChromeCustomTab +import im.vector.app.features.login.SSORedirectRouterActivity import im.vector.app.features.login.hasSso import im.vector.app.features.login.ssoIdentityProviders @@ -90,7 +91,7 @@ abstract class AbstractSSOLoginFragment2 : AbstractLoginFragme if (state.loginMode.hasSso() && state.loginMode.ssoIdentityProviders().isNullOrEmpty()) { // in this case we can prefetch (not other cases for privacy concerns) loginViewModel.getSsoUrl( - redirectUrl = LoginActivity2.VECTOR_REDIRECT_URL, + redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, deviceId = state.deviceId, providerId = null ) diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSignupUsername2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSignupUsername2.kt index 51044ac153..f9917a4c31 100644 --- a/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSignupUsername2.kt +++ b/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSignupUsername2.kt @@ -30,6 +30,7 @@ import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.extensions.toReducedUrl import im.vector.app.databinding.FragmentLoginSignupUsername2Binding import im.vector.app.features.login.LoginMode +import im.vector.app.features.login.SSORedirectRouterActivity import im.vector.app.features.login.SocialLoginButtonsView import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map @@ -97,7 +98,7 @@ class LoginFragmentSignupUsername2 @Inject constructor() : AbstractSSOLoginFragm views.loginSocialLoginButtons.listener = object : SocialLoginButtonsView.InteractionListener { override fun onProviderSelected(id: String?) { loginViewModel.getSsoUrl( - redirectUrl = LoginActivity2.VECTOR_REDIRECT_URL, + redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, deviceId = state.deviceId, providerId = id ) diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginFragmentToAny2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginFragmentToAny2.kt index 48792da007..3fa0e6c549 100644 --- a/vector/src/main/java/im/vector/app/features/login2/LoginFragmentToAny2.kt +++ b/vector/src/main/java/im/vector/app/features/login2/LoginFragmentToAny2.kt @@ -31,6 +31,7 @@ import im.vector.app.core.extensions.hidePassword import im.vector.app.core.extensions.toReducedUrl import im.vector.app.databinding.FragmentLoginSigninToAny2Binding import im.vector.app.features.login.LoginMode +import im.vector.app.features.login.SSORedirectRouterActivity import im.vector.app.features.login.SocialLoginButtonsView import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.launchIn @@ -124,7 +125,7 @@ class LoginFragmentToAny2 @Inject constructor() : AbstractSSOLoginFragment2 loginViewModel.getSsoUrl( - redirectUrl = LoginActivity2.VECTOR_REDIRECT_URL, + redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, deviceId = state.deviceId, providerId = null ) From fd0e1e44c4388d16d492bf146f26b60365c31d8b Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Wed, 8 Dec 2021 11:54:21 +0000 Subject: [PATCH 047/632] renaming login version to variants to better describe the different flows --- vector/build.gradle | 2 +- .../debug/features/DebugFeaturesStateFactory.kt | 4 ++-- .../features/debug/features/DebugVectorFeatures.kt | 4 ++-- .../java/im/vector/app/features/VectorFeatures.kt | 10 +++++----- .../app/features/navigation/DefaultNavigator.kt | 12 ++++++------ 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/vector/build.gradle b/vector/build.gradle index 9f6c91c2b5..a3c9314e8d 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -140,7 +140,7 @@ android { buildConfigField "String", "BUILD_NUMBER", "\"${buildNumber}\"" resValue "string", "build_number", "\"${buildNumber}\"" - buildConfigField "im.vector.app.features.VectorFeatures.LoginVersion", "LOGIN_VERSION", "im.vector.app.features.VectorFeatures.LoginVersion.V1" + buildConfigField "im.vector.app.features.VectorFeatures.LoginVariant", "LOGIN_VARIANT", "im.vector.app.features.VectorFeatures.LoginVariant.LEGACY" buildConfigField "im.vector.app.features.crypto.keysrequest.OutboundSessionKeySharingStrategy", "outboundSessionKeySharingStrategy", "im.vector.app.features.crypto.keysrequest.OutboundSessionKeySharingStrategy.WhenTyping" 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 8d22fc599f..ca5d26aaeb 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 @@ -28,8 +28,8 @@ class DebugFeaturesStateFactory @Inject constructor( return FeaturesState(listOf( createEnumFeature( label = "Login version", - selection = debugFeatures.loginVersion(), - default = defaultFeatures.loginVersion() + selection = debugFeatures.loginVariant(), + default = defaultFeatures.loginVariant() ) )) } 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 0831609e4f..638509e76b 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 @@ -38,8 +38,8 @@ class DebugVectorFeatures( private val dataStore = context.dataStore - override fun loginVersion(): VectorFeatures.LoginVersion { - return readPreferences().getEnum() ?: vectorFeatures.loginVersion() + override fun loginVariant(): VectorFeatures.LoginVariant { + return readPreferences().getEnum() ?: vectorFeatures.loginVariant() } fun > hasEnumOverride(type: KClass) = readPreferences().containsEnum(type) 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 e106f7f75f..9453abe1db 100644 --- a/vector/src/main/java/im/vector/app/features/VectorFeatures.kt +++ b/vector/src/main/java/im/vector/app/features/VectorFeatures.kt @@ -20,11 +20,11 @@ import im.vector.app.BuildConfig interface VectorFeatures { - fun loginVersion(): LoginVersion + fun loginVariant(): LoginVariant - enum class LoginVersion { - V1, - V2 + enum class LoginVariant { + LEGACY, + FTUE_WIP } enum class NotificationSettingsVersion { @@ -34,5 +34,5 @@ interface VectorFeatures { } class DefaultVectorFeatures : VectorFeatures { - override fun loginVersion(): VectorFeatures.LoginVersion = BuildConfig.LOGIN_VERSION + override fun loginVariant(): VectorFeatures.LoginVariant = BuildConfig.LOGIN_VARIANT } diff --git a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt index befc62d2ce..b18604a13f 100644 --- a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt +++ b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt @@ -110,18 +110,18 @@ class DefaultNavigator @Inject constructor( ) : Navigator { override fun openLogin(context: Context, loginConfig: LoginConfig?, flags: Int) { - val intent = when (features.loginVersion()) { - VectorFeatures.LoginVersion.V1 -> LoginActivity.newIntent(context, loginConfig) - VectorFeatures.LoginVersion.V2 -> LoginActivity2.newIntent(context, loginConfig) + val intent = when (features.loginVariant()) { + VectorFeatures.LoginVariant.LEGACY -> LoginActivity.newIntent(context, loginConfig) + VectorFeatures.LoginVariant.FTUE_WIP -> LoginActivity2.newIntent(context, loginConfig) } intent.addFlags(flags) context.startActivity(intent) } override fun loginSSORedirect(context: Context, data: Uri?) { - val intent = when (features.loginVersion()) { - VectorFeatures.LoginVersion.V1 -> LoginActivity.redirectIntent(context, data) - VectorFeatures.LoginVersion.V2 -> LoginActivity2.redirectIntent(context, data) + val intent = when (features.loginVariant()) { + VectorFeatures.LoginVariant.LEGACY -> LoginActivity.redirectIntent(context, data) + VectorFeatures.LoginVariant.FTUE_WIP -> LoginActivity2.redirectIntent(context, data) } context.startActivity(intent) } From 74594d8fc3095f554f50a97c3339711cba793936 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Wed, 8 Dec 2021 12:06:29 +0000 Subject: [PATCH 048/632] porting the LoginActivty2 to a dynamic FTUE activity - supports switching between a copied legacy flow (DefaultFTUE) and the WIP variant - this will allow us to make iterative changes to the default ftue flow without affecting the legacy flow/forks --- vector/src/main/AndroidManifest.xml | 2 +- .../app/core/platform/VectorBaseActivity.kt | 2 +- .../im/vector/app/features/VectorFeatures.kt | 1 + .../app/features/ftue/DefaultFTUEVariant.kt | 367 ++++++++++++++++++ .../vector/app/features/ftue/FTUEActivity.kt | 85 ++++ .../app/features/ftue/FTUEVariantFactory.kt | 43 ++ .../FTUEWipVariant.kt} | 184 ++++----- .../login2/created/AccountCreatedFragment.kt | 4 +- .../features/navigation/DefaultNavigator.kt | 8 +- 9 files changed, 589 insertions(+), 107 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/ftue/DefaultFTUEVariant.kt create mode 100644 vector/src/main/java/im/vector/app/features/ftue/FTUEActivity.kt create mode 100644 vector/src/main/java/im/vector/app/features/ftue/FTUEVariantFactory.kt rename vector/src/main/java/im/vector/app/features/{login2/LoginActivity2.kt => ftue/FTUEWipVariant.kt} (70%) diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml index 6d3c6cdc51..869e81b042 100644 --- a/vector/src/main/AndroidManifest.xml +++ b/vector/src/main/AndroidManifest.xml @@ -137,7 +137,7 @@ android:windowSoftInputMode="adjustResize" /> diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt index 181bd8c6be..4c92d70dce 100644 --- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt +++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt @@ -104,7 +104,7 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver protected val viewModelProvider get() = ViewModelProvider(this, viewModelFactory) - protected fun VectorViewModel<*, *, T>.observeViewEvents(observer: (T) -> Unit) { + fun VectorViewModel<*, *, T>.observeViewEvents(observer: (T) -> Unit) { viewEvents .stream() .onEach { 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 9453abe1db..58594be293 100644 --- a/vector/src/main/java/im/vector/app/features/VectorFeatures.kt +++ b/vector/src/main/java/im/vector/app/features/VectorFeatures.kt @@ -24,6 +24,7 @@ interface VectorFeatures { enum class LoginVariant { LEGACY, + FTUE, FTUE_WIP } diff --git a/vector/src/main/java/im/vector/app/features/ftue/DefaultFTUEVariant.kt b/vector/src/main/java/im/vector/app/features/ftue/DefaultFTUEVariant.kt new file mode 100644 index 0000000000..98b1f98df0 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/ftue/DefaultFTUEVariant.kt @@ -0,0 +1,367 @@ +/* + * 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.ftue + +import android.content.Intent +import android.view.View +import android.view.ViewGroup +import androidx.core.view.ViewCompat +import androidx.core.view.children +import androidx.core.view.isVisible +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentManager +import androidx.fragment.app.FragmentTransaction +import com.airbnb.mvrx.withState +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import im.vector.app.R +import im.vector.app.core.extensions.POP_BACK_STACK_EXCLUSIVE +import im.vector.app.core.extensions.addFragment +import im.vector.app.core.extensions.addFragmentToBackstack +import im.vector.app.core.extensions.exhaustive +import im.vector.app.core.platform.VectorBaseActivity +import im.vector.app.databinding.ActivityLoginBinding +import im.vector.app.features.home.HomeActivity +import im.vector.app.features.login.LoginAction +import im.vector.app.features.login.LoginCaptchaFragment +import im.vector.app.features.login.LoginCaptchaFragmentArgument +import im.vector.app.features.login.LoginConfig +import im.vector.app.features.login.LoginFragment +import im.vector.app.features.login.LoginGenericTextInputFormFragment +import im.vector.app.features.login.LoginGenericTextInputFormFragmentArgument +import im.vector.app.features.login.LoginMode +import im.vector.app.features.login.LoginResetPasswordFragment +import im.vector.app.features.login.LoginResetPasswordMailConfirmationFragment +import im.vector.app.features.login.LoginResetPasswordSuccessFragment +import im.vector.app.features.login.LoginServerSelectionFragment +import im.vector.app.features.login.LoginServerUrlFormFragment +import im.vector.app.features.login.LoginSignUpSignInSelectionFragment +import im.vector.app.features.login.LoginSplashFragment +import im.vector.app.features.login.LoginViewEvents +import im.vector.app.features.login.LoginViewModel +import im.vector.app.features.login.LoginViewState +import im.vector.app.features.login.LoginWaitForEmailFragment +import im.vector.app.features.login.LoginWaitForEmailFragmentArgument +import im.vector.app.features.login.LoginWebFragment +import im.vector.app.features.login.ServerType +import im.vector.app.features.login.SignMode +import im.vector.app.features.login.TextInputFormFragmentMode +import im.vector.app.features.login.isSupported +import im.vector.app.features.login.terms.LoginTermsFragment +import im.vector.app.features.login.terms.LoginTermsFragmentArgument +import im.vector.app.features.login.terms.toLocalizedLoginTerms +import org.matrix.android.sdk.api.auth.registration.FlowResult +import org.matrix.android.sdk.api.auth.registration.Stage +import org.matrix.android.sdk.api.extensions.tryOrNull + +private const val FRAGMENT_REGISTRATION_STAGE_TAG = "FRAGMENT_REGISTRATION_STAGE_TAG" +private const val FRAGMENT_LOGIN_TAG = "FRAGMENT_LOGIN_TAG" + +class DefaultFTUEVariant( + private val views: ActivityLoginBinding, + private val loginViewModel: LoginViewModel, + private val activity: VectorBaseActivity, + private val supportFragmentManager: FragmentManager +) : FTUEVariant { + + private val enterAnim = R.anim.enter_fade_in + private val exitAnim = R.anim.exit_fade_out + + private val popEnterAnim = R.anim.no_anim + private val popExitAnim = R.anim.exit_fade_out + + private val topFragment: Fragment? + get() = supportFragmentManager.findFragmentById(views.loginFragmentContainer.id) + + private val commonOption: (FragmentTransaction) -> Unit = { ft -> + // Find the loginLogo on the current Fragment, this should not return null + (topFragment?.view as? ViewGroup) + // Find findViewById does not work, I do not know why + // findViewById(R.id.loginLogo) + ?.children + ?.firstOrNull { it.id == R.id.loginLogo } + ?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") } + ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim) + } + + override fun initUiAndData(isFirstCreation: Boolean) { + if (isFirstCreation) { + addFirstFragment() + } + + with(activity) { + loginViewModel.onEach { + updateWithState(it) + } + loginViewModel.observeViewEvents { handleLoginViewEvents(it) } + } + + // Get config extra + val loginConfig = activity.intent.getParcelableExtra(FTUEActivity.EXTRA_CONFIG) + if (isFirstCreation) { + loginViewModel.handle(LoginAction.InitWith(loginConfig)) + } + } + + override fun setIsLoading(isLoading: Boolean) { + // do nothing + } + + private fun addFirstFragment() { + activity.addFragment(views.loginFragmentContainer, LoginSplashFragment::class.java) + } + + private fun handleLoginViewEvents(loginViewEvents: LoginViewEvents) { + when (loginViewEvents) { + is LoginViewEvents.RegistrationFlowResult -> { + // Check that all flows are supported by the application + if (loginViewEvents.flowResult.missingStages.any { !it.isSupported() }) { + // Display a popup to propose use web fallback + onRegistrationStageNotSupported() + } else { + if (loginViewEvents.isRegistrationStarted) { + // Go on with registration flow + handleRegistrationNavigation(loginViewEvents.flowResult) + } else { + // First ask for login and password + // I add a tag to indicate that this fragment is a registration stage. + // This way it will be automatically popped in when starting the next registration stage + activity.addFragmentToBackstack(views.loginFragmentContainer, + LoginFragment::class.java, + tag = FRAGMENT_REGISTRATION_STAGE_TAG, + option = commonOption + ) + } + } + } + is LoginViewEvents.OutdatedHomeserver -> { + MaterialAlertDialogBuilder(activity) + .setTitle(R.string.login_error_outdated_homeserver_title) + .setMessage(R.string.login_error_outdated_homeserver_warning_content) + .setPositiveButton(R.string.ok, null) + .show() + Unit + } + is LoginViewEvents.OpenServerSelection -> + activity.addFragmentToBackstack(views.loginFragmentContainer, + LoginServerSelectionFragment::class.java, + option = { ft -> + activity.findViewById(R.id.loginSplashLogo)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") } + // Disable transition of text + // findViewById(R.id.loginSplashTitle)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") } + // No transition here now actually + // findViewById(R.id.loginSplashSubmit)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") } + // TODO Disabled because it provokes a flickering + // ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim) + }) + is LoginViewEvents.OnServerSelectionDone -> onServerSelectionDone(loginViewEvents) + is LoginViewEvents.OnSignModeSelected -> onSignModeSelected(loginViewEvents) + is LoginViewEvents.OnLoginFlowRetrieved -> + activity.addFragmentToBackstack(views.loginFragmentContainer, + LoginSignUpSignInSelectionFragment::class.java, + option = commonOption) + is LoginViewEvents.OnWebLoginError -> onWebLoginError(loginViewEvents) + is LoginViewEvents.OnForgetPasswordClicked -> + activity.addFragmentToBackstack(views.loginFragmentContainer, + LoginResetPasswordFragment::class.java, + option = commonOption) + is LoginViewEvents.OnResetPasswordSendThreePidDone -> { + supportFragmentManager.popBackStack(FRAGMENT_LOGIN_TAG, POP_BACK_STACK_EXCLUSIVE) + activity.addFragmentToBackstack(views.loginFragmentContainer, + LoginResetPasswordMailConfirmationFragment::class.java, + option = commonOption) + } + is LoginViewEvents.OnResetPasswordMailConfirmationSuccess -> { + supportFragmentManager.popBackStack(FRAGMENT_LOGIN_TAG, POP_BACK_STACK_EXCLUSIVE) + activity.addFragmentToBackstack(views.loginFragmentContainer, + LoginResetPasswordSuccessFragment::class.java, + option = commonOption) + } + is LoginViewEvents.OnResetPasswordMailConfirmationSuccessDone -> { + // Go back to the login fragment + supportFragmentManager.popBackStack(FRAGMENT_LOGIN_TAG, POP_BACK_STACK_EXCLUSIVE) + } + is LoginViewEvents.OnSendEmailSuccess -> { + // Pop the enter email Fragment + supportFragmentManager.popBackStack(FRAGMENT_REGISTRATION_STAGE_TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE) + activity.addFragmentToBackstack(views.loginFragmentContainer, + LoginWaitForEmailFragment::class.java, + LoginWaitForEmailFragmentArgument(loginViewEvents.email), + tag = FRAGMENT_REGISTRATION_STAGE_TAG, + option = commonOption) + } + is LoginViewEvents.OnSendMsisdnSuccess -> { + // Pop the enter Msisdn Fragment + supportFragmentManager.popBackStack(FRAGMENT_REGISTRATION_STAGE_TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE) + activity.addFragmentToBackstack(views.loginFragmentContainer, + LoginGenericTextInputFormFragment::class.java, + LoginGenericTextInputFormFragmentArgument(TextInputFormFragmentMode.ConfirmMsisdn, true, loginViewEvents.msisdn), + tag = FRAGMENT_REGISTRATION_STAGE_TAG, + option = commonOption) + } + is LoginViewEvents.Failure, + is LoginViewEvents.Loading -> + // This is handled by the Fragments + Unit + }.exhaustive + } + + private fun updateWithState(loginViewState: LoginViewState) { + if (loginViewState.isUserLogged()) { + val intent = HomeActivity.newIntent( + activity, + accountCreation = loginViewState.signMode == SignMode.SignUp + ) + activity.startActivity(intent) + activity.finish() + return + } + + // Loading + views.loginLoading.isVisible = loginViewState.isLoading() + } + + private fun onWebLoginError(onWebLoginError: LoginViewEvents.OnWebLoginError) { + // Pop the backstack + supportFragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE) + + // And inform the user + MaterialAlertDialogBuilder(activity) + .setTitle(R.string.dialog_title_error) + .setMessage(activity.getString(R.string.login_sso_error_message, onWebLoginError.description, onWebLoginError.errorCode)) + .setPositiveButton(R.string.ok, null) + .show() + } + + private fun onServerSelectionDone(loginViewEvents: LoginViewEvents.OnServerSelectionDone) { + when (loginViewEvents.serverType) { + ServerType.MatrixOrg -> Unit // In this case, we wait for the login flow + ServerType.EMS, + ServerType.Other -> activity.addFragmentToBackstack(views.loginFragmentContainer, + LoginServerUrlFormFragment::class.java, + option = commonOption) + ServerType.Unknown -> Unit /* Should not happen */ + } + } + + private fun onSignModeSelected(loginViewEvents: LoginViewEvents.OnSignModeSelected) = withState(loginViewModel) { state -> + // state.signMode could not be ready yet. So use value from the ViewEvent + when (loginViewEvents.signMode) { + SignMode.Unknown -> error("Sign mode has to be set before calling this method") + SignMode.SignUp -> { + // This is managed by the LoginViewEvents + } + SignMode.SignIn -> { + // It depends on the LoginMode + when (state.loginMode) { + LoginMode.Unknown, + is LoginMode.Sso -> error("Developer error") + is LoginMode.SsoAndPassword, + LoginMode.Password -> activity.addFragmentToBackstack(views.loginFragmentContainer, + LoginFragment::class.java, + tag = FRAGMENT_LOGIN_TAG, + option = commonOption) + LoginMode.Unsupported -> onLoginModeNotSupported(state.loginModeSupportedTypes) + }.exhaustive + } + SignMode.SignInWithMatrixId -> activity.addFragmentToBackstack(views.loginFragmentContainer, + LoginFragment::class.java, + tag = FRAGMENT_LOGIN_TAG, + option = commonOption) + }.exhaustive + } + + /** + * Handle the SSO redirection here + */ + override fun onNewIntent(intent: Intent?) { + intent?.data + ?.let { tryOrNull { it.getQueryParameter("loginToken") } } + ?.let { loginViewModel.handle(LoginAction.LoginWithToken(it)) } + } + + private fun onRegistrationStageNotSupported() { + MaterialAlertDialogBuilder(activity) + .setTitle(R.string.app_name) + .setMessage(activity.getString(R.string.login_registration_not_supported)) + .setPositiveButton(R.string.yes) { _, _ -> + activity.addFragmentToBackstack(views.loginFragmentContainer, + LoginWebFragment::class.java, + option = commonOption) + } + .setNegativeButton(R.string.no, null) + .show() + } + + private fun onLoginModeNotSupported(supportedTypes: List) { + MaterialAlertDialogBuilder(activity) + .setTitle(R.string.app_name) + .setMessage(activity.getString(R.string.login_mode_not_supported, supportedTypes.joinToString { "'$it'" })) + .setPositiveButton(R.string.yes) { _, _ -> + activity.addFragmentToBackstack(views.loginFragmentContainer, + LoginWebFragment::class.java, + option = commonOption) + } + .setNegativeButton(R.string.no, null) + .show() + } + + private fun handleRegistrationNavigation(flowResult: FlowResult) { + // Complete all mandatory stages first + val mandatoryStage = flowResult.missingStages.firstOrNull { it.mandatory } + + if (mandatoryStage != null) { + doStage(mandatoryStage) + } else { + // Consider optional stages + val optionalStage = flowResult.missingStages.firstOrNull { !it.mandatory && it !is Stage.Dummy } + if (optionalStage == null) { + // Should not happen... + } else { + doStage(optionalStage) + } + } + } + + private fun doStage(stage: Stage) { + // Ensure there is no fragment for registration stage in the backstack + supportFragmentManager.popBackStack(FRAGMENT_REGISTRATION_STAGE_TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE) + + when (stage) { + is Stage.ReCaptcha -> activity.addFragmentToBackstack(views.loginFragmentContainer, + LoginCaptchaFragment::class.java, + LoginCaptchaFragmentArgument(stage.publicKey), + tag = FRAGMENT_REGISTRATION_STAGE_TAG, + option = commonOption) + is Stage.Email -> activity.addFragmentToBackstack(views.loginFragmentContainer, + LoginGenericTextInputFormFragment::class.java, + LoginGenericTextInputFormFragmentArgument(TextInputFormFragmentMode.SetEmail, stage.mandatory), + tag = FRAGMENT_REGISTRATION_STAGE_TAG, + option = commonOption) + is Stage.Msisdn -> activity.addFragmentToBackstack(views.loginFragmentContainer, + LoginGenericTextInputFormFragment::class.java, + LoginGenericTextInputFormFragmentArgument(TextInputFormFragmentMode.SetMsisdn, stage.mandatory), + tag = FRAGMENT_REGISTRATION_STAGE_TAG, + option = commonOption) + is Stage.Terms -> activity.addFragmentToBackstack(views.loginFragmentContainer, + LoginTermsFragment::class.java, + LoginTermsFragmentArgument(stage.policies.toLocalizedLoginTerms(activity.getString(R.string.resources_language))), + tag = FRAGMENT_REGISTRATION_STAGE_TAG, + option = commonOption) + else -> Unit // Should not happen + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/ftue/FTUEActivity.kt b/vector/src/main/java/im/vector/app/features/ftue/FTUEActivity.kt new file mode 100644 index 0000000000..805e39c48d --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/ftue/FTUEActivity.kt @@ -0,0 +1,85 @@ +/* + * 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.ftue + +import android.content.Context +import android.content.Intent +import android.net.Uri +import com.google.android.material.appbar.MaterialToolbar +import dagger.hilt.android.AndroidEntryPoint +import im.vector.app.core.extensions.lazyViewModel +import im.vector.app.core.platform.ToolbarConfigurable +import im.vector.app.core.platform.VectorBaseActivity +import im.vector.app.core.platform.lifecycleAwareLazy +import im.vector.app.databinding.ActivityLoginBinding +import im.vector.app.features.login.LoginConfig +import im.vector.app.features.pin.UnlockedActivity +import javax.inject.Inject + +@AndroidEntryPoint +class FTUEActivity : VectorBaseActivity(), ToolbarConfigurable, UnlockedActivity { + + private val ftueVariant by lifecycleAwareLazy { + ftueVariantFactory.create(this, loginViewModel = lazyViewModel(), loginViewModel2 = lazyViewModel()) + } + + @Inject lateinit var ftueVariantFactory: FTUEVariantFactory + + override fun getBinding() = ActivityLoginBinding.inflate(layoutInflater) + + override fun getCoordinatorLayout() = views.coordinatorLayout + + override fun configure(toolbar: MaterialToolbar) { + configureToolbar(toolbar) + } + + override fun onNewIntent(intent: Intent?) { + super.onNewIntent(intent) + ftueVariant.onNewIntent(intent) + } + + override fun initUiAndData() { + ftueVariant.initUiAndData(isFirstCreation()) + } + + // Hack for AccountCreatedFragment + fun setIsLoading(isLoading: Boolean) { + ftueVariant.setIsLoading(isLoading) + } + + companion object { + const val EXTRA_CONFIG = "EXTRA_CONFIG" + + fun newIntent(context: Context, loginConfig: LoginConfig?): Intent { + return Intent(context, FTUEActivity::class.java).apply { + putExtra(EXTRA_CONFIG, loginConfig) + } + } + + fun redirectIntent(context: Context, data: Uri?): Intent { + return Intent(context, FTUEActivity::class.java).apply { + setData(data) + } + } + } +} + +interface FTUEVariant { + fun onNewIntent(intent: Intent?) + fun initUiAndData(isFirstCreation: Boolean) + fun setIsLoading(isLoading: Boolean) +} diff --git a/vector/src/main/java/im/vector/app/features/ftue/FTUEVariantFactory.kt b/vector/src/main/java/im/vector/app/features/ftue/FTUEVariantFactory.kt new file mode 100644 index 0000000000..7efd6023fe --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/ftue/FTUEVariantFactory.kt @@ -0,0 +1,43 @@ +/* + * 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.ftue + +import im.vector.app.features.VectorFeatures +import im.vector.app.features.login.LoginViewModel +import im.vector.app.features.login2.LoginViewModel2 +import javax.inject.Inject + +class FTUEVariantFactory @Inject constructor( + private val vectorFeatures: VectorFeatures, +) { + + fun create(activity: FTUEActivity, loginViewModel: Lazy, loginViewModel2: Lazy) = when (vectorFeatures.loginVariant()) { + VectorFeatures.LoginVariant.LEGACY -> error("Legacy is not supported by the FTUE") + VectorFeatures.LoginVariant.FTUE -> DefaultFTUEVariant( + views = activity.getBinding(), + loginViewModel = loginViewModel.value, + activity = activity, + supportFragmentManager = activity.supportFragmentManager + ) + VectorFeatures.LoginVariant.FTUE_WIP -> FTUEWipVariant( + views = activity.getBinding(), + loginViewModel = loginViewModel2.value, + activity = activity, + supportFragmentManager = activity.supportFragmentManager + ) + } +} diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginActivity2.kt b/vector/src/main/java/im/vector/app/features/ftue/FTUEWipVariant.kt similarity index 70% rename from vector/src/main/java/im/vector/app/features/login2/LoginActivity2.kt rename to vector/src/main/java/im/vector/app/features/ftue/FTUEWipVariant.kt index ce9d9f762e..c1fc49db00 100644 --- a/vector/src/main/java/im/vector/app/features/login2/LoginActivity2.kt +++ b/vector/src/main/java/im/vector/app/features/ftue/FTUEWipVariant.kt @@ -1,11 +1,11 @@ /* - * Copyright 2019 New Vector Ltd + * 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 + * 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, @@ -14,11 +14,9 @@ * limitations under the License. */ -package im.vector.app.features.login2 +package im.vector.app.features.ftue -import android.content.Context import android.content.Intent -import android.net.Uri import android.view.View import android.view.ViewGroup import androidx.core.view.ViewCompat @@ -27,17 +25,13 @@ import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentTransaction -import com.airbnb.mvrx.viewModel -import com.google.android.material.appbar.MaterialToolbar import com.google.android.material.dialog.MaterialAlertDialogBuilder -import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.POP_BACK_STACK_EXCLUSIVE import im.vector.app.core.extensions.addFragment import im.vector.app.core.extensions.addFragmentToBackstack import im.vector.app.core.extensions.exhaustive import im.vector.app.core.extensions.resetBackstack -import im.vector.app.core.platform.ToolbarConfigurable import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.databinding.ActivityLoginBinding import im.vector.app.features.home.HomeActivity @@ -49,20 +43,41 @@ import im.vector.app.features.login.TextInputFormFragmentMode import im.vector.app.features.login.isSupported import im.vector.app.features.login.terms.LoginTermsFragmentArgument import im.vector.app.features.login.terms.toLocalizedLoginTerms +import im.vector.app.features.login2.LoginAction2 +import im.vector.app.features.login2.LoginCaptchaFragment2 +import im.vector.app.features.login2.LoginFragmentSigninPassword2 +import im.vector.app.features.login2.LoginFragmentSigninUsername2 +import im.vector.app.features.login2.LoginFragmentSignupPassword2 +import im.vector.app.features.login2.LoginFragmentSignupUsername2 +import im.vector.app.features.login2.LoginFragmentToAny2 +import im.vector.app.features.login2.LoginGenericTextInputFormFragment2 +import im.vector.app.features.login2.LoginResetPasswordFragment2 +import im.vector.app.features.login2.LoginResetPasswordMailConfirmationFragment2 +import im.vector.app.features.login2.LoginResetPasswordSuccessFragment2 +import im.vector.app.features.login2.LoginServerSelectionFragment2 +import im.vector.app.features.login2.LoginServerUrlFormFragment2 +import im.vector.app.features.login2.LoginSplashSignUpSignInSelectionFragment2 +import im.vector.app.features.login2.LoginSsoOnlyFragment2 +import im.vector.app.features.login2.LoginViewEvents2 +import im.vector.app.features.login2.LoginViewModel2 +import im.vector.app.features.login2.LoginViewState2 +import im.vector.app.features.login2.LoginWaitForEmailFragment2 +import im.vector.app.features.login2.LoginWebFragment2 import im.vector.app.features.login2.created.AccountCreatedFragment import im.vector.app.features.login2.terms.LoginTermsFragment2 -import im.vector.app.features.pin.UnlockedActivity import org.matrix.android.sdk.api.auth.registration.FlowResult import org.matrix.android.sdk.api.auth.registration.Stage import org.matrix.android.sdk.api.extensions.tryOrNull -/** - * The LoginActivity manages the fragment navigation and also display the loading View - */ -@AndroidEntryPoint -open class LoginActivity2 : VectorBaseActivity(), ToolbarConfigurable, UnlockedActivity { +private const val FRAGMENT_REGISTRATION_STAGE_TAG = "FRAGMENT_REGISTRATION_STAGE_TAG" +private const val FRAGMENT_LOGIN_TAG = "FRAGMENT_LOGIN_TAG" - private val loginViewModel: LoginViewModel2 by viewModel() +class FTUEWipVariant( + private val views: ActivityLoginBinding, + private val loginViewModel: LoginViewModel2, + private val activity: VectorBaseActivity, + private val supportFragmentManager: FragmentManager +) : FTUEVariant { private val enterAnim = R.anim.enter_fade_in private val exitAnim = R.anim.exit_fade_out @@ -76,39 +91,36 @@ open class LoginActivity2 : VectorBaseActivity(), ToolbarC private val commonOption: (FragmentTransaction) -> Unit = { ft -> // Find the loginLogo on the current Fragment, this should not return null (topFragment?.view as? ViewGroup) - // Find findViewById does not work, I do not know why - // findViewById(R.id.loginLogo) + // Find activity.findViewById does not work, I do not know why + // activity.findViewById(views.loginLogo) ?.children ?.firstOrNull { it.id == R.id.loginLogo } ?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") } ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim) } - final override fun getBinding() = ActivityLoginBinding.inflate(layoutInflater) - - override fun getCoordinatorLayout() = views.coordinatorLayout - - override fun initUiAndData() { - if (isFirstCreation()) { + override fun initUiAndData(isFirstCreation: Boolean) { + if (isFirstCreation) { addFirstFragment() } - loginViewModel.onEach { - updateWithState(it) + with(activity) { + loginViewModel.onEach { + updateWithState(it) + } + loginViewModel.observeViewEvents { handleLoginViewEvents(it) } } - loginViewModel.observeViewEvents { handleLoginViewEvents(it) } - // Get config extra - val loginConfig = intent.getParcelableExtra(EXTRA_CONFIG) - if (isFirstCreation()) { + val loginConfig = activity.intent.getParcelableExtra(FTUEActivity.EXTRA_CONFIG) + if (isFirstCreation) { // TODO Check this loginViewModel.handle(LoginAction2.InitWith(loginConfig)) } } - protected open fun addFirstFragment() { - addFragment(views.loginFragmentContainer, LoginSplashSignUpSignInSelectionFragment2::class.java) + private fun addFirstFragment() { + activity.addFragment(views.loginFragmentContainer, LoginSplashSignUpSignInSelectionFragment2::class.java) } private fun handleLoginViewEvents(event: LoginViewEvents2) { @@ -127,7 +139,7 @@ open class LoginActivity2 : VectorBaseActivity(), ToolbarC // First ask for login and password // I add a tag to indicate that this fragment is a registration stage. // This way it will be automatically popped in when starting the next registration stage - addFragmentToBackstack(views.loginFragmentContainer, + activity.addFragmentToBackstack(views.loginFragmentContainer, LoginFragment2::class.java, tag = FRAGMENT_REGISTRATION_STAGE_TAG, option = commonOption @@ -138,7 +150,7 @@ open class LoginActivity2 : VectorBaseActivity(), ToolbarC } } is LoginViewEvents2.OutdatedHomeserver -> { - MaterialAlertDialogBuilder(this) + MaterialAlertDialogBuilder(activity) .setTitle(R.string.login_error_outdated_homeserver_title) .setMessage(R.string.login_error_outdated_homeserver_warning_content) .setPositiveButton(R.string.ok, null) @@ -146,54 +158,54 @@ open class LoginActivity2 : VectorBaseActivity(), ToolbarC Unit } is LoginViewEvents2.OpenServerSelection -> - addFragmentToBackstack(views.loginFragmentContainer, + activity.addFragmentToBackstack(views.loginFragmentContainer, LoginServerSelectionFragment2::class.java, option = { ft -> - findViewById(R.id.loginSplashLogo)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") } + activity.findViewById(R.id.loginSplashLogo)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") } // Disable transition of text - // findViewById(R.id.loginSplashTitle)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") } + // activity.findViewById(views.loginSplashTitle)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") } // No transition here now actually - // findViewById(R.id.loginSplashSubmit)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") } + // activity.findViewById(views.loginSplashSubmit)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") } // TODO Disabled because it provokes a flickering // ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim) }) is LoginViewEvents2.OpenHomeServerUrlFormScreen -> { - addFragmentToBackstack(views.loginFragmentContainer, + activity.addFragmentToBackstack(views.loginFragmentContainer, LoginServerUrlFormFragment2::class.java, option = commonOption) } is LoginViewEvents2.OpenSignInEnterIdentifierScreen -> { - addFragmentToBackstack(views.loginFragmentContainer, + activity.addFragmentToBackstack(views.loginFragmentContainer, LoginFragmentSigninUsername2::class.java, option = { ft -> - findViewById(R.id.loginSplashLogo)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") } + activity.findViewById(R.id.loginSplashLogo)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") } // Disable transition of text - // findViewById(R.id.loginSplashTitle)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") } + // activity.findViewById(views.loginSplashTitle)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") } // No transition here now actually - // findViewById(R.id.loginSplashSubmit)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") } + // activity.findViewById(views.loginSplashSubmit)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") } // TODO Disabled because it provokes a flickering // ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim) }) } is LoginViewEvents2.OpenSsoOnlyScreen -> { - addFragmentToBackstack(views.loginFragmentContainer, + activity.addFragmentToBackstack(views.loginFragmentContainer, LoginSsoOnlyFragment2::class.java, option = commonOption) } is LoginViewEvents2.OnWebLoginError -> onWebLoginError(event) is LoginViewEvents2.OpenResetPasswordScreen -> - addFragmentToBackstack(views.loginFragmentContainer, + activity.addFragmentToBackstack(views.loginFragmentContainer, LoginResetPasswordFragment2::class.java, option = commonOption) is LoginViewEvents2.OnResetPasswordSendThreePidDone -> { supportFragmentManager.popBackStack(FRAGMENT_LOGIN_TAG, POP_BACK_STACK_EXCLUSIVE) - addFragmentToBackstack(views.loginFragmentContainer, + activity.addFragmentToBackstack(views.loginFragmentContainer, LoginResetPasswordMailConfirmationFragment2::class.java, option = commonOption) } is LoginViewEvents2.OnResetPasswordMailConfirmationSuccess -> { supportFragmentManager.popBackStack(FRAGMENT_LOGIN_TAG, POP_BACK_STACK_EXCLUSIVE) - addFragmentToBackstack(views.loginFragmentContainer, + activity.addFragmentToBackstack(views.loginFragmentContainer, LoginResetPasswordSuccessFragment2::class.java, option = commonOption) } @@ -202,37 +214,37 @@ open class LoginActivity2 : VectorBaseActivity(), ToolbarC supportFragmentManager.popBackStack(FRAGMENT_LOGIN_TAG, POP_BACK_STACK_EXCLUSIVE) } is LoginViewEvents2.OnSendEmailSuccess -> - addFragmentToBackstack(views.loginFragmentContainer, + activity.addFragmentToBackstack(views.loginFragmentContainer, LoginWaitForEmailFragment2::class.java, LoginWaitForEmailFragmentArgument(event.email), tag = FRAGMENT_REGISTRATION_STAGE_TAG, option = commonOption) is LoginViewEvents2.OpenSigninPasswordScreen -> { - addFragmentToBackstack(views.loginFragmentContainer, + activity.addFragmentToBackstack(views.loginFragmentContainer, LoginFragmentSigninPassword2::class.java, tag = FRAGMENT_LOGIN_TAG, option = commonOption) } is LoginViewEvents2.OpenSignupPasswordScreen -> { - addFragmentToBackstack(views.loginFragmentContainer, + activity.addFragmentToBackstack(views.loginFragmentContainer, LoginFragmentSignupPassword2::class.java, tag = FRAGMENT_REGISTRATION_STAGE_TAG, option = commonOption) } is LoginViewEvents2.OpenSignUpChooseUsernameScreen -> { - addFragmentToBackstack(views.loginFragmentContainer, + activity.addFragmentToBackstack(views.loginFragmentContainer, LoginFragmentSignupUsername2::class.java, tag = FRAGMENT_REGISTRATION_STAGE_TAG, option = commonOption) } is LoginViewEvents2.OpenSignInWithAnythingScreen -> { - addFragmentToBackstack(views.loginFragmentContainer, + activity.addFragmentToBackstack(views.loginFragmentContainer, LoginFragmentToAny2::class.java, tag = FRAGMENT_LOGIN_TAG, option = commonOption) } is LoginViewEvents2.OnSendMsisdnSuccess -> - addFragmentToBackstack(views.loginFragmentContainer, + activity.addFragmentToBackstack(views.loginFragmentContainer, LoginGenericTextInputFormFragment2::class.java, LoginGenericTextInputFormFragmentArgument(TextInputFormFragmentMode.ConfirmMsisdn, true, event.msisdn), tag = FRAGMENT_REGISTRATION_STAGE_TAG, @@ -250,14 +262,14 @@ open class LoginActivity2 : VectorBaseActivity(), ToolbarC private fun handleCancelRegistration() { // Cleanup the back stack - resetBackstack() + activity.resetBackstack() } private fun handleOnSessionCreated(event: LoginViewEvents2.OnSessionCreated) { if (event.newAccount) { // Propose to set avatar and display name // Back on this Fragment will finish the Activity - addFragmentToBackstack(views.loginFragmentContainer, + activity.addFragmentToBackstack(views.loginFragmentContainer, AccountCreatedFragment::class.java, option = commonOption) } else { @@ -267,11 +279,11 @@ open class LoginActivity2 : VectorBaseActivity(), ToolbarC private fun terminate(newAccount: Boolean) { val intent = HomeActivity.newIntent( - this, + activity, accountCreation = newAccount ) - startActivity(intent) - finish() + activity.startActivity(intent) + activity.finish() } private fun updateWithState(LoginViewState2: LoginViewState2) { @@ -280,7 +292,7 @@ open class LoginActivity2 : VectorBaseActivity(), ToolbarC } // Hack for AccountCreatedFragment - fun setIsLoading(isLoading: Boolean) { + override fun setIsLoading(isLoading: Boolean) { views.loginLoading.isVisible = isLoading } @@ -289,9 +301,9 @@ open class LoginActivity2 : VectorBaseActivity(), ToolbarC supportFragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE) // And inform the user - MaterialAlertDialogBuilder(this) + MaterialAlertDialogBuilder(activity) .setTitle(R.string.dialog_title_error) - .setMessage(getString(R.string.login_sso_error_message, onWebLoginError.description, onWebLoginError.errorCode)) + .setMessage(activity.getString(R.string.login_sso_error_message, onWebLoginError.description, onWebLoginError.errorCode)) .setPositiveButton(R.string.ok, null) .show() } @@ -300,19 +312,17 @@ open class LoginActivity2 : VectorBaseActivity(), ToolbarC * Handle the SSO redirection here */ override fun onNewIntent(intent: Intent?) { - super.onNewIntent(intent) - intent?.data ?.let { tryOrNull { it.getQueryParameter("loginToken") } } ?.let { loginViewModel.handle(LoginAction2.LoginWithToken(it)) } } private fun onRegistrationStageNotSupported() { - MaterialAlertDialogBuilder(this) + MaterialAlertDialogBuilder(activity) .setTitle(R.string.app_name) - .setMessage(getString(R.string.login_registration_not_supported)) + .setMessage(activity.getString(R.string.login_registration_not_supported)) .setPositiveButton(R.string.yes) { _, _ -> - addFragmentToBackstack(views.loginFragmentContainer, + activity.addFragmentToBackstack(views.loginFragmentContainer, LoginWebFragment2::class.java, option = commonOption) } @@ -321,11 +331,11 @@ open class LoginActivity2 : VectorBaseActivity(), ToolbarC } private fun onLoginModeNotSupported(supportedTypes: List) { - MaterialAlertDialogBuilder(this) + MaterialAlertDialogBuilder(activity) .setTitle(R.string.app_name) - .setMessage(getString(R.string.login_mode_not_supported, supportedTypes.joinToString { "'$it'" })) + .setMessage(activity.getString(R.string.login_mode_not_supported, supportedTypes.joinToString { "'$it'" })) .setPositiveButton(R.string.yes) { _, _ -> - addFragmentToBackstack(views.loginFragmentContainer, + activity.addFragmentToBackstack(views.loginFragmentContainer, LoginWebFragment2::class.java, option = commonOption) } @@ -355,53 +365,27 @@ open class LoginActivity2 : VectorBaseActivity(), ToolbarC supportFragmentManager.popBackStack(FRAGMENT_REGISTRATION_STAGE_TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE) when (stage) { - is Stage.ReCaptcha -> addFragmentToBackstack(views.loginFragmentContainer, + is Stage.ReCaptcha -> activity.addFragmentToBackstack(views.loginFragmentContainer, LoginCaptchaFragment2::class.java, LoginCaptchaFragmentArgument(stage.publicKey), tag = FRAGMENT_REGISTRATION_STAGE_TAG, option = commonOption) - is Stage.Email -> addFragmentToBackstack(views.loginFragmentContainer, + is Stage.Email -> activity.addFragmentToBackstack(views.loginFragmentContainer, LoginGenericTextInputFormFragment2::class.java, LoginGenericTextInputFormFragmentArgument(TextInputFormFragmentMode.SetEmail, stage.mandatory), tag = FRAGMENT_REGISTRATION_STAGE_TAG, option = commonOption) - is Stage.Msisdn -> addFragmentToBackstack(views.loginFragmentContainer, + is Stage.Msisdn -> activity.addFragmentToBackstack(views.loginFragmentContainer, LoginGenericTextInputFormFragment2::class.java, LoginGenericTextInputFormFragmentArgument(TextInputFormFragmentMode.SetMsisdn, stage.mandatory), tag = FRAGMENT_REGISTRATION_STAGE_TAG, option = commonOption) - is Stage.Terms -> addFragmentToBackstack(views.loginFragmentContainer, + is Stage.Terms -> activity.addFragmentToBackstack(views.loginFragmentContainer, LoginTermsFragment2::class.java, - LoginTermsFragmentArgument(stage.policies.toLocalizedLoginTerms(getString(R.string.resources_language))), + LoginTermsFragmentArgument(stage.policies.toLocalizedLoginTerms(activity.getString(R.string.resources_language))), tag = FRAGMENT_REGISTRATION_STAGE_TAG, option = commonOption) else -> Unit // Should not happen } } - - override fun configure(toolbar: MaterialToolbar) { - configureToolbar(toolbar) - } - - companion object { - private const val FRAGMENT_REGISTRATION_STAGE_TAG = "FRAGMENT_REGISTRATION_STAGE_TAG" - private const val FRAGMENT_LOGIN_TAG = "FRAGMENT_LOGIN_TAG" - - private const val EXTRA_CONFIG = "EXTRA_CONFIG" - - // Note that the domain can be displayed to the user for confirmation that he trusts it. So use a human readable string - const val VECTOR_REDIRECT_URL = "element://connect" - - fun newIntent(context: Context, loginConfig: LoginConfig?): Intent { - return Intent(context, LoginActivity2::class.java).apply { - putExtra(EXTRA_CONFIG, loginConfig) - } - } - - fun redirectIntent(context: Context, data: Uri?): Intent { - return Intent(context, LoginActivity2::class.java).apply { - setData(data) - } - } - } } diff --git a/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedFragment.kt b/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedFragment.kt index 94784b0605..c1f45c6713 100644 --- a/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedFragment.kt +++ b/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedFragment.kt @@ -34,11 +34,11 @@ import im.vector.app.core.resources.ColorProvider import im.vector.app.databinding.DialogBaseEditTextBinding import im.vector.app.databinding.FragmentLoginAccountCreatedBinding import im.vector.app.features.displayname.getBestName +import im.vector.app.features.ftue.FTUEActivity import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider import im.vector.app.features.login2.AbstractLoginFragment2 import im.vector.app.features.login2.LoginAction2 -import im.vector.app.features.login2.LoginActivity2 import im.vector.app.features.login2.LoginViewState2 import org.matrix.android.sdk.api.util.MatrixItem import java.util.UUID @@ -130,7 +130,7 @@ class AccountCreatedFragment @Inject constructor( private fun invalidateState(state: AccountCreatedViewState) { // Ugly hack... - (activity as? LoginActivity2)?.setIsLoading(state.isLoading) + (activity as? FTUEActivity)?.setIsLoading(state.isLoading) views.loginAccountCreatedSubtitle.text = getString(R.string.login_account_created_subtitle, state.userId) diff --git a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt index b18604a13f..0c335d7ddc 100644 --- a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt +++ b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt @@ -50,6 +50,7 @@ import im.vector.app.features.crypto.verification.SupportedVerificationMethodsPr import im.vector.app.features.crypto.verification.VerificationBottomSheet import im.vector.app.features.debug.DebugMenuActivity import im.vector.app.features.devtools.RoomDevToolActivity +import im.vector.app.features.ftue.FTUEActivity import im.vector.app.features.home.room.detail.RoomDetailActivity import im.vector.app.features.home.room.detail.RoomDetailArgs import im.vector.app.features.home.room.detail.search.SearchActivity @@ -58,7 +59,6 @@ import im.vector.app.features.home.room.filtered.FilteredRoomsActivity import im.vector.app.features.invite.InviteUsersToRoomActivity import im.vector.app.features.login.LoginActivity import im.vector.app.features.login.LoginConfig -import im.vector.app.features.login2.LoginActivity2 import im.vector.app.features.matrixto.MatrixToBottomSheet import im.vector.app.features.media.AttachmentData import im.vector.app.features.media.BigImageViewerActivity @@ -112,7 +112,8 @@ class DefaultNavigator @Inject constructor( override fun openLogin(context: Context, loginConfig: LoginConfig?, flags: Int) { val intent = when (features.loginVariant()) { VectorFeatures.LoginVariant.LEGACY -> LoginActivity.newIntent(context, loginConfig) - VectorFeatures.LoginVariant.FTUE_WIP -> LoginActivity2.newIntent(context, loginConfig) + VectorFeatures.LoginVariant.FTUE, + VectorFeatures.LoginVariant.FTUE_WIP -> FTUEActivity.newIntent(context, loginConfig) } intent.addFlags(flags) context.startActivity(intent) @@ -121,7 +122,8 @@ class DefaultNavigator @Inject constructor( override fun loginSSORedirect(context: Context, data: Uri?) { val intent = when (features.loginVariant()) { VectorFeatures.LoginVariant.LEGACY -> LoginActivity.redirectIntent(context, data) - VectorFeatures.LoginVariant.FTUE_WIP -> LoginActivity2.redirectIntent(context, data) + VectorFeatures.LoginVariant.FTUE, + VectorFeatures.LoginVariant.FTUE_WIP -> FTUEActivity.redirectIntent(context, data) } context.startActivity(intent) } From 03a419404734dde97302f4800888f8c4b60ea235 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Tue, 14 Dec 2021 14:44:09 +0300 Subject: [PATCH 049/632] Add location attachment icon with required permissions. --- vector/src/main/AndroidManifest.xml | 4 ++++ .../vector/app/core/utils/PermissionsTools.kt | 1 + .../attachments/AttachmentTypeSelectorView.kt | 23 +++++++++++-------- .../home/room/detail/RoomDetailFragment.kt | 15 ++++++------ .../drawable/ic_attachment_location_white.xml | 9 ++++++++ .../layout/view_attachment_type_selector.xml | 21 +++++++++++++++++ vector/src/main/res/values/strings.xml | 1 + 7 files changed, 58 insertions(+), 16 deletions(-) create mode 100644 vector/src/main/res/drawable/ic_attachment_location_white.xml diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml index 93f4ac7632..f03b8735b4 100644 --- a/vector/src/main/AndroidManifest.xml +++ b/vector/src/main/AndroidManifest.xml @@ -42,6 +42,10 @@ android:name="android.permission.WRITE_CALENDAR" tools:node="remove" /> + + + + diff --git a/vector/src/main/java/im/vector/app/core/utils/PermissionsTools.kt b/vector/src/main/java/im/vector/app/core/utils/PermissionsTools.kt index ba396ed252..5fc5e0de84 100644 --- a/vector/src/main/java/im/vector/app/core/utils/PermissionsTools.kt +++ b/vector/src/main/java/im/vector/app/core/utils/PermissionsTools.kt @@ -40,6 +40,7 @@ val PERMISSIONS_FOR_MEMBERS_SEARCH = listOf(Manifest.permission.READ_CONTACTS) val PERMISSIONS_FOR_ROOM_AVATAR = listOf(Manifest.permission.CAMERA) val PERMISSIONS_FOR_WRITING_FILES = listOf(Manifest.permission.WRITE_EXTERNAL_STORAGE) val PERMISSIONS_FOR_PICKING_CONTACT = listOf(Manifest.permission.READ_CONTACTS) +val PERMISSIONS_FOR_LOCATION_SHARING = listOf(Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION) val PERMISSIONS_EMPTY = emptyList() diff --git a/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorView.kt b/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorView.kt index ccc07ef118..da3d40b36e 100644 --- a/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorView.kt +++ b/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorView.kt @@ -40,6 +40,7 @@ import com.amulyakhare.textdrawable.util.ColorGenerator import im.vector.app.R import im.vector.app.core.extensions.getMeasurements import im.vector.app.core.utils.PERMISSIONS_EMPTY +import im.vector.app.core.utils.PERMISSIONS_FOR_LOCATION_SHARING import im.vector.app.core.utils.PERMISSIONS_FOR_PICKING_CONTACT import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO import im.vector.app.databinding.ViewAttachmentTypeSelectorBinding @@ -77,6 +78,7 @@ class AttachmentTypeSelectorView(context: Context, views.attachmentAudioButton.configure(Type.AUDIO) views.attachmentContactButton.configure(Type.CONTACT) views.attachmentPollButton.configure(Type.POLL) + views.attachmentLocationButton.configure(Type.LOCATION) width = LinearLayout.LayoutParams.MATCH_PARENT height = LinearLayout.LayoutParams.WRAP_CONTENT animationStyle = 0 @@ -110,7 +112,8 @@ class AttachmentTypeSelectorView(context: Context, animateButtonIn(views.attachmentAudioButton, 0) animateButtonIn(views.attachmentContactButton, ANIMATION_DURATION / 4) animateButtonIn(views.attachmentStickersButton, ANIMATION_DURATION / 2) - animateButtonIn(views.attachmentPollButton, ANIMATION_DURATION / 4) + animateButtonIn(views.attachmentPollButton, ANIMATION_DURATION / 2) + animateButtonIn(views.attachmentLocationButton, ANIMATION_DURATION / 4) } override fun dismiss() { @@ -124,13 +127,14 @@ class AttachmentTypeSelectorView(context: Context, fun setAttachmentVisibility(type: Type, isVisible: Boolean) { when (type) { - Type.CAMERA -> views.attachmentCameraButtonContainer - Type.GALLERY -> views.attachmentGalleryButtonContainer - Type.FILE -> views.attachmentFileButtonContainer - Type.STICKER -> views.attachmentStickersButtonContainer - Type.AUDIO -> views.attachmentAudioButtonContainer - Type.CONTACT -> views.attachmentContactButtonContainer - Type.POLL -> views.attachmentPollButtonContainer + Type.CAMERA -> views.attachmentCameraButtonContainer + Type.GALLERY -> views.attachmentGalleryButtonContainer + Type.FILE -> views.attachmentFileButtonContainer + Type.STICKER -> views.attachmentStickersButtonContainer + Type.AUDIO -> views.attachmentAudioButtonContainer + Type.CONTACT -> views.attachmentContactButtonContainer + Type.POLL -> views.attachmentPollButtonContainer + Type.LOCATION -> views.attachmentLocationButtonContainer }.let { it.isVisible = isVisible } @@ -230,6 +234,7 @@ class AttachmentTypeSelectorView(context: Context, STICKER(PERMISSIONS_EMPTY), AUDIO(PERMISSIONS_EMPTY), CONTACT(PERMISSIONS_FOR_PICKING_CONTACT), - POLL(PERMISSIONS_EMPTY) + POLL(PERMISSIONS_EMPTY), + LOCATION(PERMISSIONS_FOR_LOCATION_SHARING) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index a77899a8b0..455f93f973 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -2207,18 +2207,19 @@ class RoomDetailFragment @Inject constructor( private fun launchAttachmentProcess(type: AttachmentTypeSelectorView.Type) { when (type) { - AttachmentTypeSelectorView.Type.CAMERA -> attachmentsHelper.openCamera( + AttachmentTypeSelectorView.Type.CAMERA -> attachmentsHelper.openCamera( activity = requireActivity(), vectorPreferences = vectorPreferences, cameraActivityResultLauncher = attachmentCameraActivityResultLauncher, cameraVideoActivityResultLauncher = attachmentCameraVideoActivityResultLauncher ) - AttachmentTypeSelectorView.Type.FILE -> attachmentsHelper.selectFile(attachmentFileActivityResultLauncher) - AttachmentTypeSelectorView.Type.GALLERY -> attachmentsHelper.selectGallery(attachmentMediaActivityResultLauncher) - AttachmentTypeSelectorView.Type.AUDIO -> attachmentsHelper.selectAudio(attachmentAudioActivityResultLauncher) - AttachmentTypeSelectorView.Type.CONTACT -> attachmentsHelper.selectContact(attachmentContactActivityResultLauncher) - AttachmentTypeSelectorView.Type.STICKER -> roomDetailViewModel.handle(RoomDetailAction.SelectStickerAttachment) - AttachmentTypeSelectorView.Type.POLL -> navigator.openCreatePoll(requireContext(), roomDetailArgs.roomId) + AttachmentTypeSelectorView.Type.FILE -> attachmentsHelper.selectFile(attachmentFileActivityResultLauncher) + AttachmentTypeSelectorView.Type.GALLERY -> attachmentsHelper.selectGallery(attachmentMediaActivityResultLauncher) + AttachmentTypeSelectorView.Type.AUDIO -> attachmentsHelper.selectAudio(attachmentAudioActivityResultLauncher) + AttachmentTypeSelectorView.Type.CONTACT -> attachmentsHelper.selectContact(attachmentContactActivityResultLauncher) + AttachmentTypeSelectorView.Type.STICKER -> roomDetailViewModel.handle(RoomDetailAction.SelectStickerAttachment) + AttachmentTypeSelectorView.Type.POLL -> navigator.openCreatePoll(requireContext(), roomDetailArgs.roomId) + AttachmentTypeSelectorView.Type.LOCATION -> Timber.d("On location attachment clicked") }.exhaustive } diff --git a/vector/src/main/res/drawable/ic_attachment_location_white.xml b/vector/src/main/res/drawable/ic_attachment_location_white.xml new file mode 100644 index 0000000000..865362312b --- /dev/null +++ b/vector/src/main/res/drawable/ic_attachment_location_white.xml @@ -0,0 +1,9 @@ + + + diff --git a/vector/src/main/res/layout/view_attachment_type_selector.xml b/vector/src/main/res/layout/view_attachment_type_selector.xml index 4cd5e1910d..9ca7b8cdb0 100644 --- a/vector/src/main/res/layout/view_attachment_type_selector.xml +++ b/vector/src/main/res/layout/view_attachment_type_selector.xml @@ -194,6 +194,27 @@ android:importantForAccessibility="no" android:text="@string/attachment_type_poll" /> + + + + + + + diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 34ac5fcddc..38c7d355c3 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -2466,6 +2466,7 @@ "Gallery" "Sticker" Poll + Location Rotate and crop Couldn\'t handle share data From ccee4a9952215860d28df64cd1c0974248fefc7c Mon Sep 17 00:00:00 2001 From: Ihor Hordiichuk Date: Tue, 14 Dec 2021 14:30:42 +0000 Subject: [PATCH 050/632] Translated using Weblate (Ukrainian) Currently translated at 100.0% (2725 of 2725 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 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vector/src/main/res/values-uk/strings.xml b/vector/src/main/res/values-uk/strings.xml index dff85c08a8..1ecee76e02 100644 --- a/vector/src/main/res/values-uk/strings.xml +++ b/vector/src/main/res/values-uk/strings.xml @@ -3118,12 +3118,12 @@ Це запрошення до простору надіслане %s, не пов\'язаній із вашим обліковим записом Це запрошення до кімнати надіслане %s, не пов\'язаній із вашим обліковим записом Кімнати можуть лишатися закритими для людей ззовні простору, водночас люди в просторі можуть знаходити їх і приєднуватися. Всі нові кімнати в просторі матимуть цю опцію. - Допомагає людям у просторах знаходити закриті кімнати й приєднуватися власноруч, без потреби вручну запрошувати всіх. + Допомагає людям у просторах знаходити закриті кімнати й приєднуватися самостійно, без потреби вручну запрошувати всіх. Зауважте, що поліпшення створить нову версію кімнати. Всі наявні повідомлення залишаться в цій архівованій кімнаті. Будь-хто в батьківському просторі зможемо знайти кімнату й долучитись — нема потреба вручну запрошувати всіх. Можна змінити це в налаштуваннях кімнати будь-коли. Будь-хто в %s зможе знайти кімнату й долучитись — нема потреби вручну запрошувати всіх. Можна змінити це в налаштуваннях кімнати будь-коли. Не вдалося відповісти чи редагувати, бо голосове повідомлення активне - Кімната — версії %s, яку домашній сервер позначив як нестабільну. + Кімната — версії %s, яку домашній сервер позначив нестабільною. Дозволити будь-кому в %s знаходити й доступатись. Можете також обрати інші простори. Поліпшення кімнати — серйозна операція. Її зазвичай радять, коли кімната нестабільна через вади, брак функціоналу чи вразливості безпеки. \nЗазвичай це впливає лише на деталі опрацювання кімнати сервером. @@ -3167,7 +3167,7 @@ Згенерувати ключ безпеки для зберігання в надійному місці, наприклад у менеджері паролів чи сейфі. Не вдалося створити особисте повідомлення. Перевірте користувачів, яких бажаєте запросити, й спробуйте ще. Не вдалося розшифрувати резервну копію цим відновлювальним ключем. Звірте, чи правильний ключ ви ввели. - Оберіть свій відновлювальний ключ, введіть його власноруч чи вставте з буферу обміну + Оберіть свій ключ відновлення, введіть його власноруч або вставте з буфера обміну Використати відновлювальний ключ %1$s або %2$s треба для продовження. Відновлювальний ключ резервного копіювання ключів From f3ede9db6a3c73af9b4255b7a32bb9eba2cb8321 Mon Sep 17 00:00:00 2001 From: Denys Nykula Date: Tue, 14 Dec 2021 12:05:22 +0000 Subject: [PATCH 051/632] Translated using Weblate (Ukrainian) Currently translated at 100.0% (2725 of 2725 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 | 54 +++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/vector/src/main/res/values-uk/strings.xml b/vector/src/main/res/values-uk/strings.xml index 1ecee76e02..66d27ca3cb 100644 --- a/vector/src/main/res/values-uk/strings.xml +++ b/vector/src/main/res/values-uk/strings.xml @@ -3194,4 +3194,58 @@ Можете ввімкнути це, якщо в кімнаті співпрацюватимуть лише внутрішні команди на вашому домашньому сервері. Цього більше не можна буде змінити. Цей сеанс — користувача %1$s, а ви надаєте облікові дані користувача %2$s. Це не підтримується в ${app_name}. \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$d голос + %1$d голоси + %1$d голосів + %1$d голосів + + Системні налаштування + Версії + Отримати допомогу в використанні Element + Довідка й підтримка + Довідка + Юридичне + Цей сервер не надає політики. + Бібліотеки сторонніх осіб + Політика вашого сервера ідентифікації + Політика вашого домашнього сервера + Політика ${app_name} + Можете вимкнути це коли завгодно в налаштуваннях + Ми не передаємо даних стороннім особам + Ми не записуємо й не аналізуємо жодних даних облікового запису + тут + Допомагайте нам визначати проблеми й удосконалювати Element, надсилаючи анонімні дані про використання. Щоб розуміти, як люди використовують кілька пристроїв, ми створимо спільний для ваших пристроїв випадковий ідентифікатор. +\n +\nМожете прочитати всі наші умови %s. + Допоможіть покращити Element + Ввімкнути \ No newline at end of file From 96062b7daa3fa520a8e38b4100e134002b43347f Mon Sep 17 00:00:00 2001 From: David Langley Date: Tue, 14 Dec 2021 17:10:54 +0000 Subject: [PATCH 052/632] Enable on replies and quotes even if preference is disabled to fix newline issues. --- .../room/model/relation/RelationService.kt | 4 +--- .../sdk/api/session/room/send/SendService.kt | 2 +- .../room/relation/DefaultRelationService.kt | 4 ++-- .../internal/session/room/relation/EventEditor.kt | 3 +-- .../session/room/send/DefaultSendService.kt | 4 ++-- .../session/room/send/LocalEchoEventFactory.kt | 15 ++++++--------- .../detail/composer/MessageComposerViewModel.kt | 4 ++-- 7 files changed, 15 insertions(+), 21 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationService.kt index 59d84ef40f..0bc3120119 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationService.kt @@ -104,11 +104,9 @@ interface RelationService { * by the sdk into pills. * @param eventReplied the event referenced by the reply * @param replyText the reply text - * @param autoMarkdown If true, the SDK will generate a formatted HTML message from the body text if markdown syntax is present */ fun replyToMessage(eventReplied: TimelineEvent, - replyText: CharSequence, - autoMarkdown: Boolean = false): Cancelable? + replyText: CharSequence): Cancelable? /** * Get the current EventAnnotationsSummary diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt index 255b4d5809..6496ddc2ca 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt @@ -64,7 +64,7 @@ interface SendService { * @param autoMarkdown If true, the SDK will generate a formatted HTML message from the body text if markdown syntax is present * @return a [Cancelable] */ - fun sendQuotedTextMessage(quotedEvent: TimelineEvent, text: String, autoMarkdown: Boolean = false): Cancelable + fun sendQuotedTextMessage(quotedEvent: TimelineEvent, text: String): Cancelable /** * Method to send a media asynchronously. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/DefaultRelationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/DefaultRelationService.kt index 07927b1412..c496633515 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/DefaultRelationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/DefaultRelationService.kt @@ -131,8 +131,8 @@ internal class DefaultRelationService @AssistedInject constructor( return fetchEditHistoryTask.execute(FetchEditHistoryTask.Params(roomId, eventId)) } - override fun replyToMessage(eventReplied: TimelineEvent, replyText: CharSequence, autoMarkdown: Boolean): Cancelable? { - val event = eventFactory.createReplyTextEvent(roomId, eventReplied, replyText, autoMarkdown) + override fun replyToMessage(eventReplied: TimelineEvent, replyText: CharSequence): Cancelable? { + val event = eventFactory.createReplyTextEvent(roomId, eventReplied, replyText) ?.also { saveLocalEcho(it) } ?: return null diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/EventEditor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/EventEditor.kt index a666d40fc3..076eb06447 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/EventEditor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/EventEditor.kt @@ -67,7 +67,7 @@ internal class EventEditor @Inject constructor(private val eventSenderProcessor: val roomId = replyToEdit.roomId if (replyToEdit.root.sendState.hasFailed()) { // We create a new in memory event for the EventSenderProcessor but we keep the eventId of the failed event. - val editedEvent = eventFactory.createReplyTextEvent(roomId, originalTimelineEvent, newBodyText, false)?.copy( + val editedEvent = eventFactory.createReplyTextEvent(roomId, originalTimelineEvent, newBodyText)?.copy( eventId = replyToEdit.eventId ) ?: return NoOpCancellable updateFailedEchoWithEvent(roomId, replyToEdit.eventId, editedEvent) @@ -78,7 +78,6 @@ internal class EventEditor @Inject constructor(private val eventSenderProcessor: replyToEdit, originalTimelineEvent, newBodyText, - true, MessageType.MSGTYPE_TEXT, compatibilityBodyText ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt index fb2fb3950a..f7f29f9b34 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt @@ -97,8 +97,8 @@ internal class DefaultSendService @AssistedInject constructor( .let { sendEvent(it) } } - override fun sendQuotedTextMessage(quotedEvent: TimelineEvent, text: String, autoMarkdown: Boolean): Cancelable { - return localEchoEventFactory.createQuotedTextEvent(roomId, quotedEvent, text, autoMarkdown) + override fun sendQuotedTextMessage(quotedEvent: TimelineEvent, text: String): Cancelable { + return localEchoEventFactory.createQuotedTextEvent(roomId, quotedEvent, text) .also { createLocalEcho(it) } .let { sendEvent(it) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt index 486af8f327..52cdf94dbb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt @@ -199,7 +199,6 @@ internal class LocalEchoEventFactory @Inject constructor( eventReplaced: TimelineEvent, originalEvent: TimelineEvent, newBodyText: String, - newBodyAutoMarkdown: Boolean, msgType: String, compatibilityText: String): Event { val permalink = permalinkFactory.createPermalink(roomId, originalEvent.root.eventId ?: "", false) @@ -207,9 +206,9 @@ internal class LocalEchoEventFactory @Inject constructor( val body = bodyForReply(originalEvent.getLastMessageContent(), originalEvent.isReply()) // As we always supply formatted body for replies we should force the MarkdownParser to produce html. - val newBodyFormatted = createTextContent(newBodyText, newBodyAutoMarkdown, forceMarkdownParse = true).takeFormatted() + val newBodyFormatted = createTextContent(newBodyText, true, forceMarkdownParse = true).takeFormatted() // Body of the original message may not have formatted version, so may also have to convert to html. - val bodyFormatted = body.formattedText ?: createTextContent(body.text, newBodyAutoMarkdown, forceMarkdownParse = true).takeFormatted() + val bodyFormatted = body.formattedText ?: createTextContent(body.text, true, forceMarkdownParse = true).takeFormatted() val replyFormatted = REPLY_PATTERN.format( permalink, userLink, @@ -387,8 +386,7 @@ internal class LocalEchoEventFactory @Inject constructor( fun createReplyTextEvent(roomId: String, eventReplied: TimelineEvent, - replyText: CharSequence, - autoMarkdown: Boolean): Event? { + replyText: CharSequence): Event? { // Fallbacks and event representation // TODO Add error/warning logs when any of this is null val permalink = permalinkFactory.createPermalink(eventReplied.root, false) ?: return null @@ -398,9 +396,9 @@ internal class LocalEchoEventFactory @Inject constructor( val body = bodyForReply(eventReplied.getLastMessageContent(), eventReplied.isReply()) // As we always supply formatted body for replies we should force the MarkdownParser to produce html. - val replyTextFormatted = createTextContent(replyText, autoMarkdown, forceMarkdownParse = true).takeFormatted() + val replyTextFormatted = createTextContent(replyText, true, forceMarkdownParse = true).takeFormatted() // Body of the original message may not have formatted version, so may also have to convert to html. - val bodyFormatted = body.formattedText ?: createTextContent(body.text, autoMarkdown, forceMarkdownParse = true).takeFormatted() + val bodyFormatted = body.formattedText ?: createTextContent(body.text, true, forceMarkdownParse = true).takeFormatted() val replyFormatted = REPLY_PATTERN.format( permalink, userLink, @@ -512,12 +510,11 @@ internal class LocalEchoEventFactory @Inject constructor( roomId: String, quotedEvent: TimelineEvent, text: String, - autoMarkdown: Boolean ): Event { val messageContent = quotedEvent.getLastMessageContent() val textMsg = messageContent?.body val quoteText = legacyRiotQuoteText(textMsg, text) - return createFormattedTextEvent(roomId, createTextContent(quoteText, autoMarkdown), MessageType.MSGTYPE_TEXT) + return createFormattedTextEvent(roomId, createTextContent(quoteText, autoMarkdown=true, forceMarkdownParse = true), MessageType.MSGTYPE_TEXT) } private fun legacyRiotQuoteText(quotedText: String?, myText: String): String { 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 a63a06928a..8495cc3970 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 @@ -406,13 +406,13 @@ class MessageComposerViewModel @AssistedInject constructor( popDraft() } is SendMode.Quote -> { - room.sendQuotedTextMessage(state.sendMode.timelineEvent, action.text.toString(), action.autoMarkdown) + room.sendQuotedTextMessage(state.sendMode.timelineEvent, action.text.toString()) _viewEvents.post(MessageComposerViewEvents.MessageSent) popDraft() } is SendMode.Reply -> { state.sendMode.timelineEvent.let { - room.replyToMessage(it, action.text.toString(), action.autoMarkdown) + room.replyToMessage(it, action.text.toString()) _viewEvents.post(MessageComposerViewEvents.MessageSent) popDraft() } From bef238f8510d493c9c2ad42889df63ee587b51ff Mon Sep 17 00:00:00 2001 From: David Langley Date: Tue, 14 Dec 2021 20:40:44 +0000 Subject: [PATCH 053/632] Add simple parser for use with just quotes/replies --- .../sdk/internal/session/room/RoomModule.kt | 20 +++++++++++++++++++ .../room/send/LocalEchoEventFactory.kt | 14 ++++++------- .../session/room/send/MarkdownParser.kt | 8 +++++--- 3 files changed, 32 insertions(+), 10 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt index dbd0ae6f06..6497c997ed 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt @@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.session.room import dagger.Binds import dagger.Module import dagger.Provides +import org.commonmark.node.BlockQuote import org.commonmark.parser.Parser import org.commonmark.renderer.html.HtmlRenderer import org.matrix.android.sdk.api.session.file.FileService @@ -98,6 +99,14 @@ import org.matrix.android.sdk.internal.session.room.version.DefaultRoomVersionUp import org.matrix.android.sdk.internal.session.room.version.RoomVersionUpgradeTask import org.matrix.android.sdk.internal.session.space.DefaultSpaceService import retrofit2.Retrofit +import javax.inject.Qualifier + +/** + * Used to inject the simple commonmark Parser + */ +@Qualifier +@Retention(AnnotationRetention.RUNTIME) +internal annotation class SimpleCommonmarkParser @Module internal abstract class RoomModule { @@ -124,6 +133,17 @@ internal abstract class RoomModule { return Parser.builder().build() } + @Provides + @SimpleCommonmarkParser + @JvmStatic + fun providesSimpleParser(): Parser { + // The simple parser disables all blocks but quotes. + // Inline parsing(bold, italic, etc) is also enabled and is not easy to disable in commonmark currently. + return Parser.builder() + .enabledBlockTypes(setOf(BlockQuote::class.java)) + .build() + } + @Provides @JvmStatic fun providesHtmlRenderer(): HtmlRenderer { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt index 52cdf94dbb..9e279dfa44 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt @@ -91,9 +91,9 @@ internal class LocalEchoEventFactory @Inject constructor( return createMessageEvent(roomId, content) } - private fun createTextContent(text: CharSequence, autoMarkdown: Boolean, forceMarkdownParse: Boolean = false): TextContent { + private fun createTextContent(text: CharSequence, autoMarkdown: Boolean): TextContent { if (autoMarkdown) { - return markdownParser.parse(text, force = forceMarkdownParse) + return markdownParser.parse(text) } else { // Try to detect pills textPillsUtils.processSpecialSpansToHtml(text)?.let { @@ -206,9 +206,9 @@ internal class LocalEchoEventFactory @Inject constructor( val body = bodyForReply(originalEvent.getLastMessageContent(), originalEvent.isReply()) // As we always supply formatted body for replies we should force the MarkdownParser to produce html. - val newBodyFormatted = createTextContent(newBodyText, true, forceMarkdownParse = true).takeFormatted() + val newBodyFormatted = markdownParser.parse(newBodyText, force = true, advanced = false).takeFormatted() // Body of the original message may not have formatted version, so may also have to convert to html. - val bodyFormatted = body.formattedText ?: createTextContent(body.text, true, forceMarkdownParse = true).takeFormatted() + val bodyFormatted = body.formattedText ?: markdownParser.parse(newBodyText, force = true, advanced = false).takeFormatted() val replyFormatted = REPLY_PATTERN.format( permalink, userLink, @@ -396,9 +396,9 @@ internal class LocalEchoEventFactory @Inject constructor( val body = bodyForReply(eventReplied.getLastMessageContent(), eventReplied.isReply()) // As we always supply formatted body for replies we should force the MarkdownParser to produce html. - val replyTextFormatted = createTextContent(replyText, true, forceMarkdownParse = true).takeFormatted() + val replyTextFormatted = markdownParser.parse(replyText, force = true, advanced = false).takeFormatted() // Body of the original message may not have formatted version, so may also have to convert to html. - val bodyFormatted = body.formattedText ?: createTextContent(body.text, true, forceMarkdownParse = true).takeFormatted() + val bodyFormatted = body.formattedText ?: markdownParser.parse(replyText, force = true, advanced = false).takeFormatted() val replyFormatted = REPLY_PATTERN.format( permalink, userLink, @@ -514,7 +514,7 @@ internal class LocalEchoEventFactory @Inject constructor( val messageContent = quotedEvent.getLastMessageContent() val textMsg = messageContent?.body val quoteText = legacyRiotQuoteText(textMsg, text) - return createFormattedTextEvent(roomId, createTextContent(quoteText, autoMarkdown=true, forceMarkdownParse = true), MessageType.MSGTYPE_TEXT) + return createFormattedTextEvent(roomId, markdownParser.parse(quoteText, force = true, advanced = false), MessageType.MSGTYPE_TEXT) } private fun legacyRiotQuoteText(quotedText: String?, myText: String): String { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/MarkdownParser.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/MarkdownParser.kt index 2df668bf40..8f963ba16d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/MarkdownParser.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/MarkdownParser.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.session.room.send import org.commonmark.parser.Parser import org.commonmark.renderer.html.HtmlRenderer +import org.matrix.android.sdk.internal.session.room.SimpleCommonmarkParser import org.matrix.android.sdk.internal.session.room.send.pills.TextPillsUtils import javax.inject.Inject @@ -27,7 +28,8 @@ import javax.inject.Inject * If any change is required, please add a test covering the problem and make sure all the tests are still passing. */ internal class MarkdownParser @Inject constructor( - private val parser: Parser, + private val advancedParser: Parser, + @SimpleCommonmarkParser private val simpleParser: Parser, private val htmlRenderer: HtmlRenderer, private val textPillsUtils: TextPillsUtils ) { @@ -40,7 +42,7 @@ internal class MarkdownParser @Inject constructor( * @param force Skips the check for detecting if the input contains markdown and always converts to html. * @return TextContent containing the plain text and the formatted html if generated. */ - fun parse(text: CharSequence, force: Boolean = false): TextContent { + fun parse(text: CharSequence, force: Boolean = false, advanced: Boolean = true): TextContent { val source = textPillsUtils.processSpecialSpansToMarkdown(text) ?: text.toString() // If no special char are detected, just return plain text @@ -48,7 +50,7 @@ internal class MarkdownParser @Inject constructor( return TextContent(source) } - val document = parser.parse(source) + val document = if (advanced) advancedParser.parse(source) else simpleParser.parse(source) val htmlText = htmlRenderer.render(document) // Cleanup extra paragraph From bf48617fc6354ed0f88a03e13b0ff1fb41525b28 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Wed, 15 Dec 2021 00:13:52 +0300 Subject: [PATCH 054/632] Create base UI components of location sharing screen. --- vector/src/main/AndroidManifest.xml | 1 + .../app/core/di/MavericksViewModelModule.kt | 6 ++ .../home/room/detail/RoomDetailFragment.kt | 5 +- .../location/LocationSharingAction.kt | 23 ++++++ .../location/LocationSharingActivity.kt | 80 +++++++++++++++++++ .../location/LocationSharingFragment.kt | 40 ++++++++++ .../location/LocationSharingViewEvents.kt | 23 ++++++ .../location/LocationSharingViewModel.kt | 41 ++++++++++ .../location/LocationSharingViewState.kt | 37 +++++++++ .../features/navigation/DefaultNavigator.kt | 11 +++ .../app/features/navigation/Navigator.kt | 3 + .../res/layout/activity_location_sharing.xml | 26 ++++++ .../res/layout/fragment_location_sharing.xml | 6 ++ vector/src/main/res/values/strings.xml | 4 + 14 files changed, 305 insertions(+), 1 deletion(-) create mode 100644 vector/src/main/java/im/vector/app/features/location/LocationSharingAction.kt create mode 100644 vector/src/main/java/im/vector/app/features/location/LocationSharingActivity.kt create mode 100644 vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt create mode 100644 vector/src/main/java/im/vector/app/features/location/LocationSharingViewEvents.kt create mode 100644 vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt create mode 100644 vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt create mode 100755 vector/src/main/res/layout/activity_location_sharing.xml create mode 100644 vector/src/main/res/layout/fragment_location_sharing.xml diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml index f03b8735b4..28833ab333 100644 --- a/vector/src/main/AndroidManifest.xml +++ b/vector/src/main/AndroidManifest.xml @@ -336,6 +336,7 @@ + diff --git a/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt index d09cd21d19..b9757078ea 100644 --- a/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt @@ -53,6 +53,7 @@ import im.vector.app.features.home.room.detail.upgrade.MigrateRoomViewModel import im.vector.app.features.home.room.list.RoomListViewModel import im.vector.app.features.homeserver.HomeServerCapabilitiesViewModel import im.vector.app.features.invite.InviteUsersToRoomViewModel +import im.vector.app.features.location.LocationSharingViewModel import im.vector.app.features.login.LoginViewModel import im.vector.app.features.login2.LoginViewModel2 import im.vector.app.features.login2.created.AccountCreatedViewModel @@ -576,4 +577,9 @@ interface MavericksViewModelModule { @IntoMap @MavericksViewModelKey(CreatePollViewModel::class) fun createPollViewModelFactory(factory: CreatePollViewModel.Factory): MavericksAssistedViewModelFactory<*, *> + + @Binds + @IntoMap + @MavericksViewModelKey(LocationSharingViewModel::class) + fun createLocationSharingViewModelFactory(factory: LocationSharingViewModel.Factory): MavericksAssistedViewModelFactory<*, *> } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index 455f93f973..22eeb7759b 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -165,6 +165,7 @@ import im.vector.app.features.html.EventHtmlRenderer import im.vector.app.features.html.PillImageSpan import im.vector.app.features.html.PillsPostProcessor import im.vector.app.features.invite.VectorInviteView +import im.vector.app.features.location.LocationSharingMode import im.vector.app.features.media.ImageContentRenderer import im.vector.app.features.media.VideoContentRenderer import im.vector.app.features.notifications.NotificationDrawerManager @@ -2219,7 +2220,9 @@ class RoomDetailFragment @Inject constructor( AttachmentTypeSelectorView.Type.CONTACT -> attachmentsHelper.selectContact(attachmentContactActivityResultLauncher) AttachmentTypeSelectorView.Type.STICKER -> roomDetailViewModel.handle(RoomDetailAction.SelectStickerAttachment) AttachmentTypeSelectorView.Type.POLL -> navigator.openCreatePoll(requireContext(), roomDetailArgs.roomId) - AttachmentTypeSelectorView.Type.LOCATION -> Timber.d("On location attachment clicked") + AttachmentTypeSelectorView.Type.LOCATION -> { + navigator.openLocationSharing(requireContext(), roomDetailArgs.roomId, LocationSharingMode.STATIC_SHARING) + } }.exhaustive } 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 new file mode 100644 index 0000000000..5139888b08 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingAction.kt @@ -0,0 +1,23 @@ +/* + * 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.location + +import im.vector.app.core.platform.VectorViewModelAction + +sealed class LocationSharingAction : VectorViewModelAction { + data class OnShareLocation(val latitude: Double, val longitude: Double) : LocationSharingAction() +} diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingActivity.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingActivity.kt new file mode 100644 index 0000000000..fc685815db --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingActivity.kt @@ -0,0 +1,80 @@ +/* + * 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.location + +import android.content.Context +import android.content.Intent +import android.os.Parcelable +import com.google.android.material.appbar.MaterialToolbar +import dagger.hilt.android.AndroidEntryPoint +import im.vector.app.core.extensions.addFragment +import im.vector.app.core.platform.ToolbarConfigurable +import im.vector.app.core.platform.VectorBaseActivity +import im.vector.app.databinding.ActivityLocationSharingBinding +import kotlinx.parcelize.Parcelize + +@Parcelize +data class LocationSharingArgs( + val roomId: String, + val mode: LocationSharingMode +) : Parcelable + +@AndroidEntryPoint +class LocationSharingActivity : VectorBaseActivity(), + ToolbarConfigurable { + + override fun getBinding() = ActivityLocationSharingBinding.inflate(layoutInflater) + + override fun configure(toolbar: MaterialToolbar) { + configureToolbar(toolbar) + } + + override fun initUiAndData() { + val locationSharingArgs: LocationSharingArgs? = intent?.extras?.getParcelable(EXTRA_LOCATION_SHARING_ARGS) + if (locationSharingArgs == null) { + finish() + return + } + configure(views.toolbar) + supportActionBar?.title = getString(locationSharingArgs.mode.titleRes) + + if (isFirstCreation()) { + when (locationSharingArgs.mode) { + LocationSharingMode.STATIC_SHARING -> { + addFragment( + views.fragmentContainer, + LocationSharingFragment::class.java, + locationSharingArgs + ) + } + LocationSharingMode.PREVIEW -> { + } + } + } + } + + companion object { + + private const val EXTRA_LOCATION_SHARING_ARGS = "EXTRA_LOCATION_SHARING_ARGS" + + fun getIntent(context: Context, locationSharingArgs: LocationSharingArgs): Intent { + return Intent(context, LocationSharingActivity::class.java).apply { + putExtra(EXTRA_LOCATION_SHARING_ARGS, locationSharingArgs) + } + } + } +} 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 new file mode 100644 index 0000000000..fd2e48546d --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt @@ -0,0 +1,40 @@ +/* + * 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.location + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.airbnb.mvrx.activityViewModel +import im.vector.app.core.platform.VectorBaseFragment +import im.vector.app.databinding.FragmentLocationSharingBinding +import javax.inject.Inject + +class LocationSharingFragment @Inject constructor() : + VectorBaseFragment() { + + private val viewModel: LocationSharingViewModel by activityViewModel() + + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLocationSharingBinding { + return FragmentLocationSharingBinding.inflate(inflater, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + } +} 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 new file mode 100644 index 0000000000..1032a696ff --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewEvents.kt @@ -0,0 +1,23 @@ +/* + * 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.location + +import im.vector.app.core.platform.VectorViewEvents + +sealed class LocationSharingViewEvents : VectorViewEvents { + object Close : 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 new file mode 100644 index 0000000000..6da00f176d --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt @@ -0,0 +1,41 @@ +/* + * 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.location + +import com.airbnb.mvrx.MavericksViewModelFactory +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +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 + +class LocationSharingViewModel @AssistedInject constructor( + @Assisted private val initialState: LocationSharingViewState +) : VectorViewModel(initialState) { + + @AssistedFactory + interface Factory : MavericksAssistedViewModelFactory { + override fun create(initialState: LocationSharingViewState): LocationSharingViewModel + } + + companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() { + } + + override fun handle(action: LocationSharingAction) { + } +} 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 new file mode 100644 index 0000000000..d62e568fb2 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt @@ -0,0 +1,37 @@ +/* + * 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.location + +import androidx.annotation.StringRes +import com.airbnb.mvrx.MavericksState +import im.vector.app.R + +enum class LocationSharingMode(@StringRes val titleRes: Int) { + STATIC_SHARING(R.string.location_activity_title_static_sharing), + PREVIEW(R.string.location_activity_title_preview) +} + +data class LocationSharingViewState( + val roomId: String, + val mode: LocationSharingMode +) : MavericksState { + + constructor(locationSharingArgs: LocationSharingArgs) : this( + roomId = locationSharingArgs.roomId, + mode = locationSharingArgs.mode + ) +} diff --git a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt index 6b035e7d49..16c50daf94 100644 --- a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt +++ b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt @@ -57,6 +57,9 @@ import im.vector.app.features.home.room.detail.search.SearchActivity import im.vector.app.features.home.room.detail.search.SearchArgs import im.vector.app.features.home.room.filtered.FilteredRoomsActivity import im.vector.app.features.invite.InviteUsersToRoomActivity +import im.vector.app.features.location.LocationSharingActivity +import im.vector.app.features.location.LocationSharingArgs +import im.vector.app.features.location.LocationSharingMode import im.vector.app.features.login.LoginActivity import im.vector.app.features.login.LoginConfig import im.vector.app.features.login2.LoginActivity2 @@ -533,6 +536,14 @@ class DefaultNavigator @Inject constructor( context.startActivity(intent) } + override fun openLocationSharing(context: Context, roomId: String, mode: LocationSharingMode) { + val intent = LocationSharingActivity.getIntent( + context, + LocationSharingArgs(roomId = roomId, mode = mode) + ) + context.startActivity(intent) + } + private fun startActivity(context: Context, intent: Intent, buildTask: Boolean) { if (buildTask) { val stackBuilder = TaskStackBuilder.create(context) diff --git a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt index 6778c39a22..2668e10694 100644 --- a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt +++ b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt @@ -25,6 +25,7 @@ import androidx.activity.result.ActivityResultLauncher import androidx.core.util.Pair import im.vector.app.features.crypto.recover.SetupMode import im.vector.app.features.displayname.getBestName +import im.vector.app.features.location.LocationSharingMode import im.vector.app.features.login.LoginConfig import im.vector.app.features.media.AttachmentData import im.vector.app.features.pin.PinMode @@ -149,4 +150,6 @@ interface Navigator { fun openCallTransfer(context: Context, callId: String) fun openCreatePoll(context: Context, roomId: String) + + fun openLocationSharing(context: Context, roomId: String, mode: LocationSharingMode) } diff --git a/vector/src/main/res/layout/activity_location_sharing.xml b/vector/src/main/res/layout/activity_location_sharing.xml new file mode 100755 index 0000000000..b278bb5a1a --- /dev/null +++ b/vector/src/main/res/layout/activity_location_sharing.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/layout/fragment_location_sharing.xml b/vector/src/main/res/layout/fragment_location_sharing.xml new file mode 100644 index 0000000000..77d9ef65f8 --- /dev/null +++ b/vector/src/main/res/layout/fragment_location_sharing.xml @@ -0,0 +1,6 @@ + + + + \ 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 38c7d355c3..0f26861d36 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -3702,4 +3702,8 @@ Poll ended Remove poll Are you sure you want to remove this poll? You won\'t be able to recover it once removed. + + + Share location + Location From 18d2da4d2504a852aad275dd9616d3552f449a33 Mon Sep 17 00:00:00 2001 From: lvre <7uu3qrbvm@relay.firefox.com> Date: Tue, 14 Dec 2021 21:35:33 +0000 Subject: [PATCH 055/632] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (2725 of 2725 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 | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/vector/src/main/res/values-pt-rBR/strings.xml b/vector/src/main/res/values-pt-rBR/strings.xml index de861e0e2f..cd6c596a9f 100644 --- a/vector/src/main/res/values-pt-rBR/strings.xml +++ b/vector/src/main/res/values-pt-rBR/strings.xml @@ -3102,4 +3102,43 @@ Você concorda em enviar esta info\? Para descobrir contatos existentes, você precisa enviar info de contato (emails e números de telefone) para seu servidor de identidade. Nós hashamos seus dados antes de enviar por privacidade. Não agora + Você tem certeza que você quer remover esta sondagem\? Você não vai ser capaz de recuperá-la uma vez removida. + Remover sondagem + Sondagem terminada + Voto lançado + Habilitar Sondagens + Terminar sondagem + Isto vai parar pessoas de serem capazes de votar e vai exibir os resultados finais da sondagem. + Terminar esta sondagem\? + opção vencedora + Terminar sondagem + + Resultado final baseado em %1$d voto + Resultado final baseado em %1$d votos + + + %1$d voto lançado. Vote para ver os resultados + %1$d votos lançados. Vote para ver os resultados + + + Baseado em %1$d voto + Baseado em %1$d votos + + + %1$d voto + %1$d votos + + Versões + Conseguir ajuda com uso de Element + Jurídicos + Este servidor não provê nenhuma política. + Bibliotecas de terceiros + A política de seu servidor de identidade + A política de seu servidorcasa + Política de ${app_name} + Nós não gravamos ou perfilamos quaisquer dados de conta + Ajude-nos a identificar problemas e melhorar Element ao compartilhar dados de uso anônimos. Para entender como pessoas usam seus múltiplos dispositivos, nós vamos gerar um identificador aleatório, compartilhado por seus dispositivos. +\n +\nVocê pode ler todos os nossos termos %s. + Ajudar a melhorar Element \ No newline at end of file From 218f8f51ab18e27f16262336d762aa0699ef8497 Mon Sep 17 00:00:00 2001 From: Daimar Stein Date: Tue, 14 Dec 2021 12:04:06 +0000 Subject: [PATCH 056/632] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (2725 of 2725 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 | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/vector/src/main/res/values-pt-rBR/strings.xml b/vector/src/main/res/values-pt-rBR/strings.xml index cd6c596a9f..b11a38e8f7 100644 --- a/vector/src/main/res/values-pt-rBR/strings.xml +++ b/vector/src/main/res/values-pt-rBR/strings.xml @@ -3141,4 +3141,11 @@ \n \nVocê pode ler todos os nossos termos %s. Ajudar a melhorar Element + Configurações do sistema + Ajuda e suporte + Ajuda + Você pode desativar isso a qualquer momento nas configurações + Nós não compartilhamos informações com terceiros + aqui + Habilitar \ No newline at end of file From 469dde33b6ee4cd5ef40eacd88882ebaf39a865e Mon Sep 17 00:00:00 2001 From: waclaw66 Date: Tue, 14 Dec 2021 11:30:39 +0000 Subject: [PATCH 057/632] Translated using Weblate (Czech) Currently translated at 100.0% (2725 of 2725 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 | 50 +++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/vector/src/main/res/values-cs/strings.xml b/vector/src/main/res/values-cs/strings.xml index 92ffbe0b46..c2e7cc62ee 100644 --- a/vector/src/main/res/values-cs/strings.xml +++ b/vector/src/main/res/values-cs/strings.xml @@ -3089,4 +3089,54 @@ Souhlasíte se zasíláním těchto informací\? Pro nalezení existujících kontaktů, je třeba odeslat kontaktní informace (e-maily a telefonní čísla) na server identity. Před odesláním vaše údaje zaheslujeme kvůli ochraně osobních údajů. Nyní ne + Opravdu chcete toto hlasování odstranit\? Po odstranění ho již nebude možné obnovit. + Odstranit hlasování + Hlasování skončilo + Hlas odeslán + Povolit hlasování + Ukončit hlasování + Toto zastaví možnost hlasování a zobrazí se konečné výsledky. + Ukončit toto hlasování\? + vítězná volba + Ukončit hlasování + + Konečný výsledek na základě %1$d hlasu + Konečný výsledek na základě %1$d hlasů + Konečný výsledek na základě %1$d hlasů + + + Nikdo nehlasoval + %1$d hlasů. Hlasujte pro zobrazení výsledků + %1$d hlasů. Hlasujte pro zobrazení výsledků + + + Na základě %1$d hlasu + Na základě %1$d hlasů + Na základě %1$d hlasů + + + %1$d hlas + %1$d hlasy + %1$d hlasů + + Systémová nastavení + Verze + Získejte pomoc při používání Elementu + Nápověda a podpora + Nápověda + Právní dokumenty + Tento server neposkytuje žádné zásady. + Knihovny třetích stran + Zásady vašeho serveru identit + Zásady vašeho domovského serveru + Zásady aplikace ${app_name} + Tuto funkci můžete kdykoli vypnout v nastavení + Nesdílíme informace s třetími stranami + Nezaznamenáváme ani neprofilujeme žádné údaje o účtu + zde + Pomozte nám identifikovat problémy a vylepšit Element sdílením anonymních údajů o používání. Abychom pochopili, jak lidé používají více zařízení, vygenerujeme náhodný identifikátor sdílený vašimi zařízeními. +\n +\nMůžete si přečíst všechny naše podmínky %s. + Pomozte vylepšit Element + Povolit \ No newline at end of file From e9010dff94afdb69ef366ad71657c20bc4183a4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Tue, 14 Dec 2021 14:12:26 +0000 Subject: [PATCH 058/632] Translated using Weblate (Estonian) Currently translated at 99.8% (2722 of 2725 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 | 45 +++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/vector/src/main/res/values-et/strings.xml b/vector/src/main/res/values-et/strings.xml index 894eec751b..e26f548c40 100644 --- a/vector/src/main/res/values-et/strings.xml +++ b/vector/src/main/res/values-et/strings.xml @@ -3032,4 +3032,49 @@ Selleks et leida tuttavaid, sa peaksid saatma oma kontaktteavet (telefoninumbreid ja/või e-posti aadresse) siin rakenduses seadistatud isikutuvastusserverile. Parema andmeturvalisuse nimel me ei saada teavet mitte loetava tekstina, vaid räsina. Kas sa oled nõus selle teabe edastamisega\? Mitte praegu + Hääletatud + Kas sa oled kindel, et soovid küsitlust kustutada\? Seda tegevust sa ei saa hiljem tagasi pöörata. + Kustuta küsitlus + Küsitlus on lõppenud + Võta küsitlused kasutusele + Lõpeta küsitlus + Sellega ei saa enam keegi oma arvamust avaldada ning kuvame lõplikud tulemused. + Kas lõpetame selle küsitluse\? + Lõpeta küsitlus + + %1$d\'l häälel põhinev lõpptulemus + %1$d\'l häälel põhinev lõpptulemus + + + Juba on %1$d hääletanu. Tulemuste nägemiseks pead hääletama + Juba on %1$d hääletanut. Tulemuste nägemiseks pead hääletama + + + Põhineb %1$d\'l häälel + Põhineb %1$d\'l häälel + + + %1$d hääl + %1$d häält + + Süsteemiseadistused + Versioonid + Element\'i kasutamiseks vajalik abiteave + Abiteave ja kasutajatugi + Abiteave + Juriidiline teave + Sellel serveril puuduvad kasutustingimused. + Sinu isikutuvastusserveri kasutustingimused + Kolmandate osapoolte litsentsid + Sinu koduserveri kasutustingimused + ${app_name} kasutustingimused + Seadistustest saad alati määrata, et see funktsionaalsus pole kasutusel + Meie ei jaga teavet kolmandate osapooltega + Meie ei salvesta ega profileeri sinu kasutajakonto andmeid + siit + Võimalike vigade leidmiseks ja Element\'i arendamiseks jaga meiega anonüümseid andmeid. Selleks, et mõistaksime, kuidas kasutajad erinevaid seadmeid pruugivad me loome sinu seadmetele ühise juhusliku tunnuse. +\n +\nMeie kasutustingimused leiad %s. + Aita Element\'i arendamisel + Võta kasutusele \ No newline at end of file From b8d8b9b71813c2aec385e0315db0bee8cff55342 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Mesk=C3=B3?= Date: Tue, 14 Dec 2021 14:54:01 +0000 Subject: [PATCH 059/632] Translated using Weblate (Hungarian) Currently translated at 98.8% (2694 of 2725 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 | 1 + 1 file changed, 1 insertion(+) diff --git a/vector/src/main/res/values-hu/strings.xml b/vector/src/main/res/values-hu/strings.xml index 3ed5e62486..cf642ec4de 100644 --- a/vector/src/main/res/values-hu/strings.xml +++ b/vector/src/main/res/values-hu/strings.xml @@ -3037,4 +3037,5 @@ Ha nem te állítottad be a visszaállítási metódust, akkor egy támadó pró A meglévő kapcsolatok felderítéséhez információkat (e-mail cím és telefonszám) kell elküldeni az azonosítási szolgáltatónak. Az adatok az adatvédelem miatt hashelve lesznek elküldve. Beleegyezel az információk elküldésébe\? Nem most + Engedélyezés \ No newline at end of file From 09854abf2573739b7d6988fc96f9bacbe0ae79a1 Mon Sep 17 00:00:00 2001 From: Linerly Date: Tue, 14 Dec 2021 11:25:03 +0000 Subject: [PATCH 060/632] Translated using Weblate (Indonesian) Currently translated at 100.0% (2725 of 2725 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 | 42 +++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/vector/src/main/res/values-in/strings.xml b/vector/src/main/res/values-in/strings.xml index b0a8d4f07c..1fe0e01414 100644 --- a/vector/src/main/res/values-in/strings.xml +++ b/vector/src/main/res/values-in/strings.xml @@ -2979,4 +2979,46 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Apakah Anda setuju untuk mengirimkan info ini\? Untuk menemukan kontak yang sudah ada, Anda harus mengirim info kontak (email dan nomor telepon) ke server identitas Anda. Kami meng-hash data Anda sebelum mengirim untuk privasi. Nanti + Apakah Anda yakin untuk menghapus poll ini\? Anda tidak akan dapat memulihkannya setelah dihapus. + Hapus poll + Poll berakhir + Suara diberikan + Aktifkan Poll + Akhiri poll + Ini akan menghentikan orang-orang untuk dapat memberikan suara dan akan menampilkan hasil akhir poll. + Akhiri poll ini\? + opsi pemenang + Akhiri poll + + Hasil akhir berdasarkan oleh %1$d suara + + + %1$d suara diberikan. Berikan suara untuk melihat hasilnya + + + Berdasarkan oleh %1$d suara + + + %1$d suara + + Pengaturan sistem + Versi + Dapatkan bantuan dengan menggunakan Element + Bantuan dan dukungan + Bantuan + Hukum + Server ini tidak memiliki sebuah kebijakan. + Perpustakaan pihak ketiga + Kebijakan server identitas Anda + Kebijakan homeserver Anda + Kebijakan ${app_name} + Anda dapat mematikannya kapan saja di pengaturan + Kami tidak membagikan informasi ini dengan pihak ketiga + Kami tidak merekam atau memprofil data akun apapun + di sini + Bantu kami mengidentifikasi masalah-masalah dan membuat Element lebih baik dengan membagikan data penggunaan anonim. Untuk memahami bagaimana orang-orang menggunakan beberapa perangkat-perangkat, kami akan membuat pengenal acak, yang dibagikan oleh perangkat Anda. +\n +\nAnda dapat membaca semua kebijakan kami %s. + Bantu buat Element lebih baik + Aktifkan \ No newline at end of file From 657d7ee85a4c5e6a5a5353fb7b6594397fb8deba Mon Sep 17 00:00:00 2001 From: random Date: Tue, 14 Dec 2021 14:03:10 +0000 Subject: [PATCH 061/632] Translated using Weblate (Italian) Currently translated at 99.8% (2722 of 2725 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 | 46 +++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/vector/src/main/res/values-it/strings.xml b/vector/src/main/res/values-it/strings.xml index 72dbd52011..efb103b364 100644 --- a/vector/src/main/res/values-it/strings.xml +++ b/vector/src/main/res/values-it/strings.xml @@ -3085,4 +3085,50 @@ Sei d\'accordo con l\'invio di queste informazioni\? Per trovare i contatti esistenti, devi inviare le informazioni dei contatti (email e numeri di telefono) al tuo server d\'identità. Facciamo un hash dei dati prima di inviarli per privacy. Non ora + Vuoi davvero rimuovere questo sondaggio\? Non potrai recuperarlo una volta rimosso. + Rimuovi sondaggio + Sondaggio terminato + Voto inviato + Attiva sondaggi + Termina sondaggio + Ciò impedirà alle persone di poter votare e mostrerà i risultati finali del sondaggio. + Terminare questo sondaggio\? + opzione vincente + Termina sondaggio + + Risultato finale basato su %1$d voto + Risultato finale basato su %1$d voti + + + Nessun voto + %1$d voti. Vota per vedere i risultati + + + Basato su %1$d voto + Basato su %1$d voti + + + %1$d voto + %1$d voti + + Impostazioni di sistema + Versioni + Ricevi aiuto nell\'uso di Element + Aiuto e supporto + Aiuto + Informazioni legali + Questo server non presenta alcuna informativa. + Librerie di terze parti + L\'informativa del tuo server d\'identità + L\'informativa del tuo homeserver + Informativa di ${app_name} + Puoi disattivarlo in qualsiasi momento nelle impostazioni + Non condividiamo informazioni con terze parti + Non registriamo o profiliamo alcun dato dell\'account + qui + Aiutaci a identificare problemi e a migliorare Element condividendo dati di utilizzo anonimi. Per capire come le persone usano diversi dispositivi, genereremo un identificativo casuale, condiviso dai tuoi dispositivi. +\n +\nPuoi leggere i nostri termini di servizio %s. + Aiuta a migliorare Element + Attiva \ No newline at end of file From ad763e6fb3ffc2aeb1d02c51870aedbd43dfef4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ar=C5=ABnas=20Sve=C4=8Diulis?= Date: Wed, 15 Dec 2021 08:00:05 +0000 Subject: [PATCH 062/632] Translated using Weblate (Lithuanian) Currently translated at 4.5% (125 of 2725 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/lt/ --- vector/src/main/res/values-lt/strings.xml | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/vector/src/main/res/values-lt/strings.xml b/vector/src/main/res/values-lt/strings.xml index b18595388c..60176d6df7 100644 --- a/vector/src/main/res/values-lt/strings.xml +++ b/vector/src/main/res/values-lt/strings.xml @@ -99,7 +99,7 @@ Jūs pradėjote vaizdo skambutį. %s pradėjo vaizdo skambutį. %1$s pakeitė kambario pavadinimą į %2$s - Jūs pakeitėte kambario nuotrauka + Jūs pakeitėte kambario nuotrauką Jūs pakeitėte temą į %1$s %1$s pakeitė temą į: %2$s Jūs pakeitėte savo vardą iš %1$s į %2$s @@ -108,4 +108,17 @@ %1$s pakeitė savo vardą į %2$s Jūs pakeitėte savo profilio nuotrauką %1$s pakeitė savo profilio nuotrauką + arba + Daugiau rezultatų nėra + Rezultatų nėra + %s išsiuntė duomenis pokalbio nustatymui. + Jūs pakeitėte kambario pavadinimą į: %1$s + Jūs pakvietėte %1$s. Priežastis: %2$s + %1$s pakvietė %2$s. Priežastis: %3$s + Jūsų pakvietimas. Priežastis: %1$s + %1$s\'s pakvietimas. Priežastis: %2$s + %1$s pakvietė jus. Priežastis: %2$s + (profilio nuotrauka taip pat buvo pakeista) + Jūs pašalinote savo vardą (buvo %1$s) + %1$s pašalino savo vardą (buvo %2$s) \ No newline at end of file From d594ee780a39ca64b93e25bf0b2fc3e2c24bd687 Mon Sep 17 00:00:00 2001 From: AmeliMeow Date: Wed, 15 Dec 2021 07:18:27 +0000 Subject: [PATCH 063/632] Translated using Weblate (Lithuanian) Currently translated at 4.5% (125 of 2725 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/lt/ --- vector/src/main/res/values-lt/strings.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/vector/src/main/res/values-lt/strings.xml b/vector/src/main/res/values-lt/strings.xml index 60176d6df7..e981623b7a 100644 --- a/vector/src/main/res/values-lt/strings.xml +++ b/vector/src/main/res/values-lt/strings.xml @@ -121,4 +121,8 @@ (profilio nuotrauka taip pat buvo pakeista) Jūs pašalinote savo vardą (buvo %1$s) %1$s pašalino savo vardą (buvo %2$s) + Visi kambario dalyviai, nuo tada, kai jie prisijungė prie kambario. + Jūs baigėte skambutį. + Jūs pradėjote balso skambutį. + %1$s pakeitė kambario nuotrauką \ No newline at end of file From eb18bbb3b8e340788e2f7bd6f4cd6f78fab20d50 Mon Sep 17 00:00:00 2001 From: lvre <7uu3qrbvm@relay.firefox.com> Date: Tue, 14 Dec 2021 21:38:26 +0000 Subject: [PATCH 064/632] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (2725 of 2725 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 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vector/src/main/res/values-pt-rBR/strings.xml b/vector/src/main/res/values-pt-rBR/strings.xml index b11a38e8f7..8e31626f6f 100644 --- a/vector/src/main/res/values-pt-rBR/strings.xml +++ b/vector/src/main/res/values-pt-rBR/strings.xml @@ -3141,11 +3141,11 @@ \n \nVocê pode ler todos os nossos termos %s. Ajudar a melhorar Element - Configurações do sistema + Configurações de sistema Ajuda e suporte Ajuda - Você pode desativar isso a qualquer momento nas configurações - Nós não compartilhamos informações com terceiros + Você pode desativar isto a qualquer hora em configurações + Nós não compartilhamos informação com terceiros aqui Habilitar \ No newline at end of file From e6a94d63873513d52dfa906bebc065d4f728b834 Mon Sep 17 00:00:00 2001 From: Besnik Bleta Date: Tue, 14 Dec 2021 13:00:52 +0000 Subject: [PATCH 065/632] Translated using Weblate (Albanian) Currently translated at 99.4% (2711 of 2725 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 | 47 ++++++++++++++++++++++- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/vector/src/main/res/values-sq/strings.xml b/vector/src/main/res/values-sq/strings.xml index 8f2abca53a..c12fb416cd 100644 --- a/vector/src/main/res/values-sq/strings.xml +++ b/vector/src/main/res/values-sq/strings.xml @@ -1943,7 +1943,7 @@ Ky sesion është i besuar për shkëmbim të sigurt të mesazheve, ngaqë e verifikoi %1$s (%2$s): Gatit CrossSigning Zeroji Kyçet - Thuaje mbërritëm! A shfaq %s të njëjtën mburojë\? + Thuaje mbërritëm! A shfaq %s një shenjë\? Jo Humbi lidhja me shërbyesin Emër përdoruesi @@ -2041,7 +2041,7 @@ Fshehtëzim jo i aktivizuar Fshehtëzimi i përdorur nga kjo dhomë nuk mbulohet %s krijoi dhe formësoi dhomën. - Thuajse mbërritëm! A shfaq pajisja juaj tjetër të njëjtën mburojë\? + Thuajse mbërritëm! A shfaq pajisja juaj tjetër një shenjë\? Thuajse mbërritëm! Po pritet për ripohim… Po pritet për %s… S’u arrit të importohen kyçe @@ -3019,4 +3019,47 @@ Është bërë dalja nga sesioni! U dol nga dhoma! Jo tani + Jeni i sigurt se doni të hiqet ky pyetësor\? Po u hoq, s’do të jeni në gjendje ta rimerrni. + Hiqe pyetësorin + Pyetësori përfundoi + U votua + Aktivizo Pyetësorë + Përfundoje pyetësorin + Kjo do t’ua bëjë njerëzve të pamundur të votojnë dhe do të shfaqë rezultatet përfundimtare të pyetësorit. + Të përfundohet pyetësori\? + Përfundoje pyetësorin + + Rezultati përfundimtar, bazua në %1$d votë + Rezultati përfundimtar, bazua në %1$d vota + + + Bazuar në %1$d votë + Bazuar në %1$d vota + + + %1$d votë + %1$d vota + + Jo i passhëm + Pyetësor + Rregullime sistemi + Versione + Merrni ndihmë për përdorimin e Element-it + Ndihmë dhe asistencë + Ndihmë + Ligjore + Ky shërbyes nuk jep ndonjë palë rregullash. + Biblioteka prej palësh të treta + Rregulla të shërbyesit tuaj të identiteteve + Rregulla të shërbyesit tuaj Home + Rregulla për ${app_name} + Këtë mund të çaktivizoni në çfarëdo kohe, që nga rregullimet + Nuk u japin hollësi palëve të treta + Nuk regjistrojmë ose profilizojmë ndonjë të dhënë llogarie + këtu + Ndihmonani të identifikojmë probleme dhe të përmirësojmë Element-in, duke ndarë me ne të dhëna anonime përdorimi. Për të kuptuar se si i përdorin njerëzit disa pajisje njëherësh, do të prodhojmë një identifikues kuturu, të përbashkët për pajisjet tuaja. +\n +\nMund të lexoni krejt kushtet tona %s. + Ndihmoni të përmirësohet Element-in + Aktivizoje \ No newline at end of file From 531c06da503a4b1f698973da70443dbee85303d5 Mon Sep 17 00:00:00 2001 From: Denys Nykula Date: Tue, 14 Dec 2021 17:30:03 +0000 Subject: [PATCH 066/632] Translated using Weblate (Ukrainian) Currently translated at 100.0% (2725 of 2725 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 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/res/values-uk/strings.xml b/vector/src/main/res/values-uk/strings.xml index 66d27ca3cb..fe9304f52c 100644 --- a/vector/src/main/res/values-uk/strings.xml +++ b/vector/src/main/res/values-uk/strings.xml @@ -1356,7 +1356,7 @@ Встановіть парольну фразу Ми збережемо зашифровану копію ваших ключів на вашому домашньому сервері. Захистіть свою резервну копію парольною фразою, щоб захистити її. \n -\nДля максимальної безпеки він повинен відрізнятися від пароля вашого облікового запису. +\nДля максимальної безпеки фраза повинна відрізнятися від паролю вашого облікового запису. Захистіть свою резервну копію за допомогою парольної фрази. Експорт ключів вручну Почати використовувати резервне копіювання ключів From 7c791e3482a74f8345ca2f892db7d77520aa36c5 Mon Sep 17 00:00:00 2001 From: Ihor Hordiichuk Date: Tue, 14 Dec 2021 14:48:05 +0000 Subject: [PATCH 067/632] Translated using Weblate (Ukrainian) Currently translated at 100.0% (2725 of 2725 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 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vector/src/main/res/values-uk/strings.xml b/vector/src/main/res/values-uk/strings.xml index fe9304f52c..68fc3ff5eb 100644 --- a/vector/src/main/res/values-uk/strings.xml +++ b/vector/src/main/res/values-uk/strings.xml @@ -3233,9 +3233,9 @@ Отримати допомогу в використанні Element Довідка й підтримка Довідка - Юридичне + Правові положення Цей сервер не надає політики. - Бібліотеки сторонніх осіб + Сторонні бібліотеки Політика вашого сервера ідентифікації Політика вашого домашнього сервера Політика ${app_name} @@ -3247,5 +3247,5 @@ \n \nМожете прочитати всі наші умови %s. Допоможіть покращити Element - Ввімкнути + Увімкнути \ No newline at end of file From 75ca3bab09a45bc74d7d290d09fb5800a989581e Mon Sep 17 00:00:00 2001 From: sr093906 Date: Tue, 14 Dec 2021 12:22:02 +0000 Subject: [PATCH 068/632] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (2725 of 2725 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/zh_Hans/ --- vector/src/main/res/values-zh-rCN/strings.xml | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/vector/src/main/res/values-zh-rCN/strings.xml b/vector/src/main/res/values-zh-rCN/strings.xml index a7afd7aadb..3780cff69c 100644 --- a/vector/src/main/res/values-zh-rCN/strings.xml +++ b/vector/src/main/res/values-zh-rCN/strings.xml @@ -2985,4 +2985,46 @@ 你同意发送此信息吗? 要发现现有的联系人,您需要将联系人信息(电子邮件和电话号码)发送到您的身份服务器。出乎隐私考量,我们会在发送前对您的数据进行散列处理。 不是现在 + 您确定要删除此投票吗?一旦移除,就无法恢复。 + 删除投票 + 投票已结束 + 已投票 + 启用投票 + 结束投票 + 这将使人们无法再投票,并将显示投票的最终结果。 + 结束此投票? + 赢家选项 + 结束投票 + + 基于 %1$d 票的最终结果 + + + 已投了 %1$d 票。投票查看结果 + + + 基于 %1$d 票 + + + %1$d 票 + + 系统设置 + 版本 + 获取使用 Element 的帮助 + 帮助和支持 + 帮助 + 法律 + 此服务器不提供任何政策。 + 第三方库 + 你的身份服务器政策 + 我们记录任何账户数据或绘制任何账户数据的画像 + 你的主服务器政策 + ${app_name} 政策 + 你可以随时在设置中关闭它 + 我们与第三方共享信息 + 此处 + 通过共享匿名使用数据,帮助我们识别问题并改进 Element。为了理解人们如何使用多台设备,我们将生成一个随机标识符,由您的设备共享。 +\n +\n你可以阅读我们所有的条款 %s。 + 帮助改进 Element + 启用 \ No newline at end of file From 2d490a98cd8e054df1f8c689729bc619edba01d6 Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Wed, 15 Dec 2021 02:03:02 +0000 Subject: [PATCH 069/632] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (2725 of 2725 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 | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/vector/src/main/res/values-zh-rTW/strings.xml b/vector/src/main/res/values-zh-rTW/strings.xml index 7d7e7dbb0e..261a48ba45 100644 --- a/vector/src/main/res/values-zh-rTW/strings.xml +++ b/vector/src/main/res/values-zh-rTW/strings.xml @@ -2975,4 +2975,46 @@ 您同意傳送此資訊嗎? 要探索現有聯絡人,您必須傳送聯絡人資訊(電子郵件與電話號碼)到您的身份識別伺服器。我們會在傳送前對您的資料進行雜湊處理以保護隱私。 現在不要 + 您確定要移除此投票?移除後將無法復原。 + 移除投票 + 投票已結束 + 投票 + 啟用投票 + 結束投票 + 這將阻止人們投票並顯示投票的最終結果。 + 結束此投票? + 獲勝選項 + 結束投票 + + 以 %1$d 票為基礎的最終結果 + + + 已投 %1$d 票。投票以檢視結果 + + + 以 %1$d 票為基礎 + + + %1$d 票 + + 系統設定 + 版本 + 取得關於使用 Element 的協助 + 說明與支援 + 說明 + 法律 + 此伺服器並未提供任何政策。 + 第三方函式庫 + 您的身份識別伺服器政策 + 您的家伺服器政策 + ${app_name} 政策 + 您隨時可以在設定中關閉此功能 + 我們不會與第三方分享資訊 + 我們不會記錄或分析任何帳號資料 + 這裡 + 透過分享匿名使用資料協助我們找出問題並改善 Element。為了了解人們如何使用多裝置,我們將會產生隨機識別字串,在您的裝置間共享。 +\n +\n您可以閱讀我們的條款 %s。 + 協助改善 Element + 啟用 \ No newline at end of file From 497f9556f80baa35a40d370a30dd52cc8dda28e2 Mon Sep 17 00:00:00 2001 From: lvre <7uu3qrbvm@relay.firefox.com> Date: Tue, 14 Dec 2021 21:20:30 +0000 Subject: [PATCH 070/632] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (40 of 40 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/40103090.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/pt-BR/changelogs/40103090.txt diff --git a/fastlane/metadata/android/pt-BR/changelogs/40103090.txt b/fastlane/metadata/android/pt-BR/changelogs/40103090.txt new file mode 100644 index 0000000000..9f67fd2d62 --- /dev/null +++ b/fastlane/metadata/android/pt-BR/changelogs/40103090.txt @@ -0,0 +1,2 @@ +Principais mudanças nesta versão: Adicionar suporte para rascunho de mensagem de voz. Muitos consertos de bugs! +Changelog completo: https://github.com/vector-im/element-android/releases/tag/v1.3.9 From 3b70ea1e0334e9cbd108c62f871091b8b9735c69 Mon Sep 17 00:00:00 2001 From: LinAGKar Date: Tue, 14 Dec 2021 20:43:06 +0000 Subject: [PATCH 071/632] Translated using Weblate (Swedish) Currently translated at 100.0% (40 of 40 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/40103090.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/sv-SE/changelogs/40103090.txt diff --git a/fastlane/metadata/android/sv-SE/changelogs/40103090.txt b/fastlane/metadata/android/sv-SE/changelogs/40103090.txt new file mode 100644 index 0000000000..dce7ffe5a7 --- /dev/null +++ b/fastlane/metadata/android/sv-SE/changelogs/40103090.txt @@ -0,0 +1,2 @@ +Huvudsakliga ändringar i den här versionen: Lägg till stöd för röstmeddelandeutkast. Många buggfixar! +Full ändringslogg: https://github.com/vector-im/element-android/releases/tag/v1.3.9 From e7a1838f1cb7bcadcef079ae18baf6d566096710 Mon Sep 17 00:00:00 2001 From: Denys Nykula Date: Tue, 14 Dec 2021 11:52:08 +0000 Subject: [PATCH 072/632] Translated using Weblate (Ukrainian) Currently translated at 100.0% (40 of 40 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/uk/ --- fastlane/metadata/android/uk/changelogs/40103090.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/uk/changelogs/40103090.txt diff --git a/fastlane/metadata/android/uk/changelogs/40103090.txt b/fastlane/metadata/android/uk/changelogs/40103090.txt new file mode 100644 index 0000000000..37f8959d4c --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/40103090.txt @@ -0,0 +1,2 @@ +Основні зміни в цій версії: підтримка чернеток голосових повідомлень. Багато виправлень помилок! +Повний журнал змін: https://github.com/vector-im/element-android/releases/tag/v1.3.9 From 9478ac8450acad81a7cb216119e685bb8ffe30df Mon Sep 17 00:00:00 2001 From: sr093906 Date: Tue, 14 Dec 2021 12:11:22 +0000 Subject: [PATCH 073/632] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (40 of 40 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/zh_Hans/ --- fastlane/metadata/android/zh-CN/changelogs/40103090.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/zh-CN/changelogs/40103090.txt diff --git a/fastlane/metadata/android/zh-CN/changelogs/40103090.txt b/fastlane/metadata/android/zh-CN/changelogs/40103090.txt new file mode 100644 index 0000000000..7eb68d61e4 --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/40103090.txt @@ -0,0 +1,2 @@ +版本的主要变化:增加了对语音信息草稿的支持。许多修正! +完整更新日志:https://github.com/vector-im/element-android/releases/tag/v1.3.9 From ef1356ded5c4b0d9e87aeab7243a4e9b05645a4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Tue, 14 Dec 2021 12:48:49 +0000 Subject: [PATCH 074/632] Translated using Weblate (Estonian) Currently translated at 100.0% (40 of 40 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/et/ --- fastlane/metadata/android/et/changelogs/40103090.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/et/changelogs/40103090.txt diff --git a/fastlane/metadata/android/et/changelogs/40103090.txt b/fastlane/metadata/android/et/changelogs/40103090.txt new file mode 100644 index 0000000000..e931ba5386 --- /dev/null +++ b/fastlane/metadata/android/et/changelogs/40103090.txt @@ -0,0 +1,2 @@ +Põhilised muutused selles versioonis: Häälsõnumite võimalus. Palju veaparandusi! +Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases/tag/v1.3.9 From ca61d4d4ee211a88dba649fc75a4f745e658f635 Mon Sep 17 00:00:00 2001 From: random Date: Tue, 14 Dec 2021 14:04:16 +0000 Subject: [PATCH 075/632] Translated using Weblate (Italian) Currently translated at 100.0% (40 of 40 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/40103090.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/it-IT/changelogs/40103090.txt diff --git a/fastlane/metadata/android/it-IT/changelogs/40103090.txt b/fastlane/metadata/android/it-IT/changelogs/40103090.txt new file mode 100644 index 0000000000..d91ecfe530 --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/40103090.txt @@ -0,0 +1,2 @@ +Modifiche principali in questa versione: aggiunto supporto per le bozze dei vocali. Molte correzioni! +Cronologia completa: https://github.com/vector-im/element-android/releases/tag/v1.3.9 From e4fad7a477e82bf7149747afb569be6b42a48222 Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Wed, 15 Dec 2021 01:53:26 +0000 Subject: [PATCH 076/632] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (40 of 40 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/40103090.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/zh-TW/changelogs/40103090.txt diff --git a/fastlane/metadata/android/zh-TW/changelogs/40103090.txt b/fastlane/metadata/android/zh-TW/changelogs/40103090.txt new file mode 100644 index 0000000000..c74a27acbf --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/40103090.txt @@ -0,0 +1,2 @@ +此版本中的主要變動:新增對語音訊息草稿的支援。許多臭蟲修復! +完整的變更紀錄:https://github.com/vector-im/element-android/releases/tag/v1.3.9 From 043877bcfd81c6f8874fc5c56eeefe403f070cf0 Mon Sep 17 00:00:00 2001 From: waclaw66 Date: Tue, 14 Dec 2021 11:13:01 +0000 Subject: [PATCH 077/632] Translated using Weblate (Czech) Currently translated at 100.0% (40 of 40 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/40103090.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/cs-CZ/changelogs/40103090.txt diff --git a/fastlane/metadata/android/cs-CZ/changelogs/40103090.txt b/fastlane/metadata/android/cs-CZ/changelogs/40103090.txt new file mode 100644 index 0000000000..fe61a48d12 --- /dev/null +++ b/fastlane/metadata/android/cs-CZ/changelogs/40103090.txt @@ -0,0 +1,2 @@ +Hlavní změny v této verzi: Přidání podpory pro návrh hlasové zprávy. Opravy mnoha chyb! +Úplný seznam změn: https://github.com/vector-im/element-android/releases/tag/v1.3.9 From 3df08d1866f59b19f3094e9b8d82f784a66bd28e Mon Sep 17 00:00:00 2001 From: Linerly Date: Tue, 14 Dec 2021 11:33:41 +0000 Subject: [PATCH 078/632] Translated using Weblate (Indonesian) Currently translated at 100.0% (40 of 40 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/id/ --- fastlane/metadata/android/id/changelogs/40103090.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/id/changelogs/40103090.txt diff --git a/fastlane/metadata/android/id/changelogs/40103090.txt b/fastlane/metadata/android/id/changelogs/40103090.txt new file mode 100644 index 0000000000..b371ba9fab --- /dev/null +++ b/fastlane/metadata/android/id/changelogs/40103090.txt @@ -0,0 +1,2 @@ +Perubahan utama di versi ini: Tambahkan dukungan untuk draf pesan suara. Banyak perbaikan bug! +Changelog lengkap: https://github.com/vector-im/element-android/releases/tag/v1.3.9 From ce95131d2a9f3a2a7dabfa079c72f8ae67a2f866 Mon Sep 17 00:00:00 2001 From: Besnik Bleta Date: Tue, 14 Dec 2021 12:35:28 +0000 Subject: [PATCH 079/632] Translated using Weblate (Albanian) Currently translated at 100.0% (40 of 40 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/sq/ --- fastlane/metadata/android/sq/changelogs/40103090.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/sq/changelogs/40103090.txt diff --git a/fastlane/metadata/android/sq/changelogs/40103090.txt b/fastlane/metadata/android/sq/changelogs/40103090.txt new file mode 100644 index 0000000000..2dae814fc1 --- /dev/null +++ b/fastlane/metadata/android/sq/changelogs/40103090.txt @@ -0,0 +1,2 @@ +Ndryshimet kryesore në këtë version: Shtim mbulimi për skica mesazhesh zanore. Mjaft ndreqje të metash! +Regjistër i plotë ndryshimesh: https://github.com/vector-im/element-android/releases/tag/v1.3.9 From 13dc2d5b7642430fd6293bb966985f913b72587a Mon Sep 17 00:00:00 2001 From: David Langley Date: Wed, 15 Dec 2021 10:03:14 +0000 Subject: [PATCH 080/632] Fix documentation. --- .../org/matrix/android/sdk/api/session/room/send/SendService.kt | 2 -- .../sdk/internal/session/room/send/LocalEchoEventFactory.kt | 1 - .../android/sdk/internal/session/room/send/MarkdownParser.kt | 1 + 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt index 6496ddc2ca..de48a3011f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt @@ -60,8 +60,6 @@ interface SendService { * Method to quote an events content. * @param quotedEvent The event to which we will quote it's content. * @param text the text message to send - * @param msgType the message type: MessageType.MSGTYPE_TEXT (default) or MessageType.MSGTYPE_EMOTE - * @param autoMarkdown If true, the SDK will generate a formatted HTML message from the body text if markdown syntax is present * @return a [Cancelable] */ fun sendQuotedTextMessage(quotedEvent: TimelineEvent, text: String): Cancelable diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt index 9e279dfa44..4367ad524f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt @@ -20,7 +20,6 @@ import android.content.Context import android.graphics.Bitmap import android.media.MediaMetadataRetriever import androidx.exifinterface.media.ExifInterface -import okhttp3.internal.format import org.matrix.android.sdk.api.session.content.ContentAttachmentData import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.events.model.Event diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/MarkdownParser.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/MarkdownParser.kt index 8f963ba16d..c7ea842a4f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/MarkdownParser.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/MarkdownParser.kt @@ -40,6 +40,7 @@ internal class MarkdownParser @Inject constructor( * Parses some input text and produces html. * @param text An input CharSequence to be parsed. * @param force Skips the check for detecting if the input contains markdown and always converts to html. + * @param advanced Whether to use the full markdown support or the simple version. * @return TextContent containing the plain text and the formatted html if generated. */ fun parse(text: CharSequence, force: Boolean = false, advanced: Boolean = true): TextContent { From 824e713c51c5aa5b89a85ed5e4c105f5e76a4ba8 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Wed, 15 Dec 2021 13:57:43 +0300 Subject: [PATCH 081/632] Add mapview to the layout. --- build.gradle | 2 ++ vector/build.gradle | 3 +++ .../location/LocationSharingFragment.kt | 19 +++++++++++++++++++ .../res/layout/fragment_location_sharing.xml | 5 +++++ 4 files changed, 29 insertions(+) diff --git a/build.gradle b/build.gradle index e17f357905..29c3e61a44 100644 --- a/build.gradle +++ b/build.gradle @@ -61,6 +61,8 @@ allprojects { groups.jitsi.group.each { includeGroup it } } } + // TODO. MapTiler + mavenCentral() google { content { groups.google.regex.each { includeGroupByRegex it } diff --git a/vector/build.gradle b/vector/build.gradle index a578fdb52f..c3dd6ba50c 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -486,6 +486,9 @@ dependencies { } implementation 'commons-codec:commons-codec:1.15' + // MapTiler + implementation 'org.maplibre.gl:android-sdk:9.5.2' + // TESTS testImplementation libs.tests.junit 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 fd2e48546d..7c59972871 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 @@ -21,6 +21,8 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import com.airbnb.mvrx.activityViewModel +import com.mapbox.mapboxsdk.Mapbox +import im.vector.app.BuildConfig import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentLocationSharingBinding import javax.inject.Inject @@ -34,7 +36,24 @@ class LocationSharingFragment @Inject constructor() : return FragmentLocationSharingBinding.inflate(inflater, container, false) } + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + // Initialize Mapbox before inflating mapView + Mapbox.getInstance(requireContext()) + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + + initMapView(savedInstanceState) + } + + private fun initMapView(savedInstanceState: Bundle?) { + val key = BuildConfig.mapTilerKey + val styleUrl = "https://api.maptiler.com/maps/streets/style.json?key=${key}" + views.mapView.onCreate(savedInstanceState) + views.mapView.getMapAsync { map -> + map.setStyle(styleUrl) + } } } diff --git a/vector/src/main/res/layout/fragment_location_sharing.xml b/vector/src/main/res/layout/fragment_location_sharing.xml index 77d9ef65f8..6399b41796 100644 --- a/vector/src/main/res/layout/fragment_location_sharing.xml +++ b/vector/src/main/res/layout/fragment_location_sharing.xml @@ -3,4 +3,9 @@ android:layout_width="match_parent" android:layout_height="match_parent"> + + \ No newline at end of file From 5904a5955fed630169ee8ee669407e2412204e6c Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Wed, 15 Dec 2021 21:07:16 +0300 Subject: [PATCH 082/632] Show and track the current location of the user on map. --- vector/build.gradle | 1 + .../im/vector/app/core/di/FragmentModule.kt | 6 ++ .../location/LocationSharingFragment.kt | 102 +++++++++++++++++- .../app/features/location/LocationTracker.kt | 80 ++++++++++++++ .../src/main/res/drawable/bg_map_user_pin.xml | 10 ++ .../res/layout/fragment_location_sharing.xml | 38 +++++++ vector/src/main/res/values/strings.xml | 2 + 7 files changed, 235 insertions(+), 4 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/location/LocationTracker.kt create mode 100644 vector/src/main/res/drawable/bg_map_user_pin.xml diff --git a/vector/build.gradle b/vector/build.gradle index c3dd6ba50c..197349f0f1 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -488,6 +488,7 @@ dependencies { // MapTiler implementation 'org.maplibre.gl:android-sdk:9.5.2' + implementation 'org.maplibre.gl:android-plugin-annotation-v9:1.0.0' // TESTS diff --git a/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt b/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt index ff84a46dab..3314c0565e 100644 --- a/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt @@ -61,6 +61,7 @@ import im.vector.app.features.home.room.breadcrumbs.BreadcrumbsFragment import im.vector.app.features.home.room.detail.RoomDetailFragment import im.vector.app.features.home.room.detail.search.SearchFragment import im.vector.app.features.home.room.list.RoomListFragment +import im.vector.app.features.location.LocationSharingFragment import im.vector.app.features.login.LoginCaptchaFragment import im.vector.app.features.login.LoginFragment import im.vector.app.features.login.LoginGenericTextInputFormFragment @@ -855,4 +856,9 @@ interface FragmentModule { @IntoMap @FragmentKey(CreatePollFragment::class) fun bindCreatePollFragment(fragment: CreatePollFragment): Fragment + + @Binds + @IntoMap + @FragmentKey(LocationSharingFragment::class) + fun bindLocationSharingFragment(fragment: LocationSharingFragment): Fragment } 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 7c59972871..390d1aafa5 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 @@ -16,22 +16,55 @@ package im.vector.app.features.location +import android.graphics.drawable.Drawable +import android.graphics.drawable.LayerDrawable import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.core.content.ContextCompat import com.airbnb.mvrx.activityViewModel +import com.bumptech.glide.request.target.CustomTarget +import com.bumptech.glide.request.transition.Transition import com.mapbox.mapboxsdk.Mapbox +import com.mapbox.mapboxsdk.camera.CameraPosition +import com.mapbox.mapboxsdk.geometry.LatLng +import com.mapbox.mapboxsdk.maps.MapboxMap +import com.mapbox.mapboxsdk.maps.Style +import com.mapbox.mapboxsdk.plugins.annotation.SymbolManager +import com.mapbox.mapboxsdk.plugins.annotation.SymbolOptions +import com.mapbox.mapboxsdk.style.layers.Property import im.vector.app.BuildConfig +import im.vector.app.R +import im.vector.app.core.glide.GlideApp import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentLocationSharingBinding +import im.vector.app.features.home.AvatarRenderer +import org.billcarsonfr.jsonviewer.Utils +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.util.toMatrixItem import javax.inject.Inject -class LocationSharingFragment @Inject constructor() : - VectorBaseFragment() { +class LocationSharingFragment @Inject constructor( + private val locationTracker: LocationTracker, + private val session: Session, + private val avatarRenderer: AvatarRenderer +) : VectorBaseFragment(), LocationTracker.Callback { + + init { + locationTracker.callback = this + } private val viewModel: LocationSharingViewModel by activityViewModel() + private val glideRequests by lazy { + GlideApp.with(this) + } + + private var map: MapboxMap? = null + private var symbolManager: SymbolManager? = null + private var lastZoomValue: Double = -1.0 + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLocationSharingBinding { return FragmentLocationSharingBinding.inflate(inflater, container, false) } @@ -48,12 +81,73 @@ class LocationSharingFragment @Inject constructor() : initMapView(savedInstanceState) } + override fun onDestroyView() { + super.onDestroyView() + locationTracker.stop() + } + private fun initMapView(savedInstanceState: Bundle?) { val key = BuildConfig.mapTilerKey - val styleUrl = "https://api.maptiler.com/maps/streets/style.json?key=${key}" + val styleUrl = "https://api.maptiler.com/maps/streets/style.json?key=$key" views.mapView.onCreate(savedInstanceState) views.mapView.getMapAsync { map -> - map.setStyle(styleUrl) + map.setStyle(styleUrl) { style -> + addUserPinToMap(style) + this.symbolManager = SymbolManager(views.mapView, map, style) + this.map = map + // All set, start location tracker + locationTracker.start() + } } } + + private fun addUserPinToMap(style: Style) { + session.getUser(session.myUserId)?.toMatrixItem()?.let { + val size = Utils.dpToPx(44, requireContext()) + avatarRenderer.render(glideRequests, it, object : CustomTarget(size, size) { + override fun onResourceReady(resource: Drawable, transition: Transition?) { + val bgUserPin = ContextCompat.getDrawable(requireActivity(), R.drawable.bg_map_user_pin)!! + val layerDrawable = LayerDrawable(arrayOf(bgUserPin, resource)) + val horizontalInset = Utils.dpToPx(4, requireContext()) + val topInset = Utils.dpToPx(4, requireContext()) + val bottomInset = Utils.dpToPx(8, requireContext()) + layerDrawable.setLayerInset(1, horizontalInset, topInset, horizontalInset, bottomInset) + + style.addImage( + USER_PIN_NAME, + layerDrawable + ) + } + + override fun onLoadCleared(placeholder: Drawable?) { + // Is it possible? Put placeholder instead? + } + }) + } + } + + override fun onLocationUpdate(latitude: Double, longitude: Double) { + lastZoomValue = if (lastZoomValue == -1.0) INITIAL_ZOOM else map?.cameraPosition?.zoom ?: INITIAL_ZOOM + + val latLng = LatLng(latitude, longitude) + + map?.cameraPosition = CameraPosition.Builder() + .target(latLng) + .zoom(lastZoomValue) + .build() + + symbolManager?.deleteAll() + + symbolManager?.create( + SymbolOptions() + .withLatLng(latLng) + .withIconImage(USER_PIN_NAME) + .withIconAnchor(Property.ICON_ANCHOR_BOTTOM) + ) + } + + companion object { + const val INITIAL_ZOOM = 12.0 + const val USER_PIN_NAME = "USER_PIN_NAME" + } } diff --git a/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt b/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt new file mode 100644 index 0000000000..3cf2689aee --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt @@ -0,0 +1,80 @@ +/* + * 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.location + +import android.content.Context +import android.location.Location +import android.location.LocationListener +import android.location.LocationManager +import timber.log.Timber +import javax.inject.Inject + +class LocationTracker @Inject constructor( + private val context: Context) : LocationListener { + + interface Callback { + fun onLocationUpdate(latitude: Double, longitude: Double) + } + + private var locationManager: LocationManager? = null + var callback: Callback? = null + + fun start() { + val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as? LocationManager + + locationManager?.let { + val isGpsEnabled = it.isProviderEnabled(LocationManager.GPS_PROVIDER) + val isNetworkEnabled = it.isProviderEnabled(LocationManager.NETWORK_PROVIDER) + + val provider = when { + isGpsEnabled -> LocationManager.GPS_PROVIDER + isNetworkEnabled -> LocationManager.NETWORK_PROVIDER + else -> { + Timber.v("## LocationTracker. There is no location provider available") + return + } + } + + // Send last known location without waiting location updates + it.getLastKnownLocation(provider)?.let { lastKnownLocation -> + callback?.onLocationUpdate(lastKnownLocation.latitude, lastKnownLocation.longitude) + } + + it.requestLocationUpdates( + provider, + MIN_TIME_MILLIS_TO_UPDATE, + MIN_DISTANCE_METERS_TO_UPDATE, + this + ) + } ?: run { + Timber.v("## LocationTracker. LocationManager is not available") + } + } + + fun stop() { + locationManager?.removeUpdates(this) + } + + override fun onLocationChanged(location: Location) { + callback?.onLocationUpdate(location.latitude, location.longitude) + } + + companion object { + const val MIN_TIME_MILLIS_TO_UPDATE = 1 * 60 * 1000L // every 1 minute + const val MIN_DISTANCE_METERS_TO_UPDATE = 10f + } +} diff --git a/vector/src/main/res/drawable/bg_map_user_pin.xml b/vector/src/main/res/drawable/bg_map_user_pin.xml new file mode 100644 index 0000000000..148d3cfa29 --- /dev/null +++ b/vector/src/main/res/drawable/bg_map_user_pin.xml @@ -0,0 +1,10 @@ + + + diff --git a/vector/src/main/res/layout/fragment_location_sharing.xml b/vector/src/main/res/layout/fragment_location_sharing.xml index 6399b41796..bc587dc182 100644 --- a/vector/src/main/res/layout/fragment_location_sharing.xml +++ b/vector/src/main/res/layout/fragment_location_sharing.xml @@ -1,5 +1,6 @@ @@ -8,4 +9,41 @@ android:layout_width="match_parent" android:layout_height="match_parent" /> + + + + + + + + \ 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 0f26861d36..166f25c053 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -3706,4 +3706,6 @@ Share location Location + Share location + Share location From a3a8a5b0b57e31371c3e475ac720d8334b635980 Mon Sep 17 00:00:00 2001 From: David Langley Date: Thu, 16 Dec 2021 12:47:06 +0000 Subject: [PATCH 083/632] Extract reply formatting --- .../room/send/LocalEchoEventFactory.kt | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt index 4367ad524f..c6b6b9352a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt @@ -208,12 +208,11 @@ internal class LocalEchoEventFactory @Inject constructor( val newBodyFormatted = markdownParser.parse(newBodyText, force = true, advanced = false).takeFormatted() // Body of the original message may not have formatted version, so may also have to convert to html. val bodyFormatted = body.formattedText ?: markdownParser.parse(newBodyText, force = true, advanced = false).takeFormatted() - val replyFormatted = REPLY_PATTERN.format( + val replyFormatted = buildFormattedReply( permalink, userLink, originalEvent.senderInfo.disambiguatedDisplayName, - // Remove inner mx_reply tags if any - bodyFormatted.replace(MX_REPLY_REGEX, ""), + bodyFormatted, newBodyFormatted ) // @@ -398,12 +397,11 @@ internal class LocalEchoEventFactory @Inject constructor( val replyTextFormatted = markdownParser.parse(replyText, force = true, advanced = false).takeFormatted() // Body of the original message may not have formatted version, so may also have to convert to html. val bodyFormatted = body.formattedText ?: markdownParser.parse(replyText, force = true, advanced = false).takeFormatted() - val replyFormatted = REPLY_PATTERN.format( + val replyFormatted = buildFormattedReply( permalink, userLink, userId, - // Remove inner mx_reply tags if any - bodyFormatted.replace(MX_REPLY_REGEX, ""), + bodyFormatted, replyTextFormatted ) // @@ -422,6 +420,16 @@ internal class LocalEchoEventFactory @Inject constructor( return createMessageEvent(roomId, content) } + private fun buildFormattedReply(permalink: String, userLink: String, userId: String, bodyFormatted: String, newBodyFormatted: String): String { + return REPLY_PATTERN.format( + permalink, + userLink, + userId, + // Remove inner mx_reply tags if any + bodyFormatted.replace(MX_REPLY_REGEX, ""), + newBodyFormatted + ) + } private fun buildReplyFallback(body: TextContent, originalSenderId: String?, newBodyText: String): String { return buildString { append("> <") From bc6ca2449e85eb8ca89c067e151465ee9931453d Mon Sep 17 00:00:00 2001 From: David Langley Date: Thu, 16 Dec 2021 13:13:40 +0000 Subject: [PATCH 084/632] Add advanced parser annotation --- .../android/sdk/internal/session/room/RoomModule.kt | 10 +++++++++- .../sdk/internal/session/room/send/MarkdownParser.kt | 3 ++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt index 6497c997ed..0af05d8df2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt @@ -108,6 +108,13 @@ import javax.inject.Qualifier @Retention(AnnotationRetention.RUNTIME) internal annotation class SimpleCommonmarkParser +/** + * Used to inject the advanced commonmark Parser + */ +@Qualifier +@Retention(AnnotationRetention.RUNTIME) +internal annotation class AdvancedCommonmarkParser + @Module internal abstract class RoomModule { @@ -128,8 +135,9 @@ internal abstract class RoomModule { } @Provides + @AdvancedCommonmarkParser @JvmStatic - fun providesParser(): Parser { + fun providesAdvancedParser(): Parser { return Parser.builder().build() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/MarkdownParser.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/MarkdownParser.kt index c7ea842a4f..e442d051f6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/MarkdownParser.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/MarkdownParser.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.session.room.send import org.commonmark.parser.Parser import org.commonmark.renderer.html.HtmlRenderer +import org.matrix.android.sdk.internal.session.room.AdvancedCommonmarkParser import org.matrix.android.sdk.internal.session.room.SimpleCommonmarkParser import org.matrix.android.sdk.internal.session.room.send.pills.TextPillsUtils import javax.inject.Inject @@ -28,7 +29,7 @@ import javax.inject.Inject * If any change is required, please add a test covering the problem and make sure all the tests are still passing. */ internal class MarkdownParser @Inject constructor( - private val advancedParser: Parser, + @AdvancedCommonmarkParser private val advancedParser: Parser, @SimpleCommonmarkParser private val simpleParser: Parser, private val htmlRenderer: HtmlRenderer, private val textPillsUtils: TextPillsUtils From e7bb030d528131199eebc0a2e6160334bb54eecb Mon Sep 17 00:00:00 2001 From: David Langley Date: Thu, 16 Dec 2021 14:10:49 +0000 Subject: [PATCH 085/632] full markdown should work in replies, was hardcoded to simple irrespective of prefrence. --- .../room/model/relation/RelationService.kt | 4 +++- .../sdk/api/session/room/send/SendService.kt | 3 ++- .../room/relation/DefaultRelationService.kt | 4 ++-- .../internal/session/room/relation/EventEditor.kt | 3 ++- .../session/room/send/DefaultSendService.kt | 4 ++-- .../session/room/send/LocalEchoEventFactory.kt | 15 +++++++++------ .../detail/composer/MessageComposerViewModel.kt | 4 ++-- 7 files changed, 22 insertions(+), 15 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationService.kt index 0bc3120119..59d84ef40f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationService.kt @@ -104,9 +104,11 @@ interface RelationService { * by the sdk into pills. * @param eventReplied the event referenced by the reply * @param replyText the reply text + * @param autoMarkdown If true, the SDK will generate a formatted HTML message from the body text if markdown syntax is present */ fun replyToMessage(eventReplied: TimelineEvent, - replyText: CharSequence): Cancelable? + replyText: CharSequence, + autoMarkdown: Boolean = false): Cancelable? /** * Get the current EventAnnotationsSummary diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt index de48a3011f..606500c4e7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt @@ -60,9 +60,10 @@ interface SendService { * Method to quote an events content. * @param quotedEvent The event to which we will quote it's content. * @param text the text message to send + * @param autoMarkdown If true, the SDK will generate a formatted HTML message from the body text if markdown syntax is present * @return a [Cancelable] */ - fun sendQuotedTextMessage(quotedEvent: TimelineEvent, text: String): Cancelable + fun sendQuotedTextMessage(quotedEvent: TimelineEvent, text: String, autoMarkdown: Boolean): Cancelable /** * Method to send a media asynchronously. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/DefaultRelationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/DefaultRelationService.kt index c496633515..07927b1412 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/DefaultRelationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/DefaultRelationService.kt @@ -131,8 +131,8 @@ internal class DefaultRelationService @AssistedInject constructor( return fetchEditHistoryTask.execute(FetchEditHistoryTask.Params(roomId, eventId)) } - override fun replyToMessage(eventReplied: TimelineEvent, replyText: CharSequence): Cancelable? { - val event = eventFactory.createReplyTextEvent(roomId, eventReplied, replyText) + override fun replyToMessage(eventReplied: TimelineEvent, replyText: CharSequence, autoMarkdown: Boolean): Cancelable? { + val event = eventFactory.createReplyTextEvent(roomId, eventReplied, replyText, autoMarkdown) ?.also { saveLocalEcho(it) } ?: return null diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/EventEditor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/EventEditor.kt index 076eb06447..a666d40fc3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/EventEditor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/EventEditor.kt @@ -67,7 +67,7 @@ internal class EventEditor @Inject constructor(private val eventSenderProcessor: val roomId = replyToEdit.roomId if (replyToEdit.root.sendState.hasFailed()) { // We create a new in memory event for the EventSenderProcessor but we keep the eventId of the failed event. - val editedEvent = eventFactory.createReplyTextEvent(roomId, originalTimelineEvent, newBodyText)?.copy( + val editedEvent = eventFactory.createReplyTextEvent(roomId, originalTimelineEvent, newBodyText, false)?.copy( eventId = replyToEdit.eventId ) ?: return NoOpCancellable updateFailedEchoWithEvent(roomId, replyToEdit.eventId, editedEvent) @@ -78,6 +78,7 @@ internal class EventEditor @Inject constructor(private val eventSenderProcessor: replyToEdit, originalTimelineEvent, newBodyText, + true, MessageType.MSGTYPE_TEXT, compatibilityBodyText ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt index f7f29f9b34..fb2fb3950a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt @@ -97,8 +97,8 @@ internal class DefaultSendService @AssistedInject constructor( .let { sendEvent(it) } } - override fun sendQuotedTextMessage(quotedEvent: TimelineEvent, text: String): Cancelable { - return localEchoEventFactory.createQuotedTextEvent(roomId, quotedEvent, text) + override fun sendQuotedTextMessage(quotedEvent: TimelineEvent, text: String, autoMarkdown: Boolean): Cancelable { + return localEchoEventFactory.createQuotedTextEvent(roomId, quotedEvent, text, autoMarkdown) .also { createLocalEcho(it) } .let { sendEvent(it) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt index c6b6b9352a..c4caedc407 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt @@ -198,6 +198,7 @@ internal class LocalEchoEventFactory @Inject constructor( eventReplaced: TimelineEvent, originalEvent: TimelineEvent, newBodyText: String, + autoMarkdown: Boolean, msgType: String, compatibilityText: String): Event { val permalink = permalinkFactory.createPermalink(roomId, originalEvent.root.eventId ?: "", false) @@ -205,9 +206,9 @@ internal class LocalEchoEventFactory @Inject constructor( val body = bodyForReply(originalEvent.getLastMessageContent(), originalEvent.isReply()) // As we always supply formatted body for replies we should force the MarkdownParser to produce html. - val newBodyFormatted = markdownParser.parse(newBodyText, force = true, advanced = false).takeFormatted() + val newBodyFormatted = markdownParser.parse(newBodyText, force = true, advanced = autoMarkdown).takeFormatted() // Body of the original message may not have formatted version, so may also have to convert to html. - val bodyFormatted = body.formattedText ?: markdownParser.parse(newBodyText, force = true, advanced = false).takeFormatted() + val bodyFormatted = body.formattedText ?: markdownParser.parse(body.text, force = true, advanced = autoMarkdown).takeFormatted() val replyFormatted = buildFormattedReply( permalink, userLink, @@ -384,7 +385,8 @@ internal class LocalEchoEventFactory @Inject constructor( fun createReplyTextEvent(roomId: String, eventReplied: TimelineEvent, - replyText: CharSequence): Event? { + replyText: CharSequence, + autoMarkdown: Boolean): Event? { // Fallbacks and event representation // TODO Add error/warning logs when any of this is null val permalink = permalinkFactory.createPermalink(eventReplied.root, false) ?: return null @@ -394,9 +396,9 @@ internal class LocalEchoEventFactory @Inject constructor( val body = bodyForReply(eventReplied.getLastMessageContent(), eventReplied.isReply()) // As we always supply formatted body for replies we should force the MarkdownParser to produce html. - val replyTextFormatted = markdownParser.parse(replyText, force = true, advanced = false).takeFormatted() + val replyTextFormatted = markdownParser.parse(replyText, force = true, advanced = autoMarkdown).takeFormatted() // Body of the original message may not have formatted version, so may also have to convert to html. - val bodyFormatted = body.formattedText ?: markdownParser.parse(replyText, force = true, advanced = false).takeFormatted() + val bodyFormatted = body.formattedText ?: markdownParser.parse(body.text, force = true, advanced = autoMarkdown).takeFormatted() val replyFormatted = buildFormattedReply( permalink, userLink, @@ -517,11 +519,12 @@ internal class LocalEchoEventFactory @Inject constructor( roomId: String, quotedEvent: TimelineEvent, text: String, + autoMarkdown: Boolean, ): Event { val messageContent = quotedEvent.getLastMessageContent() val textMsg = messageContent?.body val quoteText = legacyRiotQuoteText(textMsg, text) - return createFormattedTextEvent(roomId, markdownParser.parse(quoteText, force = true, advanced = false), MessageType.MSGTYPE_TEXT) + return createFormattedTextEvent(roomId, markdownParser.parse(quoteText, force = true, advanced = autoMarkdown), MessageType.MSGTYPE_TEXT) } private fun legacyRiotQuoteText(quotedText: String?, myText: String): String { 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 8495cc3970..a63a06928a 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 @@ -406,13 +406,13 @@ class MessageComposerViewModel @AssistedInject constructor( popDraft() } is SendMode.Quote -> { - room.sendQuotedTextMessage(state.sendMode.timelineEvent, action.text.toString()) + room.sendQuotedTextMessage(state.sendMode.timelineEvent, action.text.toString(), action.autoMarkdown) _viewEvents.post(MessageComposerViewEvents.MessageSent) popDraft() } is SendMode.Reply -> { state.sendMode.timelineEvent.let { - room.replyToMessage(it, action.text.toString()) + room.replyToMessage(it, action.text.toString(), action.autoMarkdown) _viewEvents.post(MessageComposerViewEvents.MessageSent) popDraft() } From 240ce6873beb2b16a3cdd183fcbe0d2ae1cfdfdd Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 17 Dec 2021 09:15:25 +0100 Subject: [PATCH 086/632] cleaning rendering of state event in timeline --- .../timeline/factory/EncryptionItemFactory.kt | 2 +- .../timeline/factory/RoomCreateItemFactory.kt | 2 +- .../timeline/factory/TimelineItemFactory.kt | 134 ++++++++++-------- .../timeline/factory/WidgetItemFactory.kt | 2 +- .../timeline/format/NoticeEventFormatter.kt | 28 ++-- 5 files changed, 92 insertions(+), 76 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptionItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptionItemFactory.kt index 1d30136f27..14d9cce28a 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptionItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptionItemFactory.kt @@ -44,7 +44,7 @@ class EncryptionItemFactory @Inject constructor( if (!event.root.isStateEvent()) { return null } - val algorithm = event.root.getClearContent().toModel()?.algorithm + val algorithm = event.root.content.toModel()?.algorithm val informationData = informationDataFactory.create(params) val attributes = messageItemAttributesFactory.create(null, informationData, params.callback) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/RoomCreateItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/RoomCreateItemFactory.kt index 382962f98d..523fb8e682 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/RoomCreateItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/RoomCreateItemFactory.kt @@ -34,7 +34,7 @@ class RoomCreateItemFactory @Inject constructor(private val stringProvider: Stri fun create(params: TimelineItemFactoryParams): VectorEpoxyModel<*>? { val event = params.event - val createRoomContent = event.root.getClearContent().toModel() ?: return null + val createRoomContent = event.root.content.toModel() ?: return null val predecessorId = createRoomContent.predecessor?.roomId ?: return defaultRendering(params) val roomLink = session.permalinkService().createRoomPermalink(predecessorId) ?: return null val text = span { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt index c6b128315f..4af3c69aa3 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt @@ -45,67 +45,83 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me if (!timelineEventVisibilityHelper.shouldShowEvent(event, params.highlightedEventId)) { return buildEmptyItem(event, params.prevEvent, params.highlightedEventId) } - when (event.root.getClearType()) { - // Message itemsX - EventType.STICKER, - EventType.POLL_START, - EventType.MESSAGE -> messageItemFactory.create(params) - EventType.STATE_ROOM_TOMBSTONE, - EventType.STATE_ROOM_NAME, - EventType.STATE_ROOM_TOPIC, - EventType.STATE_ROOM_AVATAR, - EventType.STATE_ROOM_MEMBER, - EventType.STATE_ROOM_THIRD_PARTY_INVITE, - EventType.STATE_ROOM_CANONICAL_ALIAS, - EventType.STATE_ROOM_JOIN_RULES, - EventType.STATE_ROOM_HISTORY_VISIBILITY, - EventType.STATE_ROOM_SERVER_ACL, - EventType.STATE_ROOM_GUEST_ACCESS, - EventType.REDACTION, - EventType.STATE_ROOM_ALIASES, - EventType.KEY_VERIFICATION_ACCEPT, - EventType.KEY_VERIFICATION_START, - EventType.KEY_VERIFICATION_KEY, - EventType.KEY_VERIFICATION_READY, - EventType.KEY_VERIFICATION_MAC, - EventType.CALL_CANDIDATES, - EventType.CALL_REPLACES, - EventType.CALL_SELECT_ANSWER, - EventType.CALL_NEGOTIATE, - EventType.REACTION, - EventType.STATE_SPACE_CHILD, - EventType.STATE_SPACE_PARENT, - EventType.STATE_ROOM_POWER_LEVELS, - EventType.POLL_RESPONSE, - EventType.POLL_END -> noticeItemFactory.create(params) - EventType.STATE_ROOM_WIDGET_LEGACY, - EventType.STATE_ROOM_WIDGET -> widgetItemFactory.create(params) - EventType.STATE_ROOM_ENCRYPTION -> encryptionItemFactory.create(params) - // State room create - EventType.STATE_ROOM_CREATE -> roomCreateItemFactory.create(params) - // Calls - EventType.CALL_INVITE, - EventType.CALL_HANGUP, - EventType.CALL_REJECT, - EventType.CALL_ANSWER -> callItemFactory.create(params) - // Crypto - EventType.ENCRYPTED -> { - if (event.root.isRedacted()) { - // Redacted event, let the MessageItemFactory handle it - messageItemFactory.create(params) - } else { - encryptedItemFactory.create(params) + + // Manage state event differently, to check validity + if (event.root.isStateEvent()) { + // state event are not e2e + when (event.root.type) { + EventType.STATE_ROOM_TOMBSTONE, + EventType.STATE_ROOM_NAME, + EventType.STATE_ROOM_TOPIC, + EventType.STATE_ROOM_AVATAR, + EventType.STATE_ROOM_MEMBER, + EventType.STATE_ROOM_THIRD_PARTY_INVITE, + EventType.STATE_ROOM_CANONICAL_ALIAS, + EventType.STATE_ROOM_JOIN_RULES, + EventType.STATE_ROOM_HISTORY_VISIBILITY, + EventType.STATE_ROOM_SERVER_ACL, + EventType.STATE_ROOM_GUEST_ACCESS, + EventType.STATE_ROOM_ALIASES, + EventType.STATE_SPACE_CHILD, + EventType.STATE_SPACE_PARENT, + EventType.STATE_ROOM_POWER_LEVELS -> { + noticeItemFactory.create(params) + } + EventType.STATE_ROOM_WIDGET_LEGACY, + EventType.STATE_ROOM_WIDGET -> widgetItemFactory.create(params) + EventType.STATE_ROOM_ENCRYPTION -> encryptionItemFactory.create(params) + // State room create + EventType.STATE_ROOM_CREATE -> roomCreateItemFactory.create(params) + // Unhandled state event types + else -> { + // Should only happen when shouldShowHiddenEvents() settings is ON + Timber.v("State event type ${event.root.type} not handled") + defaultItemFactory.create(params) } } - EventType.KEY_VERIFICATION_CANCEL, - EventType.KEY_VERIFICATION_DONE -> { - verificationConclusionItemFactory.create(params) - } - // Unhandled event types - else -> { - // Should only happen when shouldShowHiddenEvents() settings is ON - Timber.v("Type ${event.root.getClearType()} not handled") - defaultItemFactory.create(params) + } else { + when (event.root.getClearType()) { + // Message itemsX + EventType.STICKER, + EventType.POLL_START, + EventType.MESSAGE -> messageItemFactory.create(params) + EventType.REDACTION, + EventType.KEY_VERIFICATION_ACCEPT, + EventType.KEY_VERIFICATION_START, + EventType.KEY_VERIFICATION_KEY, + EventType.KEY_VERIFICATION_READY, + EventType.KEY_VERIFICATION_MAC, + EventType.CALL_CANDIDATES, + EventType.CALL_REPLACES, + EventType.CALL_SELECT_ANSWER, + EventType.CALL_NEGOTIATE, + EventType.REACTION, + EventType.POLL_RESPONSE, + EventType.POLL_END -> noticeItemFactory.create(params) + // Calls + EventType.CALL_INVITE, + EventType.CALL_HANGUP, + EventType.CALL_REJECT, + EventType.CALL_ANSWER -> callItemFactory.create(params) + // Crypto + EventType.ENCRYPTED -> { + if (event.root.isRedacted()) { + // Redacted event, let the MessageItemFactory handle it + messageItemFactory.create(params) + } else { + encryptedItemFactory.create(params) + } + } + EventType.KEY_VERIFICATION_CANCEL, + EventType.KEY_VERIFICATION_DONE -> { + verificationConclusionItemFactory.create(params) + } + // Unhandled event types + else -> { + // Should only happen when shouldShowHiddenEvents() settings is ON + Timber.v("Type ${event.root.getClearType()} not handled") + defaultItemFactory.create(params) + } } } } catch (throwable: Throwable) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/WidgetItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/WidgetItemFactory.kt index 52f72810c9..a08383315c 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/WidgetItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/WidgetItemFactory.kt @@ -41,7 +41,7 @@ class WidgetItemFactory @Inject constructor( fun create(params: TimelineItemFactoryParams): VectorEpoxyModel<*>? { val event = params.event - val widgetContent: WidgetContent = event.root.getClearContent().toModel() ?: return null + val widgetContent: WidgetContent = event.root.content.toModel() ?: return null val previousWidgetContent: WidgetContent? = event.root.resolvedPrevContent().toModel() return when (WidgetType.fromString(widgetContent.type ?: previousWidgetContent?.type ?: "")) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt index 3dc46c9d70..d39b8aec5e 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt @@ -114,7 +114,7 @@ class NoticeEventFormatter @Inject constructor( } private fun formatRoomPowerLevels(event: Event, disambiguatedDisplayName: String): CharSequence? { - val powerLevelsContent: PowerLevelsContent = event.getClearContent().toModel() ?: return null + val powerLevelsContent: PowerLevelsContent = event.content.toModel() ?: return null val previousPowerLevelsContent: PowerLevelsContent = event.resolvedPrevContent().toModel() ?: return null val userIds = HashSet() userIds.addAll(powerLevelsContent.users.orEmpty().keys) @@ -142,7 +142,7 @@ class NoticeEventFormatter @Inject constructor( } private fun formatWidgetEvent(event: Event, disambiguatedDisplayName: String): CharSequence? { - val widgetContent: WidgetContent = event.getClearContent().toModel() ?: return null + val widgetContent: WidgetContent = event.content.toModel() ?: return null val previousWidgetContent: WidgetContent? = event.resolvedPrevContent().toModel() return if (widgetContent.isActive()) { val widgetName = widgetContent.getHumanName() @@ -198,7 +198,7 @@ class NoticeEventFormatter @Inject constructor( } private fun formatRoomCreateEvent(event: Event, isDm: Boolean): CharSequence? { - return event.getClearContent().toModel() + return event.content.toModel() ?.takeIf { it.creator.isNullOrBlank().not() } ?.let { if (event.isSentByCurrentUser()) { @@ -210,7 +210,7 @@ class NoticeEventFormatter @Inject constructor( } private fun formatRoomNameEvent(event: Event, senderName: String?): CharSequence? { - val content = event.getClearContent().toModel() ?: return null + val content = event.content.toModel() ?: return null return if (content.name.isNullOrBlank()) { if (event.isSentByCurrentUser()) { sp.getString(R.string.notice_room_name_removed_by_you) @@ -235,7 +235,7 @@ class NoticeEventFormatter @Inject constructor( } private fun formatRoomTopicEvent(event: Event, senderName: String?): CharSequence? { - val content = event.getClearContent().toModel() ?: return null + val content = event.content.toModel() ?: return null return if (content.topic.isNullOrEmpty()) { if (event.isSentByCurrentUser()) { sp.getString(R.string.notice_room_topic_removed_by_you) @@ -252,7 +252,7 @@ class NoticeEventFormatter @Inject constructor( } private fun formatRoomAvatarEvent(event: Event, senderName: String?): CharSequence? { - val content = event.getClearContent().toModel() ?: return null + val content = event.content.toModel() ?: return null return if (content.avatarUrl.isNullOrEmpty()) { if (event.isSentByCurrentUser()) { sp.getString(R.string.notice_room_avatar_removed_by_you) @@ -269,7 +269,7 @@ class NoticeEventFormatter @Inject constructor( } private fun formatRoomHistoryVisibilityEvent(event: Event, senderName: String?, isDm: Boolean): CharSequence? { - val historyVisibility = event.getClearContent().toModel()?.historyVisibility ?: return null + val historyVisibility = event.content.toModel()?.historyVisibility ?: return null val historyVisibilitySuffix = roomHistoryVisibilityFormatter.getNoticeSuffix(historyVisibility) return if (event.isSentByCurrentUser()) { @@ -282,7 +282,7 @@ class NoticeEventFormatter @Inject constructor( } private fun formatRoomThirdPartyInvite(event: Event, senderName: String?, isDm: Boolean): CharSequence? { - val content = event.getClearContent().toModel() + val content = event.content.toModel() val prevContent = event.resolvedPrevContent()?.toModel() return when { @@ -363,7 +363,7 @@ class NoticeEventFormatter @Inject constructor( } private fun formatRoomMemberEvent(event: Event, senderName: String?, isDm: Boolean): String? { - val eventContent: RoomMemberContent? = event.getClearContent().toModel() + val eventContent: RoomMemberContent? = event.content.toModel() val prevEventContent: RoomMemberContent? = event.resolvedPrevContent().toModel() val isMembershipEvent = prevEventContent?.membership != eventContent?.membership || eventContent?.membership == Membership.LEAVE @@ -375,7 +375,7 @@ class NoticeEventFormatter @Inject constructor( } private fun formatRoomAliasesEvent(event: Event, senderName: String?): String? { - val eventContent: RoomAliasesContent? = event.getClearContent().toModel() + val eventContent: RoomAliasesContent? = event.content.toModel() val prevEventContent: RoomAliasesContent? = event.resolvedPrevContent()?.toModel() val addedAliases = eventContent?.aliases.orEmpty() - prevEventContent?.aliases.orEmpty() @@ -408,7 +408,7 @@ class NoticeEventFormatter @Inject constructor( } private fun formatRoomServerAclEvent(event: Event, senderName: String?): String? { - val eventContent = event.getClearContent().toModel() ?: return null + val eventContent = event.content.toModel() ?: return null val prevEventContent = event.resolvedPrevContent()?.toModel() return buildString { @@ -481,7 +481,7 @@ class NoticeEventFormatter @Inject constructor( } private fun formatRoomCanonicalAliasEvent(event: Event, senderName: String?): String? { - val eventContent: RoomCanonicalAliasContent? = event.getClearContent().toModel() + val eventContent: RoomCanonicalAliasContent? = event.content.toModel() val prevContent: RoomCanonicalAliasContent? = event.resolvedPrevContent().toModel() val canonicalAlias = eventContent?.canonicalAlias?.takeIf { it.isNotEmpty() } val prevCanonicalAlias = prevContent?.canonicalAlias?.takeIf { it.isNotEmpty() } @@ -551,7 +551,7 @@ class NoticeEventFormatter @Inject constructor( } private fun formatRoomGuestAccessEvent(event: Event, senderName: String?, isDm: Boolean): String? { - val eventContent: RoomGuestAccessContent? = event.getClearContent().toModel() + val eventContent: RoomGuestAccessContent? = event.content.toModel() return when (eventContent?.guestAccess) { GuestAccess.CanJoin -> if (event.isSentByCurrentUser()) { @@ -815,7 +815,7 @@ class NoticeEventFormatter @Inject constructor( } private fun formatJoinRulesEvent(event: Event, senderName: String?, isDm: Boolean): CharSequence? { - val content = event.getClearContent().toModel() ?: return null + val content = event.content.toModel() ?: return null return when (content.joinRules) { RoomJoinRules.INVITE -> if (event.isSentByCurrentUser()) { From 6495bd9e5ed373c33047a4135ec11d469cac8ecb Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Fri, 17 Dec 2021 15:09:02 +0300 Subject: [PATCH 087/632] Send location event. --- .../room/model/message/LocationInfo.kt | 22 +++--------- .../model/message/MessageLocationContent.kt | 8 ++--- .../sdk/api/session/room/send/SendService.kt | 8 +++++ .../session/room/send/DefaultSendService.kt | 6 ++++ .../room/send/LocalEchoEventFactory.kt | 35 +++++++++++++++++++ .../app/features/location/LocationData.kt | 24 +++++++++++++ .../location/LocationSharingAction.kt | 3 +- .../location/LocationSharingFragment.kt | 28 +++++++++++++-- .../location/LocationSharingViewEvents.kt | 1 + .../location/LocationSharingViewModel.kt | 29 ++++++++++++++- .../location/LocationSharingViewState.kt | 3 +- .../app/features/location/LocationTracker.kt | 6 ++-- vector/src/main/res/values/strings.xml | 2 ++ 13 files changed, 145 insertions(+), 30 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/location/LocationData.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/LocationInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/LocationInfo.kt index a76c3c5b64..5110926294 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/LocationInfo.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/LocationInfo.kt @@ -18,29 +18,17 @@ package org.matrix.android.sdk.api.session.room.model.message import com.squareup.moshi.Json import com.squareup.moshi.JsonClass -import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileInfo @JsonClass(generateAdapter = true) data class LocationInfo( /** - * The URL to the thumbnail of the file. Only present if the thumbnail is unencrypted. + * Required. Required. RFC5870 formatted geo uri 'geo:latitude,longitude;uncertainty' like 'geo:40.05,29.24;30' representing this location. */ - @Json(name = "thumbnail_url") val thumbnailUrl: String? = null, + @Json(name = "uri") val geoUri: String? = null, /** - * Metadata about the image referred to in thumbnail_url. + * Required. A description of the location e.g. 'Big Ben, London, UK', or some kind + * of content description for accessibility e.g. 'location attachment'. */ - @Json(name = "thumbnail_info") val thumbnailInfo: ThumbnailInfo? = null, - - /** - * Information on the encrypted thumbnail file, as specified in End-to-end encryption. Only present if the thumbnail is encrypted. - */ - @Json(name = "thumbnail_file") val thumbnailFile: EncryptedFileInfo? = null + @Json(name = "description") val description: String? = null ) - -/** - * Get the url of the encrypted thumbnail or of the thumbnail - */ -fun LocationInfo.getThumbnailUrl(): String? { - return thumbnailFile?.url ?: thumbnailUrl -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLocationContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLocationContent.kt index 6881c09924..9571cc4280 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLocationContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLocationContent.kt @@ -26,7 +26,7 @@ data class MessageLocationContent( /** * Required. Must be 'm.location'. */ - @Json(name = MessageContent.MSG_TYPE_JSON_KEY) override val msgType: String, + @Json(name = MessageContent.MSG_TYPE_JSON_KEY) override val msgType: String = MessageType.MSGTYPE_LOCATION, /** * Required. A description of the location e.g. 'Big Ben, London, UK', or some kind @@ -35,14 +35,14 @@ data class MessageLocationContent( @Json(name = "body") override val body: String, /** - * Required. A geo URI representing this location. + * Required. RFC5870 formatted geo uri 'geo:latitude,longitude;uncertainty' like 'geo:40.05,29.24;30' representing this location. */ @Json(name = "geo_uri") val geoUri: String, /** - * + * See https://github.com/matrix-org/matrix-doc/blob/matthew/location/proposals/3488-location.md */ - @Json(name = "info") val locationInfo: LocationInfo? = null, + @Json(name = "org.matrix.msc3488.location") val locationInfo: LocationInfo? = null, @Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null, @Json(name = "m.new_content") override val newContent: Content? = null diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt index 5b387c3413..7983ad7068 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt @@ -122,6 +122,14 @@ interface SendService { */ fun resendMediaMessage(localEcho: TimelineEvent): Cancelable + /** + * Send a location event to the room + * @param latitude required latitude of the location + * @param longitude required longitude of the location + * @param uncertainty Accuracy of the location in meters + */ + fun sendLocation(latitude: Double, longitude: Double, uncertainty: Double?): Cancelable + /** * Remove this failed message from the timeline * @param localEcho the unsent local echo diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt index d3162aef79..dbe4d5eb73 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt @@ -115,6 +115,12 @@ internal class DefaultSendService @AssistedInject constructor( .let { sendEvent(it) } } + override fun sendLocation(latitude: Double, longitude: Double, uncertainty: Double?): Cancelable { + return localEchoEventFactory.createLocationEvent(roomId, latitude, longitude, uncertainty) + .also { createLocalEcho(it) } + .let { sendEvent(it) } + } + override fun redactEvent(event: Event, reason: String?): Cancelable { // TODO manage media/attachements? val redactionEcho = localEchoEventFactory.createRedactEvent(roomId, event.eventId!!, reason) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt index 85b22628d7..179df22326 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt @@ -32,6 +32,7 @@ import org.matrix.android.sdk.api.session.room.model.message.AudioInfo import org.matrix.android.sdk.api.session.room.model.message.AudioWaveformInfo import org.matrix.android.sdk.api.session.room.model.message.FileInfo import org.matrix.android.sdk.api.session.room.model.message.ImageInfo +import org.matrix.android.sdk.api.session.room.model.message.LocationInfo import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.model.message.MessageContentWithFormattedBody @@ -39,6 +40,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageEndPollConte import org.matrix.android.sdk.api.session.room.model.message.MessageFileContent import org.matrix.android.sdk.api.session.room.model.message.MessageFormat import org.matrix.android.sdk.api.session.room.model.message.MessageImageContent +import org.matrix.android.sdk.api.session.room.model.message.MessageLocationContent import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent import org.matrix.android.sdk.api.session.room.model.message.MessagePollResponseContent import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent @@ -194,6 +196,22 @@ internal class LocalEchoEventFactory @Inject constructor( unsignedData = UnsignedData(age = null, transactionId = localId)) } + fun createLocationEvent(roomId: String, + latitude: Double, + longitude: Double, + uncertainty: Double?): Event { + val geoUri = buildGeoUri(latitude, longitude, uncertainty) + val content = MessageLocationContent( + geoUri = geoUri, + body = geoUri, + locationInfo = LocationInfo( + geoUri = geoUri, + description = geoUri + ) + ) + return createMessageEvent(roomId, content) + } + fun createReplaceTextOfReply(roomId: String, eventReplaced: TimelineEvent, originalEvent: TimelineEvent, @@ -463,6 +481,23 @@ internal class LocalEchoEventFactory @Inject constructor( } } + /** + * Returns RFC5870 formatted geo uri 'geo:latitude,longitude;uncertainty' like 'geo:40.05,29.24;30' + * Uncertainty of the location is in meters and not required. + */ + private fun buildGeoUri(latitude: Double, longitude: Double, uncertainty: Double?): String { + return buildString { + append("geo:") + append(latitude) + append(",") + append(longitude) + uncertainty?.let { + append(";") + append(it) + } + } + } + /* * { "content": { diff --git a/vector/src/main/java/im/vector/app/features/location/LocationData.kt b/vector/src/main/java/im/vector/app/features/location/LocationData.kt new file mode 100644 index 0000000000..f85b8713d9 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/location/LocationData.kt @@ -0,0 +1,24 @@ +/* + * 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.location + +data class LocationData( + val latitude: Double, + val longitude: Double, + val uncertainty: Double? +) + 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 5139888b08..0efaefaa5b 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 @@ -19,5 +19,6 @@ package im.vector.app.features.location import im.vector.app.core.platform.VectorViewModelAction sealed class LocationSharingAction : VectorViewModelAction { - data class OnShareLocation(val latitude: Double, val longitude: Double) : LocationSharingAction() + data class OnLocationUpdate(val locationData: LocationData) : LocationSharingAction() + object OnShareLocation : 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 390d1aafa5..8cd86a2356 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 @@ -26,6 +26,7 @@ import androidx.core.content.ContextCompat import com.airbnb.mvrx.activityViewModel import com.bumptech.glide.request.target.CustomTarget import com.bumptech.glide.request.transition.Transition +import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.mapbox.mapboxsdk.Mapbox import com.mapbox.mapboxsdk.camera.CameraPosition import com.mapbox.mapboxsdk.geometry.LatLng @@ -79,6 +80,17 @@ class LocationSharingFragment @Inject constructor( super.onViewCreated(view, savedInstanceState) initMapView(savedInstanceState) + + views.shareLocationContainer.debouncedClicks { + viewModel.handle(LocationSharingAction.OnShareLocation) + } + + viewModel.observeViewEvents { + when (it) { + LocationSharingViewEvents.LocationNotAvailableError -> handleLocationNotAvailableError() + LocationSharingViewEvents.Close -> activity?.finish() + } + } } override fun onDestroyView() { @@ -126,10 +138,10 @@ class LocationSharingFragment @Inject constructor( } } - override fun onLocationUpdate(latitude: Double, longitude: Double) { + override fun onLocationUpdate(locationData: LocationData) { lastZoomValue = if (lastZoomValue == -1.0) INITIAL_ZOOM else map?.cameraPosition?.zoom ?: INITIAL_ZOOM - val latLng = LatLng(latitude, longitude) + val latLng = LatLng(locationData.latitude, locationData.longitude) map?.cameraPosition = CameraPosition.Builder() .target(latLng) @@ -144,10 +156,20 @@ class LocationSharingFragment @Inject constructor( .withIconImage(USER_PIN_NAME) .withIconAnchor(Property.ICON_ANCHOR_BOTTOM) ) + + viewModel.handle(LocationSharingAction.OnLocationUpdate(locationData)) + } + + private fun handleLocationNotAvailableError() { + MaterialAlertDialogBuilder(requireActivity()) + .setTitle(R.string.location_not_available_dialog_title) + .setMessage(R.string.location_not_available_dialog_content) + .setPositiveButton(R.string.ok, null) + .show() } companion object { - const val INITIAL_ZOOM = 12.0 + const val INITIAL_ZOOM = 15.0 const val USER_PIN_NAME = "USER_PIN_NAME" } } 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 1032a696ff..743daaf5e0 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 @@ -20,4 +20,5 @@ import im.vector.app.core.platform.VectorViewEvents sealed class LocationSharingViewEvents : VectorViewEvents { object Close : LocationSharingViewEvents() + object LocationNotAvailableError : 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 6da00f176d..4525cac446 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 @@ -23,11 +23,15 @@ 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 org.matrix.android.sdk.api.session.Session class LocationSharingViewModel @AssistedInject constructor( - @Assisted private val initialState: LocationSharingViewState + @Assisted private val initialState: LocationSharingViewState, + session: Session ) : VectorViewModel(initialState) { + private val room = session.getRoom(initialState.roomId)!! + @AssistedFactory interface Factory : MavericksAssistedViewModelFactory { override fun create(initialState: LocationSharingViewState): LocationSharingViewModel @@ -37,5 +41,28 @@ class LocationSharingViewModel @AssistedInject constructor( } override fun handle(action: LocationSharingAction) { + when (action) { + is LocationSharingAction.OnLocationUpdate -> handleLocationUpdate(action.locationData) + LocationSharingAction.OnShareLocation -> handleShareLocation() + } + } + + private fun handleShareLocation() = withState { state -> + state.lastKnownLocation?.let { location -> + room.sendLocation( + latitude = location.latitude, + longitude = location.longitude, + uncertainty = location.uncertainty + ) + _viewEvents.post(LocationSharingViewEvents.Close) + } ?: run { + _viewEvents.post(LocationSharingViewEvents.LocationNotAvailableError) + } + } + + private fun handleLocationUpdate(locationData: LocationData) { + setState { + copy(lastKnownLocation = locationData) + } } } 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 d62e568fb2..2869929b12 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 @@ -27,7 +27,8 @@ enum class LocationSharingMode(@StringRes val titleRes: Int) { data class LocationSharingViewState( val roomId: String, - val mode: LocationSharingMode + val mode: LocationSharingMode, + val lastKnownLocation: LocationData? = null ) : MavericksState { constructor(locationSharingArgs: LocationSharingArgs) : this( diff --git a/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt b/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt index 3cf2689aee..93993245f8 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt @@ -27,7 +27,7 @@ class LocationTracker @Inject constructor( private val context: Context) : LocationListener { interface Callback { - fun onLocationUpdate(latitude: Double, longitude: Double) + fun onLocationUpdate(locationData: LocationData) } private var locationManager: LocationManager? = null @@ -51,7 +51,7 @@ class LocationTracker @Inject constructor( // Send last known location without waiting location updates it.getLastKnownLocation(provider)?.let { lastKnownLocation -> - callback?.onLocationUpdate(lastKnownLocation.latitude, lastKnownLocation.longitude) + callback?.onLocationUpdate(LocationData(lastKnownLocation.latitude, lastKnownLocation.longitude, lastKnownLocation.accuracy.toDouble())) } it.requestLocationUpdates( @@ -70,7 +70,7 @@ class LocationTracker @Inject constructor( } override fun onLocationChanged(location: Location) { - callback?.onLocationUpdate(location.latitude, location.longitude) + callback?.onLocationUpdate(LocationData(location.latitude, location.longitude, location.accuracy.toDouble())) } companion object { diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 166f25c053..086469a1fd 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -3708,4 +3708,6 @@ Location Share location Share location + Element could not access your location + Element could not access your location. Please try again later. From 680c22d4c8c227902430663578ee96542b9ce6f8 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Fri, 17 Dec 2021 13:41:19 +0000 Subject: [PATCH 088/632] using app compat edit text for official emoji support --- .../home/room/detail/composer/ComposerEditText.kt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/ComposerEditText.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/ComposerEditText.kt index 03107fd3f7..c751053cdf 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/ComposerEditText.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/ComposerEditText.kt @@ -24,18 +24,21 @@ import android.text.Editable import android.util.AttributeSet import android.view.inputmethod.EditorInfo import android.view.inputmethod.InputConnection +import androidx.appcompat.widget.AppCompatEditText import androidx.core.view.OnReceiveContentListener import androidx.core.view.ViewCompat import androidx.core.view.inputmethod.EditorInfoCompat import androidx.core.view.inputmethod.InputConnectionCompat -import com.vanniktech.emoji.EmojiEditText import im.vector.app.core.extensions.ooi import im.vector.app.core.platform.SimpleTextWatcher import im.vector.app.features.html.PillImageSpan import timber.log.Timber -class ComposerEditText @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = android.R.attr.editTextStyle) : - EmojiEditText(context, attrs, defStyleAttr) { +class ComposerEditText @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = android.R.attr.editTextStyle +) : AppCompatEditText(context, attrs, defStyleAttr) { interface Callback { fun onRichContentSelected(contentUri: Uri): Boolean From f9b69fff9ebb24c4f8d3c08c723fe3295d066b4e Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 16 Dec 2021 17:17:29 +0100 Subject: [PATCH 089/632] Add tagged event entry in room account data types --- .../sdk/api/session/room/accountdata/RoomAccountDataTypes.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/accountdata/RoomAccountDataTypes.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/accountdata/RoomAccountDataTypes.kt index 96eb86c0d6..312fb7e164 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/accountdata/RoomAccountDataTypes.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/accountdata/RoomAccountDataTypes.kt @@ -21,4 +21,5 @@ object RoomAccountDataTypes { const val EVENT_TYPE_TAG = "m.tag" const val EVENT_TYPE_FULLY_READ = "m.fully_read" const val EVENT_TYPE_SPACE_ORDER = "org.matrix.msc3230.space_order" // m.space_order + const val EVENT_TYPE_TAGGED_EVENTS = "m.tagged_events" } From 706c5b2b32ee123564e850e108529842b46f9e0d Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 16 Dec 2021 17:47:43 +0100 Subject: [PATCH 090/632] Add tagged events model --- changelog.d/4753.feature | 1 + .../room/tagged_events/TaggedEventsContent.kt | 85 +++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 changelog.d/4753.feature create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/tagged_events/TaggedEventsContent.kt diff --git a/changelog.d/4753.feature b/changelog.d/4753.feature new file mode 100644 index 0000000000..1a474f0f5f --- /dev/null +++ b/changelog.d/4753.feature @@ -0,0 +1 @@ +Support tagged events in Room Account Data (MSC2437) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/tagged_events/TaggedEventsContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/tagged_events/TaggedEventsContent.kt new file mode 100644 index 0000000000..fdfa28c00a --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/tagged_events/TaggedEventsContent.kt @@ -0,0 +1,85 @@ +/* + * Copyright 2021 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.internal.session.room.tagged_events + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +/** + * Keys are event IDs, values are event information. + */ +typealias TaggedEvent = Map + +/** + * Keys are tagged event names (eg. m.favourite), values are the related events. + */ +typealias TaggedEvents = Map + +/** + * Class used to parse the content of a m.tagged_events type event. + * This kind of event defines the tagged events in a room. + * + * The content of this event is a tags key whose value is an object mapping the name of each tag + * to another object. The JSON object associated with each tag is an object where the keys are the + * event IDs and values give information about the events. + * + * Ref: https://github.com/matrix-org/matrix-doc/pull/2437 + */ +@JsonClass(generateAdapter = true) +data class TaggedEventsContent( + var tags: TaggedEvents = emptyMap() +) { + val favouriteEvents + get() = tags[TAG_FAVOURITE].orEmpty() + + val hiddenEvents + get() = tags[TAG_HIDDEN].orEmpty() + + fun tagEvent(eventId: String, info: TaggedEventInfo, tag: String) { + val tagMap = HashMap(tags[tag] ?: emptyMap()) + tagMap[eventId] = info + + val updatedTags = HashMap>(tags) + updatedTags[tag] = tagMap + tags = updatedTags + } + + fun untagEvent(eventId: String, tag: String) { + val tagMap = HashMap(tags[tag] ?: emptyMap()) + tagMap.remove(eventId) + + val updatedTags = HashMap>(tags) + updatedTags[tag] = tagMap + tags = updatedTags + } + + companion object { + const val TAGS_KEY = "tags" + const val TAG_FAVOURITE = "m.favourite" + const val TAG_HIDDEN = "m.hidden" + } +} + +data class TaggedEventInfo( + var keywords: List? = null, + + @Json(name = "origin_server_ts") + val originServerTs: Long? = null, + + @Json(name = "tagged_at") + var taggedAt: Long? = null +) From 3bd2b77af35380b5587047dd5a4696617b6ad524 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Fri, 17 Dec 2021 15:55:26 +0100 Subject: [PATCH 091/632] Fix package name --- .../room/{tagged_events => taggedevents}/TaggedEventsContent.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/{tagged_events => taggedevents}/TaggedEventsContent.kt (97%) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/tagged_events/TaggedEventsContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/taggedevents/TaggedEventsContent.kt similarity index 97% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/tagged_events/TaggedEventsContent.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/taggedevents/TaggedEventsContent.kt index fdfa28c00a..318e8f8ea2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/tagged_events/TaggedEventsContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/taggedevents/TaggedEventsContent.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.session.room.tagged_events +package org.matrix.android.sdk.internal.session.room.taggedevents import com.squareup.moshi.Json import com.squareup.moshi.JsonClass From 58f7c83f4eba872af0f4d2724f6c03f6eacd5e99 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Fri, 17 Dec 2021 17:18:39 +0100 Subject: [PATCH 092/632] Skip issue triage actions in forks --- .github/workflows/triage-incoming.yml | 4 +- .github/workflows/triage-move-labelled.yml | 87 +++++++++++--------- .github/workflows/triage-move-unlabelled.yml | 10 +-- .github/workflows/triage-priority-bugs.yml | 62 +++++++------- 4 files changed, 87 insertions(+), 76 deletions(-) diff --git a/.github/workflows/triage-incoming.yml b/.github/workflows/triage-incoming.yml index 4ecc824424..3bb5ab73aa 100644 --- a/.github/workflows/triage-incoming.yml +++ b/.github/workflows/triage-incoming.yml @@ -2,11 +2,13 @@ name: Move new issues onto Issue triage board on: issues: - types: [opened] + types: [ opened ] jobs: automate-project-columns: runs-on: ubuntu-latest + if: | + github.repository == 'vector-im/element-android' # Skip in forks steps: - uses: alex-page/github-project-automation-plus@bb266ff4dde9242060e2d5418e120a133586d488 with: diff --git a/.github/workflows/triage-move-labelled.yml b/.github/workflows/triage-move-labelled.yml index 67c4e9dbab..96d302ceea 100644 --- a/.github/workflows/triage-move-labelled.yml +++ b/.github/workflows/triage-move-labelled.yml @@ -2,12 +2,14 @@ name: Move labelled issues to correct boards and columns on: issues: - types: [labeled] - + types: [ labeled ] + jobs: move_needs_info_issues: name: X-Needs-Info issues to Need info column on triage board runs-on: ubuntu-latest + if: | + github.repository == 'vector-im/element-android' # Skip in forks steps: - uses: konradpabjan/move-labeled-or-milestoned-issue@219d384e03fa4b6460cd24f9f37d19eb033a4338 with: @@ -19,15 +21,16 @@ jobs: add_priority_design_issues_to_project: name: P1 X-Needs-Design to Design project board runs-on: ubuntu-latest - if: > - contains(github.event.issue.labels.*.name, 'X-Needs-Design') && - (contains(github.event.issue.labels.*.name, 'S-Critical') && - (contains(github.event.issue.labels.*.name, 'O-Frequent') || + if: | + github.repository == 'vector-im/element-android' && # Skip in forks + contains(github.event.issue.labels.*.name, 'X-Needs-Design') && + (contains(github.event.issue.labels.*.name, 'S-Critical') && + (contains(github.event.issue.labels.*.name, 'O-Frequent') || contains(github.event.issue.labels.*.name, 'O-Occasional')) || - contains(github.event.issue.labels.*.name, 'S-Major') && - contains(github.event.issue.labels.*.name, 'O-Frequent') || - contains(github.event.issue.labels.*.name, 'A11y') && - contains(github.event.issue.labels.*.name, 'O-Frequent')) + contains(github.event.issue.labels.*.name, 'S-Major') && + contains(github.event.issue.labels.*.name, 'O-Frequent') || + contains(github.event.issue.labels.*.name, 'A11y') && + contains(github.event.issue.labels.*.name, 'O-Frequent')) steps: - uses: octokit/graphql-action@v2.x id: add_to_project @@ -47,36 +50,38 @@ jobs: PROJECT_ID: "PN_kwDOAM0swc0sUA" GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }} -# delight_issues_to_board: -# name: Spaces issues to new Delight project board -# runs-on: ubuntu-latest -# if: > -# contains(github.event.issue.labels.*.name, 'A-Spaces') || -# contains(github.event.issue.labels.*.name, 'A-Space-Settings') || -# contains(github.event.issue.labels.*.name, 'A-Subspaces') -# steps: -# - uses: octokit/graphql-action@v2.x -# with: -# headers: '{"GraphQL-Features": "projects_next_graphql"}' -# query: | -# mutation add_to_project($projectid:ID!,$contentid:ID!) { -# addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) { -# projectNextItem { -# id -# } -# } -# } -# projectid: ${{ env.PROJECT_ID }} -# contentid: ${{ github.event.issue.node_id }} -# env: -# PROJECT_ID: "PN_kwDOAM0swc1HvQ" -# GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }} + # delight_issues_to_board: + # name: Spaces issues to new Delight project board + # runs-on: ubuntu-latest + # if: | + # github.repository == 'vector-im/element-android' && # Skip in forks + # contains(github.event.issue.labels.*.name, 'A-Spaces') || + # contains(github.event.issue.labels.*.name, 'A-Space-Settings') || + # contains(github.event.issue.labels.*.name, 'A-Subspaces') + # steps: + # - uses: octokit/graphql-action@v2.x + # with: + # headers: '{"GraphQL-Features": "projects_next_graphql"}' + # query: | + # mutation add_to_project($projectid:ID!,$contentid:ID!) { + # addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) { + # projectNextItem { + # id + # } + # } + # } + # projectid: ${{ env.PROJECT_ID }} + # contentid: ${{ github.event.issue.node_id }} + # env: + # PROJECT_ID: "PN_kwDOAM0swc1HvQ" + # GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }} move_voice-message_issues: name: A-Voice Messages to voice message board runs-on: ubuntu-latest - if: > - contains(github.event.issue.labels.*.name, 'A-Voice Messages') + if: | + github.repository == 'vector-im/element-android' && # Skip in forks + contains(github.event.issue.labels.*.name, 'A-Voice Messages') steps: - uses: octokit/graphql-action@v2.x with: @@ -98,8 +103,9 @@ jobs: move_threads_issues: name: A-Threads to Thread board runs-on: ubuntu-latest - if: > - contains(github.event.issue.labels.*.name, 'A-Threads') + if: | + github.repository == 'vector-im/element-android' && # Skip in forks + contains(github.event.issue.labels.*.name, 'A-Threads') steps: - uses: octokit/graphql-action@v2.x with: @@ -121,8 +127,9 @@ jobs: move_message_bubbles_issues: name: A-Message-Bubbles to Message bubbles board runs-on: ubuntu-latest - if: > - contains(github.event.issue.labels.*.name, 'A-Message-Bubbles') + if: | + github.repository == 'vector-im/element-android' && # Skip in forks + contains(github.event.issue.labels.*.name, 'A-Message-Bubbles') steps: - uses: octokit/graphql-action@v2.x with: diff --git a/.github/workflows/triage-move-unlabelled.yml b/.github/workflows/triage-move-unlabelled.yml index 94bd049b91..5f13165939 100644 --- a/.github/workflows/triage-move-unlabelled.yml +++ b/.github/workflows/triage-move-unlabelled.yml @@ -2,15 +2,15 @@ name: Move unlabelled from needs info columns to triaged on: issues: - types: [unlabeled] - + types: [ unlabeled ] + jobs: Move_Unabeled_Issue_On_Project_Board: name: Move no longer X-Needs-Info issues to Triaged runs-on: ubuntu-latest - if: > - ${{ - !contains(github.event.issue.labels.*.name, 'X-Needs-Info') }} + if: | + github.repository == 'vector-im/element-android' && # Skip in forks + !contains(github.event.issue.labels.*.name, 'X-Needs-Info') env: BOARD_NAME: "Issue triage" OWNER: ${{ github.repository_owner }} diff --git a/.github/workflows/triage-priority-bugs.yml b/.github/workflows/triage-priority-bugs.yml index 976879a3ae..7564387a1c 100644 --- a/.github/workflows/triage-priority-bugs.yml +++ b/.github/workflows/triage-priority-bugs.yml @@ -2,28 +2,29 @@ name: Move P1 bugs to boards on: issues: - types: [labeled, unlabeled] + types: [ labeled, unlabeled ] jobs: p1_issues_to_team_workboard: runs-on: ubuntu-latest - if: > - (!contains(github.event.issue.labels.*.name, 'A-E2EE') && - !contains(github.event.issue.labels.*.name, 'A-E2EE-Cross-Signing') && - !contains(github.event.issue.labels.*.name, 'A-E2EE-Dehydration') && - !contains(github.event.issue.labels.*.name, 'A-E2EE-Key-Backup') && - !contains(github.event.issue.labels.*.name, 'A-E2EE-SAS-Verification') && - !contains(github.event.issue.labels.*.name, 'A-Spaces') && - !contains(github.event.issue.labels.*.name, 'A-Spaces-Settings') && - !contains(github.event.issue.labels.*.name, 'A-Subspaces')) && - (contains(github.event.issue.labels.*.name, 'T-Defect') && - contains(github.event.issue.labels.*.name, 'S-Critical') && - (contains(github.event.issue.labels.*.name, 'O-Frequent') || + if: | + github.repository == 'vector-im/element-android' && # Skip in forks + (!contains(github.event.issue.labels.*.name, 'A-E2EE') && + !contains(github.event.issue.labels.*.name, 'A-E2EE-Cross-Signing') && + !contains(github.event.issue.labels.*.name, 'A-E2EE-Dehydration') && + !contains(github.event.issue.labels.*.name, 'A-E2EE-Key-Backup') && + !contains(github.event.issue.labels.*.name, 'A-E2EE-SAS-Verification') && + !contains(github.event.issue.labels.*.name, 'A-Spaces') && + !contains(github.event.issue.labels.*.name, 'A-Spaces-Settings') && + !contains(github.event.issue.labels.*.name, 'A-Subspaces')) && + (contains(github.event.issue.labels.*.name, 'T-Defect') && + contains(github.event.issue.labels.*.name, 'S-Critical') && + (contains(github.event.issue.labels.*.name, 'O-Frequent') || contains(github.event.issue.labels.*.name, 'O-Occasional')) || - contains(github.event.issue.labels.*.name, 'S-Major') && - contains(github.event.issue.labels.*.name, 'O-Frequent') || - contains(github.event.issue.labels.*.name, 'A11y') && - contains(github.event.issue.labels.*.name, 'O-Frequent')) + contains(github.event.issue.labels.*.name, 'S-Major') && + contains(github.event.issue.labels.*.name, 'O-Frequent') || + contains(github.event.issue.labels.*.name, 'A11y') && + contains(github.event.issue.labels.*.name, 'O-Frequent')) steps: - uses: alex-page/github-project-automation-plus@bb266ff4dde9242060e2d5418e120a133586d488 with: @@ -33,20 +34,21 @@ jobs: P1_issues_to_crypto_team_workboard: runs-on: ubuntu-latest - if: > - (contains(github.event.issue.labels.*.name, 'A-E2EE') || - contains(github.event.issue.labels.*.name, 'A-E2EE-Cross-Signing') || - contains(github.event.issue.labels.*.name, 'A-E2EE-Dehydration') || - contains(github.event.issue.labels.*.name, 'A-E2EE-Key-Backup') || - contains(github.event.issue.labels.*.name, 'A-E2EE-SAS-Verification')) && - (contains(github.event.issue.labels.*.name, 'T-Defect') && - contains(github.event.issue.labels.*.name, 'S-Critical') && - (contains(github.event.issue.labels.*.name, 'O-Frequent') || + if: | + github.repository == 'vector-im/element-android' && # Skip in forks + (contains(github.event.issue.labels.*.name, 'A-E2EE') || + contains(github.event.issue.labels.*.name, 'A-E2EE-Cross-Signing') || + contains(github.event.issue.labels.*.name, 'A-E2EE-Dehydration') || + contains(github.event.issue.labels.*.name, 'A-E2EE-Key-Backup') || + contains(github.event.issue.labels.*.name, 'A-E2EE-SAS-Verification')) && + (contains(github.event.issue.labels.*.name, 'T-Defect') && + contains(github.event.issue.labels.*.name, 'S-Critical') && + (contains(github.event.issue.labels.*.name, 'O-Frequent') || contains(github.event.issue.labels.*.name, 'O-Occasional')) || - contains(github.event.issue.labels.*.name, 'S-Major') && - contains(github.event.issue.labels.*.name, 'O-Frequent') || - contains(github.event.issue.labels.*.name, 'A11y') && - contains(github.event.issue.labels.*.name, 'O-Frequent')) + contains(github.event.issue.labels.*.name, 'S-Major') && + contains(github.event.issue.labels.*.name, 'O-Frequent') || + contains(github.event.issue.labels.*.name, 'A11y') && + contains(github.event.issue.labels.*.name, 'O-Frequent')) steps: - uses: alex-page/github-project-automation-plus@bb266ff4dde9242060e2d5418e120a133586d488 with: From fa92aff11c50ec7b92d3d6e35d2fbe684d4ceae4 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 23 Nov 2021 10:22:55 +0100 Subject: [PATCH 093/632] Add function to provide a Flow of a single UserAccountDataEvent --- .../main/java/org/matrix/android/sdk/flow/FlowSession.kt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowSession.kt b/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowSession.kt index 2a0abd3d24..669e27edfd 100644 --- a/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowSession.kt +++ b/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowSession.kt @@ -152,6 +152,13 @@ class FlowSession(private val session: Session) { } } + fun liveUserAccountData(type: String): Flow> { + return session.accountDataService().getLiveUserAccountDataEvent(type).asFlow() + .startWith(session.coroutineDispatchers.io) { + session.accountDataService().getUserAccountDataEvent(type).toOptional() + } + } + fun liveRoomAccountData(types: Set): Flow> { return session.accountDataService().getLiveRoomAccountDataEvents(types).asFlow() .startWith(session.coroutineDispatchers.io) { From 61b74bb0835de0b2b47f39f9d0884e0b23c806c9 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 23 Nov 2021 10:24:55 +0100 Subject: [PATCH 094/632] Add missing setting key constant --- .../java/im/vector/app/features/settings/VectorPreferences.kt | 1 + 1 file changed, 1 insertion(+) 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 64561cbc12..3436c20ce3 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 @@ -44,6 +44,7 @@ class VectorPreferences @Inject constructor(private val context: Context) { const val SETTINGS_HOME_SERVER_PREFERENCE_KEY = "SETTINGS_HOME_SERVER_PREFERENCE_KEY" const val SETTINGS_IDENTITY_SERVER_PREFERENCE_KEY = "SETTINGS_IDENTITY_SERVER_PREFERENCE_KEY" const val SETTINGS_DISCOVERY_PREFERENCE_KEY = "SETTINGS_DISCOVERY_PREFERENCE_KEY" + const val SETTINGS_EMAILS_AND_PHONE_NUMBERS_PREFERENCE_KEY = "SETTINGS_EMAILS_AND_PHONE_NUMBERS_PREFERENCE_KEY" const val SETTINGS_CLEAR_CACHE_PREFERENCE_KEY = "SETTINGS_CLEAR_CACHE_PREFERENCE_KEY" const val SETTINGS_CLEAR_MEDIA_CACHE_PREFERENCE_KEY = "SETTINGS_CLEAR_MEDIA_CACHE_PREFERENCE_KEY" From c13feb983ec6b05437aa5cb76fc3a7c35cad52ba Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Fri, 17 Dec 2021 17:28:35 +0100 Subject: [PATCH 095/632] Remove useless nullability in method param --- .../app/features/createdirect/CreateDirectRoomActivity.kt | 7 ++----- 1 file changed, 2 insertions(+), 5 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 3221a5bf66..f73799d0e9 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 @@ -168,11 +168,8 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() { } } - private fun renderCreationSuccess(roomId: String?) { - // Navigate to freshly created room - if (roomId != null) { - navigator.openRoom(this, roomId) - } + private fun renderCreationSuccess(roomId: String) { + navigator.openRoom(this, roomId) finish() } From 7e2eb2a21e2e7c1ff612c68a307bb4e17259e5a5 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Fri, 17 Dec 2021 17:30:37 +0100 Subject: [PATCH 096/632] Remove useless `val` in RoomListViewModel constructor --- .../app/features/home/room/list/RoomListViewModel.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt index 36f6dcab34..e5caaffbda 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt @@ -53,10 +53,10 @@ import timber.log.Timber class RoomListViewModel @AssistedInject constructor( @Assisted initialState: RoomListViewState, private val session: Session, - private val stringProvider: StringProvider, - private val appStateHandler: AppStateHandler, - private val vectorPreferences: VectorPreferences, - private val autoAcceptInvites: AutoAcceptInvites + stringProvider: StringProvider, + appStateHandler: AppStateHandler, + vectorPreferences: VectorPreferences, + autoAcceptInvites: AutoAcceptInvites ) : VectorViewModel(initialState) { @AssistedFactory From a9cf7b3676a20df94d2fd8fce7956fd48a7b4b12 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Fri, 17 Dec 2021 16:55:30 +0000 Subject: [PATCH 097/632] adding changelog entry --- changelog.d/4756.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/4756.bugfix diff --git a/changelog.d/4756.bugfix b/changelog.d/4756.bugfix new file mode 100644 index 0000000000..8e0c373557 --- /dev/null +++ b/changelog.d/4756.bugfix @@ -0,0 +1 @@ +Fixes newer emojis rendering strangely when inserting from the system keyboard \ No newline at end of file From 9b6b74ae2e98ed0c19a8cd0c14fadcc3ea8f6a1e Mon Sep 17 00:00:00 2001 From: rickeesingh Date: Sat, 18 Dec 2021 01:35:55 +0000 Subject: [PATCH 098/632] Added translation using Weblate (Punjabi) --- vector/src/main/res/values-pa/strings.xml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 vector/src/main/res/values-pa/strings.xml diff --git a/vector/src/main/res/values-pa/strings.xml b/vector/src/main/res/values-pa/strings.xml new file mode 100644 index 0000000000..a6b3daec93 --- /dev/null +++ b/vector/src/main/res/values-pa/strings.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file From c9d650eb0c802eae27fe9f84f318e58d661e2c0b Mon Sep 17 00:00:00 2001 From: waclaw66 Date: Sun, 19 Dec 2021 10:08:24 +0000 Subject: [PATCH 099/632] Translated using Weblate (Czech) Currently translated at 100.0% (2725 of 2725 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 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/vector/src/main/res/values-cs/strings.xml b/vector/src/main/res/values-cs/strings.xml index c2e7cc62ee..5ccf495780 100644 --- a/vector/src/main/res/values-cs/strings.xml +++ b/vector/src/main/res/values-cs/strings.xml @@ -925,8 +925,8 @@ Ostatní Pokročilé Integrace - Použijte správce integrací ke správě botů, můstků, widgetů a nálepkových sad. -\nSprávci integrací obdrží konfigurační data a mohou změnit widgety, poslat pozvánky do místností a nastavit power levels Vaším jménem. + Pomocí správce integrace můžete spravovat boty, propojení, widgety a balíčky nálepek. +\nSprávci integrací přijímají konfigurační data a mohou vaším jménem upravovat widgety, odesílat pozvánky do místností a nastavovat úrovně oprávnění. Kryptografie Správa kryprografických klíčů Cíle oznámení @@ -1625,7 +1625,7 @@ Všeobecné podmínky Pročíst všeobecné podmínky Nechte se najít druhými - Použijte boty, můstky, widgety a nálepkové sady + Použijte boty, propojení, widgety a balíčky nálepek Čtěte na Server pro identity Odpojit server pro identity @@ -1944,7 +1944,7 @@ Zapnout koncové šifrování… Jakmile zapnuto, šifrování nelze vypnout. Zapnout šifrování\? - Jakmile zapnuto, šifrování místnosti nelze vypnout. Zprávy odeslané v zašifrované místnosti nemohou být čteny serverem, ale pouze účastníky místnosti. Zapnutím šifrování mohou boty a můstky přestat správně pracovat. + Jakmile je šifrování místnosti zapnuto, nelze jej vypnout. Zprávy odeslané v zašifrované místnosti nemohou být čteny serverem, ale pouze účastníky místnosti. Zapnutí šifrování může zabránit správnému fungování mnoha botů a propojení. Zapnout šifrování Za účelem bezpečnosti ověřte %s kontrolou jednorázového kódu. Za účelem bezpečnosti to proveďte osobně nebo použijte jiný způsob komunikace. From a33f49739c53fda4c925d4eaafbda7e7d4da02dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Thu, 16 Dec 2021 20:57:36 +0000 Subject: [PATCH 100/632] Translated using Weblate (Estonian) Currently translated at 99.8% (2722 of 2725 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 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/res/values-et/strings.xml b/vector/src/main/res/values-et/strings.xml index e26f548c40..3e55a23cc8 100644 --- a/vector/src/main/res/values-et/strings.xml +++ b/vector/src/main/res/values-et/strings.xml @@ -3074,7 +3074,7 @@ siit Võimalike vigade leidmiseks ja Element\'i arendamiseks jaga meiega anonüümseid andmeid. Selleks, et mõistaksime, kuidas kasutajad erinevaid seadmeid pruugivad me loome sinu seadmetele ühise juhusliku tunnuse. \n -\nMeie kasutustingimused leiad %s. +\nMeie kasutustingimused leiad siit - %s. Aita Element\'i arendamisel Võta kasutusele \ No newline at end of file From 8ad8f05e12ea18412cd4a0f0bde0301819a5589f Mon Sep 17 00:00:00 2001 From: Danial Behzadi Date: Sat, 18 Dec 2021 08:12:57 +0000 Subject: [PATCH 101/632] Translated using Weblate (Persian) Currently translated at 100.0% (2725 of 2725 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 | 46 +++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/vector/src/main/res/values-fa/strings.xml b/vector/src/main/res/values-fa/strings.xml index 353db01f32..92b79280fd 100644 --- a/vector/src/main/res/values-fa/strings.xml +++ b/vector/src/main/res/values-fa/strings.xml @@ -3032,4 +3032,50 @@ با فرستادن این اطّلاعات موافقید؟ اکنون نه برای کشف آشنایان موجود، لازم است اطلاعات آشنایان (رایانامه‌ها و شماره تلفن‌ها) را به کارساز هویتتان بفرستید. برای محرمانگیتان، داده‌هایتان را پیش از فرستادن، در هم می‌ریزیم. + با هم‌رسانی داده‌ّای استفادهٔ ناشناس، در تشخیص مشکل‌ها و بهبود المنت یاریمان کنید. برای درک چگونگی استفادهٔ مردم از چندین افزاره، شناسه‌ای کاتوره‌ای بین افزاره‌هایتان هم‌رسانی خواهیم کرد. +\n +\nمی‌توانید از %s قوانینمان را بخوانید. + مطمئنید که می خواهید این نظرسنجی را بردارید؟ پس از این کار، قادر به بازگردانیش نیستید. + برداشتن نظرسنجی + نظرسنجی پایان یافت + رأی داده شد + به کار انداختن نظرسنجی‌ها + پایان نظرسنجی + این کار اجازهٔ رأی دادن افراد را پایان داده و نتیجهٔ نهایی نظرسنجی را نمایش خواهد داد. + پایان این نظرسنجی؟ + گزینهٔ غالب + پایان نظرسنجی + + نتیجهٔ نهایی بر مبنای %1$d رأی + نتیجهٔ نهایی بر مبنای %1$d رأی + + + رأیی داده نشده + %1$d رأی داده شده. برای دیدن نتیجه، رأی دهید + + + بر مبنای %1$d رأی + بر مبنای %1$d رأی + + + %1$d رأی + %1$d رأی + + تنظیمات سامانه + نگارش‌ها + کمک در استفاده از المنت + کمک و پشتیانی + کمک + موارد حقوقی + این کارساز هیچ سیاستی فراهم نکرده. + کتاب‌خانه‌های سوم شخص + سیاست کارساز هویتتان + سیاست کارساز خانگیتان + سیاست ${app_name} + می‌توانید هر زمان، در تنظیمات خاموشش کنید + ما اطّلاعات را با سوم‌شخص‌ها هم‌رسانی نمی‌کنیم + ما هیچ دادهٔ حسابی را ذخیره یا نمایه نمی‌کنیم + این‌جا + کمک به بهبود المنت + به کار انداختن \ No newline at end of file From 597616ea925f1dd5b5f9b5f9708b8e76114cccdf Mon Sep 17 00:00:00 2001 From: notramo Date: Fri, 17 Dec 2021 21:54:44 +0000 Subject: [PATCH 102/632] Translated using Weblate (Hungarian) Currently translated at 99.9% (2723 of 2725 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 | 67 +++++++++++++++++++---- 1 file changed, 55 insertions(+), 12 deletions(-) diff --git a/vector/src/main/res/values-hu/strings.xml b/vector/src/main/res/values-hu/strings.xml index cf642ec4de..b67c06bcfd 100644 --- a/vector/src/main/res/values-hu/strings.xml +++ b/vector/src/main/res/values-hu/strings.xml @@ -1217,7 +1217,7 @@ Figyelmeztetés: ez a fájl törlésre kerülhet, ha az alkalmazást törlik.Kérlek add meg a felhasználói nevedet. Kulcs Mentés használatának megkezdése (Haladó) - Kulcsok kimentése kézzel + Kulcsok exportálása kézzel Védd a mentésedet jelmondattal. A kulcsaid másolatait titkosítva a Matrix szervereden fogjuk tárolni. Védd jelszóval a mentést, a biztonság érdekében. \n @@ -1798,7 +1798,7 @@ Ha nem te állítottad be a visszaállítási metódust, akkor egy támadó pró QR kód kép Ellenőrzés: %s Ellenőrizve: %s - Várakozás erre: %s… + Várakozás %s felhasználóra… A nagyobb biztonsághoz ellenőrizd ezt: %s az egyszeri kód összehasonlításával mindkét eszközön. \n \nA legnagyobb biztonság érdekében ezt tedd meg személyesen. @@ -1885,7 +1885,7 @@ Ha nem te állítottad be a visszaállítási metódust, akkor egy támadó pró %1$s (%2$s) új munkamenetet használva jelentkezett be: Amíg a felhasználó nem jelöli megbízhatónak ezt a munkamenetet addig a munkamenetből küldött és a munkamenetbe érkező üzenetek figyelmeztetéssel lesznek ellátva. Vagy te is ellenőrizheted a munkamenetet. Eszközök közötti hitelesítés inicializálása - Visszaállítási Kulcsok + Kulcsok alaphelyzetbe állítása QR kód Majdnem kész! %s is ugyanazt a pipát mutatja\? Igen @@ -1988,7 +1988,7 @@ Ha nem te állítottad be a visszaállítási metódust, akkor egy támadó pró %s elkészítette és beállította a szobát. Majdnem kész! A másik eszközöd is ugyanazt a pipát mutatja\? Majdnem kész! Várakozás a megerősítésre… - Várakozás erre: %s… + Várakozás %s felhasználóra… A kulcsok betöltése sikertelen Értesítések beállítása Az üzenetek „@room”-ot tartalmaznak @@ -2483,7 +2483,7 @@ Ha nem te állítottad be a visszaállítási metódust, akkor egy támadó pró Figyelmeztetés szerver oldali támogatás és kísérleti szoba verzió szükséges Kísérleti Tér - Korlátozott szobák. Meghívtak - Szobák és emberek csoportosításának új lehetősége a Terek használata. + A Terek használata egy új lehetőség a szobák és felhasználók csoportosítására. Üdv a Terekben! Szobák hozzáadása Létező szobák és tér hozzáadása @@ -2526,12 +2526,12 @@ Ha nem te állítottad be a visszaállítási metódust, akkor egy támadó pró Készítünk szobákat számukra. Később még adhatsz hozzájuk. Milyen beszélgetéseket szeretnél itt: %s\? A folytatáshoz adj neki nevet. - Adj hozzá információkat amik segítenek az embereknek beazonosítani. Bármikor megváltoztathatod. + Adj hozzá információkat amik segítenek felismerni ezt a teret. Bármikor megváltoztathatod. Adj hozzá pár információt, hogy tűnjön ki. Bármikor megváltoztathatod. Tér készítése Csak meghívóval, saját célra és csoportoknak ideális Privát - Nyílt tér mindenkinek, a legjobb a közösségeknek + Mindenki számára szabadon hozzáférhető, a közösségeknek ajánlott Nyilvános Privát tér neked és a csoporttársaidnak Én és a csoporttársaim @@ -2543,7 +2543,7 @@ Ha nem te állítottad be a visszaállítási metódust, akkor egy támadó pró Létező térbe való belépéshez meghívó szükséges. Ezt később meg lehet változtatni Milyen típusú teret szeretnél készíteni\? - Szobák és emberek csoportosításának új lehetősége a Terek használata + A Terek használata egy új lehetőség a szobák és felhasználók csoportosítására Privát tér Nyilvános tér Tér hozzáadása @@ -2643,7 +2643,7 @@ Ha nem te állítottad be a visszaállítási metódust, akkor egy támadó pró Jelenlegi PIN kód megváltoztatása PIN megváltoztatása ${app_name} megnyitásához mindig PIN kód szükséges. - Ha nem használsz ilyent: ${app_name} akkor 2 perc elteltével PIN kód megadása szükséges. + Ha 2 percnél hosszabb ideig nem használod az ${app_name} Element alkalmazást, PIN kódot fog kérni. 2 perc elteltével PIN szükséges Eszköz specifikus biometrikus azonosítás engedélyezése, mint ujjlenyomat vagy arcfelismerés. @@ -2707,7 +2707,7 @@ Ha nem te állítottad be a visszaállítási metódust, akkor egy támadó pró Folytatás ezzel: %s Vagy Állíts be címet ehhez a szobához, hogy a felhasználók a matrix szervereden megtalálhassák (%1$s) - Meghívásossá tetted. + Meghívásossá tetted ezt a beszélgetést. %1$s meghívásossá tette. Szoba beállításai Alacsony prioritásúak közül kivesz @@ -2730,7 +2730,7 @@ Ha nem te állítottad be a visszaállítási metódust, akkor egy támadó pró Videó tömörítése (%d%%) Kép tömörítése… Teljes tartalom megmutatása titkosított szobákban - Visszajelzés adása + Visszajelzés küldése A visszajelzésed nem sikerült elküldeni (%s) Köszönjük, a visszajelzésed sikeresen elküldésre került Ha további kérdés merülne fel, kapcsolatba léphetnek velem @@ -2922,7 +2922,7 @@ Ha nem te állítottad be a visszaállítási metódust, akkor egy támadó pró Videóhívás elutasítva Hanghívás elutasítva Videóhívás befejeződött • %1$s - Videóhívás befejeződött • %1$s + Hívás befejeződött • %1$s Videó hívás folyamatban Hanghívás folyamatban Bejövő videó hívás @@ -3038,4 +3038,47 @@ Ha nem te állítottad be a visszaállítási metódust, akkor egy támadó pró Beleegyezel az információk elküldésébe\? Nem most Engedélyezés + Biztosan törlöd ezt a szavazást\? Ezt a műveletet később nem lehet visszavonni. + Szavazás törlése + Szavazás lezárva + Szavazat leadva + Szavazások bekapcsolása + Szavazás lezárása + Ez megszünteti az új szavazatok leadásának lehetőségét, és kijelzi a végleges eredményt. + Lezárod a szavazást\? + Szavazás lezárása + + Eredmény %1$d szavazat alapján + Eredmény %1$d szavazat alapján + + + Nem szavazott senki + %1$d leadott szavazat. Szavazz az eredmények megtekintéséhez + + + %1$d szavazat alapján + %1$d szavazat alapján + + + %1$d szavazat + %1$d szavazat + + Rendszerbeállítások + Verziók + Segítség az Element használatában + Segítség és támogatás + Segítség + Ez a szerver nem adott meg szabályzatot. + Harmadik féltől származó programkönyvtárak + Az azonosítási szervered szabályzata + A Matrix szervered szabályzata + ${app_name} szabályzat + Később akármikor kikapcsolhatod a beállításokban + Nem osztjuk meg az információt harmadik féllel + Nem küldünk és nem profilozunk semmilyen fiók adatot + itt + Segíts észrevennünk a hibákat, és jobbá tenni az Element-et a névtelen használati adatok küldése által. Ahhoz, hogy megértsük, hogyan használnak a felhasználók egyszerre több eszközt, egy véletlenszerű azonosítót generálunk, ami az eszközeid között meg lesz osztva. +\n +\nElolvashatod a feltételeinket %s. + Segíts az Element-et jobbá tenni \ No newline at end of file From 4c33f7c5128e1d77e4c17b4e7c25ab0c3711b6a5 Mon Sep 17 00:00:00 2001 From: Linerly Date: Sun, 19 Dec 2021 09:40:01 +0000 Subject: [PATCH 103/632] Translated using Weblate (Indonesian) Currently translated at 100.0% (2725 of 2725 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 | 24 +++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/vector/src/main/res/values-in/strings.xml b/vector/src/main/res/values-in/strings.xml index 1fe0e01414..d71b4a7043 100644 --- a/vector/src/main/res/values-in/strings.xml +++ b/vector/src/main/res/values-in/strings.xml @@ -290,7 +290,7 @@ Ijinkan akses lewat halaman selanjutnya untuk menemukan pengguna ${app_name} yan Daftar Anggota Buka kop Menyinkronkan… - Arahkan ke yang belum terbaca + Arahkan ke pesan yang belum dibaca Anda telah diundang untuk bergabung ke ruang ini oleh %s Undangan ini dikirim oleh %s, yang tidak terhubung dengan akun ini. \nAnda mungkin ingin masuk dengan akun lain, atau tambahkan email ini ke akun Anda. @@ -617,10 +617,10 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Semua ruang bawaan %s Ketik di sini… - %d pesan pemberitahuan yang belum terbaca + %d pesan pemberitahuan yang belum dibaca - %d pesan pemberitahuan yang belum terbaca + %d pesan pemberitahuan yang belum dibaca Prioritas rendah Tidak Ada @@ -714,7 +714,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Negara Nomor Telepon Tampilan halaman awal Semat ruang yang pemberitahuannya terlewatkan - Semat ruang yang pesannya belum terbaca + Pin ruangan dengan pesan yang belum dibaca Perangkat Pratinjau URL dalam obrolan Tampilkan waktu kirim untuk seluruh pesan @@ -1122,7 +1122,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Ganti Tambah Salin - Tandai sebagai terbaca + Tandai sebagai dibaca Tutup Telepon Tolak Terima @@ -1393,7 +1393,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Widget aktif LIHAT %1$s: %2$s - ** Gagal mengirim - mohon buka ruangan + ** Gagal mengirim — mohon buka ruangan Saya Undangan Baru Pesan Baru @@ -1626,7 +1626,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Ini adalah percakapan Anda. Miliki percakapan Anda. Jaga percakapan tetap pribadi dengan enkripsi Chat dengan orang-orang secara langsung atau dalam grup - Pesan belum terbaca + Pesan yang belum dibaca Anda membuatnya khusus undangan. %1$s membuatnya khusus undangan. Anda membuat akses ruangan khusus undangan. @@ -2323,8 +2323,8 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Bantu orang-orang di space untuk menemukan dan bergabung ruangan privat sendiri, tidak perlu mengundang semua secara manual. Baru: Izinkan orang-orang di space untuk menemukan dan bergabung ruangan privat Dicatat bahwa meningkatkan akan membuat versi baru dari ruangannya. Semua pesan saat ini akan tetap di ruangan yang diarsip. - Siapa saja di induk ruangan dapat menemukan dan bergabung ke ruangan ini - tidak perlu mengundang semua secara manual. Anda dapat mengubahnya di pengaturan ruangan kapan saja. - Siapa saja di %s dapat menemukan dan bergabung ke ruangan ini - tidak perlu mengundang semua secara manual. Anda dapat mengubahnya di pengaturan ruangan kapan saja. + Siapa saja di induk ruangan dapat menemukan dan bergabung ke ruangan ini — tidak perlu mengundang semua secara manual. Anda dapat mengubahnya di pengaturan ruangan kapan saja. + Siapa saja di %s dapat menemukan dan bergabung ke ruangan ini — tidak perlu mengundang semua secara manual. Anda dapat mengubahnya di pengaturan ruangan kapan saja. Pesan Suara (%1$s) Tidak dapat membalas atau mengedit saat pesan suara aktif Tidak dapat merekam sebuah pesan suara @@ -2641,7 +2641,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Tautan ini %1$s akan membawa Anda ke situs lain: %2$s. \n \nApakah Anda yakin untuk melanjutkan\? - Space Eksperimental - Ruangan yang Dibatasi. + Space Eksperimental — Ruangan yang Dibatasi. Tambahkan sebuah space ke space apa saja yang Anda bisa kelola. Beri nama untuk melanjutkan. Gagal untuk memvalidasi PIN, mohon ketuk yang baru. @@ -2890,7 +2890,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Membuat poll sederhana Opsi yang Dipilih - %d suara - Hasil akhir + %d suara — Hasil akhir %d suara @@ -3003,7 +3003,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Pengaturan sistem Versi - Dapatkan bantuan dengan menggunakan Element + Dapatkan bantuan dalam menggunakan Element Bantuan dan dukungan Bantuan Hukum From cb06ba3ad0530ee0be33bf85390e566101fc84b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ar=C5=ABnas=20Sve=C4=8Diulis?= Date: Wed, 15 Dec 2021 10:12:15 +0000 Subject: [PATCH 104/632] Translated using Weblate (Lithuanian) Currently translated at 4.6% (126 of 2725 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/lt/ --- vector/src/main/res/values-lt/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/vector/src/main/res/values-lt/strings.xml b/vector/src/main/res/values-lt/strings.xml index e981623b7a..5428fe5ee8 100644 --- a/vector/src/main/res/values-lt/strings.xml +++ b/vector/src/main/res/values-lt/strings.xml @@ -125,4 +125,5 @@ Jūs baigėte skambutį. Jūs pradėjote balso skambutį. %1$s pakeitė kambario nuotrauką + Nėra prieinamų viešųjų kambarių \ No newline at end of file From d25d407cc4ba7058c853f05b7701eaa33799dc2c Mon Sep 17 00:00:00 2001 From: Anon Translator Date: Wed, 15 Dec 2021 22:56:29 +0000 Subject: [PATCH 105/632] Translated using Weblate (Polish) Currently translated at 97.6% (2662 of 2725 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/pl/ --- vector/src/main/res/values-pl/strings.xml | 43 ++++++++++++++++++++--- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/vector/src/main/res/values-pl/strings.xml b/vector/src/main/res/values-pl/strings.xml index d1ccbc8cc2..8bf7ee98f1 100644 --- a/vector/src/main/res/values-pl/strings.xml +++ b/vector/src/main/res/values-pl/strings.xml @@ -1968,7 +1968,7 @@ Nie udało się zaimportować kluczy Oczekiwanie na %s… Prawie gotowe! Oczekiwanie na potwierdzenie… - Prawie gotowe! Czy drugie urządzenie pokazuje taką samą tarczę\? + Prawie gotowe! Czy drugie urządzenie pokazuje tarczę\? "Temat: " Dodaj temat %s aby poinformować innych czego dotyczy konwersacja w tym pokoju. @@ -2063,7 +2063,7 @@ Zakończ rozmowę Zresetuj Tryb samolotowy jest włączony - Prawie gotowe! Czy %s pokazuje taką samą tarczę\? + Prawie gotowe! Czy %s pokazuje tarczę\? Twój administrator serwera zablokował domyślne szyfrowanie punkt-punkt (e2e) w pokojach prywatnych w Wiadomościach Bezpośrednich. Nie masz uprawnień żeby uaktywnić szyfrowanie w tym pokoju. Wiadomość bezpośrednia @@ -2480,8 +2480,8 @@ Usunąłeś(aś) alternatywny adres %1$s dla tego pokoju. Usunąłeś(aś) alternatywne adresy %1$s dla tego pokoju. - - + Usunąłeś(aś) alternatywne adresy %1$s dla tego pokoju. + Usunąłeś(aś) alternatywne adresy %1$s dla tego pokoju. %1$s ustawił(a) główny adres tego pokoju na %2$s. @@ -3056,4 +3056,39 @@ Powiąż ten email ze swoim kontem Stwórzmy pokój dla każdego z nich. Możesz potem dodać kolejne, także te już istniejące. Nad czym pracujesz\? + Czy na pewno chcesz usunąć ankietę\? Nie będziesz w stanie jej odzyskać po usunięciu. + + Aktywne połączenie · + %1$d aktywnych połączeń · + %1$d aktywnych połączeń · + %1$d aktywnych połączeń · + + + Nieaktywne połączenie + %1$d nieaktywnych połączeń + %1$d nieaktywnych połączeń + %1$d nieaktywnych połączeń + + Zgadzasz się na wysłanie tych informacji\? + Ustawienia systemu + Wersje + Pomoc i wsparcie + Pomoc + Biblioteki zewnętrzne + Możesz to wyłączyć kiedy zechcesz w ustawieniach + Nie udostępniamy informacji podmiotom trzecim + Nie zbieramy i nie profilujemy danych użytkownika + tutaj + Pomóż nam znaleźć błędy i ulepszyć Element poprzez udostępnianie anonimowych danych użytkowania. Aby lepiej zrozumieć jak użytkownicy wykorzystują wiele urządzeń wygenerujemy losowy identyfikator dzielony pomiędzy Twoimi urządzeniami. +\n +\nWięcej informacji %s. + Pomóż usprawnić Element + Nie teraz + Włącz + + Usunąłeś(aś) %1$s jako adres tego pokoju. + Usunąłeś(aś) %1$s jako adresy tego pokoju. + Usunąłeś(aś) %1$s jako adresy tego pokoju. + Usunąłeś(aś) %1$s jako adresy tego pokoju. + \ No newline at end of file From 0495cdf343a54e3ae86cc926b4a42d157b34e263 Mon Sep 17 00:00:00 2001 From: lvre <7uu3qrbvm@relay.firefox.com> Date: Wed, 15 Dec 2021 18:16:39 +0000 Subject: [PATCH 106/632] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (2725 of 2725 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 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/res/values-pt-rBR/strings.xml b/vector/src/main/res/values-pt-rBR/strings.xml index 8e31626f6f..b0311a332d 100644 --- a/vector/src/main/res/values-pt-rBR/strings.xml +++ b/vector/src/main/res/values-pt-rBR/strings.xml @@ -319,7 +319,7 @@ Pesquisa global Marcar tudo como lido Histórico - Responder rápido + Resposta rápida Abrir Fechar Copiado para clipboard From 50a9e444ce7cbb703b11b10aa533dfddc24b1c1f Mon Sep 17 00:00:00 2001 From: Jozef Gaal Date: Sat, 18 Dec 2021 11:53:41 +0000 Subject: [PATCH 107/632] Translated using Weblate (Slovak) Currently translated at 53.6% (1461 of 2725 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 | 98 ++++++++++++++++------- 1 file changed, 68 insertions(+), 30 deletions(-) diff --git a/vector/src/main/res/values-sk/strings.xml b/vector/src/main/res/values-sk/strings.xml index 4570de7f9f..f0e7f3c56e 100644 --- a/vector/src/main/res/values-sk/strings.xml +++ b/vector/src/main/res/values-sk/strings.xml @@ -102,9 +102,9 @@ %1$s pridal/a adresy %2$s pre túto miestnosť. - %1$s odstránil/a adresu %2$s pre túto miestnosť. - %1$s odstránil/a adresy %3$s pre túto miestnosť. - %1$s odstránil/a adresy %3$s pre túto miestnosť. + %1$s odstránil/a %2$s adresu pre túto miestnosť. + %1$s odstránil/a %2$s adries pre túto miestnosť. + %1$s odstránil/a %2$s adresy pre túto miestnosť. %1$s pridal/a adresy %2$s a odstránil/a adresy %3$s pre túto miestnosť. %1$s nastavil/a hlavnú adresu tejto miestnosti %2$s. @@ -608,8 +608,8 @@ Aktualizovať verejné meno Naposledy videné %1$s @ %2$s - Táto operácia si vyžaduje dodatočné overenie. -Ak chcete pokračovať, prosím zadajte vaše heslo. + Táto operácia si vyžaduje dodatočné overenie. +\nAk chcete pokračovať, zadajte prosím vaše heslo. Overenie Heslo: Odoslať @@ -631,9 +631,9 @@ Ak chcete pokračovať, prosím zadajte vaše heslo. Potvrdiť nové heslo Nepodarilo sa zmeniť heslo Úspešne ste si zmenili heslo - Zobraziť všetky správy od používateľa %s? - -Pozor! Vykonaním tejto akcie reštartujete aplikáciu a opätovné načítanie môže chvíľu trvať. + Zobraziť všetky správy od %s\? +\n +\nUpozorňujeme, že táto akcia spôsobí reštart aplikácie a môže chvíľu trvať. Ste si istí, že chcete odstrániť tento cieľ oznámení? Ste si istí, že chcete odstrániť %1$s %2$s? Vyberte krajnu @@ -696,7 +696,7 @@ Pozor! Vykonaním tejto akcie reštartujete aplikáciu a opätovné načítanie V tejto miestnosti je povolené šifrovanie. V tejto miestnosti nie je povolené šifrovanie. Povoliť šifrovanie -(Pozor: Nie je viac možné zakázať!) +\n(Pozor: nie je možné ho znova vypnúť!) Adresár Vzhľad %s sa pokúšal zobraziť konkrétny bod v histórii tejto miestnosti, no zodpovedajúcu udalosť sa nepodarilo nájsť. @@ -721,9 +721,9 @@ Pozor! Vykonaním tejto akcie reštartujete aplikáciu a opätovné načítanie Exportovať Zadajte heslo Potvrďte heslo - Šifrovacie kľúče miestností boli uložené do %s - -Pozor: tento súbor môže byť automaticky zmazaný po odinštalovaní aplikácii Element. + Kľúče od miestnosti E2E boli uložené do \'%s\'. +\n +\nUpozornenie: Tento súbor sa môže vymazať, ak sa aplikácia odinštaluje. Importovať E2E kľúče miestností Importovať kľúče miestností Importovať kľúče z lokálneho súboru @@ -938,11 +938,11 @@ Pozor: tento súbor môže byť automaticky zmazaný po odinštalovaní aplikác Ak chcete aj naďalej používať domovský server %1$s , mali by ste si prečítať a odsúhlasiť naše zmluvné podmienky. Prečítať teraz Deaktivovať účet - Toto spôsobí, že váš účet nebude viac použiteľný. Nebudete sa môcť opätovne prihlásiť a nikto sa nebude môcť znovu zaregistrovať s rovnakým používateľským ID. Deaktiváciou účtu opustíte všetky miestnosti, do ktorých ste kedy vstúpili a vaše kontaktné údaje budú odstránené zo servera totožností. Túto akciu nie je možné vrátiť späť.. - -Pri deaktivácii účtu predvolene neodstraňujeme vami odoslané správy. Ak si želáte uplatniť právo zabudnutia, zaškrtnite prosím zodpovedajúce pole nižšie. - -Viditeľnosť správ odoslaných cez matrix funguje podobne ako viditeľnosť správ elektronickej pošty. To, že zabudneme vaše správy v skutočnosti znamená, že správy ktoré ste už odoslali nebudú čitateľné pre nových alebo neregistrovaných používateľov, no registrovaní používatelia, ktorí už prístup k vašim správam majú, budú aj naďalej bez zmeny môcť pristupovať k ich vlastným kópiám vašich správ. + Tým sa vaše konto stane trvalo nepoužiteľným. Nebudete sa môcť prihlásiť a nikto nebude môcť opätovne zaregistrovať rovnaké ID používateľa. To spôsobí, že váš účet opustí všetky miestnosti, ktorých sa zúčastňuje, a odstráni údaje o vašom účte zo servera identity. Táto akcia je nezvratná. +\n +\nDeaktivácia vášho účtu nezapríčiní štandardne, že zabudneme na správy, ktoré ste odoslali. Ak chcete, aby sme vaše správy zabudli, označte nižšie uvedené políčko. +\n +\nViditeľnosť správ v Matrixe je podobná ako v prípade e-mailu. Naše zabudnutie vašich správ znamená, že správy, ktoré ste poslali, nebudú sprístupnené žiadnym novým alebo neregistrovaným používateľom, ale registrovaní používatelia, ktorí už majú prístup k týmto správam, budú mať stále prístup k ich kópii. Spolu s deaktivovaním účtu si želám odstrániť všetky mnou odoslané správy (Pozor: Môže sa stať, že noví používatelia uvidia neúplnú históriu konverzácií) Aby ste mohli pokračovať, prosím zadajte svoje heslo: Deaktivovať účet @@ -1020,7 +1020,7 @@ Viditeľnosť správ odoslaných cez matrix funguje podobne ako viditeľnosť sp %d miestnosti %d miestností - Táto miestnosť bola nahradená inou a nie je viac aktívna + Táto miestnosť bola nahradená inou a nie je viac aktívna. Konverzácia pokračuje tu Táto miestnosť je pokračovaním predchádzajúcej konverzácii Staršie správy zobrazíte kliknutím sem @@ -1097,8 +1097,8 @@ Viditeľnosť správ odoslaných cez matrix funguje podobne ako viditeľnosť sp \n%1$s Registrácia tokenu Token FCM úspešne zaregistrovaný na domovskom servery. - FCM token sa nepodarilo zaregistrovať na domoskom servery: -%1$s + FCM token sa nepodarilo zaregistrovať na domovskom serveri: +\n %1$s Služba oznámení Služba oznámení je spustená. Služba oznámení nie je spustená. @@ -1112,11 +1112,11 @@ Viditeľnosť správ odoslaných cez matrix funguje podobne ako viditeľnosť sp Služba sa automaticky nespustí po reštarte zariadenia a nedostanete po reštarte žiadne oznámenia, kým nespustíte ${app_name} aspoň raz. Povoliť spustenie služby po reštarte Kontrola obmedzenia spustenia na pozadí - Obmedzenie spustenia na pozadí nie je aktívne pre ${app_name}. Tento test je potrebné spustiť cez mobilné dáta (nie cez wifi). -%1$s - Obmedzenie spustenia na pozadí je aktívne pre ${app_name}. -Činnosť aplikácii bude agresívne obmedzovaná operačným systémom, keď je ${app_name} na pozadí. Môže to mať negatívny vplyv na doručovanie oznámení. -%1$s + Obmedzenie spustenia na pozadí nie je aktívne pre ${app_name}. Tento test je potrebné spustiť cez mobilné dáta (nie cez wifi). +\n%1$s + Pre ${app_name} sú povolené obmedzenia na pozadí. +\nPráca, ktorú sa aplikácia pokúsi vykonať, bude agresívne obmedzená, keď je na pozadí, čo môže ovplyvniť oznámenia. +\n%1$s Deaktivovať obmedzenia Optimalizácia batérie Chod ${app_name} nie je ovplyvnený nastavením optimalizácie batérie. @@ -1134,8 +1134,8 @@ Viditeľnosť správ odoslaných cez matrix funguje podobne ako viditeľnosť sp Zobrazovať udalosti účtu Zahŕňa zmeny zobrazovaného mena a obrázka v profile. Pripojenie na pozadí - ${app_name} potrebuje na pozadí udržovať aktívne nenáročné spojenie, aby spoľahlivo fungovali oznámenia. -Na ďalšej obrazovke vás systém požiada o povolenie vždy bežať na pozadí pre ${app_name}. Prosím povoľte ho. + ${app_name} potrebuje na pozadí udržovať aktívne nenáročné spojenie, aby spoľahlivo fungovali oznámenia. +\nNa ďalšej obrazovke budete vyzvaní, aby ste povolili aplikácii ${názov_aplikácie}, aby sa vždy spúšťala na pozadí, prosím, prijmite. Udeliť oprávnenie Pri pokuse overiť vašu emailovú adresu sa vyskytla chyba. Heslo @@ -1303,7 +1303,7 @@ Na ďalšej obrazovke vás systém požiada o povolenie vždy bežať na pozadí Potvrdiť vaše heslo Toto nie je možné urobiť cez mobilnú aplikáciu ${app_name} Je požadované overenie - Režim synchronizácie na pozadí (Experimentálne) + Režim synchronizácie na pozadí Optimalizovaný na využívanie batérie ${app_name} sa bude synchronizovať na pozadí s cieľom ušetriť limitované zdroje (batériu). \nV závislosti od kapacity prostriedkov zariadenia môže byť synchronizácia odložená operačným systémom na neskôr. @@ -1565,9 +1565,9 @@ Na ďalšej obrazovke vás systém požiada o povolenie vždy bežať na pozadí Vitajte doma! Tu nájdete neprečítané správy Konverzácie - Tu sa zobrazia priame konverzácie + Tu sa budú zobrazovať vaše konverzácie v priamych správach. Ťuknutím na + vpravo dole niektorú z nich spustíte. Miestnosti - Tu sa zobrazia vaše miestnosti + Tu sa zobrazia vaše miestnosti. Ťuknutím na + vpravo dole nájdete existujúce miestnosti alebo si môžete založiť vlastnú. Reakcie Súhlasím Páči sa mi @@ -1687,4 +1687,42 @@ Na ďalšej obrazovke vás systém požiada o povolenie vždy bežať na pozadí Odsúhlaste podmienky používania služieb servera totožností (%s), aby vás ostatní mohli nájsť podľa emailovej adresy alebo telefónneho čísla. Povoliť podrobné záznamy. Podrobné záznamy pomôžu vývojárom poskytnúť viac detailov, keď posielate hlásenie o chybe. Aj keď je toto zapnuté, aplikácia nezaznamenáva obsah správ ani žiadne iné súkromné údaje. + Oznámenia + Viac + Zabezpečenie + Čakanie… + Nálepka + Súbor + Audio + Obrázok. + Video. + Nastavenia + Heslo + Upozornenie + Ďalej + Heslo + Používateľské meno + Ďalej + Ďalej + Ďalej + Email + Upozornenie + Úspech! + Pokračovať + Upozornenie! + Email + Ďalej + Adresa + Pokračovať + Ďalšie + Nastavenia + Stlmiť + SÚBORY + Nálepka + Galéria + Audio + Kamera + Kontakt + Súbor + Ďalšie \ No newline at end of file From dff9bbcf944482466c8a27da6966208749a53956 Mon Sep 17 00:00:00 2001 From: LinAGKar Date: Wed, 15 Dec 2021 21:12:45 +0000 Subject: [PATCH 108/632] Translated using Weblate (Swedish) Currently translated at 99.0% (2700 of 2725 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 | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/vector/src/main/res/values-sv/strings.xml b/vector/src/main/res/values-sv/strings.xml index 9d880aaa70..f472417061 100644 --- a/vector/src/main/res/values-sv/strings.xml +++ b/vector/src/main/res/values-sv/strings.xml @@ -3032,4 +3032,13 @@ Inte nu För att hitta existerande kontakter så behöver du skicka kontaktinfo (e-postadresser och telefonnummer) till din identitetsserver. Vi hashar din data innan den skickas av sekretesskäl. Går du med på att skicka den här infon\? + Du kan stänga av detta när som helst i inställningarna + Vi delar inte information med tredje parter + Vi spelar inte in eller profilerar någon kontodata + här + Hjälp oss att identifiera problem och förbättra Element genom att dela anonym användningsdata. För att förstå hur personer använder multipla enheter så generar vi en slumpmässig identifierare som delas mellan dina enheter. +\n +\nDu kan läsa alla våra villkor %s. + Hjälp att förbättra Element + Aktivera \ No newline at end of file From f1c31f048a08bd61797c8a2d1f8882bdd6b8aa21 Mon Sep 17 00:00:00 2001 From: Denys Nykula Date: Thu, 16 Dec 2021 18:33:10 +0000 Subject: [PATCH 109/632] Translated using Weblate (Ukrainian) Currently translated at 100.0% (2725 of 2725 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 | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/vector/src/main/res/values-uk/strings.xml b/vector/src/main/res/values-uk/strings.xml index 68fc3ff5eb..641b0f7aa1 100644 --- a/vector/src/main/res/values-uk/strings.xml +++ b/vector/src/main/res/values-uk/strings.xml @@ -354,7 +354,7 @@ Ви не перейшли за посиланням в електронному листі - Перегляд списку вступників + Список міток прочитання Надіслати як @@ -842,16 +842,16 @@ \n \nДодати зараз\? - %d активний участник - %d активних участника - %d активних участників - + %d активний учасник + %d активні учасники + %d активних учасників + %d активних учасників - %d участник - %d участника - %d участників - + %d учасник + %d учасники + %d учасників + %d учасників Заблокованих користувачів буде вилучено з цієї кімнати й вони не зможуть приєднатися знову. @@ -2875,7 +2875,7 @@ Попередження: вимагає підтримки сервера та експериментальної версії кімнати Бажаєте поекспериментувати\? \nМожете додати наявні простори до простору. - Експериментальний простір - обмежена кімната. + Експериментально: доступ до кімнати для будь-кого в просторі. Усі кімнати, у яких ви перебуваєте, буде показано на сторінці Домівка. Показувати у Домівці Перевірку скасовано From b2477af0f1bc9bf8deb300195634f0e747283780 Mon Sep 17 00:00:00 2001 From: Ihor Hordiichuk Date: Wed, 15 Dec 2021 21:51:50 +0000 Subject: [PATCH 110/632] Translated using Weblate (Ukrainian) Currently translated at 100.0% (2725 of 2725 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 641b0f7aa1..d022c84d27 100644 --- a/vector/src/main/res/values-uk/strings.xml +++ b/vector/src/main/res/values-uk/strings.xml @@ -1200,7 +1200,7 @@ Режим розробника Додаткові налаштування Розширені налаштування сповіщень - Голос та відео + Голос і відео Налаштування Інші сеанси Поточний сеанс @@ -1356,7 +1356,7 @@ Встановіть парольну фразу Ми збережемо зашифровану копію ваших ключів на вашому домашньому сервері. Захистіть свою резервну копію парольною фразою, щоб захистити її. \n -\nДля максимальної безпеки фраза повинна відрізнятися від паролю вашого облікового запису. +\nДля максимальної безпеки фраза повинна відрізнятися від пароля вашого облікового запису. Захистіть свою резервну копію за допомогою парольної фрази. Експорт ключів вручну Почати використовувати резервне копіювання ключів From de4a7f2777172844d5b561da4eac73a8b317fbda Mon Sep 17 00:00:00 2001 From: libexus Date: Fri, 17 Dec 2021 15:32:55 +0000 Subject: [PATCH 111/632] Translated using Weblate (German) Currently translated at 100.0% (40 of 40 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/40103050.txt | 2 ++ fastlane/metadata/android/de-DE/changelogs/40103060.txt | 2 ++ fastlane/metadata/android/de-DE/changelogs/40103090.txt | 2 ++ 3 files changed, 6 insertions(+) create mode 100644 fastlane/metadata/android/de-DE/changelogs/40103050.txt create mode 100644 fastlane/metadata/android/de-DE/changelogs/40103060.txt create mode 100644 fastlane/metadata/android/de-DE/changelogs/40103090.txt diff --git a/fastlane/metadata/android/de-DE/changelogs/40103050.txt b/fastlane/metadata/android/de-DE/changelogs/40103050.txt new file mode 100644 index 0000000000..a3e40e9e03 --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/40103050.txt @@ -0,0 +1,2 @@ +Änderungen in dieser Version: Unterstützung für Anwesenheitsstatus in Direktnachrichten (Momentan auf matrix.org deaktiviert), Android Auto funktioniert wieder. +Änderungsliste: https://github.com/vector-im/element-android/releases/tag/v1.3.5 diff --git a/fastlane/metadata/android/de-DE/changelogs/40103060.txt b/fastlane/metadata/android/de-DE/changelogs/40103060.txt new file mode 100644 index 0000000000..dcd8d3634d --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/40103060.txt @@ -0,0 +1,2 @@ +Änderungen in dieser Version: Unterstützung für Anwesenheitsstatus in Direktnachrichten (Momentan auf matrix.org deaktiviert), Android Auto funktioniert wieder. +Änderungsliste: https://github.com/vector-im/element-android/releases/tag/v1.3.6 diff --git a/fastlane/metadata/android/de-DE/changelogs/40103090.txt b/fastlane/metadata/android/de-DE/changelogs/40103090.txt new file mode 100644 index 0000000000..028df4942f --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/40103090.txt @@ -0,0 +1,2 @@ +Hauptänderungen: Verbesserungen bei Sprachnachrichten, Bugfixes. +Änderungsliste: https://github.com/vector-im/element-android/releases/tag/v1.3.9 From 63866a6a2d72aa540bb7c75cc548a68d13abcc5b Mon Sep 17 00:00:00 2001 From: Szimszon Date: Fri, 17 Dec 2021 07:38:32 +0000 Subject: [PATCH 112/632] Translated using Weblate (Hungarian) Currently translated at 100.0% (40 of 40 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/hu/ --- fastlane/metadata/android/hu-HU/changelogs/40103090.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/hu-HU/changelogs/40103090.txt diff --git a/fastlane/metadata/android/hu-HU/changelogs/40103090.txt b/fastlane/metadata/android/hu-HU/changelogs/40103090.txt new file mode 100644 index 0000000000..d4189121bb --- /dev/null +++ b/fastlane/metadata/android/hu-HU/changelogs/40103090.txt @@ -0,0 +1,2 @@ +Fő változás ebben a verzióban: Hang üzenet piszkozat támogatás. Sok egyéb hibajavítás. +Teljes változásnapló: https://github.com/vector-im/element-android/releases/tag/v1.3.9 From 7992e6a5514d9c7b36cff98216f6c2c567cca1af Mon Sep 17 00:00:00 2001 From: Jozef Gaal Date: Sat, 18 Dec 2021 11:58:29 +0000 Subject: [PATCH 113/632] Translated using Weblate (Slovak) Currently translated at 37.5% (15 of 40 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/sk/ --- .../android/sk/changelogs/40103060.txt | 2 + .../android/sk/changelogs/40103090.txt | 2 + .../metadata/android/sk/full_description.txt | 49 ++++++++++++------- fastlane/metadata/android/sk/title.txt | 2 +- 4 files changed, 35 insertions(+), 20 deletions(-) create mode 100644 fastlane/metadata/android/sk/changelogs/40103060.txt create mode 100644 fastlane/metadata/android/sk/changelogs/40103090.txt diff --git a/fastlane/metadata/android/sk/changelogs/40103060.txt b/fastlane/metadata/android/sk/changelogs/40103060.txt new file mode 100644 index 0000000000..c9a3b8bb75 --- /dev/null +++ b/fastlane/metadata/android/sk/changelogs/40103060.txt @@ -0,0 +1,2 @@ +Hlavné zmeny v tejto verzii: Pridanie podpory prítomnosti pre miestnosť s priamymi správami (poznámka: prítomnosť je na matrix.org vypnutá). Opätovné pridanie podpory Android Auto. +Úplný zoznam zmien: https://github.com/vector-im/element-android/releases/tag/v1.3.6 diff --git a/fastlane/metadata/android/sk/changelogs/40103090.txt b/fastlane/metadata/android/sk/changelogs/40103090.txt new file mode 100644 index 0000000000..d719d5055c --- /dev/null +++ b/fastlane/metadata/android/sk/changelogs/40103090.txt @@ -0,0 +1,2 @@ +Hlavné zmeny v tejto verzii: Pridanie podpory pre návrh hlasovej správy. Oprava mnohých chýb! +Úplný zoznam zmien: https://github.com/vector-im/element-android/releases/tag/v1.3.9 diff --git a/fastlane/metadata/android/sk/full_description.txt b/fastlane/metadata/android/sk/full_description.txt index b4c9e98777..78661e961e 100644 --- a/fastlane/metadata/android/sk/full_description.txt +++ b/fastlane/metadata/android/sk/full_description.txt @@ -1,30 +1,41 @@ -Element je inovatívny kolaboračný komunikátor a messenger ktorý: +Element je zabezpečený messenger a zároveň aplikácia na tímovú spoluprácu, ktorá je ideálna na skupinové konverzácie pri práci na diaľku. Táto komunikačná aplikácia využíva end-to-end šifrovanie na poskytovanie výkonných videokonferencií, zdieľania súborov a hlasových hovorov. -1. Ponecháva kontrolu nad vaším súkromím -2. Umožňuje komunikovať s kýmkoľvek v sieti Matrix a vďaka integráciám aj s rôznymi inými aplikáciami ako napríklad Slack -3. Chráni vás pred reklamami, zhromažďovaním údajov a uzavretými platformami -4. Posilňuje vašu bezpečnosť vďaka E2E šifrovaniu a krížovému podpisovaniu určenému na overovanie ostatných +Funkcie aplikácie Element zahŕňajú: +- Pokročilé nástroje na online komunikáciu +- Plne šifrované správy umožňujúce bezpečnejšiu firemnú komunikáciu aj pre pracovníkov na diaľku +- Decentralizované konverzácie založené na open source frameworku Matrix +- Bezpečné zdieľanie súborov so šifrovanými údajmi pri správe projektov +- Videochaty s funkciou Voice over IP a zdieľaním obrazovky +- Jednoduchá integrácia s obľúbenými nástrojmi na online spoluprácu, nástrojmi na riadenie projektov, službami VoIP a inými aplikáciami na tímovú komunikáciu -Element sa od ostatných komunikačných a kolaboračných aplikácií odlišuje tým, že je decentralizovaný a open-source. +Element sa úplne líši od ostatných aplikácií na zasielanie správ a spoluprácu. Funguje na Matrixe, otvorenej sieti na bezpečné posielanie správ a decentralizovanú komunikáciu. Umožňuje vlastný hosting, aby používatelia získali maximálne vlastníctvo a kontrolu nad svojimi údajmi a správami. -S Elementom sa môžete pripojiť k vlastnému serveru alebo si môžete vybrať server s dôveryhodným poskytovateľom, čím si zachováte súkromie, vlastníctvo a kontrolu nad vašimi konverzáciami a údajmi. Získate tak prístup do otvorenej siete a teda nie ste limitovaní na komunikáciu len s ostatnými Element používateľmi. A samozrejme je vaša komunikácia dobre zabezpečná. +Súkromie a šifrovanie správ +Element vás chráni pred nežiaducimi reklamami, ťažbou údajov a tzv. walled gardens. Zabezpečuje tiež všetky vaše údaje, video a hlasovú komunikáciu jeden na jedného prostredníctvom end-to-end šifrovania a overovania zariadení krížovým podpisovaním +Element vám poskytuje kontrolu nad vaším súkromím a zároveň vám umožňuje bezpečne komunikovať s kýmkoľvek v sieti Matrix alebo s inými nástrojmi na podnikovú spoluprácu vďaka integrácii s aplikáciami, ako je napríklad Slack. -Element všetko toto dokáže vďaka tomu, že pracuje podľa protokolu Matrix - štandardu na otvorenú, decentralizovanú komunikáciu. +Element môže byť na vašom vlastnom serveri. +Aby ste mali väčšiu kontrolu nad svojimi citlivými údajmi a konverzáciami, Element môže byť na vašom vlastnom serveri alebo si môžete vybrať ľubovoľný hosting založený na systéme Matrix - štandarde pre decentralizovanú komunikáciu s otvoreným zdrojovým kódom. Element vám poskytuje súkromie, súlad s bezpečnostnými predpismi a flexibilitu integrácie. -Element vám dáva kontrolu tým, že si samy vyberiete, ako budete spravovať (ang. host) vaše konverzácie. Priamo v aplikácii Element si môžete vybrať z rôznych spôsobov hostovania: +Vlastnite svoje údaje +Vy rozhodujete o tom, kde budú vaše údaje a správy uložené. Bez rizika ťažby údajov alebo prístupu tretích strán. -1. Získajte účet zdarma na verejnom servery matrix.org od vývojárov protokolu Matrix alebo si vyberte z tísíce iných serverov hostovaných dobrovoľníkmi -2. Hostujte si účet spustením vlastného servera použitím vlastného hardvéru -3. Prihláste sa k účtu na vlastnom servery objednaním služieb na platforme Element Matrix Services +Element vám dáva kontrolu rôznymi spôsobmi: +1. Získajte bezplatné konto na verejnom serveri matrix.org, ktorý hostia vývojári Matrixu, alebo si vyberte z tisícov verejných serverov, ktoré hostia dobrovoľníci. +2. Vlastný hosting účtu spustením servera na vlastnej IT infraštruktúre. +3. Zaregistrujte si účet na vlastnom serveri tak, že si jednoducho predplatíte hostingovú platformu Element Matrix Services. -Prečo si vybrať Element? +Otvorené zasielanie správ a spolupráca +Môžete komunikovať s kýmkoľvek v sieti Matrix, či už používa aplikáciu Element, inú aplikáciu Matrix alebo dokonca ak používa inú aplikáciu na zasielanie správ. -PONECHAJTE SI VAŠE ÚDAJE: Len vy rozhodujete o tom, kde si budete uchovávať vaše správy a ostatné údaje. Len vy vlastníte vaše údaje a riadite zaobchádzanie s nimi, nie nejaká megakorporácia, ktorá z nich ťaží alebo ich poskytuje tretím stranám. +Vynikajúce zabezpečenie +Skutočné end-to-end šifrovanie (správy môžu dešifrovať len účastníci konverzácie) a krížové overovanie zariadení. -OTVORENÁ KOMUNIKÁCIA a KOLABORÁCIA: Konverzovať môžete s kýmkoľvek v otvorenej sieti Matrix nezávisle na tom, či používa Element, inú kompatibilnú aplikáciu, ba dokkonca aj s tými, ktorí používajú úplne inú platformu určenú na okamžitú komunikáciu ako sú Slack, IRC alebo XMPP. +Kompletná komunikácia a integrácia +Správy, hlasové a video hovory, zdieľanie súborov, zdieľanie obrazovky a celý rad integrácií, botov a widgetov. Vytvárajte miestnosti, komunity, zostaňte v kontakte a vybavujte veci. -VEĽMI VYSOKÉ ZABEZPEČENIE: Skutočné šifrovanie od zariadenia k zariadeniu (len diskutujúci môžu dešifrovať správy) a krížové podpisovanie určené na overovanie jednotlivých zariadení členov konverzácií. +Nadviažte tam, kde ste skončili +Buďte v kontakte, nech ste kdekoľvek, vďaka plne synchronizovanej histórii správ vo všetkých zariadeniach a na webe na adrese https://app.element.io. -KOMPLETNÁ KOMUNIKÁCIA: Okamžité správy, telefonáty a video hovory, zdieľanie súborov, zdieľanie obrazovky a veľké množstvo integrácií, botov a widgetov. Vytvorte si vlastné miestnosti, založte komunity, ostante v kontakte a vyriešte problémy. - -KDEKOĽVEK SA NACHÁDZATE: Ostante v kontakte kdekoľvek ste s plne synchronizovanou históriou konverzácií naprieč všetkými vašimi zariadeniami a aj cez web na adrese https://app.element.io. +Otvorený zdroj +Element Android je projekt s otvoreným zdrojovým kódom, ktorého hostiteľom je GitHub. Nahlasujte chyby a/alebo prispievajte k jeho vývoju na adrese https://github.com/vector-im/element-android. diff --git a/fastlane/metadata/android/sk/title.txt b/fastlane/metadata/android/sk/title.txt index dd02c784e8..fa7155e82e 100644 --- a/fastlane/metadata/android/sk/title.txt +++ b/fastlane/metadata/android/sk/title.txt @@ -1 +1 @@ -Element (kedysi Riot.im) +Element - Bezpečný messenger From ccf75afed0c2a5528036ad1cd2c81624d6566d51 Mon Sep 17 00:00:00 2001 From: Danial Behzadi Date: Sat, 18 Dec 2021 08:14:03 +0000 Subject: [PATCH 114/632] Translated using Weblate (Persian) Currently translated at 100.0% (40 of 40 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/fa/ --- fastlane/metadata/android/fa/changelogs/40103090.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/fa/changelogs/40103090.txt diff --git a/fastlane/metadata/android/fa/changelogs/40103090.txt b/fastlane/metadata/android/fa/changelogs/40103090.txt new file mode 100644 index 0000000000..75810a0e23 --- /dev/null +++ b/fastlane/metadata/android/fa/changelogs/40103090.txt @@ -0,0 +1,2 @@ +تغییرات عمده در این نگارش: افزودن پشتیبان از چرک‌نویس‌های صوتی. رفع چندین مشکل! +گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases/tag/v1.3.9 From b72d40a3e7887aa64656300035d61d70d0c25175 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Thu, 16 Dec 2021 12:43:02 +0000 Subject: [PATCH 115/632] Translated using Weblate (Hebrew) Currently translated at 76.1% (2075 of 2725 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 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/vector/src/main/res/values-iw/strings.xml b/vector/src/main/res/values-iw/strings.xml index ffda58abe6..947c58ad79 100644 --- a/vector/src/main/res/values-iw/strings.xml +++ b/vector/src/main/res/values-iw/strings.xml @@ -825,10 +825,10 @@ %1$s ב %2$s ו %3$s %1$s ב %2$s - d% - d% - d% - d% + %d + %d + %d + %d הודעה %1$s: %2$d From 4a8bf437fc0061cebefb77e3d0d27828c3e1ce13 Mon Sep 17 00:00:00 2001 From: Linerly Date: Thu, 16 Dec 2021 10:26:17 +0000 Subject: [PATCH 116/632] Translated using Weblate (Indonesian) Currently translated at 100.0% (40 of 40 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/id/ --- fastlane/metadata/android/id/short_description.txt | 2 +- fastlane/metadata/android/id/title.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fastlane/metadata/android/id/short_description.txt b/fastlane/metadata/android/id/short_description.txt index 1cd770dd73..72c520403c 100644 --- a/fastlane/metadata/android/id/short_description.txt +++ b/fastlane/metadata/android/id/short_description.txt @@ -1 +1 @@ -Perpesanan grup - perpesanan, panggilan suara dan video grup terenkripsi +Perpesanan grup — perpesanan, panggilan suara dan video grup terenkripsi diff --git a/fastlane/metadata/android/id/title.txt b/fastlane/metadata/android/id/title.txt index aec5dc9351..08ad7afa67 100644 --- a/fastlane/metadata/android/id/title.txt +++ b/fastlane/metadata/android/id/title.txt @@ -1 +1 @@ -Element - Perpesanan Aman +Element — Perpesanan Aman From b8f9c3f600d5f24240f16f63fbfc0d2ef150f9ff Mon Sep 17 00:00:00 2001 From: rickeesingh Date: Sat, 18 Dec 2021 04:14:08 +0000 Subject: [PATCH 117/632] Translated using Weblate (Punjabi) Currently translated at 4.0% (110 of 2725 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/pa/ --- vector/src/main/res/values-pa/strings.xml | 113 +++++++++++++++++++++- 1 file changed, 112 insertions(+), 1 deletion(-) diff --git a/vector/src/main/res/values-pa/strings.xml b/vector/src/main/res/values-pa/strings.xml index a6b3daec93..9037c25c0c 100644 --- a/vector/src/main/res/values-pa/strings.xml +++ b/vector/src/main/res/values-pa/strings.xml @@ -1,2 +1,113 @@ - \ No newline at end of file + + %1$s ਨੇ %2$s ਲਈ ਸੱਦਾ ਰੱਦ ਕੀਤਾ + %1$s ਨੇ %2$s ਲਈ ਥਾਂ ਵਿੱਚ ਸ਼ਾਮਲ ਹੋਣ ਦਾ ਸੱਦਾ ਰੱਦ ਕੀਤਾ + ਤੁਸੀਂ %1$s ਲਈ ਥਾਂ ਵਿੱਚ ਸ਼ਾਮਲ ਹੋਣ ਦਾ ਸੱਦਾ ਰੱਦ ਕੀਤਾ + ਤੁਸੀਂ %1$s ਨੂੰ ਸੱਦਾ ਘੱਲਿਆ + %1$s ਨੇ %2$s ਨੂੰ ਸੱਦਾ ਘੱਲਿਆ + ਤੁਸੀਂ %1$s ਨੂੰ ਥਾਂ ਵਿੱਚ ਜੁੜਨ ਦਾ ਸੱਦਾ ਘੱਲਿਆ + %1$s ਨੇ %2$s ਨੂੰ ਥਾਂ ਵਿੱਚ ਜੁੜਨ ਦਾ ਸੱਦਾ ਘੱਲਿਆ + ਤੁਸੀਂ ਆਪਣੀ ਪ੍ਰੋਫ਼ਾਈਲ ਅੱਪਡੇਟ ਕੀਤੀ %1$s + %1$s ਨੇ ਆਪਣੀ ਪ੍ਰੋਫ਼ਾਈਲ ਅੱਪਡੇਟ ਕੀਤੀ %2$s + %1$s ਵੱਲੋਂ ਸੁਨੇਹਾ ਹਟਾਇਆ ਗਿਆ [ਕਾਰਨ: %2$s] + ਸੁਨੇਹਾ ਹਟਾਇਆ ਗਿਆ [ਕਾਰਨ: %1$s] + %1$s ਵਲੋਂ ਸੁਨੇਹਾ ਹਟਾਇਆ ਗਿਆ + ਸੁਨੇਹਾ ਹਟਾਇਆ ਗਿਆ + ਤੁਸੀਂ ਥਾਂ ਦਾ ਅਵਤਾਰ ਹਟਾਇਆ + %1$s ਨੇ ਥਾਂ ਦਾ ਅਵਤਾਰ ਹਟਾਇਆ + ਤੁਸੀਂ ਥਾਂ ਦਾ ਵਿਸ਼ਾ ਹਟਾਇਆ + %1$s ਨੇ ਥਾਂ ਦਾ ਵਿਸ਼ਾ ਹਟਾਇਆ + ਤੁਸੀਂ ਥਾਂ ਦਾ ਨਾਮ ਹਟਾਇਆ + %1$s ਨੂੰ ਥਾਂ ਦੇ ਨਾਮ ਵਿੱਚੋਂ ਹਟਾਇਆ ਗਿਆ + (ਅਵਤਾਰ ਵੀ ਬਦਲਿਆ ਗਿਆ) + VoIP ਕਾਨਫ਼ਰੰਸ ਮੁਕੰਮਲ + VoIP ਕਾਨਫ਼ਰੰਸ ਚਾਲੂ + ਤੁਸੀਂ ਇੱਕ VoIP ਕਾਨਫ਼ਰੰਸ ਦੀ ਬੇਨਤੀ ਕੀਤੀ + %1$s ਨੇ ਇੱਕ VoIP ਕਾਨਫ਼ਰੰਸ ਦੀ ਬੇਨਤੀ ਕੀਤੀ + 🎉 ਸਾਰੇ ਸਰਵਰਾਂ ਦੇ ਭਾਗ ਲੈਣ ਤੇ ਪਬੰਦੀ ਲਗਾ ਦਿੱਤੀ ਹੈ। ਇਹ ਥਾਂ ਹੁਣ ਵਰਤਿਆ ਨਹੀਂ ਜਾ ਸਕਦਾ। + ਕੋਈ ਬਦਲਾਅ ਨਹੀਂ। + • IP literals ਨਾਲ ਮੇਲ ਖਾਂਦੇ ਸਰਵਰ ਹੁਣ ਪਬੰਦੀਸ਼ੁਦਾ ਹਨ। + • IP literals ਨਾਲ ਮੇਲ ਖਾਂਦੇ ਸਰਵਰ ਹੁਣ ਮਨਜ਼ੂਰਸ਼ੁਦਾ ਹਨ। + • %s ਨਾਲ ਮੇਲ ਖਾਂਦੇ ਸਰਵਰ ਮਨਜ਼ੂਰਸ਼ੁਦਾ ਸੂਚੀ ਵਿੱਚੋਂ ਹਟਾ ਦਿੱਤੇ ਗਏ ਹਨ। + • %s ਨਾਲ ਮੇਲ ਖਾਂਦੇ ਸਰਵਰ ਹੁਣ ਮਨਜ਼ੂਰਸ਼ੁਦਾ ਹਨ। + • %s ਨਾਲ ਮੇਲ ਖਾਂਦੇ ਸਰਵਰ ਪਬੰਦੀਸ਼ੁਦਾ ਸੂਚੀ ਵਿੱਚੋਂ ਹਟਾ ਦਿੱਤੇ ਗਏ ਸਨ। + • %s ਨਾਲ ਮੇਲ ਖਾਂਦੇ ਸਰਵਰ ਹੁਣ ਪਬੰਦੀਸ਼ੁਦਾ ਹਨ। + ਤੁਸੀਂ ਇਸ ਥਾਂ ਲਈ ਸਰਵਰ ACL ਬਦਲੇ। + %s ਨੇ ਇਸ ਥਾਂ ਲਈ ਸਰਵਰ ACL ਬਦਲੇ। + • IP literals ਨਾਲ ਮੇਲ ਖਾਂਦੇ ਸਰਵਰ ਪਬੰਦੀਸ਼ੁਦਾ ਹਨ। + • IP literals ਨਾਲ ਮੇਲ ਖਾਂਦੇ ਸਰਵਰ ਮਨਜ਼ੂਰ ਹਨ। + • %s ਨਾਲ ਮੇਲ ਖਾਂਦੇ ਸਰਵਰ ਮਨਜ਼ੂਰ ਹਨ। + • %s ਨਾਲ ਮੇਲ ਖਾਂਦੇ ਸਰਵਰ ਪਬੰਦੀਸ਼ੁਦਾ ਹਨ। + ਤੁਸੀਂ ਇਸ ਥਾਂ ਲਈ ਸਰਵਰ ACL ਸੈੱਟ ਕੀਤੇ। + %s ਨੇ ਇਸ ਥਾਂ ਲਈ ਸਰਵਰ ACL ਸੈੱਟ ਕੀਤੇ। + ਤੁਸੀਂ ਇੱਥੇ ਅੱਪਗ੍ਰੇਡ ਹੋਏ। + %s ਨੇ ਇੱਥੇ ਅੱਪਗ੍ਰੇਡ ਕੀਤਾ। + ਤੁਸੀਂ ਇਸ ਥਾਂ ਨੂੰ ਅੱਪਗ੍ਰੇਡ ਕੀਤਾ। + %s ਨੇ ਇਸ ਥਾਂ ਨੂੰ ਅੱਪਗ੍ਰੇਡ ਕੀਤਾ। + ਤੁਸੀਂ ਅੰਤ-ਤੋਂ-ਅੰਤ ਇਨਕ੍ਰਿਪਸ਼ਨ ਚਾਲੂ ਕੀਤੀ(%1$s) + %1$s ਨੇ ਅੰਤ-ਤੋਂ-ਅੰਤ ਇਨਕ੍ਰਿਪਸ਼ਨ ਚਾਲੂ ਕੀਤੀ(%2$s) + ਅਗਿਆਤ (%s)। + ਕੋਈ ਵੀ। + ਥਾਂ ਦੇ ਸਾਰੇ ਜੀ। + ਥਾਂ ਦੇ ਸਾਰੇ ਜੀ, ਜਦੋਂ ਤੋਂ ਉਹ ਜੁੜੇ ਹਨ। + ਥਾਂ ਦੇ ਸਾਰੇ ਜੀ, ਜਦੋਂ ਤੋਂ ਉਹਨਾਂ ਨੂੰ ਸੱਦਾ ਦਿੱਤਾ ਹੈ। + %s ਨੇ ਕਾਲ ਸ਼ੁਰੂ ਕਰਨ ਲਈ ਡਾਟਾ ਘੱਲਿਆ। + ਤੁਸੀਂ ਭਵਿੱਖ ਦੇ ਸੁਨੇਹੇ %1$s ਨੂੰ ਉਪਲੱਬਧ ਕਰਵਾਏ + %1$s ਨੇ ਭਵਿੱਖ ਦੇ ਸੁਨੇਹੇ %2$s ਨੂੰ ਉਪਲੱਬਧ ਕਰਵਾਏ + ਤੁਸੀਂ %1$s ਨੂੰ ਭਵਿੱਖ ਵਿੱਚ ਥਾਂ ਦਾ ਇਤਿਹਾਸ ਉਪਲੱਬਧ ਕਰਵਾਇਆ + %1$s ਨੇ ਭਵਿੱਖ ਵਿੱਚ ਥਾਂ ਦਾ ਇਤਿਹਾਸ %2$s ਨੂੰ ਉਪਲੱਬਧ ਕਰਵਾਇਆ + ਤੁਸੀਂ ਕਾਲ ਮੁਕਾਈ। + %s ਨੇ ਕਾਲ ਮੁਕਾਈ। + ਤੁਸੀਂ ਕਾਲ ਦਾ ਜਵਾਬ ਦਿੱਤਾ। + %s ਨੇ ਕਾਲ ਦਾ ਜਵਾਬ ਦਿੱਤਾ। + ਤੁਸੀਂ ਕਾਲ ਚਾਲੂ ਕਰਨ ਲਈ ਡਾਟਾ ਘੱਲਿਆ। + ਤੁਸੀਂ ਇੱਕ ਅਵਾਜ਼ ਵਾਲੀ ਕਾਲ ਸ਼ੁਰੂ ਕੀਤੀ। + %s ਨੇ ਅਵਾਜ਼ ਵਾਲੀ ਕਾਲ ਸ਼ੁਰੂ ਕੀਤੀ। + ਤੁਸੀਂ ਇੱਕ ਵੀਡੀਓ ਕਾਲ ਸ਼ੁਰੂ ਕੀਤੀ। + %s ਨੇ ਵੀਡੀਓ ਕਾਲ ਚਾਲੂ ਕੀਤੀ। + ਤੁਸੀਂ ਥਾਂ ਦਾ ਨਾਮ ਇਸ ਵਿੱਚ ਬਦਲਿਆ: %1$s + %1$s ਨੇ ਥਾਂ ਦਾ ਨਾਮ ਇਸ ਵਿੱਚ ਬਦਲਿਆ: %2$s + ਤੁਸੀਂ ਥਾਂ ਦਾ ਅਵਤਾਰ ਬਦਲਿਆ + %1$s ਨੇ ਥਾਂ ਦਾ ਅਵਤਾਰ ਬਦਲਿਆ + ਤੁਸੀਂ ਵਿਸ਼ੇ ਨੂੰ ਇਸ ਵਿੱਚ ਬਦਲਿਆ: %1$s + %1$s ਨੇ ਵਿਸ਼ੇ ਨੂੰ ਇਸ ਵਿੱਚ ਬਦਲਿਆ: %2$s + ਤੁਸੀਂ ਆਪਣਾ ਦਿਸਣ ਵਾਲਾ ਨਾਮ ਹਟਾਇਆ (ਇਹ %1$s ਸੀ) + %1$s ਨੇ ਆਪਣਾ ਦਿਸਣ ਵਾਲਾ ਨਾਮ ਹਟਾਇਆ (ਇਹ %2$s ਸੀ) + ਤੁਸੀਂ ਆਪਣਾ ਦਿਸਣ ਵਾਲਾ ਨਾਮ %1$s ਤੋਂ %2$s ਵਿੱਚ ਬਦਲਿਆ + %1$s ਨੇ ਆਪਣਾ ਦਿਸਣ ਵਾਲਾ ਨਾਮ %2$s ਤੋਂ %3$s ਵਿੱਚ ਬਦਲਿਆ + ਤੁਸੀਂ ਆਪਣਾ ਦਿਸਣ ਵਾਲਾ ਨਾਮ %1$s ਵਿੱਚ ਬਦਲਿਆ + %1$s ਨੇ ਆਪਣਾ ਦਿਸਣ ਵਾਲਾ ਨਾਮ %2$s ਵਿੱਚ ਬਦਲਿਆ + ਤੁਸੀਂ ਆਪਣਾ ਅਵਤਾਰ ਬਦਲਿਆ + %1$s ਨੇ ਆਪਣਾ ਅਵਤਾਰ ਬਦਲਿਆ + ਤੁਸੀਂ %1$s ਦਾ ਸੱਦਾ ਵਾਪਿਸ ਲਿਆ + %1$s ਨੇ %2$s ਦਾ ਸੱਦਾ ਵਾਪਿਸ ਲਿਆ + ਤੁਸੀਂ %1$s ਤੇ ਪਬੰਦੀ ਲਗਾਈ + %1$s ਨੇ %2$s ਤੇ ਪਬੰਦੀ ਲਗਾਈ + ਤੁਸੀਂ %1$s ਤੇ ਲੱਗੀ ਪਬੰਦੀ ਹਟਾਈ + %1$s ਨੇ %2$s ਤੇ ਲੱਗੀ ਪਬੰਦੀ ਹਟਾਈ + ਤੁਸੀਂ %1$s ਦੇ ਲੱਤ ਮਾਰ ਦਿੱਤੀ ਹੈ + %1$s ਨੇ %2$s ਦੇ ਲੱਤ ਮਾਰ ਦਿੱਤੀ ਹੈ + ਤੁਸੀਂ ਸੱਦਾ ਨਕਾਰ ਦਿੱਤਾ ਹੈ + %1$s ਨੇ ਸੱਦਾ ਨਕਾਰ ਦਿੱਤਾ ਹੈ + ਤੁਸੀਂ ਥਾਂ ਛੱਡ ਦਿੱਤੀ ਹੈ + %1$s ਨੇ ਥਾਂ ਛੱਡ ਦਿੱਤੀ ਹੈ + ਤੁਸੀਂ ਥਾਂ ਛੱਡ ਦਿੱਤੀ ਹੈ + %1$s ਨੇ ਥਾਂ ਛੱਡ ਦਿੱਤੀ ਹੈ + ਤੁਸੀਂ ਜੁੜੇ ਹੋ + %1$s ਜੁੜੇ ਹਨ + ਤੁਸੀਂ ਥਾਂ ਵਿੱਚ ਜੁੜੇ ਹੋ + %1$s ਥਾਂ ਵਿੱਚ ਜੁੜੇ ਹਨ + %1$s ਨੇ ਤਹਾਨੂੰ ਸੱਦਾ ਘੱਲਿਆ ਹੈ + ਤੁਸੀਂ %1$s ਨੂੰ ਸੱਦਾ ਘੱਲਿਆ ਹੈ + %1$s ਨੇ %2$s ਨੂੰ ਸੱਦਾ ਘੱਲਿਆ ਹੈ + ਤੁਸੀਂ ਚਰਚਾ ਚਾਲੂ ਕੀਤੀ ਹੈ + %1$s ਨੇ ਚਰਚਾ ਚਾਲੂ ਕੀਤੀ ਹੈ + ਤੁਸੀਂ ਇੱਕ ਥਾਂ ਬਣਾਈ ਹੈ + %1$s ਨੇ ਇੱਕ ਥਾਂ ਬਣਾਈ ਹੈ + ਤੁਹਾਡਾ ਸੱਦਾ + %s ਵੱਲੋਂ ਸੱਦਾ + ਤੁਸੀਂ ਇੱਕ ਸਟੀਕਰ ਘੱਲਿਆ ਹੈ। + %1$s ਨੇ ਇੱਕ ਸਟੀਕਰ ਘੱਲਿਆ ਹੈ। + ਤੁਸੀਂ ਇੱਕ ਤਸਵੀਰ ਘੱਲੀ ਹੈ। + %1$s ਨੇ ਇੱਕ ਤਸਵੀਰ ਘੱਲੀ ਹੈ। + \ No newline at end of file From 55c0f1fcb34b26a0abbe885746f7d7515a0a32fd Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 20 Dec 2021 15:35:09 +0100 Subject: [PATCH 118/632] Update versions to 1.3.13 --- 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 9674bdd372..10a4d225b4 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.3.12\"" + buildConfigField "String", "SDK_VERSION", "\"1.3.13\"" buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\"" resValue "string", "git_sdk_revision", "\"${gitRevision()}\"" diff --git a/vector/build.gradle b/vector/build.gradle index fce2c97f6b..483286030d 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -15,7 +15,7 @@ kapt { // Note: 2 digits max for each value ext.versionMajor = 1 ext.versionMinor = 3 -ext.versionPatch = 12 +ext.versionPatch = 13 static def getGitTimestamp() { def cmd = 'git show -s --format=%ct' From 18b38fe21ece50b10a5941405e60ed50df98cf37 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Mon, 20 Dec 2021 11:52:01 +0000 Subject: [PATCH 119/632] making the file upload file creation all happen on the IO dispatcher --- .../internal/session/content/FileUploader.kt | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/FileUploader.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/FileUploader.kt index 1b0ccbb489..b988f2253c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/FileUploader.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/FileUploader.kt @@ -109,18 +109,23 @@ internal class FileUploader @Inject constructor( filename: String?, mimeType: String?, progressListener: ProgressRequestBody.Listener? = null): ContentUploadResponse { - val inputStream = withContext(Dispatchers.IO) { - context.contentResolver.openInputStream(uri) - } ?: throw FileNotFoundException() - val workingFile = temporaryFileCreator.create() - workingFile.outputStream().use { - inputStream.copyTo(it) - } + val workingFile = context.copyUriToTempFile(uri) return uploadFile(workingFile, filename, mimeType, progressListener).also { tryOrNull { workingFile.delete() } } } + private suspend fun Context.copyUriToTempFile(uri: Uri): File { + return withContext(Dispatchers.IO) { + val inputStream = contentResolver.openInputStream(uri) ?: throw FileNotFoundException() + val workingFile = temporaryFileCreator.create() + workingFile.outputStream().use { + inputStream.copyTo(it) + } + workingFile + } + } + private suspend fun upload(uploadBody: RequestBody, filename: String?, progressListener: ProgressRequestBody.Listener?): ContentUploadResponse { From 7714445d243e04aed8935eb419fcb658e8fb3448 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Tue, 21 Dec 2021 09:23:59 +0000 Subject: [PATCH 120/632] moving the updateAvatar work to the io dispatcher - fixes some devices/OS's from attempting to run okhttp on the main thread --- .../sdk/internal/session/profile/DefaultProfileService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DefaultProfileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DefaultProfileService.kt index a19832c523..caf4158657 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DefaultProfileService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DefaultProfileService.kt @@ -68,7 +68,7 @@ internal class DefaultProfileService @Inject constructor(private val taskExecuto } override suspend fun updateAvatar(userId: String, newAvatarUri: Uri, fileName: String) { - withContext(coroutineDispatchers.main) { + withContext(coroutineDispatchers.io) { val response = fileUploader.uploadFromUri(newAvatarUri, fileName, MimeTypes.Jpeg) setAvatarUrlTask.execute(SetAvatarUrlTask.Params(userId = userId, newAvatarUrl = response.contentUri)) userStore.updateAvatar(userId, response.contentUri) From a764e02f8a788a5c0c3414194be270a032e0e115 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Tue, 21 Dec 2021 09:35:56 +0000 Subject: [PATCH 121/632] adding changelog entry --- changelog.d/4767.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/4767.bugfix diff --git a/changelog.d/4767.bugfix b/changelog.d/4767.bugfix new file mode 100644 index 0000000000..172e9d80ca --- /dev/null +++ b/changelog.d/4767.bugfix @@ -0,0 +1 @@ +Fixing unable to change change avatar in some scenarios \ No newline at end of file From db2e52bb2541fc8c85b9d416dcfad30f0d3ba2b4 Mon Sep 17 00:00:00 2001 From: waclaw66 Date: Tue, 21 Dec 2021 10:39:21 +0000 Subject: [PATCH 122/632] Translated using Weblate (Czech) Currently translated at 100.0% (2725 of 2725 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 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/res/values-cs/strings.xml b/vector/src/main/res/values-cs/strings.xml index 5ccf495780..c7eb611e1d 100644 --- a/vector/src/main/res/values-cs/strings.xml +++ b/vector/src/main/res/values-cs/strings.xml @@ -2366,7 +2366,7 @@ Nemůžete číst tuto zprávu Čekám na tuto zprávu, může to chvíli trvat Nelze dešifrovat - Vzhledem ke koncovému se může stát, že budete muset počkat, než zpráva dorazí, protože vám nebyly správně odeslány šifrovací klíče. + Jelikož se používá koncové šifrování, může se stát, že budete muset počkat, než zpráva dorazí, protože vám nebyly správně odeslány šifrovací klíče. Nemůžete číst tuto zprávu, protože odesílatel Vás blokuje Nemůžete číst tuto zprávu, protože Vaše relace není pro odesílatele důvěryhodná Nemůžete číst tuto zprávu, protože odesílatel účelově neposlal klíče From f58aab6e9d5da2e0fca2018d5e499398c39f8524 Mon Sep 17 00:00:00 2001 From: Glandos Date: Mon, 20 Dec 2021 16:14:52 +0000 Subject: [PATCH 123/632] Translated using Weblate (French) Currently translated at 100.0% (2725 of 2725 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 | 46 +++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/vector/src/main/res/values-fr/strings.xml b/vector/src/main/res/values-fr/strings.xml index a6ee72c98a..bd22736952 100644 --- a/vector/src/main/res/values-fr/strings.xml +++ b/vector/src/main/res/values-fr/strings.xml @@ -3039,4 +3039,50 @@ Êtes vous d’accord d’envoyer ces informations \? Pour découvrir des contacts existants, il vous faut envoyer les informations de contact (adresses de courriel et numéros de téléphone) à votre serveur d’identité. Les données seront condensées avant l’envoi pour respecter la vie privée. Pas maintenant + Êtes-vous sûr de vouloir retirer ce sondage \? Vous ne pourrez plus le récupérer une fois supprimé. + Supprimer le sondage + Sondage terminé + Vote exprimé + Activer les sondages + Terminer le sondage + Cela empêchera les gens de voter et affichera le résultat final du sondage. + Terminer ce sondage \? + option gagnante + Terminer le sondage + + Résultat final sur la base de %1$d vote + Résultat final sur la base de %1$d votes + + + Aucun vote exprimé + %1$d votes exprimés. Votez pour voir les résultats + + + Sur la base de %1$d vote + Sur la base de %1$d votes + + + %1$d vote + %1$d votes + + Paramètres système + Versions + Obtenir de l’aide pour utiliser Element + Aide et support + Aide + Mentions légales + Ce serveur ne fournit aucune politique. + Bibliothèques tierces + La politique de votre serveur d’identité + La politique de votre serveur d’accueil + Politique de ${app_name} + Vous pouvez désactiver ceci à tout moment dans les paramètres + Nous ne partageons pas d’information avec des tierces parties + Nous n’enregistrons ou ne profilons aucune donnée du compte + ici + Aidez nous à identifier les problèmes et améliorer Element en envoyant des rapports d’usage anonymes. Pour comprendre de quelle manière les gens utilisent Element sur plusieurs appareils, nous créeront un identifiant aléatoire commun à tous vos appareils. +\n +\nVous pouvez lire toutes les conditions %s. + Aider à améliorer Element + Activer \ No newline at end of file From fa38db7d936f64dd92d02a8e1205de4027c0bef7 Mon Sep 17 00:00:00 2001 From: LinAGKar Date: Sun, 19 Dec 2021 19:27:40 +0000 Subject: [PATCH 124/632] Translated using Weblate (Swedish) Currently translated at 99.9% (2724 of 2725 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 | 33 +++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/vector/src/main/res/values-sv/strings.xml b/vector/src/main/res/values-sv/strings.xml index f472417061..952eda7d04 100644 --- a/vector/src/main/res/values-sv/strings.xml +++ b/vector/src/main/res/values-sv/strings.xml @@ -3041,4 +3041,37 @@ \nDu kan läsa alla våra villkor %s. Hjälp att förbättra Element Aktivera + Är du säker på att du vill ta bort den här omröstningen\? Du kommer inte kunna få tillbaka den när den har tagits bort. + Ta bort omröstning + Omröstning avslutad + Röst avgiven + Aktivera omröstningar + Avsluta omröstningen + Det här kommer att stoppa personer från att rösta och visa det slutgiltiga resultatet av omröstningen. + Avsluta den här omröstningen\? + vinnande alternativ + Avsluta omröstning + + Slutgiltigt resultat baserat på %1$d röst + Slutgiltigt resultat baserat på %1$d röster + + + Baserat på %1$d röst + Baserat på %1$d röster + + + %1$d röst + %1$d röster + + Systeminställningar + Versioner + Få hjälp med att använda Element + Hjälp och support + Hjälp + Legalt + Den här servern förser ingen policy. + Tredjepartsbibliotek + Din identitetsservers policy + ${app_name}s policy + Din hemservers policy \ No newline at end of file From 47f29e63c24f2387b29219e23ef8a16317821592 Mon Sep 17 00:00:00 2001 From: Ihor Hordiichuk Date: Tue, 21 Dec 2021 15:44:03 +0000 Subject: [PATCH 125/632] Translated using Weblate (Ukrainian) Currently translated at 100.0% (2725 of 2725 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 | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/vector/src/main/res/values-uk/strings.xml b/vector/src/main/res/values-uk/strings.xml index d022c84d27..7e4a13119e 100644 --- a/vector/src/main/res/values-uk/strings.xml +++ b/vector/src/main/res/values-uk/strings.xml @@ -216,7 +216,7 @@ Не вдалося здійснити виклик Інформація про пристрій Конференц-дзвінки не підтримуються у зашифрованих кімнатах - Все одно надіслати + Усе одно надіслати або Запрошення Не в мережі @@ -472,7 +472,7 @@ Повідомлення не надіслані. %1$s або %2$s зараз? Повідомлення не надіслані через присутність невідомих сеансів. %1$s або %2$s зараз\? Повторити надсилання - скасувати все + Скасувати все Надіслати ненадіслані повідомлення знову Видалити не надіслані повідомлення Файл не знайдено @@ -604,7 +604,7 @@ Аутентифікація Пароль: Надіслати - Залоговано як + Ви увійшли як Домашній сервер Сервер ідентифікації Інтерфейс користувача @@ -1074,7 +1074,7 @@ %1$s: %2$s +%d %d+ - Все одно подзвонити + Усе одно зателефонувати Вилучити Причина Попередній перегляд посилань у чаті, у разі якщо Ваш сервер підтримує таку можливість. @@ -1281,7 +1281,7 @@ Фільтрувати бесіди… Бесіди У вас більше немає непрочитаних повідомлень - Все прочитано! + Усе прочитано! Встановти важливість сповіщень за подіями Сховати розширені Показати розширені @@ -1834,7 +1834,7 @@ Не вдалося розшифрувати резервну копію цією парольною фразою: переконайтесь, що вказано правильну парольну фразу відновлення. Не знаєте вашої відновлювальної парольної фрази\? Ви можете %s. Парольна фраза відновлення - Забули або втратили усі можливості для відновлення\? Скинути все + Забули або втратили всі можливості для відновлення\? Скинути все Використати файл Скористатись парольною фразою відновлення або ключем Скористатись відновлювальними парольною фразою або ключем @@ -2191,7 +2191,7 @@ Невідоме налаштування доступу (%s) Будь-хто може попроситися до кімнати, учасники можуть прийняти або відхилити Вітаємо в %1$s, %2$s. - Все одно приєднатися + Усе одно приєднатися Створити Простір Приєднатися до Простору Поділитися посиланням From 945749c6fb243d147d5aac346fd23f28f94e4d37 Mon Sep 17 00:00:00 2001 From: Denys Nykula Date: Mon, 20 Dec 2021 16:18:47 +0000 Subject: [PATCH 126/632] Translated using Weblate (Ukrainian) Currently translated at 100.0% (2725 of 2725 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 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/res/values-uk/strings.xml b/vector/src/main/res/values-uk/strings.xml index 7e4a13119e..47c8a687fc 100644 --- a/vector/src/main/res/values-uk/strings.xml +++ b/vector/src/main/res/values-uk/strings.xml @@ -1354,7 +1354,7 @@ Або захистіть свою резервну копію за допомогою ключа відновлення, зберігаючи її десь у безпеці. Створення резервної копії Встановіть парольну фразу - Ми збережемо зашифровану копію ваших ключів на вашому домашньому сервері. Захистіть свою резервну копію парольною фразою, щоб захистити її. + Ми збережемо зашифровану копію ваших ключів на вашому домашньому сервері. Захистіть свою резервну копію парольною фразою. \n \nДля максимальної безпеки фраза повинна відрізнятися від пароля вашого облікового запису. Захистіть свою резервну копію за допомогою парольної фрази. From e58c35f7971dab93e0c951d05a95c7131aaddd44 Mon Sep 17 00:00:00 2001 From: Glandos Date: Mon, 20 Dec 2021 16:00:54 +0000 Subject: [PATCH 127/632] Translated using Weblate (French) Currently translated at 100.0% (40 of 40 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/fr/ --- fastlane/metadata/android/fr-FR/changelogs/40103090.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/fr-FR/changelogs/40103090.txt diff --git a/fastlane/metadata/android/fr-FR/changelogs/40103090.txt b/fastlane/metadata/android/fr-FR/changelogs/40103090.txt new file mode 100644 index 0000000000..3394e5ccfa --- /dev/null +++ b/fastlane/metadata/android/fr-FR/changelogs/40103090.txt @@ -0,0 +1,2 @@ +Principaux changements pour cette version : Ajout du support pour les brouillons de messages vocaux. Beaucoup de corrections de bugs ! +Intégralité des changements : https://github.com/vector-im/element-android/releases/tag/v1.3.9 From eb8a704ee72c70c6a34ffa92c0a42f8205953c30 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Wed, 22 Dec 2021 15:57:47 +0000 Subject: [PATCH 128/632] forcing the outgoing calls to use the same audio config as the call itself - tentatively fixes the speaker being used by previous instance changes if a reset fails --- .../im/vector/app/core/services/CallRingPlayer.kt | 13 +++++++++++-- .../java/im/vector/app/core/services/CallService.kt | 2 +- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/app/core/services/CallRingPlayer.kt b/vector/src/main/java/im/vector/app/core/services/CallRingPlayer.kt index 524ff37914..6c7e02c065 100644 --- a/vector/src/main/java/im/vector/app/core/services/CallRingPlayer.kt +++ b/vector/src/main/java/im/vector/app/core/services/CallRingPlayer.kt @@ -28,6 +28,8 @@ import android.os.VibrationEffect import android.os.Vibrator import androidx.core.content.getSystemService import im.vector.app.R +import im.vector.app.features.call.audio.CallAudioManager +import im.vector.app.features.call.webrtc.WebRtcCallManager import im.vector.app.features.notifications.NotificationUtils import org.matrix.android.sdk.api.extensions.orFalse import timber.log.Timber @@ -94,7 +96,8 @@ class CallRingPlayerIncoming( } class CallRingPlayerOutgoing( - context: Context + context: Context, + private val callManager: WebRtcCallManager ) { private val applicationContext = context.applicationContext @@ -102,7 +105,7 @@ class CallRingPlayerOutgoing( private var player: MediaPlayer? = null fun start() { - applicationContext.getSystemService()?.mode = AudioManager.MODE_IN_COMMUNICATION + callManager.setAudioModeToCallType() player?.release() player = createPlayer() if (player != null) { @@ -120,6 +123,12 @@ class CallRingPlayerOutgoing( } } + private fun WebRtcCallManager.setAudioModeToCallType() { + currentCall.get()?.let { + audioManager.setMode(if (it.mxCall.isVideoCall) CallAudioManager.Mode.VIDEO_CALL else CallAudioManager.Mode.AUDIO_CALL) + } + } + fun stop() { player?.release() player = null diff --git a/vector/src/main/java/im/vector/app/core/services/CallService.kt b/vector/src/main/java/im/vector/app/core/services/CallService.kt index d194434641..d79220e7e7 100644 --- a/vector/src/main/java/im/vector/app/core/services/CallService.kt +++ b/vector/src/main/java/im/vector/app/core/services/CallService.kt @@ -84,7 +84,7 @@ class CallService : VectorService() { super.onCreate() notificationManager = NotificationManagerCompat.from(this) callRingPlayerIncoming = CallRingPlayerIncoming(applicationContext, notificationUtils) - callRingPlayerOutgoing = CallRingPlayerOutgoing(applicationContext) + callRingPlayerOutgoing = CallRingPlayerOutgoing(applicationContext, callManager) } override fun onDestroy() { From ffdd10b5bfc764c4fd7131a0c652ae415607e3a8 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Wed, 22 Dec 2021 15:59:09 +0000 Subject: [PATCH 129/632] adding changelog entry --- changelog.d/4781.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/4781.bugfix diff --git a/changelog.d/4781.bugfix b/changelog.d/4781.bugfix new file mode 100644 index 0000000000..7ac6e62448 --- /dev/null +++ b/changelog.d/4781.bugfix @@ -0,0 +1 @@ +Tentative fix for the speaker being used instead of earpiece for the outgoing call ringtone on lineage os \ No newline at end of file From 9c627fa667eca965cc5854a46a42ac42c577fa88 Mon Sep 17 00:00:00 2001 From: gradle-update-robot Date: Thu, 23 Dec 2021 00:14:10 +0000 Subject: [PATCH 130/632] Update Gradle Wrapper from 7.3.2 to 7.3.3. Signed-off-by: gradle-update-robot --- gradle/wrapper/gradle-wrapper.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index fa58fc5aae..ee6ba9a3ac 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=dd54e87b4d7aa8ff3c6afb0f7805aa121d4b70bca55b8c9b1b896eb103184582 -distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.2-all.zip +distributionSha256Sum=c9490e938b221daf0094982288e4038deed954a3f12fb54cbf270ddf4e37d879 +distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From 863b20179383b68a1c3eeaba4203da796c5ed051 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 Dec 2021 23:07:37 +0000 Subject: [PATCH 131/632] Bump libphonenumber from 8.12.39 to 8.12.40 Bumps [libphonenumber](https://github.com/google/libphonenumber) from 8.12.39 to 8.12.40. - [Release notes](https://github.com/google/libphonenumber/releases) - [Changelog](https://github.com/google/libphonenumber/blob/master/making-metadata-changes.md) - [Commits](https://github.com/google/libphonenumber/compare/v8.12.39...v8.12.40) --- updated-dependencies: - dependency-name: com.googlecode.libphonenumber:libphonenumber dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- 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 10a4d225b4..44b002697d 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -158,7 +158,7 @@ dependencies { implementation libs.apache.commonsImaging // Phone number https://github.com/google/libphonenumber - implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.39' + implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.40' testImplementation libs.tests.junit testImplementation 'org.robolectric:robolectric:4.7.3' diff --git a/vector/build.gradle b/vector/build.gradle index 483286030d..ac1158489a 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -359,7 +359,7 @@ dependencies { implementation 'com.facebook.stetho:stetho:1.6.0' // Phone number https://github.com/google/libphonenumber - implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.39' + implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.40' // FlowBinding implementation libs.github.flowBinding From 41ae67d2149e52be1ca8c570d9bc6c4466fd127b Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Fri, 24 Dec 2021 11:12:59 +0000 Subject: [PATCH 132/632] only converting encrypted events to NotifiableMessageEvent if the decrypted type is also a message - fixes the app displaying encrypted non message events as notification messages --- .../notifications/NotifiableEventResolver.kt | 107 ++++++++++-------- 1 file changed, 57 insertions(+), 50 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt b/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt index 87b31fa92a..f73e2ab0c3 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt @@ -66,12 +66,10 @@ class NotifiableEventResolver @Inject constructor( return resolveStateRoomEvent(event, session, canBeReplaced = false, isNoisy = isNoisy) } val timelineEvent = session.getRoom(roomID)?.getTimeLineEvent(eventId) ?: return null - when (event.getClearType()) { - EventType.MESSAGE -> { - return resolveMessageEvent(timelineEvent, session, canBeReplaced = false, isNoisy = isNoisy) - } + return when (event.getClearType()) { + EventType.MESSAGE, EventType.ENCRYPTED -> { - return resolveMessageEvent(timelineEvent, session, canBeReplaced = false, isNoisy = isNoisy) + resolveMessageEvent(timelineEvent, session, canBeReplaced = false, isNoisy = isNoisy) } else -> { // If the event can be displayed, display it as is @@ -79,7 +77,7 @@ class NotifiableEventResolver @Inject constructor( // TODO Better event text display val bodyPreview = event.type ?: EventType.MISSING_TYPE - return SimpleNotifiableEvent( + SimpleNotifiableEvent( session.myUserId, eventId = event.eventId!!, editedEventId = timelineEvent.getEditedEventId(), @@ -126,18 +124,18 @@ class NotifiableEventResolver @Inject constructor( } } - private suspend fun resolveMessageEvent(event: TimelineEvent, session: Session, canBeReplaced: Boolean, isNoisy: Boolean): NotifiableEvent { + private suspend fun resolveMessageEvent(event: TimelineEvent, session: Session, canBeReplaced: Boolean, isNoisy: Boolean): NotifiableEvent? { // The event only contains an eventId, and roomId (type is m.room.*) , we need to get the displayable content (names, avatar, text, etc...) val room = session.getRoom(event.root.roomId!! /*roomID cannot be null*/) - if (room == null) { + return if (room == null) { Timber.e("## Unable to resolve room for eventId [$event]") // Ok room is not known in store, but we can still display something val body = displayableEventFormatter.format(event, isDm = false, appendAuthor = false) val roomName = stringProvider.getString(R.string.notification_unknown_room_name) val senderDisplayName = event.senderInfo.disambiguatedDisplayName - return NotifiableMessageEvent( + NotifiableMessageEvent( eventId = event.root.eventId!!, editedEventId = event.getEditedEventId(), canBeReplaced = canBeReplaced, @@ -152,51 +150,60 @@ class NotifiableEventResolver @Inject constructor( matrixID = session.myUserId ) } else { - if (event.root.isEncrypted() && event.root.mxDecryptionResult == null) { - // TODO use a global event decryptor? attache to session and that listen to new sessionId? - // for now decrypt sync - try { - val result = session.cryptoService().decryptEvent(event.root, event.root.roomId + UUID.randomUUID().toString()) - event.root.mxDecryptionResult = OlmDecryptionResult( - payload = result.clearEvent, - senderKey = result.senderCurve25519Key, - keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) }, - forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain + event.attemptToDecryptIfNeeded(session) + // only convert encrypted messages to NotifiableMessageEvents + when (event.root.getClearType()) { + EventType.MESSAGE -> { + val body = displayableEventFormatter.format(event, isDm = room.roomSummary()?.isDirect.orFalse(), appendAuthor = false).toString() + val roomName = room.roomSummary()?.displayName ?: "" + val senderDisplayName = event.senderInfo.disambiguatedDisplayName + + NotifiableMessageEvent( + eventId = event.root.eventId!!, + editedEventId = event.getEditedEventId(), + canBeReplaced = canBeReplaced, + timestamp = event.root.originServerTs ?: 0, + noisy = isNoisy, + senderName = senderDisplayName, + senderId = event.root.senderId, + body = body, + imageUri = event.fetchImageIfPresent(session), + roomId = event.root.roomId!!, + roomName = roomName, + roomIsDirect = room.roomSummary()?.isDirect ?: false, + roomAvatarPath = session.contentUrlResolver() + .resolveThumbnail(room.roomSummary()?.avatarUrl, + 250, + 250, + ContentUrlResolver.ThumbnailMethod.SCALE), + senderAvatarPath = session.contentUrlResolver() + .resolveThumbnail(event.senderInfo.avatarUrl, + 250, + 250, + ContentUrlResolver.ThumbnailMethod.SCALE), + matrixID = session.myUserId, + soundName = null ) - } catch (e: MXCryptoError) { } + else -> null } + } + } - val body = displayableEventFormatter.format(event, isDm = room.roomSummary()?.isDirect.orFalse(), appendAuthor = false).toString() - val roomName = room.roomSummary()?.displayName ?: "" - val senderDisplayName = event.senderInfo.disambiguatedDisplayName - - return NotifiableMessageEvent( - eventId = event.root.eventId!!, - editedEventId = event.getEditedEventId(), - canBeReplaced = canBeReplaced, - timestamp = event.root.originServerTs ?: 0, - noisy = isNoisy, - senderName = senderDisplayName, - senderId = event.root.senderId, - body = body, - imageUri = event.fetchImageIfPresent(session), - roomId = event.root.roomId!!, - roomName = roomName, - roomIsDirect = room.roomSummary()?.isDirect ?: false, - roomAvatarPath = session.contentUrlResolver() - .resolveThumbnail(room.roomSummary()?.avatarUrl, - 250, - 250, - ContentUrlResolver.ThumbnailMethod.SCALE), - senderAvatarPath = session.contentUrlResolver() - .resolveThumbnail(event.senderInfo.avatarUrl, - 250, - 250, - ContentUrlResolver.ThumbnailMethod.SCALE), - matrixID = session.myUserId, - soundName = null - ) + private fun TimelineEvent.attemptToDecryptIfNeeded(session: Session) { + if (root.isEncrypted() && root.mxDecryptionResult == null) { + // TODO use a global event decryptor? attache to session and that listen to new sessionId? + // for now decrypt sync + try { + val result = session.cryptoService().decryptEvent(root, root.roomId + UUID.randomUUID().toString()) + root.mxDecryptionResult = OlmDecryptionResult( + payload = result.clearEvent, + senderKey = result.senderCurve25519Key, + keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) }, + forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain + ) + } catch (e: MXCryptoError) { + } } } From a15fefc71878cb8aeb3699c3238088edb7d61129 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Fri, 24 Dec 2021 11:15:41 +0000 Subject: [PATCH 133/632] adding changelog entry --- changelog.d/4804.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/4804.bugfix diff --git a/changelog.d/4804.bugfix b/changelog.d/4804.bugfix new file mode 100644 index 0000000000..8f845662ab --- /dev/null +++ b/changelog.d/4804.bugfix @@ -0,0 +1 @@ +Fixing encrypted non message events showing up as notification messages (eg when a participant joins, mutes or leaves a voice call) \ No newline at end of file From 302b24a6b707abb5e35dc9c9c7e184a2f6abba92 Mon Sep 17 00:00:00 2001 From: egrimstad Date: Thu, 23 Dec 2021 11:37:40 +0000 Subject: [PATCH 134/632] =?UTF-8?q?Translated=20using=20Weblate=20(Norwegi?= =?UTF-8?q?an=20Bokm=C3=A5l)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 59.2% (1614 of 2725 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/nb_NO/ --- vector/src/main/res/values-nb-rNO/strings.xml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/vector/src/main/res/values-nb-rNO/strings.xml b/vector/src/main/res/values-nb-rNO/strings.xml index 454e946f5a..95dda903be 100644 --- a/vector/src/main/res/values-nb-rNO/strings.xml +++ b/vector/src/main/res/values-nb-rNO/strings.xml @@ -1654,7 +1654,7 @@ Du ba om en VoIP-konferanse %1$s ba om en VoIP-konferanse Du oppgraderte dette rommet. - %s oppgradert dette rommet. + %s oppgraderte dette rommet. Du har slått på end-to-end-kryptering (%1$s) %1$s slo på ende-til-ende-kryptering (%2$s) Du gjorde fremtidig romhistorie synlig for %1$s @@ -1756,4 +1756,6 @@ Pause talemelding Tar opp talemelding Alle i %s vil kunne finne og bli med i dette rommet - du trenger ikke å invitere alle manuelt. Du kan når som helst endre dette i rominnstillingene. + Du opphevet utestengingen av %1$s + %1$s opphevet utestengingen av %2$s \ No newline at end of file From d41ad89c9e6eda62350cf1f9037da5326ff23b9f Mon Sep 17 00:00:00 2001 From: Martin Berg Alstad Date: Thu, 23 Dec 2021 11:34:37 +0000 Subject: [PATCH 135/632] =?UTF-8?q?Translated=20using=20Weblate=20(Norwegi?= =?UTF-8?q?an=20Bokm=C3=A5l)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 59.2% (1614 of 2725 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/nb_NO/ --- vector/src/main/res/values-nb-rNO/strings.xml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/vector/src/main/res/values-nb-rNO/strings.xml b/vector/src/main/res/values-nb-rNO/strings.xml index 95dda903be..712d99b123 100644 --- a/vector/src/main/res/values-nb-rNO/strings.xml +++ b/vector/src/main/res/values-nb-rNO/strings.xml @@ -1758,4 +1758,15 @@ Alle i %s vil kunne finne og bli med i dette rommet - du trenger ikke å invitere alle manuelt. Du kan når som helst endre dette i rominnstillingene. Du opphevet utestengingen av %1$s %1$s opphevet utestengingen av %2$s + hvem som helst. + Du gjorde framtidige meldinger synlige til %1$s + %1$s gjorde framtidige meldinger synlige til %2$s + Du endret romnavnet til: %1$s + %1$s fjernet visningsnavnet sitt (det var %2$s) + Du endret visningsnavnet ditt fra %1$s til %2$s + %1$s endret visningsnavnet sitt fra %2$s til %3$s + Du endret visningsnavnet ditt til %1$s + %1$s endret visningsnavnet sitt til %2$s + %1$s utestengte %2$s + %ss invitasjon \ No newline at end of file From 6d7d80e7c1f5786ca92a6f2e455ada3dbc8bcf55 Mon Sep 17 00:00:00 2001 From: lvre <7uu3qrbvm@relay.firefox.com> Date: Wed, 22 Dec 2021 21:03:06 +0000 Subject: [PATCH 136/632] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (2725 of 2725 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 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/res/values-pt-rBR/strings.xml b/vector/src/main/res/values-pt-rBR/strings.xml index b0311a332d..3d8295d70c 100644 --- a/vector/src/main/res/values-pt-rBR/strings.xml +++ b/vector/src/main/res/values-pt-rBR/strings.xml @@ -665,7 +665,7 @@ Timeout de requisição de sinc Delay entre casa Sinc Versão - versão de olm + Versão de olm Termos & condições Notas de terceiros Copyright From 2e56d3811eb7af1147deb47dd05aa3cc11fd6b95 Mon Sep 17 00:00:00 2001 From: Dinh Quang Tuyen Date: Fri, 24 Dec 2021 09:25:11 +0000 Subject: [PATCH 137/632] Translated using Weblate (Vietnamese) Currently translated at 100.0% (2725 of 2725 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/vi/ --- vector/src/main/res/values-vi/strings.xml | 1370 ++++++++++++++++++++- 1 file changed, 1367 insertions(+), 3 deletions(-) diff --git a/vector/src/main/res/values-vi/strings.xml b/vector/src/main/res/values-vi/strings.xml index 498ee9fbbb..87715aeb44 100644 --- a/vector/src/main/res/values-vi/strings.xml +++ b/vector/src/main/res/values-vi/strings.xml @@ -291,7 +291,7 @@ Chuyển Kết nối Một số tin nhắn chưa được gửi - Phòng này có bản nháp chưa được gửi + có bản nháp chưa được gửi Xoá ảnh đại diện Thay đổi ảnh đại diện Hình ảnh @@ -753,13 +753,14 @@ Đồng bộ ban đầu: \nĐang nhập dữ liệu tài khoản Đồng bộ ban đầu: -\nĐang nhập các cộng đồng +\nĐang nhập các Community Đồng bộ ban đầu: \nĐang nhập các phòng đã rời khỏi Đồng bộ ban đầu: \nĐang nhập các phòng đã mời vào Đồng bộ ban đầu: -\nĐang nhập các phòng đã tham gia +\nTải hội thoại của bạn +\nNếu bạn đã tham gia nhiều phòng, điều này có thể mất một thời gian. Đồng bộ ban đầu: \nĐang nhập các phòng Đồng bộ ban đầu: @@ -1645,4 +1646,1367 @@ Định dạng không phù hợp \'%s\' không phải là một ID cộng đồng hợp lệ ID cộng đồng không hợp lệ + + %1$d cuộc gọi bị tạm dừng + + + %d mục + + + Mã sai, còn lại %d lần thử + + + Các lời mời đã gửi tới %1$s và %2$d người nữa + + + Hiển thị %d thiết bị bạn có thể xác minh ngay bây giờ + + + %d phiếu bầu - Kết quả cuối cùng + + + %d phiếu bầu + + + %d phiên đang hoạt động + + + %1$d người + + + Quá nhiều yêu cầu vừa được gửi. Bạn có thể thử lại trong %1$d giây… + + + Đang sao lưu %d khóa… + + + %d khóa mới vừa được thêm vào phiên này. + + + Đã khôi phục bản sao lưu với % d chìa khóa. + + + %d phòng + + + %d thành viên + + + %d widget hoạt động + + + %d thông báo + + + %1$s: %2$d tin nhắn + + + %d lời mời + + + %d phòng + + + %d tin nhắn được thông báo chưa đọc + + + %d tin nhắn được thông báo chưa đọc + + Bạn có chắc chắn muốn xóa cuộc thăm dò này không\? Bạn sẽ không thể phục hồi nó một khi bị xóa. + Xóa cuộc thăm dò ý kiến + Cuộc thăm dò kết thúc + Bỏ phiếu + Bật Cuộc thăm dò ý kiến + Kết thúc cuộc thăm dò ý kiến + Điều này sẽ ngăn mọi người có thể bỏ phiếu và sẽ hiển thị kết quả cuối cùng của cuộc thăm dò. + Kết thúc cuộc thăm dò này\? + tùy chọn người chiến thắng + Kết thúc cuộc thăm dò ý kiến + Câu hỏi không thể trống + TẠO CUỘC THĂM DÒ Ý KIẾN + THÊM TÙY CHỌN + Tùy chọn %1$d + Tạo tùy chọn + Câu hỏi hoặc chủ đề + Câu hỏi hoặc chủ đề thăm dò ý kiến + Tạo Cuộc thăm dò ý kiến + %s trong Cài đặt để nhận lời mời trực tiếp trong Element. + Liên kết email này với tài khoản của bạn + Lời mời này đến Space này đã được gửi đến %s không được liên kết với tài khoản của bạn + Lời mời này đến phòng này đã được gửi đến %s không được liên kết với tài khoản của bạn + Để giúp các thành viên Space tìm và tham gia một phòng riêng, hãy vào cài đặt của căn phòng đó bằng cách nhấn vào hình đại diện. + Giúp các thành viên Space tìm phòng riêng + Điều này giúp các phòng dễ dàng giữ riêng tư cho một Space, đồng thời cho phép mọi người trong Space tìm và tham gia cùng họ. Tất cả các phòng mới trong một Space sẽ có tùy chọn này có sẵn. + Giúp mọi người trong Space tự tìm và tham gia phòng riêng, không cần phải tự mời mọi người. + Mới: Cho phép mọi người trong Space tìm và tham gia phòng riêng + Xin lưu ý nâng cấp sẽ tạo ra một phiên bản mới của căn phòng. Tất cả các tin nhắn hiện tại sẽ ở trong phòng lưu trữ này. + Bất cứ ai trong Space cha mẹ sẽ có thể tìm và tham gia căn phòng này - không cần phải mời mọi người theo cách thủ công. Bạn sẽ có thể thay đổi điều này trong cài đặt phòng bất cứ lúc nào. + Bất kỳ ai trong %s sẽ có thể tìm và tham gia phòng này - không cần phải mời mọi người theo cách thủ công. Bạn sẽ có thể thay đổi điều này trong cài đặt phòng bất cứ lúc nào. + Tin nhắn Thoại (%1$s) + Không thể trả lời hoặc chỉnh sửa trong khi tin nhắn thoại đang hoạt động + Không thể ghi âm một tin nhắn thoại + Không thể phát tin nhắn thoại này + Bật tin nhắn thoại + Nhấn vào bản ghi âm của bạn để dừng hoặc nghe + %1$d còn lại + Giữ để ghi âm, bỏ ra để gửi + Xóa ghi âm + Đang ghi âm tin nhắn thoại + Dừng ghi âm + Tạm dừng Tin nhắn Thoại + Phát Tin nhắn Thoại + Khóa Tin nhắn Thoại + Vuốt để hủy + Ghi âm tin nhắn thoại + Cuộc gọi nhóm bắt đầu + Xin lỗi, lỗi đã xảy ra trong khi cố gắng gia nhập: %s + Nâng cấp lên phiên bản phòng được đề xuất + Phòng này đang chạy phiên bản phòng %s, mà homeerver này đã đánh dấu là không ổn định. + Cho phép bất kỳ ai trong %s tìm và truy nhập. Bạn cũng có thể chọn các Space khác. + Bạn cần sự cho phép để nâng cấp một phòng + Tự động cập nhật Space cha mẹ + Tự động mời người dùng + Bạn sẽ nâng cấp phòng này từ %1$s lên %2$s + Nâng cấp phòng là một hành động nâng cao và thường được khuyến khích khi phòng không ổn định do lỗi, thiếu tính năng hoặc lỗ hổng bảo mật. +\nĐiều này thường chỉ ảnh hưởng đến cách phòng được xử lý trên máy chủ. + Nâng cấp phòng riêng tư + Nâng cấp phòng công cộng + Yêu cầu nâng cấp + Nâng cấp + Hãy kiên nhẫn, có thể mất một thời gian. + Tham gia phòng thay thế + Hiện tại mọi người có thể không thể tham gia bất kỳ phòng riêng nào bạn làm. +\n +\nChúng tôi sẽ cải thiện điều này như một phần của bản beta, nhưng chỉ muốn cho bạn biết. + Space đồng đội chưa hoàn toàn sẵn sàng nhưng bạn vẫn có thể thử chúng + Phòng không tên + Một số phòng có thể bị ẩn vì chúng riêng tư và bạn cần một lời mời. + Một số phòng có thể bị ẩn vì chúng riêng tư và bạn cần một lời mời. +\nBạn không có quyền thêm phòng. + Space này không có phòng + Vui lòng liên hệ với quản trị viên homeerver của bạn để biết thêm thông tin + Có vẻ như homeerver của bạn chưa hỗ trợ Spaces + Cảm thấy thử nghiệm\? +\nBạn có thể thêm các Space hiện có vào một Space. + Tất cả các phòng bạn đang ở sẽ được hiển thị tại Home. + Hiển thị tất cả các phòng trong Home + Quản lý phòng và Space + Đánh dấu như không đề xuất + Đánh dấu như đề xuất + Đề nghị + Công khai Space này + Quản lý phòng + Tìm kiếm một người không có trong %s\? + %s mời bạn + Cảnh báo yêu cầu hỗ trợ máy chủ và phiên bản phòng thử nghiệm + Space thử nghiệm - Phòng hạn chế. + Bạn được mời + Space là một cách mới để nhóm phòng và con người. + Chào mừng đến Space! + Thêm phòng + Thêm một Space vào bất kỳ Space nào bạn quản lý. + Thêm Space hiện có + Thêm các phòng hiện có + Thêm các phòng và Space hiện có + Chọn những thứ để rời khỏi + Để lại các phòng và Space cụ thể… + Không rời bất kỳ phòng và Space nào + Bạn sẽ để tất cả các phòng và Space trong %s. + Rời khỏi tất cả phòng và Space + Bạn là quản trị viên duy nhất của không gian này. Rời khỏi nó sẽ có nghĩa là không ai có quyền kiểm soát nó. + Bạn sẽ không thể tham gia lại trừ khi bạn được mời lại. + Bạn là người duy nhất ở đây. Nếu bạn rời đi, sẽ không ai có thể tham gia trong tương lai, kể cả bạn + Bạn có chắc chắn muốn rời khỏi %s không\? + Rời khỏi Space + Thêm phòng + Khám phá phòng + Chào mừng bạn đến với %1$s, %2$s. + Bạn vẫn chưa ở trong phòng. Dưới đây là một số phòng được đề xuất, nhưng bạn có thể thấy nhiều hơn với đáy nút màu xanh lá cây bên phải. + Khám phá (%s) + Hoàn tất cài đặt + Mời qua email, tìm liên hệ và hơn thế nữa… + Hoàn tất việc cài đặt khám phá. + Hiện tại bạn không sử dụng máy chủ xác thực. Để mời đồng đội và có thể khám phá bởi họ, hãy cấu hình một bên dưới. + Tham gia Space + Tạo Space + Bỏ qua ngay bây giờ + Gia nhập Space của tôi %1$s %2$s + Mời theo tên người dùng hoặc thư + Mời theo tên người dùng + Mời qua email + Chỉ có anh lúc này thôi. %s sẽ còn tốt hơn với những người khác. + Mời đến %s + Mời mọi người + Mời mọi người vào Space của bạn + Sự mô tả + Đang tạo Space… + Ngẫu nhiên + Tổng quát + Hãy tạo ra một căn phòng cho mỗi người trong số họ. Bạn cũng có thể thêm nhiều hơn sau, bao gồm cả những cái đã có sẵn. + Bạn đang làm gì\? + Đảm bảo đúng người có quyền truy cập vào công ty %s. Bạn có thể mời thêm sau. + Đồng đội của bạn là ai\? + Chúng tôi sẽ tạo ra các phòng cho họ. Bạn cũng có thể thêm nhiều hơn sau. + Một số cuộc thảo luận bạn muốn có trong %s là gì\? + Đặt tên để tiếp tục. + Thêm một số chi tiết để giúp mọi người xác định nó. Bạn có thể thay đổi chúng bất cứ lúc nào. + Thêm một số chi tiết để giúp nó nổi bật. Bạn có thể thay đổi chúng bất cứ lúc nào. + Tạo một Space + Chỉ mời, tốt nhất cho bản thân hoặc các đội + Riêng tư + Mở cửa cho bất cứ ai, tốt nhất cho Community + Công cộng + Một Space riêng tư cho bạn và đồng đội của bạn + Tôi và các đồng đội + Một Space riêng tư để sắp xếp các phòng của bạn + Chỉ tôi + Đảm bảo đúng người có quyền truy nhập vào %s. Bạn có thể thay đổi điều này sau. + Bạn làm việc với ai\? + Để tham gia một Space hiện có, bạn cần một lời mời. + Bạn có thể thay đổi điều này sau + Bạn muốn tạo ra loại Space nào\? + Space là một cách mới để nhóm phòng và con người + Space riêng tư của bạn + Space công cộng của bạn + Thêm Space + Space riêng tư + Space công cộng + Tin nhắn gửi thất bại + Nâng cấp phòng lên phiên bản mới + Để lại phòng với id đã cho (hoặc phòng hiện tại nếu null) + Gia nhập Space với id đã cho + Thêm vào Space đã cho + Tạo một Space + Nội dung sự kiện + Sự kiện trạng thái được gửi! + Sự kiện được gửi! + Sự kiện bị hỏng + Loại tin nhắn bỏ lỡ + Nội dung sự kiện + Chìa khóa trạng thái + Loại + Gửi Sự kiện Trạng thái Tùy chỉnh + Sửa Nội dung + Sự kiện trạng thái + Gửi sự kiện trạng thái + Không có sẵn + Ngoại tuyến + Trực tuyến + Space công cộng + Phòng công cộng + Không kiểm tra + Kiểm tra + Nhập chìa khó từ tệp + Mở widget + Chụp màn hình + Vuốt để kết thúc cuộc gọi + Người dùng không biết + Chuyển sang %1$s + Tư vấn với %1$s + Tư vấn trước + %1$s nhấp nhẹ để trả về + Cuộc gọi đang hoạt động (%1$s) · + Cuộc gọi hiện hoạt động (%1$s) + Liên kết Matrix + Loại bỏ các thay đổi + Có những thay đổi chưa được lưu. Loại bỏ các thay đổi\? + Phòng vẫn chưa được tạo ra. Hủy bỏ việc tạo phòng\? + Liên kết bị hỏng + Mã QR không được quét! + Mã QR không hợp lệ (URI không hợp lệ)! + Không thể tự gửi tin nhắn trực tiếp! + Chia sẻ theo văn bản + Không thể tìm thấy phòng này. Hãy chắc chắn rằng nó tồn tại. + Không thể mở phòng nơi bạn bị cấm. + Xác nhận mã PIN để vô hiệu hóa MÃ PIN + Thay đổi mã PIN hiện tại của bạn + Thay đổi mã PIN + Mã PIN được yêu cầu mỗi khi bạn mở ${app_name}. + Mã PIN được yêu cầu sau 2 phút không sử dụng ${app_name}. + Yêu cầu mã PIN sau 2 phút + Chỉ hiển thị số lượng tin nhắn chưa đọc trong một thông báo đơn giản. + Hiển thị chi tiết như tên phòng và nội dung tin nhắn. + Hiện nội dung trong thông báo + Mã PIN là cách duy nhất để mở khóa ${app_name}. + Bật sinh trắc học cụ thể của thiết bị, như dấu vân tay và nhận dạng khuôn mặt. + Bật sinh trắc học + Nếu bạn muốn đặt lại mã PIN của mình, hãy nhấp vào quên mã PIN để đăng nhập và đặt lại. + Bật MÃ PIN + Cấu hình bảo vệ + Bảo vệ quyền truy cập bằng mã PIN và sinh trắc học. + Bảo vệ quyền truy cập + Để đặt lại mã PIN của mình, bạn sẽ cần đăng nhập lại và tạo mã PIN mới. + Mã PIN mới + Đặt lại mã PIN + Quên mã PIN\? + Nhập mã PIN của bạn + Xác thực mã PIN thất bại, vui lòng nhấp vào mã PIN mới. + Xác nhận mã PIN + Chọn mã PIN để bảo mật + Quá nhiều lỗi, bạn đã bị đăng xuất + Cảnh báo! Nỗ lực cuối cùng còn lại trước khi bị đăng xuất! + Xem lại cài đặt của bạn để bật thông báo Push + Thông báo Push bị vô hiệu hóa + Hủy cấm người dùng thất bại + Bị cấm bởi %1$s + Thu hồi lời mời đến %1$s\? + Thu hồi lời mời + Tìm kiếm danh bạ trên Matrix + Sổ danh bạ + Sổ danh bạ của bạn trống + Lấy lại danh bạ của bạn… + Tìm kiếm trong danh bạ của tôi + Danh bạ điện thoại + Danh bạ điện thoại của bạn trống rỗng + Thêm từ danh bạ điện thoại của tôi + Lưu khóa khôi phục trong + Bạn không thể truy nhập tin nhắn này + Đặt avatar + Bạn đã thay đổi cài đặt phòng thành công + Chủ đề + Tên phòng + Lưu trữ Khóa bảo mật của bạn ở nơi an toàn, như trình quản lý mật khẩu hoặc két sắt. + Lưu Khóa Bảo mật của bạn + Nhập lại Cụm từ Bảo mật của bạn để xác nhận. + Cụm từ Bảo mật + Nhập cụm từ bảo mật chỉ bạn biết, được sử dụng để bảo mật bí mật trên máy chủ của bạn. + Đặt Cụm từ Bảo mật + Lưu trữ Khóa bảo mật của bạn ở nơi an toàn, như trình quản lý mật khẩu hoặc két sắt. + Lưu Khóa Bảo mật của bạn + Nhập một cụm từ bí mật chỉ bạn biết và tạo khóa để sao lưu. + Sử dụng Cụm Bảo mật + Tạo khóa bảo mật để lưu trữ ở đâu đó an toàn như trình quản lý mật khẩu hoặc két sắt. + Sử dụng Khóa Bảo mật + Cài đặt + Bảo vệ chống mất quyền truy cập vào các tin nhắn và dữ liệu được mã hóa bằng cách sao lưu các khóa mã hóa trên máy chủ của bạn. + Bảo mật sao lưu + Thiết lập Sao lưu Bảo mật + Khởi động máy ảnh + Dừng máy ảnh + Bật tiếng micrô + Tắt tiếng micrô + Mở cuộc trò chuyện + Quyền + Đặt quyền + Xác nhận + Nhập URL của máy chủ xác thực + Ngoài ra, bạn có thể nhập bất kỳ URL máy chủ xác thực nào khác + Dùng %1$s + Homeerver của bạn (%1$s) đề xuất sử dụng %2$s cho máy chủ xác thực của bạn + Sự đồng ý của người dùng chưa được cung cấp. + Không có mối liên hệ hiện tại với mã định danh này. + Sự kết hợp đã thất bại. + Đối với quyền riêng tư của bạn, ${app_name} chỉ hỗ trợ gửi email và số điện thoại của người dùng băm. + Trước tiên, vui lòng chấp nhận các điều khoản của máy chủ nhận dạng trong cài đặt. + Trước tiên, vui lòng cấu hình máy chủ nhận dạng. + Hoạt động này là không thể. Homeerver đã lỗi thời. + Máy chủ nhận dạng này đã lỗi thời. ${app_name} chỉ hỗ trợ API V2. + Ngắt kết nối khỏi máy chủ nhận dạng %s\? + Mở các điều khoản của %s + Tải các ngôn ngữ có sẵn… + Các ngôn ngữ có sẵn khác + Ngôn ngữ hiện tại + Chia sẻ mã này với mọi người để họ có thể quét nó để thêm bạn và bắt đầu trò chuyện. + Mã của tôi + Chia sẻ mã của tôi + Quét mã QR + Chúng tôi không thể mời người dùng. Vui lòng kiểm tra người dùng bạn muốn mời và thử lại. + Nó không phải là mã QR Matrix hợp lệ + Lời mời được gửi đến %1$s và %2$s + Lời mời được gửi đến %1$s + 🔐️ Tham gia với tôi trên ${app_name} + Hey, nói chuyện với tôi trên ${app_name}: %s + Mời bạn bè + Mời Người dùng + Mời người dùng… + MỜI + Thêm người + Thêm thành viên + Chúng tôi không thể tạo ra DM của bạn. Vui lòng kiểm tra người dùng bạn muốn mời và thử lại. + Nối kết %1$s đang đưa bạn đến một trang khác: %2$s. +\n +\nBạn có chắc chắn muốn tiếp tục\? + Kiểm tra lại nối kết này + Vui lòng chọn mật khẩu. + Vui lòng chọn tên người dùng. + Thất bại trong việc thiết lập Xác thực chéo + Đánh dấu là Đáng tin cậy + Xác nhận danh tính của bạn bằng cách xác minh đăng nhập này, cấp cho nó quyền truy cập vào các tin nhắn được mã hóa. + Xác nhận danh tính của bạn bằng cách xác minh đăng nhập này từ một trong các phiên khác của bạn, cấp cho nó quyền truy cập vào các tin nhắn được mã hóa. + Xác minh tương tác bằng Emoji + Xác minh đăng nhập + Xác minh thủ công bằng Văn bản + Xác minh thông tin đăng nhập mới truy cập vào tài khoản của bạn: %1$s + Xác minh tất cả các phiên của bạn để đảm bảo tài khoản và tin nhắn của bạn được an toàn + Xem lại nơi bạn đăng nhập + Được mã hóa bởi một thiết bị chưa được xác minh + Không được mã hóa + gửi tuyết rơi ❄️ + gửi hoa giấy 🎉 + Gửi thông điệp đã cho với tuyết rơi + Gửi tin nhắn đã cho với hoa giấy + Bạn sẽ khởi động lại mà không có lịch sử, không có tin nhắn, thiết bị đáng tin cậy hoặc người dùng đáng tin cậy + Nếu bạn đặt lại mọi thứ + Chỉ làm điều này nếu bạn không có thiết bị nào khác mà bạn có thể xác minh thiết bị này. + Đặt lại mọi thứ + Quên hoặc mất tất cả các tùy chọn phục hồi\? Đặt lại mọi thứ + Không truy nhập được dung lượng lưu trữ an toàn + Sao lưu không thể giải mã được với Khóa Khôi phục này: vui lòng xác minh rằng bạn đã nhập Khóa Khôi phục chính xác. + Chọn Khóa Khôi phục của bạn hoặc nhập nó theo cách thủ công bằng cách nhập hoặc dán từ bảng tạm của bạn + Sử dụng Khóa Khôi phục + Dùng %1$s của bạn hoặc dùng %2$s của bạn để tiếp tục. + Chỉ được hỗ trợ trong các phòng được mã hóa + Buộc nhóm phiên hướng ra hiện tại trong một căn phòng được mã hóa phải bị loại bỏ + Sử dụng ${app_name mới nhất} trên các thiết bị khác của bạn: + hoặc một máy khách Matrix có khả năng xác thực chéo khác + ${app_name} iOS +\n${app_name} Android + ${app_name} Web +\n${app_name} Desktop + Sử dụng ${app_name} mới nhất trên các thiết bị khác của bạn, Web ${app_name}, Máy tính để bàn ${app_name}, ${app_name} iOS, ${app_name} cho Android hoặc một máy khách Matrix có khả năng xác thực chéo khác + Đặt mật khẩu tài khoản mới… + Không thể lưu tệp Media + Không thể thêm tệp Media vào Bộ sưu tập + Tệp Media được thêm vào Bộ sưu tập + Bật thiết đặt này thêm FLAG_SECURE cho tất cả các Hoạt động. Khởi động lại ứng dụng để thay đổi có hiệu lực. + Ngăn ảnh chụp màn hình của ứng dụng + Khóa khôi phục Sao lưu Chính + Không biết cụm mật khẩu Sao lưu Khóa của bạn, bạn có thể %s. + sử dụng khóa khôi phục Sao lưu Khóa của bạn + Nhập chìa khóa Cụm mật khẩu Sao lưu của bạn để tiếp tục. + %1$s (%2$s) + Lưu trữ bí mật khóa sao lưu trong SSSS + Tạo khóa SSSS từ khóa phục hồi + Tạo khóa SSSS từ cụm mật khẩu (%s) + Tạo khóa SSSS từ cụm mật khẩu + Nhận Khóa cong + Kiểm tra Khóa sao lưu (%s) + Kiểm tra Khóa sao lưu + Vui lòng nhập khóa khôi phục + Nó không phải là một khóa phục hồi hợp lệ + Cụm mật khẩu phục hồi + Nhập %s + Sử dụng Tệp + Nhập %s của bạn để tiếp tục + Xác minh bản thân và người khác để giữ an toàn cho cuộc trò chuyện của bạn + Bật xác thực chéo + Nâng cấp mã hóa có sẵn + Tin nhắn… + Tài khoản này đã bị vô hiệu hóa. + Tên người dùng và/hoặc mật khẩu không chính xác. Mật khẩu đã nhập bắt đầu hoặc kết thúc bằng không gian, vui lòng kiểm tra. + Gửi tin nhắn dưới dạng văn bản thuần túy, mà không giải thích nó dưới dạng đánh dấu + Đặt tầm quan trọng thông báo theo sự kiện + Khắc phục + Cấu hình thông báo + Thất bại trong việc nhập khóa + Đang chờ %s… + Gần xong! Đang chờ xác nhận… + + Kết quả cuối cùng dựa trên %1$d phiếu bầu + + + Đã bỏ %1$d phiếu bầu. Bỏ phiếu để xem kết quả + + + Dựa trên %1$d phiếu bầu + + + %1$d phiếu bầu + + + Ít nhất %1$s tùy chọn là cần thiết + + + %d người bạn biết đã tham gia + + + %1$d cuộc gọi đang hoạt động · + + + 1 cuộc gọi đang hoạt động (%1$s) · %2$d cuộc gọi bị tạm dừng + + Gần xong! Thiết bị khác có hiển thị dấu tick không\? + "Chủ đề: " + Thêm chủ đề + %s để cho mọi người biết căn phòng này là gì. + Đây là phần đầu của lịch sử thư trực tiếp của bạn với %s. + Đây là khởi đầu của cuộc trò chuyện này. + Đây là sự khởi đầu của %s. + Bạn đã tham gia. + %s đã tham gia. + Bạn đã tạo và cấu hình phòng. + %s đã tạo và cấu hình phòng. + Mã hóa được sử dụng bởi phòng này không được hỗ trợ + Mã hóa không được bật + Tin nhắn trong phòng này được mã hóa đầu cuối + Tin nhắn trong phòng này được mã hóa đầu cuối. Tìm hiểu thêm và xác minh người dùng trong hồ sơ của họ. + Mã hóa được bật + Nếu bạn hủy ngay bây giờ, bạn có thể mất tin nhắn và dữ liệu được mã hóa nếu bạn mất quyền truy cập vào thông tin đăng nhập của mình. +\n +\nBạn cũng có thể thiết lập Sao lưu Bảo mật và quản lý khóa của mình trong Cài đặt. + Thiết lập Cụm mật khẩu Phục hồi cho phép bạn bảo mật và mở khóa tin nhắn và tin cậy được mã hóa. + Thiết lập Cụm mật khẩu Phục hồi cho phép bạn bảo mật và mở khóa tin nhắn và tin cậy được mã hóa. +\n +\nNếu bạn không muốn đặt Mật khẩu Tin nhắn, hãy tạo Khóa Tin nhắn thay thế. + Bạn không thể làm điều đó từ điện thoại di động + Sao chép nó vào bộ nhớ đám mây cá nhân của bạn + Lưu nó trên khóa USB hoặc ổ đĩa sao lưu + In nó và lưu trữ nó ở đâu đó an toàn + %2$s &%1$s của bạn đã được đặt. +\n +\nGiữ chúng an toàn! Bạn sẽ cần chúng để mở khóa tin nhắn được mã hóa và bảo mật thông tin nếu bạn mất tất cả các phiên đang hoạt động của mình. + Đang cài đặt chìa khóa sao lưu + Đang đồng bộ chìa khóa đăng nhập của bản thân + Giới hạn là không rõ. + Homeerver của bạn chấp nhận phần đính kèm (tệp, media, v.v.) với kích thước lên tới %s. + Giới hạn tải lên tệp máy chủ + Phiên bản máy chủ + Tên máy chủ + Đăng xuất khỏi phiên này + Quản lý Phiên + Hiện Tất cả Phiên + Phiên Hoạt động + Người quản trị máy chủ của bạn đã vô hiệu hóa mã hóa đầu cuối theo mặc định trong phòng riêng và Tin nhắn trực tiếp. + Xác thực chéo không được bật + Xác thực chéo được kích hoạt. +\nCác khóa không đáng tin cậy + Xác thực chéo chéo được kích hoạt +\nChìa khóa được tin cậy. +\nKhóa riêng tư không được biết + Xác thực chéo được kích hoạt +\nKhóa riêng trên thiết bị. + Xác thực chéo + Phiên mới của bạn hiện đã được xác minh. Nó có quyền truy cập vào các tin nhắn được mã hóa của bạn và những người dùng khác sẽ thấy nó đáng tin cậy. + Tin nhắn với người dùng này được mã hóa đầu cuối và không thể được đọc bởi các bên thứ ba. + So sánh mã với mã được hiển thị trên màn hình của người dùng khác. + So sánh biểu tượng cảm xúc độc đáo, đảm bảo chúng xuất hiện theo cùng một thứ tự. + Để được an toàn, hãy làm điều này trực tiếp hoặc sử dụng một cách khác để giao tiếp. + Để an toàn, hãy xác minh %s bằng cách kiểm tra mã một lần. + Bật mã hóa + Sau khi được bật, mã hóa cho một căn phòng không thể bị vô hiệu hóa. Tin nhắn được gửi trong một căn phòng được mã hóa không thể được nhìn thấy bởi máy chủ, chỉ bởi những người tham gia của căn phòng. Cho phép mã hóa có thể ngăn nhiều bot và cầu hoạt động chính xác. + Bật mã hóa\? + Sau khi được bật, mã hóa không thể bị vô hiệu hóa. + Bạn không có quyền bật mã hóa trong phòng này. + Bật mã hóa đầu cuối… + Trình soạn thảo tin nhắn + Dòng thời gian + Gửi emote đã cho màu cầu vồng + Gửi tin nhắn đã cho có màu cầu vồng + Các phòng khác + Các phòng gần đây + Phiên này không thể chia sẻ xác minh này với các phiên khác của bạn. +\nViệc xác minh sẽ được lưu cục bộ và chia sẻ trong phiên bản tương lai của ứng dụng. + Hủy bỏ qua + ${app_name} gặp sự cố khi hiển thị nội dung sự kiện với id \'%1$s\' + ${app_name} không xử lý tin nhắn kiểu \'%1$s\' + ${app_name} không xử lý các sự kiện thuộc loại \'%1$s\' + Nhảy để đọc biên nhận + Tin nhắn trực tiếp + Tùy chỉnh (%1$d) trong %2$s + Mặc định trong %1$s + Người điều hành trong %1$s + Quản trị viên trong %1$s + Người dùng + Mời + Tùy chọn + Điều hành viên + Quản trị viên + Đang rời phòng… + Rời khỏi + Rời phòng + Tải lên + Thông báo + Cài đặt + Cài đặt phòng + Hành động Quản trị + Thêm + Tìm hiểu thêm + Bảo mật + Tin nhắn ở đây được mã hóa đầu cuối. +\n +\nTin nhắn của bạn được bảo mật bằng khóa và chỉ có bạn và người nhận có các khóa duy nhất để mở khóa chúng. + Tin nhắn trong phòng này được mã hóa đầu cuối. +\n +\nTin nhắn của bạn được bảo mật bằng khóa và chỉ có bạn và người nhận có các khóa duy nhất để mở khóa chúng. + Tin nhắn ở đây không được mã hóa đầu cuối. + Tin nhắn trong phòng này không được mã hóa đầu cuối. + Để bảo mật thêm, hãy xác minh %s bằng cách kiểm tra mã một lần trên cả hai thiết bị của bạn. +\n +\nĐể bảo mật tối đa, hãy làm điều này trực tiếp. + Đang chờ %s… + Đã xác minh %s + Xác minh %s + Hình ảnh mã QR + Nếu bạn không thể quét mã ở trên, hãy xác minh bằng cách so sánh một lựa chọn biểu tượng cảm xúc ngắn, duy nhất. + Xác minh bằng biểu tưởng cảm xúc + Xác minh bằng cách so sánh biểu tượng cảm xúc + Thay vào đó, xác minh bằng cách so sánh biểu tượng cảm xúc + Nếu bạn không trực tiếp, hãy so sánh biểu tượng cảm xúc thay thế + Không thể quét + Quét bằng thiết bị này + Quét mã của họ + Quét mã bằng thiết bị khác của bạn hoặc chuyển đổi và quét bằng thiết bị này + Quét mã bằng thiết bị của người dùng khác để xác minh lẫn nhau một cách an toàn + Bạn + Xác minh thủ công + Xác minh phiên này + Yêu cầu xác minh + Xác minh đã gửi + Bạn đã chấp nhận + %s đã chấp nhận + Bạn đã hủy bỏ + %s đã hủy bỏ + Đang chờ… + Kết luận xác minh + Phản ứng với: %s + Nút Bot + Thăm dò ý kiến + Sticker + Tệp + Thoại + Âm thanh + Hình ảnh. + Video. + Một trong những điều sau đây có thể bị xâm phạm: +\n +\n - Homeserver của bạn +\n - Homeserver mà bạn đang xác minh được kết nối với +\n - Kết nối internet của bạn hoặc của người dùng khác +\n - Thiết bị của bạn hoặc thiết bị của người dùng khác + Không bảo mật + Tìm kiếm lá chắn màu xanh lá cây để đảm bảo người dùng được tin tưởng. Tin tưởng tất cả người dùng trong một căn phòng để đảm bảo phòng được an toàn. + Để bảo mật cuối cùng, hãy sử dụng một phương tiện giao tiếp đáng tin cậy khác hoặc làm điều này trực tiếp. + Xác minh người dùng này bằng cách xác nhận biểu tượng cảm xúc duy nhất sau đây xuất hiện trên màn hình của họ, theo cùng một thứ tự. + Chúng không phù hợp + Chúng phù hợp + Đăng nhập không tin cậy + Tên miền email của bạn không được phép đăng ký trên máy chủ này + Tạo Space… + Tạo phòng… + Một số ký tự không được phép + Vui lòng cung cấp địa chỉ phòng + Địa chỉ này đã được sử dụng + Địa chỉ Space + Địa chỉ phòng + Bạn có thể bật điều này nếu phòng sẽ chỉ được sử dụng để cộng tác với các nhóm nội bộ trên nhà của bạn. Điều này không thể thay đổi sau này. + Chặn bất kỳ ai không thuộc %s tham gia phòng này + Ẩn nâng cao + Hiện nâng cao + Sau khi được bật, mã hóa không thể bị vô hiệu hóa. + Bật mã hóa + Thêm ( ͡° ͜ʖ ͡°) vào một tin nhắn văn bản thuần túy + "Thêm ̄\\_(ツ)_/ ̄ vào một tin nhắn văn bản thuần túy" + Hiển thị một số thông tin hữu ích để giúp gỡ lỗi ứng dụng + Hiện thông tin debug trên màn hình + ${app_name} có thể gặp sự cố thường xuyên hơn khi một lỗi bất ngờ xảy ra + Thất bại nhanh + Chỉ hiển thị kết quả đầu tiên, nhập thêm chữ cái + Các phiên khác + Phiên hiện tại + Cài đặt + Lắc phát hiện! + Lắc điện thoại để kiểm tra ngưỡng phát hiện + Ngưỡng phát hiện + Rageshake + Chế độ nhà phát triển kích hoạt các tính năng ẩn và cũng có thể làm cho ứng dụng kém ổn định hơn. Chỉ dành cho các nhà phát triển! + Chế độ nhà phát triển + Cài đặt nâng cao + Xem tất cả các phiên của tôi + Đồng bộ ban đầu… + Mô tả quá ngắn + Liên kết matrix.to của bạn bị dị hỏng + Phiên hiện tại dành cho người dùng %1$s và bạn cung cấp thông tin đăng nhập cho người dùng %2$s. Điều này không được hỗ trợ bởi ${app_name}. +\nTrước tiên, hãy xóa dữ liệu, sau đó đăng nhập lại trên tài khoản khác. + Xóa dữ liệu + Bạn sẽ mất quyền truy cập vào các tin nhắn an toàn trừ khi bạn đăng nhập để khôi phục khóa mã hóa của mình. + Xóa tất cả dữ liệu hiện đang được lưu trữ trên thiết bị này\? +\nĐăng nhập lại để truy cập dữ liệu tài khoản và tin nhắn của bạn. + Xóa dữ liệu + Xóa tất cả dữ liệu + Cảnh báo: Dữ liệu cá nhân của bạn (bao gồm cả khóa mã hóa) vẫn được lưu trữ trên thiết bị này. +\n +\nXóa nó nếu bạn đã hoàn tất việc sử dụng thiết bị này hoặc muốn đăng nhập vào tài khoản khác. + Xóa dữ liệu cá nhân + Mật khẩu + Đăng nhập + Đăng nhập để khôi phục các khóa mã hóa được lưu trữ độc quyền trên thiết bị này. Bạn cần họ đọc tất cả các tin nhắn an toàn của bạn trên bất kỳ thiết bị nào. + Người quản trị homeerver (%1$s) của bạn đã đăng xuất bạn ra khỏi tài khoản %2$s (%3$s). + Đăng nhập + Bạn đã đăng xuất + Đăng nhập lại + Có thể là do nhiều lý do khác nhau: +\n +\n• Bạn đã thay đổi mật khẩu của mình trên một phiên khác. +\n +\n• Bạn đã xóa phiên này khỏi phiên khác. +\n +\n• Người quản trị máy chủ của bạn đã vô hiệu hóa quyền truy cập của bạn vì lý do bảo mật. + Bạn đã đăng xuất + Nhìn thấy bởi + Không thể tìm thấy một homeerver hợp lệ. Vui lòng kiểm tra mã định danh của bạn + Đây không phải là mã định danh người dùng hợp lệ. Định dạng dự kiến: \'@user:homeserver.org\' + Nếu bạn không biết mật khẩu của mình, hãy quay lại để đặt lại mật khẩu đó. + Matrix ID + Nếu bạn thiết lập tài khoản trên homeerver, hãy sử dụng Matrix ID của bạn (ví dụ: @user:domain.com) và mật khẩu bên dưới. + Đăng nhập bằng Matrix ID + Đăng nhập bằng Matrix ID + Ngoài ra, nếu bạn đã có tài khoản và bạn biết mã định danh Ma trận và mật khẩu của mình, bạn có thể sử dụng phương pháp này: + Homeerver này đang chạy một phiên bản cũ. Yêu cầu người quản trị homeerver của bạn nâng cấp. Bạn có thể tiếp tục, nhưng một số tính năng có thể không hoạt động chính xác. + Homeerver này đang chạy một phiên bản quá cũ để kết nối. Yêu cầu người quản trị homeerver của bạn nâng cấp. + Homeerver lỗi thời + Không giống như một địa chỉ email hợp lệ + Ứng dụng không thể tạo tài khoản trên homeerver này. +\n +\nBạn có muốn đăng ký bằng máy khách web không\? + Xin lỗi, máy chủ này không chấp nhận tài khoản mới + Ứng dụng không thể đăng nhập vào homeerver này. Homeerver hỗ trợ loại signin sau đây:%1$s. +\n +\nBạn có muốn đăng nhập bằng máy khách web không\? + Lỗi xảy ra khi tải trang: %1$s (%2$d) + Nhập địa chỉ của máy chủ bạn muốn sử dụng + Nhập địa chỉ của Mô-đun Element hoặc Máy chủ bạn muốn sử dụng + Lưu trữ cao cấp cho các tổ chức + Lưu trữ cao cấp cho các tổ chức + Tham gia hàng triệu máy chủ công cộng miễn phí lớn nhất + Thăm dò ý kiến + Mở ngăn khung điều hướng + Có vẻ như máy chủ mất quá nhiều thời gian để phản hồi, điều này có thể được gây ra bởi kết nối kém hoặc lỗi với máy chủ. Hãy thử lại trong một thời gian. + Vui lòng thử lại một khi bạn đã chấp nhận các điều khoản và điều kiện của homeserver của bạn. + Nhật ký verbose sẽ giúp các nhà phát triển bằng cách cung cấp thêm nhật ký khi bạn gửi RageShake. Ngay cả khi được bật, ứng dụng không ghi lại nội dung tin nhắn hoặc bất kỳ dữ liệu riêng tư nào khác. + Bật nhật ký verbose. + Đồng ý với điều khoản dịch vụ của máy chủ xác thực (%s) để cho phép bản thân có thể khám phá bằng địa chỉ email hoặc số điện thoại. + Bạn hiện đang chia sẻ địa chỉ email hoặc số điện thoại trên máy chủ xác thực %1$s. Bạn sẽ cần kết nối lại với %2$s để ngừng chia sẻ chúng. + Mã xác minh không chính xác. + Có lỗi tra cứu số điện thoại + Bàn phím số + Kết nối thất bại + Không trả lời + Cuộc gọi video bị bỏ lỡ + Cuộc gọi thoại nhỡ + Cuộc gọi video bị từ chối + Cuộc gọi thoại từ chối + Cuộc gọi video đã kết thúc • %1$s + Cuộc gọi thoại kết thúc • %1$s + Cuộc gọi video đang hoạt động + Cuộc gọi thoại đang hoạt động + Cuộc gọi thoại đến + Cuộc gọi thoại đến + Gọi lại + Cuộc gọi này đã kết thúc + %1$s đã từ chối cuộc gọi này + Bạn đã từ chối cuộc gọi này. + Bạn đã từ chối cuộc gọi này %s + Hiện tại bạn đang ở trong cuộc gọi này + %1$s bắt đầu cuộc gọi + Bạn bắt đầu một cuộc gọi + + Thư văn bản đã được gửi đến %s. Vui lòng nhập mã xác minh mà nó chứa. + Máy chủ xác thực bạn đã chọn không có bất kỳ điều khoản dịch vụ nào. Chỉ tiếp tục nếu bạn tin tưởng chủ sở hữu dịch vụ + Máy chủ xác thực không có điều khoản dịch vụ + Vui lòng nhập url máy chủ xác thực + Không thể kết nối với máy chủ xác thực + Nhập URL máy chủ xác thực + Chính sách + Bạn có đồng ý gửi thông tin này không\? + Để khám phá các liên hệ hiện có, bạn cần gửi thông tin liên hệ (email và số điện thoại) đến máy chủ nhận dạng của mình. Chúng tôi băm dữ liệu của bạn trước khi gửi cho quyền riêng tư. + Để khám phá các liên hệ hiện có, bạn cần gửi thông tin liên hệ đến máy chủ xác thực của mình. +\n +\nChúng tôi băm dữ liệu của bạn trước khi gửi cho quyền riêng tư. Bạn có đồng ý gửi thông tin này không\? + Để khám phá các liên hệ hiện có mà bạn biết, bạn có chấp nhận gửi dữ liệu liên hệ (số điện thoại và/hoặc email) đến máy chủ xác thực được cấu hình (%1$s)\? +\n +\nĐể có thêm quyền riêng tư, dữ liệu được gửi sẽ được băm trước khi được gửi. + Gửi email và số điện thoại đến %s + Gửi email và số điện thoại + Đồng ý + Thu hồi sự đồng ý của tôi + Các liên hệ của bạn là riêng tư. Để khám phá người dùng từ danh bạ của bạn, chúng tôi cần sự cho phép của bạn để gửi thông tin liên hệ đến máy chủ xác thực của bạn. + Bạn đã không đồng ý gửi email và số điện thoại đến máy chủ xác thực này để khám phá những người dùng khác từ danh bạ của bạn. + Bạn đã đồng ý gửi email và số điện thoại đến máy chủ xác thực này để khám phá những người dùng khác từ danh bạ của bạn. + Gửi email và số điện thoại + Đang chờ + Chúng tôi đã gửi cho bạn một email xác nhận đến %s, trước tiên vui lòng kiểm tra email của bạn và nhấp vào liên kết xác nhận + Chúng tôi đã gửi cho bạn một email xác nhận đến %s, kiểm tra email của bạn và nhấp vào liên kết xác nhận + Số điện thoại có thể khám phá + Ngắt kết nối khỏi máy chủ xác thực của bạn sẽ có nghĩa là bạn sẽ không thể khám phá bởi những người dùng khác và bạn sẽ không thể mời người khác qua email hoặc điện thoại. + Tùy chọn Khám phá sẽ xuất hiện khi bạn đã thêm số điện thoại. + app_id: + Không có cổng Push đã đăng ký + Không có quy tắc Push nào được xác định + Quy tắc Push + Chuyên gia + Bảo mật & Quyền riêng tư + Sự ưa thích + Tổng quát + Phản ứng nhanh + Bạn đang xem phòng này rồi! + Thông báo của bên thứ ba khác + Phiên bản Matrix SDK + Nhập khóa e2e từ tệp \"%1$s\". + Lỗi xảy ra khi nhận được dữ liệu sao lưu khóa + Lỗi xảy ra khi nhận được thông tin tin cậy + Căn phòng đã được tạo ra, nhưng một số lời mời đã không được gửi vì lý do sau: +\n +\n%s + Công khai phòng này trong thư mục phòng + Thư mục phòng + Bất cứ ai cũng có thể tham gia vào căn phòng này. + Công cộng + Cài đặt phòng + Chủ đề + Chủ đề phòng (tùy chọn) + Tên + Tên phòng + TẠO + Phòng mới + Tin nhắn trực tiếp + Phòng + Căn phòng này không thể xem trước được. Bạn có muốn tham gia nó không\? + Căn phòng này không thể truy cập vào thời điểm này. +\nHãy thử lại sau, hoặc yêu cầu quản trị viên phòng kiểm tra xem bạn có quyền truy cập hay không. + Bản xem trước của phòng có thể đọc được trên thế giới vẫn chưa được hỗ trợ bằng ${app_name} + Phòng này không thể được xem trước + Tất cả Community + Vui lòng chờ… + Thay đổi mạng + Thay đổi + Không có mạng. Vui lòng kiểm tra kết nối internet + Tạo Space mới + Tạo phòng mới + Sự kiện bị hỏng, không thể hiển thị + Lần cuối được sửa bởi %1$s trên %2$s + Sự kiện được vận hành bởi người quản trị phòng + Sự kiện bị xóa bởi người dùng + Hiển thị người gửi các tin nhắn đã xóa + Hiển thị tin nhắn đã xóa + Tin nhắn đã xóa + Phản ứng + Xem phản ứng + Thêm phản ứng + Thích + Đồng ý + Phản ứng + Phòng + Các cuộc hội thoại tin nhắn trực tiếp của bạn sẽ được hiển thị tại đây. Nhấp vào dấu + dưới cùng bên phải để bắt đầu. + Các cuộc hội thoại + Bắt kịp các tin nhắn chưa đọc tại đây + Có vẻ như bạn đang cố gắng kết nối với một homeserver khác. Bạn có muốn đăng xuất không\? + Không có máy chủ xác thực nào được cấu hình, cần thiết để đặt lại mật khẩu của bạn. + Bạn không sử dụng bất kỳ máy chủ xác thực nào + Lỗi không xác định + Người dùng không khớp + Khóa không khớp + Một tin nhắn không hợp lệ đã được nhận + Phiên đã nhận được một tin nhắn bất ngờ + SAS không khớp + Sự cam kết băm không khớp + Phiên không thể đồng ý về một sự thỏa thuận khóa, phương pháp băm, MAC hoặc SAS + Phiên họp không biết về giao dịch đó + Quá trình xác minh đã hết thời gian + Người dùng đã hủy xác minh + %s muốn xác minh phiên của bạn + Yêu cầu xác minh + Xác minh Phiên Tương tác + Việc xác minh bị hủy bỏ. +\nLý do: %s + Bên kia đã hủy bỏ việc xác minh. +\n%s + Yêu cầu bị hủy + Xác minh Khóa + Sử dụng xác minh kế thừa. + Không có gì xuất hiện\? Không phải tất cả ứng dụng đầu cuối đều hỗ trợ xác minh tương tác. Sử dụng xác minh kế thừa. + Đã nhận được + Tin nhắn an toàn với người dùng này được mã hóa đầu cuối và không thể được các bên thứ ba đọc. + Bạn đã xác minh thành công phiên này. + Đã xác minh! + Chờ đối tác xác nhận… + Xem yêu cầu + Bạn đã nhận được yêu cầu xác minh đến. + Xác minh phiên này bằng cách xác nhận các số sau đây xuất hiện trên màn hình của đối tác + Xác minh phiên này bằng cách xác nhận biểu tượng cảm xúc sau đây xuất hiện trên màn hình của đối tác + Xác minh phiên này sẽ đánh dấu nó là đáng tin cậy và cũng đánh dấu phiên của bạn là đáng tin cậy cho đối tác. + Xác minh phiên này để đánh dấu nó là đáng tin cậy. Các phiên tin cậy của các đối tác giúp bạn yên tâm hơn khi sử dụng các tin nhắn được mã hóa đầu cuối. + Yêu cầu xác minh đến + Bắt đầu Xác minh + Để bảo mật tối đa, chúng tôi khuyên bạn nên làm điều này trực tiếp hoặc sử dụng một phương tiện liên lạc đáng tin cậy khác. + Xác minh bằng cách so sánh một chuỗi văn bản ngắn. + Bạn đã bị đăng xuất do mật khẩu không hợp lệ hoặc hết hạn. + Sử dụng cấu hình + ${app_name} đã phát hiện cấu hình máy chủ tùy chỉnh cho tên miền userId của bạn \"%1$s\": +\n%2$s + Tự động hoàn tất các tùy chọn máy chủ + Phản hồi khám phá homeserver không hợp lệ + Chữ ký + Thuật toán + Phiên bản + Tất cả các khóa được sao lưu + Thiết lập Sao lưu An toàn + Sao lưu chìa khóa của bạn. Việc này có thể mất vài phút… + Quản lý trong Khóa Sao lưu + Khóa thư bảo mật mới + Dùng Khóa Sao lưu + Không bao giờ mất tin nhắn được mã hóa + Bảo vệ chống mất quyền truy cập vào tin nhắn và dữ liệu được mã hóa + Sao lưu An toàn + Bắt đầu sử dụng Khóa Sao lưu + Không bao giờ mất tin nhắn được mã hóa + Đó là tôi. + Một bản sao lưu khóa thư bảo mật mới đã được phát hiện. +\n +\nNếu bạn không thiết lập phương pháp khôi phục mới, kẻ tấn công có thể đang cố gắng truy cập tài khoản của bạn. Thay đổi mật khẩu tài khoản của bạn và đặt phương pháp khôi phục mới ngay lập tức trong Cài đặt. + Sao lưu Khóa Mới + Xóa khóa mã hóa đã sao lưu của bạn khỏi máy chủ\? Bạn sẽ không còn có thể sử dụng khóa khôi phục của mình để đọc lịch sử tin nhắn được mã hóa. + Xóa Sao lưu + Kiểm tra trạng thái sao lưu + Thất bại trong việc xóa sao lưu (%s) + Đang xóa bản sao lưu… + Để sử dụng Sao lưu Chính trong phiên này, hãy khôi phục bằng cụm mật khẩu hoặc khóa khôi phục của bạn ngay bây giờ. + Thất bại trong việc nhận thông tin tin cậy để sao lưu (%s). + Sao lưu có chữ ký không hợp lệ từ phiên chưa được xác minh %s + Sao lưu có chữ ký không hợp lệ từ phiên đã xác minh %s + Sao lưu có chữ ký hợp lệ từ phiên chưa được xác minh %s + Sao lưu có chữ ký hợp lệ từ phiên đã xác minh %s. + Sao lưu có chữ ký hợp lệ từ phiên này. + Sao lưu có chữ ký từ phiên không xác định với ID %s. + Khóa của bạn không được sao lưu từ phiên này. + Sao lưu chính không hoạt động trong phiên này. + Key Backup đã được thiết lập chính xác cho phiên này. + Xóa Sao lưu + Khôi phục từ Sao lưu + Mã hóa phiên không được kích hoạt + Thất bại trong việc nhận được phiên bản khóa khôi phục mới nhất (%s). + Sao lưu đã khôi phục %s ! + Sao lưu không thể được giải mã bằng khóa khôi phục này: vui lòng xác minh rằng bạn đã nhập khóa khôi phục chính xác. + Vui lòng nhập khóa khôi phục + Mở khóa Lịch sử + Đang nhập các khóa… + Đang tải xuống các khóa… + Đang tính toán khóa khôi phục… + Khôi phục sao lưu: + Lỗi mạng: vui lòng kiểm tra kết nối của bạn và thử lại . + Sao lưu không thể được giải mã bằng cụm mật khẩu này: vui lòng xác minh rằng bạn đã nhập cụm mật khẩu phục hồi chính xác. + Mất chìa khóa phục hồi\? Bạn có thể thiết lập một cái mới trong cài đặt. + Khôi phục tin nhắn + Nhập Khóa Khôi phục + Sử dụng Khóa Khôi phục của bạn để mở khóa lịch sử tin nhắn được mã hóa của bạn + Không biết cụm mật khẩu phục hồi của bạn, bạn có thể %s. + sử dụng khóa khôi phục của bạn + Sử dụng cụm mật khẩu khôi phục của bạn để mở khóa lịch sử tin nhắn được mã hóa của bạn + Đang tìm phiên bản sao lưu… + Bạn có thể mất quyền truy cập vào tin nhắn của mình nếu bạn đăng xuất hoặc mất thiết bị này. + Bạn có chắc không\? + Khóa mã hóa của bạn hiện đang được sao lưu trong nền cho homeerver của bạn. Việc sao lưu ban đầu có thể mất vài phút. + Bắt đầu Sao lưu + Lỗi bất ngờ + Khóa Khôi phục + Tạo Khóa Phục hồi bằng cách sử dụng cụm mật khẩu, quá trình này có thể mất vài giây. + Chia sẻ khóa phục hồi với… + Vui lòng tạo bản sao + Dừng + Thay thế + Có vẻ như bạn đã thiết lập bản sao lưu khóa từ một phiên khác. Bạn có muốn thay thế nó bằng cái mà bạn đang tạo ra không\? + Một bản sao lưu đã tồn tại trên homeerver của bạn + Khóa phục hồi đã được lưu. + Khóa khôi phục đã được lưu thành \'%s\'. +\n +\nCảnh báo: tệp này có thể bị xóa nếu ứng dụng được gỡ cài đặt. + Lưu dưới dạng Tệp + Chia sẻ + Lưu Khóa Khôi phục + Tôi đã tạo ra một bản sao + Xong + Giữ khóa khôi phục của bạn ở đâu đó rất an toàn, như trình quản lý mật khẩu (hoặc két an toàn) + Khóa khôi phục của bạn là một mạng lưới an toàn - bạn có thể sử dụng nó để khôi phục quyền truy cập vào các tin nhắn được mã hóa của mình nếu bạn quên cụm mật khẩu của mình. +\nGiữ khóa khôi phục của bạn ở đâu đó rất an toàn, như trình quản lý mật khẩu (hoặc an toàn) + Các khóa của bạn đang được sao lưu. + Thành công! + (Nâng cao) Thiết lập với Khóa Khôi phục + Hoặc, bảo mật sao lưu của bạn bằng Khóa Khôi phục, lưu nó ở đâu đó an toàn. + Tạo Sao lưu + Đặt Cụm mật khẩu + Chúng tôi sẽ lưu trữ một bản sao được mã hóa của khóa của bạn trên homeerver của bạn. Bảo vệ bản sao lưu của bạn bằng cụm mật khẩu để giữ an toàn. +\n +\nĐể bảo mật tối đa, điều này phải khác với mật khẩu tài khoản của bạn. + Bảo mật sao lưu của bạn bằng Cụm mật khẩu. + Xuất các khóa thủ công + (Nâng cao) + Bắt đầu sử dụng Khóa Sao lưu + Tin nhắn trong các phòng được mã hóa được bảo mật bằng mã hóa đầu cuối. Chỉ có bạn và người nhận mới có chìa khóa để đọc các tin nhắn này. +\n +\nSao lưu an toàn chìa khóa của bạn để tránh mất chúng. + Không bao giờ mất tin nhắn được mã hóa + Đang đồng hộ chìa khóa người dùng + Đang đồng bộ chìa khóa Master + Xác định khóa mặc định SSSS + Tạo khóa an toàn từ cụm mật khẩu + Công khai hóa các chìa khóa nhận dạng đã tạo + Sử dụng %1$s này làm mạng lưới an toàn trong trường hợp bạn quên %2$s của mình. + Kết thúc + Giữ an toàn + Bạn đã hoàn tất! + Khóa phục hồi của bạn + Cài lập phục hồi. + Việc này có thể mất vài giây, xin hãy kiên nhẫn. + Nhập cụm từ bảo mật chỉ bạn biết, được sử dụng để bảo mật bí mật trên máy chủ của bạn. + Không sử dụng mật khẩu tài khoản của bạn. + Nhập lại %s của bạn để xác nhận. + Bảo mật & mở khóa tin nhắn mã hóa và tin cậy bằng %s. + Nhập %s của bạn để tiếp tục. + Xác nhận %s + Tạo chía khóa tin nhắn + Đặt %s + Mật khẩu tài khoản + Chìa khóa tin nhắn + Cụm mật khẩu phục hồi + Xác minh bị hủy bỏ + Xác minh đã bị hủy bỏ. Bạn có thể bắt đầu xác minh lại. + Một trong những điều sau đây có thể bị xâm phạm: +\n +\n- Mật khẩu của bạn +\n- Người ở nhà của anh +\n- Thiết bị này hoặc thiết bị khác +\n- Kết nối internet mà một trong hai thiết bị đang sử dụng +\n +\nChúng tôi khuyên bạn nên thay đổi mật khẩu và khóa khôi phục trong Cài đặt ngay lập tức. + Bạn sẽ không xác minh %1$s (%2$s) nếu bạn hủy ngay. Bắt đầu lại trong hồ sơ người dùng của họ. + Nếu bạn hủy, bạn sẽ không thể đọc tin nhắn được mã hóa trên thiết bị mới của mình và những người dùng khác sẽ không tin tưởng nó. + Nếu bạn hủy, bạn sẽ không thể đọc tin nhắn được mã hóa trên thiết bị này và những người dùng khác sẽ không tin tưởng nó. + Tài khoản của bạn có thể bị xâm phạm + Đây không phải là tôi. + Sử dụng phiên này để xác minh phiên mới của bạn, cấp cho nó quyền truy cập vào các tin nhắn được mã hóa. + Nhấp để xem xét và xác minh + Đăng nhập mới. Đây có phải là bạn không\? + Làm tươi + Mở khóa lịch sử tin nhắn được mã hóa + Kiểm toán Xuất + Yêu cầu chìa khóa + ${app_name} Android + Chìa khóa đã được cập nhật! + Sự kiện được kiểm duyệt bởi người quản trị phòng, lý do: %1$s + Sự kiện bị người dùng xóa, lý do: %1$s + Lý do tái sửa + Bao gồm một lý do + Bạn có chắc chắn muốn loại bỏ (xóa) sự kiện này không\? Lưu ý rằng nếu bạn xóa tên phòng hoặc thay đổi chủ đề, nó có thể hoàn tác thay đổi. + Xác nhận Loại bỏ + Gửi media với kích thước ban đầu + Bạn có muốn gửi phần đính kèm này đến %1$s không\? + Xóa… + Bạn chỉ nên truy cập lưu trữ bí mật từ một thiết bị đáng tin cậy + Cảnh báo: + Nhập cụm mật khẩu lưu trữ bí mật + Không thể tìm thấy bí mật trong kho + Đăng nhập mới + Nếu bạn không thể truy nhập phiên hiện có + Sử dụng Cụm mật khẩu hoặc Khóa phục hồi + Tạo một cuộc thăm dò ý kiến đơn giản + Tùy chọn đã chọn + Xóa dữ liệu tài khoản của loại %1$s\? +\n +\nSử dụng một cách thận trọng, nó có thể dẫn đến hành vi bất ngờ. + Dữ liệu tài khoản + Công cụ Dev + Chế độ máy bay đang bật + Kết nối với máy chủ đã bị mất + Không + + Gần xong! %s có hiển thị dấu tích không + Mã QR + Đặt lại khóa + Khởi tạo xác thực chéo + Cho đến khi người dùng này tin tưởng phiên này, tin nhắn được gửi đến và đi từ nó được dán nhãn cảnh báo. Ngoài ra, bạn có thể xác minh thủ công. + %1$s (%2$s) đã đăng nhập bằng phiên mới: + Phiên này được tin cậy để nhắn tin an toàn vì %1$s (%2$s) đã xác minh: + Không tin cậy + Tin cậy + Phiên + Không nhận được phiên + Cảnh báo + Đã xác minh + Xác minh + Sử dụng phiên hiện có để xác minh phiên này, cấp cho nó quyền truy cập vào các thư được mã hóa. + Bảo mật hoàn toàn + Những người dùng khác có thể không tin tưởng nó. + Xác minh đăng nhập này + Xác minh phiên này để đánh dấu nó là đáng tin cậy và cấp cho nó quyền truy cập vào các thư được mã hóa. Nếu bạn không đăng nhập vào phiên này, tài khoản của bạn có thể bị xâm phạm: + Phiên này được tin cậy để nhắn tin an toàn vì bạn đã xác minh nó: + Không có thông tin mật mã sẵn dùng + bất ổn định + ổn định + Phiên bản Mặc định + Phiên bản phòng 👓 + Không có phiên Matrix sẵn dùng + Vui lòng xóa cụm mật khẩu nếu bạn muốn ${app_name} để tạo khóa khôi phục. + Cụm mật khẩu quá yếu + Vui lòng nhập cụm mật khẩu + Cụm mật khẩu không khớp + Nhập cụm mật khẩu + Xác nhận cụm mật khẩu + Tạo cụm mật khẩu + Không tìm thấy APK Google Play Services hợp lệ. Thông báo có thể không hoạt động bình thường. + %d+ + +%d + %1$s: %2$s + %1$s: + Chỉ dành cho lỗi + Đối với tin nhắn và lỗi + Luôn luôn + Hiện vùng thông tin + thu hẹp + mở rộng + Xin lỗi, một lỗi đã xảy ra + Homeerver của bạn chưa hỗ trợ tải chậm các thành viên phòng. Hãy thử sau. + Tăng hiệu suất bằng cách chỉ tải các thành viên phòng ở trong lần xem đầu tiên. + Tải chậm các phòng và thành viên + Vui lòng %s để tiếp tục sử dụng dịch vụ này. + Vui lòng %s để tăng giới hạn này. + Homeerver này đã đạt đến giới hạn người dùng hoạt động hàng tháng. + Homeerver này đã đạt đến giới hạn Người dùng hoạt động hàng tháng của nó vì vậy một số người dùng sẽ không thể đăng nhập. + Homeserver này đã vượt quá một trong những giới hạn tài nguyên của nó. + Homeerver này đã vượt quá một trong những giới hạn tài nguyên của nó vì vậy một số người dùng sẽ không thể đăng nhập. + liên hệ với người quản trị dịch vụ của bạn + Liên hệ quản trị viên + Vượt quá giới hạn tài nguyên + Bấm vào đây để xem tin nhắn cũ hơn + Phòng này là sự tiếp nối của một cuộc trò chuyện khác. + Cuộc trò chuyện tiếp tục ở đây + Phòng này đã được thay thế và không còn hoạt động nữa. + Vui lòng nhập mật khẩu của bạn. + Vui lòng nhập tên người dùng. + Hủy kích hoạt Tài khoản + Để tiếp tục, vui lòng nhập mật khẩu của bạn: + Vui lòng quên tất cả các tin nhắn tôi đã gửi khi tài khoản của tôi bị vô hiệu hóa (Cảnh báo: điều này sẽ khiến người dùng trong tương lai thấy chế độ xem cuộc hội thoại không đầy đủ) + Điều này sẽ làm cho tài khoản của bạn vĩnh viễn không thể sử dụng được. Bạn sẽ không thể đăng nhập và không ai có thể đăng ký lại cùng một ID người dùng. Điều này sẽ khiến tài khoản của bạn rời khỏi tất cả các phòng mà nó đang tham gia và nó sẽ xóa chi tiết tài khoản của bạn khỏi máy chủ nhận dạng của bạn. Điều này là không thể đảo ngược. +\n +\nHủy kích hoạt tài khoản của bạn does không theo mặc định khiến chúng tôi quên tin nhắn bạn đã gửi. Nếu bạn muốn chúng tôi quên tin nhắn của bạn, vui lòng đánh dấu vào hộp bên dưới. +\n +\nKhả năng hiển thị tin nhắn trong Matrix tương tự như email. Chúng tôi quên tin nhắn của bạn có nghĩa là tin nhắn bạn đã gửi sẽ không được chia sẻ với bất kỳ người dùng mới hoặc chưa đăng ký nào, nhưng người dùng đã đăng ký đã có quyền truy cập vào các tin nhắn này vẫn sẽ có quyền truy cập vào bản sao của họ. + Để tiếp tục sử dụng homeserver %1$, bạn phải xem xét và đồng ý với các điều khoản và điều kiện. + Avatar + Chú ý avatar + Nhận avatar + Quên phòng + Tham gia lại + Lý do: %1$s + Bạn đã bị cấm từ %1$s bởi %2$s + Bạn đã bị đá từ %1$s bởi %2$s + Quản trị viên Community đã không cung cấp mô tả cho cộng đồng này. + Lọc nhóm phòng + Lọc thành viên nhóm + Đã mời + Đã tham gia + Phòng + Không có người dùng + Phòng + Danh bạ + Nhà + Ví dụ + ID Community + Ví dụ + Tên Community + Tạo Community + Tạo + Tin nhắn được mã hóa + Ồn ào + Im lặng + Tắt + Đánh dấu đã bị vô hiệu hóa. + Đánh dấu đã được kích hoạt. + Hiển thị thông tin về người dùng + Để sửa trình quản lý ứng dụng Matrix + Đánh dấu Bật/Tắt + Chỉ thay đổi avatar của bạn trong căn phòng hiện tại này + Thay đổi avatar của căn phòng hiện tại + Chỉ thay đổi biệt danh hiển thị của bạn trong phòng hiện tại + Thay đổi biệt danh hiển thị của bạn + Đá người dùng với id đã cho + Đặt chủ đề phòng + Rời khỏi phòng + Tham gia phòng có địa chỉ đã cho + Mời người dùng có id đã cho đến phòng hiện tại + Đặt tên phòng + Deops user với id đã cho + Xác định cấp độ quyền của người dùng + Ngừng bỏ qua người dùng, tiếp tục hiển thị thông điệp của họ + Bỏ qua người dùng, che giấu tin nhắn của họ khỏi bạn + Bỏ cấm người dùng với id đã cho + Cấm người dùng có id đã cho + Hiển thị hành động + Lệnh \"%s\" cần nhiều tham số hơn hoặc một số tham số không chính xác. + Lệnh không được nhận ra: %s + Lỗi lệnh + Kêu gọi hội nghị đang được phát triển và có thể không đáng tin cậy. + Yêu cầu Chia sẻ Khóa + Một phiên chưa được xác minh đang yêu cầu khóa mã hóa. +\nTên phiên: %1$s +\nLần nhìn thấy lần cuối: %2$s +\nNếu bạn không đăng nhập vào phiên khác, hãy bỏ qua yêu cầu này. + Phiên chưa được xác minh của bạn \'%s\' đang yêu cầu khóa mã hóa + Một phiên mới đang yêu cầu các khóa mã hóa. +\nTên phiên: %1$s +\nLần nhìn thấy lần cuối: %2$s +\nNếu bạn không đăng nhập vào phiên khác, hãy bỏ qua yêu cầu này. + Bạn đã thêm phiên mới \'%s\', nó đang yêu cầu các khóa mã hóa. + Để tiếp tục, bạn cần phải chấp nhận các điều khoản của dịch vụ này. + Tùy chọn này yêu cầu một ứng dụng của bên thứ ba để ghi lại các tin nhắn. + Gửi tin nhắn thoại + Sử dụng phím Enter của bàn phím để gửi tin nhắn + Khởi động camera hệ thống thay vì màn hình camera tùy chỉnh. + Sử dụng máy ảnh gốc + Không có widget hoạt động + Quản lý Tích hợp + Thêm ứng dụng Matrix + Không có trình quản lý tích hợp nào được cấu hình. + Một tham số không hợp lệ + Một tham số cần thiết bị thiếu. + Phòng %s không hiển thị. + Thiếu user_id trong yêu cầu + Thiếu room_id trong yêu cầu. + Bạn không được phép làm điều đó trong căn phòng này. + Bạn không ở trong căn phòng này. + Cấp độ quyền phải là số nguyên dương. + Gửi yêu cầu thất bại. + Không thể tạo widget. + Đọc Media được DRM bảo vệ + Sử dụng micrô + Sử dụng máy ảnh + Chặn Tất cả + Cho phép + Widget này muốn sử dụng các tài nguyên sau: + Rời khỏi hội nghị hiện tại và chuyển sang hội nghị khác\? + Xin lỗi, một lỗi đã xảy ra trong khi cố gắng tham gia hội nghị + Xin lỗi, các cuộc gọi hội nghị với Jitsi không được hỗ trợ trên các thiết bị cũ (thiết bị có hệ điều hành Android dưới 6.0) + ID phòng + Widget ID + ID người dùng của bạn + URL avatar của bạn + Tên hiển thị của bạn + Thu hồi quyền truy cập cho tôi + Mở trong trình duyệt + Tải lại widget + Thất bại trong việc tải widget.%s + Sử dụng nó có thể chia sẻ dữ liệu với %s: + Sử dụng nó có thể đặt cookie và chia sẻ dữ liệu với %s: + Widget này được thêm vào bởi: + Tải Widget + Widget + Widget đang hoạt động + XEM + Bạn có chắc chắn muốn xóa widget khỏi căn phòng này không\? + Tạo cuộc gọi hội nghị với jitsi + Tạo widget đã thất bại + Bạn cần sự cho phép để quản lý các widget trong phòng này + Khổng lồ + Lớn nhất + Lớn hơn + Lớn + Vừa + Nhỏ + Bé tí + Cỡ chữ + Tìm kiếm lịch sử + %1$s: %2$s %3$s + %1$s: %2$s + ** Gửi thất bại - vui lòng mở phòng + Tôi + Lời mời mới + Tin nhắn mới + Phòng + Sự kiện mới + %1$s và %2$s + %1$s trong %2$s và %3$s + %1$s trong %2$s + Gõ vào đây… + Máy chủ này đã có trong danh sách + Không thể tìm thấy máy chủ này hoặc danh sách phòng của nó + Nhập tên của một máy chủ mới mà bạn muốn khám phá. + Thêm máy chủ mới + Máy chủ của bạn + Tất cả các phòng %s gốc + Tất cả các phòng trên %s server + Tên máy chủ + Nhập homeserver để liệt kê các phòng công cộng từ + Chọn thư mục phòng + Không bao giờ gửi tin nhắn được mã hóa đến các phiên chưa được xác minh từ phiên này. + Chỉ mã hóa cho các phiên đã xác minh + Thư mục + Bật mã hóa (cảnh báo: không thể bị vô hiệu hóa trở lại!) + Mã hóa bị vô hiệu hóa trong phòng này. + Mã hóa được kích hoạt trong phòng này. + Sao chép Địa chỉ Phòng + Sao chép ID Phòng + Bỏ đặt làm địa chỉ chính + Đặt làm địa chỉ chính + Cảnh báo địa chỉ chính + Bạn sẽ không có địa chỉ chính được chỉ định cho căn phòng này. + ID cộng đồng mới (ví dụ : +foo:matrix.org) + Máy chủ này không cung cấp bất kỳ chính sách nào. + Thư viện bên thứ ba + Chính sách máy chủ xác thực của bạn + Chính sách homeerver của bạn + Chính sách ${app_name} + Bạn có thể tắt tính năng này bất cứ lúc nào trong cài đặt + Chúng tôi không chia sẻ thông tin với bên thứ ba + Chúng tôi không ghi âm hoặc tạo hồ sơ bất kỳ dữ liệu tài khoản nào + ở đây + Giúp chúng tôi xác định các vấn đề và cải thiện Element bằng cách chia sẻ dữ liệu sử dụng ẩn danh. Để hiểu cách mọi người sử dụng nhiều thiết bị, chúng tôi sẽ tạo ra một mã định danh ngẫu nhiên, được chia sẻ bởi các thiết bị của bạn. +\n +\nBạn có thể đọc tất cả các thuật ngữ của chúng tôi %s. + Giúp cải thiện Element + Thiết bị đã bị đăng xuất! + Căn phòng đã bị bỏ lại! + Chọn homeerver + Không thể kết nối đến một homeserver tại URL %s. Vui lòng kiểm tra liên kết của bạn hoặc chọn homeerver thủ công. + Không phải bây giờ + Kích hoạt + Nghe thông báo + Tùy chọn Khám phá sẽ xuất hiện sau khi bạn đã thêm email. + Địa chỉ email có thể khám phá + Hiện tại bạn không sử dụng máy chủ xác thực. Để khám phá và có thể khám phá bởi các liên hệ hiện có mà bạn biết, hãy cấu hình một danh bạ dưới đây. + Không có chính sách được cung cấp bởi máy chủ xác thực + Ẩn chính sách máy chủ xác thực + Hiện chính sách máy chủ xác thực + Mở Cài đặt Khám phá + Thêm tab dành riêng cho các thông báo chưa đọc trên màn hình chính. + Tìm kiếm theo tên, ID hoặc thư + Lọc hội thoại… + Không tìm thấy bản chỉnh sửa + Sửa tin nhắn + (đã sửa) + Tệp %1$s đã được tải xuống! + Đang tải xuống tệp %1$s… + Nén video %d%% + Nén hình ảnh… + Tệp Đang gửi (%1$s / %2$s) + Mã hóa tệp… + Gửi hình thu nhỏ (%1$s / %2$s) + Đang mã hóa hình nhỏ… + Đang đợi… + Tin nhắn trực tiếp + Hiển thị lịch sử đầy đủ trong các phòng được mã hóa + Hiện các sự kiện ẩn trong dòng thời gian + Đưa phản hồi + Gửi phản hồi thất bại (%s) + Cảm ơn, phản hồi của bạn đã được gửi thành công + Bạn có thể liên hệ với tôi nếu bạn có bất kỳ câu hỏi tiếp theo nào + Bạn đang sử dụng phiên bản beta của Space. Phản hồi của bạn sẽ giúp thông báo cho các phiên bản tiếp theo. Nền tảng và tên người dùng của bạn sẽ được ghi nhận để giúp chúng tôi sử dụng phản hồi của bạn càng nhiều càng tốt. + Phản hồi + Phản hồi Space + Đề xuất không được gửi (%s) + Cảm ơn, gợi ý đã được gửi thành công + Mô tả đề xuất của bạn ở đây + Vui lòng viết gợi ý của bạn dưới đây. + Đưa gợi ý + Token đăng ký + Cài đặt hệ thống + Phiên bản + Nhận trợ giúp về việc sử dụng Element + Trợ giúp và hỗ trợ + Trợ giúp + Pháp lý + Trợ giúp & Giới thiệu + Voice & Video + Format: + Url: + session_name: + app_display_name: + push_key: \ No newline at end of file From 01b55442610bc6415845bb52f7cf2a671d133fb8 Mon Sep 17 00:00:00 2001 From: Van Linh Nguyen Date: Fri, 24 Dec 2021 09:22:11 +0000 Subject: [PATCH 138/632] Translated using Weblate (Vietnamese) Currently translated at 100.0% (2725 of 2725 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/vi/ --- vector/src/main/res/values-vi/strings.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/vector/src/main/res/values-vi/strings.xml b/vector/src/main/res/values-vi/strings.xml index 87715aeb44..ef0abeb90e 100644 --- a/vector/src/main/res/values-vi/strings.xml +++ b/vector/src/main/res/values-vi/strings.xml @@ -3009,4 +3009,10 @@ session_name: app_display_name: push_key: + + Gửi video với kích thước gốc + + + Gửi ảnh với kích thước gốc + \ No newline at end of file From fec74bf8648371a6dcfa14005b9da5fcf549c330 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20I=2ESvindseth?= Date: Sat, 25 Dec 2021 18:52:59 +0000 Subject: [PATCH 139/632] Translated using Weblate (Norwegian Nynorsk) Currently translated at 33.6% (918 of 2725 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/nn/ --- vector/src/main/res/values-nn/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/vector/src/main/res/values-nn/strings.xml b/vector/src/main/res/values-nn/strings.xml index ed8afce0c9..3488546447 100644 --- a/vector/src/main/res/values-nn/strings.xml +++ b/vector/src/main/res/values-nn/strings.xml @@ -1021,4 +1021,5 @@ Meldingssynlegheit på Matrix liknar på epost. At vi gløymer meldingane dine t Pause Folk Folk + Du sende eit bilde. \ No newline at end of file From a0afab45fbc421e86e0647d96fa9aa37a0aec64f Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Mon, 27 Dec 2021 14:03:59 +0300 Subject: [PATCH 140/632] Show location preview and allow to share with external apps. --- .../java/im/vector/app/VectorApplication.kt | 4 + .../im/vector/app/core/di/FragmentModule.kt | 6 ++ .../core/utils/ExternalApplicationsUtil.kt | 20 ++++ .../home/room/detail/RoomDetailAction.kt | 4 + .../home/room/detail/RoomDetailFragment.kt | 21 +++- .../home/room/detail/RoomDetailViewEvents.kt | 3 + .../home/room/detail/RoomDetailViewModel.kt | 6 ++ .../timeline/factory/MessageItemFactory.kt | 40 +++++-- .../timeline/helper/LocationPinProvider.kt | 74 +++++++++++++ .../timeline/item/MessageLocationItem.kt | 80 ++++++++++++++ .../app/features/location/LocationData.kt | 49 ++++++++- .../location/LocationPreviewFragment.kt | 85 +++++++++++++++ .../location/LocationSharingActivity.kt | 9 +- .../location/LocationSharingFragment.kt | 102 +++--------------- .../app/features/location/LocationTracker.kt | 1 + .../app/features/location/MapTilerMapView.kt | 92 ++++++++++++++++ .../app/features/location/VectorMapView.kt | 36 +++++++ .../features/navigation/DefaultNavigator.kt | 9 +- .../app/features/navigation/Navigator.kt | 7 +- .../main/res/drawable/ic_share_external.xml | 5 + .../res/layout/fragment_location_preview.xml | 11 ++ .../res/layout/fragment_location_sharing.xml | 2 +- .../res/layout/item_timeline_event_base.xml | 5 + .../item_timeline_event_location_stub.xml | 17 +++ .../main/res/menu/menu_location_preview.xml | 12 +++ vector/src/main/res/values/strings.xml | 1 + 26 files changed, 601 insertions(+), 100 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/LocationPinProvider.kt create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLocationItem.kt create mode 100644 vector/src/main/java/im/vector/app/features/location/LocationPreviewFragment.kt create mode 100644 vector/src/main/java/im/vector/app/features/location/MapTilerMapView.kt create mode 100644 vector/src/main/java/im/vector/app/features/location/VectorMapView.kt create mode 100644 vector/src/main/res/drawable/ic_share_external.xml create mode 100644 vector/src/main/res/layout/fragment_location_preview.xml create mode 100644 vector/src/main/res/layout/item_timeline_event_location_stub.xml create mode 100644 vector/src/main/res/menu/menu_location_preview.xml diff --git a/vector/src/main/java/im/vector/app/VectorApplication.kt b/vector/src/main/java/im/vector/app/VectorApplication.kt index 400fb7eb89..52de28a1cf 100644 --- a/vector/src/main/java/im/vector/app/VectorApplication.kt +++ b/vector/src/main/java/im/vector/app/VectorApplication.kt @@ -36,6 +36,7 @@ import com.airbnb.epoxy.EpoxyController import com.airbnb.mvrx.Mavericks import com.facebook.stetho.Stetho import com.gabrielittner.threetenbp.LazyThreeTen +import com.mapbox.mapboxsdk.Mapbox import com.vanniktech.emoji.EmojiManager import com.vanniktech.emoji.google.GoogleEmojiProvider import dagger.hilt.android.HiltAndroidApp @@ -195,6 +196,9 @@ class VectorApplication : }) EmojiManager.install(GoogleEmojiProvider()) + + // Initialize Mapbox before inflating mapViews + Mapbox.getInstance(this) } private val startSyncOnFirstStart = object : DefaultLifecycleObserver { diff --git a/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt b/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt index 3314c0565e..4d70f49186 100644 --- a/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt @@ -61,6 +61,7 @@ import im.vector.app.features.home.room.breadcrumbs.BreadcrumbsFragment import im.vector.app.features.home.room.detail.RoomDetailFragment import im.vector.app.features.home.room.detail.search.SearchFragment import im.vector.app.features.home.room.list.RoomListFragment +import im.vector.app.features.location.LocationPreviewFragment import im.vector.app.features.location.LocationSharingFragment import im.vector.app.features.login.LoginCaptchaFragment import im.vector.app.features.login.LoginFragment @@ -861,4 +862,9 @@ interface FragmentModule { @IntoMap @FragmentKey(LocationSharingFragment::class) fun bindLocationSharingFragment(fragment: LocationSharingFragment): Fragment + + @Binds + @IntoMap + @FragmentKey(LocationPreviewFragment::class) + fun bindLocationPreviewFragment(fragment: LocationPreviewFragment): Fragment } diff --git a/vector/src/main/java/im/vector/app/core/utils/ExternalApplicationsUtil.kt b/vector/src/main/java/im/vector/app/core/utils/ExternalApplicationsUtil.kt index bdaf520ba1..f3138218eb 100644 --- a/vector/src/main/java/im/vector/app/core/utils/ExternalApplicationsUtil.kt +++ b/vector/src/main/java/im/vector/app/core/utils/ExternalApplicationsUtil.kt @@ -297,6 +297,26 @@ fun openMedia(activity: Activity, savedMediaPath: String, mimeType: String) { } } +/** + * Open external location + * @param activity the activity + * @param latitude latitude of the location + * @param longitude longitude of the location + */ +fun openLocation(activity: Activity, latitude: Double, longitude: Double) { + val locationUri = buildString { + append("geo:") + append(latitude) + append(",") + append(longitude) + append("?q=") // This is required to drop a pin to the location + append(latitude) + append(",") + append(longitude) + } + openUri(activity, locationUri) +} + fun shareMedia(context: Context, file: File, mediaMimeType: String?) { val mediaUri = try { FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".fileProvider", file) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt index f20a32848c..ce04b6812d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt @@ -20,6 +20,7 @@ import android.net.Uri import android.view.View import im.vector.app.core.platform.VectorViewModelAction import im.vector.app.features.call.conference.ConferenceEvent +import im.vector.app.features.location.LocationData import org.matrix.android.sdk.api.session.content.ContentAttachmentData import org.matrix.android.sdk.api.session.room.model.message.MessageStickerContent import org.matrix.android.sdk.api.session.room.model.message.MessageWithAttachmentContent @@ -110,4 +111,7 @@ sealed class RoomDetailAction : VectorViewModelAction { // Poll data class EndPoll(val eventId: String) : RoomDetailAction() + + // Location + data class ShowLocation(val locationData: LocationData, val userId: String) : RoomDetailAction() } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index 22eeb7759b..d530b35c0c 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -465,6 +465,7 @@ class RoomDetailFragment @Inject constructor( RoomDetailViewEvents.StopChatEffects -> handleStopChatEffects() is RoomDetailViewEvents.DisplayAndAcceptCall -> acceptIncomingCall(it) RoomDetailViewEvents.RoomReplacementStarted -> handleRoomReplacement() + is RoomDetailViewEvents.ShowLocation -> handleShowLocationPreview(it) }.exhaustive } @@ -596,6 +597,17 @@ class RoomDetailFragment @Inject constructor( } } + private fun handleShowLocationPreview(viewEvent: RoomDetailViewEvents.ShowLocation) { + navigator + .openLocationSharing( + context = requireContext(), + roomId = roomDetailArgs.roomId, + mode = LocationSharingMode.PREVIEW, + initialLocationData = viewEvent.locationData, + locationOwnerId = viewEvent.userId + ) + } + private fun requestNativeWidgetPermission(it: RoomDetailViewEvents.RequestNativeWidgetPermission) { val tag = RoomWidgetPermissionBottomSheet::class.java.name val dFrag = childFragmentManager.findFragmentByTag(tag) as? RoomWidgetPermissionBottomSheet @@ -2221,7 +2233,14 @@ class RoomDetailFragment @Inject constructor( AttachmentTypeSelectorView.Type.STICKER -> roomDetailViewModel.handle(RoomDetailAction.SelectStickerAttachment) AttachmentTypeSelectorView.Type.POLL -> navigator.openCreatePoll(requireContext(), roomDetailArgs.roomId) AttachmentTypeSelectorView.Type.LOCATION -> { - navigator.openLocationSharing(requireContext(), roomDetailArgs.roomId, LocationSharingMode.STATIC_SHARING) + navigator + .openLocationSharing( + context = requireContext(), + roomId = roomDetailArgs.roomId, + mode = LocationSharingMode.STATIC_SHARING, + initialLocationData = null, + locationOwnerId = session.myUserId + ) } }.exhaustive } 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 2e7f2bfd63..de1110dfcb 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 @@ -20,6 +20,7 @@ import android.net.Uri import android.view.View import im.vector.app.core.platform.VectorViewEvents import im.vector.app.features.call.webrtc.WebRtcCall +import im.vector.app.features.location.LocationData import org.matrix.android.sdk.api.session.widgets.model.Widget import org.matrix.android.sdk.api.util.MatrixItem import org.matrix.android.sdk.internal.crypto.model.event.WithHeldCode @@ -81,4 +82,6 @@ sealed class RoomDetailViewEvents : VectorViewEvents { data class StartChatEffect(val type: ChatEffect) : RoomDetailViewEvents() object StopChatEffects : RoomDetailViewEvents() object RoomReplacementStarted : RoomDetailViewEvents() + + data class ShowLocation(val locationData: LocationData, val userId: String) : RoomDetailViewEvents() } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index f438c6e1e4..0c67c66af8 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -50,6 +50,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.LocationData import im.vector.app.features.powerlevel.PowerLevelsFlowFactory import im.vector.app.features.session.coroutineScope import im.vector.app.features.settings.VectorDataStore @@ -330,9 +331,14 @@ class RoomDetailViewModel @AssistedInject constructor( _viewEvents.post(RoomDetailViewEvents.OpenRoom(action.replacementRoomId, closeCurrentRoom = true)) } is RoomDetailAction.EndPoll -> handleEndPoll(action.eventId) + is RoomDetailAction.ShowLocation -> handleShowLocation(action.locationData, action.userId) }.exhaustive } + private fun handleShowLocation(locationData: LocationData, userId: String) { + _viewEvents.post(RoomDetailViewEvents.ShowLocation(locationData, userId)) + } + private fun handleJitsiCallJoinStatus(action: RoomDetailAction.UpdateJoinJitsiCallStatus) = withState { state -> if (state.jitsiState.confId == null) { // If jitsi widget is removed while on the call 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 22d282d567..76e8908d2d 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 @@ -37,6 +37,7 @@ import im.vector.app.features.home.room.detail.timeline.TimelineEventController 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 +import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvider 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 @@ -49,6 +50,8 @@ 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.MessageImageVideoItem_ import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData +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 @@ -67,6 +70,7 @@ import im.vector.app.features.html.EventHtmlRenderer import im.vector.app.features.html.PillsPostProcessor import im.vector.app.features.html.SpanUtils import im.vector.app.features.html.VectorHtmlCompressor +import im.vector.app.features.location.LocationData import im.vector.app.features.media.ImageContentRenderer import im.vector.app.features.media.VideoContentRenderer import me.gujun.android.span.span @@ -82,6 +86,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageContentWithF import org.matrix.android.sdk.api.session.room.model.message.MessageEmoteContent import org.matrix.android.sdk.api.session.room.model.message.MessageFileContent import org.matrix.android.sdk.api.session.room.model.message.MessageImageInfoContent +import org.matrix.android.sdk.api.session.room.model.message.MessageLocationContent import org.matrix.android.sdk.api.session.room.model.message.MessageNoticeContent import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent @@ -116,7 +121,8 @@ class MessageItemFactory @Inject constructor( private val pillsPostProcessorFactory: PillsPostProcessor.Factory, private val spanUtils: SpanUtils, private val session: Session, - private val voiceMessagePlaybackTracker: VoiceMessagePlaybackTracker) { + private val voiceMessagePlaybackTracker: VoiceMessagePlaybackTracker, + private val locationPinProvider: LocationPinProvider) { // TODO inject this properly? private var roomId: String = "" @@ -168,16 +174,36 @@ class MessageItemFactory @Inject constructor( } } is MessageVerificationRequestContent -> buildVerificationRequestMessageItem(messageContent, informationData, highlight, callback, attributes) - is MessagePollContent -> buildPollContent(messageContent, informationData, highlight, callback, attributes) + is MessagePollContent -> buildPollItem(messageContent, informationData, highlight, callback, attributes) + is MessageLocationContent -> buildLocationItem(messageContent, informationData, highlight, callback, attributes) else -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback, attributes) } } - private fun buildPollContent(pollContent: MessagePollContent, - informationData: MessageInformationData, - highlight: Boolean, - callback: TimelineEventController.Callback?, - attributes: AbsMessageItem.Attributes): PollItem? { + private fun buildLocationItem(locationContent: MessageLocationContent, + informationData: MessageInformationData, + highlight: Boolean, + callback: TimelineEventController.Callback?, + attributes: AbsMessageItem.Attributes): MessageLocationItem? { + + val geoUri = locationContent.locationInfo?.geoUri ?: locationContent.geoUri + val locationData = LocationData.create(geoUri) + + return MessageLocationItem_() + .attributes(attributes) + .locationData(locationData) + .userId(informationData.senderId) + .locationPinProvider(locationPinProvider) + .highlighted(highlight) + .leftGuideline(avatarSizeProvider.leftGuideline) + .callback(callback) + } + + private fun buildPollItem(pollContent: MessagePollContent, + informationData: MessageInformationData, + highlight: Boolean, + callback: TimelineEventController.Callback?, + attributes: AbsMessageItem.Attributes): PollItem? { val optionViewStates = mutableListOf() val pollResponseSummary = informationData.pollResponseAggregatedSummary diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/LocationPinProvider.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/LocationPinProvider.kt new file mode 100644 index 0000000000..92a4bb8473 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/LocationPinProvider.kt @@ -0,0 +1,74 @@ +/* + * 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.helper + +import android.content.Context +import android.graphics.drawable.Drawable +import android.graphics.drawable.LayerDrawable +import androidx.core.content.ContextCompat +import com.bumptech.glide.request.target.CustomTarget +import com.bumptech.glide.request.transition.Transition +import im.vector.app.R +import im.vector.app.core.glide.GlideApp +import im.vector.app.features.home.AvatarRenderer +import org.billcarsonfr.jsonviewer.Utils +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.util.toMatrixItem +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class LocationPinProvider @Inject constructor( + private val context: Context, + private val session: Session, + private val avatarRenderer: AvatarRenderer +) { + private val cache = mutableMapOf() + + private val glideRequests by lazy { + GlideApp.with(context) + } + + fun create(userId: String, callback: (Drawable) -> Unit) { + if (cache.contains(userId)) { + callback(cache[userId]!!) + return + } + + session.getUser(userId)?.toMatrixItem()?.let { + val size = Utils.dpToPx(44, context) + avatarRenderer.render(glideRequests, it, object : CustomTarget(size, size) { + override fun onResourceReady(resource: Drawable, transition: Transition?) { + val bgUserPin = ContextCompat.getDrawable(context, R.drawable.bg_map_user_pin)!! + val layerDrawable = LayerDrawable(arrayOf(bgUserPin, resource)) + val horizontalInset = Utils.dpToPx(4, context) + val topInset = Utils.dpToPx(4, context) + val bottomInset = Utils.dpToPx(8, context) + layerDrawable.setLayerInset(1, horizontalInset, topInset, horizontalInset, bottomInset) + + cache[userId] = layerDrawable + + callback(layerDrawable) + } + + override fun onLoadCleared(placeholder: Drawable?) { + // Is it possible? Put placeholder instead? + } + }) + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLocationItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLocationItem.kt new file mode 100644 index 0000000000..d4995d3fad --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLocationItem.kt @@ -0,0 +1,80 @@ +/* + * 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 androidx.constraintlayout.widget.ConstraintLayout +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import im.vector.app.R +import im.vector.app.core.epoxy.onClick +import im.vector.app.features.home.room.detail.RoomDetailAction +import im.vector.app.features.home.room.detail.timeline.TimelineEventController +import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvider +import im.vector.app.features.location.LocationData +import im.vector.app.features.location.MapTilerMapView +import im.vector.app.features.location.VectorMapListener + +@EpoxyModelClass(layout = R.layout.item_timeline_event_base) +abstract class MessageLocationItem : AbsMessageItem() { + + @EpoxyAttribute + var callback: TimelineEventController.Callback? = null + + @EpoxyAttribute + var locationData: LocationData? = null + + @EpoxyAttribute + var userId: String? = null + + @EpoxyAttribute + var locationPinProvider: LocationPinProvider? = null + + override fun bind(holder: Holder) { + super.bind(holder) + renderSendState(holder.mapViewContainer, null) + + val location = locationData ?: return + val locationOwnerId = userId ?: return + + holder.mapView.initialize(object : VectorMapListener { + override fun onMapReady() { + holder.mapView.zoomToLocation(location.latitude, location.longitude, INITIAL_ZOOM) + + locationPinProvider?.create(locationOwnerId) { pinDrawable -> + holder.mapView.addPinToMap(locationOwnerId, pinDrawable) + holder.mapView.updatePinLocation(locationOwnerId, location.latitude, location.longitude) + } + + holder.mapView.onClick { + callback?.onTimelineItemAction(RoomDetailAction.ShowLocation(location, locationOwnerId)) + } + } + }) + } + + override fun getViewType() = STUB_ID + + class Holder : AbsMessageItem.Holder(STUB_ID) { + val mapViewContainer by bind(R.id.mapViewContainer) + val mapView by bind(R.id.mapView) + } + + companion object { + private const val STUB_ID = R.id.messageContentLocationStub + private const val INITIAL_ZOOM = 15.0 + } +} diff --git a/vector/src/main/java/im/vector/app/features/location/LocationData.kt b/vector/src/main/java/im/vector/app/features/location/LocationData.kt index f85b8713d9..874e159e80 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationData.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationData.kt @@ -16,9 +16,56 @@ package im.vector.app.features.location +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +@Parcelize data class LocationData( val latitude: Double, val longitude: Double, val uncertainty: Double? -) +) : Parcelable { + + fun toGeoUri(): String { + return buildString { + append("geo:") + append(latitude) + append(",") + append(longitude) + append("?q=") + append(latitude) + append(",") + append(longitude) + } + } + + companion object { + + /** + * Creates location data from geo uri + * @param geoUri geo:latitude,longitude;uncertainty + * @return location data or null if geo uri is not valid + */ + fun create(geoUri: String): LocationData? { + val geoParts = geoUri + .split(":") + .takeIf { it.firstOrNull() == "geo" } + ?.getOrNull(1) + ?.split(",") + + val latitude = geoParts?.firstOrNull() + val geoTailParts = geoParts?.getOrNull(1)?.split(";") + val longitude = geoTailParts?.firstOrNull() + val uncertainty = geoTailParts?.getOrNull(1) + + return if (latitude != null && longitude != null) { + LocationData( + latitude = latitude.toDouble(), + longitude = longitude.toDouble(), + uncertainty = uncertainty?.toDouble() + ) + } else null + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/location/LocationPreviewFragment.kt b/vector/src/main/java/im/vector/app/features/location/LocationPreviewFragment.kt new file mode 100644 index 0000000000..e13456dbdc --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/location/LocationPreviewFragment.kt @@ -0,0 +1,85 @@ +/* + * 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.location + +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import android.view.LayoutInflater +import android.view.MenuItem +import android.view.View +import android.view.ViewGroup +import im.vector.app.core.platform.VectorBaseFragment +import im.vector.app.databinding.FragmentLocationPreviewBinding +import javax.inject.Inject +import com.airbnb.mvrx.args +import im.vector.app.R +import im.vector.app.core.utils.openLocation +import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvider +import org.matrix.android.sdk.api.extensions.tryOrNull + +class LocationPreviewFragment @Inject constructor( + private val locationPinProvider: LocationPinProvider +) : VectorBaseFragment(), VectorMapListener { + + private val args: LocationSharingArgs by args() + + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLocationPreviewBinding { + return FragmentLocationPreviewBinding.inflate(layoutInflater, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + views.mapView.initialize(this) + } + + override fun getMenuRes() = R.menu.menu_location_preview + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.share_external -> { + onShareLocationExternal() + return true + } + } + return super.onOptionsItemSelected(item) + } + + private fun onShareLocationExternal() { + val location = args.initialLocationData ?: return + openLocation(requireActivity(), location.latitude, location.longitude) + } + + override fun onMapReady() { + val location = args.initialLocationData ?: return + val userId = args.locationOwnerId + + locationPinProvider.create(userId) { pinDrawable -> + views.mapView.apply { + zoomToLocation(location.latitude, location.longitude, INITIAL_ZOOM) + deleteAllPins() + addPinToMap(userId, pinDrawable) + updatePinLocation(userId, location.latitude, location.longitude) + } + } + } + + companion object { + const val INITIAL_ZOOM = 15.0 + } +} diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingActivity.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingActivity.kt index fc685815db..6a200435dc 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingActivity.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingActivity.kt @@ -30,7 +30,9 @@ import kotlinx.parcelize.Parcelize @Parcelize data class LocationSharingArgs( val roomId: String, - val mode: LocationSharingMode + val mode: LocationSharingMode, + val initialLocationData: LocationData?, + val locationOwnerId: String ) : Parcelable @AndroidEntryPoint @@ -62,6 +64,11 @@ class LocationSharingActivity : VectorBaseActivity { + addFragment( + views.fragmentContainer, + LocationPreviewFragment::class.java, + locationSharingArgs + ) } } } 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 8cd86a2356..c7c4c0fed1 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 @@ -16,41 +16,24 @@ package im.vector.app.features.location -import android.graphics.drawable.Drawable -import android.graphics.drawable.LayerDrawable import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.core.content.ContextCompat import com.airbnb.mvrx.activityViewModel -import com.bumptech.glide.request.target.CustomTarget -import com.bumptech.glide.request.transition.Transition import com.google.android.material.dialog.MaterialAlertDialogBuilder -import com.mapbox.mapboxsdk.Mapbox -import com.mapbox.mapboxsdk.camera.CameraPosition -import com.mapbox.mapboxsdk.geometry.LatLng -import com.mapbox.mapboxsdk.maps.MapboxMap -import com.mapbox.mapboxsdk.maps.Style -import com.mapbox.mapboxsdk.plugins.annotation.SymbolManager -import com.mapbox.mapboxsdk.plugins.annotation.SymbolOptions -import com.mapbox.mapboxsdk.style.layers.Property -import im.vector.app.BuildConfig import im.vector.app.R -import im.vector.app.core.glide.GlideApp import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentLocationSharingBinding -import im.vector.app.features.home.AvatarRenderer -import org.billcarsonfr.jsonviewer.Utils +import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvider import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.api.util.toMatrixItem import javax.inject.Inject class LocationSharingFragment @Inject constructor( private val locationTracker: LocationTracker, private val session: Session, - private val avatarRenderer: AvatarRenderer -) : VectorBaseFragment(), LocationTracker.Callback { + private val locationPinProvider: LocationPinProvider +) : VectorBaseFragment(), LocationTracker.Callback, VectorMapListener { init { locationTracker.callback = this @@ -58,28 +41,16 @@ class LocationSharingFragment @Inject constructor( private val viewModel: LocationSharingViewModel by activityViewModel() - private val glideRequests by lazy { - GlideApp.with(this) - } - - private var map: MapboxMap? = null - private var symbolManager: SymbolManager? = null private var lastZoomValue: Double = -1.0 override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLocationSharingBinding { return FragmentLocationSharingBinding.inflate(inflater, container, false) } - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - // Initialize Mapbox before inflating mapView - Mapbox.getInstance(requireContext()) - } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - initMapView(savedInstanceState) + views.mapView.initialize(this) views.shareLocationContainer.debouncedClicks { viewModel.handle(LocationSharingAction.OnShareLocation) @@ -98,64 +69,23 @@ class LocationSharingFragment @Inject constructor( locationTracker.stop() } - private fun initMapView(savedInstanceState: Bundle?) { - val key = BuildConfig.mapTilerKey - val styleUrl = "https://api.maptiler.com/maps/streets/style.json?key=$key" - views.mapView.onCreate(savedInstanceState) - views.mapView.getMapAsync { map -> - map.setStyle(styleUrl) { style -> - addUserPinToMap(style) - this.symbolManager = SymbolManager(views.mapView, map, style) - this.map = map - // All set, start location tracker - locationTracker.start() - } - } - } - - private fun addUserPinToMap(style: Style) { - session.getUser(session.myUserId)?.toMatrixItem()?.let { - val size = Utils.dpToPx(44, requireContext()) - avatarRenderer.render(glideRequests, it, object : CustomTarget(size, size) { - override fun onResourceReady(resource: Drawable, transition: Transition?) { - val bgUserPin = ContextCompat.getDrawable(requireActivity(), R.drawable.bg_map_user_pin)!! - val layerDrawable = LayerDrawable(arrayOf(bgUserPin, resource)) - val horizontalInset = Utils.dpToPx(4, requireContext()) - val topInset = Utils.dpToPx(4, requireContext()) - val bottomInset = Utils.dpToPx(8, requireContext()) - layerDrawable.setLayerInset(1, horizontalInset, topInset, horizontalInset, bottomInset) - - style.addImage( - USER_PIN_NAME, - layerDrawable - ) - } - - override fun onLoadCleared(placeholder: Drawable?) { - // Is it possible? Put placeholder instead? - } - }) + override fun onMapReady() { + locationPinProvider.create(session.myUserId) { + views.mapView.addPinToMap( + pinId = USER_PIN_NAME, + image = it, + ) + // All set, start location tracker + locationTracker.start() } } override fun onLocationUpdate(locationData: LocationData) { - lastZoomValue = if (lastZoomValue == -1.0) INITIAL_ZOOM else map?.cameraPosition?.zoom ?: INITIAL_ZOOM + lastZoomValue = if (lastZoomValue == -1.0) INITIAL_ZOOM else views.mapView.getCurrentZoom() ?: INITIAL_ZOOM - val latLng = LatLng(locationData.latitude, locationData.longitude) - - map?.cameraPosition = CameraPosition.Builder() - .target(latLng) - .zoom(lastZoomValue) - .build() - - symbolManager?.deleteAll() - - symbolManager?.create( - SymbolOptions() - .withLatLng(latLng) - .withIconImage(USER_PIN_NAME) - .withIconAnchor(Property.ICON_ANCHOR_BOTTOM) - ) + views.mapView.zoomToLocation(locationData.latitude, locationData.longitude, lastZoomValue) + views.mapView.deleteAllPins() + views.mapView.updatePinLocation(USER_PIN_NAME, locationData.latitude, locationData.longitude) viewModel.handle(LocationSharingAction.OnLocationUpdate(locationData)) } diff --git a/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt b/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt index 93993245f8..0ea1e6810b 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt @@ -67,6 +67,7 @@ class LocationTracker @Inject constructor( fun stop() { locationManager?.removeUpdates(this) + callback = null } override fun onLocationChanged(location: Location) { diff --git a/vector/src/main/java/im/vector/app/features/location/MapTilerMapView.kt b/vector/src/main/java/im/vector/app/features/location/MapTilerMapView.kt new file mode 100644 index 0000000000..627b9e5ec3 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/location/MapTilerMapView.kt @@ -0,0 +1,92 @@ +/* + * 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.location + +import android.content.Context +import android.graphics.drawable.Drawable +import android.util.AttributeSet +import com.mapbox.mapboxsdk.Mapbox +import com.mapbox.mapboxsdk.camera.CameraPosition +import com.mapbox.mapboxsdk.geometry.LatLng +import com.mapbox.mapboxsdk.maps.MapView +import com.mapbox.mapboxsdk.maps.MapboxMap +import com.mapbox.mapboxsdk.maps.Style +import com.mapbox.mapboxsdk.plugins.annotation.SymbolManager +import com.mapbox.mapboxsdk.plugins.annotation.SymbolOptions +import com.mapbox.mapboxsdk.style.layers.Property +import im.vector.app.BuildConfig + +class MapTilerMapView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : MapView(context, attrs, defStyleAttr), VectorMapView { + + private var map: MapboxMap? = null + private var symbolManager: SymbolManager? = null + private var style: Style? = null + + override fun initialize(listener: VectorMapListener) { + getMapAsync { map -> + map.setStyle(styleUrl) { style -> + this.symbolManager = SymbolManager(this, map, style) + this.map = map + this.style = style + listener.onMapReady() + } + } + } + + override fun addPinToMap(pinId: String, image: Drawable) { + style?.addImage(pinId, image) + } + + override fun updatePinLocation(pinId: String, latitude: Double, longitude: Double) { + symbolManager?.create( + SymbolOptions() + .withLatLng(LatLng(latitude, longitude)) + .withIconImage(pinId) + .withIconAnchor(Property.ICON_ANCHOR_BOTTOM) + ) + } + + override fun deleteAllPins() { + symbolManager?.deleteAll() + } + + override fun zoomToLocation(latitude: Double, longitude: Double, zoom: Double) { + map?.cameraPosition = CameraPosition.Builder() + .target(LatLng(latitude, longitude)) + .zoom(zoom) + .build() + } + + override fun getCurrentZoom(): Double? { + return map?.cameraPosition?.zoom + } + + override fun onClick(callback: () -> Unit) { + map?.addOnMapClickListener { + callback() + true + } + } + + companion object { + private const val styleUrl = "https://api.maptiler.com/maps/streets/style.json?key=${BuildConfig.mapTilerKey}" + } +} diff --git a/vector/src/main/java/im/vector/app/features/location/VectorMapView.kt b/vector/src/main/java/im/vector/app/features/location/VectorMapView.kt new file mode 100644 index 0000000000..5dbeced175 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/location/VectorMapView.kt @@ -0,0 +1,36 @@ +/* + * 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.location + +import android.graphics.drawable.Drawable + +interface VectorMapListener { + fun onMapReady() +} + +interface VectorMapView { + fun initialize(listener: VectorMapListener) + + fun addPinToMap(pinId: String, image: Drawable) + fun updatePinLocation(pinId: String, latitude: Double, longitude: Double) + fun deleteAllPins() + + fun zoomToLocation(latitude: Double, longitude: Double, zoom: Double) + fun getCurrentZoom(): Double? + + fun onClick(callback: () -> Unit) +} diff --git a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt index 16c50daf94..fb923dabb2 100644 --- a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt +++ b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt @@ -57,6 +57,7 @@ import im.vector.app.features.home.room.detail.search.SearchActivity import im.vector.app.features.home.room.detail.search.SearchArgs import im.vector.app.features.home.room.filtered.FilteredRoomsActivity import im.vector.app.features.invite.InviteUsersToRoomActivity +import im.vector.app.features.location.LocationData import im.vector.app.features.location.LocationSharingActivity import im.vector.app.features.location.LocationSharingArgs import im.vector.app.features.location.LocationSharingMode @@ -536,10 +537,14 @@ class DefaultNavigator @Inject constructor( context.startActivity(intent) } - override fun openLocationSharing(context: Context, roomId: String, mode: LocationSharingMode) { + override fun openLocationSharing(context: Context, + roomId: String, + mode: LocationSharingMode, + initialLocationData: LocationData?, + locationOwnerId: String) { val intent = LocationSharingActivity.getIntent( context, - LocationSharingArgs(roomId = roomId, mode = mode) + LocationSharingArgs(roomId = roomId, mode = mode, initialLocationData = initialLocationData, locationOwnerId = locationOwnerId) ) context.startActivity(intent) } diff --git a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt index 2668e10694..06f38b381d 100644 --- a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt +++ b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt @@ -25,6 +25,7 @@ import androidx.activity.result.ActivityResultLauncher import androidx.core.util.Pair import im.vector.app.features.crypto.recover.SetupMode import im.vector.app.features.displayname.getBestName +import im.vector.app.features.location.LocationData import im.vector.app.features.location.LocationSharingMode import im.vector.app.features.login.LoginConfig import im.vector.app.features.media.AttachmentData @@ -151,5 +152,9 @@ interface Navigator { fun openCreatePoll(context: Context, roomId: String) - fun openLocationSharing(context: Context, roomId: String, mode: LocationSharingMode) + fun openLocationSharing(context: Context, + roomId: String, + mode: LocationSharingMode, + initialLocationData: LocationData?, + locationOwnerId: String) } diff --git a/vector/src/main/res/drawable/ic_share_external.xml b/vector/src/main/res/drawable/ic_share_external.xml new file mode 100644 index 0000000000..c4b78c8a83 --- /dev/null +++ b/vector/src/main/res/drawable/ic_share_external.xml @@ -0,0 +1,5 @@ + + + diff --git a/vector/src/main/res/layout/fragment_location_preview.xml b/vector/src/main/res/layout/fragment_location_preview.xml new file mode 100644 index 0000000000..ae3e8f1778 --- /dev/null +++ b/vector/src/main/res/layout/fragment_location_preview.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/layout/fragment_location_sharing.xml b/vector/src/main/res/layout/fragment_location_sharing.xml index bc587dc182..8122b5def5 100644 --- a/vector/src/main/res/layout/fragment_location_sharing.xml +++ b/vector/src/main/res/layout/fragment_location_sharing.xml @@ -4,7 +4,7 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - 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 5a04acf677..6360b287d0 100644 --- a/vector/src/main/res/layout/item_timeline_event_base.xml +++ b/vector/src/main/res/layout/item_timeline_event_base.xml @@ -130,6 +130,11 @@ style="@style/TimelineContentStubBaseParams" android:layout="@layout/item_timeline_event_poll" /> + + + + + + + diff --git a/vector/src/main/res/menu/menu_location_preview.xml b/vector/src/main/res/menu/menu_location_preview.xml new file mode 100644 index 0000000000..2616674b8a --- /dev/null +++ b/vector/src/main/res/menu/menu_location_preview.xml @@ -0,0 +1,12 @@ + + + + + + \ 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 086469a1fd..e5fe5ad5c7 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -3710,4 +3710,5 @@ Share location Element could not access your location Element could not access your location. Please try again later. + Open with From ccd4396336927dc165374743e60e3ef285121788 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Mon, 27 Dec 2021 14:04:36 +0300 Subject: [PATCH 141/632] Allow to reply location messages. --- .../BottomSheetMessagePreviewItem.kt | 27 +++++++++++++++++++ .../action/MessageActionsEpoxyController.kt | 13 ++++++++- .../action/MessageActionsViewModel.kt | 5 ++-- .../item_bottom_sheet_message_preview.xml | 13 +++++++++ 4 files changed, 55 insertions(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetMessagePreviewItem.kt b/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetMessagePreviewItem.kt index eef057efd4..efa4702532 100644 --- a/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetMessagePreviewItem.kt +++ b/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetMessagePreviewItem.kt @@ -30,10 +30,16 @@ import im.vector.app.core.epoxy.onClick import im.vector.app.core.epoxy.util.preventMutation import im.vector.app.core.extensions.setTextOrHide import im.vector.app.features.home.AvatarRenderer +import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvider import im.vector.app.features.home.room.detail.timeline.item.BindingOptions import im.vector.app.features.home.room.detail.timeline.tools.findPillsAndProcess +import im.vector.app.features.location.LocationData +import im.vector.app.features.location.MapTilerMapView +import im.vector.app.features.location.VectorMapListener import im.vector.app.features.media.ImageContentRenderer import org.matrix.android.sdk.api.extensions.orFalse +import org.matrix.android.sdk.api.session.room.model.message.LocationInfo +import org.matrix.android.sdk.api.session.room.model.message.MessageLocationContent import org.matrix.android.sdk.api.util.MatrixItem /** @@ -66,6 +72,12 @@ abstract class BottomSheetMessagePreviewItem : VectorEpoxyModel + holder.mapView.initialize(object : VectorMapListener { + override fun onMapReady() { + holder.mapView.zoomToLocation(location.latitude, location.longitude, 15.0) + locationPinProvider?.create(matrixItem.id) { pinDrawable -> + holder.mapView.addPinToMap(matrixItem.id, pinDrawable) + holder.mapView.updatePinLocation(matrixItem.id, location.latitude, location.longitude) + } + } + }) + } } override fun unbind(holder: Holder) { @@ -105,5 +131,6 @@ abstract class BottomSheetMessagePreviewItem : VectorEpoxyModel(R.id.bottom_sheet_message_preview_body_details) val timestamp by bind(R.id.bottom_sheet_message_preview_timestamp) val imagePreview by bind(R.id.bottom_sheet_message_preview_image) + val mapView by bind(R.id.bottom_sheet_message_preview_location) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt index 3826c4cbad..0bd4f1bf21 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt @@ -33,14 +33,19 @@ import im.vector.app.core.utils.DimensionConverter import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.room.detail.timeline.TimelineEventController import im.vector.app.features.home.room.detail.timeline.format.EventDetailsFormatter +import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvider import im.vector.app.features.home.room.detail.timeline.image.buildImageContentRendererData import im.vector.app.features.home.room.detail.timeline.item.E2EDecoration import im.vector.app.features.home.room.detail.timeline.tools.createLinkMovementMethod import im.vector.app.features.home.room.detail.timeline.tools.linkify import im.vector.app.features.html.SpanUtils +import im.vector.app.features.location.LocationData import im.vector.app.features.media.ImageContentRenderer import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.failure.Failure +import org.matrix.android.sdk.api.session.events.model.toContent +import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.room.model.message.MessageLocationContent import org.matrix.android.sdk.api.session.room.send.SendState import javax.inject.Inject @@ -56,7 +61,8 @@ class MessageActionsEpoxyController @Inject constructor( private val errorFormatter: ErrorFormatter, private val spanUtils: SpanUtils, private val eventDetailsFormatter: EventDetailsFormatter, - private val dateFormatter: VectorDateFormatter + private val dateFormatter: VectorDateFormatter, + private val locationPinProvider: LocationPinProvider ) : TypedEpoxyController() { var listener: MessageActionsEpoxyControllerListener? = null @@ -68,6 +74,9 @@ class MessageActionsEpoxyController @Inject constructor( val formattedDate = dateFormatter.format(date, DateFormatKind.MESSAGE_DETAIL) val body = state.messageBody.linkify(host.listener) val bindingOptions = spanUtils.getBindingOptions(body) + val locationData = state.timelineEvent()?.root?.getClearContent()?.toModel(catchError = true)?.let { + LocationData.create(it.locationInfo?.geoUri ?: it.geoUri) + } bottomSheetMessagePreviewItem { id("preview") avatarRenderer(host.avatarRenderer) @@ -80,6 +89,8 @@ class MessageActionsEpoxyController @Inject constructor( body(body) bodyDetails(host.eventDetailsFormatter.format(state.timelineEvent()?.root)) time(formattedDate) + locationData(locationData) + locationPinProvider(host.locationPinProvider) } // Send state 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 ff7d555ee3..2b3d391634 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 @@ -424,8 +424,9 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted MessageType.MSGTYPE_VIDEO, MessageType.MSGTYPE_AUDIO, MessageType.MSGTYPE_FILE, - MessageType.MSGTYPE_POLL_START -> true - else -> false + MessageType.MSGTYPE_POLL_START, + MessageType.MSGTYPE_LOCATION -> true + else -> false } } diff --git a/vector/src/main/res/layout/item_bottom_sheet_message_preview.xml b/vector/src/main/res/layout/item_bottom_sheet_message_preview.xml index 771d4d10f0..1959cfc3ba 100644 --- a/vector/src/main/res/layout/item_bottom_sheet_message_preview.xml +++ b/vector/src/main/res/layout/item_bottom_sheet_message_preview.xml @@ -103,4 +103,17 @@ tools:text="1080 x 1024 - 43s - 12kB" tools:visibility="visible" /> + + From de809d6013098011b7da929d5c2c9380d9a2b736 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Mon, 27 Dec 2021 17:17:20 +0300 Subject: [PATCH 142/632] Add settings item to allow location sharing. --- .../app/features/home/room/detail/RoomDetailFragment.kt | 1 + .../im/vector/app/features/settings/VectorPreferences.kt | 7 +++++++ vector/src/main/res/values/strings.xml | 1 + vector/src/main/res/xml/vector_settings_preferences.xml | 5 +++++ 4 files changed, 14 insertions(+) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index d530b35c0c..25bd5877f5 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -1379,6 +1379,7 @@ class RoomDetailFragment @Inject constructor( if (!::attachmentTypeSelector.isInitialized) { attachmentTypeSelector = AttachmentTypeSelectorView(vectorBaseActivity, vectorBaseActivity.layoutInflater, this@RoomDetailFragment) attachmentTypeSelector.setAttachmentVisibility(AttachmentTypeSelectorView.Type.POLL, vectorPreferences.labsEnablePolls()) + attachmentTypeSelector.setAttachmentVisibility(AttachmentTypeSelectorView.Type.LOCATION, vectorPreferences.isLocationSharingEnabled()) } attachmentTypeSelector.show(views.composerLayout.views.attachmentButton, keyboardStateUtils.isKeyboardShowing) } 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 64561cbc12..c367d628bd 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 @@ -185,6 +185,9 @@ class VectorPreferences @Inject constructor(private val context: Context) { private const val DID_ASK_TO_ENABLE_SESSION_PUSH = "DID_ASK_TO_ENABLE_SESSION_PUSH" private const val DID_PROMOTE_NEW_RESTRICTED_JOIN_RULE = "DID_PROMOTE_NEW_RESTRICTED_JOIN_RULE" + // Location Sharing + private const val SETTINGS_PREF_ENABLE_LOCATION_SHARING = "SETTINGS_PREF_ENABLE_LOCATION_SHARING" + private const val MEDIA_SAVING_3_DAYS = 0 private const val MEDIA_SAVING_1_WEEK = 1 private const val MEDIA_SAVING_1_MONTH = 2 @@ -990,4 +993,8 @@ class VectorPreferences @Inject constructor(private val context: Context) { fun labsEnablePolls(): Boolean { return defaultPrefs.getBoolean(SETTINGS_LABS_ENABLE_POLLS, false) } + + fun isLocationSharingEnabled(): Boolean { + return defaultPrefs.getBoolean(SETTINGS_PREF_ENABLE_LOCATION_SHARING, true) + } } diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index e5fe5ad5c7..1b4d137928 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -3711,4 +3711,5 @@ Element could not access your location Element could not access your location. Please try again later. Open with + Enable location sharing diff --git a/vector/src/main/res/xml/vector_settings_preferences.xml b/vector/src/main/res/xml/vector_settings_preferences.xml index 14c7dc7b80..bc9e06f754 100644 --- a/vector/src/main/res/xml/vector_settings_preferences.xml +++ b/vector/src/main/res/xml/vector_settings_preferences.xml @@ -28,6 +28,11 @@ android:persistent="false" android:title="@string/font_size" /> + + From 15fa42ba9b367057d4382f247fc8066a484cca5b Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Mon, 27 Dec 2021 17:55:48 +0300 Subject: [PATCH 143/632] Lint fixes. --- .../epoxy/bottomsheet/BottomSheetMessagePreviewItem.kt | 2 -- .../timeline/action/MessageActionsEpoxyController.kt | 1 - .../room/detail/timeline/factory/MessageItemFactory.kt | 1 - .../java/im/vector/app/features/location/LocationData.kt | 1 - .../app/features/location/LocationPreviewFragment.kt | 9 +++------ .../im/vector/app/features/location/LocationTracker.kt | 3 +++ .../im/vector/app/features/location/MapTilerMapView.kt | 1 - 7 files changed, 6 insertions(+), 12 deletions(-) diff --git a/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetMessagePreviewItem.kt b/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetMessagePreviewItem.kt index ae9efe367a..7c18dd818e 100644 --- a/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetMessagePreviewItem.kt +++ b/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetMessagePreviewItem.kt @@ -39,8 +39,6 @@ import im.vector.app.features.location.MapTilerMapView import im.vector.app.features.location.VectorMapListener import im.vector.app.features.media.ImageContentRenderer import org.matrix.android.sdk.api.extensions.orFalse -import org.matrix.android.sdk.api.session.room.model.message.LocationInfo -import org.matrix.android.sdk.api.session.room.model.message.MessageLocationContent import org.matrix.android.sdk.api.util.MatrixItem /** diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt index 0bd4f1bf21..3e1edfa158 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt @@ -43,7 +43,6 @@ import im.vector.app.features.location.LocationData import im.vector.app.features.media.ImageContentRenderer import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.failure.Failure -import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.message.MessageLocationContent import org.matrix.android.sdk.api.session.room.send.SendState 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 b0df2d6a54..607e4c1252 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 @@ -185,7 +185,6 @@ class MessageItemFactory @Inject constructor( highlight: Boolean, callback: TimelineEventController.Callback?, attributes: AbsMessageItem.Attributes): MessageLocationItem? { - val geoUri = locationContent.locationInfo?.geoUri ?: locationContent.geoUri val locationData = LocationData.create(geoUri) diff --git a/vector/src/main/java/im/vector/app/features/location/LocationData.kt b/vector/src/main/java/im/vector/app/features/location/LocationData.kt index 874e159e80..b790bde710 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationData.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationData.kt @@ -68,4 +68,3 @@ data class LocationData( } } } - diff --git a/vector/src/main/java/im/vector/app/features/location/LocationPreviewFragment.kt b/vector/src/main/java/im/vector/app/features/location/LocationPreviewFragment.kt index e13456dbdc..1a7ed49209 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationPreviewFragment.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationPreviewFragment.kt @@ -16,21 +16,18 @@ package im.vector.app.features.location -import android.content.Intent -import android.net.Uri import android.os.Bundle import android.view.LayoutInflater import android.view.MenuItem import android.view.View import android.view.ViewGroup -import im.vector.app.core.platform.VectorBaseFragment -import im.vector.app.databinding.FragmentLocationPreviewBinding -import javax.inject.Inject import com.airbnb.mvrx.args import im.vector.app.R +import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.utils.openLocation +import im.vector.app.databinding.FragmentLocationPreviewBinding import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvider -import org.matrix.android.sdk.api.extensions.tryOrNull +import javax.inject.Inject class LocationPreviewFragment @Inject constructor( private val locationPinProvider: LocationPinProvider diff --git a/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt b/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt index 0ea1e6810b..6e55735f9b 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt @@ -16,10 +16,12 @@ package im.vector.app.features.location +import android.Manifest import android.content.Context import android.location.Location import android.location.LocationListener import android.location.LocationManager +import androidx.annotation.RequiresPermission import timber.log.Timber import javax.inject.Inject @@ -65,6 +67,7 @@ class LocationTracker @Inject constructor( } } + @RequiresPermission(anyOf = [Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION]) fun stop() { locationManager?.removeUpdates(this) callback = null diff --git a/vector/src/main/java/im/vector/app/features/location/MapTilerMapView.kt b/vector/src/main/java/im/vector/app/features/location/MapTilerMapView.kt index 627b9e5ec3..386e96988f 100644 --- a/vector/src/main/java/im/vector/app/features/location/MapTilerMapView.kt +++ b/vector/src/main/java/im/vector/app/features/location/MapTilerMapView.kt @@ -19,7 +19,6 @@ package im.vector.app.features.location import android.content.Context import android.graphics.drawable.Drawable import android.util.AttributeSet -import com.mapbox.mapboxsdk.Mapbox import com.mapbox.mapboxsdk.camera.CameraPosition import com.mapbox.mapboxsdk.geometry.LatLng import com.mapbox.mapboxsdk.maps.MapView From d632d54b6d472d718038f8b3c8b76ef1e33c1555 Mon Sep 17 00:00:00 2001 From: Szimszon Date: Sun, 26 Dec 2021 21:12:32 +0000 Subject: [PATCH 144/632] Translated using Weblate (Hungarian) Currently translated at 100.0% (2725 of 2725 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 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vector/src/main/res/values-hu/strings.xml b/vector/src/main/res/values-hu/strings.xml index b67c06bcfd..525d355108 100644 --- a/vector/src/main/res/values-hu/strings.xml +++ b/vector/src/main/res/values-hu/strings.xml @@ -3081,4 +3081,6 @@ Ha nem te állítottad be a visszaállítási metódust, akkor egy támadó pró \n \nElolvashatod a feltételeinket %s. Segíts az Element-et jobbá tenni + nyerő válasz + Jogi dolgok \ No newline at end of file From 7661d217ee49b34dade41ed90ccac696569c787b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Dec 2021 23:06:36 +0000 Subject: [PATCH 145/632] Bump org.jlleitschuh.gradle.ktlint from 10.2.0 to 10.2.1 Bumps org.jlleitschuh.gradle.ktlint from 10.2.0 to 10.2.1. --- updated-dependencies: - dependency-name: org.jlleitschuh.gradle.ktlint dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index e17f357905..255d9da849 100644 --- a/build.gradle +++ b/build.gradle @@ -29,7 +29,7 @@ buildscript { // ktlint Plugin plugins { - id "org.jlleitschuh.gradle.ktlint" version "10.2.0" + id "org.jlleitschuh.gradle.ktlint" version "10.2.1" } allprojects { From fc4d18f84e2f51cfb05cb10e3d28730439c49125 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Tue, 28 Dec 2021 14:00:37 +0300 Subject: [PATCH 146/632] Dummy maptiler key added for CI. --- vector/build.gradle | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vector/build.gradle b/vector/build.gradle index 562e0af679..2f10695c2f 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -150,6 +150,8 @@ android { // This *must* only be set in trusted environments. buildConfigField "Boolean", "handleCallAssertedIdentityEvents", "false" + buildConfigField "String", "mapTilerKey", "\"DUMMY_KEY\"" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" // Keep abiFilter for the universalApk From 6e1911e686a402c8503b0377bbda3bbd65aee4dc Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Tue, 28 Dec 2021 17:04:51 +0300 Subject: [PATCH 147/632] Lint fixes. --- .../timeline/factory/MessageItemFactory.kt | 14 ++++++++- .../timeline/item/MessageLocationItem.kt | 29 +++++++++++-------- .../app/features/location/LocationTracker.kt | 3 +- 3 files changed, 32 insertions(+), 14 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 607e4c1252..e293b1ecbd 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 @@ -33,6 +33,7 @@ import im.vector.app.core.resources.ColorProvider 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.RoomDetailAction import im.vector.app.features.home.room.detail.timeline.TimelineEventController import im.vector.app.features.home.room.detail.timeline.helper.AvatarSizeProvider import im.vector.app.features.home.room.detail.timeline.helper.ContentDownloadStateTrackerBinder @@ -71,6 +72,7 @@ import im.vector.app.features.html.PillsPostProcessor import im.vector.app.features.html.SpanUtils import im.vector.app.features.html.VectorHtmlCompressor import im.vector.app.features.location.LocationData +import im.vector.app.features.location.VectorMapView import im.vector.app.features.media.ImageContentRenderer import im.vector.app.features.media.VideoContentRenderer import me.gujun.android.span.span @@ -188,6 +190,16 @@ class MessageItemFactory @Inject constructor( val geoUri = locationContent.locationInfo?.geoUri ?: locationContent.geoUri val locationData = LocationData.create(geoUri) + val mapCallback: MessageLocationItem.Callback = object: MessageLocationItem.Callback { + override fun onMapReady(mapView: VectorMapView) { + mapView.onClick { + locationData?.let { + callback?.onTimelineItemAction(RoomDetailAction.ShowLocation(it, informationData.senderId)) + } + } + } + } + return MessageLocationItem_() .attributes(attributes) .locationData(locationData) @@ -195,7 +207,7 @@ class MessageItemFactory @Inject constructor( .locationPinProvider(locationPinProvider) .highlighted(highlight) .leftGuideline(avatarSizeProvider.leftGuideline) - .callback(callback) + .callback(mapCallback) } private fun buildPollItem(pollContent: MessagePollContent, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLocationItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLocationItem.kt index d4995d3fad..2d65fd3131 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLocationItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLocationItem.kt @@ -27,12 +27,17 @@ import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvid import im.vector.app.features.location.LocationData import im.vector.app.features.location.MapTilerMapView import im.vector.app.features.location.VectorMapListener +import im.vector.app.features.location.VectorMapView @EpoxyModelClass(layout = R.layout.item_timeline_event_base) abstract class MessageLocationItem : AbsMessageItem() { + interface Callback { + fun onMapReady(mapView: VectorMapView) + } + @EpoxyAttribute - var callback: TimelineEventController.Callback? = null + var callback: Callback? = null @EpoxyAttribute var locationData: LocationData? = null @@ -50,20 +55,20 @@ abstract class MessageLocationItem : AbsMessageItem( val location = locationData ?: return val locationOwnerId = userId ?: return - holder.mapView.initialize(object : VectorMapListener { - override fun onMapReady() { - holder.mapView.zoomToLocation(location.latitude, location.longitude, INITIAL_ZOOM) + holder.mapView.apply { + initialize(object : VectorMapListener { + override fun onMapReady() { + zoomToLocation(location.latitude, location.longitude, INITIAL_ZOOM) - locationPinProvider?.create(locationOwnerId) { pinDrawable -> - holder.mapView.addPinToMap(locationOwnerId, pinDrawable) - holder.mapView.updatePinLocation(locationOwnerId, location.latitude, location.longitude) - } + locationPinProvider?.create(locationOwnerId) { pinDrawable -> + addPinToMap(locationOwnerId, pinDrawable) + updatePinLocation(locationOwnerId, location.latitude, location.longitude) + } - holder.mapView.onClick { - callback?.onTimelineItemAction(RoomDetailAction.ShowLocation(location, locationOwnerId)) + callback?.onMapReady(this@apply) } - } - }) + }) + } } override fun getViewType() = STUB_ID diff --git a/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt b/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt index 6e55735f9b..48692ef216 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt @@ -24,6 +24,7 @@ import android.location.LocationManager import androidx.annotation.RequiresPermission import timber.log.Timber import javax.inject.Inject +import androidx.core.content.getSystemService class LocationTracker @Inject constructor( private val context: Context) : LocationListener { @@ -36,7 +37,7 @@ class LocationTracker @Inject constructor( var callback: Callback? = null fun start() { - val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as? LocationManager + val locationManager = context.getSystemService() locationManager?.let { val isGpsEnabled = it.isProviderEnabled(LocationManager.GPS_PROVIDER) From 3abba1932f3ce040cc85750e3cf55de2e8d83673 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 30 Dec 2021 10:28:52 +0100 Subject: [PATCH 148/632] Workaround to fetch all the pending toDevice events from a Synapse homeserver (#4614) Workaround to fetch all the pending toDevice events from a Synapse homeserver Co-authored-by: Valere --- changelog.d/4612.misc | 1 + .../internal/crypto/DefaultCryptoService.kt | 12 +++- .../internal/session/sync/job/SyncThread.kt | 20 ++++-- .../internal/session/sync/job/SyncWorker.kt | 71 +++++++++++++++---- 4 files changed, 83 insertions(+), 21 deletions(-) create mode 100644 changelog.d/4612.misc diff --git a/changelog.d/4612.misc b/changelog.d/4612.misc new file mode 100644 index 0000000000..43b5007b7e --- /dev/null +++ b/changelog.d/4612.misc @@ -0,0 +1 @@ +Workaround to fetch all the pending toDevice events from a Synapse homeserver \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt index 7d9c351410..9dd369f426 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt @@ -429,7 +429,17 @@ internal class DefaultCryptoService @Inject constructor( val currentCount = syncResponse.deviceOneTimeKeysCount.signedCurve25519 ?: 0 oneTimeKeysUploader.updateOneTimeKeyCount(currentCount) } - if (isStarted()) { + // There is a limit of to_device events returned per sync. + // If we are in a case of such limited to_device sync we can't try to generate/upload + // new otk now, because there might be some pending olm pre-key to_device messages that would fail if we rotate + // the old otk too early. In this case we want to wait for the pending to_device before doing anything + // As per spec: + // If there is a large queue of send-to-device messages, the server should limit the number sent in each /sync response. + // 100 messages is recommended as a reasonable limit. + // The limit is not part of the spec, so it's probably safer to handle that when there are no more to_device ( so we are sure + // that there are no pending to_device + val toDevices = syncResponse.toDevice?.events.orEmpty() + if (isStarted() && toDevices.isEmpty()) { // Make sure we process to-device messages before generating new one-time-keys #2782 deviceListManager.refreshOutdatedDeviceLists() // The presence of device_unused_fallback_key_types indicates that the server supports fallback keys. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncThread.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncThread.kt index 3faa0c9488..b6ea7a68f7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncThread.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncThread.kt @@ -30,6 +30,7 @@ import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking +import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.isTokenError import org.matrix.android.sdk.api.logger.LoggerTag @@ -71,6 +72,7 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask, private var isStarted = false private var isTokenValid = true private var retryNoNetworkTask: TimerTask? = null + private var previousSyncResponseHasToDevice = false private val activeCallListObserver = Observer> { activeCalls -> if (activeCalls.isEmpty() && backgroundDetectionObserver.isInBackground) { @@ -171,12 +173,15 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask, if (state !is SyncState.Running) { updateStateTo(SyncState.Running(afterPause = true)) } - // No timeout after a pause - val timeout = state.let { if (it is SyncState.Running && it.afterPause) 0 else DEFAULT_LONG_POOL_TIMEOUT } + val timeout = when { + previousSyncResponseHasToDevice -> 0L /* Force timeout to 0 */ + state.let { it is SyncState.Running && it.afterPause } -> 0L /* No timeout after a pause */ + else -> DEFAULT_LONG_POOL_TIMEOUT + } Timber.tag(loggerTag.value).d("Execute sync request with timeout $timeout") val params = SyncTask.Params(timeout, SyncPresence.Online) val sync = syncScope.launch { - doSync(params) + previousSyncResponseHasToDevice = doSync(params) } runBlocking { sync.join() @@ -203,10 +208,14 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask, } } - private suspend fun doSync(params: SyncTask.Params) { - try { + /** + * Will return true if the sync response contains some toDevice events. + */ + private suspend fun doSync(params: SyncTask.Params): Boolean { + return try { val syncResponse = syncTask.execute(params) _syncFlow.emit(syncResponse) + syncResponse.toDevice?.events?.isNotEmpty().orFalse() } catch (failure: Throwable) { if (failure is Failure.NetworkConnection) { canReachServer = false @@ -229,6 +238,7 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask, delay(RETRY_WAIT_TIME_MS) } } + false } finally { state.let { if (it is SyncState.Running && it.afterPause) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncWorker.kt index 763cd55714..2f1241f4d8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncWorker.kt @@ -20,6 +20,7 @@ import androidx.work.BackoffPolicy import androidx.work.ExistingWorkPolicy import androidx.work.WorkerParameters import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.failure.isTokenError import org.matrix.android.sdk.internal.SessionManager import org.matrix.android.sdk.internal.di.WorkManagerProvider @@ -34,8 +35,8 @@ import timber.log.Timber import java.util.concurrent.TimeUnit import javax.inject.Inject -private const val DEFAULT_LONG_POOL_TIMEOUT = 6L -private const val DEFAULT_DELAY_TIMEOUT = 30_000L +private const val DEFAULT_LONG_POOL_TIMEOUT_SECONDS = 6L +private const val DEFAULT_DELAY_MILLIS = 30_000L /** * Possible previous worker: None @@ -47,9 +48,12 @@ internal class SyncWorker(context: Context, workerParameters: WorkerParameters, @JsonClass(generateAdapter = true) internal data class Params( override val sessionId: String, - val timeout: Long = DEFAULT_LONG_POOL_TIMEOUT, - val delay: Long = DEFAULT_DELAY_TIMEOUT, + // In seconds + val timeout: Long = DEFAULT_LONG_POOL_TIMEOUT_SECONDS, + // In milliseconds + val delay: Long = DEFAULT_DELAY_MILLIS, val periodic: Boolean = false, + val forceImmediate: Boolean = false, override val lastFailureMessage: String? = null ) : SessionWorkerParams @@ -65,13 +69,26 @@ internal class SyncWorker(context: Context, workerParameters: WorkerParameters, Timber.i("Sync work starting") return runCatching { - doSync(params.timeout) + doSync(if (params.forceImmediate) 0 else params.timeout) }.fold( - { + { hasToDeviceEvents -> Result.success().also { if (params.periodic) { - // we want to schedule another one after delay - automaticallyBackgroundSync(workManagerProvider, params.sessionId, params.timeout, params.delay) + // we want to schedule another one after a delay, or immediately if hasToDeviceEvents + automaticallyBackgroundSync( + workManagerProvider = workManagerProvider, + sessionId = params.sessionId, + serverTimeoutInSeconds = params.timeout, + delayInSeconds = params.delay, + forceImmediate = hasToDeviceEvents + ) + } else if (hasToDeviceEvents) { + // Previous response has toDevice events, request an immediate sync request + requireBackgroundSync( + workManagerProvider = workManagerProvider, + sessionId = params.sessionId, + serverTimeoutInSeconds = 0 + ) } } }, @@ -92,16 +109,29 @@ internal class SyncWorker(context: Context, workerParameters: WorkerParameters, return params.copy(lastFailureMessage = params.lastFailureMessage ?: message) } - private suspend fun doSync(timeout: Long) { + /** + * Will return true if the sync response contains some toDevice events. + */ + private suspend fun doSync(timeout: Long): Boolean { val taskParams = SyncTask.Params(timeout * 1000, SyncPresence.Offline) - syncTask.execute(taskParams) + val syncResponse = syncTask.execute(taskParams) + return syncResponse.toDevice?.events?.isNotEmpty().orFalse() } companion object { private const val BG_SYNC_WORK_NAME = "BG_SYNCP" - fun requireBackgroundSync(workManagerProvider: WorkManagerProvider, sessionId: String, serverTimeout: Long = 0) { - val data = WorkerParamsFactory.toData(Params(sessionId, serverTimeout, 0L, false)) + fun requireBackgroundSync(workManagerProvider: WorkManagerProvider, + sessionId: String, + serverTimeoutInSeconds: Long = 0) { + val data = WorkerParamsFactory.toData( + Params( + sessionId = sessionId, + timeout = serverTimeoutInSeconds, + delay = 0L, + periodic = false + ) + ) val workRequest = workManagerProvider.matrixOneTimeWorkRequestBuilder() .setConstraints(WorkManagerProvider.workConstraints) .setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY_MILLIS, TimeUnit.MILLISECONDS) @@ -111,13 +141,24 @@ internal class SyncWorker(context: Context, workerParameters: WorkerParameters, .enqueueUniqueWork(BG_SYNC_WORK_NAME, ExistingWorkPolicy.APPEND_OR_REPLACE, workRequest) } - fun automaticallyBackgroundSync(workManagerProvider: WorkManagerProvider, sessionId: String, serverTimeout: Long = 0, delayInSeconds: Long = 30) { - val data = WorkerParamsFactory.toData(Params(sessionId, serverTimeout, delayInSeconds, true)) + fun automaticallyBackgroundSync(workManagerProvider: WorkManagerProvider, + sessionId: String, + serverTimeoutInSeconds: Long = 0, + delayInSeconds: Long = 30, + forceImmediate: Boolean = false) { + val data = WorkerParamsFactory.toData( + Params( + sessionId = sessionId, + timeout = serverTimeoutInSeconds, + delay = delayInSeconds, + forceImmediate = forceImmediate + ) + ) val workRequest = workManagerProvider.matrixOneTimeWorkRequestBuilder() .setConstraints(WorkManagerProvider.workConstraints) .setInputData(data) .setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY_MILLIS, TimeUnit.MILLISECONDS) - .setInitialDelay(delayInSeconds, TimeUnit.SECONDS) + .setInitialDelay(if (forceImmediate) 0 else delayInSeconds, TimeUnit.SECONDS) .build() // Avoid risking multiple chains of syncs by replacing the existing chain workManagerProvider.workManager From 9b94f1e370f0162ad5ff115d34a977e87990b0f5 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 30 Dec 2021 12:42:39 +0100 Subject: [PATCH 149/632] Changelog for #4747 --- changelog.d/4747.misc | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/4747.misc diff --git a/changelog.d/4747.misc b/changelog.d/4747.misc new file mode 100644 index 0000000000..37a960671c --- /dev/null +++ b/changelog.d/4747.misc @@ -0,0 +1 @@ +Cleaning rendering of state events in timeline \ No newline at end of file From cc8da82d02fe7d69f1865fab94427a5260e0ce56 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Thu, 30 Dec 2021 16:28:50 +0300 Subject: [PATCH 150/632] New attachment picker implemented. --- changelog.d/3444.bugfix | 1 + .../attachments/AttachmentTypeSelectorView.kt | 71 ++--- .../home/room/detail/RoomDetailFragment.kt | 2 +- .../ic_attachment_stickers_white_24dp.png | Bin 595 -> 0 bytes .../ic_attachment_stickers_white_24dp.png | Bin 421 -> 0 bytes .../ic_attachment_stickers_white_24dp.png | Bin 734 -> 0 bytes .../ic_attachment_stickers_white_24dp.png | Bin 1142 -> 0 bytes .../ic_attachment_stickers_white_24dp.png | Bin 1521 -> 0 bytes .../res/drawable/ic_attachment_camera.xml | 13 + .../ic_attachment_camera_white_24dp.xml | 4 - .../main/res/drawable/ic_attachment_close.xml | 15 + .../main/res/drawable/ic_attachment_file.xml | 13 + .../ic_attachment_file_white_24dp.xml | 4 - .../res/drawable/ic_attachment_gallery.xml | 12 + .../ic_attachment_gallery_white_24dp.xml | 4 - .../res/drawable/ic_attachment_location.xml | 9 + ..._white_24dp.xml => ic_attachment_poll.xml} | 2 +- .../res/drawable/ic_attachment_sticker.xml | 13 + .../layout/view_attachment_type_selector.xml | 268 ++++++------------ 19 files changed, 185 insertions(+), 246 deletions(-) create mode 100644 changelog.d/3444.bugfix delete mode 100644 vector/src/main/res/drawable-hdpi/ic_attachment_stickers_white_24dp.png delete mode 100644 vector/src/main/res/drawable-mdpi/ic_attachment_stickers_white_24dp.png delete mode 100644 vector/src/main/res/drawable-xhdpi/ic_attachment_stickers_white_24dp.png delete mode 100644 vector/src/main/res/drawable-xxhdpi/ic_attachment_stickers_white_24dp.png delete mode 100644 vector/src/main/res/drawable-xxxhdpi/ic_attachment_stickers_white_24dp.png create mode 100644 vector/src/main/res/drawable/ic_attachment_camera.xml delete mode 100644 vector/src/main/res/drawable/ic_attachment_camera_white_24dp.xml create mode 100644 vector/src/main/res/drawable/ic_attachment_close.xml create mode 100644 vector/src/main/res/drawable/ic_attachment_file.xml delete mode 100644 vector/src/main/res/drawable/ic_attachment_file_white_24dp.xml create mode 100644 vector/src/main/res/drawable/ic_attachment_gallery.xml delete mode 100644 vector/src/main/res/drawable/ic_attachment_gallery_white_24dp.xml create mode 100644 vector/src/main/res/drawable/ic_attachment_location.xml rename vector/src/main/res/drawable/{ic_attachment_poll_white_24dp.xml => ic_attachment_poll.xml} (93%) create mode 100644 vector/src/main/res/drawable/ic_attachment_sticker.xml diff --git a/changelog.d/3444.bugfix b/changelog.d/3444.bugfix new file mode 100644 index 0000000000..bf397da5b7 --- /dev/null +++ b/changelog.d/3444.bugfix @@ -0,0 +1 @@ +Attachment picker UI improvements \ No newline at end of file diff --git a/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorView.kt b/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorView.kt index ccc07ef118..f1aca2cb15 100644 --- a/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorView.kt +++ b/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorView.kt @@ -26,24 +26,18 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewAnimationUtils import android.view.animation.Animation -import android.view.animation.AnimationSet -import android.view.animation.OvershootInterpolator -import android.view.animation.ScaleAnimation import android.view.animation.TranslateAnimation import android.widget.ImageButton import android.widget.LinearLayout import android.widget.PopupWindow import androidx.core.view.doOnNextLayout import androidx.core.view.isVisible -import com.amulyakhare.textdrawable.TextDrawable -import com.amulyakhare.textdrawable.util.ColorGenerator import im.vector.app.R -import im.vector.app.core.extensions.getMeasurements +import im.vector.app.core.epoxy.onClick import im.vector.app.core.utils.PERMISSIONS_EMPTY import im.vector.app.core.utils.PERMISSIONS_FOR_PICKING_CONTACT import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO import im.vector.app.databinding.ViewAttachmentTypeSelectorBinding -import im.vector.app.features.attachments.AttachmentTypeSelectorView.Callback import kotlin.math.max private const val ANIMATION_DURATION = 250 @@ -52,17 +46,16 @@ private const val ANIMATION_DURATION = 250 * This class is the view presenting choices for picking attachments. * It will return result through [Callback]. */ + class AttachmentTypeSelectorView(context: Context, inflater: LayoutInflater, - var callback: Callback?) : - PopupWindow(context) { + var callback: Callback? +) : PopupWindow(context) { interface Callback { fun onTypeSelected(type: Type) } - private val iconColorGenerator = ColorGenerator.MATERIAL - private val views: ViewAttachmentTypeSelectorBinding private var anchor: View? = null @@ -85,32 +78,21 @@ class AttachmentTypeSelectorView(context: Context, inputMethodMode = INPUT_METHOD_NOT_NEEDED isFocusable = true isTouchable = true + + views.attachmentCloseButton.onClick { + dismiss() + } } - fun show(anchor: View, isKeyboardOpen: Boolean) { + fun show(anchor: View) { this.anchor = anchor val anchorCoordinates = IntArray(2) anchor.getLocationOnScreen(anchorCoordinates) - if (isKeyboardOpen) { - showAtLocation(anchor, Gravity.NO_GRAVITY, 0, anchorCoordinates[1] + anchor.height) - } else { - val contentViewHeight = if (contentView.height == 0) { - contentView.getMeasurements().second - } else { - contentView.height - } - showAtLocation(anchor, Gravity.NO_GRAVITY, 0, anchorCoordinates[1] - contentViewHeight) - } + showAtLocation(anchor, Gravity.NO_GRAVITY, 0, anchorCoordinates[1]) + contentView.doOnNextLayout { animateWindowInCircular(anchor, contentView) } - animateButtonIn(views.attachmentGalleryButton, ANIMATION_DURATION / 2) - animateButtonIn(views.attachmentCameraButton, ANIMATION_DURATION / 4) - animateButtonIn(views.attachmentFileButton, ANIMATION_DURATION / 2) - animateButtonIn(views.attachmentAudioButton, 0) - animateButtonIn(views.attachmentContactButton, ANIMATION_DURATION / 4) - animateButtonIn(views.attachmentStickersButton, ANIMATION_DURATION / 2) - animateButtonIn(views.attachmentPollButton, ANIMATION_DURATION / 4) } override fun dismiss() { @@ -124,28 +106,18 @@ class AttachmentTypeSelectorView(context: Context, fun setAttachmentVisibility(type: Type, isVisible: Boolean) { when (type) { - Type.CAMERA -> views.attachmentCameraButtonContainer - Type.GALLERY -> views.attachmentGalleryButtonContainer - Type.FILE -> views.attachmentFileButtonContainer - Type.STICKER -> views.attachmentStickersButtonContainer - Type.AUDIO -> views.attachmentAudioButtonContainer - Type.CONTACT -> views.attachmentContactButtonContainer - Type.POLL -> views.attachmentPollButtonContainer + Type.CAMERA -> views.attachmentCameraButton + Type.GALLERY -> views.attachmentGalleryButton + Type.FILE -> views.attachmentFileButton + Type.STICKER -> views.attachmentStickersButton + Type.AUDIO -> views.attachmentAudioButton + Type.CONTACT -> views.attachmentContactButton + Type.POLL -> views.attachmentPollButton }.let { it.isVisible = isVisible } } - private fun animateButtonIn(button: View, delay: Int) { - val animation = AnimationSet(true) - val scale = ScaleAnimation(0.0f, 1.0f, 0.0f, 1.0f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.0f) - animation.addAnimation(scale) - animation.interpolator = OvershootInterpolator(1f) - animation.duration = ANIMATION_DURATION.toLong() - animation.startOffset = delay.toLong() - button.startAnimation(animation) - } - private fun animateWindowInCircular(anchor: View, contentView: View) { val coordinates = getClickCoordinates(anchor, contentView) val animator = ViewAnimationUtils.createCircularReveal(contentView, @@ -157,12 +129,6 @@ class AttachmentTypeSelectorView(context: Context, animator.start() } - private fun animateWindowInTranslate(contentView: View) { - val animation = TranslateAnimation(0f, 0f, contentView.height.toFloat(), 0f) - animation.duration = ANIMATION_DURATION.toLong() - getContentView().startAnimation(animation) - } - private fun animateWindowOutCircular(anchor: View, contentView: View) { val coordinates = getClickCoordinates(anchor, contentView) val animator = ViewAnimationUtils.createCircularReveal(getContentView(), @@ -207,7 +173,6 @@ class AttachmentTypeSelectorView(context: Context, } private fun ImageButton.configure(type: Type): ImageButton { - this.background = TextDrawable.builder().buildRound("", iconColorGenerator.getColor(type.ordinal)) this.setOnClickListener(TypeClickListener(type)) return this } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index a77899a8b0..35f33e4f75 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -1367,7 +1367,7 @@ class RoomDetailFragment @Inject constructor( attachmentTypeSelector = AttachmentTypeSelectorView(vectorBaseActivity, vectorBaseActivity.layoutInflater, this@RoomDetailFragment) attachmentTypeSelector.setAttachmentVisibility(AttachmentTypeSelectorView.Type.POLL, vectorPreferences.labsEnablePolls()) } - attachmentTypeSelector.show(views.composerLayout.views.attachmentButton, keyboardStateUtils.isKeyboardShowing) + attachmentTypeSelector.show(views.composerLayout.views.attachmentButton) } override fun onSendMessage(text: CharSequence) { diff --git a/vector/src/main/res/drawable-hdpi/ic_attachment_stickers_white_24dp.png b/vector/src/main/res/drawable-hdpi/ic_attachment_stickers_white_24dp.png deleted file mode 100644 index d27e8f406eece350e1ec4ce11e373b3da684a24f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 595 zcmV-Z0<8UsP){SMQWR`6i12^#Xd=rZx|9bLs3v%Fr3Qv$)Im2 z={(Ym&)gLHf|9MUKUUBcO0vQJ*+5&c-x+d=pE2q*!{J#peShLHl+jBGW~mPemyc&E z8&|jC!6TmDz!1Z=Z4snIvwS7EG?b`w2+Wmgh9*jqT}`_dbjzBqM44POwGK^yrouOo zMhtZA`f#7A7m#^`c&|K3{APHBF%806e5WJgTor+KR0qPhr;^tgg4hSnC*(U hmZR^sa_7#i`2~9``8a)2aIF9U002ovPDHLkV1h5}|0Mtb diff --git a/vector/src/main/res/drawable-mdpi/ic_attachment_stickers_white_24dp.png b/vector/src/main/res/drawable-mdpi/ic_attachment_stickers_white_24dp.png deleted file mode 100644 index 40d78cf9e26325bf4a88435bcac1bb9793fa44d8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 421 zcmV;W0b2fvP)a{vy813&?X0&oBh00qSXpdchHK(0$+8+_t-_rKkeH0heQ z1NdqX(UXRxB`M&8t=I<86&jqAj`*6;x)2SRV0+4@$xH0cZ)Do3ol#%;|~m z&-#KCbF2l2Fldrk;5*>`V5orZ!77`P)t67XD2eEU5*TyQW)!1XI z$-2PENKz_)E^E%}D|=~-8(0NA+Ls&OOIae`y0e0gi%&8i^Ci^bEx94><^6}mvDOwX zsK3?$ycbDsy#|b-2^@&+nL-<|x>-Ym#N4+Wx*~;m3+vMy#Kyl<4`0m%BXl(H@R diff --git a/vector/src/main/res/drawable-xhdpi/ic_attachment_stickers_white_24dp.png b/vector/src/main/res/drawable-xhdpi/ic_attachment_stickers_white_24dp.png deleted file mode 100644 index 46e23b9cdcf20f54962a792bbd0e6233fd3f1fb0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 734 zcmV<40wMj0P)l z!Y~Yml}LNGG67U^aja{z?}^xztJI#Dv^6-<>Xm zM6zu8sSx$_{BI%(1b~Duk1TQ>vJ9*UN0z;n9ZMQTQA)RlogT1ESq|{Wuq;_(Y8&hL z9N}2DRuL{}jCw6zg<%=dH9^=`FJexiLDpq9XpgOwNujU?+h(Kj?M~smgmg&llb~sL zVgv8EJ(DsYqY~$1zDO8jx-GH@KJdbKbUiHVLNmI(^MVZKY0*Px1Z?XY>K6B|C=^9t zM%}|X7K$qHL^}2?&)hN+$v1m{4IN>udW*YH;xR*vYUqv{l$YfFbwwZ{J)gPz?`oA= zIw$_DN3IGyQg=!6f4C|Tlb(EwyjH8!(m7GxxbWL58i2jEd?*H!fjxvHJFV3@@EF)rm)L@C z@>u8nbw%Kds@d^a+RG2S!SdM>5dCf^j@E|UrwYC5MGv~A{eaw=)wZHh=t{I=N4Xl^ zgfu`G&Uu0EJKSAr!!j$-(PTUH+j}VpI;JHyWNb(@hbYM~Aj|A&SdvEt2GBF5n!qp5 z6#Li~;|mYt2HO5f=W*sUbLm_Eq7aFKZwhh4GGZAz?8zV>!#_c0P&aSNiW4-$z;CnW zqFKHaeqAXv3km_+&5NGcYJAh~MrEb-Ir=pvWqGF1pq=g%rrVTMLRMOX@W_lep*BD^ zC9V(-iEOy<0W~UO;E%NUtf}LnKeLKR<{-9m`I%KjCW4f*?RZK(Jw1*20fl%e$_<`M QTmS$707*qoM6N<$f{e{Y5dZ)H diff --git a/vector/src/main/res/drawable-xxhdpi/ic_attachment_stickers_white_24dp.png b/vector/src/main/res/drawable-xxhdpi/ic_attachment_stickers_white_24dp.png deleted file mode 100644 index 4058b25495a1a24a145a2968c0abc9ffb1754991..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1142 zcmV-+1d02JP)T2dyFj4}^zU6IUU_b0TX%9| z2Y>I~%OiH=S~CE4`6Xw2&9?7N-peYdmfjxO?%5V>%blH_ zN7^<-6t*eb9Cqex7i<{~9sUtH(uh(gR!&1>4Z>6@*xpm$;V+r3S(qwU=Nh3Zkya#?Km!uz ziIVL=6J)+39XB)_afFO*?p;C@(E@qr*H@oYhkWNLbqR}ehT-!u<~O(YK^UIj2_b&y zpsmx68DcXUzBq#Tp{uqhx9UOMdydID=50cz{zcPCZ3QZ5J9^J^7A(+F(@Bj(3@)9J z(e{y}MXIEY59)Sm+$@d1t#*j{nX4chGHYD>*9p0FtAz#IExBGjA$#xMQ>SqyW`K(t+=s=x7IlUa-gbvgR^p6zlKD@UrEm|iUs;qN3o(UiC0oqeiT>{gz|fL&@l_?(1j1WuD;>7SWYM% zp90%9+C}Rkbl{cdLk+A9-27_%oa~%)TbALpiX~})F3%!EX}6}$edv}41?qD`+GU3> zVdgKYO^@C*w43?1ixzD?_;Ys~+SFtzg{ZCQ>P0PC#a6o^eBLqJXSm)fPPSmX@$cnm zQIP@L0yrZJu%$CQ-!d|aqaR++zJMlwycUs+;s`mV0QRK#90|*Fk*3`EJEa2lUL=UK zox&g#{@v1$BIrD(lptM!cmY@N-8#()_YV@MohlknTmIs|WX3OdlqiGeDai$C_w;|{ zE&|FOL3!e9%2Mc^r8GDQ>|4=&pxY{ZxUN1_qO0$t>!i{D>iQ6OJgg2WlIWDHFBOr> ztdfR<_?-`=K$Vjom`En4tWMhd3yMg(RKa?XS@*D$87l_#&vE~TyG1Kycwhsr3ctYS z`3Xx3qf6k{NZP!N73nqKFoe6I;c`3$*29x9ISmbTfA{k+g{cBJ^e&L@!2*pb*i>xl z(G(Je=j1B8o*qk)RZcC*?d~qUN%_oTD<4fUGBPqUGSUfu0FsYR!(D(Ood5s;07*qo IM6N<$g8rEc>i_@% diff --git a/vector/src/main/res/drawable-xxxhdpi/ic_attachment_stickers_white_24dp.png b/vector/src/main/res/drawable-xxxhdpi/ic_attachment_stickers_white_24dp.png deleted file mode 100644 index c5b243564685da7e357d929374378d4aeaec6529..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1521 zcmV@~0drDELIAGL9O(c600d`2O+f$vv5yPVW%Xb=wTb3)V8M)h8-jAsC zV>B#pSqiKLnug*JX;|K4>2N12*V(Dku)M|6;7%1RHO&W^j|waS?#Mfu)^HPwT=RUr zf$UiSk><}wBqYgm09p6FhkbZj0uL;2`1!#=8Fd3V<=4=Ah?)w9oF@>>j) zji6?L6)3;OK+y;)23V2uTMU$ppk;usSk8nSicHLxu^W58AriMtTbuF)>2vhv;OdLq{V)4?L0=Cj zk(J+tOvt^wpsxqyvER=ld%#8L$-y2#HjSe|#)kx=%FvVh3_+=uolkozEEjy|Y~0TV z`i#|%@D7@g4KJqPzN5`M(qpW4S|pyV_mIl}?27bhx-OEe(KUkk%}DJW@)`kz4nZk~ zpKaH2&He2L^J(Xh%aD^o4e&Sg`7hv{JH#_qJBI{$fOi?{-1j?_aTB2z@nu9XRy!xS z@I91;c)%KiEn0X-jt~QsNB{+-{_#1{#viMl6TI}D5MqD=X{Z9u(NIhka84=%{E1j7 zT~cl5TAA$fu%URyYUdcWPro4^kSgjW|FhlaBQJ zxJR+DxZ}eIr00Wc>lB}$$Aj+#uK`>{ z$3TY*Pg`5)D&UYC*G3mnuN>`RLQ)h%m!V&vgLmJZFSDL6Zt?2;<@?hQZ^rh1R{POXX?f{HQq4~m<0iXAEnwpdvZWxNlq#`opU-?vZ!mzl7VCJ#bCqo@}{R{r1}+^C?utCN%B^`5zK6Qg_~x@4IfcV#|U+lw4B z7rjCx(P=4eLX-)}U_BVvK1ZWcv`?U1wsH_^1`t+Y1cRv-Zf#kx5eyQPE>=%XbQdTu zDSI;lW<*;f)IgaexuJ5QZiTRfC93oxiSkFm?ZwRX0}esK{K-Q$rki5_Jry-A@ykk- zKjJeD$Od&9nhrAD^vu^!zbT38rYi zv|->r)uu9lZ+7_@um-e`i**l8J!_(6zZKtI0OeT}bWHOmdCX@LA XAosu+GI>6S00000NkvXXu0mjf!1}un diff --git a/vector/src/main/res/drawable/ic_attachment_camera.xml b/vector/src/main/res/drawable/ic_attachment_camera.xml new file mode 100644 index 0000000000..8c7bedb3cf --- /dev/null +++ b/vector/src/main/res/drawable/ic_attachment_camera.xml @@ -0,0 +1,13 @@ + + + + diff --git a/vector/src/main/res/drawable/ic_attachment_camera_white_24dp.xml b/vector/src/main/res/drawable/ic_attachment_camera_white_24dp.xml deleted file mode 100644 index 5c2920d252..0000000000 --- a/vector/src/main/res/drawable/ic_attachment_camera_white_24dp.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/vector/src/main/res/drawable/ic_attachment_close.xml b/vector/src/main/res/drawable/ic_attachment_close.xml new file mode 100644 index 0000000000..76f0d3d064 --- /dev/null +++ b/vector/src/main/res/drawable/ic_attachment_close.xml @@ -0,0 +1,15 @@ + + + + diff --git a/vector/src/main/res/drawable/ic_attachment_file.xml b/vector/src/main/res/drawable/ic_attachment_file.xml new file mode 100644 index 0000000000..b3545e54a6 --- /dev/null +++ b/vector/src/main/res/drawable/ic_attachment_file.xml @@ -0,0 +1,13 @@ + + + diff --git a/vector/src/main/res/drawable/ic_attachment_file_white_24dp.xml b/vector/src/main/res/drawable/ic_attachment_file_white_24dp.xml deleted file mode 100644 index 4e6b9458f8..0000000000 --- a/vector/src/main/res/drawable/ic_attachment_file_white_24dp.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/vector/src/main/res/drawable/ic_attachment_gallery.xml b/vector/src/main/res/drawable/ic_attachment_gallery.xml new file mode 100644 index 0000000000..0f3432544f --- /dev/null +++ b/vector/src/main/res/drawable/ic_attachment_gallery.xml @@ -0,0 +1,12 @@ + + + diff --git a/vector/src/main/res/drawable/ic_attachment_gallery_white_24dp.xml b/vector/src/main/res/drawable/ic_attachment_gallery_white_24dp.xml deleted file mode 100644 index d4e68f125b..0000000000 --- a/vector/src/main/res/drawable/ic_attachment_gallery_white_24dp.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/vector/src/main/res/drawable/ic_attachment_location.xml b/vector/src/main/res/drawable/ic_attachment_location.xml new file mode 100644 index 0000000000..c2c8093e1d --- /dev/null +++ b/vector/src/main/res/drawable/ic_attachment_location.xml @@ -0,0 +1,9 @@ + + + diff --git a/vector/src/main/res/drawable/ic_attachment_poll_white_24dp.xml b/vector/src/main/res/drawable/ic_attachment_poll.xml similarity index 93% rename from vector/src/main/res/drawable/ic_attachment_poll_white_24dp.xml rename to vector/src/main/res/drawable/ic_attachment_poll.xml index 8cbcc6e47c..320dccb7fc 100644 --- a/vector/src/main/res/drawable/ic_attachment_poll_white_24dp.xml +++ b/vector/src/main/res/drawable/ic_attachment_poll.xml @@ -5,6 +5,6 @@ android:viewportHeight="24"> diff --git a/vector/src/main/res/drawable/ic_attachment_sticker.xml b/vector/src/main/res/drawable/ic_attachment_sticker.xml new file mode 100644 index 0000000000..eb59eaa75d --- /dev/null +++ b/vector/src/main/res/drawable/ic_attachment_sticker.xml @@ -0,0 +1,13 @@ + + + diff --git a/vector/src/main/res/layout/view_attachment_type_selector.xml b/vector/src/main/res/layout/view_attachment_type_selector.xml index 4cd5e1910d..3724affdd2 100644 --- a/vector/src/main/res/layout/view_attachment_type_selector.xml +++ b/vector/src/main/res/layout/view_attachment_type_selector.xml @@ -1,199 +1,109 @@ - + android:background="?android:colorBackground" + android:paddingTop="12dp" + android:paddingBottom="12dp"> - + android:layout_marginStart="12dp" + android:background="@drawable/circle" + android:contentDescription="@string/action_close" + android:src="@drawable/ic_attachment_close" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:gravity="center_vertical" + android:orientation="horizontal"> - + android:background="?android:attr/selectableItemBackground" + android:contentDescription="@string/attachment_type_gallery" + android:src="@drawable/ic_attachment_gallery" /> - - - - - - - + android:layout_marginStart="24dp" + android:background="?android:attr/selectableItemBackground" + android:contentDescription="@string/attachment_type_sticker" + android:src="@drawable/ic_attachment_sticker" /> - - - - - - - + android:layout_marginStart="24dp" + android:background="?android:attr/selectableItemBackground" + android:contentDescription="@string/attachment_type_file" + android:src="@drawable/ic_attachment_file" /> - + - + - + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + From 464ac3bce22224c224263cda93063ddda5ba8ced Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 30 Dec 2021 16:36:36 +0100 Subject: [PATCH 151/632] Fix blink effect when opening the attachment popup, and improve clickability of the buttons --- .../ui-styles/src/main/res/values/dimens.xml | 5 ++ ...composer_layout_constraint_set_compact.xml | 8 +- ...omposer_layout_constraint_set_expanded.xml | 8 +- .../layout/view_attachment_type_selector.xml | 75 ++++++++++--------- .../layout/view_voice_message_recorder.xml | 4 +- 5 files changed, 53 insertions(+), 47 deletions(-) diff --git a/library/ui-styles/src/main/res/values/dimens.xml b/library/ui-styles/src/main/res/values/dimens.xml index 864f3d3d7f..9fbf8958da 100644 --- a/library/ui-styles/src/main/res/values/dimens.xml +++ b/library/ui-styles/src/main/res/values/dimens.xml @@ -42,4 +42,9 @@ 8dp + + + 56dp + 52dp + 1dp \ No newline at end of file diff --git a/vector/src/main/res/layout/composer_layout_constraint_set_compact.xml b/vector/src/main/res/layout/composer_layout_constraint_set_compact.xml index 3378878ac6..7e926b860c 100644 --- a/vector/src/main/res/layout/composer_layout_constraint_set_compact.xml +++ b/vector/src/main/res/layout/composer_layout_constraint_set_compact.xml @@ -108,9 +108,9 @@ + android:layout_height="@dimen/composer_min_height" + android:background="?android:colorBackground"> + android:src="@drawable/ic_attachment_sticker" + app:tint="?colorPrimary" /> + android:src="@drawable/ic_attachment_file" + app:tint="?colorPrimary" /> + android:src="@drawable/ic_attachment_poll" + app:tint="?colorPrimary" /> + android:src="@drawable/ic_attachment_camera" + app:tint="?colorPrimary" /> - + - + + diff --git a/vector/src/main/res/layout/view_voice_message_recorder.xml b/vector/src/main/res/layout/view_voice_message_recorder.xml index 53be4f07f6..9f8e58d724 100644 --- a/vector/src/main/res/layout/view_voice_message_recorder.xml +++ b/vector/src/main/res/layout/view_voice_message_recorder.xml @@ -34,7 +34,7 @@ Date: Thu, 30 Dec 2021 16:52:58 +0100 Subject: [PATCH 152/632] Animate the attachment open/close button --- .../attachments/AttachmentTypeSelectorView.kt | 16 ++++++++++++++++ .../main/res/drawable/ic_attachment_close.xml | 15 --------------- .../res/layout/view_attachment_type_selector.xml | 6 ++++-- 3 files changed, 20 insertions(+), 17 deletions(-) delete mode 100644 vector/src/main/res/drawable/ic_attachment_close.xml diff --git a/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorView.kt b/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorView.kt index f1aca2cb15..c56b3ac832 100644 --- a/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorView.kt +++ b/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorView.kt @@ -84,7 +84,21 @@ class AttachmentTypeSelectorView(context: Context, } } + private fun animateOpen() { + views.attachmentCloseButton.animate() + .setDuration(200) + .rotation(135f) + } + + private fun animateClose() { + views.attachmentCloseButton.animate() + .setDuration(200) + .rotation(0f) + } + fun show(anchor: View) { + animateOpen() + this.anchor = anchor val anchorCoordinates = IntArray(2) anchor.getLocationOnScreen(anchorCoordinates) @@ -96,6 +110,8 @@ class AttachmentTypeSelectorView(context: Context, } override fun dismiss() { + animateClose() + val capturedAnchor = anchor if (capturedAnchor != null) { animateWindowOutCircular(capturedAnchor, contentView) diff --git a/vector/src/main/res/drawable/ic_attachment_close.xml b/vector/src/main/res/drawable/ic_attachment_close.xml deleted file mode 100644 index 76f0d3d064..0000000000 --- a/vector/src/main/res/drawable/ic_attachment_close.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - diff --git a/vector/src/main/res/layout/view_attachment_type_selector.xml b/vector/src/main/res/layout/view_attachment_type_selector.xml index 72b95c98db..463d05d95d 100644 --- a/vector/src/main/res/layout/view_attachment_type_selector.xml +++ b/vector/src/main/res/layout/view_attachment_type_selector.xml @@ -1,6 +1,7 @@ @@ -12,10 +13,11 @@ android:layout_margin="@dimen/composer_attachment_margin" android:background="@null" android:contentDescription="@string/action_close" - android:src="@drawable/ic_attachment_close" + android:src="@drawable/ic_attachment" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" /> + app:layout_constraintTop_toTopOf="parent" + tools:rotation="135" /> Date: Fri, 31 Dec 2021 10:20:16 +0100 Subject: [PATCH 153/632] Remove dead code and do some cleanup --- .../vector/app/core/services/CallService.kt | 20 +++++-------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/vector/src/main/java/im/vector/app/core/services/CallService.kt b/vector/src/main/java/im/vector/app/core/services/CallService.kt index d194434641..ebda43e024 100644 --- a/vector/src/main/java/im/vector/app/core/services/CallService.kt +++ b/vector/src/main/java/im/vector/app/core/services/CallService.kt @@ -57,7 +57,7 @@ class CallService : VectorService() { private val knownCalls = mutableMapOf() private val connectedCallIds = mutableSetOf() - lateinit var notificationManager: NotificationManagerCompat + private lateinit var notificationManager: NotificationManagerCompat @Inject lateinit var notificationUtils: NotificationUtils @Inject lateinit var callManager: WebRtcCallManager @Inject lateinit var avatarRenderer: AvatarRenderer @@ -125,13 +125,6 @@ class CallService : VectorService() { callRingPlayerOutgoing?.stop() displayCallInProgressNotification(intent) } - ACTION_CALL_CONNECTING -> { - // lower notification priority - displayCallInProgressNotification(intent) - // stop ringing - callRingPlayerIncoming?.stop() - callRingPlayerOutgoing?.stop() - } ACTION_CALL_TERMINATED -> { handleCallTerminated(intent) } @@ -320,12 +313,8 @@ class CallService : VectorService() { private const val ACTION_INCOMING_RINGING_CALL = "im.vector.app.core.services.CallService.ACTION_INCOMING_RINGING_CALL" private const val ACTION_OUTGOING_RINGING_CALL = "im.vector.app.core.services.CallService.ACTION_OUTGOING_RINGING_CALL" - private const val ACTION_CALL_CONNECTING = "im.vector.app.core.services.CallService.ACTION_CALL_CONNECTING" private const val ACTION_ONGOING_CALL = "im.vector.app.core.services.CallService.ACTION_ONGOING_CALL" private const val ACTION_CALL_TERMINATED = "im.vector.app.core.services.CallService.ACTION_CALL_TERMINATED" - private const val ACTION_NO_ACTIVE_CALL = "im.vector.app.core.services.CallService.NO_ACTIVE_CALL" -// private const val ACTION_ACTIVITY_VISIBLE = "im.vector.app.core.services.CallService.ACTION_ACTIVITY_VISIBLE" -// private const val ACTION_STOP_RINGING = "im.vector.app.core.services.CallService.ACTION_STOP_RINGING" private const val EXTRA_CALL_ID = "EXTRA_CALL_ID" private const val EXTRA_IS_IN_BG = "EXTRA_IS_IN_BG" @@ -351,7 +340,6 @@ class CallService : VectorService() { action = ACTION_OUTGOING_RINGING_CALL putExtra(EXTRA_CALL_ID, callId) } - ContextCompat.startForegroundService(context, intent) } @@ -362,11 +350,13 @@ class CallService : VectorService() { action = ACTION_ONGOING_CALL putExtra(EXTRA_CALL_ID, callId) } - ContextCompat.startForegroundService(context, intent) } - fun onCallTerminated(context: Context, callId: String, endCallReason: EndCallReason, rejected: Boolean) { + fun onCallTerminated(context: Context, + callId: String, + endCallReason: EndCallReason, + rejected: Boolean) { val intent = Intent(context, CallService::class.java) .apply { action = ACTION_CALL_TERMINATED From 1d3cc52991a749bc06a20eb2594c946df8a14416 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 31 Dec 2021 13:05:06 +0100 Subject: [PATCH 154/632] Changelog new management --- CHANGES.md | 1 - changelog.d/2614.feature | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 changelog.d/2614.feature diff --git a/CHANGES.md b/CHANGES.md index be4ee5342f..e0dd3298d8 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -581,7 +581,6 @@ Changes in Element 1.1.7 (2021-05-12) =================================================== Features ✨: - - Allow changing nick colors (#2610) - Spaces beta Improvements 🙌: diff --git a/changelog.d/2614.feature b/changelog.d/2614.feature new file mode 100644 index 0000000000..7feceeabba --- /dev/null +++ b/changelog.d/2614.feature @@ -0,0 +1 @@ +Allow changing nick colors from the member detail screen \ No newline at end of file From 4d6eec8972ebf7fd9eaf31762898538814eeaf0f Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Fri, 31 Dec 2021 15:12:46 +0300 Subject: [PATCH 155/632] Rate limited maptiler key is added. --- changelog.d/2210.bugfix | 1 + vector/build.gradle | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 changelog.d/2210.bugfix diff --git a/changelog.d/2210.bugfix b/changelog.d/2210.bugfix new file mode 100644 index 0000000000..6f7c09ce26 --- /dev/null +++ b/changelog.d/2210.bugfix @@ -0,0 +1 @@ +Static location sharing and rendering \ No newline at end of file diff --git a/vector/build.gradle b/vector/build.gradle index 2f10695c2f..76fe629d3c 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -150,7 +150,7 @@ android { // This *must* only be set in trusted environments. buildConfigField "Boolean", "handleCallAssertedIdentityEvents", "false" - buildConfigField "String", "mapTilerKey", "\"DUMMY_KEY\"" + buildConfigField "String", "mapTilerKey", "\"KF6tcY7YuKFgJSZ1EFQ2\"" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" From 364457d102689340d40a8c4261725b25bb7573bb Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 31 Dec 2021 13:13:48 +0100 Subject: [PATCH 156/632] Move logic to dedicated ViewModel --- .../java/im/vector/app/AppStateHandler.kt | 29 -------- .../vector/app/features/home/HomeActivity.kt | 2 + .../home/UserColorAccountDataViewModel.kt | 73 +++++++++++++++++++ 3 files changed, 75 insertions(+), 29 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/home/UserColorAccountDataViewModel.kt diff --git a/vector/src/main/java/im/vector/app/AppStateHandler.kt b/vector/src/main/java/im/vector/app/AppStateHandler.kt index afd4c53c37..9ed9dd5b23 100644 --- a/vector/src/main/java/im/vector/app/AppStateHandler.kt +++ b/vector/src/main/java/im/vector/app/AppStateHandler.kt @@ -21,13 +21,8 @@ import androidx.lifecycle.LifecycleOwner import arrow.core.Option import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.utils.BehaviorDataSource -import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider import im.vector.app.features.session.coroutineScope import im.vector.app.features.ui.UiStateRepository -import io.reactivex.Observable -import io.reactivex.android.schedulers.AndroidSchedulers -import io.reactivex.disposables.CompositeDisposable -import io.reactivex.rxkotlin.addTo import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob @@ -38,11 +33,8 @@ import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes -import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.group.model.GroupSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary -import org.matrix.android.sdk.rx.rx import javax.inject.Inject import javax.inject.Singleton @@ -62,7 +54,6 @@ fun RoomGroupingMethod.group() = (this as? RoomGroupingMethod.ByLegacyGroup)?.gr @Singleton class AppStateHandler @Inject constructor( private val sessionDataSource: ActiveSessionDataSource, - private val matrixItemColorProvider: MatrixItemColorProvider, private val uiStateRepository: UiStateRepository, private val activeSessionHolder: ActiveSessionHolder ) : DefaultLifecycleObserver { @@ -141,11 +132,6 @@ class AppStateHandler @Inject constructor( return (selectedSpaceDataSource.currentValue?.orNull() as? RoomGroupingMethod.ByLegacyGroup)?.groupSummary?.groupId } - @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) - fun entersForeground() { - observeUserAccountData() - } - override fun onResume(owner: LifecycleOwner) { observeActiveSession() } @@ -164,19 +150,4 @@ class AppStateHandler @Inject constructor( } } } - - private fun observeUserAccountData() { - sessionDataSource.observe() - .observeOn(AndroidSchedulers.mainThread()) - .switchMap { - it.orNull()?.rx()?.liveUserAccountData(setOf(UserAccountDataTypes.TYPE_OVERRIDE_COLORS)) - ?: Observable.just(emptyList()) - } - .distinctUntilChanged() - .subscribe { - val overrideColorSpecs = it?.firstOrNull()?.content?.toModel>() - matrixItemColorProvider.setOverrideColors(overrideColorSpecs) - } - .addTo(compositeDisposable) - } } diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt index e2696115f4..43456f16bb 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt @@ -106,6 +106,8 @@ class HomeActivity : private val homeActivityViewModel: HomeActivityViewModel by viewModel() @Suppress("UNUSED") private val analyticsAccountDataViewModel: AnalyticsAccountDataViewModel by viewModel() + @Suppress("UNUSED") + private val userColorAccountDataViewModel: UserColorAccountDataViewModel by viewModel() private val serverBackupStatusViewModel: ServerBackupStatusViewModel by viewModel() private val promoteRestrictedViewModel: PromoteRestrictedViewModel by viewModel() diff --git a/vector/src/main/java/im/vector/app/features/home/UserColorAccountDataViewModel.kt b/vector/src/main/java/im/vector/app/features/home/UserColorAccountDataViewModel.kt new file mode 100644 index 0000000000..9e13fe56d0 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/UserColorAccountDataViewModel.kt @@ -0,0 +1,73 @@ +/* + * 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 + +import com.airbnb.mvrx.MavericksState +import com.airbnb.mvrx.MavericksViewModelFactory +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import im.vector.app.core.di.MavericksAssistedViewModelFactory +import im.vector.app.core.di.hiltMavericksViewModelFactory +import im.vector.app.core.platform.EmptyAction +import im.vector.app.core.platform.EmptyViewEvents +import im.vector.app.core.platform.VectorViewModel +import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.mapNotNull +import kotlinx.coroutines.flow.onEach +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes +import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.flow.flow + +data class DummyState( + val dummy: Boolean = false +) : MavericksState + +class UserColorAccountDataViewModel @AssistedInject constructor( + @Assisted initialState: DummyState, + private val session: Session, + private val matrixItemColorProvider: MatrixItemColorProvider +) : VectorViewModel(initialState) { + + @AssistedFactory + interface Factory : MavericksAssistedViewModelFactory { + override fun create(initialState: DummyState): UserColorAccountDataViewModel + } + + companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() + + init { + observeAccountData() + } + + private fun observeAccountData() { + session.flow() + .liveUserAccountData(setOf(UserAccountDataTypes.TYPE_OVERRIDE_COLORS)) + .mapNotNull { it.firstOrNull() } + .mapNotNull { it.content.toModel>() } + .onEach { userColorAccountDataContent -> + matrixItemColorProvider.setOverrideColors(userColorAccountDataContent) + } + .launchIn(viewModelScope) + } + + override fun handle(action: EmptyAction) { + // No op + } +} From 07d2a15cf847222329fdf2074f08cdc429a0eb36 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 31 Dec 2021 14:45:35 +0100 Subject: [PATCH 157/632] Code cleanup --- .../home/UserColorAccountDataViewModel.kt | 7 +++- .../helper/MatrixItemColorProvider.kt | 36 ++++++++++--------- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/UserColorAccountDataViewModel.kt b/vector/src/main/java/im/vector/app/features/home/UserColorAccountDataViewModel.kt index 9e13fe56d0..77f6eade4a 100644 --- a/vector/src/main/java/im/vector/app/features/home/UserColorAccountDataViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/UserColorAccountDataViewModel.kt @@ -28,12 +28,14 @@ import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.onEach import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.flow.flow +import timber.log.Timber data class DummyState( val dummy: Boolean = false @@ -60,8 +62,11 @@ class UserColorAccountDataViewModel @AssistedInject constructor( session.flow() .liveUserAccountData(setOf(UserAccountDataTypes.TYPE_OVERRIDE_COLORS)) .mapNotNull { it.firstOrNull() } - .mapNotNull { it.content.toModel>() } + .map { it.content.toModel>() } .onEach { userColorAccountDataContent -> + if (userColorAccountDataContent == null) { + Timber.w("Invalid account data im.vector.setting.override_colors") + } matrixItemColorProvider.setOverrideColors(userColorAccountDataContent) } .launchIn(viewModelScope) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MatrixItemColorProvider.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MatrixItemColorProvider.kt index 97028c47f2..4c99894bf3 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MatrixItemColorProvider.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MatrixItemColorProvider.kt @@ -22,6 +22,7 @@ import androidx.annotation.VisibleForTesting import im.vector.app.R import im.vector.app.core.resources.ColorProvider import org.matrix.android.sdk.api.util.MatrixItem +import timber.log.Timber import javax.inject.Inject import javax.inject.Singleton import kotlin.math.abs @@ -45,35 +46,38 @@ class MatrixItemColorProvider @Inject constructor( } fun setOverrideColors(overrideColors: Map?) { - overrideColors?.forEach() { + cache.clear() + overrideColors?.forEach { setOverrideColor(it.key, it.value) } } - fun setOverrideColor(id: String, colorSpec: String?) : Boolean { + fun setOverrideColor(id: String, colorSpec: String?): Boolean { val color = parseUserColorSpec(colorSpec) - if (color == null) { + return if (color == null) { cache.remove(id) - return false + false } else { - cache.put(id, color) - return true + cache[id] = color + true } } @ColorInt private fun parseUserColorSpec(colorText: String?): Int? { - if (colorText.isNullOrBlank()) { - return null - } - try { - if (colorText.first() == '#') { - return (colorText.substring(1).toLong(radix = 16) or 0xff000000L).toInt() - } else { - return colorProvider.getColor(getUserColorByIndex(colorText.toInt())) + return if (colorText.isNullOrBlank()) { + null + } else { + try { + if (colorText.first() == '#') { + (colorText.substring(1).toLong(radix = 16) or 0xff000000L).toInt() + } else { + colorProvider.getColor(getUserColorByIndex(colorText.toInt())) + } + } catch (e: Throwable) { + Timber.e(e, "Unable to parse color $colorText") + null } - } catch (e: Throwable) { - return null } } From ddadefdbd6d27cd7394797025254572dcbffedd5 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 31 Dec 2021 15:08:30 +0100 Subject: [PATCH 158/632] Move logic to ViewModel --- .../RoomMemberProfileAction.kt | 1 + .../RoomMemberProfileController.kt | 1 + .../RoomMemberProfileFragment.kt | 29 ++--------- .../RoomMemberProfileViewModel.kt | 51 +++++++++++++++++-- .../RoomMemberProfileViewState.kt | 1 + 5 files changed, 55 insertions(+), 28 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileAction.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileAction.kt index 7a171ca3e5..1467889b4a 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileAction.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileAction.kt @@ -28,4 +28,5 @@ sealed class RoomMemberProfileAction : VectorViewModelAction { object VerifyUser : RoomMemberProfileAction() object ShareRoomMemberProfile : RoomMemberProfileAction() data class SetPowerLevel(val previousValue: Int, val newValue: Int, val askForValidation: Boolean) : RoomMemberProfileAction() + data class SetUserColorOverride(val newColor: String) : RoomMemberProfileAction() } diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileController.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileController.kt index 2aa3a08b53..06ba18f4a7 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileController.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileController.kt @@ -178,6 +178,7 @@ class RoomMemberProfileController @Inject constructor( id = "overrideColor", editable = false, title = stringProvider.getString(R.string.room_member_override_nick_color), + subtitle = state.userColorOverride, divider = false, action = { callback?.onOverrideColorClicked() } ) diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt index 3d97ce10dd..2bd93e41da 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt @@ -24,7 +24,6 @@ import android.view.MenuItem import android.view.View import android.view.ViewGroup import androidx.core.view.isVisible -import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Incomplete import com.airbnb.mvrx.Success @@ -56,10 +55,8 @@ import im.vector.app.features.home.room.detail.RoomDetailPendingActionStore import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider import im.vector.app.features.roommemberprofile.devices.DeviceListBottomSheet import im.vector.app.features.roommemberprofile.powerlevel.EditPowerLevelDialogs -import kotlinx.coroutines.launch import kotlinx.parcelize.Parcelize import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel -import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes import org.matrix.android.sdk.api.session.room.powerlevels.Role import org.matrix.android.sdk.api.util.MatrixItem import javax.inject.Inject @@ -328,36 +325,20 @@ class RoomMemberProfileFragment @Inject constructor( navigator.openBigImageViewer(requireActivity(), view, userMatrixItem) } - override fun onOverrideColorClicked(): Unit = withState(viewModel) { state -> + override fun onOverrideColorClicked(): Unit = withState(viewModel) { state -> val inflater = requireActivity().layoutInflater val layout = inflater.inflate(R.layout.dialog_base_edit_text, null) val views = DialogBaseEditTextBinding.bind(layout) - val session = injector().activeSessionHolder().getActiveSession() - val overrideColorsSetting = session.accountDataService().getUserAccountDataEvent(UserAccountDataTypes.TYPE_OVERRIDE_COLORS) - val overrideColorSpecs = overrideColorsSetting?.content?.toMap().orEmpty() - val userId = state.userId - val overrideColorSpec : String? = overrideColorSpecs[userId]?.toString() - views.editText.setText(overrideColorSpec) + views.editText.setText(state.userColorOverride) views.editText.hint = "#000000" MaterialAlertDialogBuilder(requireContext()) .setTitle(R.string.room_member_override_nick_color) .setView(layout) .setPositiveButton(R.string.ok) { _, _ -> - val newOverrideColorSpec = views.editText.text.toString() - if (newOverrideColorSpec != overrideColorSpec) { - val newOverrideColorSpecs = overrideColorSpecs.toMutableMap() - if (matrixItemColorProvider.setOverrideColor(userId, newOverrideColorSpec)) { - newOverrideColorSpecs[userId] = newOverrideColorSpec - } else { - newOverrideColorSpecs.remove(userId) - } - viewModel.viewModelScope.launch { - session.accountDataService().updateUserAccountData( - type = UserAccountDataTypes.TYPE_OVERRIDE_COLORS, - content = newOverrideColorSpecs) - } - invalidate() + val newColor = views.editText.text.toString() + if (newColor != state.userColorOverride) { + viewModel.handle(RoomMemberProfileAction.SetUserColorOverride(newColor)) } } .setNegativeButton(R.string.cancel, null) diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt index 5b07b101e7..8129dec3d4 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt @@ -28,22 +28,27 @@ import dagger.assisted.AssistedInject import im.vector.app.R import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory +import im.vector.app.core.extensions.exhaustive import im.vector.app.core.mvrx.runCatchingToAsync import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider import im.vector.app.features.displayname.getBestName +import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider import im.vector.app.features.powerlevel.PowerLevelsFlowFactory import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes 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.events.model.toModel import org.matrix.android.sdk.api.session.profile.ProfileService import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams @@ -57,10 +62,12 @@ import org.matrix.android.sdk.api.util.toOptional import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.flow.unwrap -class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private val initialState: RoomMemberProfileViewState, - private val stringProvider: StringProvider, - private val session: Session) : - VectorViewModel(initialState) { +class RoomMemberProfileViewModel @AssistedInject constructor( + @Assisted private val initialState: RoomMemberProfileViewState, + private val stringProvider: StringProvider, + private val matrixItemColorProvider: MatrixItemColorProvider, + private val session: Session +) : VectorViewModel(initialState) { @AssistedFactory interface Factory : MavericksAssistedViewModelFactory { @@ -85,6 +92,7 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v ) } observeIgnoredState() + observeAccountData() viewModelScope.launch(Dispatchers.Main) { // Do we have a room member for this id. val roomMember = withContext(Dispatchers.Default) { @@ -121,6 +129,24 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v } } + private fun observeAccountData() { + session.flow() + .liveUserAccountData(setOf(UserAccountDataTypes.TYPE_OVERRIDE_COLORS)) + .mapNotNull { it.firstOrNull() } + .map { it.content.toModel>() } + .map { userColorAccountDataContent -> + userColorAccountDataContent?.get(initialState.userId) + } + .onEach { + setState { + copy( + userColorOverride = it + ) + } + } + .launchIn(viewModelScope) + } + private fun observeIgnoredState() { session.flow().liveIgnoredUsers() .map { ignored -> @@ -143,6 +169,23 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v is RoomMemberProfileAction.BanOrUnbanUser -> handleBanOrUnbanAction(action) is RoomMemberProfileAction.KickUser -> handleKickAction(action) RoomMemberProfileAction.InviteUser -> handleInviteAction() + is RoomMemberProfileAction.SetUserColorOverride -> handleSetUserColorOverride(action) + }.exhaustive + } + + private fun handleSetUserColorOverride(action: RoomMemberProfileAction.SetUserColorOverride) { + val newOverrideColorSpecs = session.accountDataService().getUserAccountDataEvent(UserAccountDataTypes.TYPE_OVERRIDE_COLORS) + ?.content?.toMap().orEmpty().toMutableMap() + if (matrixItemColorProvider.setOverrideColor(initialState.userId, action.newColor)) { + newOverrideColorSpecs[initialState.userId] = action.newColor + } else { + newOverrideColorSpecs.remove(initialState.userId) + } + viewModelScope.launch { + session.accountDataService().updateUserAccountData( + type = UserAccountDataTypes.TYPE_OVERRIDE_COLORS, + content = newOverrideColorSpecs + ) } } diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewState.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewState.kt index a4730153c2..1f2c3d6ce4 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewState.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewState.kt @@ -41,6 +41,7 @@ data class RoomMemberProfileViewState( val allDevicesAreCrossSignedTrusted: Boolean = false, val asyncMembership: Async = Uninitialized, val hasReadReceipt: Boolean = false, + val userColorOverride: String? = null, val actionPermissions: ActionPermissions = ActionPermissions() ) : MavericksState { From 1cb91ca5dfb16f586fdc2062b9bd633294882fc6 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 31 Dec 2021 15:13:01 +0100 Subject: [PATCH 159/632] Use color parser --- .../room/detail/timeline/helper/MatrixItemColorProvider.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MatrixItemColorProvider.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MatrixItemColorProvider.kt index 4c99894bf3..1c64082f65 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MatrixItemColorProvider.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MatrixItemColorProvider.kt @@ -16,6 +16,7 @@ package im.vector.app.features.home.room.detail.timeline.helper +import android.graphics.Color import androidx.annotation.ColorInt import androidx.annotation.ColorRes import androidx.annotation.VisibleForTesting @@ -69,10 +70,10 @@ class MatrixItemColorProvider @Inject constructor( null } else { try { - if (colorText.first() == '#') { - (colorText.substring(1).toLong(radix = 16) or 0xff000000L).toInt() - } else { + if (colorText.length == 1) { colorProvider.getColor(getUserColorByIndex(colorText.toInt())) + } else { + Color.parseColor(colorText) } } catch (e: Throwable) { Timber.e(e, "Unable to parse color $colorText") From a7b72ed39d98b544a17fcf796c1f6eeab024a3ea Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 31 Dec 2021 15:22:48 +0100 Subject: [PATCH 160/632] Fix latest small bugs --- .../vector/app/core/di/MavericksViewModelModule.kt | 6 ++++++ .../roommemberprofile/RoomMemberProfileAction.kt | 2 +- .../roommemberprofile/RoomMemberProfileController.kt | 2 +- .../roommemberprofile/RoomMemberProfileViewModel.kt | 12 ++++++++---- 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt index d09cd21d19..09e161bc99 100644 --- a/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt @@ -42,6 +42,7 @@ import im.vector.app.features.home.HomeDetailViewModel import im.vector.app.features.home.PromoteRestrictedViewModel import im.vector.app.features.home.UnknownDeviceDetectorSharedViewModel import im.vector.app.features.home.UnreadMessagesSharedViewModel +import im.vector.app.features.home.UserColorAccountDataViewModel import im.vector.app.features.home.room.breadcrumbs.BreadcrumbsViewModel import im.vector.app.features.home.room.detail.RoomDetailViewModel import im.vector.app.features.home.room.detail.composer.MessageComposerViewModel @@ -412,6 +413,11 @@ interface MavericksViewModelModule { @MavericksViewModelKey(RoomMemberProfileViewModel::class) fun roomMemberProfileViewModelFactory(factory: RoomMemberProfileViewModel.Factory): MavericksAssistedViewModelFactory<*, *> + @Binds + @IntoMap + @MavericksViewModelKey(UserColorAccountDataViewModel::class) + fun userColorAccountDataViewModelFactory(factory: UserColorAccountDataViewModel.Factory): MavericksAssistedViewModelFactory<*, *> + @Binds @IntoMap @MavericksViewModelKey(RoomPreviewViewModel::class) diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileAction.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileAction.kt index 1467889b4a..87801a7e95 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileAction.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileAction.kt @@ -28,5 +28,5 @@ sealed class RoomMemberProfileAction : VectorViewModelAction { object VerifyUser : RoomMemberProfileAction() object ShareRoomMemberProfile : RoomMemberProfileAction() data class SetPowerLevel(val previousValue: Int, val newValue: Int, val askForValidation: Boolean) : RoomMemberProfileAction() - data class SetUserColorOverride(val newColor: String) : RoomMemberProfileAction() + data class SetUserColorOverride(val newColorSpec: String) : RoomMemberProfileAction() } diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileController.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileController.kt index 06ba18f4a7..860961ae29 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileController.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileController.kt @@ -179,7 +179,7 @@ class RoomMemberProfileController @Inject constructor( editable = false, title = stringProvider.getString(R.string.room_member_override_nick_color), subtitle = state.userColorOverride, - divider = false, + divider = !state.isMine, action = { callback?.onOverrideColorClicked() } ) diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt index 8129dec3d4..7cab5a1cc8 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt @@ -174,10 +174,14 @@ class RoomMemberProfileViewModel @AssistedInject constructor( } private fun handleSetUserColorOverride(action: RoomMemberProfileAction.SetUserColorOverride) { - val newOverrideColorSpecs = session.accountDataService().getUserAccountDataEvent(UserAccountDataTypes.TYPE_OVERRIDE_COLORS) - ?.content?.toMap().orEmpty().toMutableMap() - if (matrixItemColorProvider.setOverrideColor(initialState.userId, action.newColor)) { - newOverrideColorSpecs[initialState.userId] = action.newColor + val newOverrideColorSpecs = session.accountDataService() + .getUserAccountDataEvent(UserAccountDataTypes.TYPE_OVERRIDE_COLORS) + ?.content + ?.toModel>() + .orEmpty() + .toMutableMap() + if (matrixItemColorProvider.setOverrideColor(initialState.userId, action.newColorSpec)) { + newOverrideColorSpecs[initialState.userId] = action.newColorSpec } else { newOverrideColorSpecs.remove(initialState.userId) } From 6d8b5db18e3c1a92d275972096776b2ab7e05c8f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 31 Dec 2021 15:34:42 +0100 Subject: [PATCH 161/632] Fix latest small bugs --- .../roommemberprofile/RoomMemberProfileViewModel.kt | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt index 7cab5a1cc8..8961ab66cb 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt @@ -186,10 +186,14 @@ class RoomMemberProfileViewModel @AssistedInject constructor( newOverrideColorSpecs.remove(initialState.userId) } viewModelScope.launch { - session.accountDataService().updateUserAccountData( - type = UserAccountDataTypes.TYPE_OVERRIDE_COLORS, - content = newOverrideColorSpecs - ) + try { + session.accountDataService().updateUserAccountData( + type = UserAccountDataTypes.TYPE_OVERRIDE_COLORS, + content = newOverrideColorSpecs + ) + } catch (failure: Throwable) { + _viewEvents.post(RoomMemberProfileViewEvents.Failure(failure)) + } } } From 96d5652fcd96971ded31e11d4eb47805bc82536d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 31 Dec 2021 15:48:08 +0100 Subject: [PATCH 162/632] Small cleanup --- .../app/features/home/UserColorAccountDataViewModel.kt | 6 +++--- .../roommemberprofile/RoomMemberProfileViewModel.kt | 5 ++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/UserColorAccountDataViewModel.kt b/vector/src/main/java/im/vector/app/features/home/UserColorAccountDataViewModel.kt index 77f6eade4a..3d4f219a7c 100644 --- a/vector/src/main/java/im/vector/app/features/home/UserColorAccountDataViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/UserColorAccountDataViewModel.kt @@ -29,12 +29,12 @@ import im.vector.app.core.platform.VectorViewModel import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.onEach import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.flow.flow +import org.matrix.android.sdk.flow.unwrap import timber.log.Timber data class DummyState( @@ -60,8 +60,8 @@ class UserColorAccountDataViewModel @AssistedInject constructor( private fun observeAccountData() { session.flow() - .liveUserAccountData(setOf(UserAccountDataTypes.TYPE_OVERRIDE_COLORS)) - .mapNotNull { it.firstOrNull() } + .liveUserAccountData(UserAccountDataTypes.TYPE_OVERRIDE_COLORS) + .unwrap() .map { it.content.toModel>() } .onEach { userColorAccountDataContent -> if (userColorAccountDataContent == null) { diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt index 8961ab66cb..859bb6125f 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt @@ -39,7 +39,6 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -131,8 +130,8 @@ class RoomMemberProfileViewModel @AssistedInject constructor( private fun observeAccountData() { session.flow() - .liveUserAccountData(setOf(UserAccountDataTypes.TYPE_OVERRIDE_COLORS)) - .mapNotNull { it.firstOrNull() } + .liveUserAccountData(UserAccountDataTypes.TYPE_OVERRIDE_COLORS) + .unwrap() .map { it.content.toModel>() } .map { userColorAccountDataContent -> userColorAccountDataContent?.get(initialState.userId) From c3480bfd1672805b60beead0d85fd962cc6f70b7 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 31 Dec 2021 16:47:46 +0100 Subject: [PATCH 163/632] Add summary to the setting --- vector/src/main/res/values/strings.xml | 1 + vector/src/main/res/xml/vector_settings_labs.xml | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 7580c1da63..40f561234c 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -3662,6 +3662,7 @@ %s in Settings to receive invites directly in Element. Enable LaTeX mathematics + Restart the application for the change to take effect. Create Poll diff --git a/vector/src/main/res/xml/vector_settings_labs.xml b/vector/src/main/res/xml/vector_settings_labs.xml index 2e8ed08bf4..f394d1923e 100644 --- a/vector/src/main/res/xml/vector_settings_labs.xml +++ b/vector/src/main/res/xml/vector_settings_labs.xml @@ -47,13 +47,14 @@ + android:summary="@string/labs_use_restricted_join_rule_desc" + android:title="@string/labs_use_restricted_join_rule" /> + android:summary="@string/restart_the_application_to_apply_changes" + android:title="@string/labs_enable_latex_maths" /> Date: Fri, 31 Dec 2021 17:01:11 +0100 Subject: [PATCH 164/632] Add group for dependency, and use latest markwon version --- dependencies.gradle | 2 +- dependencies_groups.gradle | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/dependencies.gradle b/dependencies.gradle index b975abba0b..6cb5fac64c 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -14,7 +14,7 @@ def kotlinCoroutines = "1.5.2" def dagger = "2.40.5" def retrofit = "2.9.0" def arrow = "0.8.2" -def markwon = "4.3.1" +def markwon = "4.6.2" def moshi = "1.12.0" def lifecycle = "2.4.0" def flowBinding = "1.2.0" diff --git a/dependencies_groups.gradle b/dependencies_groups.gradle index 25a78bc0c3..7edf54fb50 100644 --- a/dependencies_groups.gradle +++ b/dependencies_groups.gradle @@ -179,6 +179,7 @@ ext.groups = [ 'org.sonatype.oss', 'org.testng', 'org.threeten', + 'ru.noties', 'xerces', 'xml-apis', ] From a80f8b96c151e30ba792a7c2710987a7ffe18f88 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 31 Dec 2021 17:35:53 +0100 Subject: [PATCH 165/632] Format --- .../java/im/vector/app/features/html/EventHtmlRenderer.kt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/html/EventHtmlRenderer.kt b/vector/src/main/java/im/vector/app/features/html/EventHtmlRenderer.kt index 2d832b60cc..36acad8854 100644 --- a/vector/src/main/java/im/vector/app/features/html/EventHtmlRenderer.kt +++ b/vector/src/main/java/im/vector/app/features/html/EventHtmlRenderer.kt @@ -35,9 +35,11 @@ import javax.inject.Inject import javax.inject.Singleton @Singleton -class EventHtmlRenderer @Inject constructor(htmlConfigure: MatrixHtmlPluginConfigure, - context: Context, - private val vectorPreferences: VectorPreferences) { +class EventHtmlRenderer @Inject constructor( + htmlConfigure: MatrixHtmlPluginConfigure, + context: Context, + vectorPreferences: VectorPreferences +) { interface PostProcessor { fun afterRender(renderedText: Spannable) From 5fd3317197fbd46628ae5a596331dc9a0e185537 Mon Sep 17 00:00:00 2001 From: Tobias Preuss Date: Thu, 16 Dec 2021 23:34:08 +0100 Subject: [PATCH 166/632] Use property access syntax. Signed-off-by: Tobias Preuss --- .../java/im/vector/app/core/utils/ExternalApplicationsUtil.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/core/utils/ExternalApplicationsUtil.kt b/vector/src/main/java/im/vector/app/core/utils/ExternalApplicationsUtil.kt index bdaf520ba1..818482043f 100644 --- a/vector/src/main/java/im/vector/app/core/utils/ExternalApplicationsUtil.kt +++ b/vector/src/main/java/im/vector/app/core/utils/ExternalApplicationsUtil.kt @@ -308,7 +308,7 @@ fun shareMedia(context: Context, file: File, mediaMimeType: String?) { val sendIntent = ShareCompat.IntentBuilder(context) .setType(mediaMimeType) .setStream(mediaUri) - .getIntent() + .intent sendShareIntent(context, sendIntent) } From 70b07471cf555327ba2b9c090dfe08033167ccee Mon Sep 17 00:00:00 2001 From: Tobias Preuss Date: Thu, 16 Dec 2021 23:13:18 +0100 Subject: [PATCH 167/632] Use ShareCompat.IntentBuilder in "shareText()" utility function. Signed-off-by: Tobias Preuss --- .../im/vector/app/core/utils/ExternalApplicationsUtil.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/vector/src/main/java/im/vector/app/core/utils/ExternalApplicationsUtil.kt b/vector/src/main/java/im/vector/app/core/utils/ExternalApplicationsUtil.kt index 818482043f..8185808484 100644 --- a/vector/src/main/java/im/vector/app/core/utils/ExternalApplicationsUtil.kt +++ b/vector/src/main/java/im/vector/app/core/utils/ExternalApplicationsUtil.kt @@ -314,10 +314,10 @@ fun shareMedia(context: Context, file: File, mediaMimeType: String?) { } fun shareText(context: Context, text: String) { - val sendIntent = Intent() - sendIntent.action = Intent.ACTION_SEND - sendIntent.type = "text/plain" - sendIntent.putExtra(Intent.EXTRA_TEXT, text) + val sendIntent = ShareCompat.IntentBuilder(context) + .setType("text/plain") + .setText(text) + .intent sendShareIntent(context, sendIntent) } From 74363ff8235764c0dc15ebb6b3dce42a6f52b56d Mon Sep 17 00:00:00 2001 From: Tobias Preuss Date: Fri, 31 Dec 2021 22:28:46 +0100 Subject: [PATCH 168/632] Let ShareCompat.IntentBuilder create the chooser intent for sharing content. + Although the system's intent chooser normally takes care of cases when no app is found we still keep the safeguard here to be prepared for exotic devices behaving different. Signed-off-by: Tobias Preuss --- changelog.d/4745.misc | 1 + .../app/core/utils/ExternalApplicationsUtil.kt | 18 ++++++++++-------- 2 files changed, 11 insertions(+), 8 deletions(-) create mode 100644 changelog.d/4745.misc diff --git a/changelog.d/4745.misc b/changelog.d/4745.misc new file mode 100644 index 0000000000..458422d112 --- /dev/null +++ b/changelog.d/4745.misc @@ -0,0 +1 @@ +Open share UI provides by the system when sharing media or text. \ No newline at end of file diff --git a/vector/src/main/java/im/vector/app/core/utils/ExternalApplicationsUtil.kt b/vector/src/main/java/im/vector/app/core/utils/ExternalApplicationsUtil.kt index 8185808484..06da70f391 100644 --- a/vector/src/main/java/im/vector/app/core/utils/ExternalApplicationsUtil.kt +++ b/vector/src/main/java/im/vector/app/core/utils/ExternalApplicationsUtil.kt @@ -305,26 +305,28 @@ fun shareMedia(context: Context, file: File, mediaMimeType: String?) { return } - val sendIntent = ShareCompat.IntentBuilder(context) + val chooserIntent = ShareCompat.IntentBuilder(context) .setType(mediaMimeType) .setStream(mediaUri) - .intent + .setChooserTitle(R.string.share) + .createChooserIntent() - sendShareIntent(context, sendIntent) + createChooser(context, chooserIntent) } fun shareText(context: Context, text: String) { - val sendIntent = ShareCompat.IntentBuilder(context) + val chooserIntent = ShareCompat.IntentBuilder(context) .setType("text/plain") .setText(text) - .intent + .setChooserTitle(R.string.share) + .createChooserIntent() - sendShareIntent(context, sendIntent) + createChooser(context, chooserIntent) } -private fun sendShareIntent(context: Context, intent: Intent) { +private fun createChooser(context: Context, intent: Intent) { try { - context.startActivity(Intent.createChooser(intent, context.getString(R.string.share))) + context.startActivity(intent) } catch (activityNotFoundException: ActivityNotFoundException) { context.toast(R.string.error_no_external_application_found) } From 47e4ff47684ce386ceda361d7e78eb1afe462102 Mon Sep 17 00:00:00 2001 From: libexus Date: Wed, 29 Dec 2021 13:35:26 +0000 Subject: [PATCH 169/632] Translated using Weblate (German) Currently translated at 98.5% (2686 of 2725 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 | 35 +++++++++++++++++++---- 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/vector/src/main/res/values-de/strings.xml b/vector/src/main/res/values-de/strings.xml index eadddc69cc..6422e2357f 100644 --- a/vector/src/main/res/values-de/strings.xml +++ b/vector/src/main/res/values-de/strings.xml @@ -79,7 +79,8 @@ Erste Synchronisation: \nImportiere Räume Erste Synchronisation: -\nImportiere betretene Räume +\nLade deine Konversationen +\nWenn du vielen Räumen beigetreten bist, kann das eine Weile dauern Erste Synchronisation: \nImportiere eingeladene Räume Erste Synchronisation: @@ -1156,7 +1157,7 @@ Formatiere Nachrichten mittels Markdown-Syntax, bevor sie gesendet werden. Dies erlaubt erweiterte Formatierungen wie Sternchen (*), um kursiven Text anzuzeigen. Lesebestätigungen zeigen Klicke auf die Lesebestätigungen für eine detailliertere Liste. - Einladungen, Kicks und Banns bleiben unberührt. + Einladungen, Kicks und Banns werden weiterhin angezeigt. Passwort Starte die System-Kamera anstelle der angepassten Kamera. Diese Option erfordert eine externe Anwendung um Sprachnachrichten aufzuzeichnen. @@ -2016,7 +2017,7 @@ Initialisiere Cross-Signing Schlüssel zurücksetzen QR-Code - Fast geschafft! Zeigt %s dasselbe Schild an\? + Fast geschafft! Zeigt %s ein Häkchen\? Ja Nein Verbindung zum Server wurde unterbrochen @@ -2121,7 +2122,7 @@ %s setzen Fehlerbehebung %s hat den Raum erstellt und konfiguriert. - Fast geschafft! Zeigt das andere Gerät das gleiche Schild an\? + Fast geschafft! Zeigt das andere Gerät ein Häkchen an\? Fast geschafft! Warte auf Bestätigung… Verschlüsselte Direktnachrichten Nachricht… @@ -2658,7 +2659,7 @@ Zurückrufen Dieser Anruf wurde beendet %1$s hat diesen Anruf abgelehnt - Du hast diesen Anruf %s abgelehnt + Du hast diesen Anruf abgelehnt %s Du nimmst zur Zeit an diesem Anruf teil %1$s hat einen Anruf gestartet Du hast einen Anruf gestartet @@ -3066,9 +3067,31 @@ Raum verlassen! Heimserver auswählen Es konnte kein Heimserver mit der Adresse %s gefunden werden. Bitte überprüfe die Adresse oder wähle den Heimserver manuell. - Untergeordneten Space hinzufügen + Untergeordneten Space hinzufügen. Bist du dir wirklich sicher, dass du diese Informationen senden willst\? E-Mail-Adressen und Telefonnummern an %s senden Nicht jetzt Auf Benachrichtigungen warten + Externe Bibliotheken + Du kannst dies jederzeit in den Einstellungen deaktivieren + Wir teilen keine Informationen mit Drittpersonen + Wir erfassen und analysieren keine Accountdaten + Hilf uns dabei Probleme zu identifizieren und Element zu verbessern, indem du anonyme Nutzungsdaten teilst. Um zu verstehen, wie Personen mehrere Geräte benutzen, werden wir eine zufällige Kennung generieren, die zwischen deinen Geräten geteilt wird. +\n +\nDu kannst alle unsere Bedingungen lesen %s. + Stelle sicher, dass die richtigen Personen Zugriff auf %s haben. Du kannst jederzeit weitere Personen einladen. + Wer ist Mitglied deines Teams\? + Der Identitätsserver gibt keine Bedingungen an + Bedingungen des Identitätsservers ausblenden + Bedingungen des Identitätsservers anzeigen + Systemeinstellungen + Versionen + Erhalte Hilfe bei der Bedienung von Element + Hilfe und Unterstützung + Hilfe + Rechtliches + Entscheide, welche Spaces Zugriff auf den Raum haben sollen. Die Mitglieder der Spaces können diesen Räumen beitreten. + hier + Hilf mit, Element zu verbessern + Aktivieren \ No newline at end of file From 6a44606c7864251f6899b2df52130da2d4287e5e Mon Sep 17 00:00:00 2001 From: Jozef Gaal Date: Thu, 30 Dec 2021 21:37:36 +0000 Subject: [PATCH 170/632] Translated using Weblate (Slovak) Currently translated at 59.4% (1619 of 2725 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 | 159 +++++++++++++++++++--- 1 file changed, 138 insertions(+), 21 deletions(-) diff --git a/vector/src/main/res/values-sk/strings.xml b/vector/src/main/res/values-sk/strings.xml index f0e7f3c56e..4613140cfd 100644 --- a/vector/src/main/res/values-sk/strings.xml +++ b/vector/src/main/res/values-sk/strings.xml @@ -72,8 +72,8 @@ Úvodná synchronizácia: \nNačítanie konverzácií \nAk ste sa pripojili k mnohým miestnostiam, môže to chvíľu trvať - Úvodná synchronizácia: -\nPrebieha import pozvaní + Počiatočná synchronizácia: +\nImport pozvaných miestností Úvodná synchronizácia: \nPrebieha import opustených miestností Úvodná synchronizácia: @@ -236,7 +236,7 @@ Zobraziť dešifrovaný zdroj Vymazať Premenovať - Ohlásiť obsah + Nahlásiť obsah Aktívny hovor Prebiehajúci konferenčný hovor. \nPripojte sa ako %1$s alebo %2$s @@ -310,8 +310,8 @@ Vytvoriť účet Prihlásiť sa Odhlásiť sa - Adresa domovského servera - Adresa servera totožností + URL adresa domovského servera + URL adresa servera totožností Hľadať Začať novú konverzáciu Uskutočniť audio hovor @@ -690,7 +690,7 @@ Nebude určená hlavná adresa tejto miestnosti. Upozornenie - Hlavná adresa Nastaviť ako hlavnú adresu - Zrušiť nastavenie ako hlavná adresa + Zrušiť nastavenie ako hlavnej adresy Kopírovať ID miestnosti Kopírovať adresu miestnosti V tejto miestnosti je povolené šifrovanie. @@ -752,7 +752,7 @@ Vyberte adresár miestností Server môže byť nedostupný alebo preťažený Zadajte adresu domovského servera, z ktorého sa načíta zoznam verejných miestností - URL adresa domovského servera + Názov servera Všetky miestnosti na serveri %s Všetky Matrix miestnosti na serveri %s Hľadať historické @@ -818,7 +818,7 @@ Všetky správy Len zmienky Umlčať - Pridať odkaz na domovskú obrazovku + Pridať na domovskú obrazovku Náhľady URL adries Vibrovať keď spomeniete iného používateľa Príslušnosť ku komunitám @@ -852,9 +852,9 @@ Akcie Oznámiť chybu zatrasením zariadenia - 1 zmena stavu - %d zmeny stavu - %d zmien stavu + 1 zmena členstva + %d zmeny členstva + %d zmien členstva Zobraziť členov Otvoriť záhlavie @@ -890,9 +890,9 @@ %d neprečítaných správ - 1 neprečítaná správa - %d neprečítané správy - %d neprečítaných správ + 1 neprečítaná oznámená správa + %d neprečítané oznámené správy + %d neprečítaných oznámených správ 1 miestnosť @@ -1096,7 +1096,7 @@ Token FCM sa nepodarilo získať: \n%1$s Registrácia tokenu - Token FCM úspešne zaregistrovaný na domovskom servery. + Token FCM úspešne zaregistrovaný na domovskom serveri. FCM token sa nepodarilo zaregistrovať na domovskom serveri: \n %1$s Služba oznámení @@ -1184,7 +1184,7 @@ [%1$s] \nV zariadení nemáte nastavený účet Google. Prosím, pridajte si účet cez správcu účtov. Konfigurácia ${app_name} nemá vplyv na zobrazenie tejto chyby. Pridať účet - Aplikácia sa nepotrebuje pripájať k domovskému serveru, keď beží na pozadí, čo môže predĺžiť výdrž batérie + Aplikácia sa nemusí pripájať k domovskému serveru, keď beží na pozadí, čo môže predĺžiť výdrž batérie Nastaviť hlučné oznámenia Nastaviť oznámenia prichádzajúceho hovoru Nastaviť tiché oznámenia @@ -1275,7 +1275,7 @@ Zabrániť neúmyselnému telefonovaniu Žiadať potvrdenie pred uskutočnením hovoru Povoliť záložný server pre nadviazanie hovoru - Ak váš domovský server neponúka adresu servera turn, na nadviazanie hovorov sa použije %s (počas hovoru zdieľate svoju adresu IP) + Použije sa %s ako asistencia, keď ju váš domovský server neponúka (vaša IP adresa bude zdieľaná počas hovoru) Prebiehajúci hovor (%s) Návrat k hovoru Na uskutočnenie tejto akcie si prosím v nastaveniach pridajte server totožností. @@ -1317,8 +1317,8 @@ %s \nSynchronizácia môže byť odložená vzávislosti od kapacity zariadenia (batéria a režim spánku). Integrácie - Použite správcu integrácií na nastavenie botov, mostov, widgetov a balíčkov s nálepkami. -\nSprávci integrácií zbierajú údaje konfigurácie, môžu meniť widgety, posielať pozvania vstúpiť do miestnosti a nastavovať úroveň moci vo vašom mene. + Použite správcu integrácií na nastavenie botov, premostení, widgetov a balíčkov s nálepkami. +\nSprávcovia integrácie dostávajú konfiguračné údaje a môžu vo vašom mene upravovať widgety, posielať pozvánky do miestnosti a nastavovať úrovne oprávnení. Bezpečné zálohovanie Spravovať Nastaviť bezpečné zálohovanie @@ -1423,7 +1423,7 @@ \n \nPozor: Keď odinštalujete aplikáciu Element, môže dôjsť k zmazaniu tohoto súboru. Kľúč obnovenia bol uložený. - Záloha už existuje na domovskom servery + Záloha už existuje na vašom domovskom serveri Zdá sa, že ste si už zálohovanie kľúčov nastavili z inej relácie. Chcete ho nahradiť zálohou, ktorú vytvárate teraz\? Nahradiť Zastaviť @@ -1593,7 +1593,7 @@ Priame konverzácie Nová miestnosť VYTVORIŤ - Názov miestnosti + Názov Verejná Ktokoľvek môže vstúpiť do tejto miestnosti Adresár miestností @@ -1725,4 +1725,121 @@ Kontakt Súbor Ďalšie + Nastavenia miestnosti + Zistiť viac + Overený %s + Overiť %s + Manuálne overiť + Žiadosť o overenie + Overenie odoslané + Prijali ste + Zrušili ste overenie + %s bolo zrušené + Nezabezpečené + Zhodujú sa + Povoliť šifrovanie + Režim pre vývojárov + Pokročilé nastavenia + Úvodná synchronizácia… + Prihlásiť sa + Prihlásiť sa + Matrix ID + Zastaralý domovský server + Poslať znova + Zadať kód + Telefónne číslo + Email (voliteľné) + Nové heslo + Prihlásiť sa + Zaregistrovať sa + Zistiť viac + Neprečítané správy + Len zmienky + Všetky správy + Ignorovať používateľa + IGNOROVAŤ POUŽÍVATEĽA + Je to nevyžiadaná správa + %s prečítané + Skryť heslo + Zobraziť heslo + Poslať prílohu + Verzie + Pomocník + Právne informácie + tu + Povoliť + Anketa + Nedostupný + Nedostupný + Pripojený + Kľúčové slová + \@miestnosť + Žiadne + Priestory + Hlas + Aktualizovať + nestabilná + stabilná + Spätná väzba + Navrhované + Popis + Náhodné + Všeobecné + Súkromný + Verejný + Verejná + Súkromná + Priestory + Pozvánky + Zlyhalo + Odoslané + Odosielanie + Typ + Vybrané + Video + Obrázok + Snímka obrazovky + Prepnúť + Používatelia + Podržať + alebo + Povolenia + Zverejniť + Zrušiť zverejnenie + Pridať + "Téma: " + Návrhy + Kontakty + Nedávne + Téma + Opustiť + Nastavenia + Obnoviť predvolené + Anketa + Kód + Téma + Rola + Odoslať + POZVAŤ + Nešifrované + Správa… + Koniec + Obnoviť + Odstrániť… + Upozornenie: + Nie + Áno + Dôveryhodné + Upozornenie + Overený + Overiť + Krížové podpisovanie + Časová os + Prestať ignorovať + Používatelia + Pozvánky + Vlastná úroveň + Moderátori + Správcovia + Nahrané súbory \ No newline at end of file From d1e46a18fa0728cdd7aaac8522ca89e3919513c4 Mon Sep 17 00:00:00 2001 From: LinAGKar Date: Tue, 28 Dec 2021 21:39:24 +0000 Subject: [PATCH 171/632] Translated using Weblate (Swedish) Currently translated at 100.0% (2725 of 2725 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 | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/vector/src/main/res/values-sv/strings.xml b/vector/src/main/res/values-sv/strings.xml index 952eda7d04..9a67d76b5d 100644 --- a/vector/src/main/res/values-sv/strings.xml +++ b/vector/src/main/res/values-sv/strings.xml @@ -3074,4 +3074,8 @@ Din identitetsservers policy ${app_name}s policy Din hemservers policy + + %1$d röst avgiven. Rösta för att se resultatet + %1$d röster avgivna. Rösta för att se resultatet + \ No newline at end of file From 03cc812a35d3ac40bd59f722453b9003a040602f Mon Sep 17 00:00:00 2001 From: Denys Nykula Date: Thu, 30 Dec 2021 12:28:42 +0000 Subject: [PATCH 172/632] Translated using Weblate (Ukrainian) Currently translated at 100.0% (2725 of 2725 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 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/res/values-uk/strings.xml b/vector/src/main/res/values-uk/strings.xml index 47c8a687fc..1f17bc2628 100644 --- a/vector/src/main/res/values-uk/strings.xml +++ b/vector/src/main/res/values-uk/strings.xml @@ -1083,7 +1083,7 @@ Форматування Markdown Форматувати повідомлення з використанням синтаксису Markdown перед надсиланням. Це дає можливість розгорнутого форматування, як-от використання зірочок для показу курсивного тексту. Показувати підтвердження прочитання - Натискати на підтвердження прочитання для детального переліку. + Натискайте мітки прочитання для детального переліку. Показувати події приєднання та виходу Це не стосується запрошень, вилучень і блокувань. Показувати події облікових записів From fa65156e87453669f02c8866c3f02cc9dfa27cf9 Mon Sep 17 00:00:00 2001 From: Jozef Gaal Date: Sat, 1 Jan 2022 09:31:04 +0000 Subject: [PATCH 173/632] Translated using Weblate (Slovak) Currently translated at 67.7% (1845 of 2725 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 | 275 +++++++++++++++++++++- 1 file changed, 268 insertions(+), 7 deletions(-) diff --git a/vector/src/main/res/values-sk/strings.xml b/vector/src/main/res/values-sk/strings.xml index 4613140cfd..8c5d9d1be8 100644 --- a/vector/src/main/res/values-sk/strings.xml +++ b/vector/src/main/res/values-sk/strings.xml @@ -784,7 +784,7 @@ Spustiť overenie Zdieľať bez overenia Ignorovať žiadosť - Hlučné oznámenia + Hlasné oznámenia Tiché oznámenia Hlásenie o chybe Poslať fotku @@ -799,7 +799,7 @@ Nerozpoznaný príkaz: %s Vypnuté - Hlučné + Hlasné Šifrovaná správa Podrobnosti o komunite Načítavanie… @@ -814,7 +814,7 @@ Ste si istí, že chcete začať video hovor? Zoznam komunít Zakázanie používateľa ho vylúči z tejto miestnosti a zabráni mu v ďalšom vstupe. - Všetky správy (hlučné) + Všetky správy (hlasné) Všetky správy Len zmienky Umlčať @@ -963,9 +963,9 @@ Túto akciu nie je možné vykonať, pretože neboli udelené všetky potrebné oprávnenia. Ak je to možné, prosím popis napíšte v angličtine. - 1 sekunda - %d sekundy - %d sekúnd + %ds + %ds + %ds 1 minúta @@ -1185,7 +1185,7 @@ \nV zariadení nemáte nastavený účet Google. Prosím, pridajte si účet cez správcu účtov. Konfigurácia ${app_name} nemá vplyv na zobrazenie tejto chyby. Pridať účet Aplikácia sa nemusí pripájať k domovskému serveru, keď beží na pozadí, čo môže predĺžiť výdrž batérie - Nastaviť hlučné oznámenia + Nastaviť hlasné oznámenia Nastaviť oznámenia prichádzajúceho hovoru Nastaviť tiché oznámenia Vybrať farbu upozornení LED, vibrácie, zvuky… @@ -1842,4 +1842,265 @@ Moderátori Správcovia Nahrané súbory + + %1$s, %2$s, %3$s a %4$d ďalší + %1$s, %2$s, %3$s a %4$d ďalší + %1$s, %2$s, %3$s a %4$d ďalších + + %1$s, %2$s, %3$s a %4$s + %1$s, %2$s a %3$s + Nepodarilo sa overiť + Pokračovať s %s + Aktualizovať miestnosť + Zmeniť názov miestnosti + Zmeniť viditeľnosť histórie + Zapnúť šifrovanie miestnosti + Zmeniť obrázok miestnosti + Zrušiť zverejnenie tejto adresy + Zverejniť túto adresu + Ďalšie zverejnené adresy: + Nemôžete napísať priamu správu sebe! + Pridať tému + %1$d z %2$d + Žiadne ďalšie výsledky + Téma miestnosti (voliteľné) + %1$s a %2$s + Zadajte svoj PIN + Zrušiť stlmenie mikrofónu + Stlmiť mikrofón + Načítanie dostupných jazykov… + Ďalšie dostupné jazyky + Označiť ako dôveryhodné + Zapnúť krížové podpisovanie + Je dostupná aktualizácia šifrovania + Správy obsahujúce @miestnosť + Čaká sa na %s… + Šifrovanie nie je zapnuté + Udržujte ho v bezpečí + Toto som nebol ja + Nové prihlásenie + Overte toto prihlásenie + + %d aktívna relácia + %d aktívne relácie + %d aktívnych relácií + + Zobraziť všetky relácie + Zapnúť end-to-end šifrovanie… + Moderátor v %1$s + Správca v %1$s + Odchod z miestnosti… + Čaká sa na %s… + Overte pomocou emoji + Naskenujte ich kód + Overiť túto reláciu + Nezhodujú sa + Nedôveryhodné prihlásenie + Vymazať všetky údaje + Vymazať osobné údaje + Ste odhlásený + Prihláste sa znova + Ste odhlásený + Používateľské meno alebo emailová adresa + Potvrdiť telefónne číslo + Telefónne číslo (nepovinné) + Nastaviť telefónne číslo + Vybrať server + Opustiť túto miestnosť + Odstrániť z obľúbených + Pridať k obľúbeným + Oznámenie pomocou zvuku + Všetky správy (hlasné) + Nahlásiť tento obsah + %1$s dňa %2$s + Odstrániť anketu + Povoliť ankety + Ukončiť anketu + Ukončiť anketu + + %1$d hlas + %1$d hlasy + %1$d hlasov + + Systémové nastavenia + Teraz nie + Vytvoriť možnosti + Vytvoriť anketu + Vybrať domovský server + Vytváranie priestoru… + Adresa priestoru + Povolenia priestoru + Pripojenie zlyhalo + Nastavenia účtu + Vaše kľúčové slová + Zistiť viac + Aktualizácia miestnosti + Pozvánky do miestnosti + Skupinové správy + Priame správy + My používateľské meno + Predvolené oznámenia + %1$ds ostáva + Vyžaduje sa aktualizácia + Vybrať priestory + Predvolená verzia + Adresy priestoru + Adresa priestoru + Nepomenovaná miestnosť + Súkromný priestor + Verejný priestor + Verejný priestor + Neznáma osoba + Váš server + Pridať miestnosti + Poskytnite spätnú väzbu + Spätná väzba na priestory + Spravovať miestnosti + Verejná miestnosť + Kompresia obrázku… + Vždy sa opýtať + Opustiť priestor + Pridať miestnosti + Preskúmať miestnosti + Pripojiť sa k priestoru + Vytvoriť priestor + Zdieľať odkaz + Pozvať ľudí + Vytváranie priestoru… + Pridať priestor + Navrhované miestnosti + Správa odoslaná + Adresár miestností + DOZVEDIEŤ SA VIAC + ROZUMIEM + ${app_name} Android + Žiadna zmena. + Obsah udalosti + Upraviť obsah + Vývojárske nástroje + Vymazať obrázok + Zmeniť obrázok + Otvoriť widgety + + %d záznam + %d záznamy + %d záznamov + + Verzia servera + Názov servera + Nastavenia miestnosti + Verzia miestnosti + Nová hodnota + + Pozastavený hovor + %1$d pozastavené hovory + %1$d pozastavených hovorov + + Číselník + Zavolať späť + Vyčistiť históriu + Zmeniť tému + Zmeniť povolenia + Upraviť widgety + Poslať oznámenie všetkým + Zakázať používateľov + Vylúčiť používateľov + Zmeniť nastavenia + Pozvať používateľov + Odoslať správy + Predvolená rola + Oprávnenia v miestnosti + Predvolené nastavenie systému + Zmeniť PIN + Lokálne adresy + Hlavná adresa + Zverejnené adresy + Adresy miestnosti + Adresy miestnosti + Prístup k miestnosti + Pozvať priateľov + Pridať ľudí + Vytváranie miestnosti… + Adresa miestnosti + Skryť pokročilé možnosti + Zobraziť pokročilé možnosti + QR kód + Začať konverzáciu + Priama správa + Zahodiť zmeny + Nastavenia miestnosti + Názov miestnosti + Zapnúť biometrické údaje + Nastaviť ochranu + Obnoviť všetko + Vstúpili ste. + %s sa pripojil/a. + + %d pozvánka + %d pozvánky + %d pozvánok + + + %d sekunda + %d sekundy + %d sekúnd + + Odstrániť %s\? + Telefónne čísla + Emailové adresy + Zapnúť PIN + Nový PIN + Obnoviť PIN + Zabudli ste PIN kód\? + Potvrdiť PIN kód + Odvolať pozvánku + Zoznam kontaktov + Telefónny zoznam + Nie je možné dešifrovať + Názov miestnosti + Bezpečnostná fráza + Nastaviť + Bezpečné zálohovanie + Otvoriť konverzáciu + Použiť %1$s + Aktuálny jazyk + Pozvať používateľov + Pozývanie používateľov… + Pridať členov + Overiť prihlásenie + %1$s (%2$s) + Prístupová fráza pre obnovenie + Zadajte %s + Použiť súbor + Nastavenie oznámení + Šifrovanie zapnuté + Hotovo! + Potvrdiť %s + Heslo účtu + Kľúč správy + Overovanie zrušené + Potvrdiť odstránenie + + %d hlas + %d hlasy + %d hlasov + + Údaje o účte + Vývojárske nástroje + QR kód + Inicializovať krížové podpisovanie + Nedôveryhodné + Dokončiť zabezpečenie + Spravovať relácie + Aktívne relácie + Povoliť šifrovanie + Povoliť šifrovanie\? + Iné miestnosti + Nedávne miestnosti + Opustiť miestnosť + + Jedna osoba + %1$d osoby + %1$d osôb + \ No newline at end of file From d7c9e15a73129165144a77088129979381de1577 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 3 Jan 2022 12:01:10 +0100 Subject: [PATCH 174/632] Create EpoxyCharSequence: wrapper for a CharSequence, which support mutation of the CharSequence, which can happen during rendering --- .../epoxy/charsequence/EpoxyCharSequence.kt | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 vector/src/main/java/im/vector/app/core/epoxy/charsequence/EpoxyCharSequence.kt diff --git a/vector/src/main/java/im/vector/app/core/epoxy/charsequence/EpoxyCharSequence.kt b/vector/src/main/java/im/vector/app/core/epoxy/charsequence/EpoxyCharSequence.kt new file mode 100644 index 0000000000..dcd19b598d --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/epoxy/charsequence/EpoxyCharSequence.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.core.epoxy.charsequence + +/** + * Wrapper for a CharSequence, which support mutation of the CharSequence, which can happen during rendering + */ +class EpoxyCharSequence(val charSequence: CharSequence) { + private val hash = charSequence.toString().hashCode() + + override fun hashCode() = hash + override fun equals(other: Any?) = other is EpoxyCharSequence && other.hash == hash +} From ca44d8f4d802ecdd181023675eef0ff3bbb10840 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 3 Jan 2022 13:43:41 +0100 Subject: [PATCH 175/632] Use EpoxyCharSequence for all Epoxy items Or use String if possible --- .../BottomSheetMessagePreviewItem.kt | 15 +++--- .../bottomsheet/BottomSheetRadioActionItem.kt | 4 +- .../bottomsheet/BottomSheetSendStateItem.kt | 2 +- .../app/core/epoxy/charsequence/Extensions.kt | 22 ++++++++ .../profiles/notifications/RadioButtonItem.kt | 2 +- .../BottomSheetGenericRadioAction.kt | 2 +- .../ui/list/GenericEmptyWithActionItem.kt | 4 +- .../app/core/ui/list/GenericFooterItem.kt | 2 +- .../im/vector/app/core/ui/list/GenericItem.kt | 9 ++-- .../app/core/ui/list/GenericPillItem.kt | 5 +- .../app/core/ui/list/GenericWithValueItem.kt | 7 +-- .../command/AutocompleteCommandItem.kt | 6 +-- ...eysBackupSettingsRecyclerViewController.kt | 50 +++++++++++-------- .../cancel/VerificationCancelController.kt | 14 ++++-- .../cancel/VerificationNotMeController.kt | 3 +- .../VerificationChooseMethodController.kt | 3 +- .../VerificationConclusionController.kt | 10 ++-- .../emoji/VerificationEmojiCodeController.kt | 5 +- .../BottomSheetVerificationActionItem.kt | 4 +- .../BottomSheetVerificationDecimalCodeItem.kt | 2 +- .../BottomSheetVerificationNoticeItem.kt | 5 +- .../BottomSheetVerificationWaitingItem.kt | 2 +- .../VerificationQRWaitingController.kt | 3 +- .../VerificationQrScannedByOtherController.kt | 5 +- .../request/VerificationRequestController.kt | 5 +- .../devtools/RoomStateListController.kt | 9 ++-- .../app/features/discovery/SettingsItem.kt | 2 +- .../features/form/FormAdvancedToggleItem.kt | 2 +- .../app/features/form/FormSwitchItem.kt | 2 +- .../readreceipts/DisplayReadReceiptItem.kt | 2 +- .../detail/search/SearchResultController.kt | 3 +- .../room/detail/search/SearchResultItem.kt | 5 +- .../action/MessageActionsEpoxyController.kt | 5 +- .../ViewEditHistoryEpoxyController.kt | 7 +-- .../timeline/factory/EncryptedItemFactory.kt | 3 +- .../timeline/factory/MessageItemFactory.kt | 38 +++++++------- .../timeline/factory/NoticeItemFactory.kt | 3 +- .../timeline/factory/RoomCreateItemFactory.kt | 3 +- .../detail/timeline/item/DaySeparatorItem.kt | 2 +- .../room/detail/timeline/item/DefaultItem.kt | 2 +- .../timeline/item/MessageBlockCodeItem.kt | 9 ++-- .../detail/timeline/item/MessageFileItem.kt | 2 +- .../detail/timeline/item/MessageTextItem.kt | 11 ++-- .../room/detail/timeline/item/NoticeItem.kt | 5 +- .../detail/timeline/item/RoomCreateItem.kt | 5 +- .../timeline/item/StatusTileTimelineItem.kt | 4 +- .../timeline/item/WidgetTileTimelineItem.kt | 2 +- .../reactions/ReactionInfoSimpleItem.kt | 9 ++-- .../reactions/ViewReactionsEpoxyController.kt | 3 +- .../home/room/list/RoomCategoryItem.kt | 2 +- .../home/room/list/RoomInvitationItem.kt | 2 +- .../home/room/list/RoomSummaryItem.kt | 13 ++--- .../home/room/list/RoomSummaryItemFactory.kt | 6 +-- .../poll/create/CreatePollController.kt | 5 +- .../createroom/CreateSubSpaceController.kt | 3 +- .../devices/DeviceListEpoxyController.kt | 26 +++++----- .../devices/DeviceTrustInfoEpoxyController.kt | 9 ++-- .../joinrule/RoomJoinRuleRadioAction.kt | 2 +- .../CrossSigningSettingsController.kt | 15 +++--- ...ceVerificationInfoBottomSheetController.kt | 46 ++++++++--------- .../devtools/AccountDataEpoxyController.kt | 3 +- .../GossipingTrailPagedEpoxyController.kt | 11 ++-- .../IncomingKeyRequestPagedController.kt | 5 +- .../OutgoingKeyRequestPagedController.kt | 5 +- .../HomeserverSettingsController.kt | 5 +- .../create/SpaceAdd3pidEpoxyController.kt | 3 +- .../explore/SpaceDirectoryController.kt | 3 +- .../spaces/manage/AddRoomListController.kt | 5 +- .../people/SpacePeopleListController.kt | 5 +- .../app/features/userdirectory/ActionItem.kt | 2 +- .../userdirectory/UserListController.kt | 5 +- 71 files changed, 290 insertions(+), 220 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/core/epoxy/charsequence/Extensions.kt diff --git a/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetMessagePreviewItem.kt b/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetMessagePreviewItem.kt index 46a98e6963..f6088b5eda 100644 --- a/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetMessagePreviewItem.kt +++ b/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetMessagePreviewItem.kt @@ -26,6 +26,7 @@ import im.vector.app.R import im.vector.app.core.epoxy.ClickListener import im.vector.app.core.epoxy.VectorEpoxyHolder import im.vector.app.core.epoxy.VectorEpoxyModel +import im.vector.app.core.epoxy.charsequence.EpoxyCharSequence import im.vector.app.core.epoxy.onClick import im.vector.app.core.epoxy.util.preventMutation import im.vector.app.core.extensions.setTextOrHide @@ -50,13 +51,13 @@ abstract class BottomSheetMessagePreviewItem : VectorEpoxyModel() { @EpoxyAttribute - var title: CharSequence? = null + var title: String? = null @StringRes @EpoxyAttribute @@ -47,7 +47,7 @@ abstract class BottomSheetRadioActionItem : VectorEpoxyModel() { @EpoxyAttribute - var title: CharSequence? = null + var title: String? = null @StringRes @EpoxyAttribute diff --git a/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericRadioAction.kt b/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericRadioAction.kt index 516612717a..88eaf587a8 100644 --- a/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericRadioAction.kt +++ b/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericRadioAction.kt @@ -23,7 +23,7 @@ import im.vector.app.core.platform.VectorSharedAction * Parent class for a bottom sheet action */ open class BottomSheetGenericRadioAction( - open val title: CharSequence?, + open val title: String?, open val description: String? = null, open val isSelected: Boolean ) : VectorSharedAction { diff --git a/vector/src/main/java/im/vector/app/core/ui/list/GenericEmptyWithActionItem.kt b/vector/src/main/java/im/vector/app/core/ui/list/GenericEmptyWithActionItem.kt index 359488e3f4..5801ca6b7c 100644 --- a/vector/src/main/java/im/vector/app/core/ui/list/GenericEmptyWithActionItem.kt +++ b/vector/src/main/java/im/vector/app/core/ui/list/GenericEmptyWithActionItem.kt @@ -39,10 +39,10 @@ import im.vector.app.core.extensions.setTextOrHide abstract class GenericEmptyWithActionItem : VectorEpoxyModel() { @EpoxyAttribute - var title: CharSequence? = null + var title: String? = null @EpoxyAttribute - var description: CharSequence? = null + var description: String? = null @EpoxyAttribute @DrawableRes diff --git a/vector/src/main/java/im/vector/app/core/ui/list/GenericFooterItem.kt b/vector/src/main/java/im/vector/app/core/ui/list/GenericFooterItem.kt index 8adad00a1a..323c21b76b 100644 --- a/vector/src/main/java/im/vector/app/core/ui/list/GenericFooterItem.kt +++ b/vector/src/main/java/im/vector/app/core/ui/list/GenericFooterItem.kt @@ -38,7 +38,7 @@ import im.vector.app.features.themes.ThemeUtils abstract class GenericFooterItem : VectorEpoxyModel() { @EpoxyAttribute - var text: CharSequence? = null + var text: String? = null @EpoxyAttribute var style: ItemStyle = ItemStyle.NORMAL_TEXT diff --git a/vector/src/main/java/im/vector/app/core/ui/list/GenericItem.kt b/vector/src/main/java/im/vector/app/core/ui/list/GenericItem.kt index cdfd1d75fc..3a691ddfd5 100644 --- a/vector/src/main/java/im/vector/app/core/ui/list/GenericItem.kt +++ b/vector/src/main/java/im/vector/app/core/ui/list/GenericItem.kt @@ -28,6 +28,7 @@ import im.vector.app.R import im.vector.app.core.epoxy.ClickListener import im.vector.app.core.epoxy.VectorEpoxyHolder import im.vector.app.core.epoxy.VectorEpoxyModel +import im.vector.app.core.epoxy.charsequence.EpoxyCharSequence import im.vector.app.core.epoxy.onClick import im.vector.app.core.extensions.setTextOrHide @@ -41,10 +42,10 @@ import im.vector.app.core.extensions.setTextOrHide abstract class GenericItem : VectorEpoxyModel() { @EpoxyAttribute - var title: CharSequence? = null + var title: EpoxyCharSequence? = null @EpoxyAttribute - var description: CharSequence? = null + var description: EpoxyCharSequence? = null @EpoxyAttribute var style: ItemStyle = ItemStyle.NORMAL_TEXT @@ -71,7 +72,7 @@ abstract class GenericItem : VectorEpoxyModel() { override fun bind(holder: Holder) { super.bind(holder) - holder.titleText.setTextOrHide(title) + holder.titleText.setTextOrHide(title?.charSequence) if (titleIconResourceId != -1) { holder.titleIcon.setImageResource(titleIconResourceId) @@ -82,7 +83,7 @@ abstract class GenericItem : VectorEpoxyModel() { holder.titleText.textSize = style.toTextSize() - holder.descriptionText.setTextOrHide(description) + holder.descriptionText.setTextOrHide(description?.charSequence) if (hasIndeterminateProcess) { holder.progressBar.isVisible = true diff --git a/vector/src/main/java/im/vector/app/core/ui/list/GenericPillItem.kt b/vector/src/main/java/im/vector/app/core/ui/list/GenericPillItem.kt index 451b7f086f..e116561ecb 100644 --- a/vector/src/main/java/im/vector/app/core/ui/list/GenericPillItem.kt +++ b/vector/src/main/java/im/vector/app/core/ui/list/GenericPillItem.kt @@ -28,6 +28,7 @@ import im.vector.app.R import im.vector.app.core.epoxy.ClickListener import im.vector.app.core.epoxy.VectorEpoxyHolder import im.vector.app.core.epoxy.VectorEpoxyModel +import im.vector.app.core.epoxy.charsequence.EpoxyCharSequence import im.vector.app.core.epoxy.onClick import im.vector.app.core.extensions.setTextOrHide import im.vector.app.features.themes.ThemeUtils @@ -39,7 +40,7 @@ import im.vector.app.features.themes.ThemeUtils abstract class GenericPillItem : VectorEpoxyModel() { @EpoxyAttribute - var text: CharSequence? = null + var text: EpoxyCharSequence? = null @EpoxyAttribute var style: ItemStyle = ItemStyle.NORMAL_TEXT @@ -60,7 +61,7 @@ abstract class GenericPillItem : VectorEpoxyModel() { override fun bind(holder: Holder) { super.bind(holder) - holder.textView.setTextOrHide(text) + holder.textView.setTextOrHide(text?.charSequence) holder.textView.typeface = style.toTypeFace() holder.textView.textSize = style.toTextSize() holder.textView.gravity = if (centered) Gravity.CENTER_HORIZONTAL else Gravity.START diff --git a/vector/src/main/java/im/vector/app/core/ui/list/GenericWithValueItem.kt b/vector/src/main/java/im/vector/app/core/ui/list/GenericWithValueItem.kt index 4ba403fd9a..f95281eb75 100644 --- a/vector/src/main/java/im/vector/app/core/ui/list/GenericWithValueItem.kt +++ b/vector/src/main/java/im/vector/app/core/ui/list/GenericWithValueItem.kt @@ -27,6 +27,7 @@ import im.vector.app.R import im.vector.app.core.epoxy.ClickListener import im.vector.app.core.epoxy.VectorEpoxyHolder import im.vector.app.core.epoxy.VectorEpoxyModel +import im.vector.app.core.epoxy.charsequence.EpoxyCharSequence import im.vector.app.core.epoxy.onClick import im.vector.app.core.extensions.setTextOrHide import im.vector.app.features.themes.ThemeUtils @@ -41,10 +42,10 @@ import im.vector.app.features.themes.ThemeUtils abstract class GenericWithValueItem : VectorEpoxyModel() { @EpoxyAttribute - var title: CharSequence? = null + var title: EpoxyCharSequence? = null @EpoxyAttribute - var value: CharSequence? = null + var value: String? = null @EpoxyAttribute @ColorInt @@ -62,7 +63,7 @@ abstract class GenericWithValueItem : VectorEpoxyModel() { @EpoxyAttribute - var name: CharSequence? = null + var name: String? = null @EpoxyAttribute - var parameters: CharSequence? = null + var parameters: String? = null @EpoxyAttribute - var description: CharSequence? = null + var description: String? = null @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var clickListener: ClickListener? = null diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupSettingsRecyclerViewController.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupSettingsRecyclerViewController.kt index ea0acd8be8..f007fe44f0 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupSettingsRecyclerViewController.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupSettingsRecyclerViewController.kt @@ -22,6 +22,7 @@ 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.charsequence.toEpoxyCharSequence import im.vector.app.core.epoxy.errorWithRetryItem import im.vector.app.core.epoxy.loadingItem import im.vector.app.core.resources.StringProvider @@ -73,11 +74,11 @@ class KeysBackupSettingsRecyclerViewController @Inject constructor( KeysBackupState.Disabled -> { genericItem { id("summary") - title(host.stringProvider.getString(R.string.keys_backup_settings_status_not_setup)) + title(host.stringProvider.getString(R.string.keys_backup_settings_status_not_setup).toEpoxyCharSequence()) style(ItemStyle.BIG_TEXT) if (data.keysBackupVersionTrust()?.usable == false) { - description(host.stringProvider.getString(R.string.keys_backup_settings_untrusted_backup)) + description(host.stringProvider.getString(R.string.keys_backup_settings_untrusted_backup).toEpoxyCharSequence()) } } @@ -88,12 +89,12 @@ class KeysBackupSettingsRecyclerViewController @Inject constructor( KeysBackupState.Enabling -> { genericItem { id("summary") - title(host.stringProvider.getString(R.string.keys_backup_settings_status_ko)) + title(host.stringProvider.getString(R.string.keys_backup_settings_status_ko).toEpoxyCharSequence()) style(ItemStyle.BIG_TEXT) if (data.keysBackupVersionTrust()?.usable == false) { - description(host.stringProvider.getString(R.string.keys_backup_settings_untrusted_backup)) + description(host.stringProvider.getString(R.string.keys_backup_settings_untrusted_backup).toEpoxyCharSequence()) } else { - description(keyBackupState.toString()) + description(keyBackupState.toString().toEpoxyCharSequence()) } endIconResourceId(R.drawable.unit_test_ko) } @@ -103,12 +104,12 @@ class KeysBackupSettingsRecyclerViewController @Inject constructor( KeysBackupState.ReadyToBackUp -> { genericItem { id("summary") - title(host.stringProvider.getString(R.string.keys_backup_settings_status_ok)) + title(host.stringProvider.getString(R.string.keys_backup_settings_status_ok).toEpoxyCharSequence()) style(ItemStyle.BIG_TEXT) if (data.keysBackupVersionTrust()?.usable == false) { - description(host.stringProvider.getString(R.string.keys_backup_settings_untrusted_backup)) + description(host.stringProvider.getString(R.string.keys_backup_settings_untrusted_backup).toEpoxyCharSequence()) } else { - description(host.stringProvider.getString(R.string.keys_backup_info_keys_all_backup_up)) + description(host.stringProvider.getString(R.string.keys_backup_info_keys_all_backup_up).toEpoxyCharSequence()) } endIconResourceId(R.drawable.unit_test_ok) } @@ -119,7 +120,7 @@ class KeysBackupSettingsRecyclerViewController @Inject constructor( KeysBackupState.BackingUp -> { genericItem { id("summary") - title(host.stringProvider.getString(R.string.keys_backup_settings_status_ok)) + title(host.stringProvider.getString(R.string.keys_backup_settings_status_ok).toEpoxyCharSequence()) style(ItemStyle.BIG_TEXT) hasIndeterminateProcess(true) @@ -129,10 +130,11 @@ class KeysBackupSettingsRecyclerViewController @Inject constructor( val remainingKeysToBackup = totalKeys - backedUpKeys if (data.keysBackupVersionTrust()?.usable == false) { - description(host.stringProvider.getString(R.string.keys_backup_settings_untrusted_backup)) + description(host.stringProvider.getString(R.string.keys_backup_settings_untrusted_backup).toEpoxyCharSequence()) } else { description(host.stringProvider - .getQuantityString(R.plurals.keys_backup_info_keys_backing_up, remainingKeysToBackup, remainingKeysToBackup)) + .getQuantityString(R.plurals.keys_backup_info_keys_backing_up, remainingKeysToBackup, remainingKeysToBackup) + .toEpoxyCharSequence()) } } @@ -144,14 +146,14 @@ class KeysBackupSettingsRecyclerViewController @Inject constructor( // Add infos genericItem { id("version") - title(host.stringProvider.getString(R.string.keys_backup_info_title_version)) - description(keyVersionResult?.version ?: "") + title(host.stringProvider.getString(R.string.keys_backup_info_title_version).toEpoxyCharSequence()) + description(keyVersionResult?.version.orEmpty().toEpoxyCharSequence()) } genericItem { id("algorithm") - title(host.stringProvider.getString(R.string.keys_backup_info_title_algorithm)) - description(keyVersionResult?.algorithm ?: "") + title(host.stringProvider.getString(R.string.keys_backup_info_title_algorithm).toEpoxyCharSequence()) + description(keyVersionResult?.algorithm.orEmpty().toEpoxyCharSequence()) } if (vectorPreferences.developerMode()) { @@ -189,7 +191,7 @@ class KeysBackupSettingsRecyclerViewController @Inject constructor( keysVersionTrust().signatures.forEach { genericItem { id(UUID.randomUUID().toString()) - title(host.stringProvider.getString(R.string.keys_backup_info_title_signature)) + title(host.stringProvider.getString(R.string.keys_backup_info_title_signature).toEpoxyCharSequence()) val isDeviceKnown = it.device != null val isDeviceVerified = it.device?.isVerified ?: false @@ -197,21 +199,23 @@ class KeysBackupSettingsRecyclerViewController @Inject constructor( val deviceId: String = it.deviceId ?: "" if (!isDeviceKnown) { - description(host.stringProvider.getString(R.string.keys_backup_settings_signature_from_unknown_device, deviceId)) + description(host.stringProvider.getString(R.string.keys_backup_settings_signature_from_unknown_device, deviceId).toEpoxyCharSequence()) endIconResourceId(R.drawable.e2e_warning) } else { if (isSignatureValid) { if (host.session.sessionParams.deviceId == it.deviceId) { - description(host.stringProvider.getString(R.string.keys_backup_settings_valid_signature_from_this_device)) + description(host.stringProvider.getString(R.string.keys_backup_settings_valid_signature_from_this_device).toEpoxyCharSequence()) endIconResourceId(R.drawable.e2e_verified) } else { if (isDeviceVerified) { description(host.stringProvider - .getString(R.string.keys_backup_settings_valid_signature_from_verified_device, deviceId)) + .getString(R.string.keys_backup_settings_valid_signature_from_verified_device, deviceId) + .toEpoxyCharSequence()) endIconResourceId(R.drawable.e2e_verified) } else { description(host.stringProvider - .getString(R.string.keys_backup_settings_valid_signature_from_unverified_device, deviceId)) + .getString(R.string.keys_backup_settings_valid_signature_from_unverified_device, deviceId) + .toEpoxyCharSequence()) endIconResourceId(R.drawable.e2e_warning) } } @@ -220,10 +224,12 @@ class KeysBackupSettingsRecyclerViewController @Inject constructor( endIconResourceId(R.drawable.e2e_warning) if (isDeviceVerified) { description(host.stringProvider - .getString(R.string.keys_backup_settings_invalid_signature_from_verified_device, deviceId)) + .getString(R.string.keys_backup_settings_invalid_signature_from_verified_device, deviceId) + .toEpoxyCharSequence()) } else { description(host.stringProvider - .getString(R.string.keys_backup_settings_invalid_signature_from_unverified_device, deviceId)) + .getString(R.string.keys_backup_settings_invalid_signature_from_unverified_device, deviceId) + .toEpoxyCharSequence()) } } } diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/cancel/VerificationCancelController.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/cancel/VerificationCancelController.kt index f10aa3421f..05a5bb7238 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/cancel/VerificationCancelController.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/cancel/VerificationCancelController.kt @@ -20,6 +20,8 @@ import androidx.core.text.toSpannable import com.airbnb.epoxy.EpoxyController import im.vector.app.R import im.vector.app.core.epoxy.bottomSheetDividerItem +import im.vector.app.core.epoxy.charsequence.EpoxyCharSequence +import im.vector.app.core.epoxy.charsequence.toEpoxyCharSequence import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.StringProvider import im.vector.app.core.utils.colorizeMatchingText @@ -49,12 +51,12 @@ class VerificationCancelController @Inject constructor( if (state.currentDeviceCanCrossSign) { bottomSheetVerificationNoticeItem { id("notice") - notice(host.stringProvider.getString(R.string.verify_cancel_self_verification_from_trusted)) + notice(host.stringProvider.getString(R.string.verify_cancel_self_verification_from_trusted).toEpoxyCharSequence()) } } else { bottomSheetVerificationNoticeItem { id("notice") - notice(host.stringProvider.getString(R.string.verify_cancel_self_verification_from_untrusted)) + notice(host.stringProvider.getString(R.string.verify_cancel_self_verification_from_untrusted).toEpoxyCharSequence()) } } } else { @@ -63,9 +65,11 @@ class VerificationCancelController @Inject constructor( bottomSheetVerificationNoticeItem { id("notice") notice( - host.stringProvider.getString(R.string.verify_cancel_other, otherDisplayName, otherUserID) - .toSpannable() - .colorizeMatchingText(otherUserID, host.colorProvider.getColorFromAttribute(R.attr.vctr_notice_text_color)) + EpoxyCharSequence( + host.stringProvider.getString(R.string.verify_cancel_other, otherDisplayName, otherUserID) + .toSpannable() + .colorizeMatchingText(otherUserID, host.colorProvider.getColorFromAttribute(R.attr.vctr_notice_text_color)) + ) ) } } diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/cancel/VerificationNotMeController.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/cancel/VerificationNotMeController.kt index d816f3d134..5b9c82958e 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/cancel/VerificationNotMeController.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/cancel/VerificationNotMeController.kt @@ -19,6 +19,7 @@ package im.vector.app.features.crypto.verification.cancel import com.airbnb.epoxy.EpoxyController import im.vector.app.R import im.vector.app.core.epoxy.bottomSheetDividerItem +import im.vector.app.core.epoxy.charsequence.toEpoxyCharSequence import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.StringProvider import im.vector.app.features.crypto.verification.VerificationBottomSheetViewState @@ -46,7 +47,7 @@ class VerificationNotMeController @Inject constructor( val host = this bottomSheetVerificationNoticeItem { id("notice") - notice(host.eventHtmlRenderer.render(host.stringProvider.getString(R.string.verify_not_me_self_verification))) + notice(host.eventHtmlRenderer.render(host.stringProvider.getString(R.string.verify_not_me_self_verification)).toEpoxyCharSequence()) } bottomSheetDividerItem { diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/choose/VerificationChooseMethodController.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/choose/VerificationChooseMethodController.kt index 7025343fc6..352c21a156 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/choose/VerificationChooseMethodController.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/choose/VerificationChooseMethodController.kt @@ -19,6 +19,7 @@ package im.vector.app.features.crypto.verification.choose import com.airbnb.epoxy.EpoxyController import im.vector.app.R import im.vector.app.core.epoxy.bottomSheetDividerItem +import im.vector.app.core.epoxy.charsequence.toEpoxyCharSequence import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.StringProvider import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationActionItem @@ -60,7 +61,7 @@ class VerificationChooseMethodController @Inject constructor( bottomSheetVerificationNoticeItem { id("notice") - notice(scanCodeInstructions) + notice(scanCodeInstructions.toEpoxyCharSequence()) } if (state.otherCanScanQrCode && !state.qrCodeText.isNullOrBlank()) { diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/conclusion/VerificationConclusionController.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/conclusion/VerificationConclusionController.kt index 1984a70345..1314fd6fec 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/conclusion/VerificationConclusionController.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/conclusion/VerificationConclusionController.kt @@ -19,6 +19,7 @@ package im.vector.app.features.crypto.verification.conclusion import com.airbnb.epoxy.EpoxyController import im.vector.app.R import im.vector.app.core.epoxy.bottomSheetDividerItem +import im.vector.app.core.epoxy.charsequence.toEpoxyCharSequence import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.StringProvider import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationActionItem @@ -53,7 +54,8 @@ class VerificationConclusionController @Inject constructor( id("notice") notice(host.stringProvider.getString( if (state.isSelfVerification) R.string.verification_conclusion_ok_self_notice - else R.string.verification_conclusion_ok_notice)) + else R.string.verification_conclusion_ok_notice) + .toEpoxyCharSequence()) } bottomSheetVerificationBigImageItem { @@ -66,7 +68,7 @@ class VerificationConclusionController @Inject constructor( ConclusionState.WARNING -> { bottomSheetVerificationNoticeItem { id("notice") - notice(host.stringProvider.getString(R.string.verification_conclusion_not_secure)) + notice(host.stringProvider.getString(R.string.verification_conclusion_not_secure).toEpoxyCharSequence()) } bottomSheetVerificationBigImageItem { @@ -76,7 +78,7 @@ class VerificationConclusionController @Inject constructor( bottomSheetVerificationNoticeItem { id("warning_notice") - notice(host.eventHtmlRenderer.render(host.stringProvider.getString(R.string.verification_conclusion_compromised))) + notice(host.eventHtmlRenderer.render(host.stringProvider.getString(R.string.verification_conclusion_compromised)).toEpoxyCharSequence()) } bottomDone() @@ -84,7 +86,7 @@ class VerificationConclusionController @Inject constructor( ConclusionState.CANCELLED -> { bottomSheetVerificationNoticeItem { id("notice_cancelled") - notice(host.stringProvider.getString(R.string.verify_cancelled_notice)) + notice(host.stringProvider.getString(R.string.verify_cancelled_notice).toEpoxyCharSequence()) } bottomSheetDividerItem { diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/emoji/VerificationEmojiCodeController.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/emoji/VerificationEmojiCodeController.kt index eab53ea954..838f25ddfa 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/emoji/VerificationEmojiCodeController.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/emoji/VerificationEmojiCodeController.kt @@ -21,6 +21,7 @@ import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Success import im.vector.app.R import im.vector.app.core.epoxy.bottomSheetDividerItem +import im.vector.app.core.epoxy.charsequence.toEpoxyCharSequence import im.vector.app.core.epoxy.errorWithRetryItem import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.resources.ColorProvider @@ -64,7 +65,7 @@ class VerificationEmojiCodeController @Inject constructor( is Success -> { bottomSheetVerificationNoticeItem { id("notice") - notice(host.stringProvider.getString(R.string.verification_emoji_notice)) + notice(host.stringProvider.getString(R.string.verification_emoji_notice).toEpoxyCharSequence()) } bottomSheetVerificationEmojisItem { @@ -101,7 +102,7 @@ class VerificationEmojiCodeController @Inject constructor( is Success -> { bottomSheetVerificationNoticeItem { id("notice") - notice(host.stringProvider.getString(R.string.verification_code_notice)) + notice(host.stringProvider.getString(R.string.verification_code_notice).toEpoxyCharSequence()) } bottomSheetVerificationDecimalCodeItem { diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/epoxy/BottomSheetVerificationActionItem.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/epoxy/BottomSheetVerificationActionItem.kt index 093eddb45e..7dc7a31441 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/epoxy/BottomSheetVerificationActionItem.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/epoxy/BottomSheetVerificationActionItem.kt @@ -42,10 +42,10 @@ abstract class BottomSheetVerificationActionItem : VectorEpoxyModel() { @EpoxyAttribute - var code: CharSequence = "" + var code: String = "" override fun bind(holder: Holder) { super.bind(holder) diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/epoxy/BottomSheetVerificationNoticeItem.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/epoxy/BottomSheetVerificationNoticeItem.kt index 232cdd0f4a..f63459991b 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/epoxy/BottomSheetVerificationNoticeItem.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/epoxy/BottomSheetVerificationNoticeItem.kt @@ -22,6 +22,7 @@ import com.airbnb.epoxy.EpoxyModelClass import im.vector.app.R import im.vector.app.core.epoxy.VectorEpoxyHolder import im.vector.app.core.epoxy.VectorEpoxyModel +import im.vector.app.core.epoxy.charsequence.EpoxyCharSequence /** * A action for bottom sheet. @@ -30,11 +31,11 @@ import im.vector.app.core.epoxy.VectorEpoxyModel abstract class BottomSheetVerificationNoticeItem : VectorEpoxyModel() { @EpoxyAttribute - var notice: CharSequence = "" + lateinit var notice: EpoxyCharSequence override fun bind(holder: Holder) { super.bind(holder) - holder.notice.text = notice + holder.notice.text = notice.charSequence } class Holder : VectorEpoxyHolder() { diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/epoxy/BottomSheetVerificationWaitingItem.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/epoxy/BottomSheetVerificationWaitingItem.kt index d8fc928471..46a1dd04a8 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/epoxy/BottomSheetVerificationWaitingItem.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/epoxy/BottomSheetVerificationWaitingItem.kt @@ -30,7 +30,7 @@ import im.vector.app.core.epoxy.VectorEpoxyModel abstract class BottomSheetVerificationWaitingItem : VectorEpoxyModel() { @EpoxyAttribute - var title: CharSequence = "" + var title: String = "" override fun bind(holder: Holder) { super.bind(holder) diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/qrconfirmation/VerificationQRWaitingController.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/qrconfirmation/VerificationQRWaitingController.kt index e7a8058111..cef5994c38 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/qrconfirmation/VerificationQRWaitingController.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/qrconfirmation/VerificationQRWaitingController.kt @@ -18,6 +18,7 @@ package im.vector.app.features.crypto.verification.qrconfirmation import com.airbnb.epoxy.EpoxyController import im.vector.app.R +import im.vector.app.core.epoxy.charsequence.toEpoxyCharSequence import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.StringProvider import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationBigImageItem @@ -45,7 +46,7 @@ class VerificationQRWaitingController @Inject constructor( bottomSheetVerificationNoticeItem { id("notice") apply { - notice(host.stringProvider.getString(R.string.qr_code_scanned_verif_waiting_notice)) + notice(host.stringProvider.getString(R.string.qr_code_scanned_verif_waiting_notice).toEpoxyCharSequence()) } } diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/qrconfirmation/VerificationQrScannedByOtherController.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/qrconfirmation/VerificationQrScannedByOtherController.kt index f3990842f9..bb21a6ccef 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/qrconfirmation/VerificationQrScannedByOtherController.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/qrconfirmation/VerificationQrScannedByOtherController.kt @@ -19,6 +19,7 @@ package im.vector.app.features.crypto.verification.qrconfirmation import com.airbnb.epoxy.EpoxyController import im.vector.app.R import im.vector.app.core.epoxy.bottomSheetDividerItem +import im.vector.app.core.epoxy.charsequence.toEpoxyCharSequence import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.StringProvider import im.vector.app.features.crypto.verification.VerificationBottomSheetViewState @@ -51,10 +52,10 @@ class VerificationQrScannedByOtherController @Inject constructor( id("notice") apply { if (state.isMe) { - notice(host.stringProvider.getString(R.string.qr_code_scanned_self_verif_notice)) + notice(host.stringProvider.getString(R.string.qr_code_scanned_self_verif_notice).toEpoxyCharSequence()) } else { val name = state.otherUserMxItem?.getBestName() ?: "" - notice(host.stringProvider.getString(R.string.qr_code_scanned_by_other_notice, name)) + notice(host.stringProvider.getString(R.string.qr_code_scanned_by_other_notice, name).toEpoxyCharSequence()) } } } diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/request/VerificationRequestController.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/request/VerificationRequestController.kt index 6440c0032b..d8082865c8 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/request/VerificationRequestController.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/request/VerificationRequestController.kt @@ -23,6 +23,7 @@ import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized import im.vector.app.R import im.vector.app.core.epoxy.bottomSheetDividerItem +import im.vector.app.core.epoxy.charsequence.toEpoxyCharSequence import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.StringProvider import im.vector.app.core.utils.colorizeMatchingText @@ -57,7 +58,7 @@ class VerificationRequestController @Inject constructor( if (state.hasAnyOtherSession) { bottomSheetVerificationNoticeItem { id("notice") - notice(host.stringProvider.getString(R.string.verification_open_other_to_verify)) + notice(host.stringProvider.getString(R.string.verification_open_other_to_verify).toEpoxyCharSequence()) } bottomSheetSelfWaitItem { @@ -112,7 +113,7 @@ class VerificationRequestController @Inject constructor( bottomSheetVerificationNoticeItem { id("notice") - notice(styledText) + notice(styledText.toEpoxyCharSequence()) } bottomSheetDividerItem { diff --git a/vector/src/main/java/im/vector/app/features/devtools/RoomStateListController.kt b/vector/src/main/java/im/vector/app/features/devtools/RoomStateListController.kt index 08aa119a1e..ee5ae600ff 100644 --- a/vector/src/main/java/im/vector/app/features/devtools/RoomStateListController.kt +++ b/vector/src/main/java/im/vector/app/features/devtools/RoomStateListController.kt @@ -18,6 +18,7 @@ package im.vector.app.features.devtools import com.airbnb.epoxy.TypedEpoxyController import im.vector.app.R +import im.vector.app.core.epoxy.charsequence.toEpoxyCharSequence import im.vector.app.core.epoxy.noResultItem import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.StringProvider @@ -48,8 +49,8 @@ class RoomStateListController @Inject constructor( stateEventsGroups.forEach { entry -> genericItem { id(entry.key) - title(entry.key) - description(host.stringProvider.getQuantityString(R.plurals.entries, entry.value.size, entry.value.size)) + title(entry.key.toEpoxyCharSequence()) + description(host.stringProvider.getQuantityString(R.plurals.entries, entry.value.size, entry.value.size).toEpoxyCharSequence()) itemClickAction { host.interactionListener?.processAction(RoomDevToolAction.ShowStateEventType(entry.key)) } @@ -88,8 +89,8 @@ class RoomStateListController @Inject constructor( text = stateEvent.stateKey.let { "\"$it\"" } textStyle = "normal" } - }) - description(contentJson) + }.toEpoxyCharSequence()) + description(contentJson.toEpoxyCharSequence()) itemClickAction { host.interactionListener?.processAction(RoomDevToolAction.ShowStateEvent(stateEvent)) } diff --git a/vector/src/main/java/im/vector/app/features/discovery/SettingsItem.kt b/vector/src/main/java/im/vector/app/features/discovery/SettingsItem.kt index 9726e63d39..1a14b4c9f3 100644 --- a/vector/src/main/java/im/vector/app/features/discovery/SettingsItem.kt +++ b/vector/src/main/java/im/vector/app/features/discovery/SettingsItem.kt @@ -43,7 +43,7 @@ abstract class SettingsItem : EpoxyModelWithHolder() { var descriptionResId: Int? = null @EpoxyAttribute - var description: CharSequence? = null + var description: String? = null @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var itemClickListener: ClickListener? = null diff --git a/vector/src/main/java/im/vector/app/features/form/FormAdvancedToggleItem.kt b/vector/src/main/java/im/vector/app/features/form/FormAdvancedToggleItem.kt index 08ed29b4eb..d50b429c97 100644 --- a/vector/src/main/java/im/vector/app/features/form/FormAdvancedToggleItem.kt +++ b/vector/src/main/java/im/vector/app/features/form/FormAdvancedToggleItem.kt @@ -31,7 +31,7 @@ import im.vector.app.features.themes.ThemeUtils @EpoxyModelClass(layout = R.layout.item_form_advanced_toggle) abstract class FormAdvancedToggleItem : VectorEpoxyModel() { - @EpoxyAttribute lateinit var title: CharSequence + @EpoxyAttribute lateinit var title: String @EpoxyAttribute var expanded: Boolean = false @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var listener: ClickListener? = null diff --git a/vector/src/main/java/im/vector/app/features/form/FormSwitchItem.kt b/vector/src/main/java/im/vector/app/features/form/FormSwitchItem.kt index 4cf09a1ab6..800a90135b 100644 --- a/vector/src/main/java/im/vector/app/features/form/FormSwitchItem.kt +++ b/vector/src/main/java/im/vector/app/features/form/FormSwitchItem.kt @@ -39,7 +39,7 @@ abstract class FormSwitchItem : VectorEpoxyModel() { var switchChecked: Boolean = false @EpoxyAttribute - var title: CharSequence? = null + var title: String? = null @EpoxyAttribute var summary: String? = null diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/readreceipts/DisplayReadReceiptItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/readreceipts/DisplayReadReceiptItem.kt index 7a91dae183..9e3cbef1cc 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/readreceipts/DisplayReadReceiptItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/readreceipts/DisplayReadReceiptItem.kt @@ -34,7 +34,7 @@ import org.matrix.android.sdk.api.util.MatrixItem abstract class DisplayReadReceiptItem : EpoxyModelWithHolder() { @EpoxyAttribute lateinit var matrixItem: MatrixItem - @EpoxyAttribute var timestamp: CharSequence? = null + @EpoxyAttribute var timestamp: String? = null @EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var userClicked: ClickListener? = null diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchResultController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchResultController.kt index 1b4d9faaec..342f1d9562 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchResultController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchResultController.kt @@ -26,6 +26,7 @@ import com.airbnb.epoxy.VisibilityState import im.vector.app.R import im.vector.app.core.date.DateFormatKind import im.vector.app.core.date.VectorDateFormatter +import im.vector.app.core.epoxy.charsequence.toEpoxyCharSequence import im.vector.app.core.epoxy.loadingItem import im.vector.app.core.epoxy.noResultItem import im.vector.app.core.resources.StringProvider @@ -118,7 +119,7 @@ val text = ((event.content?.get("m.new_content") as? Content) ?: event.content)? .id(eventAndSender.event.eventId) .avatarRenderer(avatarRenderer) .formattedDate(dateFormatter.format(event.originServerTs, DateFormatKind.MESSAGE_SIMPLE)) - .spannable(spannable) + .spannable(spannable.toEpoxyCharSequence()) .sender(eventAndSender.sender ?: eventAndSender.event.senderId?.let { session.getRoomMember(it, data.roomId) }?.toMatrixItem()) .listener { listener?.onItemClicked(eventAndSender.event) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchResultItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchResultItem.kt index c0f71ed6cc..9d146792d9 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchResultItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchResultItem.kt @@ -24,6 +24,7 @@ import im.vector.app.R import im.vector.app.core.epoxy.ClickListener import im.vector.app.core.epoxy.VectorEpoxyHolder import im.vector.app.core.epoxy.VectorEpoxyModel +import im.vector.app.core.epoxy.charsequence.EpoxyCharSequence import im.vector.app.core.epoxy.onClick import im.vector.app.core.extensions.setTextOrHide import im.vector.app.features.displayname.getBestName @@ -35,7 +36,7 @@ abstract class SearchResultItem : VectorEpoxyModel() { @EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer @EpoxyAttribute var formattedDate: String? = null - @EpoxyAttribute lateinit var spannable: CharSequence + @EpoxyAttribute lateinit var spannable: EpoxyCharSequence @EpoxyAttribute var sender: MatrixItem? = null @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var listener: ClickListener? = null @@ -46,7 +47,7 @@ abstract class SearchResultItem : VectorEpoxyModel() { sender?.let { avatarRenderer.render(it, holder.avatarImageView) } holder.memberNameView.setTextOrHide(sender?.getBestName()) holder.timeView.text = formattedDate - holder.contentView.text = spannable + holder.contentView.text = spannable.charSequence } class Holder : VectorEpoxyHolder() { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt index 3826c4cbad..15f33db6ff 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt @@ -27,6 +27,7 @@ import im.vector.app.core.epoxy.bottomsheet.bottomSheetActionItem import im.vector.app.core.epoxy.bottomsheet.bottomSheetMessagePreviewItem import im.vector.app.core.epoxy.bottomsheet.bottomSheetQuickReactionsItem import im.vector.app.core.epoxy.bottomsheet.bottomSheetSendStateItem +import im.vector.app.core.epoxy.charsequence.toEpoxyCharSequence import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.resources.StringProvider import im.vector.app.core.utils.DimensionConverter @@ -77,8 +78,8 @@ class MessageActionsEpoxyController @Inject constructor( data(state.timelineEvent()?.buildImageContentRendererData(host.dimensionConverter.dpToPx(66))) userClicked { host.listener?.didSelectMenuAction(EventSharedAction.OpenUserProfile(state.informationData.senderId)) } bindingOptions(bindingOptions) - body(body) - bodyDetails(host.eventDetailsFormatter.format(state.timelineEvent()?.root)) + body(body.toEpoxyCharSequence()) + bodyDetails(host.eventDetailsFormatter.format(state.timelineEvent()?.root)?.toEpoxyCharSequence()) time(formattedDate) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryEpoxyController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryEpoxyController.kt index ed9f831018..96d139f11d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryEpoxyController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryEpoxyController.kt @@ -23,6 +23,7 @@ import com.airbnb.mvrx.Success import im.vector.app.R import im.vector.app.core.date.DateFormatKind import im.vector.app.core.date.VectorDateFormatter +import im.vector.app.core.epoxy.charsequence.toEpoxyCharSequence import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.StringProvider import im.vector.app.core.ui.list.genericFooterItem @@ -75,7 +76,7 @@ class ViewEditHistoryEpoxyController @Inject constructor( if (sourceEvents.isEmpty()) { genericItem { id("footer") - title(host.stringProvider.getString(R.string.no_message_edits_found)) + title(host.stringProvider.getString(R.string.no_message_edits_found).toEpoxyCharSequence()) } } else { var lastDate: Calendar? = null @@ -133,8 +134,8 @@ class ViewEditHistoryEpoxyController @Inject constructor( } genericItem { id(timelineEvent.eventId) - title(host.dateFormatter.format(timelineEvent.originServerTs, DateFormatKind.EDIT_HISTORY_ROW)) - description(spannedDiff ?: body) + title(host.dateFormatter.format(timelineEvent.originServerTs, DateFormatKind.EDIT_HISTORY_ROW).toEpoxyCharSequence()) + description((spannedDiff ?: body).toEpoxyCharSequence()) } } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt index 662846704c..b8d7d96ecf 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt @@ -18,6 +18,7 @@ package im.vector.app.features.home.room.detail.timeline.factory import im.vector.app.R import im.vector.app.core.epoxy.VectorEpoxyModel +import im.vector.app.core.epoxy.charsequence.toEpoxyCharSequence import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.DrawableProvider import im.vector.app.core.resources.StringProvider @@ -110,7 +111,7 @@ class EncryptedItemFactory @Inject constructor(private val messageInformationDat .leftGuideline(avatarSizeProvider.leftGuideline) .highlighted(params.isHighlighted) .attributes(attributes) - .message(spannableStr) + .message(spannableStr.toEpoxyCharSequence()) .movementMethod(createLinkMovementMethod(params.callback)) } else -> null 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 28b2d98909..e0d6e46084 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 @@ -28,6 +28,7 @@ import dagger.Lazy import im.vector.app.R import im.vector.app.core.epoxy.ClickListener import im.vector.app.core.epoxy.VectorEpoxyModel +import im.vector.app.core.epoxy.charsequence.toEpoxyCharSequence import im.vector.app.core.files.LocalFilesHelper import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.StringProvider @@ -501,14 +502,14 @@ class MessageItemFactory @Inject constructor( val bindingOptions = spanUtils.getBindingOptions(body) val linkifiedBody = body.linkify(callback) - return MessageTextItem_().apply { - if (informationData.hasBeenEdited) { - val spannable = annotateWithEdited(linkifiedBody, callback, informationData) - message(spannable) - } else { - message(linkifiedBody) - } - } + return MessageTextItem_() + .message( + if (informationData.hasBeenEdited) { + annotateWithEdited(linkifiedBody, callback, informationData) + } else { + linkifiedBody + }.toEpoxyCharSequence() + ) .useBigFont(linkifiedBody.length <= MAX_NUMBER_OF_EMOJI_FOR_BIG_FONT * 2 && containsOnlyEmojis(linkifiedBody.toString())) .bindingOptions(bindingOptions) .searchForPills(isFormatted) @@ -530,13 +531,13 @@ class MessageItemFactory @Inject constructor( .apply { if (informationData.hasBeenEdited) { val spannable = annotateWithEdited("", callback, informationData) - editedSpan(spannable) + editedSpan(spannable.toEpoxyCharSequence()) } } .leftGuideline(avatarSizeProvider.leftGuideline) .attributes(attributes) .highlighted(highlight) - .message(formattedBody) + .message(formattedBody.toEpoxyCharSequence()) } private fun annotateWithEdited(linkifiedBody: CharSequence, @@ -599,7 +600,7 @@ class MessageItemFactory @Inject constructor( .imageContentRenderer(imageContentRenderer) .previewUrlCallback(callback) .attributes(attributes) - .message(message) + .message(message.toEpoxyCharSequence()) .bindingOptions(bindingOptions) .highlighted(highlight) .movementMethod(createLinkMovementMethod(callback)) @@ -617,14 +618,13 @@ class MessageItemFactory @Inject constructor( val message = formattedBody.linkify(callback) return MessageTextItem_() - .apply { - if (informationData.hasBeenEdited) { - val spannable = annotateWithEdited(message, callback, informationData) - message(spannable) - } else { - message(message) - } - } + .message( + if (informationData.hasBeenEdited) { + annotateWithEdited(message, callback, informationData) + } else { + message + }.toEpoxyCharSequence() + ) .bindingOptions(bindingOptions) .leftGuideline(avatarSizeProvider.leftGuideline) .previewUrlRetriever(callback?.getPreviewUrlRetriever()) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/NoticeItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/NoticeItemFactory.kt index ed6620dcd4..70c8fa7574 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/NoticeItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/NoticeItemFactory.kt @@ -16,6 +16,7 @@ package im.vector.app.features.home.room.detail.timeline.factory +import im.vector.app.core.epoxy.charsequence.EpoxyCharSequence import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.room.detail.timeline.format.NoticeEventFormatter import im.vector.app.features.home.room.detail.timeline.helper.AvatarSizeProvider @@ -37,7 +38,7 @@ class NoticeItemFactory @Inject constructor(private val eventFormatter: NoticeEv val attributes = NoticeItem.Attributes( avatarRenderer = avatarRenderer, informationData = informationData, - noticeText = formattedText, + noticeText = EpoxyCharSequence(formattedText), itemLongClickListener = { view -> params.callback?.onEventLongClicked(informationData, null, view) ?: false }, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/RoomCreateItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/RoomCreateItemFactory.kt index 523fb8e682..3a3d269058 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/RoomCreateItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/RoomCreateItemFactory.kt @@ -18,6 +18,7 @@ package im.vector.app.features.home.room.detail.timeline.factory import im.vector.app.R import im.vector.app.core.epoxy.VectorEpoxyModel +import im.vector.app.core.epoxy.charsequence.toEpoxyCharSequence import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.UserPreferencesProvider import im.vector.app.features.home.room.detail.timeline.item.RoomCreateItem_ @@ -46,7 +47,7 @@ class RoomCreateItemFactory @Inject constructor(private val stringProvider: Stri } } return RoomCreateItem_() - .text(text) + .text(text.toEpoxyCharSequence()) } private fun defaultRendering(params: TimelineItemFactoryParams): VectorEpoxyModel<*>? { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/DaySeparatorItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/DaySeparatorItem.kt index c63ec36205..4c9664e612 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/DaySeparatorItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/DaySeparatorItem.kt @@ -26,7 +26,7 @@ import im.vector.app.core.epoxy.VectorEpoxyHolder @EpoxyModelClass(layout = R.layout.item_timeline_event_day_separator) abstract class DaySeparatorItem : EpoxyModelWithHolder() { - @EpoxyAttribute lateinit var formattedDay: CharSequence + @EpoxyAttribute lateinit var formattedDay: String override fun bind(holder: Holder) { super.bind(holder) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/DefaultItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/DefaultItem.kt index e6c6e1d372..9d0a3b9a9c 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/DefaultItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/DefaultItem.kt @@ -56,7 +56,7 @@ abstract class DefaultItem : BaseEventItem() { data class Attributes( val avatarRenderer: AvatarRenderer, val informationData: MessageInformationData, - val text: CharSequence, + val text: String, val itemLongClickListener: View.OnLongClickListener? = null ) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageBlockCodeItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageBlockCodeItem.kt index 3f6add5699..be9b727017 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageBlockCodeItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageBlockCodeItem.kt @@ -20,6 +20,7 @@ import android.widget.TextView import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass import im.vector.app.R +import im.vector.app.core.epoxy.charsequence.EpoxyCharSequence import im.vector.app.core.epoxy.onClick import im.vector.app.core.extensions.setTextOrHide import me.saket.bettermovementmethod.BetterLinkMovementMethod @@ -28,19 +29,19 @@ import me.saket.bettermovementmethod.BetterLinkMovementMethod abstract class MessageBlockCodeItem : AbsMessageItem() { @EpoxyAttribute - var message: CharSequence? = null + var message: EpoxyCharSequence? = null @EpoxyAttribute - var editedSpan: CharSequence? = null + var editedSpan: EpoxyCharSequence? = null override fun bind(holder: Holder) { super.bind(holder) - holder.messageView.text = message + holder.messageView.text = message?.charSequence renderSendState(holder.messageView, holder.messageView) holder.messageView.onClick(attributes.itemClickListener) holder.messageView.setOnLongClickListener(attributes.itemLongClickListener) holder.editedView.movementMethod = BetterLinkMovementMethod.getInstance() - holder.editedView.setTextOrHide(editedSpan) + holder.editedView.setTextOrHide(editedSpan?.charSequence) } override fun getViewType() = STUB_ID 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 9893a1f5bc..b15f909b79 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 @@ -34,7 +34,7 @@ import im.vector.app.features.home.room.detail.timeline.helper.ContentUploadStat abstract class MessageFileItem : AbsMessageItem() { @EpoxyAttribute - var filename: CharSequence = "" + var filename: String = "" @EpoxyAttribute var mxcUrl: String = "" diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt index 276008f09e..db7a4ff683 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt @@ -24,6 +24,7 @@ import androidx.core.widget.TextViewCompat import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass import im.vector.app.R +import im.vector.app.core.epoxy.charsequence.EpoxyCharSequence import im.vector.app.core.epoxy.onClick import im.vector.app.core.epoxy.onLongClickIgnoringLinks import im.vector.app.core.epoxy.util.preventMutation @@ -42,7 +43,7 @@ abstract class MessageTextItem : AbsMessageItem() { var searchForPills: Boolean = false @EpoxyAttribute - var message: CharSequence? = null + var message: EpoxyCharSequence? = null @EpoxyAttribute var bindingOptions: BindingOptions? = null @@ -82,14 +83,14 @@ abstract class MessageTextItem : AbsMessageItem() { holder.messageView.textSize = 14F } if (searchForPills) { - message?.findPillsAndProcess(coroutineScope) { + message?.charSequence?.findPillsAndProcess(coroutineScope) { // mmm.. not sure this is so safe in regards to cell reuse it.bind(holder.messageView) } } val textFuture = if (bindingOptions?.canUseTextFuture.orFalse()) { PrecomputedTextCompat.getTextFuture( - message ?: "", + message?.charSequence ?: "", TextViewCompat.getTextMetricsParams(holder.messageView), null) } else { @@ -105,9 +106,9 @@ abstract class MessageTextItem : AbsMessageItem() { holder.messageView.setTextFuture(textFuture) } else { holder.messageView.text = if (bindingOptions?.preventMutation.orFalse()) { - message.preventMutation() + message?.charSequence.preventMutation() } else { - message + message?.charSequence } } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/NoticeItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/NoticeItem.kt index 4876e8e500..2851668df5 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/NoticeItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/NoticeItem.kt @@ -23,6 +23,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.charsequence.EpoxyCharSequence import im.vector.app.core.epoxy.onClick import im.vector.app.core.ui.views.ShieldImageView import im.vector.app.features.home.AvatarRenderer @@ -37,7 +38,7 @@ abstract class NoticeItem : BaseEventItem() { override fun bind(holder: Holder) { super.bind(holder) - holder.noticeTextView.text = attributes.noticeText + holder.noticeTextView.text = attributes.noticeText.charSequence attributes.avatarRenderer.render(attributes.informationData.matrixItem, holder.avatarImageView) holder.view.setOnLongClickListener(attributes.itemLongClickListener) holder.avatarImageView.onClick(attributes.avatarClickListener) @@ -74,7 +75,7 @@ abstract class NoticeItem : BaseEventItem() { data class Attributes( val avatarRenderer: AvatarRenderer, val informationData: MessageInformationData, - val noticeText: CharSequence, + val noticeText: EpoxyCharSequence, val itemLongClickListener: View.OnLongClickListener? = null, val readReceiptsCallback: TimelineEventController.ReadReceiptsCallback? = null, val avatarClickListener: ClickListener? = null diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/RoomCreateItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/RoomCreateItem.kt index 8ad3bb0cf0..34edcfe9f4 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/RoomCreateItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/RoomCreateItem.kt @@ -22,17 +22,18 @@ import com.airbnb.epoxy.EpoxyModelClass import im.vector.app.R import im.vector.app.core.epoxy.VectorEpoxyHolder import im.vector.app.core.epoxy.VectorEpoxyModel +import im.vector.app.core.epoxy.charsequence.EpoxyCharSequence import me.saket.bettermovementmethod.BetterLinkMovementMethod @EpoxyModelClass(layout = R.layout.item_timeline_event_create) abstract class RoomCreateItem : VectorEpoxyModel() { - @EpoxyAttribute lateinit var text: CharSequence + @EpoxyAttribute lateinit var text: EpoxyCharSequence override fun bind(holder: Holder) { super.bind(holder) holder.description.movementMethod = BetterLinkMovementMethod.getInstance() - holder.description.text = text + holder.description.text = text.charSequence } class Holder : VectorEpoxyHolder() { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/StatusTileTimelineItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/StatusTileTimelineItem.kt index c76e2b230a..6531efb82d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/StatusTileTimelineItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/StatusTileTimelineItem.kt @@ -83,8 +83,8 @@ abstract class StatusTileTimelineItem : AbsBaseMessageItem() { @EpoxyAttribute - lateinit var reactionKey: CharSequence + lateinit var reactionKey: EpoxyCharSequence @EpoxyAttribute - lateinit var authorDisplayName: CharSequence + lateinit var authorDisplayName: String @EpoxyAttribute - var timeStamp: CharSequence? = null + var timeStamp: String? = null @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var userClicked: ClickListener? = null override fun bind(holder: Holder) { super.bind(holder) - holder.emojiReactionView.text = reactionKey.preventMutation() + holder.emojiReactionView.text = reactionKey.charSequence.preventMutation() holder.displayNameView.text = authorDisplayName timeStamp?.let { holder.timeStampView.text = it diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reactions/ViewReactionsEpoxyController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reactions/ViewReactionsEpoxyController.kt index 0031cf9feb..0cdb31a8fd 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reactions/ViewReactionsEpoxyController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reactions/ViewReactionsEpoxyController.kt @@ -22,6 +22,7 @@ import com.airbnb.mvrx.Incomplete import com.airbnb.mvrx.Success import im.vector.app.EmojiSpanify import im.vector.app.R +import im.vector.app.core.epoxy.charsequence.toEpoxyCharSequence import im.vector.app.core.resources.StringProvider import im.vector.app.core.ui.list.genericFooterItem import im.vector.app.core.ui.list.genericLoaderItem @@ -56,7 +57,7 @@ class ViewReactionsEpoxyController @Inject constructor( reactionInfoSimpleItem { id(reactionInfo.eventId) timeStamp(reactionInfo.timestamp) - reactionKey(host.emojiSpanify.spanify(reactionInfo.reactionKey)) + reactionKey(host.emojiSpanify.spanify(reactionInfo.reactionKey).toEpoxyCharSequence()) authorDisplayName(reactionInfo.authorName ?: reactionInfo.authorId) userClicked { host.listener?.didSelectUser(reactionInfo.authorId) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomCategoryItem.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomCategoryItem.kt index 5840870d46..bec3ccc643 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomCategoryItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomCategoryItem.kt @@ -32,7 +32,7 @@ import im.vector.app.features.themes.ThemeUtils @EpoxyModelClass(layout = R.layout.item_room_category) abstract class RoomCategoryItem : VectorEpoxyModel() { - @EpoxyAttribute lateinit var title: CharSequence + @EpoxyAttribute lateinit var title: String @EpoxyAttribute var expanded: Boolean = false @EpoxyAttribute var unreadNotificationCount: Int = 0 @EpoxyAttribute var showHighlighted: Boolean = false diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomInvitationItem.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomInvitationItem.kt index 4413776636..28cc9a9bd0 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomInvitationItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomInvitationItem.kt @@ -39,7 +39,7 @@ abstract class RoomInvitationItem : VectorEpoxyModel( @EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer @EpoxyAttribute lateinit var matrixItem: MatrixItem - @EpoxyAttribute var secondLine: CharSequence? = null + @EpoxyAttribute var secondLine: String? = null @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var listener: ClickListener? = null @EpoxyAttribute lateinit var changeMembershipState: ChangeMembershipState @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var acceptListener: ClickListener? = null diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItem.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItem.kt index fd0ccd17e9..4261acd7ee 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItem.kt @@ -30,6 +30,7 @@ import im.vector.app.R import im.vector.app.core.epoxy.ClickListener import im.vector.app.core.epoxy.VectorEpoxyHolder import im.vector.app.core.epoxy.VectorEpoxyModel +import im.vector.app.core.epoxy.charsequence.EpoxyCharSequence import im.vector.app.core.epoxy.onClick import im.vector.app.core.extensions.setTextOrHide import im.vector.app.core.ui.views.PresenceStateImageView @@ -44,16 +45,12 @@ import org.matrix.android.sdk.api.util.MatrixItem @EpoxyModelClass(layout = R.layout.item_room) abstract class RoomSummaryItem : VectorEpoxyModel() { - @EpoxyAttribute lateinit var typingMessage: CharSequence + @EpoxyAttribute lateinit var typingMessage: String @EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer @EpoxyAttribute lateinit var matrixItem: MatrixItem - // Used only for diff calculation - @EpoxyAttribute lateinit var lastEvent: String - - // We use DoNotHash here as Spans are not implementing equals/hashcode - @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) lateinit var lastFormattedEvent: CharSequence - @EpoxyAttribute lateinit var lastEventTime: CharSequence + @EpoxyAttribute lateinit var lastFormattedEvent: EpoxyCharSequence + @EpoxyAttribute lateinit var lastEventTime: String @EpoxyAttribute var encryptionTrustLevel: RoomEncryptionTrustLevel? = null @EpoxyAttribute var userPresence: UserPresence? = null @EpoxyAttribute var showPresence: Boolean = false @@ -76,7 +73,7 @@ abstract class RoomSummaryItem : VectorEpoxyModel() { } holder.titleView.text = matrixItem.getBestName() holder.lastEventTimeView.text = lastEventTime - holder.lastEventView.text = lastFormattedEvent + holder.lastEventView.text = lastFormattedEvent.charSequence holder.unreadCounterBadgeView.render(UnreadCounterBadgeView.State(unreadNotificationCount, showHighlighted)) holder.unreadIndentIndicator.isVisible = hasUnreadMessage holder.draftView.isVisible = hasDraft diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt index fdb7d3a323..bf289c2e7d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt @@ -23,6 +23,7 @@ import im.vector.app.R import im.vector.app.core.date.DateFormatKind import im.vector.app.core.date.VectorDateFormatter import im.vector.app.core.epoxy.VectorEpoxyModel +import im.vector.app.core.epoxy.charsequence.toEpoxyCharSequence import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.resources.StringProvider import im.vector.app.features.home.AvatarRenderer @@ -111,7 +112,7 @@ class RoomSummaryItemFactory @Inject constructor(private val displayableEventFor val showHighlighted = roomSummary.highlightCount > 0 val showSelected = selectedRoomIds.contains(roomSummary.roomId) var latestFormattedEvent: CharSequence = "" - var latestEventTime: CharSequence = "" + var latestEventTime = "" val latestEvent = roomSummary.latestPreviewableEvent if (latestEvent != null) { latestFormattedEvent = displayableEventFormatter.format(latestEvent, roomSummary.isDirect, roomSummary.isDirect.not()) @@ -129,8 +130,7 @@ class RoomSummaryItemFactory @Inject constructor(private val displayableEventFor .matrixItem(roomSummary.toMatrixItem()) .lastEventTime(latestEventTime) .typingMessage(typingMessage) - .lastEvent(latestFormattedEvent.toString()) - .lastFormattedEvent(latestFormattedEvent) + .lastFormattedEvent(latestFormattedEvent.toEpoxyCharSequence()) .showHighlighted(showHighlighted) .showSelected(showSelected) .hasFailedSending(roomSummary.hasFailedSending) diff --git a/vector/src/main/java/im/vector/app/features/poll/create/CreatePollController.kt b/vector/src/main/java/im/vector/app/features/poll/create/CreatePollController.kt index 94a3d0c7d3..90128d9263 100644 --- a/vector/src/main/java/im/vector/app/features/poll/create/CreatePollController.kt +++ b/vector/src/main/java/im/vector/app/features/poll/create/CreatePollController.kt @@ -20,6 +20,7 @@ import android.view.Gravity import android.view.inputmethod.EditorInfo import com.airbnb.epoxy.EpoxyController import im.vector.app.R +import im.vector.app.core.epoxy.charsequence.toEpoxyCharSequence import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.StringProvider import im.vector.app.core.ui.list.ItemStyle @@ -49,7 +50,7 @@ class CreatePollController @Inject constructor( genericItem { id("question_title") style(ItemStyle.BIG_TEXT) - title(host.stringProvider.getString(R.string.create_poll_question_title)) + title(host.stringProvider.getString(R.string.create_poll_question_title).toEpoxyCharSequence()) } val questionImeAction = if (currentState.options.isEmpty()) EditorInfo.IME_ACTION_DONE else EditorInfo.IME_ACTION_NEXT @@ -69,7 +70,7 @@ class CreatePollController @Inject constructor( genericItem { id("options_title") style(ItemStyle.BIG_TEXT) - title(host.stringProvider.getString(R.string.create_poll_options_title)) + title(host.stringProvider.getString(R.string.create_poll_options_title).toEpoxyCharSequence()) } currentState.options.forEachIndexed { index, option -> diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateSubSpaceController.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateSubSpaceController.kt index 26ea2f30a3..c8d907b0b2 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateSubSpaceController.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateSubSpaceController.kt @@ -20,6 +20,7 @@ import com.airbnb.epoxy.TypedEpoxyController import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Loading import im.vector.app.R +import im.vector.app.core.epoxy.charsequence.toEpoxyCharSequence import im.vector.app.core.epoxy.profiles.buildProfileAction import im.vector.app.core.resources.StringProvider import im.vector.app.core.ui.list.genericPillItem @@ -52,7 +53,7 @@ class CreateSubSpaceController @Inject constructor( id("beta") imageRes(R.drawable.ic_beta_pill) tintIcon(false) - text(host.stringProvider.getString(R.string.space_add_space_to_any_space_you_manage)) + text(host.stringProvider.getString(R.string.space_add_space_to_any_space_you_manage).toEpoxyCharSequence()) } formEditableSquareAvatarItem { diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListEpoxyController.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListEpoxyController.kt index 0325cb132e..83a4e614ec 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListEpoxyController.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListEpoxyController.kt @@ -22,6 +22,7 @@ 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.charsequence.toEpoxyCharSequence import im.vector.app.core.epoxy.errorWithRetryItem import im.vector.app.core.epoxy.loadingItem import im.vector.app.core.resources.ColorProvider @@ -40,7 +41,7 @@ class DeviceListEpoxyController @Inject constructor(private val stringProvider: private val colorProvider: ColorProvider, private val dimensionConverter: DimensionConverter, private val vectorPreferences: VectorPreferences) : - TypedEpoxyController() { + TypedEpoxyController() { interface InteractionListener { fun onDeviceSelected(device: CryptoDeviceInfo) @@ -75,11 +76,11 @@ class DeviceListEpoxyController @Inject constructor(private val stringProvider: style(ItemStyle.BIG_TEXT) titleIconResourceId(if (allGreen) R.drawable.ic_shield_trusted else R.drawable.ic_shield_warning) title( - host.stringProvider.getString( - if (allGreen) R.string.verification_profile_verified else R.string.verification_profile_warning - ) + host.stringProvider + .getString(if (allGreen) R.string.verification_profile_verified else R.string.verification_profile_warning) + .toEpoxyCharSequence() ) - description(host.stringProvider.getString(R.string.verification_conclusion_ok_notice)) + description(host.stringProvider.getString(R.string.verification_conclusion_ok_notice).toEpoxyCharSequence()) } if (vectorPreferences.developerMode()) { @@ -90,7 +91,7 @@ class DeviceListEpoxyController @Inject constructor(private val stringProvider: genericItem { id("sessions") style(ItemStyle.BIG_TEXT) - title(host.stringProvider.getString(R.string.room_member_profile_sessions_section_title)) + title(host.stringProvider.getString(R.string.room_member_profile_sessions_section_title).toEpoxyCharSequence()) } if (deviceList.isEmpty()) { // Can this really happen? @@ -105,7 +106,7 @@ class DeviceListEpoxyController @Inject constructor(private val stringProvider: id(device.deviceId) titleIconResourceId(if (device.isVerified) R.drawable.ic_shield_trusted else R.drawable.ic_shield_warning) apply { - if (host.vectorPreferences.developerMode()) { + val title = if (host.vectorPreferences.developerMode()) { val seq = span { +(device.displayName() ?: device.deviceId) +"\n" @@ -115,10 +116,11 @@ class DeviceListEpoxyController @Inject constructor(private val stringProvider: textSize = host.dimensionConverter.spToPx(14) } } - title(seq) + seq } else { - title(device.displayName() ?: device.deviceId) + device.displayName() ?: device.deviceId } + title(title.toEpoxyCharSequence()) } value( host.stringProvider.getString( @@ -163,7 +165,7 @@ class DeviceListEpoxyController @Inject constructor(private val stringProvider: textColor = host.colorProvider.getColorFromAttribute(R.attr.vctr_content_secondary) textSize = host.dimensionConverter.spToPx(12) } - } + }.toEpoxyCharSequence() ) } } @@ -179,7 +181,7 @@ class DeviceListEpoxyController @Inject constructor(private val stringProvider: textColor = host.colorProvider.getColorFromAttribute(R.attr.vctr_content_secondary) textSize = host.dimensionConverter.spToPx(12) } - } + }.toEpoxyCharSequence() ) } } @@ -195,7 +197,7 @@ class DeviceListEpoxyController @Inject constructor(private val stringProvider: textColor = host.colorProvider.getColorFromAttribute(R.attr.vctr_content_secondary) textSize = host.dimensionConverter.spToPx(12) } - } + }.toEpoxyCharSequence() ) } } diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceTrustInfoEpoxyController.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceTrustInfoEpoxyController.kt index bce219a711..c700842c19 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceTrustInfoEpoxyController.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceTrustInfoEpoxyController.kt @@ -18,6 +18,7 @@ package im.vector.app.features.roommemberprofile.devices import com.airbnb.epoxy.TypedEpoxyController import im.vector.app.R +import im.vector.app.core.epoxy.charsequence.toEpoxyCharSequence import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.StringProvider import im.vector.app.core.ui.list.ItemStyle @@ -52,9 +53,9 @@ class DeviceTrustInfoEpoxyController @Inject constructor(private val stringProvi style(ItemStyle.BIG_TEXT) titleIconResourceId(if (isVerified) R.drawable.ic_shield_trusted else R.drawable.ic_shield_warning) title( - host.stringProvider.getString( - if (isVerified) R.string.verification_profile_verified else R.string.verification_profile_warning - ) + host.stringProvider + .getString(if (isVerified) R.string.verification_profile_verified else R.string.verification_profile_warning) + .toEpoxyCharSequence() ) } genericFooterItem { @@ -88,7 +89,7 @@ class DeviceTrustInfoEpoxyController @Inject constructor(private val stringProvi textColor = host.colorProvider.getColorFromAttribute(R.attr.vctr_content_secondary) textSize = host.dimensionConverter.spToPx(14) } - } + }.toEpoxyCharSequence() ) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleRadioAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleRadioAction.kt index cdeb49f9ef..608d5489e0 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleRadioAction.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleRadioAction.kt @@ -21,7 +21,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomJoinRules class RoomJoinRuleRadioAction( val roomJoinRule: RoomJoinRules, - title: CharSequence, + title: String, description: String, isSelected: Boolean ) : BottomSheetGenericRadioAction( diff --git a/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsController.kt b/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsController.kt index 649321474a..d00cb783c9 100644 --- a/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsController.kt +++ b/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsController.kt @@ -17,6 +17,7 @@ package im.vector.app.features.settings.crosssigning import com.airbnb.epoxy.TypedEpoxyController import im.vector.app.R +import im.vector.app.core.epoxy.charsequence.toEpoxyCharSequence import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.StringProvider import im.vector.app.core.ui.list.genericButtonItem @@ -47,7 +48,7 @@ class CrossSigningSettingsController @Inject constructor( genericItem { id("can") titleIconResourceId(R.drawable.ic_shield_trusted) - title(host.stringProvider.getString(R.string.encryption_information_dg_xsigning_complete)) + title(host.stringProvider.getString(R.string.encryption_information_dg_xsigning_complete).toEpoxyCharSequence()) } genericButtonItem { id("Reset") @@ -61,7 +62,7 @@ class CrossSigningSettingsController @Inject constructor( genericItem { id("trusted") titleIconResourceId(R.drawable.ic_shield_custom) - title(host.stringProvider.getString(R.string.encryption_information_dg_xsigning_trusted)) + title(host.stringProvider.getString(R.string.encryption_information_dg_xsigning_trusted).toEpoxyCharSequence()) } genericButtonItem { id("Reset") @@ -75,7 +76,7 @@ class CrossSigningSettingsController @Inject constructor( genericItem { id("enable") titleIconResourceId(R.drawable.ic_shield_black) - title(host.stringProvider.getString(R.string.encryption_information_dg_xsigning_not_trusted)) + title(host.stringProvider.getString(R.string.encryption_information_dg_xsigning_not_trusted).toEpoxyCharSequence()) } genericButtonItem { id("Reset") @@ -88,7 +89,7 @@ class CrossSigningSettingsController @Inject constructor( else -> { genericItem { id("not") - title(host.stringProvider.getString(R.string.encryption_information_dg_xsigning_disabled)) + title(host.stringProvider.getString(R.string.encryption_information_dg_xsigning_disabled).toEpoxyCharSequence()) } genericPositiveButtonItem { @@ -115,7 +116,7 @@ class CrossSigningSettingsController @Inject constructor( textColor = host.colorProvider.getColorFromAttribute(R.attr.vctr_content_secondary) textSize = host.dimensionConverter.spToPx(12) } - } + }.toEpoxyCharSequence() ) } } @@ -131,7 +132,7 @@ class CrossSigningSettingsController @Inject constructor( textColor = host.colorProvider.getColorFromAttribute(R.attr.vctr_content_secondary) textSize = host.dimensionConverter.spToPx(12) } - } + }.toEpoxyCharSequence() ) } } @@ -147,7 +148,7 @@ class CrossSigningSettingsController @Inject constructor( textColor = host.colorProvider.getColorFromAttribute(R.attr.vctr_content_secondary) textSize = host.dimensionConverter.spToPx(12) } - } + }.toEpoxyCharSequence() ) } } diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetController.kt b/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetController.kt index c109920cd6..22e1e67aa4 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetController.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetController.kt @@ -18,6 +18,7 @@ package im.vector.app.features.settings.devices import com.airbnb.epoxy.TypedEpoxyController import im.vector.app.R import im.vector.app.core.epoxy.bottomSheetDividerItem +import im.vector.app.core.epoxy.charsequence.toEpoxyCharSequence import im.vector.app.core.epoxy.loadingItem import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.StringProvider @@ -34,7 +35,7 @@ import javax.inject.Inject class DeviceVerificationInfoBottomSheetController @Inject constructor( private val stringProvider: StringProvider, private val colorProvider: ColorProvider) : - TypedEpoxyController() { + TypedEpoxyController() { var callback: Callback? = null @@ -88,8 +89,8 @@ class DeviceVerificationInfoBottomSheetController @Inject constructor( id("trust${cryptoDeviceInfo.deviceId}") style(ItemStyle.BIG_TEXT) titleIconResourceId(shield) - title(host.stringProvider.getString(R.string.encryption_information_verified)) - description(host.stringProvider.getString(R.string.settings_active_sessions_verified_device_desc)) + title(host.stringProvider.getString(R.string.encryption_information_verified).toEpoxyCharSequence()) + description(host.stringProvider.getString(R.string.settings_active_sessions_verified_device_desc).toEpoxyCharSequence()) } } else if (data.canVerifySession) { // You need to complete security, only if there are other session(s) available, or if 4S contains secrets @@ -97,12 +98,11 @@ class DeviceVerificationInfoBottomSheetController @Inject constructor( id("trust${cryptoDeviceInfo.deviceId}") style(ItemStyle.BIG_TEXT) titleIconResourceId(shield) - title(host.stringProvider.getString(R.string.crosssigning_verify_this_session)) - if (data.hasOtherSessions) { - description(host.stringProvider.getString(R.string.confirm_your_identity)) - } else { - description(host.stringProvider.getString(R.string.confirm_your_identity_quad_s)) - } + title(host.stringProvider.getString(R.string.crosssigning_verify_this_session).toEpoxyCharSequence()) + description(host.stringProvider + .getString(if (data.hasOtherSessions) R.string.confirm_your_identity else R.string.confirm_your_identity_quad_s) + .toEpoxyCharSequence() + ) } } } else { @@ -117,16 +117,16 @@ class DeviceVerificationInfoBottomSheetController @Inject constructor( id("trust${cryptoDeviceInfo.deviceId}") style(ItemStyle.BIG_TEXT) titleIconResourceId(shield) - title(host.stringProvider.getString(R.string.encryption_information_verified)) - description(host.stringProvider.getString(R.string.settings_active_sessions_verified_device_desc)) + title(host.stringProvider.getString(R.string.encryption_information_verified).toEpoxyCharSequence()) + description(host.stringProvider.getString(R.string.settings_active_sessions_verified_device_desc).toEpoxyCharSequence()) } } else { genericItem { id("trust${cryptoDeviceInfo.deviceId}") titleIconResourceId(shield) style(ItemStyle.BIG_TEXT) - title(host.stringProvider.getString(R.string.encryption_information_not_verified)) - description(host.stringProvider.getString(R.string.settings_active_sessions_unverified_device_desc)) + title(host.stringProvider.getString(R.string.encryption_information_not_verified).toEpoxyCharSequence()) + description(host.stringProvider.getString(R.string.settings_active_sessions_unverified_device_desc).toEpoxyCharSequence()) } } } @@ -135,8 +135,8 @@ class DeviceVerificationInfoBottomSheetController @Inject constructor( // DEVICE INFO SECTION genericItem { id("info${cryptoDeviceInfo.deviceId}") - title(cryptoDeviceInfo.displayName() ?: "") - description("(${cryptoDeviceInfo.deviceId})") + title(cryptoDeviceInfo.displayName().orEmpty().toEpoxyCharSequence()) + description("(${cryptoDeviceInfo.deviceId})".toEpoxyCharSequence()) } if (isMine && !currentSessionIsTrusted && data.canVerifySession) { @@ -176,24 +176,24 @@ class DeviceVerificationInfoBottomSheetController @Inject constructor( id("trust${cryptoDeviceInfo.deviceId}") style(ItemStyle.BIG_TEXT) titleIconResourceId(shield) - title(host.stringProvider.getString(R.string.encryption_information_verified)) - description(host.stringProvider.getString(R.string.settings_active_sessions_verified_device_desc)) + title(host.stringProvider.getString(R.string.encryption_information_verified).toEpoxyCharSequence()) + description(host.stringProvider.getString(R.string.settings_active_sessions_verified_device_desc).toEpoxyCharSequence()) } } else { genericItem { id("trust${cryptoDeviceInfo.deviceId}") titleIconResourceId(shield) style(ItemStyle.BIG_TEXT) - title(host.stringProvider.getString(R.string.encryption_information_not_verified)) - description(host.stringProvider.getString(R.string.settings_active_sessions_unverified_device_desc)) + title(host.stringProvider.getString(R.string.encryption_information_not_verified).toEpoxyCharSequence()) + description(host.stringProvider.getString(R.string.settings_active_sessions_unverified_device_desc).toEpoxyCharSequence()) } } // DEVICE INFO SECTION genericItem { id("info${cryptoDeviceInfo.deviceId}") - title(cryptoDeviceInfo.displayName() ?: "") - description("(${cryptoDeviceInfo.deviceId})") + title(cryptoDeviceInfo.displayName().orEmpty().toEpoxyCharSequence()) + description("(${cryptoDeviceInfo.deviceId})".toEpoxyCharSequence()) } // ACTIONS @@ -287,8 +287,8 @@ class DeviceVerificationInfoBottomSheetController @Inject constructor( val info = data.deviceInfo.invoke() ?: return genericItem { id("info${info.deviceId}") - title(info.displayName ?: "") - description("(${info.deviceId})") + title(info.displayName.orEmpty().toEpoxyCharSequence()) + description("(${info.deviceId})".toEpoxyCharSequence()) } genericFooterItem { 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 5f0004c0de..77ef13c2f1 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 @@ -22,6 +22,7 @@ import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Loading import com.airbnb.mvrx.Success import im.vector.app.R +import im.vector.app.core.epoxy.charsequence.toEpoxyCharSequence import im.vector.app.core.epoxy.loadingItem import im.vector.app.core.resources.StringProvider import im.vector.app.core.ui.list.genericFooterItem @@ -67,7 +68,7 @@ class AccountDataEpoxyController @Inject constructor( dataList.forEach { accountData -> genericWithValueItem { id(accountData.type) - title(accountData.type) + title(accountData.type.toEpoxyCharSequence()) itemClickAction { host.interactionListener?.didTap(accountData) } diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingTrailPagedEpoxyController.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingTrailPagedEpoxyController.kt index 86f64c6b77..08d0d60909 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingTrailPagedEpoxyController.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingTrailPagedEpoxyController.kt @@ -21,8 +21,8 @@ import com.airbnb.epoxy.paging.PagedListEpoxyController import im.vector.app.R import im.vector.app.core.date.DateFormatKind import im.vector.app.core.date.VectorDateFormatter +import im.vector.app.core.epoxy.charsequence.toEpoxyCharSequence import im.vector.app.core.resources.ColorProvider -import im.vector.app.core.resources.StringProvider import im.vector.app.core.ui.list.GenericItem_ import im.vector.app.core.utils.createUIHandler import me.gujun.android.span.span @@ -38,7 +38,6 @@ import org.matrix.android.sdk.internal.crypto.model.rest.SecretShareRequest import javax.inject.Inject class GossipingTrailPagedEpoxyController @Inject constructor( - private val stringProvider: StringProvider, private val vectorDateFormatter: VectorDateFormatter, private val colorProvider: ColorProvider ) : PagedListEpoxyController( @@ -63,7 +62,7 @@ class GossipingTrailPagedEpoxyController @Inject constructor( "${event.getClearType()} [encrypted]" } else { event.type - } + }?.toEpoxyCharSequence() ) description( span { @@ -157,11 +156,11 @@ class GossipingTrailPagedEpoxyController @Inject constructor( +"${content?.requestingDeviceId}" } else if (event.getClearType() == EventType.ENCRYPTED) { span("**Failed to Decrypt** ${event.mCryptoError}") { - textColor = host.colorProvider.getColorFromAttribute(R.attr.colorError) - } + textColor = host.colorProvider.getColorFromAttribute(R.attr.colorError) + } } } - } + }.toEpoxyCharSequence() ) } } diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/IncomingKeyRequestPagedController.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/IncomingKeyRequestPagedController.kt index 3c90a45237..1e13fa37bb 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/IncomingKeyRequestPagedController.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/IncomingKeyRequestPagedController.kt @@ -20,6 +20,7 @@ import com.airbnb.epoxy.EpoxyModel import com.airbnb.epoxy.paging.PagedListEpoxyController import im.vector.app.core.date.DateFormatKind import im.vector.app.core.date.VectorDateFormatter +import im.vector.app.core.epoxy.charsequence.toEpoxyCharSequence import im.vector.app.core.ui.list.GenericItem_ import im.vector.app.core.utils.createUIHandler import me.gujun.android.span.span @@ -45,7 +46,7 @@ class IncomingKeyRequestPagedController @Inject constructor( return GenericItem_().apply { id(roomKeyRequest.requestId) - title(roomKeyRequest.requestId) + title(roomKeyRequest.requestId?.toEpoxyCharSequence()) description( span { span("From: ") { @@ -65,7 +66,7 @@ class IncomingKeyRequestPagedController @Inject constructor( textStyle = "bold" } +roomKeyRequest.state.name - } + }.toEpoxyCharSequence() ) } } diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/OutgoingKeyRequestPagedController.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/OutgoingKeyRequestPagedController.kt index c2a3bc9827..5d89bf9b1c 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/OutgoingKeyRequestPagedController.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/OutgoingKeyRequestPagedController.kt @@ -18,6 +18,7 @@ package im.vector.app.features.settings.devtools import com.airbnb.epoxy.EpoxyModel import com.airbnb.epoxy.paging.PagedListEpoxyController +import im.vector.app.core.epoxy.charsequence.toEpoxyCharSequence import im.vector.app.core.ui.list.GenericItem_ import im.vector.app.core.utils.createUIHandler import me.gujun.android.span.span @@ -40,7 +41,7 @@ class OutgoingKeyRequestPagedController @Inject constructor() : PagedListEpoxyCo return GenericItem_().apply { id(roomKeyRequest.requestId) - title(roomKeyRequest.requestId) + title(roomKeyRequest.requestId.toEpoxyCharSequence()) description( span { span("roomId: ") { @@ -56,7 +57,7 @@ class OutgoingKeyRequestPagedController @Inject constructor() : PagedListEpoxyCo textStyle = "bold" } +roomKeyRequest.state.name - } + }.toEpoxyCharSequence() ) } } diff --git a/vector/src/main/java/im/vector/app/features/settings/homeserver/HomeserverSettingsController.kt b/vector/src/main/java/im/vector/app/features/settings/homeserver/HomeserverSettingsController.kt index cf623d9d9f..d2ec53523c 100644 --- a/vector/src/main/java/im/vector/app/features/settings/homeserver/HomeserverSettingsController.kt +++ b/vector/src/main/java/im/vector/app/features/settings/homeserver/HomeserverSettingsController.kt @@ -22,6 +22,7 @@ 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.charsequence.toEpoxyCharSequence import im.vector.app.core.epoxy.errorWithRetryItem import im.vector.app.core.epoxy.loadingItem import im.vector.app.core.error.ErrorFormatter @@ -143,14 +144,14 @@ class HomeserverSettingsController @Inject constructor( genericWithValueItem { id("room_version_default") - title(host.stringProvider.getString(R.string.settings_server_default_room_version)) + title(host.stringProvider.getString(R.string.settings_server_default_room_version).toEpoxyCharSequence()) value(roomCapabilities.defaultRoomVersion) } roomCapabilities.supportedVersion.forEach { genericWithValueItem { id("room_version_${it.version}") - title(it.version) + title(it.version.toEpoxyCharSequence()) value( host.stringProvider.getString( when (it.status) { diff --git a/vector/src/main/java/im/vector/app/features/spaces/create/SpaceAdd3pidEpoxyController.kt b/vector/src/main/java/im/vector/app/features/spaces/create/SpaceAdd3pidEpoxyController.kt index 05d8a78b30..7e1902aac0 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/create/SpaceAdd3pidEpoxyController.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/create/SpaceAdd3pidEpoxyController.kt @@ -20,6 +20,7 @@ import android.text.InputType import com.airbnb.epoxy.TypedEpoxyController import com.google.android.material.textfield.TextInputLayout import im.vector.app.R +import im.vector.app.core.epoxy.charsequence.toEpoxyCharSequence import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.StringProvider import im.vector.app.core.ui.list.ItemStyle @@ -57,7 +58,7 @@ class SpaceAdd3pidEpoxyController @Inject constructor( genericPillItem { id("no_IDS") imageRes(R.drawable.ic_baseline_perm_contact_calendar_24) - text(host.stringProvider.getString(R.string.create_space_identity_server_info_none)) + text(host.stringProvider.getString(R.string.create_space_identity_server_info_none).toEpoxyCharSequence()) } genericButtonItem { id("Discover_Settings") diff --git a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryController.kt b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryController.kt index 4aa4256857..977294f495 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryController.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryController.kt @@ -24,6 +24,7 @@ import com.airbnb.mvrx.Incomplete import com.airbnb.mvrx.Uninitialized import im.vector.app.R import im.vector.app.core.epoxy.ClickListener +import im.vector.app.core.epoxy.charsequence.toEpoxyCharSequence import im.vector.app.core.epoxy.errorWithRetryItem import im.vector.app.core.epoxy.loadingItem import im.vector.app.core.error.ErrorFormatter @@ -87,7 +88,7 @@ class SpaceDirectoryController @Inject constructor( span(host.stringProvider.getString(R.string.spaces_no_server_support_description)) { textColor = host.colorProvider.getColorFromAttribute(R.attr.vctr_content_secondary) } - } + }.toEpoxyCharSequence() ) } } else { diff --git a/vector/src/main/java/im/vector/app/features/spaces/manage/AddRoomListController.kt b/vector/src/main/java/im/vector/app/features/spaces/manage/AddRoomListController.kt index 15103dd870..ec88e9f8a6 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/manage/AddRoomListController.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/manage/AddRoomListController.kt @@ -20,6 +20,7 @@ import androidx.recyclerview.widget.DiffUtil import com.airbnb.epoxy.EpoxyModel import com.airbnb.epoxy.paging.PagedListEpoxyController import im.vector.app.R +import im.vector.app.core.epoxy.charsequence.toEpoxyCharSequence import im.vector.app.core.ui.list.GenericPillItem_ import im.vector.app.core.utils.createUIHandler import im.vector.app.features.home.AvatarRenderer @@ -56,7 +57,7 @@ class AddRoomListController @Inject constructor( var listener: Listener? = null var ignoreRooms: List? = null - var subHeaderText: CharSequence? = null + var subHeaderText: String? = null var initialLoadOccurred = false @@ -130,7 +131,7 @@ class AddRoomListController @Inject constructor( add( GenericPillItem_().apply { id("sub_header") - text(host.subHeaderText) + text(host.subHeaderText?.toEpoxyCharSequence()) imageRes(R.drawable.ic_info) } ) diff --git a/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleListController.kt b/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleListController.kt index 86d8e12ef6..62bd866cb1 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleListController.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleListController.kt @@ -18,6 +18,7 @@ package im.vector.app.features.spaces.people import com.airbnb.epoxy.TypedEpoxyController import im.vector.app.R +import im.vector.app.core.epoxy.charsequence.toEpoxyCharSequence import im.vector.app.core.epoxy.dividerItem import im.vector.app.core.epoxy.loadingItem import im.vector.app.core.epoxy.profiles.profileMatrixItemWithPowerLevel @@ -128,7 +129,7 @@ class SpacePeopleListController @Inject constructor( span { +"\n" +host.stringProvider.getString(R.string.no_result_placeholder) - } + }.toEpoxyCharSequence() ) description( span { @@ -138,7 +139,7 @@ class SpacePeopleListController @Inject constructor( textColor = host.colorProvider.getColorFromAttribute(R.attr.colorPrimary) textStyle = "bold" } - } + }.toEpoxyCharSequence() ) itemClickAction { host.listener?.onInviteToSpaceSelected() diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/ActionItem.kt b/vector/src/main/java/im/vector/app/features/userdirectory/ActionItem.kt index 22f9225522..a4c71c8cb9 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/ActionItem.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/ActionItem.kt @@ -31,7 +31,7 @@ import im.vector.app.core.extensions.setTextOrHide @EpoxyModelClass(layout = R.layout.item_contact_action) abstract class ActionItem : VectorEpoxyModel() { - @EpoxyAttribute var title: CharSequence? = null + @EpoxyAttribute var title: String? = null @EpoxyAttribute @DrawableRes var actionIconRes: Int? = null @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var clickAction: ClickListener? = null diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserListController.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListController.kt index 2028e59073..cb0ec7c36b 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/UserListController.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListController.kt @@ -22,6 +22,7 @@ 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.charsequence.toEpoxyCharSequence import im.vector.app.core.epoxy.errorWithRetryItem import im.vector.app.core.epoxy.loadingItem import im.vector.app.core.epoxy.noResultItem @@ -154,7 +155,7 @@ class UserListController @Inject constructor(private val session: Session, textStyle = "bold" textColor = host.colorProvider.getColorFromAttribute(R.attr.colorPrimary) } - } + }.toEpoxyCharSequence() ) itemClickAction { host.callback?.giveIdentityServerConsent() @@ -182,7 +183,7 @@ class UserListController @Inject constructor(private val session: Session, textStyle = "bold" textColor = host.colorProvider.getColorFromAttribute(R.attr.colorPrimary) } - } + }.toEpoxyCharSequence() ) itemClickAction { host.callback?.onSetupDiscovery() From 27161bf7941dc3d62d210b5b3a3a1e7ba4a6dd4d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 3 Jan 2022 14:17:29 +0100 Subject: [PATCH 176/632] preventMutation is not needed anymore, since we now uses EpoxyCharSequence --- .../vector/app/features/html/SpanUtilsTest.kt | 3 --- .../BottomSheetMessagePreviewItem.kt | 8 +------ .../vector/app/core/epoxy/util/Extensions.kt | 21 ------------------- .../detail/timeline/item/BindingOptions.kt | 4 +--- .../detail/timeline/item/MessageTextItem.kt | 7 +------ .../reactions/ReactionInfoSimpleItem.kt | 3 +-- .../im/vector/app/features/html/SpanUtils.kt | 10 +-------- 7 files changed, 5 insertions(+), 51 deletions(-) delete mode 100644 vector/src/main/java/im/vector/app/core/epoxy/util/Extensions.kt diff --git a/vector/src/androidTest/java/im/vector/app/features/html/SpanUtilsTest.kt b/vector/src/androidTest/java/im/vector/app/features/html/SpanUtilsTest.kt index a42a6f0212..31d8770123 100644 --- a/vector/src/androidTest/java/im/vector/app/features/html/SpanUtilsTest.kt +++ b/vector/src/androidTest/java/im/vector/app/features/html/SpanUtilsTest.kt @@ -108,7 +108,6 @@ class SpanUtilsTest : InstrumentedTest { val string = SpannableString("Text") val result = spanUtils.getBindingOptions(string) result.canUseTextFuture shouldBeEqualTo true - result.preventMutation shouldBeEqualTo false } @Test @@ -117,7 +116,6 @@ class SpanUtilsTest : InstrumentedTest { string.setSpan(StrikethroughSpan(), 10, 23, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) val result = spanUtils.getBindingOptions(string) result.canUseTextFuture shouldBeEqualTo false - result.preventMutation shouldBeEqualTo false } @Test @@ -125,7 +123,6 @@ class SpanUtilsTest : InstrumentedTest { val string = SpannableString("Emoji \uD83D\uDE2E\u200D\uD83D\uDCA8") val result = spanUtils.getBindingOptions(string) result.canUseTextFuture shouldBeEqualTo false - result.preventMutation shouldBeEqualTo true } private fun trueIfAlwaysAllowed() = Build.VERSION.SDK_INT < Build.VERSION_CODES.P diff --git a/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetMessagePreviewItem.kt b/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetMessagePreviewItem.kt index f6088b5eda..35db585b79 100644 --- a/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetMessagePreviewItem.kt +++ b/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetMessagePreviewItem.kt @@ -28,14 +28,12 @@ import im.vector.app.core.epoxy.VectorEpoxyHolder import im.vector.app.core.epoxy.VectorEpoxyModel import im.vector.app.core.epoxy.charsequence.EpoxyCharSequence import im.vector.app.core.epoxy.onClick -import im.vector.app.core.epoxy.util.preventMutation import im.vector.app.core.extensions.setTextOrHide import im.vector.app.features.displayname.getBestName import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.room.detail.timeline.item.BindingOptions import im.vector.app.features.home.room.detail.timeline.tools.findPillsAndProcess import im.vector.app.features.media.ImageContentRenderer -import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.util.MatrixItem /** @@ -85,11 +83,7 @@ abstract class BottomSheetMessagePreviewItem : VectorEpoxyModel() { if (bindingOptions?.canUseTextFuture.orFalse()) { holder.messageView.setTextFuture(textFuture) } else { - holder.messageView.text = if (bindingOptions?.preventMutation.orFalse()) { - message?.charSequence.preventMutation() - } else { - message?.charSequence - } + holder.messageView.text = message?.charSequence } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reactions/ReactionInfoSimpleItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reactions/ReactionInfoSimpleItem.kt index e8a2636c82..f6976d96bf 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reactions/ReactionInfoSimpleItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reactions/ReactionInfoSimpleItem.kt @@ -26,7 +26,6 @@ import im.vector.app.core.epoxy.ClickListener import im.vector.app.core.epoxy.VectorEpoxyHolder import im.vector.app.core.epoxy.charsequence.EpoxyCharSequence import im.vector.app.core.epoxy.onClick -import im.vector.app.core.epoxy.util.preventMutation /** * Item displaying an emoji reaction (single line with emoji, author, time) @@ -48,7 +47,7 @@ abstract class ReactionInfoSimpleItem : EpoxyModelWithHolder Date: Mon, 3 Jan 2022 13:49:55 +0100 Subject: [PATCH 177/632] Format --- .../features/home/room/detail/search/SearchResultController.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchResultController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchResultController.kt index 342f1d9562..4c5a52864d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchResultController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchResultController.kt @@ -101,7 +101,7 @@ class SearchResultController @Inject constructor( // Take new content first @Suppress("UNCHECKED_CAST") -val text = ((event.content?.get("m.new_content") as? Content) ?: event.content)?.get("body") as? String ?: return@forEach + val text = ((event.content?.get("m.new_content") as? Content) ?: event.content)?.get("body") as? String ?: return@forEach val spannable = setHighLightedText(text, data.highlights) ?: return@forEach val eventDate = Calendar.getInstance().apply { From 4642299572430769fa3e53a8ccf9789428b492c4 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 3 Jan 2022 14:29:38 +0100 Subject: [PATCH 178/632] Changelog --- changelog.d/4837.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/4837.bugfix diff --git a/changelog.d/4837.bugfix b/changelog.d/4837.bugfix new file mode 100644 index 0000000000..d1eae295f5 --- /dev/null +++ b/changelog.d/4837.bugfix @@ -0,0 +1 @@ +Stop using CharSequence as EpoxyAttribute because it can lead to crash if the CharSequence mutates during rendering. \ No newline at end of file From 54d76af54bd434002b8d80282cddc2683f358f78 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 3 Jan 2022 15:17:34 +0100 Subject: [PATCH 179/632] Revert "Skip issue triage GitHub actions in forks" --- .github/workflows/triage-incoming.yml | 4 +- .github/workflows/triage-move-labelled.yml | 87 +++++++++----------- .github/workflows/triage-move-unlabelled.yml | 10 +-- .github/workflows/triage-priority-bugs.yml | 62 +++++++------- 4 files changed, 76 insertions(+), 87 deletions(-) diff --git a/.github/workflows/triage-incoming.yml b/.github/workflows/triage-incoming.yml index 3bb5ab73aa..4ecc824424 100644 --- a/.github/workflows/triage-incoming.yml +++ b/.github/workflows/triage-incoming.yml @@ -2,13 +2,11 @@ name: Move new issues onto Issue triage board on: issues: - types: [ opened ] + types: [opened] jobs: automate-project-columns: runs-on: ubuntu-latest - if: | - github.repository == 'vector-im/element-android' # Skip in forks steps: - uses: alex-page/github-project-automation-plus@bb266ff4dde9242060e2d5418e120a133586d488 with: diff --git a/.github/workflows/triage-move-labelled.yml b/.github/workflows/triage-move-labelled.yml index 96d302ceea..67c4e9dbab 100644 --- a/.github/workflows/triage-move-labelled.yml +++ b/.github/workflows/triage-move-labelled.yml @@ -2,14 +2,12 @@ name: Move labelled issues to correct boards and columns on: issues: - types: [ labeled ] - + types: [labeled] + jobs: move_needs_info_issues: name: X-Needs-Info issues to Need info column on triage board runs-on: ubuntu-latest - if: | - github.repository == 'vector-im/element-android' # Skip in forks steps: - uses: konradpabjan/move-labeled-or-milestoned-issue@219d384e03fa4b6460cd24f9f37d19eb033a4338 with: @@ -21,16 +19,15 @@ jobs: add_priority_design_issues_to_project: name: P1 X-Needs-Design to Design project board runs-on: ubuntu-latest - if: | - github.repository == 'vector-im/element-android' && # Skip in forks - contains(github.event.issue.labels.*.name, 'X-Needs-Design') && - (contains(github.event.issue.labels.*.name, 'S-Critical') && - (contains(github.event.issue.labels.*.name, 'O-Frequent') || + if: > + contains(github.event.issue.labels.*.name, 'X-Needs-Design') && + (contains(github.event.issue.labels.*.name, 'S-Critical') && + (contains(github.event.issue.labels.*.name, 'O-Frequent') || contains(github.event.issue.labels.*.name, 'O-Occasional')) || - contains(github.event.issue.labels.*.name, 'S-Major') && - contains(github.event.issue.labels.*.name, 'O-Frequent') || - contains(github.event.issue.labels.*.name, 'A11y') && - contains(github.event.issue.labels.*.name, 'O-Frequent')) + contains(github.event.issue.labels.*.name, 'S-Major') && + contains(github.event.issue.labels.*.name, 'O-Frequent') || + contains(github.event.issue.labels.*.name, 'A11y') && + contains(github.event.issue.labels.*.name, 'O-Frequent')) steps: - uses: octokit/graphql-action@v2.x id: add_to_project @@ -50,38 +47,36 @@ jobs: PROJECT_ID: "PN_kwDOAM0swc0sUA" GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }} - # delight_issues_to_board: - # name: Spaces issues to new Delight project board - # runs-on: ubuntu-latest - # if: | - # github.repository == 'vector-im/element-android' && # Skip in forks - # contains(github.event.issue.labels.*.name, 'A-Spaces') || - # contains(github.event.issue.labels.*.name, 'A-Space-Settings') || - # contains(github.event.issue.labels.*.name, 'A-Subspaces') - # steps: - # - uses: octokit/graphql-action@v2.x - # with: - # headers: '{"GraphQL-Features": "projects_next_graphql"}' - # query: | - # mutation add_to_project($projectid:ID!,$contentid:ID!) { - # addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) { - # projectNextItem { - # id - # } - # } - # } - # projectid: ${{ env.PROJECT_ID }} - # contentid: ${{ github.event.issue.node_id }} - # env: - # PROJECT_ID: "PN_kwDOAM0swc1HvQ" - # GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }} +# delight_issues_to_board: +# name: Spaces issues to new Delight project board +# runs-on: ubuntu-latest +# if: > +# contains(github.event.issue.labels.*.name, 'A-Spaces') || +# contains(github.event.issue.labels.*.name, 'A-Space-Settings') || +# contains(github.event.issue.labels.*.name, 'A-Subspaces') +# steps: +# - uses: octokit/graphql-action@v2.x +# with: +# headers: '{"GraphQL-Features": "projects_next_graphql"}' +# query: | +# mutation add_to_project($projectid:ID!,$contentid:ID!) { +# addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) { +# projectNextItem { +# id +# } +# } +# } +# projectid: ${{ env.PROJECT_ID }} +# contentid: ${{ github.event.issue.node_id }} +# env: +# PROJECT_ID: "PN_kwDOAM0swc1HvQ" +# GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }} move_voice-message_issues: name: A-Voice Messages to voice message board runs-on: ubuntu-latest - if: | - github.repository == 'vector-im/element-android' && # Skip in forks - contains(github.event.issue.labels.*.name, 'A-Voice Messages') + if: > + contains(github.event.issue.labels.*.name, 'A-Voice Messages') steps: - uses: octokit/graphql-action@v2.x with: @@ -103,9 +98,8 @@ jobs: move_threads_issues: name: A-Threads to Thread board runs-on: ubuntu-latest - if: | - github.repository == 'vector-im/element-android' && # Skip in forks - contains(github.event.issue.labels.*.name, 'A-Threads') + if: > + contains(github.event.issue.labels.*.name, 'A-Threads') steps: - uses: octokit/graphql-action@v2.x with: @@ -127,9 +121,8 @@ jobs: move_message_bubbles_issues: name: A-Message-Bubbles to Message bubbles board runs-on: ubuntu-latest - if: | - github.repository == 'vector-im/element-android' && # Skip in forks - contains(github.event.issue.labels.*.name, 'A-Message-Bubbles') + if: > + contains(github.event.issue.labels.*.name, 'A-Message-Bubbles') steps: - uses: octokit/graphql-action@v2.x with: diff --git a/.github/workflows/triage-move-unlabelled.yml b/.github/workflows/triage-move-unlabelled.yml index 5f13165939..94bd049b91 100644 --- a/.github/workflows/triage-move-unlabelled.yml +++ b/.github/workflows/triage-move-unlabelled.yml @@ -2,15 +2,15 @@ name: Move unlabelled from needs info columns to triaged on: issues: - types: [ unlabeled ] - + types: [unlabeled] + jobs: Move_Unabeled_Issue_On_Project_Board: name: Move no longer X-Needs-Info issues to Triaged runs-on: ubuntu-latest - if: | - github.repository == 'vector-im/element-android' && # Skip in forks - !contains(github.event.issue.labels.*.name, 'X-Needs-Info') + if: > + ${{ + !contains(github.event.issue.labels.*.name, 'X-Needs-Info') }} env: BOARD_NAME: "Issue triage" OWNER: ${{ github.repository_owner }} diff --git a/.github/workflows/triage-priority-bugs.yml b/.github/workflows/triage-priority-bugs.yml index 7564387a1c..976879a3ae 100644 --- a/.github/workflows/triage-priority-bugs.yml +++ b/.github/workflows/triage-priority-bugs.yml @@ -2,29 +2,28 @@ name: Move P1 bugs to boards on: issues: - types: [ labeled, unlabeled ] + types: [labeled, unlabeled] jobs: p1_issues_to_team_workboard: runs-on: ubuntu-latest - if: | - github.repository == 'vector-im/element-android' && # Skip in forks - (!contains(github.event.issue.labels.*.name, 'A-E2EE') && - !contains(github.event.issue.labels.*.name, 'A-E2EE-Cross-Signing') && - !contains(github.event.issue.labels.*.name, 'A-E2EE-Dehydration') && - !contains(github.event.issue.labels.*.name, 'A-E2EE-Key-Backup') && - !contains(github.event.issue.labels.*.name, 'A-E2EE-SAS-Verification') && - !contains(github.event.issue.labels.*.name, 'A-Spaces') && - !contains(github.event.issue.labels.*.name, 'A-Spaces-Settings') && - !contains(github.event.issue.labels.*.name, 'A-Subspaces')) && - (contains(github.event.issue.labels.*.name, 'T-Defect') && - contains(github.event.issue.labels.*.name, 'S-Critical') && - (contains(github.event.issue.labels.*.name, 'O-Frequent') || + if: > + (!contains(github.event.issue.labels.*.name, 'A-E2EE') && + !contains(github.event.issue.labels.*.name, 'A-E2EE-Cross-Signing') && + !contains(github.event.issue.labels.*.name, 'A-E2EE-Dehydration') && + !contains(github.event.issue.labels.*.name, 'A-E2EE-Key-Backup') && + !contains(github.event.issue.labels.*.name, 'A-E2EE-SAS-Verification') && + !contains(github.event.issue.labels.*.name, 'A-Spaces') && + !contains(github.event.issue.labels.*.name, 'A-Spaces-Settings') && + !contains(github.event.issue.labels.*.name, 'A-Subspaces')) && + (contains(github.event.issue.labels.*.name, 'T-Defect') && + contains(github.event.issue.labels.*.name, 'S-Critical') && + (contains(github.event.issue.labels.*.name, 'O-Frequent') || contains(github.event.issue.labels.*.name, 'O-Occasional')) || - contains(github.event.issue.labels.*.name, 'S-Major') && - contains(github.event.issue.labels.*.name, 'O-Frequent') || - contains(github.event.issue.labels.*.name, 'A11y') && - contains(github.event.issue.labels.*.name, 'O-Frequent')) + contains(github.event.issue.labels.*.name, 'S-Major') && + contains(github.event.issue.labels.*.name, 'O-Frequent') || + contains(github.event.issue.labels.*.name, 'A11y') && + contains(github.event.issue.labels.*.name, 'O-Frequent')) steps: - uses: alex-page/github-project-automation-plus@bb266ff4dde9242060e2d5418e120a133586d488 with: @@ -34,21 +33,20 @@ jobs: P1_issues_to_crypto_team_workboard: runs-on: ubuntu-latest - if: | - github.repository == 'vector-im/element-android' && # Skip in forks - (contains(github.event.issue.labels.*.name, 'A-E2EE') || - contains(github.event.issue.labels.*.name, 'A-E2EE-Cross-Signing') || - contains(github.event.issue.labels.*.name, 'A-E2EE-Dehydration') || - contains(github.event.issue.labels.*.name, 'A-E2EE-Key-Backup') || - contains(github.event.issue.labels.*.name, 'A-E2EE-SAS-Verification')) && - (contains(github.event.issue.labels.*.name, 'T-Defect') && - contains(github.event.issue.labels.*.name, 'S-Critical') && - (contains(github.event.issue.labels.*.name, 'O-Frequent') || + if: > + (contains(github.event.issue.labels.*.name, 'A-E2EE') || + contains(github.event.issue.labels.*.name, 'A-E2EE-Cross-Signing') || + contains(github.event.issue.labels.*.name, 'A-E2EE-Dehydration') || + contains(github.event.issue.labels.*.name, 'A-E2EE-Key-Backup') || + contains(github.event.issue.labels.*.name, 'A-E2EE-SAS-Verification')) && + (contains(github.event.issue.labels.*.name, 'T-Defect') && + contains(github.event.issue.labels.*.name, 'S-Critical') && + (contains(github.event.issue.labels.*.name, 'O-Frequent') || contains(github.event.issue.labels.*.name, 'O-Occasional')) || - contains(github.event.issue.labels.*.name, 'S-Major') && - contains(github.event.issue.labels.*.name, 'O-Frequent') || - contains(github.event.issue.labels.*.name, 'A11y') && - contains(github.event.issue.labels.*.name, 'O-Frequent')) + contains(github.event.issue.labels.*.name, 'S-Major') && + contains(github.event.issue.labels.*.name, 'O-Frequent') || + contains(github.event.issue.labels.*.name, 'A11y') && + contains(github.event.issue.labels.*.name, 'O-Frequent')) steps: - uses: alex-page/github-project-automation-plus@bb266ff4dde9242060e2d5418e120a133586d488 with: From 98df73325770cab1de34f44205262c40983806d0 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 3 Jan 2022 15:25:21 +0100 Subject: [PATCH 180/632] Split long line and use Kotlin style --- .../internal/DollarMathsDelimiterProcessor.kt | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/DollarMathsDelimiterProcessor.kt b/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/DollarMathsDelimiterProcessor.kt index 55b27a21bc..cfd03fa8f1 100644 --- a/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/DollarMathsDelimiterProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/DollarMathsDelimiterProcessor.kt @@ -22,17 +22,11 @@ import org.commonmark.parser.delimiter.DelimiterProcessor import org.commonmark.parser.delimiter.DelimiterRun class DollarMathsDelimiterProcessor : DelimiterProcessor { - override fun getOpeningCharacter(): Char { - return '$' - } + override fun getOpeningCharacter() = '$' - override fun getClosingCharacter(): Char { - return '$' - } + override fun getClosingCharacter() = '$' - override fun getMinLength(): Int { - return 1 - } + override fun getMinLength() = 1 override fun getDelimiterUse(opener: DelimiterRun, closer: DelimiterRun): Int { return if (opener.length() == 1 && closer.length() == 1) 1 // inline @@ -41,7 +35,11 @@ class DollarMathsDelimiterProcessor : DelimiterProcessor { } override fun process(opener: Text, closer: Text, delimiterUse: Int) { - val maths = if (delimiterUse == 1) InlineMaths(InlineMaths.InlineDelimiter.SINGLE_DOLLAR) else DisplayMaths(DisplayMaths.DisplayDelimiter.DOUBLE_DOLLAR) + val maths = if (delimiterUse == 1) { + InlineMaths(InlineMaths.InlineDelimiter.SINGLE_DOLLAR) + } else { + DisplayMaths(DisplayMaths.DisplayDelimiter.DOUBLE_DOLLAR) + } var tmp = opener.next while (tmp != null && tmp !== closer) { val next = tmp.next From b39caeb04df454d07acc673ae6aea73f43aecb66 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 3 Jan 2022 15:27:59 +0100 Subject: [PATCH 181/632] 2 new enums --- .../src/main/java/org/commonmark/ext/maths/DisplayMaths.kt | 3 ++- .../src/main/java/org/commonmark/ext/maths/InlineMaths.kt | 3 ++- tools/check/forbidden_strings_in_code.txt | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/DisplayMaths.kt b/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/DisplayMaths.kt index a69649640f..b8ee36e724 100644 --- a/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/DisplayMaths.kt +++ b/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/DisplayMaths.kt @@ -19,6 +19,7 @@ import org.commonmark.node.CustomBlock class DisplayMaths(private val delimiter: DisplayDelimiter) : CustomBlock() { enum class DisplayDelimiter { - DOUBLE_DOLLAR, SQUARE_BRACKET_ESCAPED + DOUBLE_DOLLAR, + SQUARE_BRACKET_ESCAPED } } diff --git a/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/InlineMaths.kt b/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/InlineMaths.kt index 799626045d..962b1b8cbf 100644 --- a/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/InlineMaths.kt +++ b/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/InlineMaths.kt @@ -20,7 +20,8 @@ import org.commonmark.node.Delimited class InlineMaths(private val delimiter: InlineDelimiter) : CustomNode(), Delimited { enum class InlineDelimiter { - SINGLE_DOLLAR, ROUND_BRACKET_ESCAPED + SINGLE_DOLLAR, + ROUND_BRACKET_ESCAPED } override fun getOpeningDelimiter(): String { diff --git a/tools/check/forbidden_strings_in_code.txt b/tools/check/forbidden_strings_in_code.txt index 6ca86be095..cbfaf20247 100644 --- a/tools/check/forbidden_strings_in_code.txt +++ b/tools/check/forbidden_strings_in_code.txt @@ -160,7 +160,7 @@ Formatter\.formatShortFileSize===1 # android\.text\.TextUtils ### This is not a rule, but a warning: the number of "enum class" has changed. For Json classes, it is mandatory that they have `@JsonClass(generateAdapter = false)`. If the enum is not used as a Json class, change the value in file forbidden_strings_in_code.txt -enum class===114 +enum class===116 ### Do not import temporary legacy classes import org.matrix.android.sdk.internal.legacy.riot===3 From e03c806fd817e8d3c327ec385a13b61d438f8155 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 3 Jan 2022 15:30:51 +0100 Subject: [PATCH 182/632] Split long line --- .../settings/KeysBackupSettingsRecyclerViewController.kt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupSettingsRecyclerViewController.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupSettingsRecyclerViewController.kt index f007fe44f0..010255256e 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupSettingsRecyclerViewController.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupSettingsRecyclerViewController.kt @@ -199,12 +199,16 @@ class KeysBackupSettingsRecyclerViewController @Inject constructor( val deviceId: String = it.deviceId ?: "" if (!isDeviceKnown) { - description(host.stringProvider.getString(R.string.keys_backup_settings_signature_from_unknown_device, deviceId).toEpoxyCharSequence()) + description(host.stringProvider + .getString(R.string.keys_backup_settings_signature_from_unknown_device, deviceId) + .toEpoxyCharSequence()) endIconResourceId(R.drawable.e2e_warning) } else { if (isSignatureValid) { if (host.session.sessionParams.deviceId == it.deviceId) { - description(host.stringProvider.getString(R.string.keys_backup_settings_valid_signature_from_this_device).toEpoxyCharSequence()) + description(host.stringProvider + .getString(R.string.keys_backup_settings_valid_signature_from_this_device) + .toEpoxyCharSequence()) endIconResourceId(R.drawable.e2e_verified) } else { if (isDeviceVerified) { From 12b775c26dba0838a7b70de52e80f8eef206d5b6 Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 3 Jan 2022 16:06:48 +0100 Subject: [PATCH 183/632] Timeline : clean after PR reviews --- changelog.d/4405.feature | 1 + changelog.d/4405.removal | 1 + .../sdk/internal/database/DatabaseCleaner.kt | 83 ------------------- .../internal/database/model/ChunkEntity.kt | 1 - .../sdk/internal/session/SessionModule.kt | 5 -- .../session/room/timeline/DefaultTimeline.kt | 28 +++---- .../room/timeline/LoadTimelineStrategy.kt | 6 +- 7 files changed, 19 insertions(+), 106 deletions(-) create mode 100644 changelog.d/4405.feature create mode 100644 changelog.d/4405.removal delete mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/DatabaseCleaner.kt diff --git a/changelog.d/4405.feature b/changelog.d/4405.feature new file mode 100644 index 0000000000..9a840a9d12 --- /dev/null +++ b/changelog.d/4405.feature @@ -0,0 +1 @@ +Change internal timeline management. \ No newline at end of file diff --git a/changelog.d/4405.removal b/changelog.d/4405.removal new file mode 100644 index 0000000000..2d1543cb2b --- /dev/null +++ b/changelog.d/4405.removal @@ -0,0 +1 @@ +Introduce method onStateUpdated on Timeline.Callback \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/DatabaseCleaner.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/DatabaseCleaner.kt deleted file mode 100644 index 6d567600ad..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/DatabaseCleaner.kt +++ /dev/null @@ -1,83 +0,0 @@ -/* - * 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.internal.database - -import io.realm.RealmConfiguration -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.api.session.SessionLifecycleObserver -import org.matrix.android.sdk.internal.database.model.RoomEntity -import org.matrix.android.sdk.internal.di.SessionDatabase -import org.matrix.android.sdk.internal.task.TaskExecutor -import timber.log.Timber -import javax.inject.Inject - -private const val MAX_NUMBER_OF_EVENTS_IN_DB = 35_000L -private const val MIN_NUMBER_OF_EVENTS_BY_CHUNK = 300 - -/** - * This class makes sure to stay under a maximum number of events as it makes Realm to be unusable when listening to events - * when the database is getting too big. This will try incrementally to remove the biggest chunks until we get below the threshold. - * We make sure to still have a minimum number of events so it's not becoming unusable. - * So this won't work for users with a big number of very active rooms. - */ -internal class DatabaseCleaner @Inject constructor(@SessionDatabase private val realmConfiguration: RealmConfiguration, - private val taskExecutor: TaskExecutor) : SessionLifecycleObserver { - - override fun onSessionStarted(session: Session) { - taskExecutor.executorScope.launch(Dispatchers.Default) { - awaitTransaction(realmConfiguration) { realm -> - val allRooms = realm.where(RoomEntity::class.java).findAll() - Timber.v("There are ${allRooms.size} rooms in this session") - // cleanUp(realm, MAX_NUMBER_OF_EVENTS_IN_DB / 2L) - } - } - } - - /* - private fun cleanUp(realm: Realm, threshold: Long) { - val numberOfEvents = realm.where(EventEntity::class.java).findAll().size - val numberOfTimelineEvents = realm.where(TimelineEventEntity::class.java).findAll().size - Timber.v("Number of events in db: $numberOfEvents | Number of timeline events in db: $numberOfTimelineEvents") - if (threshold <= MIN_NUMBER_OF_EVENTS_BY_CHUNK || numberOfTimelineEvents < MAX_NUMBER_OF_EVENTS_IN_DB) { - Timber.v("Db is low enough") - } else { - val thresholdChunks = realm.where(ChunkEntity::class.java) - .greaterThan(ChunkEntityFields.NUMBER_OF_TIMELINE_EVENTS, threshold) - .findAll() - - Timber.v("There are ${thresholdChunks.size} chunks to clean with more than $threshold events") - for (chunk in thresholdChunks) { - val maxDisplayIndex = chunk.nextDisplayIndex(PaginationDirection.FORWARDS) - val thresholdDisplayIndex = maxDisplayIndex - threshold - val eventsToRemove = chunk.timelineEvents.where().lessThan(TimelineEventEntityFields.DISPLAY_INDEX, thresholdDisplayIndex).findAll() - Timber.v("There are ${eventsToRemove.size} events to clean in chunk: ${chunk.identifier()} from room ${chunk.room?.first()?.roomId}") - //chunk.numberOfTimelineEvents = chunk.numberOfTimelineEvents - eventsToRemove.size - eventsToRemove.forEach { - val canDeleteRoot = it.root?.stateKey == null - it.deleteOnCascade(canDeleteRoot) - } - // We reset the prevToken so we will need to fetch again. - chunk.prevToken = null - } - cleanUp(realm, (threshold / 1.5).toLong()) - } - } - - */ -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/ChunkEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/ChunkEntity.kt index 82b7517181..ecb602019a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/ChunkEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/ChunkEntity.kt @@ -31,7 +31,6 @@ internal open class ChunkEntity(@Index var prevToken: String? = null, var nextChunk: ChunkEntity? = null, var stateEvents: RealmList = RealmList(), var timelineEvents: RealmList = RealmList(), - // var numberOfTimelineEvents: Long = 0, // Only one chunk will have isLastForward == true @Index var isLastForward: Boolean = false, @Index var isLastBackward: Boolean = false diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt index ebc2176a13..e2cfea479d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt @@ -47,7 +47,6 @@ import org.matrix.android.sdk.internal.crypto.secrets.DefaultSharedSecretStorage import org.matrix.android.sdk.internal.crypto.tasks.DefaultRedactEventTask import org.matrix.android.sdk.internal.crypto.tasks.RedactEventTask import org.matrix.android.sdk.internal.crypto.verification.VerificationMessageProcessor -import org.matrix.android.sdk.internal.database.DatabaseCleaner import org.matrix.android.sdk.internal.database.EventInsertLiveObserver import org.matrix.android.sdk.internal.database.RealmSessionProvider import org.matrix.android.sdk.internal.database.SessionRealmConfigurationFactory @@ -339,10 +338,6 @@ internal abstract class SessionModule { @IntoSet abstract fun bindIdentityService(service: DefaultIdentityService): SessionLifecycleObserver - @Binds - @IntoSet - abstract fun bindDatabaseCleaner(cleaner: DatabaseCleaner): SessionLifecycleObserver - @Binds @IntoSet abstract fun bindRealmSessionProvider(provider: RealmSessionProvider): SessionLifecycleObserver 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 bb15bcb9ca..71823cd458 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 @@ -47,20 +47,20 @@ import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicReference -internal class DefaultTimeline internal constructor(private val roomId: String, - private val initialEventId: String?, - private val realmConfiguration: RealmConfiguration, - private val loadRoomMembersTask: LoadRoomMembersTask, - private val readReceiptHandler: ReadReceiptHandler, - private val settings: TimelineSettings, - private val coroutineDispatchers: MatrixCoroutineDispatchers, - paginationTask: PaginationTask, - getEventTask: GetContextOfEventTask, - fetchTokenAndPaginateTask: FetchTokenAndPaginateTask, - timelineEventMapper: TimelineEventMapper, - timelineInput: TimelineInput, - threadsAwarenessHandler: ThreadsAwarenessHandler, - eventDecryptor: TimelineEventDecryptor) : Timeline { +internal class DefaultTimeline(private val roomId: String, + private val initialEventId: String?, + private val realmConfiguration: RealmConfiguration, + private val loadRoomMembersTask: LoadRoomMembersTask, + private val readReceiptHandler: ReadReceiptHandler, + private val settings: TimelineSettings, + private val coroutineDispatchers: MatrixCoroutineDispatchers, + paginationTask: PaginationTask, + getEventTask: GetContextOfEventTask, + fetchTokenAndPaginateTask: FetchTokenAndPaginateTask, + timelineEventMapper: TimelineEventMapper, + timelineInput: TimelineInput, + threadsAwarenessHandler: ThreadsAwarenessHandler, + eventDecryptor: TimelineEventDecryptor) : Timeline { companion object { val BACKGROUND_HANDLER = createBackgroundHandler("DefaultTimeline_Thread") 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 2e71a8099f..528b564e8b 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 @@ -48,9 +48,9 @@ internal class LoadTimelineStrategy( private val mode: Mode, private val dependencies: Dependencies) { - sealed class Mode { - object Live : Mode() - data class Permalink(val originEventId: String) : Mode() + sealed interface Mode { + object Live : Mode + data class Permalink(val originEventId: String) : Mode fun originEventId(): String? { return if (this is Permalink) { From 74af485137cbe7fc556afb539c5674fd3915fbca Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 3 Jan 2022 16:33:41 +0100 Subject: [PATCH 184/632] Small cleanup. Remove unused methods. --- .../core/utils/ExternalApplicationsUtil.kt | 166 +++--------------- .../im/vector/app/core/utils/SystemUtils.kt | 12 +- .../home/room/detail/RoomDetailFragment.kt | 7 +- 3 files changed, 30 insertions(+), 155 deletions(-) diff --git a/vector/src/main/java/im/vector/app/core/utils/ExternalApplicationsUtil.kt b/vector/src/main/java/im/vector/app/core/utils/ExternalApplicationsUtil.kt index 06da70f391..99cd863141 100644 --- a/vector/src/main/java/im/vector/app/core/utils/ExternalApplicationsUtil.kt +++ b/vector/src/main/java/im/vector/app/core/utils/ExternalApplicationsUtil.kt @@ -80,11 +80,7 @@ fun openUrlInExternalBrowser(context: Context, uri: Uri?) { putExtra(Browser.EXTRA_CREATE_NEW_TAB, true) } - try { - context.startActivity(browserIntent) - } catch (activityNotFoundException: ActivityNotFoundException) { - context.toast(R.string.error_no_external_application_found) - } + context.safeStartActivity(browserIntent) } } @@ -123,22 +119,6 @@ fun openUrlInChromeCustomTab(context: Context, } } -/** - * Open sound recorder external application - */ -fun openSoundRecorder(activity: Activity, requestCode: Int) { - val recordSoundIntent = Intent(MediaStore.Audio.Media.RECORD_SOUND_ACTION) - - // Create chooser - val chooserIntent = Intent.createChooser(recordSoundIntent, activity.getString(R.string.go_on_with)) - - try { - activity.startActivityForResult(chooserIntent, requestCode) - } catch (activityNotFoundException: ActivityNotFoundException) { - activity.toast(R.string.error_no_external_application_found) - } -} - /** * Open file selection activity */ @@ -153,96 +133,14 @@ fun openFileSelection(activity: Activity, fileIntent.type = MimeTypes.Any try { - activityResultLauncher - ?.launch(fileIntent) - ?: run { - activity.startActivityForResult(fileIntent, requestCode) - } - } catch (activityNotFoundException: ActivityNotFoundException) { - activity.toast(R.string.error_no_external_application_found) - } -} - -/** - * Open external video recorder - */ -fun openVideoRecorder(activity: Activity, requestCode: Int) { - val captureIntent = Intent(MediaStore.ACTION_VIDEO_CAPTURE) - - // lowest quality - captureIntent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 0) - - try { - activity.startActivityForResult(captureIntent, requestCode) - } catch (activityNotFoundException: ActivityNotFoundException) { - activity.toast(R.string.error_no_external_application_found) - } -} - -/** - * Open external camera - * @return the latest taken picture camera uri - */ -fun openCamera(activity: Activity, titlePrefix: String, requestCode: Int): String? { - val captureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE) - - // the following is a fix for buggy 2.x devices - val date = Date() - val formatter = SimpleDateFormat("yyyyMMddHHmmss", Locale.US) - val values = ContentValues() - values.put(MediaStore.Images.Media.TITLE, titlePrefix + formatter.format(date)) - // The Galaxy S not only requires the name of the file to output the image to, but will also not - // set the mime type of the picture it just took (!!!). We assume that the Galaxy S takes image/jpegs - // so the attachment uploader doesn't freak out about there being no mimetype in the content database. - values.put(MediaStore.Images.Media.MIME_TYPE, MimeTypes.Jpeg) - var dummyUri: Uri? = null - try { - dummyUri = activity.contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values) - - if (null == dummyUri) { - Timber.e("Cannot use the external storage media to save image") + if (activityResultLauncher != null) { + activityResultLauncher.launch(fileIntent) + } else { + activity.startActivityForResult(fileIntent, requestCode) } - } catch (uoe: UnsupportedOperationException) { - Timber.e(uoe, "Unable to insert camera URI into MediaStore.Images.Media.EXTERNAL_CONTENT_URI.") - Timber.e("no SD card? Attempting to insert into device storage.") - } catch (e: Exception) { - Timber.e(e, "Unable to insert camera URI into MediaStore.Images.Media.EXTERNAL_CONTENT_URI.") - } - - if (null == dummyUri) { - try { - dummyUri = activity.contentResolver.insert(MediaStore.Images.Media.INTERNAL_CONTENT_URI, values) - if (null == dummyUri) { - Timber.e("Cannot use the internal storage to save media to save image") - } - } catch (e: Exception) { - Timber.e(e, "Unable to insert camera URI into internal storage. Giving up.") - } - } - - if (dummyUri != null) { - captureIntent.putExtra(MediaStore.EXTRA_OUTPUT, dummyUri) - Timber.v("trying to take a photo on $dummyUri") - } else { - Timber.v("trying to take a photo with no predefined uri") - } - - // Store the dummy URI which will be set to a placeholder location. When all is lost on Samsung devices, - // this will point to the data we're looking for. - // Because Activities tend to use a single MediaProvider for all their intents, this field will only be the - // *latest* TAKE_PICTURE Uri. This is deemed acceptable as the normal flow is to create the intent then immediately - // fire it, meaning onActivityResult/getUri will be the next thing called, not another createIntentFor. - val result = if (dummyUri == null) null else dummyUri.toString() - - try { - activity.startActivityForResult(captureIntent, requestCode) - - return result } catch (activityNotFoundException: ActivityNotFoundException) { activity.toast(R.string.error_no_external_application_found) } - - return null } /** @@ -254,11 +152,7 @@ fun sendMailTo(address: String, subject: String? = null, message: String? = null intent.putExtra(Intent.EXTRA_SUBJECT, subject) intent.putExtra(Intent.EXTRA_TEXT, message) - try { - activity.startActivity(intent) - } catch (activityNotFoundException: ActivityNotFoundException) { - activity.toast(R.string.error_no_external_application_found) - } + activity.safeStartActivity(intent) } /** @@ -267,11 +161,7 @@ fun sendMailTo(address: String, subject: String? = null, message: String? = null fun openUri(activity: Activity, uri: String) { val intent = Intent(Intent.ACTION_VIEW, Uri.parse(uri)) - try { - activity.startActivity(intent) - } catch (activityNotFoundException: ActivityNotFoundException) { - activity.toast(R.string.error_no_external_application_found) - } + activity.safeStartActivity(intent) } /** @@ -290,11 +180,7 @@ fun openMedia(activity: Activity, savedMediaPath: String, mimeType: String) { addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) } - try { - activity.startActivity(intent) - } catch (activityNotFoundException: ActivityNotFoundException) { - activity.toast(R.string.error_no_external_application_found) - } + activity.safeStartActivity(intent) } fun shareMedia(context: Context, file: File, mediaMimeType: String?) { @@ -311,7 +197,7 @@ fun shareMedia(context: Context, file: File, mediaMimeType: String?) { .setChooserTitle(R.string.share) .createChooserIntent() - createChooser(context, chooserIntent) + context.safeStartActivity(chooserIntent) } fun shareText(context: Context, text: String) { @@ -321,14 +207,14 @@ fun shareText(context: Context, text: String) { .setChooserTitle(R.string.share) .createChooserIntent() - createChooser(context, chooserIntent) + context.safeStartActivity(chooserIntent) } -private fun createChooser(context: Context, intent: Intent) { +fun Context.safeStartActivity(intent: Intent) { try { - context.startActivity(intent) + startActivity(intent) } catch (activityNotFoundException: ActivityNotFoundException) { - context.toast(R.string.error_no_external_application_found) + toast(R.string.error_no_external_application_found) } } @@ -454,25 +340,18 @@ fun openPlayStore(activity: Activity, appId: String = BuildConfig.APPLICATION_ID try { activity.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=$appId"))) } catch (activityNotFoundException: ActivityNotFoundException) { - try { - activity.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("https://play.google.com/store/apps/details?id=$appId"))) - } catch (activityNotFoundException: ActivityNotFoundException) { - activity.toast(R.string.error_no_external_application_found) - } + activity.safeStartActivity(Intent(Intent.ACTION_VIEW, Uri.parse("https://play.google.com/store/apps/details?id=$appId"))) } } fun openAppSettingsPage(activity: Activity) { - try { - activity.startActivity( - Intent().apply { - action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS - addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - data = Uri.fromParts("package", activity.packageName, null) - }) - } catch (activityNotFoundException: ActivityNotFoundException) { - activity.toast(R.string.error_no_external_application_found) - } + activity.safeStartActivity( + Intent().apply { + action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + data = Uri.fromParts("package", activity.packageName, null) + } + ) } /** @@ -488,9 +367,8 @@ fun selectTxtFileToWrite( intent.addCategory(Intent.CATEGORY_OPENABLE) intent.type = "text/plain" intent.putExtra(Intent.EXTRA_TITLE, defaultFileName) - + val chooserIntent = Intent.createChooser(intent, chooserHint) try { - val chooserIntent = Intent.createChooser(intent, chooserHint) activityResultLauncher.launch(chooserIntent) } catch (activityNotFoundException: ActivityNotFoundException) { activity.toast(R.string.error_no_external_application_found) diff --git a/vector/src/main/java/im/vector/app/core/utils/SystemUtils.kt b/vector/src/main/java/im/vector/app/core/utils/SystemUtils.kt index 966b38828e..1fa2b8151a 100644 --- a/vector/src/main/java/im/vector/app/core/utils/SystemUtils.kt +++ b/vector/src/main/java/im/vector/app/core/utils/SystemUtils.kt @@ -124,9 +124,9 @@ fun startNotificationChannelSettingsIntent(fragment: Fragment, channelID: String } fun startAddGoogleAccountIntent(context: Context, activityResultLauncher: ActivityResultLauncher) { + val intent = Intent(Settings.ACTION_ADD_ACCOUNT) + intent.putExtra(Settings.EXTRA_ACCOUNT_TYPES, arrayOf("com.google")) try { - val intent = Intent(Settings.ACTION_ADD_ACCOUNT) - intent.putExtra(Settings.EXTRA_ACCOUNT_TYPES, arrayOf("com.google")) activityResultLauncher.launch(intent) } catch (activityNotFoundException: ActivityNotFoundException) { context.toast(R.string.error_no_external_application_found) @@ -135,9 +135,9 @@ fun startAddGoogleAccountIntent(context: Context, activityResultLauncher: Activi @RequiresApi(Build.VERSION_CODES.O) fun startInstallFromSourceIntent(context: Context, activityResultLauncher: ActivityResultLauncher) { + val intent = Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES) + .setData(Uri.parse(String.format("package:%s", context.packageName))) try { - val intent = Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES) - .setData(Uri.parse(String.format("package:%s", context.packageName))) activityResultLauncher.launch(intent) } catch (activityNotFoundException: ActivityNotFoundException) { context.toast(R.string.error_no_external_application_found) @@ -177,9 +177,9 @@ fun startImportTextFromFileIntent(context: Context, activityResultLauncher: Acti val intent = Intent(Intent.ACTION_GET_CONTENT).apply { type = "text/plain" } - if (intent.resolveActivity(context.packageManager) != null) { + try { activityResultLauncher.launch(intent) - } else { + } catch (activityNotFoundException: ActivityNotFoundException) { context.toast(R.string.error_no_external_application_found) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index 35f33e4f75..91cae1230f 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -108,6 +108,7 @@ import im.vector.app.core.utils.onPermissionDeniedDialog import im.vector.app.core.utils.onPermissionDeniedSnackbar import im.vector.app.core.utils.openUrlInExternalBrowser import im.vector.app.core.utils.registerForPermissionsResult +import im.vector.app.core.utils.safeStartActivity import im.vector.app.core.utils.saveMedia import im.vector.app.core.utils.shareMedia import im.vector.app.core.utils.shareText @@ -782,11 +783,7 @@ class RoomDetailFragment @Inject constructor( addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_ACTIVITY_NEW_TASK) } - if (intent.resolveActivity(requireActivity().packageManager) != null) { - requireActivity().startActivity(intent) - } else { - requireActivity().toast(R.string.error_no_external_application_found) - } + requireActivity().safeStartActivity(intent) } private fun installApk(action: RoomDetailViewEvents.OpenFile) { From 2dc88d14778b28c040635d10af7845420e85b63a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 3 Jan 2022 20:22:57 +0100 Subject: [PATCH 185/632] Fix code quality --- tools/check/forbidden_strings_in_code.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/check/forbidden_strings_in_code.txt b/tools/check/forbidden_strings_in_code.txt index 6ca86be095..a0c7c2f4c6 100644 --- a/tools/check/forbidden_strings_in_code.txt +++ b/tools/check/forbidden_strings_in_code.txt @@ -160,7 +160,7 @@ Formatter\.formatShortFileSize===1 # android\.text\.TextUtils ### This is not a rule, but a warning: the number of "enum class" has changed. For Json classes, it is mandatory that they have `@JsonClass(generateAdapter = false)`. If the enum is not used as a Json class, change the value in file forbidden_strings_in_code.txt -enum class===114 +enum class===115 ### Do not import temporary legacy classes import org.matrix.android.sdk.internal.legacy.riot===3 From 81e2e8db06a62ec5b68f29cf3243682005c1ecd4 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 4 Jan 2022 12:42:24 +0100 Subject: [PATCH 186/632] Fix lint issue --- vector/src/main/res/values-vi/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/res/values-vi/strings.xml b/vector/src/main/res/values-vi/strings.xml index ef0abeb90e..44ead6a909 100644 --- a/vector/src/main/res/values-vi/strings.xml +++ b/vector/src/main/res/values-vi/strings.xml @@ -2779,7 +2779,7 @@ \nHủy kích hoạt tài khoản của bạn does không theo mặc định khiến chúng tôi quên tin nhắn bạn đã gửi. Nếu bạn muốn chúng tôi quên tin nhắn của bạn, vui lòng đánh dấu vào hộp bên dưới. \n \nKhả năng hiển thị tin nhắn trong Matrix tương tự như email. Chúng tôi quên tin nhắn của bạn có nghĩa là tin nhắn bạn đã gửi sẽ không được chia sẻ với bất kỳ người dùng mới hoặc chưa đăng ký nào, nhưng người dùng đã đăng ký đã có quyền truy cập vào các tin nhắn này vẫn sẽ có quyền truy cập vào bản sao của họ. - Để tiếp tục sử dụng homeserver %1$, bạn phải xem xét và đồng ý với các điều khoản và điều kiện. + Để tiếp tục sử dụng homeserver %1$s, bạn phải xem xét và đồng ý với các điều khoản và điều kiện. Avatar Chú ý avatar Nhận avatar From 9946e64c23e5a2203c1e8291f8544603c9a3f425 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 4 Jan 2022 12:47:46 +0100 Subject: [PATCH 187/632] Fix lint issue (TypographyDashes) --- vector/lint.xml | 1 + vector/src/main/res/values-vi/strings.xml | 16 ++++++++-------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/vector/lint.xml b/vector/lint.xml index 9d9b208df7..818349da24 100644 --- a/vector/lint.xml +++ b/vector/lint.xml @@ -72,6 +72,7 @@ + - Exit Actions + Exit Sign out Are you sure you want to sign out? Voice Call @@ -453,11 +453,15 @@ Add Switch Unpublish - Copied to clipboard - Disable Return Enable Not now + Agree + Like + "Change" + + Copied to clipboard + Disable Confirmation @@ -2210,10 +2214,6 @@ Your rooms will be displayed here. Tap the + bottom right to find existing ones or start some of your own. Reactions - - Agree - - Like Add Reaction View Reactions Reactions @@ -2230,7 +2230,6 @@ Create New Room Create New Space No network. Please check your Internet connection. - "Change" "Change network" "Please wait…" "All Communities" From 64e56b0dd7aafc5e5193447f53f2fe864de27794 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 4 Jan 2022 12:56:36 +0100 Subject: [PATCH 189/632] Remove unused strings --- vector/src/main/res/values-bg/strings.xml | 8 -------- vector/src/main/res/values-ca/strings.xml | 8 -------- vector/src/main/res/values-cs/strings.xml | 10 ---------- vector/src/main/res/values-de/strings.xml | 10 +--------- vector/src/main/res/values-eo/strings.xml | 8 -------- vector/src/main/res/values-es/strings.xml | 8 -------- vector/src/main/res/values-et/strings.xml | 8 -------- vector/src/main/res/values-eu/strings.xml | 8 -------- vector/src/main/res/values-fa/strings.xml | 8 -------- vector/src/main/res/values-fi/strings.xml | 8 -------- vector/src/main/res/values-fr-rCA/strings.xml | 8 -------- vector/src/main/res/values-fr/strings.xml | 8 -------- vector/src/main/res/values-hu/strings.xml | 8 -------- vector/src/main/res/values-in/strings.xml | 6 ------ vector/src/main/res/values-it/strings.xml | 8 -------- vector/src/main/res/values-iw/strings.xml | 12 ------------ vector/src/main/res/values-kab/strings.xml | 8 -------- vector/src/main/res/values-lv/strings.xml | 10 ---------- vector/src/main/res/values-nl/strings.xml | 4 ---- vector/src/main/res/values-pl/strings.xml | 10 ---------- vector/src/main/res/values-pt-rBR/strings.xml | 8 -------- vector/src/main/res/values-ru/strings.xml | 10 ---------- vector/src/main/res/values-sk/strings.xml | 5 ----- vector/src/main/res/values-sq/strings.xml | 8 -------- vector/src/main/res/values-sv/strings.xml | 8 -------- vector/src/main/res/values-uk/strings.xml | 12 ------------ vector/src/main/res/values-vi/strings.xml | 6 ------ vector/src/main/res/values-zh-rCN/strings.xml | 6 ------ vector/src/main/res/values-zh-rTW/strings.xml | 6 ------ vector/src/main/res/values/strings.xml | 8 -------- 30 files changed, 1 insertion(+), 242 deletions(-) diff --git a/vector/src/main/res/values-bg/strings.xml b/vector/src/main/res/values-bg/strings.xml index b3fbcbcd1d..90b6894063 100644 --- a/vector/src/main/res/values-bg/strings.xml +++ b/vector/src/main/res/values-bg/strings.xml @@ -2066,14 +2066,6 @@ Използвай парола или ключ за възстановяване Създава проста анкета Избрана опция - - %d глас - Финални резултати - %d гласа - Финални резултати - - - %d глас - %d гласа - Изтриване на профилните данни от тип %1$s\? \n \nВнимавайте с това - може да доведе до неочаквано поведение. diff --git a/vector/src/main/res/values-ca/strings.xml b/vector/src/main/res/values-ca/strings.xml index 8a15e17d28..118abcfb57 100644 --- a/vector/src/main/res/values-ca/strings.xml +++ b/vector/src/main/res/values-ca/strings.xml @@ -2253,14 +2253,6 @@ Utilitza una frase de recuperació o una clau Crea una votació simple Opció seleccionada - - %d vot - Resultat final - %d vots - Resultat final - - - %d vot - %d vots - Vols eliminar les dades del compte de tipus %1$s\? \n \nVés en compte, pot ser que provoqui comportaments inesperats. diff --git a/vector/src/main/res/values-cs/strings.xml b/vector/src/main/res/values-cs/strings.xml index 4cd56f167f..693c17d1db 100644 --- a/vector/src/main/res/values-cs/strings.xml +++ b/vector/src/main/res/values-cs/strings.xml @@ -1996,16 +1996,6 @@ Spojení k serveru bylo ztraceno Vývojářské nástroje Údaje účtu - - %d hlas - %d hlasy - %d hlasů - - - %d hlas - Konečné výsledky - %d hlasy - Konečné výsledky - %d hlasů - Konečné výsledky - Zvolená možnost Vytvoří jednoduché hlasování Použijte metodu obnovy diff --git a/vector/src/main/res/values-de/strings.xml b/vector/src/main/res/values-de/strings.xml index 375897e715..d7b95fd64d 100644 --- a/vector/src/main/res/values-de/strings.xml +++ b/vector/src/main/res/values-de/strings.xml @@ -2023,16 +2023,8 @@ Verbindung zum Server wurde unterbrochen Entwicklerwerkzeuge Kontodaten - - %d Stimme - %d Stimmen - - - %d Stimme - Endergebnis - %d Stimmen - Endergebnis - - Ausgewählte Option Erstellt eine einfache Umfrage + Ausgewählte Option Nutze eine Wiederherstellungsmethode Wenn du auf keine existierende Sitzung zugreifen kannst Neue Anmeldung diff --git a/vector/src/main/res/values-eo/strings.xml b/vector/src/main/res/values-eo/strings.xml index 85a322186c..bc7a6483da 100644 --- a/vector/src/main/res/values-eo/strings.xml +++ b/vector/src/main/res/values-eo/strings.xml @@ -2233,14 +2233,6 @@ Uzi rehavan pasfrazon aŭ ŝlosilon Faras simplan enketon Elektita elekteblo - - %d voĉo – finaj rezultoj - %d voĉoj – finaj rezultoj - - - %d voĉo - %d voĉoj - Ĉu forigi la datumojn de konto de la speco «%1$s»\? \n \nForu tion zorge, ĝi povus kaŭzi neatenditan konduton. diff --git a/vector/src/main/res/values-es/strings.xml b/vector/src/main/res/values-es/strings.xml index 029ac6443e..eb921d520a 100644 --- a/vector/src/main/res/values-es/strings.xml +++ b/vector/src/main/res/values-es/strings.xml @@ -2226,14 +2226,6 @@ Por favor permite el acceso en la próxima ventana emergente para descubrir usua %1$s (%2$s) iniciado sesión con una nueva sesión: Hasta que este usuario confíe en esta sesión, los mensajes enviados hacia y desde ella se etiquetan con advertencias. Alternativamente, puede verificarlo manualmente. ¡Casi ahí! ¿Es %s muestra el mismo escudo\? - - %d voto - %d votos - - - %d voto - Resultados finales - %d votos - Resultados finales - Crea una encuesta simple Use una contraseña o clave de recuperación Si no puede acceder a una sesión existente diff --git a/vector/src/main/res/values-et/strings.xml b/vector/src/main/res/values-et/strings.xml index 836ad63ecb..1a35902987 100644 --- a/vector/src/main/res/values-et/strings.xml +++ b/vector/src/main/res/values-et/strings.xml @@ -2243,14 +2243,6 @@ Lennurežiim on kasutusel Arendaja töövahendid Kasutajakonto andmed - - %d hääl - %d häält - - - %d hääl - lõplikud tulemused - %d häält - lõplikud tulemused - Tehtud valik Loob lihtsa hääletuse Kasuta taastamiseks mõeldud paroolifraasi või võtit diff --git a/vector/src/main/res/values-eu/strings.xml b/vector/src/main/res/values-eu/strings.xml index da17cc5aee..da975dc2af 100644 --- a/vector/src/main/res/values-eu/strings.xml +++ b/vector/src/main/res/values-eu/strings.xml @@ -2233,14 +2233,6 @@ Abisua: Fitxategi hau ezabatu daiteke aplikazioa desinstalatzen bada. Erabiltzaile-izena Garapen tresnak Kontuaren datuak - - boto %d - %d boto - - - boto %d - Azken emaitza - %d boto - Azken emaitza - Hautatutako aukera Inkesta sinplea sortzen du Erabili berreskuratze metodo bat diff --git a/vector/src/main/res/values-fa/strings.xml b/vector/src/main/res/values-fa/strings.xml index cf1b814a13..88840717aa 100644 --- a/vector/src/main/res/values-fa/strings.xml +++ b/vector/src/main/res/values-fa/strings.xml @@ -1448,14 +1448,6 @@ اتّصال به کارساز از دست رفت حالت هواپیما روشن است داده‌های حساب - - %d رأی - %d رأی - - - %d رأی - نتایج نهایی - %d رأی - نتایج نهایی - انتخاب گزیده ایجاد نظرسنجی‌ای ساده استفاده از یک کلید یا عبارت بازیابی diff --git a/vector/src/main/res/values-fi/strings.xml b/vector/src/main/res/values-fi/strings.xml index 73d192edb0..d177d36bac 100644 --- a/vector/src/main/res/values-fi/strings.xml +++ b/vector/src/main/res/values-fi/strings.xml @@ -1954,14 +1954,6 @@ Ei Yhteys kotipalvelimeen on poikki Kehittäjätyökalut - - %d ääni - %d ääntä - - - %d ääni - lopulliset tulokset - %d ääntä - lopulliset tulokset - Luo yksinkertaisen äänestyksen Jos et pääse käsiksi olemassaolevaan istuntoon Uusi sisäänkirjautuminen diff --git a/vector/src/main/res/values-fr-rCA/strings.xml b/vector/src/main/res/values-fr-rCA/strings.xml index 6da35764ed..1ffb00f106 100644 --- a/vector/src/main/res/values-fr-rCA/strings.xml +++ b/vector/src/main/res/values-fr-rCA/strings.xml @@ -518,14 +518,6 @@ Envoyer l’image en taille originale Envoyer les images en taille originale - - %d vote − Résultats finaux - %d votes − Résultats finaux - - - %d vote - %d votes - %d session active %d sessions actives diff --git a/vector/src/main/res/values-fr/strings.xml b/vector/src/main/res/values-fr/strings.xml index 0b3518d0d4..e91032fc89 100644 --- a/vector/src/main/res/values-fr/strings.xml +++ b/vector/src/main/res/values-fr/strings.xml @@ -1937,14 +1937,6 @@ Nom d’utilisateur Outils de développement Données du compte - - %d vote - %d votes - - - %d vote − Résultats finaux - %d votes − Résultats finaux - Option sélectionnée Crée un sondage simple Utiliser une phrase secrète ou une clé de récupération diff --git a/vector/src/main/res/values-hu/strings.xml b/vector/src/main/res/values-hu/strings.xml index c46e41eec6..7d963b7646 100644 --- a/vector/src/main/res/values-hu/strings.xml +++ b/vector/src/main/res/values-hu/strings.xml @@ -1894,14 +1894,6 @@ Ha nem te állítottad be a visszaállítási metódust, akkor egy támadó pró Felhasználónév Fejlesztői Eszközök Fiók Adatok - - %d szavazat - %d szavazat - - - %d szavazat – Végeredmény - %d szavazat – Végeredmény - Kiválasztott Beállítások Egyszerű szavazás készítése Használd a visszaállítási eljárást diff --git a/vector/src/main/res/values-in/strings.xml b/vector/src/main/res/values-in/strings.xml index d7df1380e6..52a79b2dbc 100644 --- a/vector/src/main/res/values-in/strings.xml +++ b/vector/src/main/res/values-in/strings.xml @@ -2889,12 +2889,6 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Gunakan Frasa Sandi Pemulihan atau Kunci Membuat poll sederhana Opsi yang Dipilih - - %d suara — Hasil akhir - - - %d suara - Hapus data akun dengan tipe %1$s\? \n \nHati-hati menggunakannya, ini dapat menyebabkan perilaku yang tidak terduga. diff --git a/vector/src/main/res/values-it/strings.xml b/vector/src/main/res/values-it/strings.xml index 9828c01b0d..1c9edffadf 100644 --- a/vector/src/main/res/values-it/strings.xml +++ b/vector/src/main/res/values-it/strings.xml @@ -2015,14 +2015,6 @@ Nome utente Strumenti per sviluppatori Dati account - - %d voto - %d voti - - - %d voto - Risultato finale - %d voti - Risultato finale - Opzione selezionata Crea un semplice sondaggio Usa una Passphrase o un codice di recupero diff --git a/vector/src/main/res/values-iw/strings.xml b/vector/src/main/res/values-iw/strings.xml index ee41898f4e..5fe877ea77 100644 --- a/vector/src/main/res/values-iw/strings.xml +++ b/vector/src/main/res/values-iw/strings.xml @@ -2042,12 +2042,6 @@ השתמש במפתח אבטחה התקן הגן מפני אובדן גישה להודעות ונתונים מוצפנים על ידי גיבוי של מפתחות הצפנה בשרת שלך. - - %d הצבעה - %d הצבעות - %d הצבעות - %d הצבעות - כלי מפתחים מצב טיסה פועל הקישוריות לשרת אבדה @@ -2250,12 +2244,6 @@ שלח תמונות בגודל המקורי שלח תמונות בגודל המקורי - - %d הצבעה - תוצאות סופיות - %d הצבעות - תוצאות סופיות - %d הצבעעות - תוצאות סופיות - %d הצבעות - תוצאות סופיות - בחר PIN לאבטחה שגיאות רבות מדי, יצאת מהחשבון אַזהָרָה! ניסיון אחרון שנותר לפני היציאה! diff --git a/vector/src/main/res/values-kab/strings.xml b/vector/src/main/res/values-kab/strings.xml index 18be6bd2a6..c8fbe86cfe 100644 --- a/vector/src/main/res/values-kab/strings.xml +++ b/vector/src/main/res/values-kab/strings.xml @@ -2112,14 +2112,6 @@ Tuqqna ɣer uqeddac truḥ Askar n usafag yermed Ifecka n tneflit - - %d tafrant - %d tifranin - - - %d tafrant - Igmaḍ n taggara - %d tifranin - Igmaḍ n taggara - Yerna assenqed afessas Seqdec tafyirt tuffirt n tririt neɣ tasarut Ma yella ur tezmireḍ ara ad tkecmeḍ ɣer tɣimit i yellan diff --git a/vector/src/main/res/values-lv/strings.xml b/vector/src/main/res/values-lv/strings.xml index ef54cbd567..96d260a223 100644 --- a/vector/src/main/res/values-lv/strings.xml +++ b/vector/src/main/res/values-lv/strings.xml @@ -2563,16 +2563,6 @@ Nākotnē šī pārbaudes procedūra plānota sarežģītāka. Sūtīt attēlu ar oriģinālo izmēru Sūtīt attēlus ar oriģinālo izmēru - - %d balsu - Galīgie rezultāti - %d balss - Galīgie rezultāti - %d balsis - Galīgie rezultāti - - - %d balsu - %d balss - %d balsis - nestabila stabila Noklusējuma versija diff --git a/vector/src/main/res/values-nl/strings.xml b/vector/src/main/res/values-nl/strings.xml index 838ef31052..03a8ec331c 100644 --- a/vector/src/main/res/values-nl/strings.xml +++ b/vector/src/main/res/values-nl/strings.xml @@ -1641,10 +1641,6 @@ Sleutelverzoeken Verwijderen Bevestigen Gekozen Optie - - %d stem - %d stemmen - Accountgegevens Dev Tools QR-code diff --git a/vector/src/main/res/values-pl/strings.xml b/vector/src/main/res/values-pl/strings.xml index 4a6422fb3f..fa2a7c73e0 100644 --- a/vector/src/main/res/values-pl/strings.xml +++ b/vector/src/main/res/values-pl/strings.xml @@ -1791,16 +1791,6 @@ Nie Narzędzia programistyczne Dane konta - - %d głos - %d głosów - - - - %d głos - wyniki końcowe - %d głosów - wyniki końcowe - - Wybrana Opcja Tworzy prostą ankietę Użyj hasła odzyskiwania lub klucza diff --git a/vector/src/main/res/values-pt-rBR/strings.xml b/vector/src/main/res/values-pt-rBR/strings.xml index 6fcce1e672..83f0ddb6b2 100644 --- a/vector/src/main/res/values-pt-rBR/strings.xml +++ b/vector/src/main/res/values-pt-rBR/strings.xml @@ -2129,14 +2129,6 @@ Modo avião está ligado Ferramentas Dev Dados de Conta - - %d voto - %d votos - - - %d voto - Resultados finais - %d votos - Resultados finais - Opção Selecionada Cria uma sondagem simples Use uma Chave ou a Frasepasse de Recuperação diff --git a/vector/src/main/res/values-ru/strings.xml b/vector/src/main/res/values-ru/strings.xml index 948447e134..944c4905b9 100644 --- a/vector/src/main/res/values-ru/strings.xml +++ b/vector/src/main/res/values-ru/strings.xml @@ -2232,16 +2232,6 @@ Недоверенные Эта сессия является доверенной для безопасного обмена сообщениями, так как %1$s (%2$s) проверил(а) его: %1$s (%2$s) вошел(ла), используя новую сессию: - - %d голос - %d голоса - %d голосов - - - %d голос - финальные результаты - %d голоса - финальные результаты - %d голосов - финальные результаты - Выберите вариант Создать простой опрос Новый вход diff --git a/vector/src/main/res/values-sk/strings.xml b/vector/src/main/res/values-sk/strings.xml index fb15a84939..a35b155f80 100644 --- a/vector/src/main/res/values-sk/strings.xml +++ b/vector/src/main/res/values-sk/strings.xml @@ -2080,11 +2080,6 @@ Kľúč správy Overovanie zrušené Potvrdiť odstránenie - - %d hlas - %d hlasy - %d hlasov - Údaje o účte Vývojárske nástroje QR kód diff --git a/vector/src/main/res/values-sq/strings.xml b/vector/src/main/res/values-sq/strings.xml index 692482be9b..deb1aa035b 100644 --- a/vector/src/main/res/values-sq/strings.xml +++ b/vector/src/main/res/values-sq/strings.xml @@ -1950,14 +1950,6 @@ %s i verifikuar Mjete Zhvilluesi Të dhëna Llogarie - - %d votë - %d vota - - - %d votë - Përfundimet finale - %d vota - Përfundimet finale - Mundësi e Përzgjedhur Krijoni një pyetësor të thjeshtë Përdorni Frazëkalim Rikthimesh ose Kyç diff --git a/vector/src/main/res/values-sv/strings.xml b/vector/src/main/res/values-sv/strings.xml index 5ca0269d43..b185cc1bb9 100644 --- a/vector/src/main/res/values-sv/strings.xml +++ b/vector/src/main/res/values-sv/strings.xml @@ -2184,14 +2184,6 @@ Nej Anslutningen till servern har tappats Flygplansläge är på - - %d röst - %d röster - - - %d röst - Slutgiltigt resultat - %d röster - Slutgiltigt resultat - Valt alternativ Skapar en enkel omröstning Använd en återställningslösenfras eller -nyckel diff --git a/vector/src/main/res/values-uk/strings.xml b/vector/src/main/res/values-uk/strings.xml index 2e29b7a94d..4bf26d75ef 100644 --- a/vector/src/main/res/values-uk/strings.xml +++ b/vector/src/main/res/values-uk/strings.xml @@ -2754,18 +2754,6 @@ Звірити Створює просте опитування Вибрана опція - - %d голос - Підсумок - %d голоси - Підсумок - %d голосів - Підсумок - %d голосів - Підсумок - - - %d голос - %d голоси - %d голосів - %d голосів - Ініціалізувати перехресне підписування %1$s (%2$s) входить за допомогою нового сеансу: нестабільна diff --git a/vector/src/main/res/values-vi/strings.xml b/vector/src/main/res/values-vi/strings.xml index 442b338f8f..cc65231fb3 100644 --- a/vector/src/main/res/values-vi/strings.xml +++ b/vector/src/main/res/values-vi/strings.xml @@ -1661,12 +1661,6 @@ Hiển thị %d thiết bị bạn có thể xác minh ngay bây giờ - - %d phiếu bầu - Kết quả cuối cùng - - - %d phiếu bầu - %d phiên đang hoạt động diff --git a/vector/src/main/res/values-zh-rCN/strings.xml b/vector/src/main/res/values-zh-rCN/strings.xml index 80bfdb40a7..f8aa0e5979 100644 --- a/vector/src/main/res/values-zh-rCN/strings.xml +++ b/vector/src/main/res/values-zh-rCN/strings.xml @@ -1980,12 +1980,6 @@ 飞行模式已打开 开发工具 账号数据 - - %d 票 - - - %d 票 - 最终结果 - 已选选项 创建简单调查 使用恢复密语或密钥 diff --git a/vector/src/main/res/values-zh-rTW/strings.xml b/vector/src/main/res/values-zh-rTW/strings.xml index 094edbcd91..852c03a5ce 100644 --- a/vector/src/main/res/values-zh-rTW/strings.xml +++ b/vector/src/main/res/values-zh-rTW/strings.xml @@ -1925,12 +1925,6 @@ 使用者名稱 開發者工具 帳號資料 - - %d 投票 - - - %d 投票 - 最後結果 - 已選取的選項 建立簡易投票 使用復原通關密語或金鑰 diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 3bfab95a3d..48068834c8 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -2935,14 +2935,6 @@ Account Data Delete the account data of type %1$s?\n\nUse with caution, it may lead to unexpected behavior. - - %d vote - %d votes - - - %d vote - Final results - %d votes - Final results - Selected Option Creates a simple poll Use a Recovery Passphrase or Key From eb875823d55cb06cfd0ccb43f2b3ed8faac5cee1 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 4 Jan 2022 13:00:01 +0100 Subject: [PATCH 190/632] Remove unused class and resource after new poll implementation --- .../res/values/stylable_pool_result_line.xml | 11 --- .../timeline/item/PollResultLineView.kt | 74 ------------------- .../main/res/layout/view_poll_result_line.xml | 40 ---------- vector/src/main/res/values-bg/strings.xml | 2 - vector/src/main/res/values-ca/strings.xml | 2 - vector/src/main/res/values-cs/strings.xml | 2 - vector/src/main/res/values-de/strings.xml | 2 - vector/src/main/res/values-eo/strings.xml | 2 - vector/src/main/res/values-es/strings.xml | 2 - vector/src/main/res/values-et/strings.xml | 2 - vector/src/main/res/values-eu/strings.xml | 1 - vector/src/main/res/values-fa/strings.xml | 2 - vector/src/main/res/values-fr-rCA/strings.xml | 2 - vector/src/main/res/values-fr/strings.xml | 2 - vector/src/main/res/values-hu/strings.xml | 2 - vector/src/main/res/values-in/strings.xml | 2 - vector/src/main/res/values-it/strings.xml | 2 - vector/src/main/res/values-iw/strings.xml | 2 - vector/src/main/res/values-kab/strings.xml | 2 - vector/src/main/res/values-lv/strings.xml | 2 - vector/src/main/res/values-nl/strings.xml | 2 - vector/src/main/res/values-pl/strings.xml | 2 - vector/src/main/res/values-pt-rBR/strings.xml | 2 - vector/src/main/res/values-ru/strings.xml | 2 - vector/src/main/res/values-sq/strings.xml | 2 - vector/src/main/res/values-sv/strings.xml | 2 - vector/src/main/res/values-uk/strings.xml | 2 - vector/src/main/res/values-vi/strings.xml | 2 - vector/src/main/res/values-zh-rCN/strings.xml | 2 - vector/src/main/res/values-zh-rTW/strings.xml | 2 - vector/src/main/res/values/strings.xml | 2 - 31 files changed, 180 deletions(-) delete mode 100644 library/ui-styles/src/main/res/values/stylable_pool_result_line.xml delete mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollResultLineView.kt delete mode 100644 vector/src/main/res/layout/view_poll_result_line.xml diff --git a/library/ui-styles/src/main/res/values/stylable_pool_result_line.xml b/library/ui-styles/src/main/res/values/stylable_pool_result_line.xml deleted file mode 100644 index 93e9851106..0000000000 --- a/library/ui-styles/src/main/res/values/stylable_pool_result_line.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollResultLineView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollResultLineView.kt deleted file mode 100644 index aa864851cd..0000000000 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollResultLineView.kt +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2020 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package im.vector.app.features.home.room.detail.timeline.item - -import android.content.Context -import android.graphics.Typeface -import android.util.AttributeSet -import android.view.View -import android.widget.LinearLayout -import androidx.core.content.withStyledAttributes -import im.vector.app.R -import im.vector.app.core.extensions.setTextOrHide -import im.vector.app.databinding.ViewPollResultLineBinding - -class PollResultLineView @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0 -) : LinearLayout(context, attrs, defStyleAttr) { - - private val views: ViewPollResultLineBinding - - var label: String? = null - set(value) { - field = value - views.pollResultItemLabel.setTextOrHide(value) - } - - var percent: String? = null - set(value) { - field = value - views.pollResultItemPercent.setTextOrHide(value) - } - - var optionSelected: Boolean = false - set(value) { - field = value - views.pollResultItemSelectedIcon.visibility = if (value) View.VISIBLE else View.INVISIBLE - } - - var isWinner: Boolean = false - set(value) { - field = value - // Text in main color - views.pollResultItemLabel.setTypeface(views.pollResultItemLabel.typeface, if (value) Typeface.BOLD else Typeface.NORMAL) - views.pollResultItemPercent.setTypeface(views.pollResultItemPercent.typeface, if (value) Typeface.BOLD else Typeface.NORMAL) - } - - init { - inflate(context, R.layout.view_poll_result_line, this) - views = ViewPollResultLineBinding.bind(this) - orientation = HORIZONTAL - - context.withStyledAttributes(attrs, R.styleable.PollResultLineView) { - label = getString(R.styleable.PollResultLineView_optionName) ?: "" - percent = getString(R.styleable.PollResultLineView_optionCount) ?: "" - optionSelected = getBoolean(R.styleable.PollResultLineView_optionSelected, false) - isWinner = getBoolean(R.styleable.PollResultLineView_optionIsWinner, false) - } - } -} diff --git a/vector/src/main/res/layout/view_poll_result_line.xml b/vector/src/main/res/layout/view_poll_result_line.xml deleted file mode 100644 index bf115a3c43..0000000000 --- a/vector/src/main/res/layout/view_poll_result_line.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/vector/src/main/res/values-bg/strings.xml b/vector/src/main/res/values-bg/strings.xml index 90b6894063..5b8f718b43 100644 --- a/vector/src/main/res/values-bg/strings.xml +++ b/vector/src/main/res/values-bg/strings.xml @@ -1921,7 +1921,6 @@ Изчакване… Резултат от проверка Реагира с: %s - Бот бутони Анкета Стикер Файл @@ -2065,7 +2064,6 @@ Ако не можете да достъпите съществуваща сесия Използвай парола или ключ за възстановяване Създава проста анкета - Избрана опция Изтриване на профилните данни от тип %1$s\? \n \nВнимавайте с това - може да доведе до неочаквано поведение. diff --git a/vector/src/main/res/values-ca/strings.xml b/vector/src/main/res/values-ca/strings.xml index 118abcfb57..18373760d9 100644 --- a/vector/src/main/res/values-ca/strings.xml +++ b/vector/src/main/res/values-ca/strings.xml @@ -2252,7 +2252,6 @@ Si no pots accedir a una sessió existent Utilitza una frase de recuperació o una clau Crea una votació simple - Opció seleccionada Vols eliminar les dades del compte de tipus %1$s\? \n \nVés en compte, pot ser que provoqui comportaments inesperats. @@ -2514,7 +2513,6 @@ Esperant… Conclusió de la verificació Ha reaccionat amb: %s - Botons de bot Votació Adhesiu Imatge. diff --git a/vector/src/main/res/values-cs/strings.xml b/vector/src/main/res/values-cs/strings.xml index 693c17d1db..507cd6d8d5 100644 --- a/vector/src/main/res/values-cs/strings.xml +++ b/vector/src/main/res/values-cs/strings.xml @@ -1996,7 +1996,6 @@ Spojení k serveru bylo ztraceno Vývojářské nástroje Údaje účtu - Zvolená možnost Vytvoří jednoduché hlasování Použijte metodu obnovy Pokud se nemůžete dostat do existující relace @@ -2407,7 +2406,6 @@ Zobrazit stavové události účastníků v místnosti Zahrnuje události pozvat/vstoupit/opustit/vykopnout/vykázat a změny avatara/veřejného jména. Hlasování - Tlačítka botů Reagoval(a): %s Výsledek ověření Odkaz byl chybně zformován diff --git a/vector/src/main/res/values-de/strings.xml b/vector/src/main/res/values-de/strings.xml index d7b95fd64d..a9effebe50 100644 --- a/vector/src/main/res/values-de/strings.xml +++ b/vector/src/main/res/values-de/strings.xml @@ -2024,7 +2024,6 @@ Entwicklerwerkzeuge Kontodaten Erstellt eine einfache Umfrage - Ausgewählte Option Nutze eine Wiederherstellungsmethode Wenn du auf keine existierende Sitzung zugreifen kannst Neue Anmeldung @@ -2426,7 +2425,6 @@ Status-Ereignisse der Raummitglieder zeigen Bezieht Einladungs-/Beitritts-/Verlassen-/Entfernen-/Verbannen-Ereignisse und Avatar-/Anzeigenamen-Wechsel mit ein. Umfrage - Bot-Schaltflächen Reagierte mit: %s Der Link war fehlerhaft Du bist nicht berechtigt, einen Anruf in diesem Raum zu starten diff --git a/vector/src/main/res/values-eo/strings.xml b/vector/src/main/res/values-eo/strings.xml index bc7a6483da..36d1d6a449 100644 --- a/vector/src/main/res/values-eo/strings.xml +++ b/vector/src/main/res/values-eo/strings.xml @@ -803,7 +803,6 @@ Atendante… Rezulto de kontrolo Reagis per: %s - Butonoj de robotoj Enketo Glumarko Dosiero @@ -2232,7 +2231,6 @@ Se vi ne povas aliri jaman salutaĵon Uzi rehavan pasfrazon aŭ ŝlosilon Faras simplan enketon - Elektita elekteblo Ĉu forigi la datumojn de konto de la speco «%1$s»\? \n \nForu tion zorge, ĝi povus kaŭzi neatenditan konduton. diff --git a/vector/src/main/res/values-es/strings.xml b/vector/src/main/res/values-es/strings.xml index eb921d520a..099ab2f2ca 100644 --- a/vector/src/main/res/values-es/strings.xml +++ b/vector/src/main/res/values-es/strings.xml @@ -1966,7 +1966,6 @@ Por favor permite el acceso en la próxima ventana emergente para descubrir usua Modo Avión Activado Herramientas de desarrollo Datos de cuenta - Seleccionar Opcion Nuevo inicio de sesión Advertencia: Eliminar… @@ -2430,7 +2429,6 @@ Por favor permite el acceso en la próxima ventana emergente para descubrir usua \n \nTus mensajes están asegurados con un candado. Solo tú y tú destinatario tenéis las llaves especiales para descifrarlos. Los mensajes aquí no están cifrados Extremo-a-Extremo. - Botones de Bot Encuesta Eliminar de baja prioridad Añadir a Baja prioridad diff --git a/vector/src/main/res/values-et/strings.xml b/vector/src/main/res/values-et/strings.xml index 1a35902987..50cb6bf018 100644 --- a/vector/src/main/res/values-et/strings.xml +++ b/vector/src/main/res/values-et/strings.xml @@ -2243,7 +2243,6 @@ Lennurežiim on kasutusel Arendaja töövahendid Kasutajakonto andmed - Tehtud valik Loob lihtsa hääletuse Kasuta taastamiseks mõeldud paroolifraasi või võtit Kui sa ei pääse ligi olemasolevale sessioonile @@ -2363,7 +2362,6 @@ Näita jututoa liikmete olekusündmusi Sealhulgas kutsumisi, liitumisi, lahkumisi, müksamisi, keelamisi ning tunnuspildi ja kuvatava nime muutusi. Küsitlus - Robotinupud Reageeris: %s Verifitseerimise tulemus Link oli vigane diff --git a/vector/src/main/res/values-eu/strings.xml b/vector/src/main/res/values-eu/strings.xml index da975dc2af..13ad092f42 100644 --- a/vector/src/main/res/values-eu/strings.xml +++ b/vector/src/main/res/values-eu/strings.xml @@ -2233,7 +2233,6 @@ Abisua: Fitxategi hau ezabatu daiteke aplikazioa desinstalatzen bada. Erabiltzaile-izena Garapen tresnak Kontuaren datuak - Hautatutako aukera Inkesta sinplea sortzen du Erabili berreskuratze metodo bat Ezin baduzu badagoen saio bat erabili diff --git a/vector/src/main/res/values-fa/strings.xml b/vector/src/main/res/values-fa/strings.xml index 88840717aa..8582626fd3 100644 --- a/vector/src/main/res/values-fa/strings.xml +++ b/vector/src/main/res/values-fa/strings.xml @@ -1367,7 +1367,6 @@ پرونده برچسب نظرسنجی - دکمه‌های بات واکنش داده با: %s نتیجه‌گیری تأیید در حال انتظار… @@ -1448,7 +1447,6 @@ اتّصال به کارساز از دست رفت حالت هواپیما روشن است داده‌های حساب - انتخاب گزیده ایجاد نظرسنجی‌ای ساده استفاده از یک کلید یا عبارت بازیابی اگر به نشست‌های موجود دسترسی ندارید diff --git a/vector/src/main/res/values-fr-rCA/strings.xml b/vector/src/main/res/values-fr-rCA/strings.xml index 1ffb00f106..8380765a4f 100644 --- a/vector/src/main/res/values-fr-rCA/strings.xml +++ b/vector/src/main/res/values-fr-rCA/strings.xml @@ -62,7 +62,6 @@ Si vous n’avez pas accès à une session existante Utiliser une phrase secrète ou une clé de récupération Crée un sondage simple - Option sélectionnée Supprimer les données du compte de type %1$s \? \n \nÀ utiliser avec précaution, ceci peut entraîner des comportements inattendus. @@ -190,7 +189,6 @@ En attente… Conclusion de la vérification A réagi avec : %s - Boutons de robot Sondage Autocollants Fichier diff --git a/vector/src/main/res/values-fr/strings.xml b/vector/src/main/res/values-fr/strings.xml index e91032fc89..54e90b8957 100644 --- a/vector/src/main/res/values-fr/strings.xml +++ b/vector/src/main/res/values-fr/strings.xml @@ -1937,7 +1937,6 @@ Nom d’utilisateur Outils de développement Données du compte - Option sélectionnée Crée un sondage simple Utiliser une phrase secrète ou une clé de récupération Si vous n’avez pas accès à une session existante @@ -2244,7 +2243,6 @@ Les messages ici sont chiffrés de bout en bout. \n \nVos messages sont sécurisés avec des verrous et seuls vous et le destinataire en possédez la clé unique pour les déverrouiller. - Boutons de robot Autocollants Ce serveur d\'accueil utilise une version obsolète. Demandez à l’administrateur de votre serveur d\'accueil de le mettre à jour. Vous pouvez continuer, mais certaines fonctionnalités peuvent ne pas fonctionner correctement. Entrez l’adresse du serveur que vous voulez utiliser diff --git a/vector/src/main/res/values-hu/strings.xml b/vector/src/main/res/values-hu/strings.xml index 7d963b7646..abd24364a2 100644 --- a/vector/src/main/res/values-hu/strings.xml +++ b/vector/src/main/res/values-hu/strings.xml @@ -1894,7 +1894,6 @@ Ha nem te állítottad be a visszaállítási metódust, akkor egy támadó pró Felhasználónév Fejlesztői Eszközök Fiók Adatok - Kiválasztott Beállítások Egyszerű szavazás készítése Használd a visszaállítási eljárást Ha nem érsz el létező munkamenetet @@ -2684,7 +2683,6 @@ Ha nem te állítottad be a visszaállítási metódust, akkor egy támadó pró \nAz üzeneteidet zárolással vannak biztosítva és csak neked és a címzetteknek van meg a kulcs hozzá. Itt az üzenetek nincsenek végponttól végpontig titkosítva. Azonosítás eredménye - Bot Gomb Szavazás Szoba létrehozása… Néhány karakter nem engedélyezett diff --git a/vector/src/main/res/values-in/strings.xml b/vector/src/main/res/values-in/strings.xml index 52a79b2dbc..909c7e83e8 100644 --- a/vector/src/main/res/values-in/strings.xml +++ b/vector/src/main/res/values-in/strings.xml @@ -2173,7 +2173,6 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Menunggu… Kesimpulan Verifikasi Bereaksi dengan: %s - Tombol Bot Poll Stiker File @@ -2888,7 +2887,6 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Sign In Baru Gunakan Frasa Sandi Pemulihan atau Kunci Membuat poll sederhana - Opsi yang Dipilih Hapus data akun dengan tipe %1$s\? \n \nHati-hati menggunakannya, ini dapat menyebabkan perilaku yang tidak terduga. diff --git a/vector/src/main/res/values-it/strings.xml b/vector/src/main/res/values-it/strings.xml index 1c9edffadf..7df19547ed 100644 --- a/vector/src/main/res/values-it/strings.xml +++ b/vector/src/main/res/values-it/strings.xml @@ -2015,7 +2015,6 @@ Nome utente Strumenti per sviluppatori Dati account - Opzione selezionata Crea un semplice sondaggio Usa una Passphrase o un codice di recupero Se non puoi accedere a una sessione esistente @@ -2417,7 +2416,6 @@ Mostra eventi di stato dei membri della stanza Includi eventi di invito/entrata/uscita/kick/ban e modifiche di avatar/nome. Sondaggio - Pulsanti bot Reagito con: %s Verifica conclusa Vuoi eliminare i dati dell\'account di tipo %1$s\? diff --git a/vector/src/main/res/values-iw/strings.xml b/vector/src/main/res/values-iw/strings.xml index 5fe877ea77..e358463098 100644 --- a/vector/src/main/res/values-iw/strings.xml +++ b/vector/src/main/res/values-iw/strings.xml @@ -1283,7 +1283,6 @@ הכנתי עותק מסקנת אימות הגיב עם: %s - כפתורי בוט משאל מדבקה קובץ @@ -2021,7 +2020,6 @@ אם אינך יכול לגשת להפעלה קיימת השתמש בביטוי סיסמה או מפתח שחזור יוצר סקר פשוט - אפשרות נבחרת למחוק את נתוני החשבון מסוג %1$s\? \n \nהשתמש בזהירות, זה עלול להוביל להתנהגות בלתי צפויה. diff --git a/vector/src/main/res/values-kab/strings.xml b/vector/src/main/res/values-kab/strings.xml index c8fbe86cfe..d5c2ac0c53 100644 --- a/vector/src/main/res/values-kab/strings.xml +++ b/vector/src/main/res/values-kab/strings.xml @@ -1453,7 +1453,6 @@ Ittraju %s… Isuli-d Isefka n umiḍan - Taxtirit yettefernen Anekcum amaynut Sekcem tasarut tuffirt n uḥraz uffir Ɣur-k·m: @@ -2287,7 +2286,6 @@ %d n tsintin Sken tidyanin n waddad n uɛeggal n texxamt - Tiqeffalin n uṛubut Taggrayt n usenqed Aseɣwen ur yemsil ara akken iwata Ur tesɛiḍ ara tasiregt ad tebduḍ asiwel deg texxamt-a diff --git a/vector/src/main/res/values-lv/strings.xml b/vector/src/main/res/values-lv/strings.xml index 96d260a223..3166600dec 100644 --- a/vector/src/main/res/values-lv/strings.xml +++ b/vector/src/main/res/values-lv/strings.xml @@ -1275,7 +1275,6 @@ Nākotnē šī pārbaudes procedūra plānota sarežģītāka. Citas istabas Nesenās istabas Verifikācijas slēdziens - Bota pogas Aptauja Uzlīme Kāds no uzskaitītajiem var būt kompromitēts: @@ -2159,7 +2158,6 @@ Nākotnē šī pārbaudes procedūra plānota sarežģītāka. Atslēgas jau ir atjauninātas! Pasākumu moderē telpas administrators, iemesls: %1$s Lietotājs izdzēsa notikumu, iemesls: %1$s - Izvēlētā Opcija Dzēst %1$s tipa konta datus\? \n \nLietojiet piesardzīgi, tas var izraisīt neparedzētu uzvedību. diff --git a/vector/src/main/res/values-nl/strings.xml b/vector/src/main/res/values-nl/strings.xml index 03a8ec331c..8a986c49d0 100644 --- a/vector/src/main/res/values-nl/strings.xml +++ b/vector/src/main/res/values-nl/strings.xml @@ -1588,7 +1588,6 @@ %1$ds over %s is toegetreden. Conclusie Bevestiging - Botknoppen Gespreksinstellingen Gespreksnaam Integraties Beheren @@ -1640,7 +1639,6 @@ Bevestiging Geannuleerd Sleutelverzoeken Verwijderen Bevestigen - Gekozen Optie Accountgegevens Dev Tools QR-code diff --git a/vector/src/main/res/values-pl/strings.xml b/vector/src/main/res/values-pl/strings.xml index fa2a7c73e0..f9adef1f7e 100644 --- a/vector/src/main/res/values-pl/strings.xml +++ b/vector/src/main/res/values-pl/strings.xml @@ -1791,7 +1791,6 @@ Nie Narzędzia programistyczne Dane konta - Wybrana Opcja Tworzy prostą ankietę Użyj hasła odzyskiwania lub klucza Użyj klucza odzyskiwania lub hasła @@ -2067,7 +2066,6 @@ Wiadomości tutaj nie są zaszyfrowane w trybie punkt-punkt (e2e). Weryfikacja wniosków Zareagowano: %s - Przyciski botów Tworzenie pokoju… Niektóre znaki nie są dozwolone Podaj adres pokoju diff --git a/vector/src/main/res/values-pt-rBR/strings.xml b/vector/src/main/res/values-pt-rBR/strings.xml index 83f0ddb6b2..c86cc89ed8 100644 --- a/vector/src/main/res/values-pt-rBR/strings.xml +++ b/vector/src/main/res/values-pt-rBR/strings.xml @@ -2129,7 +2129,6 @@ Modo avião está ligado Ferramentas Dev Dados de Conta - Opção Selecionada Cria uma sondagem simples Use uma Chave ou a Frasepasse de Recuperação Se você não pode acessar uma sessão existente @@ -2435,7 +2434,6 @@ Este número de telefone já está definido. Mostrar eventos de estado de membros de sala Sondagem - Botões de Bot Reagiu com: %s Conclusão de Verificação Deletar os dados de conta de tipo %1$s\? diff --git a/vector/src/main/res/values-ru/strings.xml b/vector/src/main/res/values-ru/strings.xml index 944c4905b9..0979c60526 100644 --- a/vector/src/main/res/values-ru/strings.xml +++ b/vector/src/main/res/values-ru/strings.xml @@ -2232,7 +2232,6 @@ Недоверенные Эта сессия является доверенной для безопасного обмена сообщениями, так как %1$s (%2$s) проверил(а) его: %1$s (%2$s) вошел(ла), используя новую сессию: - Выберите вариант Создать простой опрос Новый вход Пока этот пользователь не доверяет этой сессии, сообщения, отправленные в обе стороны, помечаются предупреждениями. Кроме того, вы можете подтвердить сессию вручную. @@ -2498,7 +2497,6 @@ Показать события статуса участников комнаты Включает в себя события приглашения/ присоединения/выхода/исключения/бана и изменение аватара/отображаемого имени. Голосование - Кнопки бота Отреагировал: %s Результат проверки Ссылка была искажена diff --git a/vector/src/main/res/values-sq/strings.xml b/vector/src/main/res/values-sq/strings.xml index deb1aa035b..1787bb2a65 100644 --- a/vector/src/main/res/values-sq/strings.xml +++ b/vector/src/main/res/values-sq/strings.xml @@ -1950,7 +1950,6 @@ %s i verifikuar Mjete Zhvilluesi Të dhëna Llogarie - Mundësi e Përzgjedhur Krijoni një pyetësor të thjeshtë Përdorni Frazëkalim Rikthimesh ose Kyç Nëse s’hyni dot në një sesion ekzistues @@ -2269,7 +2268,6 @@ Nëse s’e dini fjalëkalimin, kthehuni mbrapsht që ta ricaktoni. Ngjitës Pyetësor - Butona Roboti Reagoi me: %s Përfundim Verifikimi Veprime Përgjegjësi diff --git a/vector/src/main/res/values-sv/strings.xml b/vector/src/main/res/values-sv/strings.xml index b185cc1bb9..429df1e535 100644 --- a/vector/src/main/res/values-sv/strings.xml +++ b/vector/src/main/res/values-sv/strings.xml @@ -2184,7 +2184,6 @@ Nej Anslutningen till servern har tappats Flygplansläge är på - Valt alternativ Skapar en enkel omröstning Använd en återställningslösenfras eller -nyckel Ny inloggning @@ -2363,7 +2362,6 @@ Visa statushändelser angående rumsmedlemmar Inkluderar när personer bjuds in, går med, lämnar, kickas, bannas eller byter sin avatar eller sitt namn. Omröstning - Bottknappar Reagerade med: %s Verifieringsavslutning Länken var felformaterad diff --git a/vector/src/main/res/values-uk/strings.xml b/vector/src/main/res/values-uk/strings.xml index 4bf26d75ef..75492dbafb 100644 --- a/vector/src/main/res/values-uk/strings.xml +++ b/vector/src/main/res/values-uk/strings.xml @@ -2585,7 +2585,6 @@ Ми надіслали вам електронний лист для підтвердження на %s, відкрийте свою е-пошту та клацніть на посилання для підтвердження Ми надіслали вам електронний лист для підтвердження на %s, відкрийте свою е-пошту та клацніть на посилання для підтвердження Ви - Кнопки бота Опитування Файл Голосове @@ -2753,7 +2752,6 @@ Звірити порівнявши емоджі Звірити Створює просте опитування - Вибрана опція Ініціалізувати перехресне підписування %1$s (%2$s) входить за допомогою нового сеансу: нестабільна diff --git a/vector/src/main/res/values-vi/strings.xml b/vector/src/main/res/values-vi/strings.xml index cc65231fb3..9d1c1a3e17 100644 --- a/vector/src/main/res/values-vi/strings.xml +++ b/vector/src/main/res/values-vi/strings.xml @@ -2253,7 +2253,6 @@ Đang chờ… Kết luận xác minh Phản ứng với: %s - Nút Bot Thăm dò ý kiến Sticker Tệp @@ -2692,7 +2691,6 @@ Nếu bạn không thể truy nhập phiên hiện có Sử dụng Cụm mật khẩu hoặc Khóa phục hồi Tạo một cuộc thăm dò ý kiến đơn giản - Tùy chọn đã chọn Xóa dữ liệu tài khoản của loại %1$s\? \n \nSử dụng một cách thận trọng, nó có thể dẫn đến hành vi bất ngờ. diff --git a/vector/src/main/res/values-zh-rCN/strings.xml b/vector/src/main/res/values-zh-rCN/strings.xml index f8aa0e5979..95ec1a3b8d 100644 --- a/vector/src/main/res/values-zh-rCN/strings.xml +++ b/vector/src/main/res/values-zh-rCN/strings.xml @@ -1980,7 +1980,6 @@ 飞行模式已打开 开发工具 账号数据 - 已选选项 创建简单调查 使用恢复密语或密钥 如果你无法访问已有会话 @@ -2284,7 +2283,6 @@ 显示聊天室成员状态事件 包括邀请/加入/离开/踢掉/封禁事件和头像/昵称更改。 轮询 - 机器人按钮 回应:%s 验证结果 是否删除类型 %1$s 的账号数据? diff --git a/vector/src/main/res/values-zh-rTW/strings.xml b/vector/src/main/res/values-zh-rTW/strings.xml index 852c03a5ce..bd90327bcd 100644 --- a/vector/src/main/res/values-zh-rTW/strings.xml +++ b/vector/src/main/res/values-zh-rTW/strings.xml @@ -1925,7 +1925,6 @@ 使用者名稱 開發者工具 帳號資料 - 已選取的選項 建立簡易投票 使用復原通關密語或金鑰 如果您無法存取既有的工作階段的話 @@ -2319,7 +2318,6 @@ 顯示聊天室成員狀態活動 包含邀請/加入/離開/踢除/封鎖事件與大頭貼/顯示名稱變更等。 投票 - 機器人按鈕 反應:%s 驗證結論 連結格式錯誤 diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 48068834c8..ffc634abed 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -2755,7 +2755,6 @@ File Sticker Poll - Bot Buttons Reacted with: %s Verification Conclusion @@ -2935,7 +2934,6 @@ Account Data Delete the account data of type %1$s?\n\nUse with caution, it may lead to unexpected behavior. - Selected Option Creates a simple poll Use a Recovery Passphrase or Key If you can’t access an existing session From ee69ece69716596e0bd0d223691c201ed7bf8eb0 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 4 Jan 2022 13:06:02 +0100 Subject: [PATCH 191/632] Fix some typo in string resource name --- .../room/detail/timeline/factory/EncryptedItemFactory.kt | 2 +- vector/src/main/res/layout/bottom_sheet_space_invite.xml | 2 +- vector/src/main/res/layout/item_verification_wait.xml | 2 +- vector/src/main/res/values-ar/strings.xml | 2 +- vector/src/main/res/values-az/strings.xml | 2 +- vector/src/main/res/values-bg/strings.xml | 4 ++-- vector/src/main/res/values-bn-rBD/strings.xml | 2 +- vector/src/main/res/values-bn-rIN/strings.xml | 2 +- vector/src/main/res/values-ca/strings.xml | 4 ++-- vector/src/main/res/values-cs/strings.xml | 8 ++++---- vector/src/main/res/values-da/strings.xml | 2 +- vector/src/main/res/values-de/strings.xml | 8 ++++---- vector/src/main/res/values-el/strings.xml | 2 +- vector/src/main/res/values-eo/strings.xml | 8 ++++---- vector/src/main/res/values-es-rMX/strings.xml | 2 +- vector/src/main/res/values-es/strings.xml | 8 ++++---- vector/src/main/res/values-et/strings.xml | 8 ++++---- vector/src/main/res/values-eu/strings.xml | 4 ++-- vector/src/main/res/values-fa/strings.xml | 8 ++++---- vector/src/main/res/values-fi/strings.xml | 6 +++--- vector/src/main/res/values-fr-rCA/strings.xml | 6 +++--- vector/src/main/res/values-fr/strings.xml | 8 ++++---- vector/src/main/res/values-fy/strings.xml | 2 +- vector/src/main/res/values-gl/strings.xml | 2 +- vector/src/main/res/values-hu/strings.xml | 8 ++++---- vector/src/main/res/values-in/strings.xml | 8 ++++---- vector/src/main/res/values-is/strings.xml | 2 +- vector/src/main/res/values-it/strings.xml | 8 ++++---- vector/src/main/res/values-iw/strings.xml | 2 +- vector/src/main/res/values-ja/strings.xml | 4 ++-- vector/src/main/res/values-kab/strings.xml | 4 ++-- vector/src/main/res/values-ko/strings.xml | 2 +- vector/src/main/res/values-lv/strings.xml | 8 ++++---- vector/src/main/res/values-ml/strings.xml | 2 +- vector/src/main/res/values-nb-rNO/strings.xml | 2 +- vector/src/main/res/values-nl/strings.xml | 2 +- vector/src/main/res/values-nn/strings.xml | 2 +- vector/src/main/res/values-pl/strings.xml | 8 ++++---- vector/src/main/res/values-pt-rBR/strings.xml | 8 ++++---- vector/src/main/res/values-pt/strings.xml | 2 +- vector/src/main/res/values-ro/strings.xml | 2 +- vector/src/main/res/values-ru/strings.xml | 8 ++++---- vector/src/main/res/values-sk/strings.xml | 2 +- vector/src/main/res/values-sq/strings.xml | 8 ++++---- vector/src/main/res/values-sr/strings.xml | 2 +- vector/src/main/res/values-sv/strings.xml | 8 ++++---- vector/src/main/res/values-te/strings.xml | 2 +- vector/src/main/res/values-uk/strings.xml | 8 ++++---- vector/src/main/res/values-vi/strings.xml | 8 ++++---- vector/src/main/res/values-vls/strings.xml | 2 +- vector/src/main/res/values-zh-rCN/strings.xml | 8 ++++---- vector/src/main/res/values-zh-rTW/strings.xml | 8 ++++---- vector/src/main/res/values/strings.xml | 8 ++++---- 53 files changed, 125 insertions(+), 125 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt index b8d7d96ecf..89c9c51f0c 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt @@ -56,7 +56,7 @@ class EncryptedItemFactory @Inject constructor(private val messageInformationDat val spannableStr = if (vectorPreferences.developerMode()) { val errorDescription = if (cryptoError == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID) { - stringProvider.getString(R.string.notice_crypto_error_unkwown_inbound_session_id) + stringProvider.getString(R.string.notice_crypto_error_unknown_inbound_session_id) } else { // TODO i18n cryptoError?.name diff --git a/vector/src/main/res/layout/bottom_sheet_space_invite.xml b/vector/src/main/res/layout/bottom_sheet_space_invite.xml index 1fa132a086..abd35e25ea 100644 --- a/vector/src/main/res/layout/bottom_sheet_space_invite.xml +++ b/vector/src/main/res/layout/bottom_sheet_space_invite.xml @@ -50,7 +50,7 @@ android:layout_marginBottom="16dp" app:icon="@drawable/ic_add_people" app:iconTint="?vctr_content_secondary" - app:title="@string/invite_by_mxid_or_mail" /> + app:title="@string/invite_by_username_or_mail" /> حدَّثَ %1$s ملفه الشخصي %2$s أرسلَ %1$s دعوة إلى %2$s للإنضمام إلى الغُرفة ** يتعذَّر فك التشفير: ⁨%s⁩ ** - لم يُرسل جِهاز المُرسل مفاتيحًا لهذه الرِّسالة. + لم يُرسل جِهاز المُرسل مفاتيحًا لهذه الرِّسالة. تعذَّر إرسال الرِّسالة فَشلَ رفع الصُّورة خطأ في الشَّبكة diff --git a/vector/src/main/res/values-az/strings.xml b/vector/src/main/res/values-az/strings.xml index 3821dcfb73..d92a66f2c3 100644 --- a/vector/src/main/res/values-az/strings.xml +++ b/vector/src/main/res/values-az/strings.xml @@ -50,7 +50,7 @@ %1$s %2$s üçün dəvəti qəbul etdi ** Şifrəni aça bilmir: %s ** - Göndərənin cihazı bu mesaj üçün açarları bizə göndərməyib. + Göndərənin cihazı bu mesaj üçün açarları bizə göndərməyib. Redaktə etmək olmur Mesaj göndərmək olmur diff --git a/vector/src/main/res/values-bg/strings.xml b/vector/src/main/res/values-bg/strings.xml index 5b8f718b43..9110cd0430 100644 --- a/vector/src/main/res/values-bg/strings.xml +++ b/vector/src/main/res/values-bg/strings.xml @@ -47,7 +47,7 @@ В момента не е възможно да се присъедините отново към празна стая. Имейл адрес Телефонен номер - Устройството на подателя не изпрати ключовете за това съобщение. + Устройството на подателя не изпрати ключовете за това съобщение. %1$s изпрати стикер. Покана от %s Покана за стая @@ -2164,7 +2164,7 @@ Поддържа се само в шифровани стаи Принудително премахва текущата изходяща групова сесия от шифрованата стая Използвайте последната версия на ${app_name} за устройствата си: - или друг Matrix клиент поддържаш кръстосано-подписване + или друг Matrix клиент поддържаш кръстосано-подписване ${app_name} iOS \n${app_name} Android ${app_name} Web diff --git a/vector/src/main/res/values-bn-rBD/strings.xml b/vector/src/main/res/values-bn-rBD/strings.xml index db2ee10bbc..63d8b506bb 100644 --- a/vector/src/main/res/values-bn-rBD/strings.xml +++ b/vector/src/main/res/values-bn-rBD/strings.xml @@ -1077,7 +1077,7 @@ চিত্র আপলোড করতে ব্যর্থ বার্তা পাঠাতে অক্ষম পুনরায় প্রতিক্রিয়া করতে পারেনি - প্রেরকের ডিভাইস আমাদের এই বার্তার জন্য কীগুলি প্রেরণ করেনি। + প্রেরকের ডিভাইস আমাদের এই বার্তার জন্য কীগুলি প্রেরণ করেনি। ** ডিক্রিপ্ট করতে অক্ষম: %s ** %1$s %2$s থেকে %3$s পর্যন্ত %1$s %2$s এর পাওয়ার স্তর পরিবর্তন করেছে। diff --git a/vector/src/main/res/values-bn-rIN/strings.xml b/vector/src/main/res/values-bn-rIN/strings.xml index 0fd00b03fe..6d3b426e3c 100644 --- a/vector/src/main/res/values-bn-rIN/strings.xml +++ b/vector/src/main/res/values-bn-rIN/strings.xml @@ -99,7 +99,7 @@ %1$s %2$s এর পাওয়ার স্তর পরিবর্তন করেছে। %1$s %2$s থেকে %3$s পর্যন্ত ** ডিক্রিপ্ট করতে অক্ষম: %s ** - প্রেরকের ডিভাইস আমাদের এই বার্তার জন্য কীগুলি প্রেরণ করেনি। + প্রেরকের ডিভাইস আমাদের এই বার্তার জন্য কীগুলি প্রেরণ করেনি। পুনরায় প্রতিক্রিয়া করতে পারেনি বার্তা পাঠাতে অক্ষম চিত্র আপলোড করতে ব্যর্থ diff --git a/vector/src/main/res/values-ca/strings.xml b/vector/src/main/res/values-ca/strings.xml index 18373760d9..214711224f 100644 --- a/vector/src/main/res/values-ca/strings.xml +++ b/vector/src/main/res/values-ca/strings.xml @@ -38,7 +38,7 @@ %1$s ha enviat una invitació a %2$s perquè s\'uneixi a la sala %1$s ha acceptat la invitació de %2$s ** No s\'ha pogut desxifrar: %s ** - El dispositiu del remitent no ens ha enviat les claus per aquest missatge. + El dispositiu del remitent no ens ha enviat les claus per aquest missatge. No s\'ha pogut redactar No s\'ha pogut enviar el missatge No s\'ha pogut pujar la imatge @@ -2025,7 +2025,7 @@ Obre el xat Silencia el micròfon El codi PIN és l\'única manera de desbloquejar ${app_name}. - o un altre client Matrix compatible amb la signatura creuada + o un altre client Matrix compatible amb la signatura creuada La signatura creuada no està activada La signatura creuada està activada \nClaus no fiables diff --git a/vector/src/main/res/values-cs/strings.xml b/vector/src/main/res/values-cs/strings.xml index 507cd6d8d5..7b33363896 100644 --- a/vector/src/main/res/values-cs/strings.xml +++ b/vector/src/main/res/values-cs/strings.xml @@ -40,7 +40,7 @@ %1$s do této místnosti pozvali %2$s %1$s přijali pozvání pro %2$s ** Nelze dešifrovat: %s ** - Odesílatelovo zařízení nám neposlalo klíče pro tuto zprávu. + Odesílatelovo zařízení nám neposlalo klíče pro tuto zprávu. Nelze vymazat Zprávu nelze odeslat Obrázek nelze nahrát @@ -2144,7 +2144,7 @@ \n${app_name} Desktop ${app_name} iOS \n${app_name} Android - nebo jiný Matrix klient schopný křížového podepisování + nebo jiný Matrix klient schopný křížového podepisování Použijte na svých zařízeních nejnovější ${app_name}: Vynutí zahození probíhající skupinové relace v šifrované místnosti Podporováno jen v šifrovaných místnostech @@ -2747,7 +2747,7 @@ Budou moci %s prozkoumat Pozvat do %s Sdílet odkaz - Pozvat podle uživatelského jména + Pozvat podle uživatelského jména Pozvat emailem V tomto okamžiku jste to jen Vy. %s bude s dalšími lepší. Pozvat lidi @@ -3003,7 +3003,7 @@ Pozvání e-mailem, vyhledávání kontaktů a další… Dokončit nastavení objevování. V současné době nepoužíváte server identit. Chcete-li pozvat kolegy a být pro ně zjistitelní, nakonfigurujte jej níže. - Pozvat pomocí uživatelského jména nebo e-mailu + Pozvat pomocí uživatelského jména nebo e-mailu Zajistěte, aby měli do společnosti %s přístup ti správní lidé. Další můžete pozvat později. Kdo jsou vaši kolegové\? Přidat do daného prostoru diff --git a/vector/src/main/res/values-da/strings.xml b/vector/src/main/res/values-da/strings.xml index 077bf0efda..546fe51ed2 100644 --- a/vector/src/main/res/values-da/strings.xml +++ b/vector/src/main/res/values-da/strings.xml @@ -39,7 +39,7 @@ %1$s inviterede %2$s til rummet %1$s accepterede invitationen til %2$s ** Kunne ikke dekryptere: %s ** - Afsenderens enhed har ikke sendt os nøglerne til denne besked. + Afsenderens enhed har ikke sendt os nøglerne til denne besked. Kunne ikke hemmeligholde Kunne ikke sende besked Kunne ikke uploade billede diff --git a/vector/src/main/res/values-de/strings.xml b/vector/src/main/res/values-de/strings.xml index a9effebe50..69ae5dd4f7 100644 --- a/vector/src/main/res/values-de/strings.xml +++ b/vector/src/main/res/values-de/strings.xml @@ -39,7 +39,7 @@ %1$s hat eine Einladung an %2$s gesendet %1$s hat die Einladung in %2$s akzeptiert ** Nicht entschlüsselbar: %s ** - Das absendende Gerät hat uns keine Schlüssel für diese Nachricht übermittelt. + Das absendende Gerät hat uns keine Schlüssel für diese Nachricht übermittelt. Entfernen nicht möglich Nachricht kann nicht gesendet werden @@ -2152,7 +2152,7 @@ \n${app_name} Desktop ${app_name} iOS \n${app_name} Android - oder einen anderen cross-signing-fähigen Matrix Client + oder einen anderen cross-signing-fähigen Matrix Client Nutze die neueste Version von ${app_name} auf deinen anderen Geräten: Erzwingt das Verwerfen der aktuell ausgehende Gruppensitzung in einem verschlüsseltem Raum Wird nur in verschlüsselten Räumen unterstützt @@ -2770,7 +2770,7 @@ Nur zu diesem Raum In Space \"%s\" einladen Link teilen - Mithilfe eines Benutzernamens einladen + Mithilfe eines Benutzernamens einladen Mithilfe einer E-Mail-Adresse einladen Personen einladen Lade Personen in deinen Space ein @@ -2993,7 +2993,7 @@ Du wirst alle Räume und Spaces in %s verlassen. Alle Räume und Spaces verlassen Willst du %s wirklich verlassen\? - Mit Benutzername oder E-Mail einladen + Mit Benutzername oder E-Mail einladen Zum ausgewählten Space hinzufügen Erstelle Space… Hilfreiche Informationen zur Fehlersuche anzeigen diff --git a/vector/src/main/res/values-el/strings.xml b/vector/src/main/res/values-el/strings.xml index 1ae7629fef..2772f9f5d4 100644 --- a/vector/src/main/res/values-el/strings.xml +++ b/vector/src/main/res/values-el/strings.xml @@ -38,7 +38,7 @@ Ο/Η %1$s δέχτηκε την πρόσκληση για το %2$s ** Αδυναμία αποκρυπτογράφησης: %s ** - Η συσκευή του/της αποστολέα δεν μας έχει στείλει τα κλειδιά για αυτό το μήνυμα. + Η συσκευή του/της αποστολέα δεν μας έχει στείλει τα κλειδιά για αυτό το μήνυμα. Αποτυχία αποστολής μηνύματος diff --git a/vector/src/main/res/values-eo/strings.xml b/vector/src/main/res/values-eo/strings.xml index 36d1d6a449..184d5c396b 100644 --- a/vector/src/main/res/values-eo/strings.xml +++ b/vector/src/main/res/values-eo/strings.xml @@ -14,7 +14,7 @@ %1$s nuligis inviton por %2$s %1$s ŝanĝis sian profilbildon ** Ne eblas malĉifri: %s ** - La aparato de la sendinto ne sendis al ni la ŝlosilojn por tiu mesaĝo. + La aparato de la sendinto ne sendis al ni la ŝlosilojn por tiu mesaĝo. %1$s: %2$s %1$s ŝanĝis sian prezentan nomon al %2$s %1$s ŝanĝis sian prezentan nomon de %2$s al %3$s @@ -2139,7 +2139,7 @@ Por daŭrigi, necesas via %1$s aŭ via %2$s. Subtenata nur en ĉifritaj ĉambroj Uzu la plej freŝan version de ${app_name} per aliaj viaj aparatoj: - aŭ alian klienton de Matrix kapablan je delegaj subskriboj + aŭ alian klienton de Matrix kapablan je delegaj subskriboj ${app_name} iOS \n${app_name} Android ${app_name} Web (por TTT) @@ -2684,7 +2684,7 @@ Oni povos esplori en %s Inviti al %s Havigi ligilon - Inviti per uzantonomo + Inviti per uzantonomo Inviti per retpoŝto Nun ĉeestas nur vi. %s eĉ pli bonos kun aliuloj. Inviti al %s @@ -2867,7 +2867,7 @@ Foriri de ĉiuj ĉambroj kaj aroj Ĉu vi certe volas foriri de %s\? Trovado (%s) - Inviti laŭ uzantonomo aŭ retpoŝtadreso + Inviti laŭ uzantonomo aŭ retpoŝtadreso Aldoni al la aro Aktiva voko (%1$s) · diff --git a/vector/src/main/res/values-es-rMX/strings.xml b/vector/src/main/res/values-es-rMX/strings.xml index b96e90fbe2..40dee50d85 100644 --- a/vector/src/main/res/values-es-rMX/strings.xml +++ b/vector/src/main/res/values-es-rMX/strings.xml @@ -43,7 +43,7 @@ %1$s aceptó la invitación de %2$s ** No se puede descifrar: %s ** - El dispositivo del remitente no nos ha enviado las claves de este mensaje. + El dispositivo del remitente no nos ha enviado las claves de este mensaje. No se pudo redactar diff --git a/vector/src/main/res/values-es/strings.xml b/vector/src/main/res/values-es/strings.xml index 099ab2f2ca..bc600d5e56 100644 --- a/vector/src/main/res/values-es/strings.xml +++ b/vector/src/main/res/values-es/strings.xml @@ -39,7 +39,7 @@ %1$s invitó a %2$s a unirse a la sala %1$s aceptó la invitación para %2$s ** No es posible descifrar: %s ** - El dispositivo emisor no nos ha enviado las claves para este mensaje. + El dispositivo emisor no nos ha enviado las claves para este mensaje. No se pudo redactar No es posible enviar el mensaje @@ -2333,7 +2333,7 @@ Por favor permite el acceso en la próxima ventana emergente para descubrir usua \n${app_name} de escritorio ${app_name} iOS \n${app_name} Android - u otro cliente Matrix con capacidad de firma cruzada + u otro cliente Matrix con capacidad de firma cruzada Utilice la última versión de ${app_name} en sus otros dispositivos: Obliga a descartar la sesión de grupo saliente actual en una sala cifrada Solo se admite en salas cifradas @@ -2616,7 +2616,7 @@ Por favor permite el acceso en la próxima ventana emergente para descubrir usua Sólo a esta sala Invitar a %s Compartir enlace - Invitar por nombre de usuario + Invitar por nombre de usuario Invitar por correo electrónico Invitar a %s Invitar gente a tu espacio @@ -2920,7 +2920,7 @@ Por favor permite el acceso en la próxima ventana emergente para descubrir usua Invitar por correo electrónico, buscar contactos y más… Terminé de configurar el descubrimiento. Actualmente no está utilizando un servidor de identidad. Para invitar a compañeros de equipo y ser detectado por ellos, configure uno a continuación. - Invitar por nombre de usuario o correo + Invitar por nombre de usuario o correo Asegúrate que las personas adecuadas tengan acceso a %s. Puede invitar a más persona más tarde. ¿Quiénes son tus compañeros de equipo\? Agregar al espacio dado diff --git a/vector/src/main/res/values-et/strings.xml b/vector/src/main/res/values-et/strings.xml index 50cb6bf018..5cf67f79f3 100644 --- a/vector/src/main/res/values-et/strings.xml +++ b/vector/src/main/res/values-et/strings.xml @@ -44,7 +44,7 @@ %1$s võttis tagasi jututoaga liitumise kutse kasutajalt %2$s %1$s võttis vastu kutse %2$s nimel ** Ei õnnestu dekrüptida: %s ** - Sõnumi saatja seade ei ole selle sõnumi jaoks saatnud dekrüptimisvõtmeid. + Sõnumi saatja seade ei ole selle sõnumi jaoks saatnud dekrüptimisvõtmeid. Ei saanud muuta sõnumit Sõnumi saatmine ei õnnestunud Pildi üleslaadimine ei õnnestunud @@ -2327,7 +2327,7 @@ \n${app_name} Desktop ${app_name} iOS \n${app_name} Android - või mõnda teist Matrix\'i klienti, mis oskab risttunnustamist + või mõnda teist Matrix\'i klienti, mis oskab risttunnustamist Kasuta oma muus seadmes kõige uuemat ${app_name}\'i versiooni: Sunnib loobuma praeguse krüptitud jututoa rühmavestluse seansist Funktsionaalsus on toetatud ainult krüptitud jututubades @@ -2690,7 +2690,7 @@ Nad saavad tutvuda %s kogukonnakeskusega Kutse kasutajale %s Jaga linki - Kutsu kasutajanime alusel + Kutsu kasutajanime alusel Saada kutse e-kirjaga Hetkel oled siin vaid sina. Aga %s läheb aina paremaks, kui teised liituvad. Kutsu teisi kasutajaid @@ -2949,7 +2949,7 @@ Kutsu e-posti aadressi alusel, leia tuttavaid, jne… Lõpeta leitavuse seadistamine. Hetkel sa ei kasuta isikutuvastusserverit. Kaaslastele kutse saatmiseks ning end teistele leitavaks tegemiseks palun seadista ta alljärgnevalt. - Kutsu kasutajanime või e-posti aadressi alusel + Kutsu kasutajanime või e-posti aadressi alusel Kontrolli, et vajalikel inimestel oleks ligipääs %s kogukonda. Teistele võid kutse saata ka hiljem. Kes on su kaasteelised\? Lisa näidatud kogukonnakeskusesse diff --git a/vector/src/main/res/values-eu/strings.xml b/vector/src/main/res/values-eu/strings.xml index 13ad092f42..655ae5ae9b 100644 --- a/vector/src/main/res/values-eu/strings.xml +++ b/vector/src/main/res/values-eu/strings.xml @@ -43,7 +43,7 @@ %1$s erabiltzaileak %2$s gelarako gonbidapena onartu du ** Ezin izan da deszifratu: %s ** - Igorlearen gailuak ez dizkigu mezu honetarako gakoak bidali. + Igorlearen gailuak ez dizkigu mezu honetarako gakoak bidali. Ezin izan da kendu Ezin izan da mezua bidali @@ -2408,7 +2408,7 @@ Abisua: Fitxategi hau ezabatu daiteke aplikazioa desinstalatzen bada. \n${app_name} Desktop ${app_name} iOS \n${app_name} Android - edo zeharka sinatzeko gaitasuna duen beste Matrix bezero bat + edo zeharka sinatzeko gaitasuna duen beste Matrix bezero bat Erabili azken ${app_name} bertsioa zure beste gailuetan: Uneko irteerako talde saioa zifratutako gela batean baztertzera behartzen du Zifratutako gelatan onartzen da soilik diff --git a/vector/src/main/res/values-fa/strings.xml b/vector/src/main/res/values-fa/strings.xml index 8582626fd3..c8c4e4031f 100644 --- a/vector/src/main/res/values-fa/strings.xml +++ b/vector/src/main/res/values-fa/strings.xml @@ -45,7 +45,7 @@ %1$s دعوت پیوستن به اتاق %2$s را باطل کرد %1$s دعوت برای %2$s را پذیرفت ** ناتوان در رمزگشایی: %s ** - دستگاه فرستنده، کلیدهای این پیام را برایمان نفرستاده است. + دستگاه فرستنده، کلیدهای این پیام را برایمان نفرستاده است. ناتوان در فرستادن پیام شکست در بارگذاری تصویر خطای شبکه @@ -1596,7 +1596,7 @@ برای ادامه از %1$s یا %2$sتان استفاده کنید. پشتیبانی‌شده فقط در اتاق‌های رمزشده از آخرین نگارش المنت روی دیگر افزاره‌تان استفاده کنید: - یا دیگر کاره‌های ماتریکس دادای قابلیت ورود چندگانه + یا دیگر کاره‌های ماتریکس دادای قابلیت ورود چندگانه تأیید دستی با متن تأیید ورود جدیدی که به حسابتان دسترسی دارد: %1$s تأیید همهٔ نشست‌هایتان برای اطمینان از این که حساب و پیام‌هایتان امنند @@ -2690,7 +2690,7 @@ آنها قادر به کاوش در %s خواهند بود دعوت به %s به اشتراک‌گذاری لینک - دعوت با شناسه‌کاربری + دعوت با شناسه‌کاربری دعوت با ایمیل در حال حاضر فقط شما هستید. %s با دیگران حتی بهتر خواهد بود. دعوت افراد @@ -2950,7 +2950,7 @@ پایان برپا سازی کشف. همگروهی‌هایتان کیند؟ در حال حاضر از کارساز هویتی استفاده نمی‌کنید. برای دعوت همگروهی‌ها و قابل کشف بودن به دستشان، در پایین یکی پیکربندی کنید. - دعوت با نام کاربری یا نامه + دعوت با نام کاربری یا نامه مطمئن شوید افراد درستی به %s دسترسی دارند. بعداً می‌توانید تفراد بیش‌تری را دعوت‌کنید. افزودن به فضای داده شده ایجاد کردن فضا… diff --git a/vector/src/main/res/values-fi/strings.xml b/vector/src/main/res/values-fi/strings.xml index d177d36bac..07bed56193 100644 --- a/vector/src/main/res/values-fi/strings.xml +++ b/vector/src/main/res/values-fi/strings.xml @@ -38,7 +38,7 @@ %1$s lähetti liittymiskutsun huoneeseen käyttäjälle %2$s %1$s hyväksyi kutsun käyttäjän %2$s puolesta ** Salauksen purku epäonnistui: %s ** - Lähettäjän laite ei ole lähettänyt avaimia tähän viestiin. + Lähettäjän laite ei ole lähettänyt avaimia tähän viestiin. Viestin lähetys epäonnistui Kuvan lataaminen epäonnistui Verkkovirhe @@ -2318,8 +2318,8 @@ He voivat selata avaruutta %s Kutsu avaruuteen %s Jaa linkki - Kutsu käyttäjänimellä tai sähköpostilla - Kutsu käyttäjänimellä + Kutsu käyttäjänimellä tai sähköpostilla + Kutsu käyttäjänimellä Kutsu sähköpostitse Kutsu avaruuteen %s Kutsu ihmisiä diff --git a/vector/src/main/res/values-fr-rCA/strings.xml b/vector/src/main/res/values-fr-rCA/strings.xml index 8380765a4f..4b6f25fe57 100644 --- a/vector/src/main/res/values-fr-rCA/strings.xml +++ b/vector/src/main/res/values-fr-rCA/strings.xml @@ -808,7 +808,7 @@ Seulement pris en charge dans les salons chiffrés Force la session de groupe sortante actuelle dans un salon chiffré à être abandonnée Utilisez la dernière version de ${app_name} sur vos autres appareils : - ou un autre client Matrix qui prend en charge la signature croisée + ou un autre client Matrix qui prend en charge la signature croisée ${app_name} iOS \n${app_name} Android ${app_name} Web @@ -2365,7 +2365,7 @@ Erreur de réseau Envoi du message impossible Effacement impossible - L’appareil de l’expéditeur ne nous a pas envoyé les clés pour ce message. + L’appareil de l’expéditeur ne nous a pas envoyé les clés pour ce message. ** Déchiffrement impossible : %s ** %1$s de %2$s à %3$s %1$s a modifié le rang de %2$s. @@ -2758,7 +2758,7 @@ Permettra de parcourir les salons de %s Inviter dans %s Partager le lien - Inviter par nom d’utilisateur + Inviter par nom d’utilisateur Inviter par courriel Vous êtes seul·e pour l’instant. %s sera plus agréable avec de la compagnie. Inviter à %s diff --git a/vector/src/main/res/values-fr/strings.xml b/vector/src/main/res/values-fr/strings.xml index 54e90b8957..5bc09859f9 100644 --- a/vector/src/main/res/values-fr/strings.xml +++ b/vector/src/main/res/values-fr/strings.xml @@ -39,7 +39,7 @@ %1$s a envoyé une invitation à %2$s pour rejoindre le salon %1$s a accepté l’invitation de %2$s ** Déchiffrement impossible : %s ** - L’appareil de l’expéditeur ne nous a pas envoyé les clés pour ce message. + L’appareil de l’expéditeur ne nous a pas envoyé les clés pour ce message. Effacement impossible Envoi du message impossible L’envoi de l’image a échoué @@ -2064,7 +2064,7 @@ \n${app_name} pour Bureau ${app_name} iOS \n${app_name} Android - ou un autre client Matrix qui prend en charge la signature croisée + ou un autre client Matrix qui prend en charge la signature croisée Utilisez la dernière version de ${app_name} sur vos autres appareils : Force la session de groupe sortante actuelle dans un salon chiffré à être abandonnée Seulement pris en charge dans les salons chiffrés @@ -2719,7 +2719,7 @@ Permettra de parcourir les salons de %s Inviter dans %s Partager le lien - Inviter par nom d’utilisateur + Inviter par nom d’utilisateur Inviter par e-mail Vous êtes seul pour l’instant. %s sera plus agréable avec de la compagnie. Invitez des personnes dans votre espace @@ -2958,7 +2958,7 @@ Terminer le réglage de la découverte. Vous n’utilisez actuellement pas de serveur d’identité. Pour inviter des proches et qu’ils puissent vous trouver, configurez-en un ci-dessous. Qui sont vos proches \? - Inviter par nom d’utilisateur ou courriel + Inviter par nom d’utilisateur ou courriel Assurez-vous que l’accès à la société %s est accordé aux bonnes personnes. Vous pourrez en inviter d’autres plus tard. Ajouter à l’espace mentionné Création de l’espace… diff --git a/vector/src/main/res/values-fy/strings.xml b/vector/src/main/res/values-fy/strings.xml index e2817c87d1..bf8208dd12 100644 --- a/vector/src/main/res/values-fy/strings.xml +++ b/vector/src/main/res/values-fy/strings.xml @@ -1163,7 +1163,7 @@ %1$s hat de útnûging wegere. Reden: %2$s Inisjele syngronisaasje: \nKryptografy ymportearje - It apparaat fan de ôfstjoerder hat gjin kaaien foar dit berjocht stjoerd. + It apparaat fan de ôfstjoerder hat gjin kaaien foar dit berjocht stjoerd. %1$s fan %2$s nei %3$s %1$s hat it machtigingsnivo fan %2$s oanpast. Jo hawwe it machtigingsnivo fan %1$s oanpast. diff --git a/vector/src/main/res/values-gl/strings.xml b/vector/src/main/res/values-gl/strings.xml index 4af9956161..fdf9c0de2c 100644 --- a/vector/src/main/res/values-gl/strings.xml +++ b/vector/src/main/res/values-gl/strings.xml @@ -42,7 +42,7 @@ %1$s envioulle un convite a %2$s para que entre na sala %1$s aceptou o convite para %2$s ** Imposíbel descifrar: %s ** - O dispositivo do que envía non enviou as chaves desta mensaxe. + O dispositivo do que envía non enviou as chaves desta mensaxe. Non se puido redactar Non foi posíbel enviar a mensaxe Erro da conexión diff --git a/vector/src/main/res/values-hu/strings.xml b/vector/src/main/res/values-hu/strings.xml index abd24364a2..b33ffd5aae 100644 --- a/vector/src/main/res/values-hu/strings.xml +++ b/vector/src/main/res/values-hu/strings.xml @@ -39,7 +39,7 @@ %1$s meghívót küldött %2$s számára, hogy csatlakozzon a szobához %1$s elfogadta a meghívót ebbe: %2$s ** Visszafejtés sikertelen: %s ** - A küldő eszköze nem küldte el a kulcsokat ehhez az üzenethez. + A küldő eszköze nem küldte el a kulcsokat ehhez az üzenethez. Kitakarás sikertelen Üzenet küldése sikertelen Kép feltöltése sikertelen @@ -2020,7 +2020,7 @@ Ha nem te állítottad be a visszaállítási metódust, akkor egy támadó pró \n${app_name} Desktop ${app_name} iOS \n${app_name} Android - "vagy más eszközök közötti hitelesítést támogató Matrix-klienst" + "vagy más eszközök közötti hitelesítést támogató Matrix-klienst" Az ${app_name} legújabb kliensét használd a többi eszközödön: A jelenlegi csoport munkamenet törlését kikényszeríti a titkosított szobában Csak a titkosított szobákban támogatott @@ -2502,7 +2502,7 @@ Ha nem te állítottad be a visszaállítási metódust, akkor egy támadó pró Felfedezhetik ezt: %s Meghívó ide: %s Megosztás hivatkozás - Meghívás felhasználónévvel + Meghívás felhasználónévvel Meghívás e-maillel Egyelőre csak te vagy itt, %s még jobb lehet másokkal együtt. Meghívó ide: %s @@ -2954,7 +2954,7 @@ Ha nem te állítottad be a visszaállítási metódust, akkor egy támadó pró E-mail alapú meghívás, kapcsolatok megtalálása és sok más… Felderíthetőségi beállítás befejezése. Jelenleg nem használsz azonosítási szolgáltatást. Ahhoz, hogy a csoporttársaidat meghívd vagy megtaláljanak állíts be egyet alább. - Meghívás felhasználói névvel vagy e-mail címmel + Meghívás felhasználói névvel vagy e-mail címmel Ellenőrizd, hogy a megfelelő személyeknek van hozzáférése ehhez a céghez: %s. Később meghívhatsz másokat is. Kik a csoporttársaid\? A megadott térhez adás diff --git a/vector/src/main/res/values-in/strings.xml b/vector/src/main/res/values-in/strings.xml index 909c7e83e8..db7a0fce71 100644 --- a/vector/src/main/res/values-in/strings.xml +++ b/vector/src/main/res/values-in/strings.xml @@ -1273,7 +1273,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Tidak dapat mengunggah gambar Tidak dapat dihapus Tidak dapat mengirim pesan - Perangkat pengirim belum mengirimi kami kunci untuk pesan ini. + Perangkat pengirim belum mengirimi kami kunci untuk pesan ini. ** Tidak dapat mendekripsi: %s ** %1$s dari %2$s ke %3$s Anda mengubah tingkat daya %1$s. @@ -2421,8 +2421,8 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Mereka akan dapat menjelajahi %s Undang ke %s Bagikan tautan - Undang dari nama pengguna atau email - Undang dari nama pengguna + Undang dari nama pengguna atau email + Undang dari nama pengguna Undang dari email Hanya Anda saja saat ini. %s akan lebih baik dengan orang lain. Undang ke %s @@ -2683,7 +2683,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Memaksa sesi kelompok outbound saat ini di ruang terenkripsi untuk dihapus Gunakan ${app_name} di perangkat Anda yang lain: Gunakan ${app_name} di perangkat Anda yang lain, ${app_name} Web, ${app_name} Desktop, ${app_name} iOS, ${app_name} untuk Android, atau client Matrix lainnya yang mendukung tanda tangan silang - atau client Matrix lainnya yang mendukung tanda tangan silang + atau client Matrix lainnya yang mendukung tanda tangan silang ${app_name} iOS \n${app_name} Android ${app_name} Web diff --git a/vector/src/main/res/values-is/strings.xml b/vector/src/main/res/values-is/strings.xml index 6512cc3b7d..23c4f557c6 100644 --- a/vector/src/main/res/values-is/strings.xml +++ b/vector/src/main/res/values-is/strings.xml @@ -46,7 +46,7 @@ %1$s uppfærði notandasniðið sitt %2$s %1$s sendi boð til %2$s um þátttöku í spjallrásinni %1$s samþykkti boð um að taka þátt í %2$s - Tæki sendandans hefur ekki sent okkur dulritunarlyklana fyrir þessi skilaboð. + Tæki sendandans hefur ekki sent okkur dulritunarlyklana fyrir þessi skilaboð. Gat ekki ritstýrt Ekki er í augnablikinu hægt að taka aftur þátt í spjallrás sem er tóm. Boð á spjallrás diff --git a/vector/src/main/res/values-it/strings.xml b/vector/src/main/res/values-it/strings.xml index 7df19547ed..4391fc7a1f 100644 --- a/vector/src/main/res/values-it/strings.xml +++ b/vector/src/main/res/values-it/strings.xml @@ -39,7 +39,7 @@ %1$s ha mandato un invito a %2$s per entrare alla stanza %1$s ha accettato l\'invito a %2$s ** Impossibile decriptare: %s ** - Il dispositivo del mittente non ha inviato le chiavi per questo messaggio. + Il dispositivo del mittente non ha inviato le chiavi per questo messaggio. Impossibile riscrivere Impossibile inviare il messaggio Invio dell\'immagine fallito @@ -2142,7 +2142,7 @@ \n${app_name} Desktop ${app_name} iOS \n${app_name} Android - o un altro client Matrix che supporti la firma incrociata + o un altro client Matrix che supporti la firma incrociata Usa l\'ultima versione di ${app_name} anche sui tuoi altri dispositivi: Forza l\'attuale sessione di gruppo in uscita in una stanza cifrata ad essere scartata Supportato solo nelle stanze criptate @@ -2743,7 +2743,7 @@ Potranno esplorare %s Invita in %s Condividi link - Invita per nome utente + Invita per nome utente Invita per email Al momento ci sei solo tu. Con più persone %s sarà ancora migliore. Invita persone @@ -3003,7 +3003,7 @@ Termina configurazione Invita per email, trova contatti e altro… Attualmente non stai usando un server d\'identità. Per poter invitare compagni ed essere individuabile da loro, configurane uno sotto. - Invita per nome utente o email + Invita per nome utente o email Assicurati che le persone giuste abbiano accesso a %s. Puoi invitarne altre dopo. Chi sono i tuoi compagni di squadra\? Aggiungi allo spazio scelto diff --git a/vector/src/main/res/values-iw/strings.xml b/vector/src/main/res/values-iw/strings.xml index e358463098..77d3379ccb 100644 --- a/vector/src/main/res/values-iw/strings.xml +++ b/vector/src/main/res/values-iw/strings.xml @@ -2123,7 +2123,7 @@ נתמך רק בחדרים מוצפנים מכריח את ההפעלה הקבוצתית החוצה הנוכחית בחדר מוצפן להיזרק השתמש באלמנט האחרון במכשירים האחרים שלך: - או לקוח מטריקס אחר עם יכולת חתימה צולבת + או לקוח מטריקס אחר עם יכולת חתימה צולבת אלמנט iOS \nאלמנט אנדרואיד רשת האינטרנט diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml index 0bbcfe04df..e5c1cae150 100644 --- a/vector/src/main/res/values-ja/strings.xml +++ b/vector/src/main/res/values-ja/strings.xml @@ -47,7 +47,7 @@ %1$s は %2$s にルームに参加するよう招待状を送りました %1$sは%2$sの招待を受け入れました ** 解読できません: %s ** - 送信者の端末からこのメッセージのキーが送信されていません。 + 送信者の端末からこのメッセージのキーが送信されていません。 修正できませんでした メッセージを送信できません 画像のアップロードに失敗しました @@ -1811,7 +1811,7 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場 次に 次に 次に - ユーザー名で招待 + ユーザー名で招待 ユーザー名を選択してください。 ユーザー名やパスワードが正しくありません。 入力したパスワードは、スペースで開始または終了しますので、ご確認ください。 そのユーザー名は既に使用されています diff --git a/vector/src/main/res/values-kab/strings.xml b/vector/src/main/res/values-kab/strings.xml index d5c2ac0c53..a2423fe993 100644 --- a/vector/src/main/res/values-kab/strings.xml +++ b/vector/src/main/res/values-kab/strings.xml @@ -97,7 +97,7 @@ Tbeddleḍ aswir n tezmert n %1$s. %1$s ibeddel aswir n tezmert n %2$s. ** Awgelhen d awezɣi: %s ** - Ibenk n umazan ur aɣ-d-yuzin ara tisura i yizen-a. + Ibenk n umazan ur aɣ-d-yuzin ara tisura i yizen-a. Tuzna n yizen d tawezɣit Tuccḍa deg uẓeṭṭa Tuccḍa deg Matrix @@ -939,7 +939,7 @@ Anedbal-ik·im n uqeddac issens awgelhen seg yixef ɣer yixef s wudem amezwer deg texxamin tusligin & yiznan usriden. Tiɣimiyin S tidet tebɣiḍ ad tekkseḍ aneḍru-a\? Err deg uqerru-k·m ma yella tekkseḍ asnifel n yisem neɣ asentel n texxamt, yemzer asnifel-a ad yettusefsex. - neɣ amsaɣ-nniḍen n Matrix yemṣadan d uzmul amdigan + neɣ amsaɣ-nniḍen n Matrix yemṣadan d uzmul amdigan Ḥettem tiɣimit n ugraw ara d-yeffɣen akka tura deg texxamt tawgelhant ad tettwakkes Sentem timagit-ik·im s usenqed n yinekcam-a seg yiwet gar tɣimiyin-inek·inem-nniḍen, serreḥ-as ad tekcem ɣer yiznan yettwawgelhen. D awezɣi ad ternuḍ izen-inek·inem uslig. Ttxil-k·m senqed iseqdacen i tebɣiḍ ad d-tnecdeḍ syen ɛreḍ tikkelt-nniḍen. diff --git a/vector/src/main/res/values-ko/strings.xml b/vector/src/main/res/values-ko/strings.xml index 4a8e207b63..895799d51f 100644 --- a/vector/src/main/res/values-ko/strings.xml +++ b/vector/src/main/res/values-ko/strings.xml @@ -45,7 +45,7 @@ %1$s님이 %2$s님에게 방 초대를 보냈습니다 %1$s님이 %2$s의 초대를 수락했습니다 ** 암호를 복호화할 수 없음: %s ** - 발신인의 기기에서 이 메시지의 키를 보내지 않았습니다. + 발신인의 기기에서 이 메시지의 키를 보내지 않았습니다. 검열할 수 없습니다 메시지를 보낼 수 없습니다 사진 업로드에 실패했습니다 diff --git a/vector/src/main/res/values-lv/strings.xml b/vector/src/main/res/values-lv/strings.xml index 3166600dec..3cb036e448 100644 --- a/vector/src/main/res/values-lv/strings.xml +++ b/vector/src/main/res/values-lv/strings.xml @@ -39,7 +39,7 @@ %1$s nosūtīja %2$s uzaicinājumu pievienoties istabai %1$s pieņēma uzaicinājumu %2$s ** Neizdodas atšifrēt: %s ** - Sūtītāja ierīce mums nav nenosūtījusi atslēgas priekš šīs ziņas. + Sūtītāja ierīce mums nav nenosūtījusi atslēgas priekš šīs ziņas. Nevarēja rediģēt Neizdodas nosūtīt ziņu Neizdevās augšuplādēt attēlu @@ -960,7 +960,7 @@ Nākotnē šī pārbaudes procedūra plānota sarežģītāka. Verificējiet visas savas sesijas, lai nodrošinātos, ka jūsu konts un ziņas ir drošībā Pārskatiet savas pierakstīšanās Nešifrēts - vai kādu citu Matrix lietotni ar cross-signing atbalstu + vai kādu citu Matrix lietotni ar cross-signing atbalstu Šis konts ir deaktivizēts. Šifrētas ziņas grupas čatos Šifrētas ziņas viens-pret-vienu čatos @@ -2349,8 +2349,8 @@ Nākotnē šī pārbaudes procedūra plānota sarežģītāka. Viņi varēs izpētīt %s Uzaicināt uz %s Kopīgot saiti - Ielūgt pēc lietotājvārda vai pa e-pastu - Ielūgt pēc lietotājvārda + Ielūgt pēc lietotājvārda vai pa e-pastu + Ielūgt pēc lietotājvārda Ielūgt pa e-pastu Šobrīd esi tikai tu. %s būs vēl labāk kopā ar citiem. Uzaicināt uz %s diff --git a/vector/src/main/res/values-ml/strings.xml b/vector/src/main/res/values-ml/strings.xml index 13381aa165..287548e0a3 100644 --- a/vector/src/main/res/values-ml/strings.xml +++ b/vector/src/main/res/values-ml/strings.xml @@ -607,7 +607,7 @@ ചിത്രം അപ്‌ലോഡുചെയ്യുന്നതിൽ പരാജയപ്പെട്ടു സന്ദേശം അയയ്‌ക്കാനായില്ല പുനഃക്രമീകരിക്കാൻ കഴിഞ്ഞില്ല - അയച്ചയാളുടെ ഉപകരണം ഈ സന്ദേശത്തിനുള്ള കീകൾ ഞങ്ങൾക്ക് അയച്ചിട്ടില്ല. + അയച്ചയാളുടെ ഉപകരണം ഈ സന്ദേശത്തിനുള്ള കീകൾ ഞങ്ങൾക്ക് അയച്ചിട്ടില്ല. ** ഡീക്രിപ്റ്റ് ചെയ്യാൻ കഴിഞ്ഞില്ല: %s ** %1$s %2$s ന്റെ അധികാര നില മാറ്റി. നിങ്ങൾ %1$s ന്റെ അധികാര നില മാറ്റി. diff --git a/vector/src/main/res/values-nb-rNO/strings.xml b/vector/src/main/res/values-nb-rNO/strings.xml index a8d0b5e22d..839f1cf518 100644 --- a/vector/src/main/res/values-nb-rNO/strings.xml +++ b/vector/src/main/res/values-nb-rNO/strings.xml @@ -1629,7 +1629,7 @@ Nettverksfeil Kunne ikke laste opp bildet Kan ikke sende melding - Avsenderens enhet har ikke sendt oss nøklene til denne meldingen. + Avsenderens enhet har ikke sendt oss nøklene til denne meldingen. ** Kan ikke dekryptere: %s ** Tilpasset Tilpasset (%1$d) diff --git a/vector/src/main/res/values-nl/strings.xml b/vector/src/main/res/values-nl/strings.xml index 8a986c49d0..3fffdced70 100644 --- a/vector/src/main/res/values-nl/strings.xml +++ b/vector/src/main/res/values-nl/strings.xml @@ -39,7 +39,7 @@ %1$s heeft een uitnodiging naar %2$s gestuurd om het gesprek toe te treden %1$s heeft de uitnodiging voor %2$s aanvaard ** Kan niet ontsleutelen: %s ** - Het apparaat van de afzender heeft geen sleutels voor dit bericht gestuurd. + Het apparaat van de afzender heeft geen sleutels voor dit bericht gestuurd. Kon niet verwijderd worden Kan bericht niet verzenden diff --git a/vector/src/main/res/values-nn/strings.xml b/vector/src/main/res/values-nn/strings.xml index 3488546447..f86d4e5938 100644 --- a/vector/src/main/res/values-nn/strings.xml +++ b/vector/src/main/res/values-nn/strings.xml @@ -40,7 +40,7 @@ %1$s inviterte %2$s til rommet %1$s sa ja til innbjodingi til %2$s ** Fekk ikkje til å dekryptera: %s ** - Avsendareiningi hev ikkje sendt oss nyklane fyr denna meldingi. + Avsendareiningi hev ikkje sendt oss nyklane fyr denna meldingi. Kunde ikkje gjera um Fekk ikkje til å senda meldingi Fekk ikkje til å lasta biletet upp diff --git a/vector/src/main/res/values-pl/strings.xml b/vector/src/main/res/values-pl/strings.xml index f9adef1f7e..0bb609cd2f 100644 --- a/vector/src/main/res/values-pl/strings.xml +++ b/vector/src/main/res/values-pl/strings.xml @@ -56,7 +56,7 @@ %1$s zaktualizował swój profil %2$s %1$s wysłał(a) zaproszenie do %2$s aby dołączył(a) do tego pokoju %1$s zaakceptował(a) zaproszenie dla %2$s - Urządzenie nadawcy nie wysłało nam kluczy do tej wiadomości. + Urządzenie nadawcy nie wysłało nam kluczy do tej wiadomości. Nie można zredagować Obecnie nie jest możliwe ponowne dołączenie do pustego pokoju. Wiadomość usunięta @@ -2718,8 +2718,8 @@ Będą w stanie przeglądać %s Zaproś do %s Udostępnij link - Zaproś przez nazwę użytkownika lub email - Zaproś przez nazwę użytkownika + Zaproś przez nazwę użytkownika lub email + Zaproś przez nazwę użytkownika Zaproś przez email Aktualnie jesteś tylko Ty. %s będzie jeszcze lepsza kiedy dołączą inni. Zaproś do %s @@ -2859,7 +2859,7 @@ Opinie Synchronizacja klucza samopodpisującego (Self Signing key) Weryfikacja ręczna poprzez tekst - lub innego klienta Matrix z krzyżową weryfikacją nowych sesji logowania + lub innego klienta Matrix z krzyżową weryfikacją nowych sesji logowania Nie masz uprawnień do zmiany poziomu pokoju Oczekiwanie na historię szyfrowania Ten pokój pracuje na wersji pokoju %s, którą serwer domowy oznaczył jako niestabilną. diff --git a/vector/src/main/res/values-pt-rBR/strings.xml b/vector/src/main/res/values-pt-rBR/strings.xml index c86cc89ed8..c057570025 100644 --- a/vector/src/main/res/values-pt-rBR/strings.xml +++ b/vector/src/main/res/values-pt-rBR/strings.xml @@ -39,7 +39,7 @@ %1$s enviou um convite para %2$s para se juntar à sala %1$s aceitou o convite para %2$s ** Incapaz de decriptar: %s ** - O dispositivo do/da enviador(a) não nos enviou as chaves para esta mensagem. + O dispositivo do/da enviador(a) não nos enviou as chaves para esta mensagem. Não foi possível redigir Não foi possível enviar mensagem @@ -2264,7 +2264,7 @@ \n${app_name} Desktop ${app_name} iOS \n${app_name} Android - ou um outro cliente Matrix capaz de assinatura cruzada + ou um outro cliente Matrix capaz de assinatura cruzada Use o ${app_name} mais recente em seus outros dispositivos: Força a atual sessão de grupo de saída em uma sala encriptada a ser descartada Somente suportado em salas encriptadas @@ -2777,7 +2777,7 @@ Elas não vão fazer parte de %s Só a esta sala Compartilhar link - Convidar por nome de usuária(o) + Convidar por nome de usuária(o) Convidar por email Convidar pessoas Boas-vindas a %1$s, %2$s. @@ -3019,7 +3019,7 @@ Convidar por email, encontrar contatos e mais… Termine de configurar descoberta. Você não está atualmente usando um servidor de identidade. A fim de convidar colegas e ser descobertável por elas(es), configure um abaixo. - Convidar por nome de usuária(o) ou mail + Convidar por nome de usuária(o) ou mail Assegure que as pessoas certas têm acesso a companhia %s. Você pode convidar outras mais tarde. Quem são suas/seus colegas\? Adicionar ao dado Espaço diff --git a/vector/src/main/res/values-pt/strings.xml b/vector/src/main/res/values-pt/strings.xml index b4aa314cba..ca25d8f9e5 100644 --- a/vector/src/main/res/values-pt/strings.xml +++ b/vector/src/main/res/values-pt/strings.xml @@ -39,7 +39,7 @@ %1$s enviou um convite para que %2$s se junte à sala %1$s aceitou o convite para %2$s ** Impossível decifrar: %s ** - O dispositivo de quem enviou a mensagem não nos enviou as chaves para esta mensagem. + O dispositivo de quem enviou a mensagem não nos enviou as chaves para esta mensagem. Não foi possível apagar Não foi possível enviar a mensagem diff --git a/vector/src/main/res/values-ro/strings.xml b/vector/src/main/res/values-ro/strings.xml index 464a590f62..0e92e74907 100644 --- a/vector/src/main/res/values-ro/strings.xml +++ b/vector/src/main/res/values-ro/strings.xml @@ -389,7 +389,7 @@ A apărut o eroare în timpul incărcării imaginii Nu s-a putut trimite mesajul Nu se poate redacta - Dispozitivul expeditorului nu a trimis cheile de decriptare pentru acest mesaj. + Dispozitivul expeditorului nu a trimis cheile de decriptare pentru acest mesaj. Acum sunt blocate serverele ce au adrese IP ce se potrivesc. Acum sunt permise serverele ce au adrese IP ce se potrivesc. Sunt blocate serverele ce au adrese IP ce se potrivesc. diff --git a/vector/src/main/res/values-ru/strings.xml b/vector/src/main/res/values-ru/strings.xml index 0979c60526..fa8238c4ec 100644 --- a/vector/src/main/res/values-ru/strings.xml +++ b/vector/src/main/res/values-ru/strings.xml @@ -39,7 +39,7 @@ %1$s отправил(а) приглашение %2$s присоединиться к комнате %1$s принял(а) приглашение от %2$s ** Невозможно расшифровать: %s ** - Устройство отправителя не предоставило нам ключ для расшифровки этого сообщения. + Устройство отправителя не предоставило нам ключ для расшифровки этого сообщения. Не удалось изменить Не удалось отправить сообщение @@ -2335,7 +2335,7 @@ \n${app_name} для ПК ${app_name} для iOS \n${app_name} для Android - или другой, поддерживаемый кросс-подпись Matrix клиент + или другой, поддерживаемый кросс-подпись Matrix клиент Принудительно отбрасывает текущую групповую сессию для отправки сообщений в зашифрованную комнату Чтобы продолжить, используйте ваш %1$s или используйте ваш %2$s. Используйте ключ восстановления @@ -2878,7 +2878,7 @@ Они смогут изучить %s Пригласить в %s Поделиться ссылкой - Пригласить по имени пользователя + Пригласить по имени пользователя Пригласить по электронной почте На данный момент здесь только вы. С другими в %s будет ещё лучше. Пригласить в %s @@ -3104,7 +3104,7 @@ Приглашение по электронной почте, поиск контактов и многое другое… Завершите настройку обнаружения. В настоящее время вы не используете сервер идентификации. Чтобы приглашать членов команды и быть доступным для них, настройте один из них ниже. - Приглашение по имени пользователя или по почте + Приглашение по имени пользователя или по почте Убедитесь, что нужные люди имеют доступ к %s. Вы можете пригласить больше людей позже. Кто ваши члены команды\? Добавить в данное пространство diff --git a/vector/src/main/res/values-sk/strings.xml b/vector/src/main/res/values-sk/strings.xml index a35b155f80..e4cb519544 100644 --- a/vector/src/main/res/values-sk/strings.xml +++ b/vector/src/main/res/values-sk/strings.xml @@ -39,7 +39,7 @@ %1$s pozval/a %2$s vstúpiť do miestnosti %1$s prijal/a pozvanie pre %2$s ** Nie je možné dešifrovať: %s ** - Zo zariadenia odosieľateľa nebolo možné získať kľúče potrebné na dešifrovanie tejto správy. + Zo zariadenia odosieľateľa nebolo možné získať kľúče potrebné na dešifrovanie tejto správy. Nie je možné vymazať Nie je možné odoslať správu Nepodarilo sa nahrať obrázok diff --git a/vector/src/main/res/values-sq/strings.xml b/vector/src/main/res/values-sq/strings.xml index 1787bb2a65..c1c1efcdd7 100644 --- a/vector/src/main/res/values-sq/strings.xml +++ b/vector/src/main/res/values-sq/strings.xml @@ -30,7 +30,7 @@ %1$s përditësoi profilin e tij %2$s %1$s pranoi ftesën tuaj për %2$s ** S’arrihet të shfshehtëzohet: %s ** - Pajisja e dërguesit nuk na ka dërguar kyçet për këtë mesazh. + Pajisja e dërguesit nuk na ka dërguar kyçet për këtë mesazh. S’u redaktua dot S’arrihet të dërgohet mesazh Ngarkimi i figurës dështoi @@ -2077,7 +2077,7 @@ \n${app_name} Desktop ${app_name} iOS \n${app_name} Android - ose një tjetër klient Matrix i aftë për <em>cross-signing</em + ose një tjetër klient Matrix i aftë për <em>cross-signing</em Përdorni ${app_name}-in më të ri në pajisjet tuaja të tjera: Mbulohet vetëm për dhoma të fshehtëzuara Përdorni %1$s tuaj ose përdorni %2$s tuaj që të vazhdohet. @@ -2679,7 +2679,7 @@ Do të jenë në gjendje të eksplorojnë %s Ftojeni në %s Jepuni lidhjen - Ftoni përmes emri përdoruesi + Ftoni përmes emri përdoruesi Ftoni përmes email-i Vetëm ju, hëpërhë. %s do të jetë edhe më mirë me të tjerë. Ftoni njerëz @@ -2938,7 +2938,7 @@ Ftoni me email, gjeni kontakte, etj… Përfundo ujdisjen e zbulimit. Aktualisht nuk përdorni ndonjë shërbyes identitetesh. Që të ftoni shokë të ekipit dhe të jeni i zbulueshëm prej tyre, formësoni një të tillë më poshtë. - Ftoni me emër përdoruesi ose email + Ftoni me emër përdoruesi ose email Sigurohuni se te %s e shoqërisë kanë hyrje personat e duhur. Më vonë mund të ftoni të tjerë. Cilët janë shokët tuaj të ekipit\? Shtoni te Hapësira e dhënë diff --git a/vector/src/main/res/values-sr/strings.xml b/vector/src/main/res/values-sr/strings.xml index 065fd3154b..31623fb9bc 100644 --- a/vector/src/main/res/values-sr/strings.xml +++ b/vector/src/main/res/values-sr/strings.xml @@ -67,7 +67,7 @@ Мрежна грешка Неуспело слање слике Не могу да пошаљем поруку - Уређај пошиљаоца није нам послао кључеве за ову поруку. + Уређај пошиљаоца није нам послао кључеве за ову поруку. Изменили сте ниво снаге корисника %1$s. посебно посебно (%1$d) diff --git a/vector/src/main/res/values-sv/strings.xml b/vector/src/main/res/values-sv/strings.xml index 429df1e535..ed0d1ede73 100644 --- a/vector/src/main/res/values-sv/strings.xml +++ b/vector/src/main/res/values-sv/strings.xml @@ -99,7 +99,7 @@ %1$s ändrade behörighetsnivå för %2$s. %1$s från %2$s till %3$s ** Kan inte avkryptera: %s ** - Avsändarens enhet har inte gett oss nycklarna för det här meddelandet. + Avsändarens enhet har inte gett oss nycklarna för det här meddelandet. Kunde inte dölja Kunde inte skicka meddelandet Misslyckades att ladda upp bilden @@ -1566,7 +1566,7 @@ Förhindra skärmdumpar av appen Att aktivera den här inställningen lägger till FLAG_SECURE till alla aktiviteter. Starta om appen för att inställningen ska få effekt. Använd senaste ${app_name} på dina andra enheter; ${app_name} Web, ${app_name} iOS, ${app_name} för Android, eller annan Matrixklient som stöder korssignering - eller en annan Matrixklient som städer korssignering + eller en annan Matrixklient som städer korssignering Använd senaste ${app_name} på dina andra enheter: Tvingar den nuvarande utgående gruppsessionen i ett krypterat rum att kasseras Säkerhetskopian kunde inte avkrypteras med den här återställningsnyckeln: vänligen verifiera att du skrev in rätt återställningsnyckel. @@ -2701,7 +2701,7 @@ Föreslagna rum Bjud in till %s Dela länk - Bjud in med användarnamn + Bjud in med användarnamn Bjud in via e-post Det är bara du här för tillfället. %s kommer att vara ännu bättre med fler. Bjud in personer till ditt utrymme @@ -2992,7 +2992,7 @@ Bjud in med e-post, hitta kontakter och mer… Slutför inställning av upptäckbarhet. Du använder för närvarande ingen identitetsserver. För att bjuda in teammedlemmar och kunna upptäckas av dem, konfigurera en nedan. - Bjud in med användarnamn eller e-postadress + Bjud in med användarnamn eller e-postadress Otillgänglig Offline Online diff --git a/vector/src/main/res/values-te/strings.xml b/vector/src/main/res/values-te/strings.xml index 14232e65eb..b0dc11775a 100644 --- a/vector/src/main/res/values-te/strings.xml +++ b/vector/src/main/res/values-te/strings.xml @@ -38,7 +38,7 @@ %2$sకోసం %1$s ఆహ్వానాన్ని అంగీకరించారు ** వ్యక్తీకరించడానికి సాధ్యం కాలేదు: %s ** - ఈ సందేశానికి పంపేవారి పరికరం మాకు కీలను పంపలేదు. + ఈ సందేశానికి పంపేవారి పరికరం మాకు కీలను పంపలేదు. గది స్క్రీన్ సందేశం పంపడం సాధ్యం కాలేదు diff --git a/vector/src/main/res/values-uk/strings.xml b/vector/src/main/res/values-uk/strings.xml index 75492dbafb..a999c6fa3f 100644 --- a/vector/src/main/res/values-uk/strings.xml +++ b/vector/src/main/res/values-uk/strings.xml @@ -45,7 +45,7 @@ %1$s надсилає запрошення %2$s приєднатися до кімнати %1$s приймає запрошення до %2$s ** Неможливо розшифрувати: %s ** - Пристрій відправника не надіслав нам ключ для цього повідомлення. + Пристрій відправника не надіслав нам ключ для цього повідомлення. Не вдається редагувати Не вдалося надіслати повідомлення Не вдалося завантажити зображення @@ -1850,7 +1850,7 @@ Новий вхід. Це були ви\? Оновити Скинути ключі - або будь-який інший, здатний до перехресного підписування, Matrix-клієнт + або будь-який інший, здатний до перехресного підписування, Matrix-клієнт Перехресне підписування не ввімкнене Перехресне підписування увімкнено. \nКлючі не є довіреними @@ -2195,7 +2195,7 @@ Створити Простір Приєднатися до Простору Поділитися посиланням - Запросити за користувацьким іменем + Запросити за користувацьким іменем Запросити електронним листом Проведіть пальцем, щоб скасувати Записати голосове повідомлення @@ -3071,7 +3071,7 @@ Нове: Дозволити людям у просторах знаходити та приєднуватися до приватних кімнат Лише в цю кімнату Вони зможуть переглядати %s - Запрошення за іменем користувача або е-поштою + Запрошення за іменем користувача або е-поштою Головне Поділитися у тексті Показувати лише кількість непрочитаних повідомлень у звичайному сповіщенні. diff --git a/vector/src/main/res/values-vi/strings.xml b/vector/src/main/res/values-vi/strings.xml index 9d1c1a3e17..96e00b3103 100644 --- a/vector/src/main/res/values-vi/strings.xml +++ b/vector/src/main/res/values-vi/strings.xml @@ -327,7 +327,7 @@ Tải hình ảnh lên thất bại Không thể gửi tin nhắn Không thể rút lại - Thiết bị của người gửi chưa gửi cho chúng tôi mã khoá cho tin nhắn này. + Thiết bị của người gửi chưa gửi cho chúng tôi mã khoá cho tin nhắn này. ** Không thể giải mã: %s ** %1$s từ %2$s thành %3$s Tuỳ chỉnh @@ -1825,8 +1825,8 @@ Tạo Space Bỏ qua ngay bây giờ Gia nhập Space của tôi %1$s %2$s - Mời theo tên người dùng hoặc thư - Mời theo tên người dùng + Mời theo tên người dùng hoặc thư + Mời theo tên người dùng Mời qua email Chỉ có anh lúc này thôi. %s sẽ còn tốt hơn với những người khác. Mời đến %s @@ -2052,7 +2052,7 @@ Chỉ được hỗ trợ trong các phòng được mã hóa Buộc nhóm phiên hướng ra hiện tại trong một căn phòng được mã hóa phải bị loại bỏ Sử dụng ${app_name mới nhất} trên các thiết bị khác của bạn: - hoặc một máy khách Matrix có khả năng xác thực chéo khác + hoặc một máy khách Matrix có khả năng xác thực chéo khác ${app_name} iOS \n${app_name} Android ${app_name} Web diff --git a/vector/src/main/res/values-vls/strings.xml b/vector/src/main/res/values-vls/strings.xml index c8190e7a68..8f8f0d9ad4 100644 --- a/vector/src/main/res/values-vls/strings.xml +++ b/vector/src/main/res/values-vls/strings.xml @@ -48,7 +48,7 @@ %1$s èt d’uutnodigienge vo %2$s anveird ** Kun nie ountsleuteln: %s ** - ’t Toestel van den afzender èt geen sleutels vo da bericht hier gesteurd. + ’t Toestel van den afzender èt geen sleutels vo da bericht hier gesteurd. Kosteg nie verwyderd wordn Kosteg ’t bericht nie verzendn diff --git a/vector/src/main/res/values-zh-rCN/strings.xml b/vector/src/main/res/values-zh-rCN/strings.xml index 95ec1a3b8d..9007b57ad1 100644 --- a/vector/src/main/res/values-zh-rCN/strings.xml +++ b/vector/src/main/res/values-zh-rCN/strings.xml @@ -33,7 +33,7 @@ %1$s 移除了聊天室名称 %1$s 移除了聊天室主题 ** 无法解密:%s ** - 发送者的设备没有向我们发送此消息的密钥。 + 发送者的设备没有向我们发送此消息的密钥。 无法发送消息 上传图像失败 网络错误 @@ -2119,7 +2119,7 @@ \n${app_name} Desktop ${app_name} iOS \n${app_name} Android - 或其他能够交叉签名的 Matrix 客户端 + 或其他能够交叉签名的 Matrix 客户端 在你的其他设备上使用最新的 ${app_name}: 强制丢弃加密聊天室中的当前出站群组会话 仅在加密聊天室中支持 @@ -2614,7 +2614,7 @@ 他们将可以探索 %s 邀请至 %s 分享链接 - 通过用户名进行邀请 + 通过用户名进行邀请 通过电子邮件进行邀请 此刻仅有你自己。%s 与他人一道会更好。 邀请至 %s @@ -2905,7 +2905,7 @@ 通过电子邮件邀请、寻找联系人和更多… 设置“发现”已毕。 您目前没有使用身份服务器。为了邀请队友并被他们发现,请在下方配置一个这样的服务器。 - 通过用户名或邮件邀请 + 通过用户名或邮件邀请 确保只有合适的人能访问 %s 空间。稍后你可以邀请更多的人加入。 谁是你的队友? 添加至规定的空间 diff --git a/vector/src/main/res/values-zh-rTW/strings.xml b/vector/src/main/res/values-zh-rTW/strings.xml index bd90327bcd..f042772f2e 100644 --- a/vector/src/main/res/values-zh-rTW/strings.xml +++ b/vector/src/main/res/values-zh-rTW/strings.xml @@ -39,7 +39,7 @@ %1$s 傳送加入房間的邀請給 %2$s %1$s 接受 %2$s 的邀請 ** 無法解密:%s ** - 傳送者的裝置並未在此訊息傳送他們的金鑰。 + 傳送者的裝置並未在此訊息傳送他們的金鑰。 無法編輯 無法傳送訊息 上傳圖片失敗 @@ -2051,7 +2051,7 @@ \n${app_name} 桌面版 ${app_name} iOS \n${app_name} Android - 或其他有交叉簽章功能的 Matrix 客戶端 + 或其他有交叉簽章功能的 Matrix 客戶端 在您的其他裝置上使用最新的 ${app_name}: 強制丟棄目前在加密聊天室中的外發群組工作階段 僅在加密聊天室中支援 @@ -2640,7 +2640,7 @@ 他們將可以探索 %s 邀請至 %s 分享連結 - 透過使用者名稱邀請 + 透過使用者名稱邀請 透過電子郵件邀請 此刻只有您。%s 與其他人一起會更好。 邀請夥伴 @@ -2895,7 +2895,7 @@ 透過電子郵件邀請、尋找聯絡人以及更多…… 完成探索設定。 您目前並未使用身份認證伺服器。為了邀請隊友並被他們探索,請在下方設定一個。 - 透過使用者名稱或電子郵件邀請 + 透過使用者名稱或電子郵件邀請 確保合適的人可以存取 %s 公司。您可以稍後再邀請。 誰是您的隊友? 新增至指定的空間 diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index ffc634abed..aebe6312c6 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -147,7 +147,7 @@ %1$s from %2$s to %3$s ** Unable to decrypt: %s ** - The sender\'s device has not sent us the keys for this message. + The sender\'s device has not sent us the keys for this message. @@ -3129,7 +3129,7 @@ ${app_name} Web\n${app_name} Desktop ${app_name} iOS\n${app_name} Android - or another cross-signing capable Matrix client + or another cross-signing capable Matrix client Use the latest ${app_name} on your other devices: Forces the current outbound group session in an encrypted room to be discarded @@ -3506,8 +3506,8 @@ Invite to %s It’s just you at the moment. %s will be even better with others. Invite by email - Invite by username - Invite by username or mail + Invite by username + Invite by username or mail Share link Invite to %s "They’ll be able to explore %s" From 108e92478331f78a55287276881cfb52917b8a87 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 4 Jan 2022 13:17:41 +0100 Subject: [PATCH 192/632] Remove unused strings --- vector/src/main/res/values-ar/strings.xml | 11 ----------- vector/src/main/res/values-bg/strings.xml | 11 ----------- vector/src/main/res/values-bn-rBD/strings.xml | 11 ----------- vector/src/main/res/values-bn-rIN/strings.xml | 12 ------------ vector/src/main/res/values-bs/strings.xml | 11 ----------- vector/src/main/res/values-ca/strings.xml | 12 ------------ vector/src/main/res/values-cs/strings.xml | 11 ----------- vector/src/main/res/values-da/strings.xml | 8 -------- vector/src/main/res/values-de/strings.xml | 12 ------------ vector/src/main/res/values-el/strings.xml | 8 -------- vector/src/main/res/values-en-rGB/strings.xml | 1 - vector/src/main/res/values-eo/strings.xml | 11 ----------- vector/src/main/res/values-es-rMX/strings.xml | 12 ------------ vector/src/main/res/values-es/strings.xml | 12 ------------ vector/src/main/res/values-et/strings.xml | 11 ----------- vector/src/main/res/values-eu/strings.xml | 12 ------------ vector/src/main/res/values-fa/strings.xml | 11 ----------- vector/src/main/res/values-fi/strings.xml | 13 ------------- vector/src/main/res/values-fr-rCA/strings.xml | 11 ----------- vector/src/main/res/values-fr/strings.xml | 11 ----------- vector/src/main/res/values-fy/strings.xml | 11 ----------- vector/src/main/res/values-gl/strings.xml | 10 ---------- vector/src/main/res/values-hr/strings.xml | 11 ----------- vector/src/main/res/values-hu/strings.xml | 11 ----------- vector/src/main/res/values-in/strings.xml | 11 ----------- vector/src/main/res/values-is/strings.xml | 11 ----------- vector/src/main/res/values-it/strings.xml | 12 ------------ vector/src/main/res/values-iw/strings.xml | 11 ----------- vector/src/main/res/values-ja/strings.xml | 11 ----------- vector/src/main/res/values-kab/strings.xml | 11 ----------- vector/src/main/res/values-ko/strings.xml | 11 ----------- vector/src/main/res/values-lv/strings.xml | 11 ----------- vector/src/main/res/values-ml/strings.xml | 8 -------- vector/src/main/res/values-nb-rNO/strings.xml | 11 ----------- vector/src/main/res/values-nl/strings.xml | 12 ------------ vector/src/main/res/values-nn/strings.xml | 11 ----------- vector/src/main/res/values-pl/strings.xml | 11 ----------- vector/src/main/res/values-pt-rBR/strings.xml | 12 ------------ vector/src/main/res/values-pt/strings.xml | 12 ------------ vector/src/main/res/values-ro/strings.xml | 2 -- vector/src/main/res/values-ru/strings.xml | 12 ------------ vector/src/main/res/values-sk/strings.xml | 11 ----------- vector/src/main/res/values-sq/strings.xml | 11 ----------- vector/src/main/res/values-sv/strings.xml | 11 ----------- vector/src/main/res/values-te/strings.xml | 11 ----------- vector/src/main/res/values-th/strings.xml | 8 -------- vector/src/main/res/values-tr/strings.xml | 11 ----------- vector/src/main/res/values-tzm/strings.xml | 1 - vector/src/main/res/values-uk/strings.xml | 12 ------------ vector/src/main/res/values-vi/strings.xml | 11 ----------- vector/src/main/res/values-zh-rCN/strings.xml | 11 ----------- vector/src/main/res/values-zh-rTW/strings.xml | 11 ----------- vector/src/main/res/values/strings.xml | 13 ------------- 53 files changed, 557 deletions(-) diff --git a/vector/src/main/res/values-ar/strings.xml b/vector/src/main/res/values-ar/strings.xml index 02bc6c370c..ee8374d549 100644 --- a/vector/src/main/res/values-ar/strings.xml +++ b/vector/src/main/res/values-ar/strings.xml @@ -428,14 +428,6 @@ الغرف الرسائل الملفات - الدليل - المفضلة - الغرف - الدعوات - أنشئ غرفة - انضم إلى غرفة - انضم إلى غرفة - اكتب معرّف غرفة أو اختصارها يبحث في الدليل.. اترك المحادثة انسَ @@ -737,7 +729,6 @@ ابحث رشّح أعضاء الغرفة الناس - أولوية منخفضة كل الرسائل (مزعج) كل الرسائل الإشارات فقط @@ -875,8 +866,6 @@ قد يعني هذا بأن أحدهم يعترض الاتصال بعدوانية، أو أن هاتفك لا يثق بالشهادة التي قدّمها الخادوم البعيد. إن قال مدير الخادوم بأن هذا متوقع، فتأكد من أن البصمة أدناه تطابق البصمة التي وفّرها. تغيّرت الشهادة من شهادة كنت تثق بها إلى شهادة لا تثق بها. لربما جدّد الخادوم شهادته. راسل إدارة الخادوم واسألهم عن البصمة المتوقعة. - انضم - ابدأ دردشة تصفح الدليل لم يُعثر على غرف تطابق ‏⁨%2$s⁩ diff --git a/vector/src/main/res/values-bg/strings.xml b/vector/src/main/res/values-bg/strings.xml index 9110cd0430..a839251b66 100644 --- a/vector/src/main/res/values-bg/strings.xml +++ b/vector/src/main/res/values-bg/strings.xml @@ -517,17 +517,6 @@ СЪОБЩЕНИЯ ХОРА ФАЙЛОВЕ - ВЛЕЗ - ДИРЕКТОРИЯ - ЛЮБИМИ - СТАИ - НИСЪК ПРИОРИТЕТ - ПОКАНИ - Започни чат - Създай стая - Присъедини се към стая - Присъединяване към стая - Напишете ID или псевдоним на стая Прегледай директорията %d стая diff --git a/vector/src/main/res/values-bn-rBD/strings.xml b/vector/src/main/res/values-bn-rBD/strings.xml index 63d8b506bb..410a31e599 100644 --- a/vector/src/main/res/values-bn-rBD/strings.xml +++ b/vector/src/main/res/values-bn-rBD/strings.xml @@ -395,17 +395,6 @@ %d ঘরগুলি ব্রাউস নির্দেশনা - আদর্শ ঘরের পরিচয় অথবা একটা ঘর আর উপনাম - যোগকরুন একটা ঘর - যোগদান করুন ঘরে - ঘর তৈরিকরা - চ্যাট শুরু করুন - আমন্ত্রণ - কম গুরুত্ব - ঘরগুলি - প্রিয় - নির্দেশক - যোগদান নথি লোকজন বার্তাগুলি diff --git a/vector/src/main/res/values-bn-rIN/strings.xml b/vector/src/main/res/values-bn-rIN/strings.xml index 6d3b426e3c..506e6f8370 100644 --- a/vector/src/main/res/values-bn-rIN/strings.xml +++ b/vector/src/main/res/values-bn-rIN/strings.xml @@ -684,18 +684,6 @@ লোকজন নথি - যোগদান - নির্দেশক - প্রিয় - ঘরগুলি - "কম গুরুত্ব " - আমন্ত্রণ - চ্যাট শুরু করুন - ঘর তৈরিকরা - যোগদান করুন ঘরে - যোগকরুন একটা ঘর - আদর্শ ঘরের পরিচয় অথবা একটা ঘর আর উপনাম - ব্রাউস নির্দেশনা "একটা ঘর " diff --git a/vector/src/main/res/values-bs/strings.xml b/vector/src/main/res/values-bs/strings.xml index 8628a56a26..3822490ec6 100644 --- a/vector/src/main/res/values-bs/strings.xml +++ b/vector/src/main/res/values-bs/strings.xml @@ -318,17 +318,6 @@ Da li ste sigurani? PORUKE LJUDI FAJLOVI - PRIDRUŽI SE - DIREKTORIJ - FAVORITI - SOBE - NISKI PRIORITET - POZIVI - Započni razgovor - Napravi sobu - Pridruži se sobi - Pridriži se u sobu - Upišite ID sobe ili alias sobe Pretražite imenik Traži direktorij.. Favorit diff --git a/vector/src/main/res/values-ca/strings.xml b/vector/src/main/res/values-ca/strings.xml index 214711224f..aae71ec96c 100644 --- a/vector/src/main/res/values-ca/strings.xml +++ b/vector/src/main/res/values-ca/strings.xml @@ -629,18 +629,6 @@ MISSATGES PARTICIPANTS FITXERS - - UNEIX-TE - DIRECTORI - PREFERITS - SALES - PRIORITAT BAIXA - CONVIDA - Inicia un xat - Crea una sala - Uneix-te a la sala - Uneix-te a una sala - Escriviu un id de sala o un àlies de sala Navega pel directori S\'està cercant al directori… diff --git a/vector/src/main/res/values-cs/strings.xml b/vector/src/main/res/values-cs/strings.xml index 7b33363896..b3635182a6 100644 --- a/vector/src/main/res/values-cs/strings.xml +++ b/vector/src/main/res/values-cs/strings.xml @@ -714,17 +714,6 @@ ZPRÁVY LIDÉ SOUBORY - VSTOUPIT - ADRESÁŘ - OBLÍBENÉ - MÍSTNOSTI - NÍZKÁ PRIORITA - POZVÁNKY - Začít konverzaci - Vytvořit místnost - Vstoupit do místnosti - Vstoupit do místnosti - Zadejte ID nebo přezdívku místnosti Procházet adresář %d místnost diff --git a/vector/src/main/res/values-da/strings.xml b/vector/src/main/res/values-da/strings.xml index 546fe51ed2..b29877c645 100644 --- a/vector/src/main/res/values-da/strings.xml +++ b/vector/src/main/res/values-da/strings.xml @@ -407,14 +407,6 @@ Er du sikker? MEDDELELSER FOLK FILER - OVERSIGT - FAVORITTER - RUM - LAV PRIORITET - INVITATIONER - Start chat - Opret rum - Indtast ID eller alias på et rum Gennemse oversigt 1 rum diff --git a/vector/src/main/res/values-de/strings.xml b/vector/src/main/res/values-de/strings.xml index 69ae5dd4f7..e98ccf80ad 100644 --- a/vector/src/main/res/values-de/strings.xml +++ b/vector/src/main/res/values-de/strings.xml @@ -610,18 +610,6 @@ NACHRICHTEN PERSONEN DATEIEN - - BEITRETEN - VERZEICHNIS - FAVORITEN - RÄUME - NIEDRIGE PRIORITÄT - EINLADUNGEN - Gespräch beginnen - Raum erstellen - Raum beitreten - Einem Raum beitreten - Raum-ID oder Raum-Alias eingeben Verzeichnis durchsuchen Verzeichnis wird durchsucht… diff --git a/vector/src/main/res/values-el/strings.xml b/vector/src/main/res/values-el/strings.xml index 2772f9f5d4..e05e45e5a2 100644 --- a/vector/src/main/res/values-el/strings.xml +++ b/vector/src/main/res/values-el/strings.xml @@ -89,7 +89,6 @@ Ευρετήριο Αποστολή Αποστολή πληροφοριών - Έναρξη συνομιλίας Ο διακομιστής είναι μη διαθέσιμος ή υπερφορτωμένος Προβολή αποκρυπτογραφημένου κώδικα Προβολή κώδικα @@ -594,13 +593,6 @@ Ρυθμίσεις Λεπτομέρειες Δωματίου ΚΑΛΕΣΜΕΝΟΣ/Η - Πληκτρολογείστε ταυτότητα (ID) δωματίου ή ψευδώνυμο δωματίου - Ένταξη στο δωμάτιο - Δημιουργία δωματίου - ΠΡΟΣΚΛΗΣΕΙΣ - ΧΑΜΗΛΗ ΠΡΟΤΕΡΑΙΟΤΗΤΑ - ΔΩΜΑΤΙΑ - ΣΥΜΜΕΤΟΧΗ Η αναζήτηση σε κρυπτογραφημένα δωμάτια δεν υποστηρίζεται ακόμα. ΑΡΧΕΙΑ ΜΗΝΥΜΑΤΑ diff --git a/vector/src/main/res/values-en-rGB/strings.xml b/vector/src/main/res/values-en-rGB/strings.xml index 76003bbc69..eaa0d1f8ec 100644 --- a/vector/src/main/res/values-en-rGB/strings.xml +++ b/vector/src/main/res/values-en-rGB/strings.xml @@ -4,7 +4,6 @@ Filter favourites Remove from favourites Add to favourites - FAVOURITES Synchronising Self Signing key Synchronising User key Synchronising Master key diff --git a/vector/src/main/res/values-eo/strings.xml b/vector/src/main/res/values-eo/strings.xml index 184d5c396b..8b7ceab44d 100644 --- a/vector/src/main/res/values-eo/strings.xml +++ b/vector/src/main/res/values-eo/strings.xml @@ -285,7 +285,6 @@ Agordoj PERSONOJ DOSIEROJ - ALIĜI Personoj Ĉambroj Neniuj uzantoj @@ -482,13 +481,6 @@ Neniuj rezultoj ĈAMBROJ MESAĜOJ - ELSTARIGITAJ - ĈAMBROJ - INVITOJ - Komenci babilon - Krei ĉambron - Aliĝi al ĉambro - Aliĝi al ĉambro %d ĉambro %d ĉambroj @@ -1877,8 +1869,6 @@ %1$s ĉambro trovita por %2$s %1$s ĉambroj trovitaj por %2$s - Entajpu identigilon de ĉambro aŭ kromnomon de ĉambro - MALALTA PRIORITATO Serĉado en ĉifritaj ĉambroj ankoraŭ en estas subtenata. Filtri forbaritajn uzantojn Akceptu la atestilon nur se administranto de la servilo publikigis fingrospuron akordan kun tiu ĉi-supre. @@ -2351,7 +2341,6 @@ Inkluzivigi la ĉambron en katalogo de ĉambroj Serĉante tra katalogo… Foliumi katalogon - KATALOGO KATALOGO DE UZANTOJ (%s) Katalogo de uzantoj La ĉambro ankoraŭ ne kreiĝis. Ĉu nuligi kreadon de la ĉambro\? diff --git a/vector/src/main/res/values-es-rMX/strings.xml b/vector/src/main/res/values-es-rMX/strings.xml index 40dee50d85..073cc098fe 100644 --- a/vector/src/main/res/values-es-rMX/strings.xml +++ b/vector/src/main/res/values-es-rMX/strings.xml @@ -410,18 +410,6 @@ MENSAJES PERSONAS ARCHIVOS - - ENTRAR - DIRECTORIO - FAVORITOS - SALAS - BAJA PRIORIDAD - INVITACIONES - Comenzar chat - Crear sala - Unirse a la sala - Unirse a una sala - Type a room id or a room alias Buscar directorio Buscando directorio… diff --git a/vector/src/main/res/values-es/strings.xml b/vector/src/main/res/values-es/strings.xml index bc600d5e56..3a75e843d7 100644 --- a/vector/src/main/res/values-es/strings.xml +++ b/vector/src/main/res/values-es/strings.xml @@ -612,18 +612,6 @@ Por favor permite el acceso en la próxima ventana emergente para descubrir usua MENSAJES PERSONAS ARCHIVOS - - UNIRSE - DIRECTORIO - FAVORITOS - SALAS - PRIORIDAD BAJA - INVITACIONES - Iniciar conversación - Crear sala - Unirse a la sala - Unirse a una sala - Escribe una ID o alias de sala Explorar directorio Buscando directorio… diff --git a/vector/src/main/res/values-et/strings.xml b/vector/src/main/res/values-et/strings.xml index 5cf67f79f3..4c896f553f 100644 --- a/vector/src/main/res/values-et/strings.xml +++ b/vector/src/main/res/values-et/strings.xml @@ -636,16 +636,6 @@ SÕNUMID INIMESED FAILID - LIITU - LEMMIKUD - JUTUTOAD - VÄHETÄHTIS - KUTSED - Alusta vestlust - Loo jututuba - Liitu jututoaga - Liitu jututoaga - Sisesta jututoa tunnus, nimi või alias Sirvi kataloogi Otsevestlus VAATA @@ -1209,7 +1199,6 @@ Eira Sõrmejälg (%s): Serveri õigsust ei olnud võimalik kontrollida. - LOEND Lahenda teavitustega seotud vigu Vigade tuvastamine Käivita testid diff --git a/vector/src/main/res/values-eu/strings.xml b/vector/src/main/res/values-eu/strings.xml index 655ae5ae9b..e616b30afd 100644 --- a/vector/src/main/res/values-eu/strings.xml +++ b/vector/src/main/res/values-eu/strings.xml @@ -489,18 +489,6 @@ Kontuan izan ekintza honek aplikazioa berrabiaraziko duela eta denbora bat behar JENDEA FITXATEGIAK - ELKARTU - DIREKTORIOA - GOGOKOAK - GELAK - LEHENTASUN BAXUA - GONBIDAPENAK - Hasi txata - Sortu gela - Elkartu gelara - Elkartu gela batetara - Idatzi gelaren IDa edo ezizena - Arakatu direktorioa Direktorioa bilatzen… diff --git a/vector/src/main/res/values-fa/strings.xml b/vector/src/main/res/values-fa/strings.xml index c8c4e4031f..135d9a6199 100644 --- a/vector/src/main/res/values-fa/strings.xml +++ b/vector/src/main/res/values-fa/strings.xml @@ -519,7 +519,6 @@ فرستادن یک پاسخ (رمزنشده)… پالایش اعضای اتاق اتاق‌ها - اتاق‌ها %d اتاق %d اتاق @@ -940,16 +939,6 @@ جست‌وجو افراد پرونده‌ها - پیوستن - شاخه - محبوب‌ها - کم‌اهمیت - دعوت‌ها - شروع گپ - ایجاد اتاق - پیوستن به اتاق - پیوستن به یک اتاق - شناسهٔ اتاق یا نام مستعارش را بنویسید مرور شاخه جست‌وجو کردن شاخه… تمام پیام‌ها (پرصدا) diff --git a/vector/src/main/res/values-fi/strings.xml b/vector/src/main/res/values-fi/strings.xml index 07bed56193..eaf548f3f0 100644 --- a/vector/src/main/res/values-fi/strings.xml +++ b/vector/src/main/res/values-fi/strings.xml @@ -560,19 +560,6 @@ VIESTIT HENKILÖT TIEDOSTOT - - LIITY - LUETTELO - SUOSIKIT - HUONEET - MATALA TÄRKEYS - - KUTSUT - Aloita keskustelu - Luo huone - Liity huoneeseen - Liity huoneeseen - Syötä huonetunniste tai -alias Selaa luetteloa Haetaan luettelosta… diff --git a/vector/src/main/res/values-fr-rCA/strings.xml b/vector/src/main/res/values-fr-rCA/strings.xml index 4b6f25fe57..f7e126e708 100644 --- a/vector/src/main/res/values-fr-rCA/strings.xml +++ b/vector/src/main/res/values-fr-rCA/strings.xml @@ -1448,17 +1448,6 @@ Notification sonore pour chaque message Recherche dans le répertoire… Parcourir le répertoire - Saisissez un identifiant ou un alias de salon - Rejoindre un salon - Rejoindre le salon - Créer un salon - Nouvelle discussion - INVITATIONS - PRIORITÉ BASSE - SALONS - FAVORIS - RÉPERTOIRE - REJOINDRE La recherche dans les salons chiffrés n\'est pas encore prise en charge. FICHIERS PARTICIPANTS diff --git a/vector/src/main/res/values-fr/strings.xml b/vector/src/main/res/values-fr/strings.xml index 5bc09859f9..c14b75d2fd 100644 --- a/vector/src/main/res/values-fr/strings.xml +++ b/vector/src/main/res/values-fr/strings.xml @@ -444,16 +444,6 @@ MESSAGES PARTICIPANTS FICHIERS - REJOINDRE - RÉPERTOIRE - FAVORIS - SALONS - PRIORITÉ BASSE - INVITATIONS - Nouvelle discussion - Créer un salon - Rejoindre le salon - Rejoindre un salon Parcourir le répertoire Recherche dans le répertoire… Favori @@ -694,7 +684,6 @@ Voulez-vous cacher tous les messages de cet utilisateur \? \n \nVeuillez noter que cette action redémarrera l’application et pourra prendre un certain temps. - Saisissez un identifiant ou un alias de salon Allumer l’écran pendant 3 secondes Quand je suis invité sur un salon Paramètres utilisateur diff --git a/vector/src/main/res/values-fy/strings.xml b/vector/src/main/res/values-fy/strings.xml index bf8208dd12..2ec2daf266 100644 --- a/vector/src/main/res/values-fy/strings.xml +++ b/vector/src/main/res/values-fy/strings.xml @@ -859,11 +859,6 @@ Profylôfbylding Ferzje %s Ferzje - ÚTNÛGINGEN - LEGE PRIORITEIT - KEAMERS - FAVORITEN - MAP KEAMERS Gjin resultaten Download annulearje @@ -1416,12 +1411,6 @@ %d keamers Katalogus trochblêdzje - Fier in petear(by)namme yn - In keamer yngean - De keamer yngean - Petear oanmeitsje - Petear starte - LID WURDE Sykjen yn fersifere keamers wurdt op dit stuit net stipe. BESTANNEN PERSOANEN diff --git a/vector/src/main/res/values-gl/strings.xml b/vector/src/main/res/values-gl/strings.xml index fdf9c0de2c..004551daf2 100644 --- a/vector/src/main/res/values-gl/strings.xml +++ b/vector/src/main/res/values-gl/strings.xml @@ -482,11 +482,6 @@ MENSAXES XENTE FICHEIROS - Convites - Iniciar conversa - Crear sala - Unirse á sala - Unirse á sala Buscando cartafol… Todas as mensaxes (alto) Todas as mensaxes @@ -779,11 +774,6 @@ UNIUSE Cancelar a subida Cancelar a descarga - UNIRSE - DIRECTORIO - FAVORITOS - SALAS - BAIXA PRIORIDADE 1 sala %d salas diff --git a/vector/src/main/res/values-hr/strings.xml b/vector/src/main/res/values-hr/strings.xml index 28e441d96d..a5ad514dc6 100644 --- a/vector/src/main/res/values-hr/strings.xml +++ b/vector/src/main/res/values-hr/strings.xml @@ -454,17 +454,6 @@ PORUKE OSOBE DATOTEKE - PRIDRUŽILI SE - POPIS - OMILJENO - SOBE - NISKI PRIORITET - POZIVNICE - Započni ćaskanje - Izradi sobu - Pridruži se sobi - Pridruži se sobi - Unesite identitet ili alternativni naziv sobe Pregledaj popis %d soba diff --git a/vector/src/main/res/values-hu/strings.xml b/vector/src/main/res/values-hu/strings.xml index b33ffd5aae..0691860a2d 100644 --- a/vector/src/main/res/values-hu/strings.xml +++ b/vector/src/main/res/values-hu/strings.xml @@ -500,17 +500,6 @@ Vedd figyelembe, hogy az alkalmazás újraindul ami sok időt vehet igénybe."< ÜZENETEK EMBEREK FÁJLOK - BELÉPÉS - KÖNYVTÁR - KEDVENCEK - SZOBÁK - ALACSONY PRIORITÁSÚ - MEGHÍVÁSOK - Csevegés indítása - Szoba létrehozása - Csatlakozás szobához - Csatlakozás egy szobához - Írd be a szoba azonosítóját vagy nevét Könyvtár böngészése Könyvtár keresése… Kedvenc diff --git a/vector/src/main/res/values-in/strings.xml b/vector/src/main/res/values-in/strings.xml index db7a0fce71..4e3d82d565 100644 --- a/vector/src/main/res/values-in/strings.xml +++ b/vector/src/main/res/values-in/strings.xml @@ -413,17 +413,6 @@ Ijinkan akses lewat halaman selanjutnya untuk menemukan pengguna ${app_name} yan PESAN ORANG BERKAS - GABUNG - DIREKTORI - FAVORIT - RUANG - PRIORITAS RENDAH - UNDANGAN - Mulai percakapan - Buat ruang - Gabung ke ruang - Gabung ke ruang - Ketik id atau alias ruang Jelajahi direktori %d ruangan diff --git a/vector/src/main/res/values-is/strings.xml b/vector/src/main/res/values-is/strings.xml index 23c4f557c6..1a42790035 100644 --- a/vector/src/main/res/values-is/strings.xml +++ b/vector/src/main/res/values-is/strings.xml @@ -259,16 +259,6 @@ SKILABOÐ FÓLK SKRÁR - TAKA ÞÁTT - MAPPA - EFTIRLÆTI - SPJALLRÁSIR - LÍTILL FORGANGUR - BOÐSGESTIR - Hefja spjall - Búa til spjallrás - Taka þátt í spjallrás - Taka þátt í spjallrás Vafra í möppu %d spjallrás @@ -626,7 +616,6 @@ Bjóða miðað við auðkenni TENGILIÐIR Á TÆKI (%d) Bjóða notendum miðað við auðkenni - Settu inn auðkenni eða samheiti spjallrásar %1$s spjallrás fannst fyrir %2$s %1$s spjallrásir fundust fyrir %2$s diff --git a/vector/src/main/res/values-it/strings.xml b/vector/src/main/res/values-it/strings.xml index 4391fc7a1f..14e73d1757 100644 --- a/vector/src/main/res/values-it/strings.xml +++ b/vector/src/main/res/values-it/strings.xml @@ -610,18 +610,6 @@ MESSAGGI UTENTI FILE - - ENTRA - ELENCO - PREFERITI - STANZE - BASSA PRIORITÀ - INVITI - Avvia chat - Crea stanza - Entra nella stanza - Entra in una stanza - Digita l\'ID stanza o il suo nome Esplora l\'elenco Ricerca negli elenchi… diff --git a/vector/src/main/res/values-iw/strings.xml b/vector/src/main/res/values-iw/strings.xml index 77d3379ccb..b0456ec186 100644 --- a/vector/src/main/res/values-iw/strings.xml +++ b/vector/src/main/res/values-iw/strings.xml @@ -684,17 +684,6 @@ %d חדרים חפש בתיקיות - הקלד מזהה חדר או כינוי לחדר - הצטרף לחדר - הצטרף לחדר - צור חדר - התחלת שיחת צ\'אט - הזמנות - עדיפות נמוכה - חדרים - מועדפים - תיקייה ראשית - הצטרף עדיין אין תמיכה בחיפוש בחדרים מוצפנים. קבצים אנשים diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml index e5c1cae150..ef83886d3d 100644 --- a/vector/src/main/res/values-ja/strings.xml +++ b/vector/src/main/res/values-ja/strings.xml @@ -450,14 +450,6 @@ 参加者 ファイル 部屋 - お気に入り - 部屋 - 低優先度 - 会話を開始 - 部屋を作成 - 部屋へ参加 - 部屋へ参加 - 参加 部屋一覧を見る 退室 会話 @@ -602,9 +594,6 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場 ダウンロードをキャンセルする 検索 メッセージ - ディレクトリ - 招待 - 部屋のIDまたは部屋のエイリアスを入力してください ディレクトリを検索中… サードパーティの通知 このアプリのシステムの情報を見る。 diff --git a/vector/src/main/res/values-kab/strings.xml b/vector/src/main/res/values-kab/strings.xml index a2423fe993..53bc1a4c6c 100644 --- a/vector/src/main/res/values-kab/strings.xml +++ b/vector/src/main/res/values-kab/strings.xml @@ -323,8 +323,6 @@ Nadi Sizdeg iɛeggalen n texxamt Ulac igmad - Bdu adiwenni - Rnu taxxamt Iznan i meṛṛa (sɛan ṣṣut) Iznan i meṛṛa Sens imesli @@ -866,15 +864,6 @@ IZNAN IMDANEN IFUYLA - RNU - AKARAM - INURIFEN - TIXXAMIN - TAZWART TADDAYT - INEBGAWEN - Rnu ɣer texxamt - Rnu ɣer texxamt - Aru asulay n texxamt neɣ isem yettunefken i texxamt 1 texxamt %d texxamin diff --git a/vector/src/main/res/values-ko/strings.xml b/vector/src/main/res/values-ko/strings.xml index 895799d51f..2f7110f19e 100644 --- a/vector/src/main/res/values-ko/strings.xml +++ b/vector/src/main/res/values-ko/strings.xml @@ -506,17 +506,6 @@ 메시지 사람 파일 - 참가 - 목록 - 즐겨찾기 - - 중요하지 않음 - 초대 - 대화 시작 - 방 만들기 - 방 참가하기 - 방 참가하기 - 방 ID나 방 별칭을 입력 목록 찾기 %d개의 방 diff --git a/vector/src/main/res/values-lv/strings.xml b/vector/src/main/res/values-lv/strings.xml index 3cb036e448..2602c9ab12 100644 --- a/vector/src/main/res/values-lv/strings.xml +++ b/vector/src/main/res/values-lv/strings.xml @@ -578,17 +578,6 @@ Lūdzu dod piekļuves atļauju nākamajā uznirstošajā logā, lai būtu iespē ZIŅAS CILVĒKI FAILI - PIEVIENOTIES - KATALOGS - IZLASE - ISTABAS - ZEMA PRIORITĀTE - UZAICINĀJUMI - Sākt saraksti - Izveidot istabu - Ieiet istabā - Ieiet istabā - Ievadiet istabas ID vai aliasu Pārlūkot katalogu Meklē katalogā… Visas ziņas (ar skaņu) diff --git a/vector/src/main/res/values-ml/strings.xml b/vector/src/main/res/values-ml/strings.xml index 287548e0a3..59fe0fd4cf 100644 --- a/vector/src/main/res/values-ml/strings.xml +++ b/vector/src/main/res/values-ml/strings.xml @@ -268,7 +268,6 @@ ക്രമീകരണങ്ങൾ സന്ദേശങ്ങൾ മറക്കൂ - മുറികൾ ഫയലുകൾ ആളുകൾ സന്ദേശങ്ങൾ @@ -382,10 +381,6 @@ %d മുറി %d മുറികൾ - ഒരു മുറിയിൽ ചേരുക - മുറിയിൽ ചേരുക - മുറി സൃഷ്ടിക്കൂ - ചാറ്റ് ആരംഭിക്കുക ഫലങ്ങളൊന്നുമില്ല മുറി വിശദാംശങ്ങൾ വിഷയം മാറ്റുക @@ -802,8 +797,6 @@ പ്രാപ്തമാക്കുക പകർപ്പാവകാശം നിശബ്ദമാക്കുക - കഷണങ്ങൾ - ചേരുക തിരയുക അവഗണിക്കുക നിരോധനം മാറ്റുക @@ -890,7 +883,6 @@ നേരിട്ടുള്ള ചാറ്റ് ഡയറക്‌ടറിയിൽ തിരയുന്നു… ഡയറക്ടറി ബ്രൗസ് ചെയ്യുക - കുറഞ്ഞ മുൻ‌ഗണന ഡൗൺലോഡ് റദ്ദാക്കുക അപ്‌ലോഡ് റദ്ദാക്കുക diff --git a/vector/src/main/res/values-nb-rNO/strings.xml b/vector/src/main/res/values-nb-rNO/strings.xml index 839f1cf518..dd6ab9e600 100644 --- a/vector/src/main/res/values-nb-rNO/strings.xml +++ b/vector/src/main/res/values-nb-rNO/strings.xml @@ -146,7 +146,6 @@ Avbryt nedlasting Ingen treff ROM - ROM Demp Direktesamtale Glem @@ -698,9 +697,6 @@ INVITERT MELDINGER FILER - Opprett et rom - Bli med i rommet - Bli med i et rom Betingelser og vilkår Personvernregler Legg til E-postadresse @@ -1023,12 +1019,6 @@ %1$s rom funnet for %2$s Bla gjennom katalogen - Skriv inn et rom-ID eller et romalias - Start chat - LAV PRIORITET - FAVORITTER - KATALOG - BLI MED Søking i krypterte rom støttes ikke ennå. FOLK Årsak til å rapportere dette innholdet @@ -1613,7 +1603,6 @@ Varslingstjeneste Play Tjenester Sjekk Søker i katalogen… - INVITASJONER Ikke noe innhold Sender Mislyktes diff --git a/vector/src/main/res/values-nl/strings.xml b/vector/src/main/res/values-nl/strings.xml index 3fffdced70..b5b3e6968b 100644 --- a/vector/src/main/res/values-nl/strings.xml +++ b/vector/src/main/res/values-nl/strings.xml @@ -456,18 +456,6 @@ BERICHTEN PERSONEN BESTANDEN - - TOETREDEN - CATALOGUS - FAVORIETEN - GESPREKKEN - LAGE PRIORITEIT - UITNODIGINGEN - Gesprek beginnen - Gesprek aanmaken - Gesprek toetreden - Treed een gesprek toe - Voer een gespreks(bij)naam in Catalogus doorbladeren Catalogus wordt doorzocht… diff --git a/vector/src/main/res/values-nn/strings.xml b/vector/src/main/res/values-nn/strings.xml index f86d4e5938..01fe889ede 100644 --- a/vector/src/main/res/values-nn/strings.xml +++ b/vector/src/main/res/values-nn/strings.xml @@ -436,17 +436,6 @@ MELDINGAR FOLK FILER - VERT MED - UTVAL - YNDLINGAR - ROM - LÅGRETT - INNBJODINGAR - Start samtale - Laga eit rom - Vert med i romet - Vert med i eit rom - Skriv inn ein rom-ID eller eit romalias Bla gjennom katalog %d rom diff --git a/vector/src/main/res/values-pl/strings.xml b/vector/src/main/res/values-pl/strings.xml index 0bb609cd2f..b93ffc9247 100644 --- a/vector/src/main/res/values-pl/strings.xml +++ b/vector/src/main/res/values-pl/strings.xml @@ -357,17 +357,6 @@ WIADOMOŚCI LUDZIE PLIKI - DOŁĄCZ - KATALOG - ULUBIONE - POKÓJ - NISKI PRIORYTET - ZAPROSZENIA - Rozpocznij rozmowę - Utwórz pokój - Dołącz do pokoju - Dołącz do pokoju - Wprowadź ID lub nazwę pokoju Przeglądaj katalog Przeszukiwanie katalogu… Dodaj do ulubionych diff --git a/vector/src/main/res/values-pt-rBR/strings.xml b/vector/src/main/res/values-pt-rBR/strings.xml index c057570025..786f9a514b 100644 --- a/vector/src/main/res/values-pt-rBR/strings.xml +++ b/vector/src/main/res/values-pt-rBR/strings.xml @@ -614,18 +614,6 @@ MENSAGENS PESSOAS ARQUIVOS - - JUNTAR-SE - DIRETÓRIO - FAVORITOS - SALAS - BAIXA PRIORIDADE - CONVITES - Começar chat - Criar sala - Juntar-se a sala - Juntar-se a uma sala - Digite uma id de sala ou um alias de sala Navegar diretório Pesquisando diretório… diff --git a/vector/src/main/res/values-pt/strings.xml b/vector/src/main/res/values-pt/strings.xml index ca25d8f9e5..ff78d50424 100644 --- a/vector/src/main/res/values-pt/strings.xml +++ b/vector/src/main/res/values-pt/strings.xml @@ -405,18 +405,6 @@ Note que esta acção irá reiniciar a aplicação e poderá levar algum tempo.< MENSAGENS PESSOAS FICHEIROS - - ENTRAR - LISTAGEM - FAVORITOS - SALAS - BAIXA PRIORIDADE - CONVITES - Iniciar conversa - Criar sala - Entrar na sala - Entrar numa sala - Insira o id ou o pseudónimo da sala Navegar diretório A pesquisar o directório… diff --git a/vector/src/main/res/values-ro/strings.xml b/vector/src/main/res/values-ro/strings.xml index 0e92e74907..c4b918c207 100644 --- a/vector/src/main/res/values-ro/strings.xml +++ b/vector/src/main/res/values-ro/strings.xml @@ -183,12 +183,10 @@ %d camere %d de camere - CAMERE CAMERE Camere Camere sugerate Camere - FAVORITE Favorite Preferințe Adresă de e-mail diff --git a/vector/src/main/res/values-ru/strings.xml b/vector/src/main/res/values-ru/strings.xml index fa8238c4ec..7adf19bb67 100644 --- a/vector/src/main/res/values-ru/strings.xml +++ b/vector/src/main/res/values-ru/strings.xml @@ -627,18 +627,6 @@ СООБЩЕНИЯ ЛЮДИ ФАЙЛЫ - - ПРИСОЕДИНИЛИСЬ - КАТАЛОГ - ИЗБРАННОЕ - КОМНАТЫ - МАЛОВАЖНЫЕ - ПРИГЛАШЕНИЯ - Начать чат - Создать комнату - Войти в комнату - Войти в комнату - Введите ID комнаты или псевдоним Просмотр каталога Поиск в каталоге… diff --git a/vector/src/main/res/values-sk/strings.xml b/vector/src/main/res/values-sk/strings.xml index e4cb519544..38882e4bad 100644 --- a/vector/src/main/res/values-sk/strings.xml +++ b/vector/src/main/res/values-sk/strings.xml @@ -527,17 +527,6 @@ SPRÁVY ĽUDIA SÚBORY - VSTÚPIŤ - ADRESÁR - OBĽÚBENÉ - MIESTNOSTI - NÍZKA PRIORITA - POZVANIA - Začať konverzáciu - Vytvoriť miestnosť - Vstúpiť do miestnosti - Vstúpiť do miestnosti - Zadajte alias alebo ID miestnosti Prezrieť adresár Vyhľadávanie v adresári… Do obľúbených diff --git a/vector/src/main/res/values-sq/strings.xml b/vector/src/main/res/values-sq/strings.xml index c1c1efcdd7..15514c135d 100644 --- a/vector/src/main/res/values-sq/strings.xml +++ b/vector/src/main/res/values-sq/strings.xml @@ -554,17 +554,6 @@ MESAZHE PERSONA KARTELA - MERRNI PJESË - DREJTORI - TË PARAPËLQYERA - DHOMA - ME PËRPARËSI TË ULËT - FTESA - Filloni fjalosje - Krijo dhomë - Hyni në dhomë - Hyni në një dhomë - Shtypni një ID ose alias dhome Shfletoni në drejtori Po kërkohet në drejtori… Krejt mesazhet (e zhurmshme) diff --git a/vector/src/main/res/values-sv/strings.xml b/vector/src/main/res/values-sv/strings.xml index ed0d1ede73..f9813769c8 100644 --- a/vector/src/main/res/values-sv/strings.xml +++ b/vector/src/main/res/values-sv/strings.xml @@ -647,7 +647,6 @@ Ignorera Filtrera rumsmedlemmar RUM - RUM %d rum %d rum @@ -1292,16 +1291,6 @@ Sök Inga resultat MEDDELANDEN - GÅ MED - KATALOG - FAVORITER - LÅG PRIORITET - INBJUDNINGAR - Starta chatt - Skapa rum - Gå med i rum - Gå med i ett rum - Skriv ett rums-ID eller ett rumsalias Bläddra i katalogen Söker i katalog… Favorit diff --git a/vector/src/main/res/values-te/strings.xml b/vector/src/main/res/values-te/strings.xml index b0dc11775a..be675b8a28 100644 --- a/vector/src/main/res/values-te/strings.xml +++ b/vector/src/main/res/values-te/strings.xml @@ -348,17 +348,6 @@ ప్రజలు దస్త్రాలు - చేరు - డైరెక్టరీ - గదులు - తక్కువ ప్రాధాన్యత - ఆహ్వానాలు - చాట్ ప్రారంభించండి - గదిని సృష్టించండి - గదిలో చేరండి - ఒక గదిలో చేరండి - ఒక గది id లేదా ఒక గది alias టైప్ చేయండి - డైరెక్టరీలో విహరించండి డైరెక్టరీని వెతుకుతున్నాం.. diff --git a/vector/src/main/res/values-th/strings.xml b/vector/src/main/res/values-th/strings.xml index 091dc268a0..00c9d049cb 100644 --- a/vector/src/main/res/values-th/strings.xml +++ b/vector/src/main/res/values-th/strings.xml @@ -173,7 +173,6 @@ %d ห้อง - เข้าร่วม ยังไม่รองรับการค้นหาในห้องที่เข้ารหัส ไฟล์ ผู้คน @@ -289,12 +288,6 @@ ลงทะเบียน กำลังค้นหาไดเรกทอรี… เรียกดูไดเรกทอรี - เข้าร่วมห้อง - เข้าร่วมห้อง - สร้างห้อง - เริ่มแชท - ห้อง - ไดเรกทอรี เข้าร่วมแล้ว ไดเรกทอรีผู้ใช้ (%s) ทำให้เป็นผู้ดูแล @@ -309,7 +302,6 @@ ล้าง กำลังซิงค์… เอาออก - รายการโปรด รหัสผ่าน เปลี่ยนรหัสผ่าน เส้นเวลา diff --git a/vector/src/main/res/values-tr/strings.xml b/vector/src/main/res/values-tr/strings.xml index 9427b11614..efa85bbfcf 100644 --- a/vector/src/main/res/values-tr/strings.xml +++ b/vector/src/main/res/values-tr/strings.xml @@ -447,17 +447,6 @@ MESAJLAR KİŞİLER DOSYALAR - KATIL - DİZİN - FAVORİLER - ODALAR - DÜŞÜK ÖNCELİK - DAVETLER - Sohbet başlat - Oda oluştur - Odaya katıl - Bir odaya katıl - Oda ID\'si ya da takma adı girin Dizinlere gözat 1 oda diff --git a/vector/src/main/res/values-tzm/strings.xml b/vector/src/main/res/values-tzm/strings.xml index 3566c14f0d..6fc9d7527f 100644 --- a/vector/src/main/res/values-tzm/strings.xml +++ b/vector/src/main/res/values-tzm/strings.xml @@ -39,7 +39,6 @@ Dɣer Azen Tuzinin - LKEM IFUYLA Rzu Ifuyla diff --git a/vector/src/main/res/values-uk/strings.xml b/vector/src/main/res/values-uk/strings.xml index a999c6fa3f..df7606ee6f 100644 --- a/vector/src/main/res/values-uk/strings.xml +++ b/vector/src/main/res/values-uk/strings.xml @@ -512,18 +512,6 @@ ПОВІДОМЛЕННЯ ЛЮДИ ФАЙЛИ - - ПРИЄДНАЛИСЯ - КАТАЛОГ - ОБРАНІ - КІМНАТИ - НЕВАЖЛИВІ - ЗАПРОШЕННЯ - Почати чат - Створити кімнату - Увійти до кімнати - Увійти до кімнати - Введіть id кімнати чи аліас Огляд каталогу Пошук у каталозі… diff --git a/vector/src/main/res/values-vi/strings.xml b/vector/src/main/res/values-vi/strings.xml index 96e00b3103..bee7cb4061 100644 --- a/vector/src/main/res/values-vi/strings.xml +++ b/vector/src/main/res/values-vi/strings.xml @@ -1432,17 +1432,6 @@ %d phòng Duyệt danh mục phòng - Nhập ID hoặc tên phòng - Tham gia phòng - Tham gia phòng - Tạo phòng - Bắt đầu chat - LỜI MỜI - ƯU TIÊN THẤP - PHÒNG - YÊU THÍCH - DANH MỤC - THAM GIA Tìm kiếm trong phòng mã hóa chưa được hỗ trợ. FILES NGƯỜI diff --git a/vector/src/main/res/values-zh-rCN/strings.xml b/vector/src/main/res/values-zh-rCN/strings.xml index 9007b57ad1..953ccc0f21 100644 --- a/vector/src/main/res/values-zh-rCN/strings.xml +++ b/vector/src/main/res/values-zh-rCN/strings.xml @@ -329,10 +329,6 @@ 聊天室 消息 文件 - 加入 - 聊天室 - 开始聊天 - 创建聊天室 私聊 消息 设置 @@ -448,8 +444,6 @@ \n注意,此操作会重启应用并将花费一些时间。 取消上传 取消下载 - 收藏夹 - 低优先级 降低优先级 退出对话 版权 @@ -598,8 +592,6 @@ 这可能意味着有人正在恶意劫持你的流量,或者你的手机不信任远程服务器提供的数字证书。 如果服务器管理员说这是预期的情况,请确保下面的指纹与管理员提供的指纹相匹配。 报告这个内容的原因 - 目录 - 邀请 浏览目录 正在搜索目录… 收藏夹 @@ -723,9 +715,6 @@ 搜索历史 历史消息 用 ID 邀请 - 进入聊天室 - 进入一个聊天室 - 输入聊天室 ID 或者聊天室别名 跳到未读 主页显示 固定含错过通知的聊天室 diff --git a/vector/src/main/res/values-zh-rTW/strings.xml b/vector/src/main/res/values-zh-rTW/strings.xml index f042772f2e..363383caa5 100644 --- a/vector/src/main/res/values-zh-rTW/strings.xml +++ b/vector/src/main/res/values-zh-rTW/strings.xml @@ -622,17 +622,6 @@ 訊息 聯絡人 檔案 - 加入 - 目錄 - 最愛 - 聊天室 - 低優先度 - 邀請 - 開始聊天 - 新建聊天室 - 加入聊天室 - 加入聊天室 - 輸入聊天室 ID 或聊天室別名 瀏覽目錄 %d 個聊天室 diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index aebe6312c6..e8f90cc683 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -1047,19 +1047,6 @@ FILES Searching in encrypted rooms is not supported yet. - - JOIN - DIRECTORY - FAVORITES - ROOMS - LOW PRIORITY - INVITES - Start chat - Create room - Join room - Join a room - Type a room id or a room alias - Browse directory From 9f13a9df5499cc19b61eb6445aacbebe5a37ae54 Mon Sep 17 00:00:00 2001 From: David Langley Date: Tue, 4 Jan 2022 12:20:56 +0000 Subject: [PATCH 193/632] If latestPreviewableEvent is null we should return false(no messages in room). --- changelog.d/4749.bugfix | 1 + .../sdk/internal/session/room/summary/RoomSummaryUpdater.kt | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 changelog.d/4749.bugfix diff --git a/changelog.d/4749.bugfix b/changelog.d/4749.bugfix new file mode 100644 index 0000000000..5ea29f66a0 --- /dev/null +++ b/changelog.d/4749.bugfix @@ -0,0 +1 @@ +Fix for broken unread message indicator on the room list when there are no messages in the room. \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt index 3556cabb33..f3ff38e237 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt @@ -136,7 +136,7 @@ internal class RoomSummaryUpdater @Inject constructor( roomSummaryEntity.hasUnreadMessages = roomSummaryEntity.notificationCount > 0 || // avoid this call if we are sure there are unread events - !isEventRead(realm.configuration, userId, roomId, latestPreviewableEvent?.eventId) + latestPreviewableEvent?.let { !isEventRead(realm.configuration, userId, roomId, it.eventId) } ?: false roomSummaryEntity.setDisplayName(roomDisplayNameResolver.resolve(realm, roomId)) roomSummaryEntity.avatarUrl = roomAvatarResolver.resolve(realm, roomId) From 63c63bc46241163eb5fbd5ccd9168b478d3269fa Mon Sep 17 00:00:00 2001 From: Claire Gizard Date: Tue, 4 Jan 2022 13:49:18 +0100 Subject: [PATCH 194/632] Update error message when joining room --- changelog.d/4847.bugfix | 1 + .../src/main/java/im/vector/app/core/error/ErrorFormatter.kt | 4 ++++ vector/src/main/res/values/strings.xml | 1 + 3 files changed, 6 insertions(+) create mode 100644 changelog.d/4847.bugfix diff --git a/changelog.d/4847.bugfix b/changelog.d/4847.bugfix new file mode 100644 index 0000000000..d311882934 --- /dev/null +++ b/changelog.d/4847.bugfix @@ -0,0 +1 @@ +Translate the error observed when the user is not allowed to join a room \ No newline at end of file diff --git a/vector/src/main/java/im/vector/app/core/error/ErrorFormatter.kt b/vector/src/main/java/im/vector/app/core/error/ErrorFormatter.kt index 6494f31336..8640fa6f05 100644 --- a/vector/src/main/java/im/vector/app/core/error/ErrorFormatter.kt +++ b/vector/src/main/java/im/vector/app/core/error/ErrorFormatter.kt @@ -100,6 +100,10 @@ class DefaultErrorFormatter @Inject constructor( throwable.error.code == MatrixError.M_THREEPID_AUTH_FAILED -> { stringProvider.getString(R.string.error_threepid_auth_failed) } + throwable.error.code == MatrixError.M_UNKNOWN && + throwable.error.message == "Not allowed to join this room" -> { + stringProvider.getString(R.string.room_error_access_unauthorized) + } else -> { throwable.error.message.takeIf { it.isNotEmpty() } ?: throwable.error.code.takeIf { it.isNotEmpty() } diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 34ac5fcddc..26daeeed83 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -169,6 +169,7 @@ It is not currently possible to re-join an empty room. + You are not allowed to join this room Email address From 3db27f89bb5dd782e5540ea3f6c07cb7e07bae6b Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 4 Jan 2022 14:02:52 +0100 Subject: [PATCH 195/632] Remove useless comments in translations --- vector/src/main/res/values-ar/strings.xml | 7 -- vector/src/main/res/values-bg/strings.xml | 7 -- vector/src/main/res/values-ca/strings.xml | 68 --------------- vector/src/main/res/values-de/strings.xml | 67 --------------- vector/src/main/res/values-es-rMX/strings.xml | 61 ------------- vector/src/main/res/values-es/strings.xml | 68 --------------- vector/src/main/res/values-eu/strings.xml | 7 -- vector/src/main/res/values-fi/strings.xml | 85 ------------------- vector/src/main/res/values-fr/strings.xml | 6 -- vector/src/main/res/values-hu/strings.xml | 6 -- vector/src/main/res/values-is/strings.xml | 7 -- vector/src/main/res/values-it/strings.xml | 62 -------------- vector/src/main/res/values-ja/strings.xml | 5 -- vector/src/main/res/values-lv/strings.xml | 7 -- vector/src/main/res/values-nl/strings.xml | 67 --------------- vector/src/main/res/values-pl/strings.xml | 2 - vector/src/main/res/values-pt-rBR/strings.xml | 69 --------------- vector/src/main/res/values-pt/strings.xml | 62 -------------- vector/src/main/res/values-ru/strings.xml | 67 --------------- vector/src/main/res/values-sk/strings.xml | 7 -- vector/src/main/res/values-uk/strings.xml | 63 -------------- vector/src/main/res/values-zh-rCN/strings.xml | 10 --- 22 files changed, 810 deletions(-) diff --git a/vector/src/main/res/values-ar/strings.xml b/vector/src/main/res/values-ar/strings.xml index ee8374d549..4e42582338 100644 --- a/vector/src/main/res/values-ar/strings.xml +++ b/vector/src/main/res/values-ar/strings.xml @@ -518,7 +518,6 @@ لست في هذه الغرفة. ليس لديك تصريح لفعل هذا في هذه الغرفة. استخدم الكمرة الأصيلة - تحذير! لم تُرسل الرسائل بسبب وجود أجهزة مجهولة. ماذا أفعل؟ %1$s. ‏%2$s. ليس ’⁨%s⁩‘ تنسيقا صالحا لاختصار @@ -578,10 +577,8 @@ الغرفة ⁨%s⁩ غير ظاهرة. لقد أضفت جهازا جديدا ’⁨%s⁩‘ يطلب مفاتيح التعمية. تجاهل الطلب - خطأ في الأمر لم يُفهم الأمر: ⁨%s⁩ - أزل تحتاج تصريح الدعوة لبدء اجتماع في هذه الغرفة أو @@ -765,13 +762,11 @@ %d ودجة نشطة %d ودجة نشطة - أضِف تطبيقات ماترِكس يطلب جهازك غير المؤكّد ”‏⁨%s⁩“ مفاتيح التعمية. ابدأ التأكيد شارِك دون تأكيد رسالة معمّاة - أنشئ اسم المجتمع معرّف المجتمع @@ -844,7 +839,6 @@ معطّل مزعج ما زالت مكالمات الاجتماعات قيد التطوير وقد لا يُعتمد عليها. - تحتوي هذه الغرفة أجهزة مجهولة لم يُتأكّد منها. يعني هذا عدم وجود أي ضمانات بأن الأجهزة هي فعلا ملك لمن يستخدمها. ننصحك بأن تبدأ عملية التأكيد لكل جهاز وذلك قبل أن تتابع. يمكنك طبعا إرسال الرسالة دون تأكيد إن أردت ذلك. @@ -907,7 +901,6 @@ يجب أن يكون مستوى السلطة عددا صحيحا موجبا. مثال مثال - الغرفة دعوة أرسل إلى diff --git a/vector/src/main/res/values-bg/strings.xml b/vector/src/main/res/values-bg/strings.xml index a839251b66..b2e5b7956c 100644 --- a/vector/src/main/res/values-bg/strings.xml +++ b/vector/src/main/res/values-bg/strings.xml @@ -212,7 +212,6 @@ Създадохте дискусията %1$s създаде дискусията Шифровано съобщение - Светла тема Тъмна тема Черна тема @@ -748,7 +747,6 @@ %d активно приспособление %d активни приспособления - Неуспешно създаване на приспособление. Неуспешно изпращане на заявката. Нивото на достъп трябва да бъде позитивно число. @@ -850,19 +848,15 @@ Най-голям Огромен Използване на камерата на телефона - Добавихте ново устройство \'%s\', което изисква ключове за шифроване. Вашето непотвърдено устройство \'%s\' изисква ключове за шифроване. Започни потвърждението Сподели без потвърждение Игнорирай поканата - Предупреждение! Груповите разговори са в процес на разработка и не са надеждни. - Грешка в командата Неразпозната команда: %s - Изключено Шумно Създай @@ -871,7 +865,6 @@ Пример ID на общност пример - Начало Хора Стаи diff --git a/vector/src/main/res/values-ca/strings.xml b/vector/src/main/res/values-ca/strings.xml index aae71ec96c..6d3a3820d4 100644 --- a/vector/src/main/res/values-ca/strings.xml +++ b/vector/src/main/res/values-ca/strings.xml @@ -48,7 +48,6 @@ %1$s a canviat el seu àlies a %2$s %s ha realitzat una videotrucada. %s ha realitzat una trucada de veu. - Invitació de %s Convida a la sala %1$s i %2$s @@ -263,16 +262,13 @@ %1$s ha iniciat una videoconferència Has iniciat una videoconferència %1$s ha finalitzat la videoconferència - Tema clar Tema fosc Tema negre - Sincronitzant… Escoltant esdeveniments Notificacions amb so Notificacions silencioses - Missatges Sala Configuració @@ -280,7 +276,6 @@ Historial Informe d\'errors Detalls de la comunitat - D\'acord Cancel·la Desa @@ -313,7 +308,6 @@ o Convida Fora de línia - Tanca la sessió Trucada Videotrucada @@ -325,24 +319,19 @@ Tanca S\'ha copiat al porta-retalls Desactiva - Confirmació Avís - Inici Preferits Persones Sales - Filtra noms de sala Filtra preferits Filtra persones Filtra noms de sala Filtra noms de comunitat - Invitacions Prioritat baixa - Xats personals Llibreta d\'adreces local Directori d\'usuari @@ -350,7 +339,6 @@ No hi ha xats No has donat permís a ${app_name} perquè pugui accedir als teus contactes locals Sense resultats - Sales Directori de sala No hi ha sales @@ -359,7 +347,6 @@ %d usuari %d usuaris - Convida Comunitats No hi ha grups @@ -395,7 +382,6 @@ Fes una foto o un vídeo Fes una foto Fes un vídeo - Entra Crea un compte Tramet @@ -443,7 +429,6 @@ La contrasenya s\'ha reiniciat. \n \nSe t\'ha desconnectat de totes les teves sessions i no rebràs més notificacions. Per reactivar les notificacions, torna a iniciar sessió a cada dispositiu. - La URL ha de començar per http[s]:// No s\'ha pogut iniciar la sessió: error de xarxa No s\'ha pogut iniciar la sessió @@ -458,28 +443,20 @@ S\'han enviat massa peticions Aquest nom d\'usuari ja està en ús L\'enllaç del correu electrònic que encara no heu fet clic - - Llista de confirmacions de lectura - - Envia com Original Gran Mitjana Petita - "Voleu cancel·lar la baixada? Voleu cancel·lar la pujada? %d s %1$dm %2$ds - Ahir Avui - Nom de la sala Tema de la sala - Truca Trucada establerta Establint la trucada… @@ -493,10 +470,8 @@ Ha fallat la connexió de mitjans No es pot iniciar la càmera s\'ha contestat la trucada des d\'un altre lloc - Fes una foto o un vídeo" No es poden gravar vídeos" - Informació Per poder enviar i desar fitxers adjunts, ${app_name} necessita permís per accedir a la galeria de fotos i vídeos. \n @@ -517,31 +492,25 @@ \n \nPermets que ${app_name} accedeixi als teus contactes amb aquesta finalitat\? No s\'ha realitzat l\'acció per falta de permisos - Desat Desar a baixades? NO Continua - Elimina Uneix-te Previsualitza Rebutja - Vés fins al primer no llegit - L\'usuari %s t\'ha convidat a unir-te a aquesta sala Aquesta invitació s\'ha enviat a %s, que no està associat amb aquest compte. \nPotser hauries d\'iniciar sessió amb un compte diferent o afegir aquest correu electrònic al teu compte. Estàs intentant accedir a %s. Vols unir-te per poder participar en la discussió\? una sala Això és una previsualització de la sala. Les interaccions de la sala estan desactivades. - Nou xat Afegeix un participant 1 participant - Marxa de la sala Estàs segur que vols marxar de la sala\? Estàs segur que vols eliminar %s d\'aquest xat\? @@ -570,7 +539,6 @@ \nN\'estàs segur\? Estàs segur que vols convidar a %s a aquest xat\? Si vetes un usuari, se l\'expulsarà d\'aquesta sala i no podrà tornar a unir-s\'hi. - Convida per ID CONTACTES LOCALS (%d) DIRECTORI D\'USUARI (%s) @@ -578,7 +546,6 @@ Convida un usuari per ID Introdueix un o més correus electrònics o identificadors de Matrix Correu electrònic o ID de Matrix - Cerca %s està escrivint… %1$s & %2$s estan escrivint… @@ -594,7 +561,6 @@ Elimina els missatges no enviats No s\'ha trobat el fitxer No tens permís per publicar en aquesta sala. - Confia No hi confiïs Tanca sessió @@ -606,7 +572,6 @@ El certificat ha canviat respecte aquell en el qual el telefon confia. Això NO ÉS GENS HABITUAL. Es recomana que NO ACCEPTEU el certificat nou. El certificat en el que confiàveu ha canviat per un en el que no confieu. El servidor pot haver renovat el certificat. Contacteu amb l\'administrador del servidor per saber l\'empremta digital esperada. Només accepteu el certificat si l\'administrador del servidor ha publicat una empremta digital que coincideixi amb l\'anterior. - Detalls de la sala Participants Fitxers @@ -614,14 +579,12 @@ L\'ID és incorrecte. Ha de ser una adreça de correu electrònic o un identificador de Matrix com \'@partlocal:domini\' CONVIDATS S\'HAN UNIT - Motiu per informar d\'aquest contingut Vols amagar tots els missatges d\'aquest usuari\? \n \nTingues en compte que aquesta acció reiniciarà l\'aplicació i pot trigar una estona. Cancel·la la pujada Cancel·la la baixada - Cerca Filtra els participants de la sala No hi ha resultats @@ -629,17 +592,14 @@ MISSATGES PARTICIPANTS FITXERS - Navega pel directori S\'està cercant al directori… - Preferit Treu prioritat Xat directe Marxa del xat Oblida Afegeix a la pantalla d\'inici - Missatges Configuració Versió @@ -647,7 +607,6 @@ Avisos de tercers Copyright Política de privacitat - Foto de perfil Àlies Correu electrònic @@ -699,7 +658,6 @@ Mostra sempre l\'hora a tots els missatges Mostra l\'hora en el format de 12 hores Vibra quan mencionin un usuari - Analítiques Mode d\'estalvi de dades Detalls de la sessió @@ -746,42 +704,33 @@ S\'ha produït un error mentre es validava el número de telèfon Codi Insígnia - Tres dies Una setmana Un mes Per sempre - - Foto de la sala Nom de la sala Tema Etiqueta de la sala Etiquetat com a: - Preferit Prioritat baixa Cap - Accés i visibilitat Mostra aquesta sala al directori de sales Accés a la sala Permisos de lectura de l\'històric de la sala Qui pot llegir l\'històric\? Qui pot accedir a la sala? - Qualsevol Només participants (a partir del moment en què es seleccioni aquesta opció) Només participants (des de que són convidats) Només participants (des de que s\'uneixen a la sala) - La sala ha de tenir una adreça per tal d\'unir-s\'hi. Només persones que hagin estat convidades Qualsevol que tingui l\'enllaç de la sala, a part dels convidats Qualsevol que tingui l\'enllaç de la sala, inclosos els convidats - Usuaris vetats - Avançat ID intern d\'aquesta sala Adreces @@ -792,7 +741,6 @@ Necessites tancar la sessió per poder activar el xifrat. Xifra només a sessions verificades No enviïs mai, des d\'aquesta sessió, missatges xifrats a sessions no verificades en aquesta sala. - Aquesta sala no té adreces locals Adreça nova (p.e. #foo:matrix.org) Aquesta sala no mostra insígnies per a cap comunitat @@ -811,12 +759,9 @@ El xifrat en aquesta sala està desactivat. Activa el xifrat \n(avís: no es podrà desactivar!) - Directori Tema - %s ha intentat carregar un moment concret de la cronologia d\'aquesta sala però no l\'ha trobat. - Informació de l\'encriptació d\'extrem a extrem Informació d\'esdeveniment ID d\'usuari @@ -860,23 +805,19 @@ Verifica comparant el següent amb la configuració d\'usuari de la teva altra sessió: Si no coincideixen pot ser que la seguretat de la comunicació estigui compromesa. Verifica que les claus coincideixen - La sala conté sessions desconegudes Aquesta sala conté sessions desconegudes que no han estat verificades. \nAixò vol dir que no hi ha garanties de que aquestes sessions pertanyin als usuaris que diuen ser. \nRecomanem que, abans de continuar, duguis a terme el procés de verificació de cadascuna de les sessions. Però, si ho prefereixes, pots reenviar el missatge sense la verificació. \n \nSessions desconegudes: - Tria un directori de sales És possible que el servidor no estigui disponible o que estigui sobrecarregat Introdueix un servidor base per veure les seves sales públiques URL del servidor base Totes les sales del servidor %s Totes les sales natives de %s - Busca a l\'historial - Mida de la font Molt petita Petita @@ -885,12 +826,10 @@ Molt gran Més gran Enorme - Necessites permisos per gestionar ginys en aquesta sala Ha fallat la creació del giny Fes conferències amb jitsi Confirmes que vols eliminar el giny d\'aquesta sala\? - No s\'ha pogut crear el giny. No s\'ha pogut enviar la sol·licitud. El nivell d\'autoritat ha de ser un enter positiu. @@ -901,30 +840,24 @@ La sala %s no és visible. Afegeix aplicacions de Matrix Utilitza la càmera nativa - Has afegit una nova sessió \'%s\' que està sol·licitant les claus de xifrat. La teva sessió no verificada \'%s\' està sol·licitant les claus de xifrat. Inicia la verificació Comparteix sense verificar Ignora la sol·licitut - Avís! Les conferències estan en desenvolupament i pot ser que no funcionin bé. - Error de comandament Ordre no reconegut: %s - Apagat Amb so Missatge xifrat - Crea Crea una comunitat Nom de la comunitat Exemple ID de la comunitat exemple - Inici Usuaris Sales @@ -995,7 +928,6 @@ %d giny actiu %d ginys actius - Envia un adhesiu Envia un adhesiu Llicències de tercers diff --git a/vector/src/main/res/values-de/strings.xml b/vector/src/main/res/values-de/strings.xml index e98ccf80ad..810781e442 100644 --- a/vector/src/main/res/values-de/strings.xml +++ b/vector/src/main/res/values-de/strings.xml @@ -40,23 +40,15 @@ %1$s hat die Einladung in %2$s akzeptiert ** Nicht entschlüsselbar: %s ** Das absendende Gerät hat uns keine Schlüssel für diese Nachricht übermittelt. - Entfernen nicht möglich Nachricht kann nicht gesendet werden Bild konnte nicht hochgeladen werden - Netzwerk-Fehler Matrix-Fehler - - - - Es ist aktuell nicht möglich, einen leeren Raum erneut zu betreten. - E-Mail-Adresse Telefonnummer %1$s hat einen Sticker gesendet. - Einladung von %s Raumeinladung %1$s und %2$s @@ -271,7 +263,6 @@ Du hast eine Videokonferenz beendet Du hast eine Videokonferenz gestartet Videokonferenz von %1$s gestartet - Nachrichten "Raum Einstellungen @@ -280,7 +271,6 @@ Akzeptiere Ablehnen Anruf beenden - Ok Abbrechen Speichern @@ -312,7 +302,6 @@ Trotzdem senden oder Einladen - Abmelden Sprachanruf Videoanruf @@ -324,30 +313,24 @@ Schließen In Zwischenablage kopiert Deaktivieren - Bestätigung Warnung - Home Favoriten Personen Räume - Raumnamen filtern Favoriten filtern Personen filtern Raumnamen filtern - Einladungen Niedrige Priorität - Konversationen Lokales Adressbuch Nur Matrix-Kontakte Keine Konversationen ${app_name} wurde nicht erlaubt, auf lokale Kontakte zuzugreifen Keine Ergebnisse - Räume Raumverzeichnis Keine Räume @@ -383,7 +366,6 @@ Videoanruf starten Dateien senden Foto oder Video aufnehmen - Anmelden Konto erstellen Absenden @@ -429,7 +411,6 @@ Dein Passwort wurde zurückgesetzt. \n \nDu wurdest aus allen Sitzungen abgemeldet und wirst keine Push-Benachrichtigungen mehr erhalten. Um die Push-Benachrichtigungen wieder zu aktivieren, musst du dich auf jedem Gerät erneut anmelden. - URL muss mit \'http[s]://\' beginnen Login unmöglich: Netzwerkfehler Login unmöglich @@ -444,28 +425,20 @@ Es wurden zu viele Anfragen gesendet Dieser Benutzername wird bereits verwendet Der Link in der E-Mail wurde noch nicht geöffnet - - Lesebestätigungsliste - - Sende als Original Groß Mittel Klein - Download abbrechen? Upload abbrechen? %d s %1$dm:%2$ds - Gestern Heute - Raumname Raumthema - Anruf verbunden Verbindungsaufbau… Anruf beendet @@ -478,10 +451,8 @@ Medien-Verbindung fehlgeschlagen Kann Kamera nicht initialisieren Anruf woanders entgegengenommen - Foto oder Video aufnehmen Video kann nicht aufgenommen werden - Information ${app_name} benötigt die Berechtigung, auf deine Fotos und Videos zugreifen zu können, um Anhänge zu senden und zu speichern.\n\nBitte erlaube den Zugriff im nächsten Dialog, um Dateien von deinem Gerät zu versenden. ${app_name} benötigt die Berechtigung, auf deine Kamera zugreifen zu können, um Bilder aufzunehmen und Video-Anrufe durchzuführen. @@ -500,31 +471,25 @@ \n \nStimmst du der Nutzung deines Adressbuchs zu diesem Zweck zu\? Entschuldige. Die Aktion wurde aufgrund fehlender Berechtigungen nicht ausgeführt - Gespeichert In Downloads speichern? Ja Nein Fortsetzen - Entfernen Betreten Vorschau Ablehnen - Zur ersten ungelesenen Nachricht - Du wurdest von %s in diesen Raum eingeladen Diese Einladung wurde an %s gesendet, welche nicht mit diesem Konto verknüpft ist. \nDu kannst dich mit einem anderen Konto anmelden oder diese E-Mail-Adresse zu diesem Konto hinzufügen. Du möchtest auf %s zugreifen. Möchtest du den Raum betreten, um an der Diskussion teilzunehmen? einen Raum Das ist die Vorschau des Raums. Interaktionen mit dem Raum sind deaktiviert. - Neuer Chat Mitglied hinzufügen 1 Mitglied - Raum verlassen Raum wirklich verlassen\? %s wirklich aus diesem Chat entfernen möchtest\? @@ -552,14 +517,12 @@ Du wirst diese Änderung nicht rückgängig machen können, da die Person dieselbe Berechtigungsstufe wie du erhalten wird. \nBist du sicher\? "Bist du sicher, dass du %s in diesen Chat einladen willst?" - Mit ID einladen LOKALE KONTAKTE (%d) Nur Matrix-Benutzer Benutzer per ID einladen Bitte gib eine oder mehrere E-Mail-Adressen oder eine Matrix-ID ein E-Mail oder Matrix-ID - Suchen %s schreibt… %1$s & %2$s schreiben… @@ -575,7 +538,6 @@ Nicht gesendete Nachrichten löschen Datei nicht gefunden Du bist nicht berechtigt, in diesen Raum zu schreiben. - Vertrauen Nicht vertrauen Abmelden @@ -587,7 +549,6 @@ Das Zertifikat unterscheidet sich von dem Zertifikat, dem dein Gerät ursprünglich vertraut hat. Dies ist SEHR UNGEWÖHNLICH. Es wird empfohlen, dass du dieses neue Zertifikat NICHT AKZEPTIERST. Das Zertifikat hat sich von einem ursprünglich vertrauenswürdigem Zertifikat in ein nicht vertrauenswürdiges Zertifikat geändert. Eventuell wurde das Zertifikat des Servers erneuert. Bitte erkundige dich beim Server-Administrator, welcher Fingerprint als vertrauenswürdig gilt. Akzeptiere das Zertifikat nur dann, wenn der Server-Administrator einen Fingerprint veröffentlicht hat, der mit dem obigen übereinstimmt. - Raumdetails Personen Dateien @@ -595,14 +556,12 @@ Ungültige ID. Eine E-Mail-Adresse oder eine Matrix-ID (\'@localpart:domain\') ist erforderlich EINGELADEN TEILNEHMER - Grund für das Melden dieses Inhalts Möchtest du alle Nachrichten dieses Nutzers verbergen\? \n \nBeachte: Diese Aktion wird die App neu starten und einige Zeit brauchen. Hochladen abbrechen Herunterladen abbrechen - Suchen Raummitglieder filtern Keine Suchergebnisse @@ -610,16 +569,13 @@ NACHRICHTEN PERSONEN DATEIEN - Verzeichnis durchsuchen Verzeichnis wird durchsucht… - Favorit Niedrige Priorität Direkter Chat Konversation verlassen Vergessen - Nachrichten Einstellungen Version @@ -627,7 +583,6 @@ Nutzungshinweise von Drittanbietern Urheberrechtserklärung Datenschutzerklärung - Profilbild Anzeigename E-Mail-Adresse @@ -655,7 +610,6 @@ Urheberrechtserklärung Datenschutzerklärung Cache leeren - Nutzereinstellungen Benachrichtigungen Ignorierte Benutzer @@ -711,37 +665,29 @@ Hier Aktivierungscode eintragen Fehler beim Verifizieren der Telefonnummer Code - - Raumbild Raumname Thema Raummarkierung Markiert als: - Favorit Niedrige Priorität Keine - Zugriff und Sichtbarkeit Diesen Raum im Raumverzeichnis anzeigen Raumzugriff Lesbarkeit des Chatverlaufs Wer kann den Chatverlauf lesen? Wer kann auf diesen Raum zugreifen? - Alle Nur Mitglieder Nur Mitglieder (ab Einladung) Nur Mitglieder (ab Beitreten) - Um einen Link zu einem Raum erstellen zu können, muss dieser eine Adresse haben. Nur eingeladene Personen Alle, die den Raumlink kennen (ausgenommen Gäste) Alle, die den Raumlink kennen (auch Gäste) - Verbannte Benutzer - Erweitert Interne ID dieses Raumes Adressen @@ -752,7 +698,6 @@ Du musst dich abmelden, um die Verschlüsselung aktivieren zu können. Nur für verifizierte Sitzungen verschlüsseln Niemals verschlüsselte Nachrichten an unverifizierte Sitzungen in diesem Raum von dieser Sitzung senden. - Dieser Raum hat keine lokalen Adressen Neue Adresse (z. B. #foo:matrix.org) Ungültiges Adressformat @@ -766,11 +711,8 @@ Verschlüsselung ist in diesem Raum aktiviert. Verschlüsselung ist in diesem Raum deaktiviert. Verschlüsselung aktivieren \n(Warnung: Kann nicht wieder deaktiviert werden!) - Verzeichnis - %s versuchte einen bestimmten Punkt in diesem Chatverlauf zu laden, konnte ihn aber nicht finden. - Ende-zu-Ende-Verschlüsselungs-Informationen Ereignisinformation Nutzer-ID @@ -814,21 +756,18 @@ Vergleiche die folgenden Zeichen mit den Einstellungen in der Sitzung des anderen Nutzers und bestätige: Falls sie nicht übereinstimmen, wurde die Kommunikation vielleicht kompromittiert. Ich bestätige, dass die Schlüssel übereinstimmen - Raum enthält unbekannte Sitzungen Dieser Raum enthält unbekannte Sitzungen, die noch nicht verifiziert wurden. \nEs gibt also keine Garantie, dass diese Sitzung wirklich der angegebenen Person gehören. \nWir empfehlen, den Verifizierungsprozess für jedes Gerät zu durchlaufen, bevor du fortfährst. Du kannst die Nachricht aber auch ohne Verifizierung senden, wenn du das vorziehst. \n \nUnbekannte Sitzungen: - Raumverzeichnis auswählen Der Server kann nicht verfügbar oder überlastet sein Gib einen Home-Server ein, um seine öffentlichen Räume aufzulisten Server-Name Alle Räume auf dem %s-Server Alle nativen %s-Räume - Suche nach historischen Bedienoberfläche Sprache @@ -859,7 +798,6 @@ Widget konnte nicht erstellt werden Konferenzgespräche mit Jitsi durchführen Soll das Widget wirklich aus diesem Raum gelöscht werden? - Widget konnte nicht erstellt werden. Berechtigungslevel muss eine positive ganze Zahl sein. Du bist nicht Mitglied in diesem Raum. @@ -886,17 +824,14 @@ Foto aufnehmen Video aufnehmen Systemeigene Kamera verwenden - Ohne Verifizierung teilen Anfrage ignorieren Anonymisierte Analysedaten Fehlerbericht Warnung! Konferenzgespräche werden noch entwickelt und sind vielleicht nicht zuverlässig. - Befehlsfehler Unbekanntes Kommando: %s - Aus Laut Verschlüsselte Nachricht @@ -930,7 +865,6 @@ Beispiel Community-ID Beispiel - Startseite Personen Räume @@ -988,7 +922,6 @@ %d aktives Widget %d aktive Widgets - Profilbild %d Mitgliedsänderung diff --git a/vector/src/main/res/values-es-rMX/strings.xml b/vector/src/main/res/values-es-rMX/strings.xml index 073cc098fe..01af66238c 100644 --- a/vector/src/main/res/values-es-rMX/strings.xml +++ b/vector/src/main/res/values-es-rMX/strings.xml @@ -45,32 +45,24 @@ ** No se puede descifrar: %s ** El dispositivo del remitente no nos ha enviado las claves de este mensaje. - No se pudo redactar No se puede enviar el mensaje La subida de la imagen falló - Error de la red Error de Matrix - - - - No es posible volver a unirse a una sala vacía. - Correo electrónico Número telefónico %1$s envió una calcomanía. - Invitación de %s Invitación de Sala %1$s y %2$s @@ -86,13 +78,11 @@ Mensaje eliminado por %1$s Mensaje eliminado [motivo: %1$s] Mensaje eliminado por %1$s [motivo: %2$s] - Mensajes Sala Configuraciones Detalles de los miembros Historial - OK Cancelar Guardar @@ -123,7 +113,6 @@ Enviar de todos modos o Invitar - Cerrar la sesión Llamada de Voz Llamada de Video @@ -135,30 +124,24 @@ Cerrar Copiado Desactivar - Confirmación Advertencia - Home Favoritos Personas Salas - Filtrar salas Filtrar favoritos Filtrar personas Filtrar salas - Invitaciones Prioridad baja - Conversaciones Libreta local de direcciones Solamente contactos Matrix Sin conversaciones No ha permitido a ${app_name} acceder a sus contactos locales Sin resultados - Salas Directorio de salas Sin salas @@ -194,7 +177,6 @@ Comenzar Llamada de video Enviar archivos Tomar foto o video - Iniciar sesión Crear cuenta Entregar @@ -238,7 +220,6 @@ Un correo fue enviado a %s. Una vez que sigas el enlace, seleccione abajo. Falló la verificación de correo electrónico: asegúrese de haber hecho clic en el correo Su contraseña fue actualizada.\n\nHaz sido desconectado de todos tus dispositivos y las notificaciones están apagadas. Para encender las notificaciones, entre su cuenta en cada dispositivo. - El enlace sólo puede comenzar con http[s]:// Sesión falló: Error con la red Sesión falló @@ -253,28 +234,20 @@ Demasiadas peticiones enviadas Este nombre de usuario ya está reservado para otra persona El enlace que no siguió - - Lista de Recibos de Lectura - - Enviar como Original Grande Mediano Chico - "¿Cancelar la descarga? ¿Cancelar la subida? %d s %1$dm %2$ds - Ayer Hoy - Nombre de la Sala Tema de la Sala - Llamada conectada La llamada se está conectando… Llamada finalizada @@ -287,10 +260,8 @@ Conexión de media falló La cámara no se pudo encender llamada fue contestada en otra parte - Tomar una foto o grabar un video" No pudo grabar video" - Información ${app_name} necesita tu permiso para entrar en tu almacenaje de fotos y videos para enviar y guardar archivos.\n\nPor favor permite el acceso en el siguiente mensaje para poder enviar archivos desde su dispostivo. ${app_name} necesita tu permiso para usar tu cámara para tomar fotos y hacer llamadas de video. @@ -303,30 +274,24 @@ ¿Permitir el acceso a ${app_name} para leer tus contactos ? Perdón. Operación no realizada debido a permisos faltantes - Guardado ¿Guardar en Descargas\? NO Continuar - Retirar Entrar Preestreno Rechazar - Saltar al primer mensaje no leído. - Has sido invitado por %s a entrar a esta sala Esta invitación fue enviado a %s, que no esta asociado con esta cuenta.\nTal vez desea entrar con otra cuenta, o añadir este correo electrónico a esta cuenta. Estás intentando entrar a %s. ¿Desea entrar para participar en la conversación? una sala Esto es un preestreno de esta sala. Los interacciones están impedidos. - Chat Nuevo Añadir miembro 1 miembro - Salir de la sala ¿Estás seguro que quieres salir de la sala? ¿Estás seguro que quieres quitar a %s de este chat? @@ -353,14 +318,12 @@ Mostrar lista de dispositivos No podrás deshacer este cambio porque estás provocando que el usuario tenga el mismo poder que el tuyo.\n¿Estás seguro? "¿Estás seguro que quieres invitar a %s que entre en este chat?" - Invitado por ID CONTACTOS LOCALES (%d) Solmente usuarios de Matrix Invitar usuario por ID Ingrese una o varias direcciones de correo o ID Matrix Correo o ID Matrix - Buscar %s está escribiendo… %1$s & %2$s están escribiendo… @@ -376,7 +339,6 @@ Borrar mensajes no enviados No se pudo encontrar el archivo No tienes permiso para enviar en esta sala - Confiar No confiar Salir @@ -388,7 +350,6 @@ El certificado cambio de el que confiaba tu dispositivo. Esto es MUY RARO. Se recomienda que no aceptes el certificado nuevo. El certificado cambio de uno que confiaba tu dispositivo a un certificado desconfiado. El servidor posiblemente actualizo su certificado. Pongase en contacto con el administrador de el servidor para la huella esperada. SOLAMENTE acepta el certificado de arriba si coincide con el certificado que publicó el administrador de el servidor. - Detalles de la Sala Personas Archivos @@ -396,13 +357,11 @@ Identificación malformada. Debe ser correo electrónico o identificación de Matrix como \'@localpart:domain\' INVITADO ENTRO - Razón por reportar este contenido ¿Desea ocultar todos los mensajes de este usuario\? \nConsidere que esta acción reiniciará la aplicación y tardar un tiempo. Cancelar Subida Cancelar Descarga - Buscar Buscar miembros No hay resultados @@ -410,16 +369,13 @@ MENSAJES PERSONAS ARCHIVOS - Buscar directorio Buscando directorio… - Destacar Despriorizar Chat Directo Salir de la Conversacion Forget - Mensajes Configuraciones Versión @@ -427,7 +383,6 @@ Avisos de partidos terceros Derechos de autor Políticas de privacidad - Foto de Perfil Nombre Visible Correo Electrónico @@ -455,7 +410,6 @@ Derechos de autor Políticas de privacidad Borrar memoria cache - Configuraciones de Usuario Notificaciones Usuarios ignorados @@ -508,37 +462,29 @@ Escribe un código de activación Error en la validación de tu número telefónico Código - - Foto de la Sala Nombre de la Sala Tema Etiqueta de la Sala Etiquetado como: - Destacar Baja prioridad Ninguno - Acceso y Visibilidad Inscribir esta sala en el directorio de salas Acceso a la Sala Historial y Legibilidad de la Sala ¿Quien puede leer el historial? ¿Quien puede entrar a la sala? - Todos Solamente miembros (desde seleccionar esta opción) Solamente miembros (desde su invitación) Solamente miembros (desde entrar a la sala) - Para hacer enlace a una sala debe tener una dirección. Solamente los invitados Los que saben el enlace de esta sala, excepto húespedes Los que saben el enlace de la sala, incluso húespedes - Usuarios prohibidos - Avanzado La identificación interna de esta sala Direcciones @@ -549,7 +495,6 @@ Necesitas salir de tu cuenta para encender el cifrado. Cifrar solamente para dispositivos verificados Nunca enviar mensajes cifrados a dispositivos no verificados en esta sala de este dispositivo. - Esta sala no tiene direcciones locales Dirección nueva (e.g #foo:matrix.org") Formato de alias no válido @@ -563,11 +508,8 @@ La cifración está encendida en esta sala. La cifración está apagada en esta sala. Encender cifración \n(¡Aviso: no se puede apagar!) - Directorio - %s estuvo intentando cargar un punto especifico en el historial de esta sala pero no lo pudo encontrar. - Información de la cifración de Persona-a-Persona Información de eventos Identificación de usuario @@ -609,21 +551,18 @@ Para verificar que este dispositivo es confiable, por favor contacte el dueño de este dispositivo por otro medio (e.g. cara-a-cara o por llamada) y verifique que la clave de este dispositivo que ellos ven en su página de configuraciones coincide con el de abajo: Si coinciden, seleccione Verificar. Si no, otro esta interceptando este dispositivo y vas a querer bloquearlo.\nEn el futuro, este procceso será mas sofisticado. Verifico que las claves coinciden - Esta sala contiene dispositivos desconocidos Esta sala contiene dispositivos desconocidos que no se han verificado. Esto significa que no se garantiza que los dispositivos pertenezcan a las personas que dicen de que son. Recomendamos que los verifiques todos antes de continuar. Sin embargo, puedes reenviar el mensaje sin verificarlos si prefieres. Dispositivos desconocidos: - Select a room directory The server may be unavailable or overloaded Type a homeserver to list public rooms from Homeserver URL All rooms on %s server All native %s rooms - Search for historical Sincronizando… Cargando… diff --git a/vector/src/main/res/values-es/strings.xml b/vector/src/main/res/values-es/strings.xml index 3a75e843d7..a7223134f9 100644 --- a/vector/src/main/res/values-es/strings.xml +++ b/vector/src/main/res/values-es/strings.xml @@ -40,23 +40,15 @@ %1$s aceptó la invitación para %2$s ** No es posible descifrar: %s ** El dispositivo emisor no nos ha enviado las claves para este mensaje. - No se pudo redactar No es posible enviar el mensaje No se pudo cargar la imagen - Error de red Error de Matrix - - - - Actualmente no es posible volver a unirse a una sala vacía. - Dirección de correo electrónico Número telefónico %1$s envió una pegatina. - Invitación de %s Invitación a Sala %1$s y %2$s @@ -270,13 +262,11 @@ Videoconferencia terminada por %1$s Has empezado una videoconferencia Videoconferencia empezada por %1$s - Mensajes Sala Ajustes Detalles de Miembro Histórico - Correcto Cancelar Guardar @@ -308,7 +298,6 @@ Enviar de Todos Modos o Invitar - Cerrar sesión Llamada de Voz Llamada de Vídeo @@ -320,30 +309,24 @@ Cerrar Copiado al portapapeles Deshabilitar - Confirmación Advertencia - Inicio Favoritos Personas Salas y Grupos - Filtrar salas Filtrar favoritos Filtrar personas Filtrar salas - Invitaciones Prioridad baja - Conversaciones Agenda de contactos local Solo contactos de Matrix No hay conversaciones No permitiste que ${app_name} acceda a tus contactos locales No hay resultados - Salas Directorio de salas No hay salas @@ -379,7 +362,6 @@ Iniciar Llamada de Vídeo Enviar archivos Tomar foto o vídeo - Iniciar sesión Crear cuenta Enviar @@ -427,7 +409,6 @@ Tu contraseña fue restablecida. \n \nSe ha cerrado sesión en todas tus sesiones y ya no recibirás notificaciones push. Para volver a habilitar las notificaciones, vuelve a iniciar sesión en cada dispositivo. - La URL debe comenzar con http[s]:// No es posible iniciar sesión: Error de red No es posible iniciar sesión @@ -442,28 +423,20 @@ Se enviaron demasiadas solicitudes Este nombre de usuario ya está en uso El enlace del correo electrónico que aún no se ha seguido - - Lista de Recibos de Lectura - - Enviar como Original Grande Mediano Pequeño - "¿Cancelar la descarga? ¿Cancelar la subida? %d s %1$dmin %2$dseg - Ayer Hoy - Nombre de la sala Tema de la sala - Llamada conectada Conectando llamada… Llamada finalizada @@ -476,10 +449,8 @@ Falló la Conexión de Medios No se puede iniciar la cámara llamada contestada en otra parte - Tomar una foto o un vídeo No se puede grabar vídeo - Información ${app_name} necesita permiso para acceder a tu biblioteca de fotos y vídeos para enviar y guardar archivos adjuntos. \n @@ -502,31 +473,25 @@ Por favor permite el acceso en la próxima ventana emergente para descubrir usua \n \n¿Permitir que ${app_name} acceda a tus contactos \? Lo sentimos. Acción no realizada, debido a que faltan permisos - Guardado ¿Guardar en descargas? NO Continuar - Eliminar Unirse Vista Previa Rechazar - Mensajes no leídos. - Has sido invitado por %s a unirte a esta sala Esta invitación fue enviada a %s, que no esta asociado a esta cuenta. \nQuizás quieras iniciar sesión con otra cuenta, o añadir este correo electrónico a esta cuenta. Estás intentando acceder a %s. ¿Quieres unirte para participar en la discusión? una sala Esta es una vista previa de esta sala. Las interacciones dentro de la sala se han deshabilitado. - Nueva Conversación Añadir miembro 1 miembro - Salir de la sala ¿Seguro que quieres salir de la sala? ¿Seguro que quieres eliminar a %s de esta conversación? @@ -554,14 +519,12 @@ Por favor permite el acceso en la próxima ventana emergente para descubrir usua No podrás deshacer este cambio porque estás ascendiendo al usuario al mismo nivel de autoridad que tú. \n¿Estás seguro\? ¿Seguro que quieres invitar a %s a esta conversación? - Invitar por ID CONTACTOS LOCALES (%d) Solo usuarios de Matrix Invitar usuario por ID Por favor, ingresa una o más direcciones de correo electrónico o ID de Matrix Correo electrónico o ID de Matrix - Buscar %s está escribiendo… %1$s y %2$s están escribiendo… @@ -577,7 +540,6 @@ Por favor permite el acceso en la próxima ventana emergente para descubrir usua Eliminar mensajes no enviados Archivo no encontrado No tienes permiso para publicar en esta sala - Confiar No confiar Cerrar Sesión @@ -589,7 +551,6 @@ Por favor permite el acceso en la próxima ventana emergente para descubrir usua El certificado cambió de uno que era confiable para tu teléfono. Esto es MUY INUSUAL. Se recomienda NO ACEPTAR este nuevo certificado. El certificado cambió de uno que era confiable a uno que no es confiable. El servidor puede haber renovado su certificado. Contacta al administrador del servidor para obtener la huella digital. Solo acepta el certificado si el administrador del servidor ha publicado una huella digital que coincide con la anterior. - Detalles de Sala Personas Archivos @@ -597,14 +558,12 @@ Por favor permite el acceso en la próxima ventana emergente para descubrir usua ID mal formada. Debería ser una dirección de correo electrónico o una ID de Matrix como \'@partelocal:dominio\' INVITADOS SE UNIERON - Motivo para reportar este contenido ¿Quieres ocultar todos los mensajes de este usuario\? \n \nTen en cuenta que esta acción reiniciará la aplicación y puede tardar algo de tiempo. Cancelar Subida Cancelar Descarga - Buscar Filtrar miembros de la sala No hay resultados @@ -612,16 +571,13 @@ Por favor permite el acceso en la próxima ventana emergente para descubrir usua MENSAJES PERSONAS ARCHIVOS - Explorar directorio Buscando directorio… - Agregar a Favoritos Dejar de priorizar Conversación Directa Salir de la Conversación Olvidar - Mensajes Ajustes Versión @@ -629,7 +585,6 @@ Por favor permite el acceso en la próxima ventana emergente para descubrir usua Avisos de terceros Derechos de autor Política de privacidad - Imagen de Perfil Nombre Público Correo Electrónico @@ -657,7 +612,6 @@ Por favor permite el acceso en la próxima ventana emergente para descubrir usua Derechos de autor Política de privacidad Borrar caché - Ajustes de usuario Notificaciones Usuarios ignorados @@ -713,37 +667,29 @@ Por favor permite el acceso en la próxima ventana emergente para descubrir usua Ingresa un código de activación Error en la validación de tu número telefónico Código - - Imagen de Sala Nombre de Sala Tema Etiqueta de Sala Etiquetado como: - Agregar a Favoritos Prioridad baja Ninguno - Acceso y visibilidad Listar esta sala en el directorio de salas Acceso a Sala Legibilidad del Historial de la Sala ¿Quién puede leer el historial? ¿Quién puede acceder a esta sala? - Todos Solo miembros (desde el momento en que se selecciona esta opción) Solo miembros (desde que fueron invitados) Solo miembros (desde que se unieron) - Para crear un enlace a una sala, debe tener una dirección. Solo personas que han sido invitadas Cualquier persona que conozca el enlace a esta sala, excepto invitados Cualquier persona que conozca el enlace a esta sala, incluyendo invitados - Usuarios vetados - Avanzado La ID interna de esta sala Direcciones @@ -754,7 +700,6 @@ Por favor permite el acceso en la próxima ventana emergente para descubrir usua Necesitas cerrar sesión para poder habilitar el cifrado. Cifrar solo a sesiones verificadas Nunca enviar mensajes cifrados a sesiones sin verificar en esta sala desde esta sesión. - Esta sala no tiene direcciones locales Dirección nueva (ej. #foo:matrix.org) Formato de alias inválido @@ -769,11 +714,8 @@ Por favor permite el acceso en la próxima ventana emergente para descubrir usua El cifrado está deshabilitado en esta sala. Habilitar cifrado \n(advertencia: ¡no se puede volver a deshabilitar!) - Directorio - %s estaba intentando cargar un momento específico en la línea de tiempo de esta sala pero no pudo encontrarlo. - Información de cifrado Extremo-a-Extremo Información de eventos ID de Usuario @@ -817,21 +759,18 @@ Por favor permite el acceso en la próxima ventana emergente para descubrir usua Para verificar que esta sesión es confiable, por favor contacta a su dueño por algún otro medio (ej. cara a cara o por teléfono) y pregúntale si la clave que ve en sus Ajustes de Usuario para esta sesión coincide con la clave a continuación: Si coincide, presione el botón de verificar a continuación. Si no coincide, entonces alguien está interceptando esta sesión y probablemente debería prohibirlo. En el futuro, este proceso de verificación será más sofisticado. Verifico que las claves coinciden - La sala contiene sesiones desconocidas Esta sala contiene sesiones desconocidas que no han sido verificadas. \nEsto significa que no hay garantía de que las sesiones pertenezcan a los usuarios a los que dicen pertenecer. \nRecomendamos que hagas el proceso de verificación por cada sesión antes de continuar. Pero puedes reenviar el mensaje sin verificarlas si prefieres. \n \nSesiones desconocidas: - Selecciona un directorio de salas El servidor puede estar no disponible o sobrecargado Escribe un servidor doméstico desde donde listar las salas públicas Nombre del servidor Todas las salas en el servidor %s Todas las salas nativas de %s - Buscar en el historial Interfaz de usuario Idioma @@ -877,7 +816,6 @@ Por favor permite el acceso en la próxima ventana emergente para descubrir usua La creación del componente falló Crear llamadas de conferencia con jitsi ¿Seguro que quieres eliminar el widget de esta sala\? - No es posible crear el componente. El envío de la solicitud falló. El nivel de autoridad debe ser un número entero positivo. @@ -888,19 +826,15 @@ Por favor permite el acceso en la próxima ventana emergente para descubrir usua La sala %s no está visible. Añadir aplicaciones de Matrix Utilizar cámara nativa - Has añadido una nueva sesión \'%s\', que está solicitando claves de cifrado. Tu sesión sin verificar \'%s\' está solicitando claves de cifrado. Iniciar verificación Compartir sin verificar Ignorar solicitud - ¡Advertencia! Las llamadas de conferencia están en desarrollo y pueden no ser confiables. - Error de comando Comando no reconocido: %s - Desactivado Ruidoso Mensaje cifrado @@ -938,7 +872,6 @@ Por favor permite el acceso en la próxima ventana emergente para descubrir usua Ejemplo ID de Comunidad ejemplo - Inicio Personas Salas @@ -998,7 +931,6 @@ Por favor permite el acceso en la próxima ventana emergente para descubrir usua %d componente activo %d componentes activos - Enviar una pegatina Actualmente no tienes ningún paquete de stickers habilitado. \n diff --git a/vector/src/main/res/values-eu/strings.xml b/vector/src/main/res/values-eu/strings.xml index e616b30afd..187ea5f5d7 100644 --- a/vector/src/main/res/values-eu/strings.xml +++ b/vector/src/main/res/values-eu/strings.xml @@ -772,7 +772,6 @@ Abisua: Fitxategi hau ezabatu daiteke aplikazioa desinstalatzen bada. Sortu konferentzia deiak Jitsi bidez Ziur trepeta ezabatu nahi duzula gela honetatik? - Ezin izan da trepeta sortu. Eskariaren bidalketak huts egin du. Botere maila osoko zenbaki positibo bat izan behar da. @@ -791,22 +790,18 @@ Abisua: Fitxategi hau ezabatu daiteke aplikazioa desinstalatzen bada. Erabili kamera natiboa - \'%s\' saio berria gehitu duzu, eta zifratze-gakoak eskatzen ari da. Egiaztatu gabeko zure \'%s\' saioa zifratze-gakoak eskatzen ari da. Hasi egiaztaketa Partekatu egiaztatu gabe Ezikusi eskaria - Abisua! Konferentzia deiak garapenean daude eta agian ez dabiltza behar bezala. - Komandoaren errorea Komando ezezaguna: %s - Ez Zaratatsua @@ -861,7 +856,6 @@ Abisua: Fitxategi hau ezabatu daiteke aplikazioa desinstalatzen bada. Komunitatearen IDa adibidea - Hasiera Jendea Gelak @@ -934,7 +928,6 @@ Abisua: Fitxategi hau ezabatu daiteke aplikazioa desinstalatzen bada. %d trepeta aktibo - Hartzailearen abatarra Jakinarazpen abatarra Abatarra diff --git a/vector/src/main/res/values-fi/strings.xml b/vector/src/main/res/values-fi/strings.xml index eaf548f3f0..af21bcc933 100644 --- a/vector/src/main/res/values-fi/strings.xml +++ b/vector/src/main/res/values-fi/strings.xml @@ -48,9 +48,7 @@ Puhelinnumero Takaisinveto epäonnistui %1$s: %2$s - Kutsu käyttäjältä %s - Huonekutsu %1$s ja %2$s Tyhjä huone @@ -208,13 +206,11 @@ %1$s loi huoneen Lähetit tarran. Lähetit kuvan. - Viestit Huone Asetukset Jäsenen tiedot Historiallinen - OK Peruuta Tallenna @@ -222,7 +218,6 @@ Lähetä Lähetä uudelleen Poista - Lainaa Jaa Myöhemmin @@ -230,8 +225,6 @@ Pysyvä linkki Lähdekoodi Näytä salaamaton lähde - - Poista Nimeä uudelleen Ilmoita epäilyttävästä sisällöstä @@ -249,7 +242,6 @@ Lähetä silti tai Kutsu - Kirjaudu ulos Äänipuhelu Videopuhelu @@ -261,30 +253,24 @@ Sulje Kopioitu leikepöydälle Poista käytöstä - Vahvistus Varoitus - Koti Suosikit Ihmiset Huoneet - Suodata huoneista Suodata suosikeista Suodata henkilöistä Suodata huoneista - Kutsut Matala prioriteetti - Keskustelut Paikalliset yhteystiedot Ainoastaan Matrix-yhteyshenkilöt Ei keskusteluita Et ole sallinut ${app_name}ille pääsyä paikallisiin yhteystietoihisi Ei tuloksia - Huoneet Huoneluettelo Ei huoneita @@ -314,17 +300,12 @@ Kirjaudu ulos Kotipalvelimen URL-osoite Identiteettipalvelimen URL-osoite - Etsi - - Aloita uusi keskustelu Aloita puhelu Aloita videopuhelu - Lähetä tiedostoja Ota kuva tai video - Kirjaudu sisään Luo tili Lähetä @@ -361,23 +342,19 @@ Käyttäjätunnus on jo käytössä Kotipalvelin: Identiteettipalvelin: - Olen varmistanut sähköpostiosoitteeni Palauttaaksesi salasanasi, anna tiliisi liitetty sähköpostiosoite: Anna tiliisi liitetty sähköpostiosoite. Anna uusi salasana. Osoitteeseen %s on lähetetty sähköposti. Kun olet avannut siinä olevan linkin, paina alla olevaa nappia. - Sähköpostiosoitteesi vahvistaminen epäonnistui. Varmista, että klikkasit sähköpostissa olevaa linkkiä Salasanasi on vaihdettu.\n\nSinut on kirjauduttu ulos kaikista laitteistasi, etkä enää saa viesti-ilmoituksia. Ottaaksesi käyttöön ilmoitukset uudelleen, kirjaudu sisään uudelleen kaikilla laitteillasi. - URL-osoitteen on alettava seuraavasti: http[s]:// Kirjautuminen epäonnistui: Verkkovirhe Kirjautuminen epäonnistui Rekisteröityminen epäonnistui: Verkkovirhe Rekisteröityminen epäonnistui Rekisteröityminen epäonnistui: sähköpostin varmistaminen epäonnistui - Syötäthän toimivan osoitteen Väärä käyttäjätunnus tai salasana Annettua tunnistetta ei hyväksytty @@ -386,29 +363,20 @@ Liian monta pyyntöä Käyttäjänimi on jo käytössä Sähköpostiisi lähetetty linkki, jota ei ole vielä klikattu - - - Lukukuittaukset - - Valitse koko Alkuperäinen Iso Keskikokoinen Pieni - Peru lataus? Peru lähetys? %d s %1$d min %2$d s - Eilen Tänään - Huoneen nimi Huoneen aihe - Yhdistetty Yhdistetään… Puhelu loppui @@ -418,17 +386,11 @@ Saapuva puhelu Puhelu käynnissä… Toinen puoli ei vastannut. - Mediayhteys epäonnistui Kameran alustus epäonnistui puheluun vastattiin muualta - - Ota kuva tai video" - Videointi epäonnistui - - Huomio ${app_name} tarvitsee käyttöluvan mediagalleriaasi lähettäkseen liitteitä.\n\nSalli tiedostojen käyttö seuraavalla näytöllä liittääksesi kuvia ja muita tiedostoja viesteihin. ${app_name} tarvitsee käyttöluvan kameraan ottaakseen kuvia ja suorittakseen videopuheluita. @@ -447,32 +409,25 @@ \n \nSaako ${app_name} käyttää yhteystietojasi tätä varten\? Toimenpide epäonnistui puuttuvien käyttölupien takia - Tallennettu Tallenna latauskansioon\? KYLLÄ EI Jatka - Poista Liity Esikatsele Hylkää - Siirry ensimmäiseen lukemattomaan viestiin. - %s on kutsunut sinut huoneeseen Tämä kutsu lähetettiin osoitteeseen %s, jota ei ole liitetty tiliisi. \nVoit kirjautua sisään toisella tilillä tai lisätä tämän sähköpostiosoitteen tiliisi. Olet avaamassa huonetta %s. Haluatko liittyä huoneeseen osallistuaksesi keskusteluun? huone - Tämä on huoneen esikatselu. Liity huoneeseen osallistuaksesi keskusteluun. - Uusi keskustelu Lisää jäsen yksi jäsen - Poistu huoneesta Haluatko varmasti poistua huoneesta? Haluatko varmasti poistaa käyttäjän %s tästä keskustelusta\? @@ -499,19 +454,16 @@ Näytä istuntolista Olet ylentämässä käyttäjää samalle tasolle kuin oma käyttäjätasosi. Et voi perua tätä toimintoa.\nOletko varma? Haluatko kutsua käyttäjän %s tähän keskusteluun\? - Kutsu tunnisteella PAIKALLISET YHTEYSTIEDOT (%d) Vain Matrix-käyttäjät Kutsu käyttäjä tunnisteella Syötä yksi tai useampi sähköpostiosoite tai Matrix-tunniste Sähköposti tai Matrix-tunniste - Etsi %s kirjoittaa… %1$s ja %2$s kirjoittavat… %1$s, %2$s ja muita kirjoittaa… - Lähetä salattu viesti… Lähetä viesti (salaamaton)… Yhteys palvelimeen katkesi. @@ -523,21 +475,17 @@ Poista lähettämättömät viestit Tiedostoa ei löydy Sinulla ei ole oikeutta lähettää viestejä tähän huoneeseen. - Luota Älä luota Kirjaudu ulos Jätä huomiotta - Sormenjälki (%s): Palvelimen identiteettiä ei voitu vahvistaa. - Tämä voi tarkoittaa että joku yrittää kaapata sinun viestintääsi tai että laitteesi ei luota palvelimen varmenteeseen. Jos palvelimen ylläpitäjä on ilmoittanut, että tämä on odotettua, varmista että alla oleva sormenjälki on sama kuin hänen antamansa. Sertifikaatti johon laitteesi luotti aikaisemmin on vaihtunut. Tämä on HYVIN EPÄTAVALLISTA. On suositeltavaa, että ET hyväksy tätä uutta sertifikaattia. Sertifikaatti on vaihtunut ennestään luotetusta ei-luotettuun. Palvelin on voinut uusia sertifikaattinsa. Kysy palvelimen ylläpitäjältä, mikä sormenjäljen pitäisi olla. Hyväksy sertifikaatti vain, jos palvelimen ylläpitäjä on julkaissut sormenjäljen, joka täsmää yllä olevan kanssa. - Huoneen tiedot Henkilöt Tiedostot @@ -545,14 +493,12 @@ Epämuotoinen tunnus. Anna sähköpostiosoite tai Matrix-tunnus (esim. \'@tunnus:verkkotunnus\') KUTSUTUT JÄSENET - Syy sisällön ilmoittamiseen Haluatko piilottaa kaikki tämän käyttäjän viestit\? \n \nHuomaa, että tämä toiminto käynnistää sovelluksen uudelleen ja siinä saattaa kestää jonkin aikaa. Peru lähetys Peru lataus - Etsi Etsi huoneen jäsenistä Ei tuloksia @@ -560,16 +506,13 @@ VIESTIT HENKILÖT TIEDOSTOT - Selaa luetteloa Haetaan luettelosta… - Suosikki Matala tärkeys Yksityiskeskustelu Poistu keskustelusta Unohda - Viestit Asetukset Versio @@ -577,7 +520,6 @@ Kolmannen osapuolen tiedot Tekijänoikeustiedot Tietosuojakäytäntö - Profiilikuva Nimi Sähköposti @@ -605,7 +547,6 @@ Tekijänoikeus Tietosuojakäytäntö Tyhjennä välimuisti - Käyttäjäasetukset Ilmoitukset Piilotetut henkilöt @@ -634,7 +575,6 @@ Kirjautuneena nimellä Kotipalvelin Identiteettipalvelin - Odotetaan vahvistusta Tarkista sähköpostisi ja klikkaa sinne saamaasi linkkiä. Kun olet tehnyt tämän, paina jatka. Sähköpostin vahvistaminen epäonnistui. Tarkista sähköpostisi ja avaa lähettämässämme viestissä oleva linkki. Tämän jälkeen paina painiketta ”jatka”. @@ -652,7 +592,6 @@ \nHuomaa, että tämä toiminto käynnistää sovelluksen uudelleen ja siinä saattaa kestää jonkin aikaa. Haluatko poistaa tämän ilmoituskohteen? Haluatko varmasti poistaa kohteen %1$s %2$s\? - Valitse maa Maa Valitse maa @@ -663,37 +602,29 @@ Anna aktivointikoodi Puhelinnumeron validointi epäonnistui Aktivointikoodi - - Huoneen kuva Huoneen nimi Aihe Huoneen luokittelu Luokiteltu: - Suosikki Matala tärkeys Ei mikään - Näkyvyys ja pääsy Listaa tämä huone huoneluettelossa Huoneen pääsy Huoneen historian näkyvyys Ketkä saavat nähdä huoneen historian\? Ketkä pääsevät tähän huoneeseen\? - Kuka tahansa Vain jäsenet (tämän asetuksen valitsemisesta alkaen) Vain jäsenet (heidän kutsumisestaan alkaen) Vain jäsenet (heidän liittymisestään alkaen) - Linkittääksesi huoneeseen, sillä pitää olla osoite. Vain kutsutut Kaikki, jotka tietävät huoneen osoitteen (paitsi vieraat) Kaikki jotka tietävät huoneen osoitteen, mukaanlukien vieraat - Porttikiellon saaneet käyttäjät - Lisäasetukset Tämän huoneen sisäinen ID Osoitteet @@ -704,7 +635,6 @@ Kirjaudu ulos salauksen aktivoimiseksi. Lähetä salatut viestit vain vahvistetuille laitteille Älä lähetä tältä laitteelta salattuja viestejä tämän huoneen vahvistamattomille laitteille. - Huoneella ei ole paikallisia osoitteita Uusi osoite (esim. #foo:matrix.org") Virheellinen aliaksen muoto @@ -719,14 +649,9 @@ Tämä huone ei käytä salausta. Ota salaus käyttöön \n(varoitus: salausta ei voi poistaa käytöstä!) - Luettelo - %s yritti ladata tietyn kohdan huoneen historiassa, mutta sitä ei löytynyt. - - Päästä päähän -salauksen lisätiedot - Tapahtuman tiedot Käyttäjän Matrix-ID Curve25519-ID-avain @@ -767,20 +692,16 @@ Poista kielto Vahvista laite Vahvistaaksesi, että tähän laitteeseen voi luottaa, ota yhteyttä sen omistajaan jollain muulla tavalla (esimerkiksi soittamalla tai tapaamalla) ja varmista että hänen laitteensa avain on sama kuin alla oleva: - Jos avaimet eivät täsmää, keskustelusi eivät luultavasti ole turvassa. Vahvistan, että avaimet täsmäävät - Huoneessa on tuntemattomia istuntoja Huoneessa on tuntemattomia laitteita joita ei ole vahvistettu.\nLaitteet eivät välttämättä kuulu väitetyille omistajilleen.\nJokainen uusi laite kannattaa vahvistaa ennen kuin jatkat, mutta voit myös lähettää viestit vahvistamattomille laitteille.\n\nTuntemattomat laitteet: - Valitse huoneluettelo Palvelin saattaa olla tavoittamattomissa tai ylikuormitettu Syötä kotipalvelin, jolta julkiset huoneet listataan Palvelimen nimi Kaikki huoneet palvelimella %s Kaikki alkuperäiset %s huoneet - Etsi historiasta Käyttäjäluettelo KÄYTTÄJÄHAKEMISTO (%s) @@ -826,7 +747,6 @@ Pienoissovelluksen luonti epäonnistui Luo konferenssipuheluita jitsin avulla Haluatko varmasti poistaa pienoissovelluksen tästä huoneesta\? - Sovelmaa ei voitu luoda. Pyynnön lähetys epäonnistui. Oikeustason täytyy olla positiivinen luku. @@ -837,19 +757,15 @@ Huone %s ei ole näkyvillä. Lisää integraatioita Käytä järjestelmän kamerasovellusta - Lisäsit uuden istunnon \'%s\', joka pyytää salausavaimia. Vahvistamaton laitteesi \'%s\' pyytää salausavaimia. Aloita varmennus Jaa ilman varmennusta Hylkää pyyntö - Varoitus! Konferenssipuhelut ovat kehitysvaiheessa eivätkä välttämättä luotettavia. - Komentovirhe Tuntematon komento: %s - Pois Äänekäs Salattu viesti @@ -884,7 +800,6 @@ Esimerkki Yhteisön ID esimerkki - Koti Ihmiset Huoneet diff --git a/vector/src/main/res/values-fr/strings.xml b/vector/src/main/res/values-fr/strings.xml index c14b75d2fd..effd708d17 100644 --- a/vector/src/main/res/values-fr/strings.xml +++ b/vector/src/main/res/values-fr/strings.xml @@ -778,7 +778,6 @@ La création du widget a échoué Créer des appels en téléconférence avec jitsi Voulez-vous vraiment supprimer le widget de ce salon ? - Impossible de créer le widget. Échec de l’envoi de la requête. Le rang doit être un entier positif. @@ -802,14 +801,11 @@ Prendre une vidéo Statistiques d’utilisation Utiliser la caméra de l’appareil - Rapport d’anomalie Attention ! L’appel en téléconférence est en cours de développement et peut ne pas être fiable. - Erreur de commande Commande non reconnue : %s - Désactivé Notification sonore Message chiffré @@ -844,7 +840,6 @@ Exemple Identifiant de communauté Exemple - Accueil Personnes Salons @@ -902,7 +897,6 @@ %d widgets actif %d widgets actifs - Avatar %d changement de statut diff --git a/vector/src/main/res/values-hu/strings.xml b/vector/src/main/res/values-hu/strings.xml index 0691860a2d..60022ee275 100644 --- a/vector/src/main/res/values-hu/strings.xml +++ b/vector/src/main/res/values-hu/strings.xml @@ -734,7 +734,6 @@ Figyelmeztetés: ez a fájl törlésre kerülhet, ha az alkalmazást törli.Kisalkalmazás létrehozása sikertelen Hozz létre konferencia hívást a jitsi segítségével Biztos vagy benne hogy törölni akarod ezt a kisalkalmazást ebből a szobából? - Kisalkalmazás létrehozása sikertelen. Felkérés elküldése sikertelen. Az erősségi szintnek egy pozitív egész számnak kell lennie. @@ -760,14 +759,11 @@ Figyelmeztetés: ez a fájl törlésre kerülhet, ha az alkalmazást törli.Videofelvétel Elemzés Beépített kamera használata - Hibajelentés Figyelmeztetés! A konferenciabeszélgetés fejlesztés alatt van, elképzelhető, hogy nem működik még megfelelően. - Utasítás hiba Ismeretlen utasítás: %s - Ki Hangos Titkosított üzenet @@ -800,7 +796,6 @@ Figyelmeztetés: ez a fájl törlésre kerülhet, ha az alkalmazást törli.Példa Közösség azonosító példa - Szobák Emberek Nincsenek felhasználók @@ -865,7 +860,6 @@ Figyelmeztetés: ez a fájl törlésre kerülhet, ha az alkalmazást törli.%d aktív kisalkalmazás %d aktív kisalkalmazás - Profilkép Címzett profilképe Észlelési profilkép diff --git a/vector/src/main/res/values-is/strings.xml b/vector/src/main/res/values-is/strings.xml index 1a42790035..cabf9d133b 100644 --- a/vector/src/main/res/values-is/strings.xml +++ b/vector/src/main/res/values-is/strings.xml @@ -392,22 +392,18 @@ %d virkur viðmótshluti %d virkir viðmótshlutar - Þú ert ekki á þessari spjallrás. Þú hefur ekki réttindi til þess að gera þetta á þessari spjallrás. Spjallrásin %s er ekki sýnileg. Aðvörun! Skipanavilla Óþekkt skipun: %s - Slökkt Hávært Dulrituð skilaboð - Búa til Dæmi dæmi - Heim Fólk Spjallrásir @@ -455,7 +451,6 @@ samtali svarað annars staðar Boð um samtal Nota innbyggða myndavél - Búa til samfélag Heiti samfélags Auðkenni samfélags @@ -492,7 +487,6 @@ Hefja sannvottun Deila án sannvottunar Hunsa beiðni - Þér hefur verið sparkað úr %1$s af %2$s Þú hefur verið settur í bann á %1$s af %2$s Tilraunir @@ -670,7 +664,6 @@ Þú bættir við nýju tæki \'%s\', sem er að krefjast dulritunarlykla. ósannvottaða tækið þitt \'%s\' er að krefjast dulritunarlykla. Símafundir eru í þróun og gætu verið óáreiðanlegir. - Kerfisstjóri samfélagsins hefur ekki gefið upp ítarlega lýsingu fyrir þetta samfélag. Skrifaðu heimanetþjón til að telja upp opinberar spjallrásir á Vantar spjallrásarauðkenni í beiðni. diff --git a/vector/src/main/res/values-it/strings.xml b/vector/src/main/res/values-it/strings.xml index 14e73d1757..e3efb18aa4 100644 --- a/vector/src/main/res/values-it/strings.xml +++ b/vector/src/main/res/values-it/strings.xml @@ -49,7 +49,6 @@ Indirizzo email Numero di telefono %1$s ha inviato uno sticker. - Invito da %s Invito nella stanza %1$s e %2$s @@ -264,13 +263,11 @@ Hai terminato la videoconferenza Videoconferenza terminata da %1$s Videoconferenza iniziata da %1$s - Messaggi Stanza Impostazioni Dettagli sui membri Cronologia - OK Annulla Salva @@ -303,7 +300,6 @@ o Invita Disconnesso - Disconnetti Chiamata audio Chiamata video @@ -315,23 +311,18 @@ Chiudi Copiato negli appunti Disabilita - Conferma Attenzione - Home Preferiti Chat dirette Stanze - Cerca Cerca tra i preferiti Cerca tra le chat dirette Cerca tra le stanze - Inviti Bassa priorità - Conversazioni Rubrica locale Elenco utenti @@ -339,7 +330,6 @@ Nessuna conversazione ${app_name} non ha avuto l\'autorizzazione ad accedere alla tua Rubrica locale Nessun risultato - Stanze Elenco stanze Nessuna stanza @@ -378,7 +368,6 @@ Accetta Rifiuta Riaggancia - Accedi Crea account Invia @@ -426,7 +415,6 @@ La tua password è stata reimpostata. \n \nSei stato disconnesso da tutte le sessioni e non riceverai più alcuna notifica. Per ripristinare le notifiche, riconnettiti su ciascun dispositivo. - L\'URL deve iniziare con http[s]:// Impossibile accedere: errore di rete Impossibile accedere @@ -441,28 +429,20 @@ Sono state inviate troppe richieste Questo nome utente è già in uso Il link nella mail non è ancora stato cliccato - - Elenco ricevute lette - - Invia come Originario Grande Medio Piccolo - "Annullare il download? Annullare l\'upload? %d s %1$dm %2$ds - Ieri Oggi - Nome stanza Argomento stanza - Chiamata in corso Chiamata in connessione… Chiamata terminata @@ -475,10 +455,8 @@ La connessione al supporto multimediale è fallita Impossibile avviare la fotocamera chiamata risposta altrove - Fai una foto o un video Impossibile registrare video - Informazione ${app_name} deve essere autorizzato ad accedere alla tua Galleria di foto e video per poter inviare e salvare allegati. \n @@ -499,31 +477,25 @@ \n \nTi sta bene comunicare i dati di tutti i tuoi contatti per questo scopo\? Purtroppo l\'azione non è stata eseguita a causa di autorizzazioni mancanti - Salvato Salvare nei download? NO Continua - Rimuovi Entra Anteprima Rifiuta - Vai ai non letti - Sei stato invitato da %s a entrare in questa stanza Questo invito è stato spedito a %s, che non è associato a questo account. \nPuoi aggiungere questa email al tuo account o provare ad accedere con un account differente. Stai provando ad accedere a %s. Vuoi entrare e partecipare alla discussione\? una stanza Questa è l\'anteprima della stanza. Le interazioni sono disabilitate. - Nuova chat Aggiungi utente 1 utente - Esci dalla stanza Sei sicuro di voler uscire dalla stanza? Sei sicuro di voler rimuovere %s da questa chat? @@ -551,7 +523,6 @@ La nomina non potrà essere annullata perché darai a questo utente i tuoi stessi poteri. \nSicuro di voler procedere\? Sicuro di voler invitare %s\? - Invita tramite ID utente CONTATTI LOCALI (%d) ELENCO UTENTI (%s) @@ -559,7 +530,6 @@ Invita tramite ID utente Per favore inserisci uno o più indirizzi email o ID utente Email o ID utente - Cerca %s sta scrivendo… %1$s & %2$s stanno scrivendo… @@ -575,7 +545,6 @@ Elimina i messaggi non inviati File non trovato Non hai il permesso di pubblicare in questa stanza. - Fidati Non fidarti Disconnetti @@ -587,7 +556,6 @@ Il certificato è diverso da quello precedentemente contrassegnato sul tuo telefono come \"affidabile\". Questa cosa è MOLTO INSOLITA. Si raccomanda di NON ACCETTARE questo nuovo certificato. Il certificato del server è cambiato: quello precedente era stato contrassegnato come affidabile ma quello attuale no. Può darsi che il certificato precedente sia scaduto e sia stato semplicemente sostituito con uno nuovo. Contatta l\'amministratore del server per verificare l\'impronta digitale in uso. Contrassegna il certificato come affidabile solo se l\'mpronta digitale comunicata dall\'amministratore del server corrisponde a quella qua sopra. - Dettagli stanza Utenti File @@ -595,14 +563,12 @@ ID malformato. Dovrebbe essere un indirizzo email o un ID utente Matrix come \'@localpart:domain\' INVITATI MEMBRI - Motivo per cui segnali questo contenuto Desideri nascondere tutti i messaggi di questo utente\? \n \nTieni presente che questa azione riavvierà l\'app e ciò potrebbe richiedere molto tempo. Annulla l\'Upload Annulla il Download - Cerca Cerca tra i membri della stanza Nessun risultato @@ -610,16 +576,13 @@ MESSAGGI UTENTI FILE - Esplora l\'elenco Ricerca negli elenchi… - Preferito Bassa priorità Chat diretta Esci dalla conversazione Dimentica - Messaggi Impostazioni Versione @@ -627,7 +590,6 @@ Avvisi di terze parti Copyright Politica sulla privacy - Immagine del profilo Nome visualizzato Email @@ -717,42 +679,33 @@ Inserisci un codice di attivazione Errore durante la verifica del numero di telefono Codice - 3 giorni 1 settimana 1 mese Per sempre - - Icona della stanza Nome della stanza Argomento Etichetta dela stanza Etichetta come: - Preferito Bassa priorità Nessuna etichetta - Accesso e visibilità Mostra questa stanza nell\'elenco delle stanze pubbliche Accesso alla stanza Accesso alla Timeline Chi può leggere la Timeline\? Chi può entrare in questa stanza? - Chiunque Solo i membri (dal momento in cui questa opzione è stata selezionata) Solo i membri (dal momento in cui vengono invitati) Solo i membri (dal momento in cui entrano nella stanza) - Una stanza deve avere un indirizzo per poter essere linkata. Solo le persone che sono state invitate Chiunque conosca il link della stanza, eccetto gli ospiti Chiunque conosca il link della stanza, compresi gli ospiti - Utenti bannati - Avanzate ID interno della stanza Indirizzi @@ -763,7 +716,6 @@ Per abilitare la crittografia devi disconnetterti. Cripta solo per le sessioni verificate Da questa sessione non inviare mai messaggi cifrati alle sessioni non verificate presenti in questa stanza. - Questa stanza non ha indirizzi locali Nuovo indirizzo stanza (es. #foo:matrix.org) Il formato del nome della stanza non è corretto @@ -778,12 +730,9 @@ La Crittografia in questa stanza non è attiva. Attiva crittografia \n(attenzione: non potrà più essere disattivata!) - Elenco Tema - %s stava cercando di caricare un punto specifico nella cronologia di questa stanza ma non è stato in grado di trovarlo. - Informazioni sulla crittografia E2E Informazioni sull\'evento ID utente @@ -827,23 +776,19 @@ Conferma confrontando la seguente con le impostazioni utente della tua altra sessione: Se non corrispondono, la sicurezza delle tue comunicazioni potrebbe essere compromessa. Ho verificato che le chiavi corrispondono - La stanza contiene sessioni sconosciute Questa stanza contiene sessioni sconosciute che non sono state verificate. \nNon v\'è alcuna garanzia che le sessioni appartengano agli utenti dichiarati. \nTi consigliamo di verificare ogni sessione prima di continuare, ma se lo preferisci, è comunque possibile inviare il messaggio anche senza la verifica. \n \nSessioni sconosciute: - Scegli un elenco di stanze Il server potrebbe essere non disponibile o sovraccarico Inserisci un Home Server per vedere le sue stanze pubbliche Nome del server Tutte le stanze sull\'Home Server %s Tutte le stanze native %s - Cerca tra i messaggi passati - Dimensione font Minuscolo Piccolo @@ -861,7 +806,6 @@ Devi avere il permesso per poter gestire i widget in questa stanza Avvia una conferenza usando Jitsi Vuoi davvero eliminare questo widget dalla stanza\? - Impossibile creare il widget. L\'invio della richiesta è fallito. Non sei in questa stanza. @@ -947,22 +891,17 @@ %d widget attivo %d widget attivi - La stanza %s non è visibile. Usa la fotocamera di sistema - Hai aggiunto una nuova sessione \'%s\' che sta richiedendo le chiavi crittografiche. La tua sessione non verificata \'%s\' sta chiedendo le chiavi crittografiche. Avvia la verifica Condividi senza verificare Ignora la richiesta - Attenzione! Le conferenze sono in fase di sviluppo e potrebbero non essere affidabili. - Errore di comando Comando non riconosciuto: %s - Spento Rumoroso Crea @@ -971,7 +910,6 @@ Esempio ID comunità esempio - Home Stanze Nessun utente diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml index ef83886d3d..2aae2b4581 100644 --- a/vector/src/main/res/values-ja/strings.xml +++ b/vector/src/main/res/values-ja/strings.xml @@ -640,15 +640,11 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場 不明なデバイスが存在しているため、メッセージを送ることができませんでした。今 %1$s または %2$s しますか? 要求されたフィンガープリントキー Ed25519 jitsiを用いて会議通話を始める - 端末のカメラを使う - 警告! ビデオ会議は開発中であり、確実でない可能性があります。 - コマンドエラー 認識されないコマンド: %s - 音量大 暗号化されたメッセージ @@ -687,7 +683,6 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場 コミュニティID - ホーム 参加者 部屋 diff --git a/vector/src/main/res/values-lv/strings.xml b/vector/src/main/res/values-lv/strings.xml index 2602c9ab12..ab51eb6c2b 100644 --- a/vector/src/main/res/values-lv/strings.xml +++ b/vector/src/main/res/values-lv/strings.xml @@ -879,7 +879,6 @@ Nākotnē šī pārbaudes procedūra plānota sarežģītāka. %d aktīvi vidžeti %d aktīvu vidžetu - Neizdevās uzstādīt vidžetu. Neizdevās nosūtīt pieprasījumu. Pieejas līmenim jābūt lielākam par 0. @@ -890,30 +889,24 @@ Nākotnē šī pārbaudes procedūra plānota sarežģītāka. Istaba %s ir neredzama. Pievienot Matrix lietotnes Lietot iebūvēto kameru - Tu pievienoji jaunu ierīci \'%s\', kura pieprasa šifrēšanas atslēgas. Tava neverificētā ierīce \'%s\' pieprasa šifrēšanas atslēgas. Sākt tās verifikāciju Dalīties bez verificēšanas Ignorēt pieprasījumu - Uzmanību! Konferences zvans ir izstrādes stadijā un var nebūt uzticams. - Komandas kļūda Nepazīstama komanda: %s - Izslēgts Ar skaņu Šifrēta ziņa - Izveidot Izveidot kopienu Kopienas nosaukums Piemērs Kopienas Id piemērs - Sākums Cilvēki Istabas diff --git a/vector/src/main/res/values-nl/strings.xml b/vector/src/main/res/values-nl/strings.xml index b5b3e6968b..2a545ba448 100644 --- a/vector/src/main/res/values-nl/strings.xml +++ b/vector/src/main/res/values-nl/strings.xml @@ -40,22 +40,15 @@ %1$s heeft de uitnodiging voor %2$s aanvaard ** Kan niet ontsleutelen: %s ** Het apparaat van de afzender heeft geen sleutels voor dit bericht gestuurd. - Kon niet verwijderd worden Kan bericht niet verzenden Uploaden van de afbeelding mislukt - Netwerkfout Matrix-fout - - - Het is momenteel niet mogelijk om een leeg gesprek opnieuw toe te treden. - E-mailadres Telefoonnummer %1$s heeft een sticker gestuurd. - Uitnodiging van %s Gespreksuitnodiging %1$s en %2$s @@ -116,13 +109,11 @@ %1$s heeft gasten de toegang tot het gesprek verhinderd. %1$s heeft eind-tot-eindversleuteling ingeschakeld. %1$s heeft eind-tot-eindversleuteling ingeschakeld (onbekend algoritme %2$s). - Berichten Gesprek Instellingen Info over deelnemer Historisch - Oké Annuleren Opslaan @@ -154,7 +145,6 @@ Toch sturen of Uitnodigen - Afmelden Spraakoproep Video-oproep @@ -166,30 +156,24 @@ Sluiten Gekopieerd naar klembord Uitschakelen - Bevestiging Waarschuwing - Thuis Favorieten Personen Gesprekken - Gespreksnamen filteren Favorieten filteren Personen filteren Gespreksnamen filteren - Uitnodigingen Lage prioriteit - Gesprekken Lokale contactenlijst Alleen Matrix-contacten Geen gesprekken U heeft ${app_name} geen toegang tot uw lokale contacten gegeven Geen resultaten - Gesprekken Gesprekscatalogus Geen gesprekken @@ -225,7 +209,6 @@ Video-oproep beginnen Bestanden versturen Foto of video maken - Aanmelden Account aanmaken Indienen @@ -273,7 +256,6 @@ Uw wachtwoord is opnieuw ingesteld. \n \nU bent op alle sessies afgemeld en zult niet langer pushmeldingen ontvangen. Om meldingen opnieuw in te schakelen, meldt u zich op elk apparaat opnieuw aan. - URL moet met http[s]:// beginnen Aanmelden mislukt: netwerkfout Aanmelden mislukt @@ -288,28 +270,20 @@ Er zijn te veel verzoeken verstuurd Deze gebruikersnaam is al in gebruik Er is nog niet op de koppeling in de e-mail geklikt - - Leesbevestigingslijst - - Versturen als Origineel Groot Medium Klein - "Download annuleren? Upload annuleren? %d s %1$dm %2$ds - Gisteren Vandaag - Gespreksnaam Gespreksonderwerp - Oproep verbonden Oproep is aan het verbinden… Oproep beëindigd @@ -322,10 +296,8 @@ Mediaverbinding is mislukt Kan de camera niet initialiseren oproep elders opgenomen - Een afbeelding of video maken" Kan geen video opnemen" - Informatie ${app_name} heeft toegang nodig tot uw mediabestanden om bijlagen te verzenden en op te slaan. \n @@ -346,31 +318,25 @@ \n \nWilt u uw adresboek hiervoor delen\? Sorry. De actie is niet toegepast vanwege ontbrekende rechten - Opgeslagen In downloads opslaan? JA NEE Verdergaan - Verwijderen Toetreden Voorvertoning Afwijzen - Ga naar ongelezen - %s heeft u uitgenodigd in dit gesprek Deze uitnodiging is naar %s verstuurd, maar die is niet geassocieerd met deze account. \nMisschien wilt u zich met een andere account aanmelden, of dit e-mailadres aan deze account toevoegen. U probeert toegang te verkrijgen tot %s. Zou u het gesprek willen toetreden om eraan deel te nemen\? een gesprek Dit is een voorvertoning van dit gesprek. Gespreksinteracties zijn uitgeschakeld. - Nieuw gesprek Deelnemer toevoegen 1 deelnemer - Gesprek verlaten Weet u zeker dat u het gesprek wilt verlaten\? Weet u zeker dat u %s uit dit gesprek wilt verwijderen\? @@ -398,14 +364,12 @@ U kunt deze veranderingen niet ongedaan maken aangezien u de gebruiker tot hetzelfde niveau als uzelf promoveert. \nWeet u het zeker\? Weet u zeker dat u %s in dit gesprek wilt uitnodigen\? - Uitnodigen met ID LOKALE CONTACTEN (%d) Enkel Matrix-gebruikers Gebruiker uitnodigen met ID Voer één of meer e-mailadressen of Matrix-ID’s in E-mailadres of Matrix-ID - Zoeken %s is aan het typen… %1$s en %2$s zijn aan het typen… @@ -421,7 +385,6 @@ Onverstuurde berichten verwijderen Bestand niet gevonden U heeft geen toestemming om dit naar dit gesprek te sturen. - Vertrouwen Niet vertrouwen Afmelden @@ -433,7 +396,6 @@ Het certificaat is veranderd van één dat door uw telefoon werd vertrouwd naar een ander. Dit is HEEL ONGEBRUIKELIJK. Het wordt aangeraden om dit nieuwe certificaat NIET TE AANVAARDEN. Het certificaat is veranderd van een vertrouwd naar een onvertrouwd certificaat. De server heeft misschien zijn certificaat vernieuwd. Contacteer de serverbeheerder voor de verwachte vingerafdruk. Aanvaard het certificaat alleen als de serverbeheerder een vingerafdruk heeft gepubliceerd die overeenkomt met degene hierboven. - Info over gesprek Personen Bestanden @@ -441,14 +403,12 @@ Ongeldige ID. Het zou een e-mailadres of een Matrix-ID zoals ‘@gebruikersnaam:domein’ moeten zijn UITGENODIGD TOEGETREDEN - Reden voor het melden van deze inhoud Wilt u alle berichten van deze gebruiker verbergen\? \n \nLet op: deze actie zal de app opnieuw opstarten; dit kan even duren. Upload annuleren Download annuleren - Zoeken Gespreksleden filteren Geen resultaten @@ -456,16 +416,13 @@ BERICHTEN PERSONEN BESTANDEN - Catalogus doorbladeren Catalogus wordt doorzocht… - Favoriet Lage prioriteit Tweegesprek Gesprek verlaten Vergeten - Berichten Instellingen Versie @@ -473,7 +430,6 @@ Derdepartijvermeldingen Copyright Privacybeleid - Profielfoto Naam E-mailadres @@ -501,7 +457,6 @@ Copyright Privacybeleid Cache wissen - Gebruikersinstellingen Meldingen Genegeerde gebruikers @@ -557,37 +512,29 @@ Voer een activeringscode in Er is een fout opgetreden bij het valideren van uw telefoonnummer Code - - Gespreksafbeelding Gespreksnaam Onderwerp Gesprekslabel Gelabeld als: - Favoriet Lage prioriteit Geen - Toegankelijk- en zichtbaarheid Dit gesprek vermelden in de gesprekscatalogus Toegang tot gesprek Toegang tot de gespreksgeschiedenis Wie kan er de geschiedenis lezen\? Wie heeft er toegang tot dit gesprek\? - Iedereen Alleen deelnemers (vanaf het moment dat deze optie wordt geselecteerd) Alleen deelnemers (vanaf het moment dat ze worden uitgenodigd) Alleen deelnemers (vanaf het moment dat ze toetreden) - Om naar een gesprek te verwijzen moet dit een adres hebben. Alleen personen die uitgenodigd zijn Iedereen die de koppeling van het gesprek kent, met uitzondering van gasten Iedereen die de koppeling van het gesprek kent, inclusief gasten - Verbannen gebruikers - Geavanceerd Interne ID van dit gesprek Adressen @@ -598,7 +545,6 @@ Eind-tot-eind-versleuteling is actief Alleen naar geverifieerde sessies versleutelen Ongeverifieerde sessies in dit gesprek nooit berichten sturen vanaf deze sessie. - Dit gesprek heeft geen lokale adressen Nieuw adres (bv. #foo:matrix.org) Ongeldig bijnaamformaat @@ -613,11 +559,8 @@ Versleuteling is uitgeschakeld in dit gesprek. Versleuteling inschakelen \n(let op: dit kan niet meer uitgeschakeld worden!) - Catalogus - %s heeft geprobeerd een specifiek punt in de geschiedenis van dit gesprek te laden, maar kon het niet vinden. - Informatie over eind-tot-eind-versleuteling Gebeurtenisinformatie Gebruikers-ID @@ -661,21 +604,18 @@ Om te verifiëren dat deze sessie vertrouwd kan worden, contacteert u de eigenaar via een andere methode (bv. persoonlijk of via een telefoontje) en vraagt u hem/haar of de sleutel die hij/zij ziet in zijn/haar Gebruikersinstellingen van deze sessie overeenkomt met de sleutel hieronder: Als het overeenkomt, drukt u op de knop ‘Verifiëren’ hieronder. Als het niet overeenkomt, dan onderschept iemand anders deze sessie en zou u het beter blokkeren. In de toekomst zal dit verificatieproces verbeterd worden. Ik verifieer dat de sleutels overeenkomen - Dit gesprek bevat onbekende sessies Dit gesprek bevat onbekende sessies die niet geverifieerd zijn. \nDit betekent dat er geen garantie is dat de sessies bij de gebruikers horen waartoe ze beweren te horen. \nWe raden u aan om bij elke sessie door het verificatieprocces heen te gaan voordat u verdergaat, maar u kunt het bericht ook zonder te verifiëren opnieuw versturen. \n \nOnbekende sessies: - Kies een gesprekscatalogus Het kan zijn dat de server niet beschikbaar of overbelast is Voer een thuisserver in om de publieke gesprekken ervan weer te geven Thuisserver-naam Alle gesprekken op server %s Alle lokale gesprekken op %s - Zoeken in de historiek Offline Gebruikerscatalogus @@ -712,7 +652,6 @@ Aanmaken van widget is mislukt Vergadergesprekken maken met jitsi Weet u zeker dat u deze widget uit dit gesprek wilt verwijderen\? - Kan widget niet aanmaken. Versturen van verzoek mislukt. Het machtsniveau moet een positief geheel getal zijn. @@ -732,19 +671,15 @@ Berichten die mijn gebruikersnaam bevatten Statistische gegevens Systeemcamera gebruiken - U heeft een nieuwe sessie ‘%s’ toegevoegd, die versleutelingssleutels aanvraagt. Uw ongeverifieerde sessie ‘%s’ vraagt versleutelingssleutels aan. Verificatie starten Delen zonder te verifiëren Verzoek negeren - Let op! Vergadergesprekken zijn in ontwikkeling en kunnen dus nog kuren vertonen. - Opdrachtfout Onbekende opdracht: %s - Uit Lawaaierig Versleuteld bericht @@ -816,14 +751,12 @@ %d actieve widget %d actieve widgets - Aanmaken Gemeenschap aanmaken Gemeenschapsnaam Voorbeeld Gemeenschaps-ID voorbeeld - Thuis Personen Gesprekken diff --git a/vector/src/main/res/values-pl/strings.xml b/vector/src/main/res/values-pl/strings.xml index b93ffc9247..0410c8a8da 100644 --- a/vector/src/main/res/values-pl/strings.xml +++ b/vector/src/main/res/values-pl/strings.xml @@ -527,9 +527,7 @@ Ludzie Przykład przykład - Wiadomość zaszyfrowana - Utwórz Wyłączone Uwaga! diff --git a/vector/src/main/res/values-pt-rBR/strings.xml b/vector/src/main/res/values-pt-rBR/strings.xml index 786f9a514b..cba3afa4d6 100644 --- a/vector/src/main/res/values-pt-rBR/strings.xml +++ b/vector/src/main/res/values-pt-rBR/strings.xml @@ -40,23 +40,15 @@ %1$s aceitou o convite para %2$s ** Incapaz de decriptar: %s ** O dispositivo do/da enviador(a) não nos enviou as chaves para esta mensagem. - Não foi possível redigir Não foi possível enviar mensagem Falha para fazer upload de imagem - Erro de rede Erro de Matrix - - - - Não é atualmente possível se re-juntar a uma sala vazia. - Endereço de email Número de telefone %1$s enviou um sticker. - Convite de %s Convite de Sala %1$s e %2$s @@ -271,7 +263,6 @@ Conferência de vídeo terminada por %1$s Você começou conferência de vídeo Conferência de vídeo começada por %1$s - Mensagens Sala Configurações @@ -280,7 +271,6 @@ Aceitar Declinar Desligar - OK Cancelar Salvar @@ -312,7 +302,6 @@ Enviar Mesmo Assim ou Convidar - Fazer signout Chamar por Voz Chamar por Vídeo @@ -324,30 +313,24 @@ Fechar Copiado para clipboard Desabilitar - Confirmação Aviso - Home Favoritos Pessoas Salas - Filtrar nomes de salas Filtrar favoritos Filtrar pessoas Filtrar nomes de salas - Convites Baixa prioridade - Conversas Agenda de endereços local Contatos de Matrix somente Nenhuma conversa Você não permitiu que ${app_name} acesse seus contatos locais Nenhum resultado - Salas Diretório de salas Nenhuma sala @@ -383,7 +366,6 @@ Começar Chamada de Vídeo Enviar arquivos Tirar foto ou vídeo - Fazer login Criar Conta Submeter @@ -431,7 +413,6 @@ Sua senha tem sido resettada. \n \nVocê tem sido feito logout de todas as sessões e não vai mais receber notificações push. Para re-habilitar notificações, re-faça login em cada dispositivo. - URL deve começar com http[s]:// Incapaz de fazer login: Erro de rede Incapaz de fazer login @@ -446,28 +427,20 @@ Requisições demais tem sido enviadas Este nome de usuária(o) já é usado O link de email que não tem sido clicado ainda - - Lista de Recibos de Leitura - - Enviar como Original Grande Médio Pequeno - Cancelar o download? Cancelar o upload\? %d s %1$dm %2$ds - Ontem Hoje - Nome de sala Tópico de sala - Chamada conectada Chamada conectando… Chamada terminada @@ -480,10 +453,8 @@ Conexão de Mídia Falhou Não é possível inicializar a câmera chamada atendida em algum outro lugar - Tirar uma foto ou um vídeo Não é possível gravar vídeo - Informação ${app_name} precisa de permissão para acessar sua biblioteca de fotos e vídeos para enviar e salvar anexos. \n @@ -504,31 +475,25 @@ \n \nVocê concorda em compartilhar seu livro de endereços para este propósito\? Desculpe. Ação não performada, devido a permissões faltando - Salvo Salvar em downloads\? SIM NÃO Continuar - Remover Juntar-se Previsualizar Rejeitar - Pular para não-lida(s) - Você tem sido convidada(o) para se juntar a esta sala por %s Este convite foi enviado para %s, que não está associada(o) com esta conta. \nVocê pode desejar fazer login com uma conta diferente, ou adicionar este email a sua conta. Você está tentando acessar %s. Você gostaria de se juntar a fim de participar na discussão\? uma sala Esta é uma previsualização desta sala. Interações de sala têm sido desabilitadas. - Novo Chat Adicionar membro 1 membro - Sair de sala Você tem certeza que você quer sair da sala\? Você tem certeza que você quer remover %s deste chat\? @@ -556,14 +521,12 @@ Você não vai ser capaz de desfazer esta mudança já que você está promovendo a(o) usuária(o) para ter o mesmo nível de poder que você. \nVocê tem certeza\? Você tem certeza você que quer convidar %s para este chat\? - Convidar por ID CONTATOS LOCAIS (%d) Usuárias(os) Matrix somente Convidar usuária(o) por ID Por favor entre um ou mais endereços de email ou ID Matrix Email ou ID Matrix - Pesquisar %s está digitando… %1$s & %2$s estão digitando… @@ -579,7 +542,6 @@ Deletar mensagens não-enviadas Arquivo não encontrado Você não tem permissão para postar nesta sala. - Confiar Não confiar Fazer logout @@ -591,7 +553,6 @@ O certificado tem mudado de um que era confiado por seu telefone. Isto é ALTAMENTE INCOMUM. É recomendado que você NÃO ACEITE este novo certificado. O certificado tem mudado de um previamente confiado para um que não é confiado. O servidor pode ter renovado seu certificado. Contacte o/a administrador(a) de servidor para a impressão digital esperada. Somente aceite o certificado se o/a administrador(a) de servidor tem publicado uma impressão digital que corresponde com a acima. - Detalhes de Sala Pessoas Arquivos @@ -599,14 +560,12 @@ ID malformada. Devia ser um endereço de email ou uma ID Matrix como \'@partlocal:dominio\' CONVIDADAS(OS) SE JUNTARAM - Razão por reportar este conteúdo Você quer esconder todas as mensagens desta(e) usuária(o)\? \n \nNote que esta ação vai recomeçar o app e pode levar algum tempo. Cancelar Upload Cancelar Download - Pesquisar Filtrar membros de sala Nenhum resultado @@ -614,16 +573,13 @@ MENSAGENS PESSOAS ARQUIVOS - Navegar diretório Pesquisando diretório… - Favoritar Des-prioritizar Chat Direto Sair de Conversa Esquecer - Mensagens Configurações Versão @@ -631,7 +587,6 @@ Notas de terceiros Copyright Política de privacidade - Imagem de Perfil Nome de Exibição Email @@ -659,7 +614,6 @@ Copyright Política de privacidade Limpar cache - Configurações de usuária(o) Notificações Usuárias(os) ignoradas(os) @@ -715,37 +669,29 @@ Entre um código de ativação Erro enquanto validando seu número de telefone Código - - Foto da Sala Nome de Sala Tópico Etiqueta da Sala Etiquetada como: - Favoritar Baixa prioridade Nenhuma - Acesso e visibilidade Listar esta sala em diretório de salas Acesso a Sala Legibilidade de Histórico de Sala Quem pode ler o histórico\? Quem pode acessar esta sala? - Qualquer pessoa Membros somente (desde o ponto no tempo de seleção desta opção) Membros somente (desde que eles foram convidados) Membros somente (desde que eles se juntaram) - Para linkar a uma sala ela deve ter um endereço. Somente pessoas que têm sido convidadas Qualquer pessoa que sabe o link da sala, a parte de visitantes Qualquer pessoa que sabe o link da sala, incluindo visitantes - Usuárias(os) banidas(os) - Avançadas ID interno desta sala Endereços @@ -756,7 +702,6 @@ Você precisa fazer logout para ser capaz de habilitar a encriptação. Encriptar para sessões verificadas somente Nunca enviar mensagens encriptadas para sessões não-confirmadas nesta sala desta sessão. - Esta sala não tem nenhum endereço local Novo endereço (e.g. #foo:matrix.org) Formato de alias inválido @@ -771,11 +716,8 @@ Encriptação está desabilitada nesta sala. Habilitar encriptação \n(aviso: não pode ser desabilitada de novo!) - Diretório - %s estava tentando carregar um ponto específico na timeline desta sala mas foi incapaz de encontrá-lo. - Informação de encriptação ponta-a-ponta Informação de evento Id de usuária(o) @@ -819,21 +761,18 @@ Confirme ao comparar o seguinte com as Configurações de Usuária(o) em sua outra sessão: Se não corresponderem, a segurança de sua comunicação pode estar comprometida. Eu verifico que as chaves correspondem - Sala contém sessões desconhecidas Esta sala contém sessões desconhecidas que não têm sido verificadas. \nIsto significa que não há nenhuma garantia que as sessões pertencem às/aos usuárias(os) às/aos quais elas clamam pertencer. \nNós recomendamos que você passe pelo processo de verificação para cada sessão antes de continuar, mas você pode reenviar a mensagem sem verificar se você preferir. \n \nSessões desconhecidas: - Selecionar um diretório de salas O servidor pode estar indisponível ou sobrecarregado Digite um servidorcasa para de onde listar salas públicas Nome de servidor Todas as salas em servidor %s Todas as salas nativas de %s - Pesquisar por histórico Offline Diretório de usuárias(os) @@ -905,7 +844,6 @@ Criação de widget tem falhado Criar chamadas de conferência com jitsi Você tem certeza que você quer deletar o widget desta sala\? - Incapaz de criar widget. Falha para enviar requisição. Nível de poder deve ser um inteiro positivo. @@ -916,30 +854,24 @@ Sala %s não está visível. Adicionar apps Matrix Usar câmera nativa - Você adicionou uma nova sessão \'%s\', que está requisitando chaves de encriptação. Sua sessão não-verificada \'%s\' está requisitando chaves de encriptação. Começar verificação Compartilhar sem verificar Ignorar requisição - Aviso! Chamamento de conferência está em desenvolvimento e pode não ser estável. - Erro de comando Comando irreconhecido: %s - Desativada Barulhenta Mensagem encriptada - Criar Criar Comunidade Nome da comunidade Exemplo ID de comunidade exemplo - Home Pessoas Salas @@ -1000,7 +932,6 @@ %d widget ativo %d widgets ativos - Avatar de recibo Avatar de nota Avatar diff --git a/vector/src/main/res/values-pt/strings.xml b/vector/src/main/res/values-pt/strings.xml index ff78d50424..d13dab7a1f 100644 --- a/vector/src/main/res/values-pt/strings.xml +++ b/vector/src/main/res/values-pt/strings.xml @@ -40,22 +40,14 @@ %1$s aceitou o convite para %2$s ** Impossível decifrar: %s ** O dispositivo de quem enviou a mensagem não nos enviou as chaves para esta mensagem. - Não foi possível apagar Não foi possível enviar a mensagem O envio da imagem falhou - Erro de conexão à Internet Erro do Matrix - - - - Ainda não é possível voltar a entrar numa sala vazia. - Endereço de e-mail Número de telefone - Convite de %s Convite para sala %1$s e %2$s @@ -64,13 +56,11 @@ %s fez o upgrade da sala. Mensagem removida Mensagem removida por %1$s - Mensagens Sala Definições Detalhes dos Membros Histórico - OK Cancelar Guardar @@ -101,7 +91,6 @@ Enviar na mesma ou Convidar - Sair Chamada de voz Chamada de vídeo @@ -113,30 +102,24 @@ Fechar Copiado para a área de transferência Desativar - Confirmação Aviso - Início Favoritos Pessoas Salas - Pesquisar salas Pesquisar favoritos Pesquisar pessoas Pesquisar salas - Convites Prioridade baixa - Conversas Lista de endereços local Apenas contactos do Matrix Não há conversas Não permitiu ao ${app_name} aceder aos seus contactos locais Sem resultados - Salas Lista de salas Sem salas @@ -172,7 +155,6 @@ Iniciar chamada de vídeo Enviar ficheiros Capturar foto ou vídeo - Iniciar sessão Registar Enviar @@ -220,7 +202,6 @@ Poderá adicionar o seu email ao seu perfil nas definições. A sua senha foi redefinida. A sessão foi terminada em todos os dispositivos e não receberá mais notificações. Para reactivar as notificações, inicie sessão novamente em cada um dos dispositivos. - URL tem que começar com http[s]:// Não foi possível iniciar sessão: Erro de rede Não foi possível iniciar sessão @@ -235,28 +216,20 @@ A sessão foi terminada em todos os dispositivos e não receberá mais notifica Foram enviados demasiados pedidos Este nome de utilizador já está a ser usado O link no email ainda não foi clicado - - Ler lista de recibos de leitura - - Enviar como Original Grande Média Pequena - Cancelar a transferência? Cancelar o envio? %d s %1$dm %2$ds - Ontem Hoje - Nome da sala Tópico da sala - Chamada estabelecida A estabelecer a chamada… Chamada terminada @@ -269,10 +242,8 @@ A sessão foi terminada em todos os dispositivos e não receberá mais notifica A ligação multimédia falhou Não foi possível iniciar a câmara chamada respondida noutro sítio - Tirar fotografia ou gravar um vídeo Não foi possível gravar vídeo - Informação O ${app_name} necessita de permissão para aceder ao seu armazenamento de fotos e vídeos para poder enviar e guardar anexos. @@ -295,31 +266,25 @@ Por favor, permita o acesso na próxima janela para poder encontrar utilizadores Permitir ao ${app_name} aceder aos seus contactos? Desculpe… A ação não foi realizada, por falta de permissões - Guardado Guardar nas transferências? SIM NÃO Continuar - Remover Entrar Visualizar Rejeitar - Ir para a primeira mensagem não lida. - Foi convidado para entrar nesta sala por %s Este convite foi enviado para %s, que não está associado com esta conta. Pode querer iniciar sessão com uma conta diferente, ou adicionar este email à sua conta. Está a tentar aceder a %s. Quer entrar na sala para poder participar na conversa? uma sala Isto é uma pré-visualização desta sala. As interações com a sala foram desativadas. - Nova conversa Adicionar membro 1 membro - Sair da sala Tem a certeza que quer sair da sala? Tem certeza que quer remover %s desta conversa? @@ -347,14 +312,12 @@ Pode querer iniciar sessão com uma conta diferente, ou adicionar este email à Não poderá reverter esta alteração visto que está a promover este utilizador para ter o mesmo nível de permissões que você. Tem a certeza? Tem a certeza que quer convidar %s para esta conversa? - Invite by ID CONTACTOS LOCAIS (%d) Apenas utilizadores Matrix Convidar utilizador por ID Insira um ou mais endereços de e-mail ou IDs Matrix E-mail ou ID Matrix - Pesquisar %s está a escrever… %1$s e %2$s estão a escrever… @@ -370,7 +333,6 @@ Tem a certeza? Apagar mensagens não enviadas Ficheiro não encontrado Não tem permissão para enviar mensagens nesta sala - Confiar Não confiar Sair @@ -382,7 +344,6 @@ Tem a certeza? O certificado mudou de um que era confiado pelo seu telefone. Isto é ALTAMENTE INVULGAR. Recomendamos que NÃO ACEITE este novo certificado. O certificado mudou de um que era anteriormente confiável para um que não é confiável. O servidor pode ter renovado o seu certificado. Contacte o administrador do servidor para obter a impressão digital expectável. Apenas aceite o certificado se o administrador do servidor publicou uma impressão digital que corresponde à impressão digital acima. - Detalhes da sala Pessoas Ficheiros @@ -390,14 +351,12 @@ Tem a certeza? ID mal formatado. Tem que ser um endereço de e-mail ou um ID Matrix como por exemplo \'@utilizador:domínio\' CONVIDADOS ENTRARAM - Motivos para denunciar este conteúdo Deseja ocultar todas as mensagens deste utilizador? Note que esta acção irá reiniciar a aplicação e poderá levar algum tempo. Cancelar envio Cancelar transferência - Pesquisar Filtrar membros da sala Nenhum resultado @@ -405,16 +364,13 @@ Note que esta acção irá reiniciar a aplicação e poderá levar algum tempo.< MENSAGENS PESSOAS FICHEIROS - Navegar diretório A pesquisar o directório… - Adicionar aos favoritos Baixa prioridade Conversa direta Deixar a conversa Esquecer - Mensagens Definições Versão @@ -422,7 +378,6 @@ Note que esta acção irá reiniciar a aplicação e poderá levar algum tempo.< Licenças de terceiros Direitos de autor Política de privacidade - Imagem do perfil Nome a apresentar E-mail @@ -450,7 +405,6 @@ Note que esta acção irá reiniciar a aplicação e poderá levar algum tempo.< Direitos de autor Política de privacidade Limpar cache - Definições de utilizador Notificações Utilizadores ignorados @@ -506,37 +460,29 @@ Note que esta acção irá reiniciar a aplicação e poderá levar algum tempo.< Insira o código de ativação Erro ao validar o seu número de telefone Código - - Foto da sala Nome da sala Tópico Etiqueta da sala Etiquetada como: - Adicionar aos favoritos Baixa prioridade Nenhum - Acesso e visibilidade Listar esta sala no diretório público de salas Acesso à sala Acesso ao histórico da sala Quem pode ler o histórico? Quem pode aceder a esta sala? - Qualquer pessoa Apenas membros (a partir do momento em que esta opção foi escolhida) Apenas membros (desde que foram convidados) Apenas membros (desde que entraram) - Para referenciar uma sala, ela necessita de ter um endereço. Apenas as pessoas que foram convidadas Qualquer pessoa que saiba o endereço da sala, exceto visitantes Qualquer pessoa que saiba o endereço da sala, incluindo visitantes - Utilizadores banidos - Avançadas ID interno desta sala Endereços @@ -547,7 +493,6 @@ Note que esta acção irá reiniciar a aplicação e poderá levar algum tempo.< Precisa sair da sua conta para poder ativar a criptografia. Cifrar apenas para dispositivos verificados Nunca enviar mensagens cifradas para dispositivos não verificados nesta sala a partir deste dispositivo. - Esta sala não tem endereços locais Novo endereço (por exemplo: #foo:matrix.org) Formato inválido de endereço (alias) @@ -562,11 +507,8 @@ Note que esta acção irá reiniciar a aplicação e poderá levar algum tempo.< Criptografia está desativada nesta sala. Ativar criptografia (atenção: não é possível desativar novamente!) - Diretório - %s estava a tentar carregar um ponto específico do histórico da sala mas não foi possível encontrá-lo. - Informação sobre criptografia ponta-a-ponta Informação do evento ID do utilizador @@ -610,21 +552,18 @@ Atenção: este ficheiro poderá ser apagado se a aplicação for desinstalada.< Para confirmar que este dispositivo pode ser considerado confiável, entre em contacto com o seu dono usando outros meios (p. ex. pessoalmente ou através de uma chamada telefónica) e pergunte-lhe se a chave que ele vê nas suas Definições de Utilizador para este dispositivo é igual à chave abaixo: Se é igual, clique no botão verificar, abaixo. Se não é igual, então alguém está a interceptar este dispositivo e você poderá querer colocá-lo na lista negra. No futuro, este processo de verificação irá ser mais sofisticado. Eu confirmo que as chaves são iguais - A sala contém dispositivos desconhecidos Esta sala contém dispositivos desconhecidos que não foram verificados. Isso significa que não existe a garantia de que estes dispositivos pertencem a quem dizem pertencer. Recomendamos que faça o processo de verificação para cada dispositivo antes de continuar, mas você pode reenviar a mensagem sem efetuar a verificação se preferir. Dispositivos desconhecidos: - Selecione um diretório de salas O servidor pode estar indisponível ou sobrecarregado Insira um servidor a partir do qual listar as suas salas públicas URL do servidor (Homeserver) Todas as salas no servidor %s Todas as nativas de %s - Procurar histórico Manter ficheiros multimédia Mostrar sempre a hora das mensagens @@ -659,7 +598,6 @@ Dispositivos desconhecidos: Criação de widget falhou Criar chamadas de conferência com o jitsi Tem a certeza que quer remover o widget? - Não foi possível criar o widget. O envio do pedido falhou. Nível de utilizador tem que ser um inteiro positivo. diff --git a/vector/src/main/res/values-ru/strings.xml b/vector/src/main/res/values-ru/strings.xml index 7adf19bb67..d9bf3ffbbd 100644 --- a/vector/src/main/res/values-ru/strings.xml +++ b/vector/src/main/res/values-ru/strings.xml @@ -40,23 +40,15 @@ %1$s принял(а) приглашение от %2$s ** Невозможно расшифровать: %s ** Устройство отправителя не предоставило нам ключ для расшифровки этого сообщения. - Не удалось изменить Не удалось отправить сообщение Не удалось загрузить изображение - Сетевая ошибка Ошибка Matrix - - - - В настоящее время невозможно вновь присоединиться к пустой комнате. - Адрес электронной почты Номер телефона %1$s отправил стикер. - Приглашение от %s Приглашение в комнату %1$s и %2$s @@ -286,7 +278,6 @@ %1$s завершил(а) видеоконференцию Вы начали видеоконференцию %1$s начал(а) видеоконференцию - Сообщения Комната Настройки @@ -295,7 +286,6 @@ Принять Отклонить Завершить звонок - OK Отмена Сохранить @@ -327,7 +317,6 @@ Все равно отправить или Приглашение - Выйти из учётной записи Голосовой вызов Видео вызов @@ -339,30 +328,24 @@ Закрыть Скопировано в буфер Отключить - Подтверждение Предупреждение - Начало Избранные Люди Комнаты - Фильтр названия комнаты Фильтр избранного Фильтр людей Фильтр названия комнаты - Приглашения Маловажные - Беседы Локальные контакты Только Matrix контакты Нет диалогов Вы не дали доступ ${app_name} к внутренним контактам Нет результатов - Комнаты Список комнат Нет комнат @@ -400,7 +383,6 @@ Начать видеовызов Отправить файлы Камера - Вход Создать аккаунт Отправить @@ -448,7 +430,6 @@ Ваш пароль сброшен. \n \nОсуществлен выход на всех сессиях - вы не будете получать push уведомления. Для включения push уведомлений заново войдите на каждом из ваших устройств. - URL должен начинаться с http[s]:// Сбой входа: сетевая ошибка Сбой входа @@ -463,28 +444,20 @@ Отправлено слишком много запросов Логин уже используется Вы не перешли по высланной в email ссылке - - Чтение списка вступивших - - Отправить как Оригинал Крупный Средний Мелкий - "Отменить загрузку? Отменить загрузку? %d с %1$dм %2$dс - Вчера Сегодня - Название комнаты Тема комнаты - Вызов соединён Устанавливается соединение… Вызов закончен @@ -497,10 +470,8 @@ Медиавызов не удался Невозможно инициализировать камеру Звонок принят на другом устройстве - Снять фото или видео" Не удалось записать видео" - Element Информация ${app_name} нуждается в разрешении на доступ к вашей фото-и видеотеке для отправки и сохранения вложений. \n @@ -517,31 +488,25 @@ \n \nСогласны ли вы поделиться своей адресной книгой для этой цели\? Извините. Действие не выполнено из-за недостаточных разрешений - Сохранено Сохранить в загрузки? ДА НЕТ Продолжить - Удалить Присоединиться Просмотр Отклонить - Перейти к непрочитанному - %s пригласил вас присоединиться к этой комнате Приглашение пришло на адрес %s, который не связан с этим аккаунтом. \nВозможно, вы захотите войти в систему с другим аккаунтом или добавить этот email в свою учетную запись. Вы пытаетесь получить доступ к %s. Хотите присоединиться к обсуждению? комната Это пред. просмотр комнаты. Вы в режиме только чтения. - Новый чат Добавить участника 1 пользователь - Покинуть комнату Вы уверены, что хотите выйти из комнаты\? Вы уверены, что хотите исключить %s из чата? @@ -569,14 +534,12 @@ Вы не сможете отменить это действие, поскольку пользователь получит такой же уровень доступа, как и у вас. \nВы уверены\? "Вы уверены что хотите пригласить %s в этот чат?" - Пригласить по ID Локальные Контакты (%d) Только зарегистрированные Пригласить пользователя по ID Пожалуйста, введите один или несколько адресов email или Matrix ID Email или Matrix ID - Поиск %s печатает… %1$s & %2$s печатают… @@ -592,7 +555,6 @@ Удалить неотправленные сообщения Файл не найден У вас нет прав писать сообщения в этом чате. - Доверять Не доверять Выйти @@ -604,7 +566,6 @@ Сертификат сервера изменился и ваш телефон теперь ему не доверяет. Это ОЧЕНЬ ПОДОЗРИТЕЛЬНО. Рекомендуется, НЕ ДОВЕРЯТЬ этому новому сертификату. Сертификат изменился с ранее доверенного на недействительный. Возможно, сервер обновил свой сертификат. Свяжитесь с администратором сервера для получения ожидаемого отпечатка сертификата. Примите сертификат только если администратор сервера опубликовал отпечаток сертификата, который соответствует указанному выше. - Подробности комнаты Люди Файлы @@ -612,14 +573,12 @@ Некорректный ID. Используйте email или Matrix ID вида \'@localpart:domain\' ПРИГЛАШЕНЫ ПРИСОЕДИНИЛИСЬ - Причина отчета о контенте Вы хотите скрыть все сообщения этого пользователя\? \n \nУчтите, что это действие перезапустит приложение и может занять некоторое время. Отменить загрузку Отменить загрузку - Поиск Фильтр списка пользователей Нет результатов @@ -627,16 +586,13 @@ СООБЩЕНИЯ ЛЮДИ ФАЙЛЫ - Просмотр каталога Поиск в каталоге… - Избранное Уменьшить приоритет Прямой чат Покинуть обсуждение Забыть - Сообщения Настройки Версия @@ -644,7 +600,6 @@ Прочие уведомления Авторские права Политика конфиденциальности - Аватар Отображаемое имя Email @@ -672,7 +627,6 @@ Авторские права Политика конфиденциальности Очистить весь кэш - Параметры пользователя Уведомления Игнорируемые @@ -728,37 +682,29 @@ Введите код активации Ошибка проверки телефона Код - - Аватар комнаты Название комнаты Тема Метки комнаты Отмечено как: - Избранное Маловажные Нет - Доступность и видимость Отображать комнату в каталоге Доступ к комнате Доступ к истории комнаты Кто может читать историю? Кто имеет доступ к комнате? - Все Только участники (с момента выбора этой опции) Только участники (с момента приглашения) Только участники (с момента присоединения) - Для генерации ссылки команда должна иметь адрес. Только приглашенные Все у кого есть ссылка на комнату, кроме гостей Все у кого есть ссылка на комнату, включая гостей - Забаненные пользователи - Дополнительно Внутренний ID комнаты Адреса @@ -769,7 +715,6 @@ Вам необходимо выйти, чтобы включить шифрование. Шифровать сообщения только для проверенных сессий Никогда не отправлять шифрованное сообщение на непроверенные сессии в этой комнате с этой сессии. - У этой комнаты еще нет локального адреса Новый адрес (например #foo:matrix.org") Неверный формат псевдонима @@ -784,11 +729,8 @@ В этой комнате выключено шифрование. Включить шифрование \n(внимание: не может быть снова отключено!) - Каталог - %s пытается загрузить историю комнаты, но не может ее найти. - Информация о сквозном шифровании Информация о событии ID пользователя @@ -832,21 +774,18 @@ Чтобы убедиться, что этой сессии можно доверять, обратитесь к его владельцу, используя другие способы (например, лично или по телефону), и спросите, соответствует ли ключ, который он видит в настройках для этой сессии: Если совпадает, то нажмите кнопку подтвердить ниже. Если не совпадает, возможно кто-то пытается перехватить сессию и вы захотите добавить его в черный список. В будущем данный процесс будет улучшен. Я проверил, что ключи совпадают - Комната содержит неизвестные сессии Эта комната содержит неизвестные сессии, которые не были подтверждены. \nЭто означает, что эти сессии могут не принадлежать тем пользователям, на которых они претендуют. \nПеред продолжением рекомендуем вам пройти процесс проверки для каждой сессии, но вы можете отправить сообщение повторно, не проверяя. \n \nНеизвестные сессии: - Выбор списка комнат Сервер возможно недоступен или перегружен Введите домашний сервер для отображения списка публичных комнат Имя сервера Все комнаты на сервере %s Все местные комнаты %s - Поиск в истории Пользовательский интерфейс Язык @@ -881,7 +820,6 @@ Создание виджета не удалось Осуществлять конференц звонки через Jitsi Вы уверены, что хотите удалить виджет из этой комнаты? - Не удалось создать виджет. Не удалось отправить запрос. Уровень доступа должен быть числом больше 0. @@ -907,14 +845,11 @@ Снять видео Аналитика Использовать встроенную камеру - Сообщить об ошибке Внимание! Конференц-связь находится в разработке и может быть ненадежной. - Ошибка команды Нераспознанная команда: %s - Выкл Громко Зашифрованное сообщение @@ -948,7 +883,6 @@ Пример ID сообщества пример - Начало Люди Комнаты @@ -988,7 +922,6 @@ %d активных виджетов - Аватар %d активный участник diff --git a/vector/src/main/res/values-sk/strings.xml b/vector/src/main/res/values-sk/strings.xml index 38882e4bad..dead4166a9 100644 --- a/vector/src/main/res/values-sk/strings.xml +++ b/vector/src/main/res/values-sk/strings.xml @@ -757,7 +757,6 @@ Nie je možné vytvoriť widget Konferenčné hovory uskutočňovať použitím jitsi Ste si istí, že chcete odstrániť widget z tejto miestnosti? - Nie je možné vytvoriť widget. Nepodarilo sa odoslať požiadavku. Úroveň moci musí byť kladné celé číslo. @@ -767,7 +766,6 @@ V požiadavke chýba user_id. Miestnosť %s nie je viditeľná. Pridať Matrix aplikácie - Pridali ste novú reláciu \'%s\', ktorá požaduje šifrovacie kľúče. Vaša neoverená relácia \'%s\' požaduje šifrovacie kľúče. Spustiť overenie @@ -780,13 +778,10 @@ Poslať video Štatistika Používať vstavaný fotoaparát a kameru - Upozornenie! Konferenčné hovory sú stále vo vývoji a nemusia byť úplne spoľahlivé. - Chybný príkaz Nerozpoznaný príkaz: %s - Vypnuté Hlasné Šifrovaná správa @@ -822,7 +817,6 @@ Príklad ID komunity príklad - Domov Ľudia Miestnosti @@ -894,7 +888,6 @@ %d aktívne widgety %d aktívnych widgetov - Potvrdenie o prečítaní Obrázok oznámenia Obrázok diff --git a/vector/src/main/res/values-uk/strings.xml b/vector/src/main/res/values-uk/strings.xml index df7606ee6f..fca2de1dfe 100644 --- a/vector/src/main/res/values-uk/strings.xml +++ b/vector/src/main/res/values-uk/strings.xml @@ -4,7 +4,6 @@ %1$s надсилає зображення. %s запрошення %1$s запрошує %2$s - Запрошення від %s Запрошення до кімнати %1$s і %2$s @@ -173,21 +172,17 @@ Ви відкликали запрошення для %1$s Ви заблокували %1$s Ви заблокували %1$s - Світла Тема Темна Тема OLED Тема - Синхронізація… Стеження за подіями - Повідомлення Кімната Налаштування Інформація про учасника Історія Звіт про ваду - Гаразд Скасувати Зберегти @@ -220,7 +215,6 @@ або Запрошення Не в мережі - Вийти з облікового запису Голосовий виклик Відеовиклик @@ -232,23 +226,18 @@ Закрити Скопійовано до буфера обміну Вимкнути - Підтвердження Попередження - Домівка Обрані Люди Кімнати - Пошук кімнат Пошук обраних Пошук людей Пошук кімнат - Запрошення Неважливі - Бесіди Локальні контакти Каталог користувачів @@ -256,7 +245,6 @@ Немає бесід Ви не надали ${app_name} доступу до контактів Немає результатів - Кімнати Каталог кімнат Кімнати відсутні @@ -293,7 +281,6 @@ Здійснити відеовиклик Надсилання файлів Зняти світлину чи відео - Увійти Реєстрація Надіслати @@ -337,7 +324,6 @@ На адресу %s надіслано електронний лист. Як тільки ви перейдете за посиланням у ньому, натисніть нижче. Не вдалося перевірити email: переконайтеся, що ви перейшли за посиланням у листі Пароль скинуто.\n\nВсі ваші пристрої деаутентифіковано, а ви більше не отримаєте push-сповіщень. Щоб увімкнути їх, залогуйтеся знову на всіх пристроях. - URL має починатися з http[s]:// Збій входу: Мережева помилка Збій входу @@ -352,28 +338,20 @@ Надіслано забагато запитів Ім\'я користувача вже використовується Ви не перейшли за посиланням в електронному листі - - Список міток прочитання - - Надіслати як Оригінал Великий Середній Малий - "Скасувати завантаження? Скасувати відвантаження? %d с %1$dхв %2$dс - Вчора Сьогодні - Назва кімнати Тема кімнати - Виклик з\'єднано Встановлюється з\'єднання… Виклик завершено @@ -386,10 +364,8 @@ Медіавиклик не вдався Неможливо ініціювати камеру Дзвінок прийнято іншим пристроєм - Зняти світлину чи відео" Не вдалося записати відео" - Інформація Для передачі та збереження вкладень потрібен доступ до медіатеки.\n\nБудь ласка, надайте його у наступному виринаючому вікні, щоб отримати змогу надсилати файли з вашого телефону. Для зйомки та відеодзвінків необхідно мати доступ до камери. @@ -402,30 +378,24 @@ \n \nНадати ${app_name} доступ до ваших контактів\? Вибачте.. Дію не виконано через нестачу дозволів - Збережено Зберегти в завантаженнях? ТАК НІ Продовжити - Видалити Приєднатися Попередній перегляд Відхилити - Перейти до непрочитаного - %s запросив(ла) вас приєднатися до цієї кімнати Запрошення надіслано на адресу %s, не пов\'язану з цим обліковим записом.\nВи можете увійти під іншим обліковим записом чи приєднати email до цього. Ви намагаєтесь отримати доступ до %s. Бажаєте приєднатися щоб взяти участь у обговоренні? кімната Це попередній перегляд кімнати. Ви в режимі лише читання. - Новий Чат Додати учасника 1 учасник - Вийти з кімнати Ви впевнені, що хочете вийти з кімнати\? Ви впевнені, що бажаєте вилучити %s з цієї кімнати\? @@ -453,7 +423,6 @@ Ви не зможете скасувати цю дію, оскільки надаєте користувачу той же рівень повноважень, що й у вас. \nВи впевнені\? Ви впевнені, що бажаєте запросити %s до цієї кімнати\? - Запросити за ID Локальні Контакти (%d) Каталог Користувачів (%s) @@ -461,7 +430,6 @@ Запросити користувача за ID Введіть одну чи декілька email адрес або Matrix ID Email або Matrix ID - Пошук %s друкує… %1$s & %2$s друкують… @@ -477,7 +445,6 @@ Видалити не надіслані повідомлення Файл не знайдено Ви не маєте дозволу писати повідомлення у цій кімнаті. - Довіряти Не довіряти Вийти @@ -489,7 +456,6 @@ Сертифікат почав відрізнятися від того, якому довіряв ваш телефон. Це ДУЖЕ НЕЗВИЧНО. Наполегливо радимо НЕ ПРИЙМАТИ цей новий сертифікат. Сертифікат змінився з довіреного на недовірений. Сервер міг оновити свій сертифікат. Зв\'яжіться з адміністратором сервера, щоб отримати дійсний відбиток. Приймайте сертифікат лише у випадку збігу відбитку вище з відбитком, оприлюдненим адміністратором сервера. - Деталі про кімнату Люди Файли @@ -497,14 +463,12 @@ Викривлений ID. Використовуйте email адресу або Matrix ID вигляду \'@localpart:domain\' ЗАПРОШЕНІ ПРИЄДНАЛИСЯ - Причина скарги на цей вміст Бажаєте сховати всі повідомлення цього користувача\? \n \nЗауважте, що це перезавантажить застосунок та може тривати деякий час. Скасувати відвантаження Скасувати завантаження - Пошук Фільтр переліку користувачів Тут порожньо @@ -512,16 +476,13 @@ ПОВІДОМЛЕННЯ ЛЮДИ ФАЙЛИ - Огляд каталогу Пошук у каталозі… - Обране Не терміново Особиста бесіда Вийти з бесіди Забути - Повідомлення Налаштування Версія @@ -529,7 +490,6 @@ Примітки третіх сторін Copyright Політика приватності - Аватар Показуване ім\'я Е-пошта @@ -579,7 +539,6 @@ Сеанси Показувати час надсилання для всіх повідомлень Показувати час надсилання у 12-годинному форматі - Телеметрія Режим економії трафіку Про пристрій @@ -625,42 +584,33 @@ Введіть код активації Сталася помилка під час перевірки вашого номера Код - 3 дні 1 тиждень 1 місяць Завжди - - Світлина кімнати Назва кімнати Тема Мітка кімнати Позначено як: - Улюблені Неважливі Жодного - Доступ та видимість Показувати кімнату в каталозі Доступ Прочитність історії Хто може читати історію повідомлень? Хто має доступ до кімнати? - Будь-хто Лише учасники (з моменту вибору цієї опції) Лише учасники (з моменту запрошення) Лише учасники (з моменту вступу) - Щоб посилатися на кімнату, вона повинна мати адресу. Лише запрошені Будь-хто з посиланням, окрім гостів Будь-хто з посиланням, включно з гостями - Заблоковані користувачі - Розширені Внутрішній ID кімнати Адреси @@ -671,7 +621,6 @@ Вийдіть з облікового запису, щоб отримати змогу увімкнути шифрування. Шифрувати лише для звірених сеансів Ніколи не надсилати зашифровані повідомлення з цього сеансу незвіреним сеансам у цій кімнаті. - Кімната не має локальної адреси Нова адреса (наприклад #foo:matrix.org) Хибний формат псевдоніма @@ -685,11 +634,8 @@ У цій кімнаті увімкнено шифрування. У цій кімнаті вимкнено шифрування. Увімкнути шифрування \n(увага: вимкнути його неможливо!) - Каталог - %s намагався завантажити певну точку зі стрічки подій цієї кімнати, але не зміг її знайти. - Дані про наскрізне шифрування Інформація про подію ID користувача @@ -733,23 +679,19 @@ Підтвердьте, порівнявши вказане за допомогою налаштувань користувача в іншому сеансі: Якщо вони відрізняються, безпека вашого зв\'язку може бути під загрозою. Я підтверджую, що ключі збігаються - Кімната містить невідомі сеанси Кімната містить незвірені невідомі сеанси. \nЦе означає відсутність будь-яких гарантій у тому, що сеанси належать тим користувачам, які заявляють про належність цих сеансів їм. \nМи радимо вам звірити кожен сеанс перед тим, як продовжити, проте ви можете перенадіслати повідомлення без звірки, якщо цього бажаєте. \n \nНевідомі сеанси: - Вибір каталогу кімнат Можливо сервер недоступний чи перевантажений Введіть сервер для показу каталогу публічних кімнат Ім\'я сервера Всі кімнати на сервері %s Усі кімнати сервера %s - Пошук в історії - Розмір шрифту Крихітний Дрібний @@ -758,12 +700,10 @@ Великий Найбільший Величезний - Вам потрібен дозвіл, щоб керувати віджетами у цій кімнаті Помилка створення віджету Здійснювати конференц дзвінки через Jitsi Ви впевнені, що бажаєте видалити віджет із цієї кімнати\? - Не вдалося створити віджет. Не вдалося надіслати запит. Рівень повноважень має бути цілим додатним числом. @@ -779,19 +719,16 @@ Записати відео Виклик Тема - Увага! Вимкнено Гучно Зашифроване повідомлення Використовувати рідну камеру - Щойно доданий вами пристрій \'%s\' править ключі шифрування. Ваш незвірений пристрій «%s» вимагає ключі шифрування. Почати перевірку Поділитись без перевірки Знехтувати запит - Помилка виконання команди Команду %s не розпізнано Відомості про спільноту diff --git a/vector/src/main/res/values-zh-rCN/strings.xml b/vector/src/main/res/values-zh-rCN/strings.xml index 953ccc0f21..1fd1a48f34 100644 --- a/vector/src/main/res/values-zh-rCN/strings.xml +++ b/vector/src/main/res/values-zh-rCN/strings.xml @@ -685,17 +685,14 @@ 按收藏夹过滤 按联系人过滤 按聊天室名称过滤 - 邀请 低优先级 - 对话 本地通讯录 只显示 Matrix 联系人 没有对话 没有结果 你没有授予 ${app_name} 访问本地通讯录的权限 - 聊天室 聊天室目录 没有聊天室 @@ -704,14 +701,12 @@ 请输入一个或多个电子邮箱地址或 Matrix ID 电子邮箱地址或 Matrix ID 忘记 - 选择一个聊天室目录 服务器可能不可用或过载 请输入一个要列出公共聊天室的主服务器 服务器名称 %s 服务器上的所有聊天室 所有本地 %s 聊天室 - 搜索历史 历史消息 用 ID 邀请 @@ -752,7 +747,6 @@ 创建挂件失败 用 jitsi 创建会议通话 你确定要删除这个挂件吗? - 无法创建挂件。 发送请求失败。 特权级别必须是正整数。 @@ -776,13 +770,10 @@ 拍摄照片 拍摄视频 使用原生相机 - 警告! 会议通话正在开发中,可能不可靠。 - 命令错误 不可识别的命令:%s - 关闭 已加密消息 响铃通知 @@ -849,7 +840,6 @@ 已启用 %d 个挂件 - 社群名称 社群 ID 主页 From 185afe403fb2db6da2c15b8d37e0838004e5d0e1 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 4 Jan 2022 14:15:46 +0100 Subject: [PATCH 196/632] Rename some string resources --- .../im/vector/app/ui/robot/ElementRobot.kt | 6 +- .../core/dialogs/ConfirmationDialogBuilder.kt | 2 +- .../dialogs/GalleryOrCameraDialogHelper.kt | 2 +- .../app/core/dialogs/ManuallyVerifyDialog.kt | 2 +- .../app/core/dialogs/PhotoOrVideoDialog.kt | 6 +- .../dialogs/UnrecognizedCertificateDialog.kt | 2 +- .../core/utils/ExternalApplicationsUtil.kt | 4 +- .../vector/app/core/utils/PermissionsTools.kt | 2 +- .../im/vector/app/features/MainActivity.kt | 2 +- .../call/conference/VectorJitsiActivity.kt | 2 +- .../settings/KeysBackupSettingsFragment.kt | 2 +- .../setup/KeysBackupSetupActivity.kt | 4 +- .../crypto/recover/BootstrapBottomSheet.kt | 2 +- .../IncomingVerificationRequestHandler.kt | 2 +- .../cancel/VerificationCancelController.kt | 2 +- .../cancel/VerificationNotMeController.kt | 2 +- .../request/VerificationRequestController.kt | 2 +- .../discovery/DiscoverySettingsFragment.kt | 6 +- .../change/SetIdentityServerFragment.kt | 2 +- .../vector/app/features/home/HomeActivity.kt | 2 +- .../home/room/detail/RoomDetailFragment.kt | 16 ++--- .../room/detail/StartCallActionsHandler.kt | 4 +- .../timeline/action/EventSharedAction.kt | 10 +-- .../timeline/item/CallTileTimelineItem.kt | 4 +- .../home/room/list/RoomListFragment.kt | 4 +- .../home/room/list/RoomSummaryItemFactory.kt | 2 +- .../app/features/link/LinkHandlerActivity.kt | 2 +- .../login/LoginResetPasswordFragment.kt | 2 +- .../app/features/login/LoginSplashFragment.kt | 2 +- .../login2/LoginResetPasswordFragment2.kt | 2 +- .../login2/created/AccountCreatedFragment.kt | 2 +- .../matrixto/MatrixToRoomSpaceFragment.kt | 2 +- .../notifications/NotificationUtils.kt | 4 +- .../im/vector/app/features/pin/PinFragment.kt | 2 +- .../RoomMemberProfileController.kt | 2 +- .../RoomMemberProfileFragment.kt | 2 +- .../powerlevel/EditPowerLevelDialogs.kt | 4 +- .../roomprofile/RoomProfileFragment.kt | 6 +- .../roomprofile/alias/RoomAliasFragment.kt | 6 +- .../RoomAliasBottomSheetSharedAction.kt | 4 +- .../members/RoomMemberListFragment.kt | 4 +- .../settings/RoomSettingsFragment.kt | 2 +- .../settings/joinrule/RoomJoinRuleFragment.kt | 4 +- .../BackgroundSyncModeChooserDialog.kt | 2 +- .../settings/VectorSettingsGeneralFragment.kt | 2 +- .../VectorSettingsPreferencesFragment.kt | 2 +- ...ceVerificationInfoBottomSheetController.kt | 2 +- .../devices/VectorSettingsDevicesFragment.kt | 2 +- .../settings/devtools/AccountDataFragment.kt | 6 +- .../threepids/ThreePidsSettingsFragment.kt | 4 +- .../features/share/IncomingShareFragment.kt | 4 +- .../signout/soft/SoftLogoutFragment.kt | 2 +- .../explore/SpaceDirectoryController.kt | 2 +- .../spaces/explore/SpaceDirectoryFragment.kt | 2 +- .../spaces/invite/SpaceInviteBottomSheet.kt | 4 +- .../spaces/manage/SpaceAddRoomFragment.kt | 2 +- .../spaces/manage/SpaceSettingsFragment.kt | 2 +- .../app/features/widgets/WidgetFragment.kt | 4 +- .../workers/signout/SignOutUiWorker.kt | 2 +- .../res/layout/bottom_sheet_leave_space.xml | 2 +- .../bottom_sheet_room_widget_permission.xml | 2 +- ...ottom_sheet_space_advertise_restricted.xml | 2 +- .../layout/bottom_sheet_tombstone_join.xml | 2 +- .../src/main/res/layout/composer_layout.xml | 4 +- ...composer_layout_constraint_set_compact.xml | 4 +- ...omposer_layout_constraint_set_expanded.xml | 4 +- .../layout/fragment_attachments_preview.xml | 2 +- .../res/layout/fragment_bootstrap_reauth.xml | 2 +- .../layout/fragment_join_rules_recycler.xml | 4 +- .../main/res/layout/fragment_login_terms.xml | 2 +- .../res/layout/fragment_login_terms_2.xml | 2 +- .../fragment_matrix_to_room_space_card.xml | 4 +- .../main/res/layout/fragment_review_terms.xml | 4 +- .../fragment_room_preview_no_preview.xml | 2 +- .../layout/fragment_space_leave_advanced.xml | 2 +- .../res/layout/fragment_space_preview.xml | 4 +- .../res/layout/fragment_ssss_reset_all.xml | 4 +- .../item_form_text_input_with_delete.xml | 2 +- .../src/main/res/layout/item_generic_list.xml | 2 +- .../src/main/res/layout/item_public_room.xml | 2 +- .../src/main/res/layout/item_pushgateway.xml | 2 +- .../res/layout/item_room_directory_server.xml | 2 +- .../main/res/layout/item_room_invitation.xml | 4 +- .../layout/item_settings_continue_cancel.xml | 2 +- .../res/layout/item_settings_three_pid.xml | 2 +- .../main/res/layout/item_suggested_room.xml | 2 +- .../item_timeline_event_call_tile_stub.xml | 4 +- ...item_timeline_event_media_message_stub.xml | 2 +- .../item_timeline_event_verification_stub.xml | 4 +- .../src/main/res/layout/item_uploads_file.xml | 4 +- .../main/res/layout/item_uploads_video.xml | 2 +- .../layout/merge_image_attachment_overlay.xml | 4 +- .../main/res/layout/vector_invite_view.xml | 4 +- .../src/main/res/layout/view_button_state.xml | 4 +- .../layout/view_voice_message_recorder.xml | 2 +- vector/src/main/res/menu/menu_devtools.xml | 2 +- .../src/main/res/menu/menu_manage_space.xml | 2 +- .../src/main/res/menu/menu_space_add_room.xml | 2 +- vector/src/main/res/menu/menu_timeline.xml | 4 +- vector/src/main/res/menu/menu_widget.xml | 2 +- .../res/menu/vector_attachments_preview.xml | 2 +- .../src/main/res/menu/vector_media_viewer.xml | 2 +- .../res/menu/vector_room_member_profile.xml | 2 +- .../src/main/res/menu/vector_room_profile.xml | 2 +- .../main/res/menu/vector_room_settings.xml | 2 +- vector/src/main/res/values-ar/strings.xml | 66 +++++++++--------- .../src/main/res/values-b+sr+Latn/strings.xml | 40 +++++------ vector/src/main/res/values-bg/strings.xml | 66 +++++++++--------- vector/src/main/res/values-bn-rBD/strings.xml | 64 ++++++++--------- vector/src/main/res/values-bn-rIN/strings.xml | 64 ++++++++--------- vector/src/main/res/values-bs/strings.xml | 34 +++++----- vector/src/main/res/values-ca/strings.xml | 66 +++++++++--------- vector/src/main/res/values-cs/strings.xml | 66 +++++++++--------- vector/src/main/res/values-da/strings.xml | 34 +++++----- vector/src/main/res/values-de/strings.xml | 66 +++++++++--------- vector/src/main/res/values-el/strings.xml | 62 ++++++++--------- vector/src/main/res/values-eo/strings.xml | 66 +++++++++--------- vector/src/main/res/values-es-rMX/strings.xml | 40 +++++------ vector/src/main/res/values-es/strings.xml | 66 +++++++++--------- vector/src/main/res/values-et/strings.xml | 66 +++++++++--------- vector/src/main/res/values-eu/strings.xml | 58 ++++++++-------- vector/src/main/res/values-fa/strings.xml | 66 +++++++++--------- vector/src/main/res/values-fi/strings.xml | 66 +++++++++--------- vector/src/main/res/values-fr-rCA/strings.xml | 66 +++++++++--------- vector/src/main/res/values-fr/strings.xml | 66 +++++++++--------- vector/src/main/res/values-fy/strings.xml | 66 +++++++++--------- vector/src/main/res/values-ga/strings.xml | 66 +++++++++--------- vector/src/main/res/values-gl/strings.xml | 54 +++++++-------- vector/src/main/res/values-hr/strings.xml | 58 ++++++++-------- vector/src/main/res/values-hu/strings.xml | 66 +++++++++--------- vector/src/main/res/values-in/strings.xml | 66 +++++++++--------- vector/src/main/res/values-is/strings.xml | 34 +++++----- vector/src/main/res/values-it/strings.xml | 66 +++++++++--------- vector/src/main/res/values-iw/strings.xml | 66 +++++++++--------- vector/src/main/res/values-ja/strings.xml | 66 +++++++++--------- vector/src/main/res/values-kab/strings.xml | 66 +++++++++--------- vector/src/main/res/values-ko/strings.xml | 62 ++++++++--------- vector/src/main/res/values-lt/strings.xml | 8 +-- vector/src/main/res/values-lv/strings.xml | 66 +++++++++--------- vector/src/main/res/values-ml/strings.xml | 66 +++++++++--------- vector/src/main/res/values-nb-rNO/strings.xml | 66 +++++++++--------- vector/src/main/res/values-nl/strings.xml | 66 +++++++++--------- vector/src/main/res/values-nn/strings.xml | 64 ++++++++--------- vector/src/main/res/values-pl/strings.xml | 66 +++++++++--------- vector/src/main/res/values-pt-rBR/strings.xml | 66 +++++++++--------- vector/src/main/res/values-pt/strings.xml | 48 ++++++------- vector/src/main/res/values-ro/strings.xml | 8 +-- vector/src/main/res/values-ru/strings.xml | 66 +++++++++--------- vector/src/main/res/values-sk/strings.xml | 66 +++++++++--------- vector/src/main/res/values-sl/strings.xml | 4 +- vector/src/main/res/values-sq/strings.xml | 64 ++++++++--------- vector/src/main/res/values-sr/strings.xml | 66 +++++++++--------- vector/src/main/res/values-sv/strings.xml | 66 +++++++++--------- vector/src/main/res/values-szl/strings.xml | 40 +++++------ vector/src/main/res/values-te/strings.xml | 34 +++++----- vector/src/main/res/values-th/strings.xml | 60 ++++++++-------- vector/src/main/res/values-tlh/strings.xml | 30 ++++---- vector/src/main/res/values-tr/strings.xml | 66 +++++++++--------- vector/src/main/res/values-tzm/strings.xml | 38 +++++------ vector/src/main/res/values-uk/strings.xml | 66 +++++++++--------- vector/src/main/res/values-uz/strings.xml | 54 +++++++-------- vector/src/main/res/values-vi/strings.xml | 66 +++++++++--------- vector/src/main/res/values-vls/strings.xml | 4 +- vector/src/main/res/values-zh-rCN/strings.xml | 66 +++++++++--------- vector/src/main/res/values-zh-rTW/strings.xml | 66 +++++++++--------- vector/src/main/res/values/strings.xml | 68 +++++++++---------- 166 files changed, 1876 insertions(+), 1878 deletions(-) diff --git a/vector/src/androidTest/java/im/vector/app/ui/robot/ElementRobot.kt b/vector/src/androidTest/java/im/vector/app/ui/robot/ElementRobot.kt index 99af7851ef..74e4478635 100644 --- a/vector/src/androidTest/java/im/vector/app/ui/robot/ElementRobot.kt +++ b/vector/src/androidTest/java/im/vector/app/ui/robot/ElementRobot.kt @@ -134,10 +134,10 @@ class ElementRobot { activity.runOnUiThread { popup.performClick() } waitUntilViewVisible(withId(R.id.bottomSheetFragmentContainer)) - waitUntilViewVisible(ViewMatchers.withText(R.string.skip)) - clickOn(R.string.skip) + waitUntilViewVisible(ViewMatchers.withText(R.string.action_skip)) + clickOn(R.string.action_skip) assertDisplayed(R.string.are_you_sure) - clickOn(R.string.skip) + clickOn(R.string.action_skip) waitUntilViewVisible(withId(R.id.bottomSheetFragmentContainer)) }.onFailure { Timber.w("Verification popup missing", it) } } diff --git a/vector/src/main/java/im/vector/app/core/dialogs/ConfirmationDialogBuilder.kt b/vector/src/main/java/im/vector/app/core/dialogs/ConfirmationDialogBuilder.kt index d2d0967939..6f45091cf9 100644 --- a/vector/src/main/java/im/vector/app/core/dialogs/ConfirmationDialogBuilder.kt +++ b/vector/src/main/java/im/vector/app/core/dialogs/ConfirmationDialogBuilder.kt @@ -56,7 +56,7 @@ object ConfirmationDialogBuilder { ?.takeIf { it.isNotBlank() } confirmation(reason) } - .setNegativeButton(R.string.cancel, null) + .setNegativeButton(R.string.action_cancel, null) .show() } } diff --git a/vector/src/main/java/im/vector/app/core/dialogs/GalleryOrCameraDialogHelper.kt b/vector/src/main/java/im/vector/app/core/dialogs/GalleryOrCameraDialogHelper.kt index 23c2e13f6f..8f70808087 100644 --- a/vector/src/main/java/im/vector/app/core/dialogs/GalleryOrCameraDialogHelper.kt +++ b/vector/src/main/java/im/vector/app/core/dialogs/GalleryOrCameraDialogHelper.kt @@ -113,7 +113,7 @@ class GalleryOrCameraDialogHelper( )) { _, which -> onAvatarTypeSelected(if (which == 0) Type.Camera else Type.Gallery) } - .setPositiveButton(R.string.cancel, null) + .setPositiveButton(R.string.action_cancel, null) .show() } diff --git a/vector/src/main/java/im/vector/app/core/dialogs/ManuallyVerifyDialog.kt b/vector/src/main/java/im/vector/app/core/dialogs/ManuallyVerifyDialog.kt index 979558afd8..9e318bf693 100644 --- a/vector/src/main/java/im/vector/app/core/dialogs/ManuallyVerifyDialog.kt +++ b/vector/src/main/java/im/vector/app/core/dialogs/ManuallyVerifyDialog.kt @@ -34,7 +34,7 @@ object ManuallyVerifyDialog { .setPositiveButton(R.string.encryption_information_verify) { _, _ -> onVerified() } - .setNegativeButton(R.string.cancel, null) + .setNegativeButton(R.string.action_cancel, null) views.encryptedDeviceInfoDeviceName.text = cryptoDeviceInfo.displayName() views.encryptedDeviceInfoDeviceId.text = cryptoDeviceInfo.deviceId diff --git a/vector/src/main/java/im/vector/app/core/dialogs/PhotoOrVideoDialog.kt b/vector/src/main/java/im/vector/app/core/dialogs/PhotoOrVideoDialog.kt index dad1fba600..0b76446ce2 100644 --- a/vector/src/main/java/im/vector/app/core/dialogs/PhotoOrVideoDialog.kt +++ b/vector/src/main/java/im/vector/app/core/dialogs/PhotoOrVideoDialog.kt @@ -57,7 +57,7 @@ class PhotoOrVideoDialog( .setPositiveButton(R.string._continue) { _, _ -> submit(views, vectorPreferences, listener) } - .setNegativeButton(R.string.cancel, null) + .setNegativeButton(R.string.action_cancel, null) .show() } } @@ -98,11 +98,11 @@ class PhotoOrVideoDialog( MaterialAlertDialogBuilder(activity) .setTitle(R.string.option_take_photo_video) .setView(dialogLayout) - .setPositiveButton(R.string.save) { _, _ -> + .setPositiveButton(R.string.action_save) { _, _ -> submitSettings(views) listener.onUpdated() } - .setNegativeButton(R.string.cancel, null) + .setNegativeButton(R.string.action_cancel, null) .show() } diff --git a/vector/src/main/java/im/vector/app/core/dialogs/UnrecognizedCertificateDialog.kt b/vector/src/main/java/im/vector/app/core/dialogs/UnrecognizedCertificateDialog.kt index 39458a054a..f46737d6c6 100644 --- a/vector/src/main/java/im/vector/app/core/dialogs/UnrecognizedCertificateDialog.kt +++ b/vector/src/main/java/im/vector/app/core/dialogs/UnrecognizedCertificateDialog.kt @@ -151,7 +151,7 @@ class UnrecognizedCertificateDialog @Inject constructor( } builder.setNeutralButton(R.string.ssl_logout_account) { _, _ -> callback.onReject() } } else { - builder.setNegativeButton(R.string.cancel) { _, _ -> callback.onReject() } + builder.setNegativeButton(R.string.action_cancel) { _, _ -> callback.onReject() } } builder.setOnDismissListener { diff --git a/vector/src/main/java/im/vector/app/core/utils/ExternalApplicationsUtil.kt b/vector/src/main/java/im/vector/app/core/utils/ExternalApplicationsUtil.kt index 06da70f391..9e7587056f 100644 --- a/vector/src/main/java/im/vector/app/core/utils/ExternalApplicationsUtil.kt +++ b/vector/src/main/java/im/vector/app/core/utils/ExternalApplicationsUtil.kt @@ -308,7 +308,7 @@ fun shareMedia(context: Context, file: File, mediaMimeType: String?) { val chooserIntent = ShareCompat.IntentBuilder(context) .setType(mediaMimeType) .setStream(mediaUri) - .setChooserTitle(R.string.share) + .setChooserTitle(R.string.action_share) .createChooserIntent() createChooser(context, chooserIntent) @@ -318,7 +318,7 @@ fun shareText(context: Context, text: String) { val chooserIntent = ShareCompat.IntentBuilder(context) .setType("text/plain") .setText(text) - .setChooserTitle(R.string.share) + .setChooserTitle(R.string.action_share) .createChooserIntent() createChooser(context, chooserIntent) diff --git a/vector/src/main/java/im/vector/app/core/utils/PermissionsTools.kt b/vector/src/main/java/im/vector/app/core/utils/PermissionsTools.kt index ba396ed252..19dc341f12 100644 --- a/vector/src/main/java/im/vector/app/core/utils/PermissionsTools.kt +++ b/vector/src/main/java/im/vector/app/core/utils/PermissionsTools.kt @@ -168,6 +168,6 @@ fun FragmentActivity.onPermissionDeniedDialog(@StringRes rationaleMessage: Int) .setPositiveButton(R.string.open_settings) { _, _ -> openAppSettingsPage(this) } - .setNegativeButton(R.string.cancel, null) + .setNegativeButton(R.string.action_cancel, null) .show() } diff --git a/vector/src/main/java/im/vector/app/features/MainActivity.kt b/vector/src/main/java/im/vector/app/features/MainActivity.kt index 5f9c5433fe..b4706780b7 100644 --- a/vector/src/main/java/im/vector/app/features/MainActivity.kt +++ b/vector/src/main/java/im/vector/app/features/MainActivity.kt @@ -209,7 +209,7 @@ class MainActivity : VectorBaseActivity(), UnlockedActivity .setTitle(R.string.dialog_title_error) .setMessage(errorFormatter.toHumanReadable(failure)) .setPositiveButton(R.string.global_retry) { _, _ -> doCleanUp() } - .setNegativeButton(R.string.cancel) { _, _ -> startNextActivityAndFinish(ignoreClearCredentials = true) } + .setNegativeButton(R.string.action_cancel) { _, _ -> startNextActivityAndFinish(ignoreClearCredentials = true) } .setCancelable(false) .show() } diff --git a/vector/src/main/java/im/vector/app/features/call/conference/VectorJitsiActivity.kt b/vector/src/main/java/im/vector/app/features/call/conference/VectorJitsiActivity.kt index 0fdfea8bff..a668f66f30 100644 --- a/vector/src/main/java/im/vector/app/features/call/conference/VectorJitsiActivity.kt +++ b/vector/src/main/java/im/vector/app/features/call/conference/VectorJitsiActivity.kt @@ -135,7 +135,7 @@ class VectorJitsiActivity : VectorBaseActivity(), JitsiMee .setPositiveButton(R.string.action_switch) { _, _ -> jitsiViewModel.handle(JitsiCallViewActions.SwitchTo(action.args, false)) } - .setNegativeButton(R.string.cancel, null) + .setNegativeButton(R.string.action_cancel, null) .show() } diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupSettingsFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupSettingsFragment.kt index cf6bd99f23..50d5e56483 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupSettingsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupSettingsFragment.kt @@ -78,7 +78,7 @@ class KeysBackupSettingsFragment @Inject constructor(private val keysBackupSetti .setPositiveButton(R.string.keys_backup_settings_delete_confirm_title) { _, _ -> viewModel.handle(KeyBackupSettingsAction.DeleteKeyBackup) } - .setNegativeButton(R.string.cancel, null) + .setNegativeButton(R.string.action_cancel, null) .setCancelable(true) .show() } diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupActivity.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupActivity.kt index 3d2ef648ff..0db06209fe 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupActivity.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupActivity.kt @@ -177,8 +177,8 @@ class KeysBackupSetupActivity : SimpleFragmentActivity() { MaterialAlertDialogBuilder(this) .setTitle(R.string.keys_backup_setup_skip_title) .setMessage(R.string.keys_backup_setup_skip_msg) - .setNegativeButton(R.string.cancel, null) - .setPositiveButton(R.string.leave) { _, _ -> + .setNegativeButton(R.string.action_cancel, null) + .setPositiveButton(R.string.action_leave) { _, _ -> finish() } .show() diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapBottomSheet.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapBottomSheet.kt index 727180385d..8448422a56 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapBottomSheet.kt @@ -119,7 +119,7 @@ class BootstrapBottomSheet : VectorBaseBottomSheetDialogFragment + .setNegativeButton(R.string.action_skip) { _, _ -> bottomSheetResult = ResultListener.RESULT_CANCEL dismiss() } diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/IncomingVerificationRequestHandler.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/IncomingVerificationRequestHandler.kt index 6c009d3786..14ffda58a9 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/IncomingVerificationRequestHandler.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/IncomingVerificationRequestHandler.kt @@ -92,7 +92,7 @@ class IncomingVerificationRequestHandler @Inject constructor( tx.cancel() } addButton( - context.getString(R.string.ignore), + context.getString(R.string.action_ignore), { tx.cancel() } ) addButton( diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/cancel/VerificationCancelController.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/cancel/VerificationCancelController.kt index 05a5bb7238..f0909dbc26 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/cancel/VerificationCancelController.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/cancel/VerificationCancelController.kt @@ -80,7 +80,7 @@ class VerificationCancelController @Inject constructor( bottomSheetVerificationActionItem { id("cancel") - title(host.stringProvider.getString(R.string.skip)) + title(host.stringProvider.getString(R.string.action_skip)) titleColor(host.colorProvider.getColorFromAttribute(R.attr.colorError)) iconRes(R.drawable.ic_arrow_right) iconColor(host.colorProvider.getColorFromAttribute(R.attr.colorError)) diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/cancel/VerificationNotMeController.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/cancel/VerificationNotMeController.kt index 5b9c82958e..34f97d3cb2 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/cancel/VerificationNotMeController.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/cancel/VerificationNotMeController.kt @@ -56,7 +56,7 @@ class VerificationNotMeController @Inject constructor( bottomSheetVerificationActionItem { id("skip") - title(host.stringProvider.getString(R.string.skip)) + title(host.stringProvider.getString(R.string.action_skip)) titleColor(host.colorProvider.getColorFromAttribute(R.attr.vctr_content_primary)) iconRes(R.drawable.ic_arrow_right) iconColor(host.colorProvider.getColorFromAttribute(R.attr.vctr_content_primary)) diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/request/VerificationRequestController.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/request/VerificationRequestController.kt index d8082865c8..d40ee1f5c9 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/request/VerificationRequestController.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/request/VerificationRequestController.kt @@ -93,7 +93,7 @@ class VerificationRequestController @Inject constructor( bottomSheetVerificationActionItem { id("skip") - title(host.stringProvider.getString(R.string.skip)) + title(host.stringProvider.getString(R.string.action_skip)) titleColor(host.colorProvider.getColorFromAttribute(R.attr.colorError)) iconRes(R.drawable.ic_arrow_right) iconColor(host.colorProvider.getColorFromAttribute(R.attr.colorError)) diff --git a/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsFragment.kt b/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsFragment.kt index 2c37a19e7d..523e8cb9bb 100644 --- a/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsFragment.kt @@ -153,7 +153,7 @@ class DiscoverySettingsFragment @Inject constructor( .setTitle(R.string.change_identity_server) .setMessage(getString(R.string.settings_discovery_disconnect_with_bound_pid, state.identityServer(), state.identityServer())) .setPositiveButton(R.string._continue) { _, _ -> navigateToChangeIdentityServerFragment() } - .setNegativeButton(R.string.cancel, null) + .setNegativeButton(R.string.action_cancel, null) .show() Unit } else { @@ -177,8 +177,8 @@ class DiscoverySettingsFragment @Inject constructor( MaterialAlertDialogBuilder(requireActivity()) .setTitle(R.string.disconnect_identity_server) .setMessage(message) - .setPositiveButton(R.string.disconnect) { _, _ -> viewModel.handle(DiscoverySettingsAction.DisconnectIdentityServer) } - .setNegativeButton(R.string.cancel, null) + .setPositiveButton(R.string.action_disconnect) { _, _ -> viewModel.handle(DiscoverySettingsAction.DisconnectIdentityServer) } + .setNegativeButton(R.string.action_cancel, null) .show() } } diff --git a/vector/src/main/java/im/vector/app/features/discovery/change/SetIdentityServerFragment.kt b/vector/src/main/java/im/vector/app/features/discovery/change/SetIdentityServerFragment.kt index fcea2e92b1..f71f224034 100644 --- a/vector/src/main/java/im/vector/app/features/discovery/change/SetIdentityServerFragment.kt +++ b/vector/src/main/java/im/vector/app/features/discovery/change/SetIdentityServerFragment.kt @@ -119,7 +119,7 @@ class SetIdentityServerFragment @Inject constructor( .setPositiveButton(R.string._continue) { _, _ -> processIdentityServerChange() } - .setNegativeButton(R.string.cancel, null) + .setNegativeButton(R.string.action_cancel, null) .show() Unit } diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt index e2696115f4..293f3426d5 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt @@ -400,7 +400,7 @@ class HomeActivity : dismissedAction = Runnable { homeActivityViewModel.handle(HomeActivityViewActions.PushPromptHasBeenReviewed) } - addButton(getString(R.string.dismiss), { + addButton(getString(R.string.action_dismiss), { homeActivityViewModel.handle(HomeActivityViewActions.PushPromptHasBeenReviewed) }, true) addButton(getString(R.string.settings), { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index 0d454201cd..620730cb4d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -397,7 +397,7 @@ class RoomDetailFragment @Inject constructor( when (mode) { is SendMode.Regular -> renderRegularMode(mode.text) is SendMode.Edit -> renderSpecialMode(mode.timelineEvent, R.drawable.ic_edit, R.string.edit, mode.text) - is SendMode.Quote -> renderSpecialMode(mode.timelineEvent, R.drawable.ic_quote, R.string.quote, mode.text) + is SendMode.Quote -> renderSpecialMode(mode.timelineEvent, R.drawable.ic_quote, R.string.action_quote, mode.text) is SendMode.Reply -> renderSpecialMode(mode.timelineEvent, R.drawable.ic_reply, R.string.reply, mode.text) is SendMode.Voice -> renderVoiceMessageMode(mode.text) } @@ -1054,7 +1054,7 @@ class RoomDetailFragment @Inject constructor( .setPositiveButton(R.string.settings) { _, _ -> navigator.openSettings(requireActivity(), VectorSettingsActivity.EXTRA_DIRECT_ACCESS_GENERAL) } - .setNegativeButton(R.string.cancel, null) + .setNegativeButton(R.string.action_cancel, null) .show() } @@ -1062,7 +1062,7 @@ class RoomDetailFragment @Inject constructor( autoCompleter.exitSpecialMode() views.composerLayout.collapse() views.composerLayout.setTextIfDifferent(content) - views.composerLayout.views.sendButton.contentDescription = getString(R.string.send) + views.composerLayout.views.sendButton.contentDescription = getString(R.string.action_send) } private fun renderSpecialMode(event: TimelineEvent, @@ -1574,7 +1574,7 @@ class RoomDetailFragment @Inject constructor( val reason = views.dialogReportContentInput.text.toString() roomDetailViewModel.handle(RoomDetailAction.ReportContent(action.eventId, action.senderId, reason)) } - .setNegativeButton(R.string.cancel, null) + .setNegativeButton(R.string.action_cancel, null) .show() } @@ -1584,7 +1584,7 @@ class RoomDetailFragment @Inject constructor( activity = requireActivity(), askForReason = action.askForReason, confirmationRes = action.dialogDescriptionRes, - positiveRes = R.string.remove, + positiveRes = R.string.action_remove, reasonHintRes = R.string.delete_event_dialog_reason_hint, titleRes = action.dialogTitleRes ) { reason -> @@ -1704,7 +1704,7 @@ class RoomDetailFragment @Inject constructor( .setPositiveButton(R.string._continue) { _, _ -> openUrlInExternalBrowser(requireContext(), url) } - .setNegativeButton(R.string.cancel, null) + .setNegativeButton(R.string.action_cancel, null) .show() } else { // Open in external browser, in a new Tab @@ -2076,7 +2076,7 @@ class RoomDetailFragment @Inject constructor( MaterialAlertDialogBuilder(requireContext(), R.style.ThemeOverlay_Vector_MaterialAlertDialog) .setTitle(R.string.end_poll_confirmation_title) .setMessage(R.string.end_poll_confirmation_description) - .setNegativeButton(R.string.cancel, null) + .setNegativeButton(R.string.action_cancel, null) .setPositiveButton(R.string.end_poll_confirmation_approve_button) { _, _ -> roomDetailViewModel.handle(RoomDetailAction.EndPoll(eventId)) } @@ -2087,7 +2087,7 @@ class RoomDetailFragment @Inject constructor( MaterialAlertDialogBuilder(requireContext(), R.style.ThemeOverlay_Vector_MaterialAlertDialog_Destructive) .setTitle(R.string.room_participants_action_ignore_title) .setMessage(R.string.room_participants_action_ignore_prompt_msg) - .setNegativeButton(R.string.cancel, null) + .setNegativeButton(R.string.action_cancel, null) .setPositiveButton(R.string.room_participants_action_ignore) { _, _ -> roomDetailViewModel.handle(RoomDetailAction.IgnoreUser(senderId)) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/StartCallActionsHandler.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/StartCallActionsHandler.kt index 92a75b449a..6b5ed3ba66 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/StartCallActionsHandler.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/StartCallActionsHandler.kt @@ -97,7 +97,7 @@ class StartCallActionsHandler( // create the widget, then navigate to it.. roomDetailViewModel.handle(RoomDetailAction.AddJitsiWidget(isVideoCall)) } - .setNegativeButton(fragment.getString(R.string.cancel), null) + .setNegativeButton(fragment.getString(R.string.action_cancel), null) .show() } } @@ -112,7 +112,7 @@ class StartCallActionsHandler( .setPositiveButton(if (isVideoCall) R.string.start_video_call else R.string.start_voice_call) { _, _ -> safeStartCall2(isVideoCall) } - .setNegativeButton(R.string.cancel, null) + .setNegativeButton(R.string.action_cancel, null) .show() } else { safeStartCall2(isVideoCall) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/EventSharedAction.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/EventSharedAction.kt index 30d69d6533..39e04e8ae4 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/EventSharedAction.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/EventSharedAction.kt @@ -43,28 +43,28 @@ sealed class EventSharedAction(@StringRes val titleRes: Int, EventSharedAction(R.string.edit, R.drawable.ic_edit) data class Quote(val eventId: String) : - EventSharedAction(R.string.quote, R.drawable.ic_quote) + EventSharedAction(R.string.action_quote, R.drawable.ic_quote) data class Reply(val eventId: String) : EventSharedAction(R.string.reply, R.drawable.ic_reply) data class Share(val eventId: String, val messageContent: MessageContent) : - EventSharedAction(R.string.share, R.drawable.ic_share) + EventSharedAction(R.string.action_share, R.drawable.ic_share) data class Save(val eventId: String, val messageContent: MessageWithAttachmentContent) : - EventSharedAction(R.string.save, R.drawable.ic_material_save) + EventSharedAction(R.string.action_save, R.drawable.ic_material_save) data class Resend(val eventId: String) : EventSharedAction(R.string.global_retry, R.drawable.ic_refresh_cw) data class Remove(val eventId: String) : - EventSharedAction(R.string.remove, R.drawable.ic_trash, true) + EventSharedAction(R.string.action_remove, R.drawable.ic_trash, true) data class Redact(val eventId: String, val askForReason: Boolean, val dialogTitleRes: Int, val dialogDescriptionRes: Int) : EventSharedAction(R.string.message_action_item_redact, R.drawable.ic_delete, true) data class Cancel(val eventId: String, val force: Boolean) : - EventSharedAction(R.string.cancel, R.drawable.ic_close_round) + EventSharedAction(R.string.action_cancel, R.drawable.ic_close_round) data class ViewSource(val content: String) : EventSharedAction(R.string.view_source, R.drawable.ic_view_source) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/CallTileTimelineItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/CallTileTimelineItem.kt index 5f8ac822da..5abc9d714c 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/CallTileTimelineItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/CallTileTimelineItem.kt @@ -142,7 +142,7 @@ abstract class CallTileTimelineItem : AbsBaseMessageItem { holder.rejectView.isVisible = true - holder.rejectView.setText(R.string.leave) + holder.rejectView.setText(R.string.action_leave) holder.rejectView.setLeftDrawable(R.drawable.ic_call_hangup, R.attr.colorOnPrimary) holder.rejectView.onClick { attributes.callback?.onTimelineItemAction(RoomDetailAction.LeaveJitsiCall) @@ -176,7 +176,7 @@ abstract class CallTileTimelineItem : AbsBaseMessageItem { diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt index f67abfbc88..5171319a41 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt @@ -404,10 +404,10 @@ class RoomListFragment @Inject constructor( MaterialAlertDialogBuilder(requireContext(), if (isPublicRoom) 0 else R.style.ThemeOverlay_Vector_MaterialAlertDialog_Destructive) .setTitle(R.string.room_participants_leave_prompt_title) .setMessage(message) - .setPositiveButton(R.string.leave) { _, _ -> + .setPositiveButton(R.string.action_leave) { _, _ -> roomListViewModel.handle(RoomListAction.LeaveRoom(roomId)) } - .setNegativeButton(R.string.cancel, null) + .setNegativeButton(R.string.action_cancel, null) .show() } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt index bf289c2e7d..ff57544a36 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt @@ -72,7 +72,7 @@ class RoomSummaryItemFactory @Inject constructor(private val displayableEventFor ) .buttonLabel( if (error != null) stringProvider.getString(R.string.global_retry) - else stringProvider.getString(R.string.join) + else stringProvider.getString(R.string.action_join) ) .loading(suggestedRoomJoiningStates[spaceChildInfo.childRoomId] is Loading) .memberCount(spaceChildInfo.activeMemberCount ?: 0) diff --git a/vector/src/main/java/im/vector/app/features/link/LinkHandlerActivity.kt b/vector/src/main/java/im/vector/app/features/link/LinkHandlerActivity.kt index c22f8eb779..2cb41784b7 100644 --- a/vector/src/main/java/im/vector/app/features/link/LinkHandlerActivity.kt +++ b/vector/src/main/java/im/vector/app/features/link/LinkHandlerActivity.kt @@ -121,7 +121,7 @@ class LinkHandlerActivity : VectorBaseActivity() { .setMessage(R.string.error_user_already_logged_in) .setCancelable(false) .setPositiveButton(R.string.logout) { _, _ -> safeSignout(uri) } - .setNegativeButton(R.string.cancel) { _, _ -> finish() } + .setNegativeButton(R.string.action_cancel) { _, _ -> finish() } .show() } diff --git a/vector/src/main/java/im/vector/app/features/login/LoginResetPasswordFragment.kt b/vector/src/main/java/im/vector/app/features/login/LoginResetPasswordFragment.kt index f8b302d5e7..52bd80a16f 100644 --- a/vector/src/main/java/im/vector/app/features/login/LoginResetPasswordFragment.kt +++ b/vector/src/main/java/im/vector/app/features/login/LoginResetPasswordFragment.kt @@ -88,7 +88,7 @@ class LoginResetPasswordFragment @Inject constructor() : AbstractLoginFragment doSubmit() } - .setNegativeButton(R.string.cancel, null) + .setNegativeButton(R.string.action_cancel, null) .show() } else { doSubmit() diff --git a/vector/src/main/java/im/vector/app/features/login/LoginSplashFragment.kt b/vector/src/main/java/im/vector/app/features/login/LoginSplashFragment.kt index 527f9f99b3..dd0bfc0ed6 100644 --- a/vector/src/main/java/im/vector/app/features/login/LoginSplashFragment.kt +++ b/vector/src/main/java/im/vector/app/features/login/LoginSplashFragment.kt @@ -79,7 +79,7 @@ class LoginSplashFragment @Inject constructor( .setPositiveButton(R.string.login_error_homeserver_from_url_not_found_enter_manual) { _, _ -> loginViewModel.handle(LoginAction.OnGetStarted(resetLoginConfig = true)) } - .setNegativeButton(R.string.cancel, null) + .setNegativeButton(R.string.action_cancel, null) .show() } else { super.onError(throwable) diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginResetPasswordFragment2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginResetPasswordFragment2.kt index c00ed7275c..038a12bea7 100644 --- a/vector/src/main/java/im/vector/app/features/login2/LoginResetPasswordFragment2.kt +++ b/vector/src/main/java/im/vector/app/features/login2/LoginResetPasswordFragment2.kt @@ -124,7 +124,7 @@ class LoginResetPasswordFragment2 @Inject constructor() : AbstractLoginFragment2 showWarning = false doSubmit() } - .setNegativeButton(R.string.cancel, null) + .setNegativeButton(R.string.action_cancel, null) .show() } else { doSubmit() diff --git a/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedFragment.kt b/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedFragment.kt index c1f45c6713..92fd26e5e8 100644 --- a/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedFragment.kt +++ b/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedFragment.kt @@ -107,7 +107,7 @@ class AccountCreatedFragment @Inject constructor( val newName = views.editText.text.toString() viewModel.handle(AccountCreatedAction.SetDisplayName(newName)) } - .setNegativeButton(R.string.cancel, null) + .setNegativeButton(R.string.action_cancel, null) .show() } diff --git a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToRoomSpaceFragment.kt b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToRoomSpaceFragment.kt index 26df42dbe1..3b0fc175b0 100644 --- a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToRoomSpaceFragment.kt +++ b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToRoomSpaceFragment.kt @@ -108,7 +108,7 @@ class MatrixToRoomSpaceFragment @Inject constructor( views.matrixToCardMainButton.isVisible = true views.matrixToCardSecondaryButton.isVisible = true views.matrixToCardMainButton.button.text = getString(joinTextRes) - views.matrixToCardSecondaryButton.button.text = getString(R.string.decline) + views.matrixToCardSecondaryButton.button.text = getString(R.string.action_decline) } Membership.JOIN -> { views.matrixToCardMainButton.isVisible = true diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt index 1479e577e7..96e8c2af4b 100755 --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt @@ -691,7 +691,7 @@ class NotificationUtils @Inject constructor(private val context: Context, addAction( R.drawable.vector_notification_reject_invitation, - stringProvider.getString(R.string.reject), + stringProvider.getString(R.string.action_reject), rejectIntentPendingIntent ) @@ -708,7 +708,7 @@ class NotificationUtils @Inject constructor(private val context: Context, ) addAction( R.drawable.vector_notification_accept_invitation, - stringProvider.getString(R.string.join), + stringProvider.getString(R.string.action_join), joinIntentPendingIntent ) diff --git a/vector/src/main/java/im/vector/app/features/pin/PinFragment.kt b/vector/src/main/java/im/vector/app/features/pin/PinFragment.kt index 2309d42f60..a42e88f06d 100644 --- a/vector/src/main/java/im/vector/app/features/pin/PinFragment.kt +++ b/vector/src/main/java/im/vector/app/features/pin/PinFragment.kt @@ -162,7 +162,7 @@ class PinFragment @Inject constructor( .setPositiveButton(getString(R.string.auth_pin_new_pin_action)) { _, _ -> launchResetPinFlow() } - .setNegativeButton(R.string.cancel, null) + .setNegativeButton(R.string.action_cancel, null) .show() } diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileController.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileController.kt index 08eab292a8..ab1c605582 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileController.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileController.kt @@ -302,7 +302,7 @@ class RoomMemberProfileController @Inject constructor( return if (isIgnored) { stringProvider.getString(R.string.unignore) } else { - stringProvider.getString(R.string.ignore) + stringProvider.getString(R.string.action_ignore) } } } diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt index 48823714f5..40b6a0fe9b 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt @@ -168,7 +168,7 @@ class RoomMemberProfileFragment @Inject constructor( .withArgs(roomId = null, otherUserId = startVerification.userId) .show(parentFragmentManager, "VERIF") } - .setNegativeButton(R.string.cancel, null) + .setNegativeButton(R.string.action_cancel, null) .show() } } diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/powerlevel/EditPowerLevelDialogs.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/powerlevel/EditPowerLevelDialogs.kt index e6b898c2b9..926c9fb60e 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/powerlevel/EditPowerLevelDialogs.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/powerlevel/EditPowerLevelDialogs.kt @@ -61,7 +61,7 @@ object EditPowerLevelDialogs { } listener(newValue) } - .setNegativeButton(R.string.cancel, null) + .setNegativeButton(R.string.action_cancel, null) .setOnKeyListener(DialogInterface.OnKeyListener { dialog, keyCode, event -> if (event.action == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) { @@ -96,7 +96,7 @@ object EditPowerLevelDialogs { .setPositiveButton(R.string.room_participants_power_level_demote) { _, _ -> onValidate() } - .setNegativeButton(R.string.cancel, null) + .setNegativeButton(R.string.action_cancel, null) .show() } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt index 5a8519ecb4..ee4d0601c6 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt @@ -235,7 +235,7 @@ class RoomProfileFragment @Inject constructor( MaterialAlertDialogBuilder(requireActivity()) .setTitle(R.string.room_settings_enable_encryption_dialog_title) .setMessage(R.string.room_settings_enable_encryption_dialog_content) - .setNegativeButton(R.string.cancel, null) + .setNegativeButton(R.string.action_cancel, null) .setPositiveButton(R.string.room_settings_enable_encryption_dialog_submit) { _, _ -> roomProfileViewModel.handle(RoomProfileAction.EnableEncryption) } @@ -284,10 +284,10 @@ class RoomProfileFragment @Inject constructor( MaterialAlertDialogBuilder(requireContext(), if (isPublicRoom) 0 else R.style.ThemeOverlay_Vector_MaterialAlertDialog_Destructive) .setTitle(R.string.room_participants_leave_prompt_title) .setMessage(message) - .setPositiveButton(R.string.leave) { _, _ -> + .setPositiveButton(R.string.action_leave) { _, _ -> roomProfileViewModel.handle(RoomProfileAction.LeaveRoom) } - .setNegativeButton(R.string.cancel, null) + .setNegativeButton(R.string.action_cancel, null) .show() } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt index 15686a6848..3128a590ce 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt @@ -135,7 +135,7 @@ class RoomAliasFragment @Inject constructor( MaterialAlertDialogBuilder(requireContext(), R.style.ThemeOverlay_Vector_MaterialAlertDialog_Destructive) .setTitle(R.string.dialog_title_confirmation) .setMessage(getString(R.string.room_alias_unpublish_confirmation, alias)) - .setNegativeButton(R.string.cancel, null) + .setNegativeButton(R.string.action_cancel, null) .setPositiveButton(R.string.action_unpublish) { _, _ -> viewModel.handle(RoomAliasAction.UnpublishAlias(alias)) } @@ -190,8 +190,8 @@ class RoomAliasFragment @Inject constructor( MaterialAlertDialogBuilder(requireContext(), R.style.ThemeOverlay_Vector_MaterialAlertDialog_Destructive) .setTitle(R.string.dialog_title_confirmation) .setMessage(getString(R.string.room_alias_delete_confirmation, alias)) - .setNegativeButton(R.string.cancel, null) - .setPositiveButton(R.string.delete) { _, _ -> + .setNegativeButton(R.string.action_cancel, null) + .setPositiveButton(R.string.action_delete) { _, _ -> viewModel.handle(RoomAliasAction.RemoveLocalAlias(alias)) } .show() diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetSharedAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetSharedAction.kt index d7cb923603..7625972b05 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetSharedAction.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetSharedAction.kt @@ -28,7 +28,7 @@ sealed class RoomAliasBottomSheetSharedAction( VectorSharedAction { data class ShareAlias(val matrixTo: String) : RoomAliasBottomSheetSharedAction( - R.string.share, + R.string.action_share, R.drawable.ic_material_share ) @@ -41,7 +41,7 @@ sealed class RoomAliasBottomSheetSharedAction( ) data class DeleteAlias(val alias: String) : RoomAliasBottomSheetSharedAction( - R.string.delete, + R.string.action_delete, R.drawable.ic_trash_24, true ) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListFragment.kt index 8840f61600..ba7e020ebe 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListFragment.kt @@ -126,8 +126,8 @@ class RoomMemberListFragment @Inject constructor( MaterialAlertDialogBuilder(requireActivity()) .setTitle(R.string.three_pid_revoke_invite_dialog_title) .setMessage(getString(R.string.three_pid_revoke_invite_dialog_content, content.displayName)) - .setNegativeButton(R.string.cancel, null) - .setPositiveButton(R.string.revoke) { _, _ -> + .setNegativeButton(R.string.action_cancel, null) + .setPositiveButton(R.string.action_revoke) { _, _ -> viewModel.handle(RoomMemberListAction.RevokeThreePidInvite(stateKey)) } .show() diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt index 0a5f8f4d9a..3fa0bbaa5b 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt @@ -231,7 +231,7 @@ class RoomSettingsFragment @Inject constructor( .setPositiveButton(R.string.warning_unsaved_change_discard) { _, _ -> viewModel.handle(RoomSettingsAction.Cancel) } - .setNegativeButton(R.string.cancel, null) + .setNegativeButton(R.string.action_cancel, null) .show() true } else { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleFragment.kt index eaf19fe075..4e42cce3ee 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleFragment.kt @@ -60,7 +60,7 @@ class RoomJoinRuleFragment @Inject constructor( .setPositiveButton(R.string.warning_unsaved_change_discard) { _, _ -> requireActivity().finish() } - .setNegativeButton(R.string.cancel, null) + .setNegativeButton(R.string.action_cancel, null) .show() return true } @@ -75,7 +75,7 @@ class RoomJoinRuleFragment @Inject constructor( views.cancelButton.isVisible = true views.positiveButton.text = getString(R.string.warning_unsaved_change_discard) views.positiveButton.isVisible = true - views.positiveButton.text = getString(R.string.save) + views.positiveButton.text = getString(R.string.action_save) views.positiveButton.debouncedClicks { viewModel.handle(RoomJoinRuleChooseRestrictedActions.DoUpdateJoinRules) } diff --git a/vector/src/main/java/im/vector/app/features/settings/BackgroundSyncModeChooserDialog.kt b/vector/src/main/java/im/vector/app/features/settings/BackgroundSyncModeChooserDialog.kt index 95a0422742..c64ade96c0 100644 --- a/vector/src/main/java/im/vector/app/features/settings/BackgroundSyncModeChooserDialog.kt +++ b/vector/src/main/java/im/vector/app/features/settings/BackgroundSyncModeChooserDialog.kt @@ -35,7 +35,7 @@ class BackgroundSyncModeChooserDialog : DialogFragment() { val dialog = MaterialAlertDialogBuilder(requireActivity()) .setTitle(R.string.settings_background_fdroid_sync_mode) .setView(view) - .setPositiveButton(R.string.cancel, null) + .setPositiveButton(R.string.action_cancel, null) .create() views.backgroundSyncModeBattery.setOnClickListener { 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 27548dc756..c572b72ca8 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 @@ -371,7 +371,7 @@ class VectorSettingsGeneralFragment @Inject constructor( .setView(view) .setCancelable(false) .setPositiveButton(R.string.settings_change_password, null) - .setNegativeButton(R.string.cancel, null) + .setNegativeButton(R.string.action_cancel, null) .setOnDismissListener { view.hideKeyboard() } 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 dbea253649..2e2fab06a3 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 @@ -187,7 +187,7 @@ class VectorSettingsPreferencesFragment @Inject constructor( .setTitle(R.string.font_size) .setView(layout) .setPositiveButton(R.string.ok, null) - .setNegativeButton(R.string.cancel, null) + .setNegativeButton(R.string.action_cancel, null) .show() val index = FontScale.getFontScaleValue(activity).index diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetController.kt b/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetController.kt index 22e1e67aa4..b35d32f473 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetController.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetController.kt @@ -272,7 +272,7 @@ class DeviceVerificationInfoBottomSheetController @Inject constructor( } bottomSheetVerificationActionItem { id("rename") - title(host.stringProvider.getString(R.string.rename)) + title(host.stringProvider.getString(R.string.action_rename)) titleColor(host.colorProvider.getColorFromAttribute(R.attr.vctr_content_primary)) iconRes(R.drawable.ic_arrow_right) iconColor(host.colorProvider.getColorFromAttribute(R.attr.vctr_content_primary)) diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/VectorSettingsDevicesFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/VectorSettingsDevicesFragment.kt index 531e9a944b..5bbb03c8a4 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/VectorSettingsDevicesFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/VectorSettingsDevicesFragment.kt @@ -136,7 +136,7 @@ class VectorSettingsDevicesFragment @Inject constructor( viewModel.handle(DevicesAction.Rename(deviceInfo.deviceId!!, newName)) } - .setNegativeButton(R.string.cancel, null) + .setNegativeButton(R.string.action_cancel, null) .show() } diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataFragment.kt index a586e14d99..bce15bbf4b 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataFragment.kt @@ -82,10 +82,10 @@ class AccountDataFragment @Inject constructor( override fun didLongTap(data: UserAccountDataEvent) { MaterialAlertDialogBuilder(requireActivity(), R.style.ThemeOverlay_Vector_MaterialAlertDialog_Destructive) - .setTitle(R.string.delete) + .setTitle(R.string.action_delete) .setMessage(getString(R.string.delete_account_data_warning, data.type)) - .setNegativeButton(R.string.cancel, null) - .setPositiveButton(R.string.delete) { _, _ -> + .setNegativeButton(R.string.action_cancel, null) + .setPositiveButton(R.string.action_delete) { _, _ -> viewModel.handle(AccountDataAction.DeleteAccountData(data.type)) } .show() diff --git a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsFragment.kt index a893f0f508..bdb1fb895f 100644 --- a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsFragment.kt @@ -184,10 +184,10 @@ class ThreePidsSettingsFragment @Inject constructor( override fun deleteThreePid(threePid: ThreePid) { MaterialAlertDialogBuilder(requireActivity(), R.style.ThemeOverlay_Vector_MaterialAlertDialog_Destructive) .setMessage(getString(R.string.settings_remove_three_pid_confirmation_content, threePid.getFormattedValue())) - .setPositiveButton(R.string.remove) { _, _ -> + .setPositiveButton(R.string.action_remove) { _, _ -> viewModel.handle(ThreePidsSettingsAction.DeleteThreePid(threePid)) } - .setNegativeButton(R.string.cancel, null) + .setNegativeButton(R.string.action_cancel, null) .show() } diff --git a/vector/src/main/java/im/vector/app/features/share/IncomingShareFragment.kt b/vector/src/main/java/im/vector/app/features/share/IncomingShareFragment.kt index e1efef4d5a..62fb064536 100644 --- a/vector/src/main/java/im/vector/app/features/share/IncomingShareFragment.kt +++ b/vector/src/main/java/im/vector/app/features/share/IncomingShareFragment.kt @@ -200,10 +200,10 @@ class IncomingShareFragment @Inject constructor( MaterialAlertDialogBuilder(requireActivity()) .setTitle(R.string.send_attachment) .setMessage(getString(R.string.share_confirm_room, roomSummary.displayName)) - .setPositiveButton(R.string.send) { _, _ -> + .setPositiveButton(R.string.action_send) { _, _ -> navigator.openRoomForSharingAndFinish(requireActivity(), roomSummary.roomId, sharedData) } - .setNegativeButton(R.string.cancel, null) + .setNegativeButton(R.string.action_cancel, null) .show() } diff --git a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutFragment.kt b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutFragment.kt index 016d340f80..f40f35a6e2 100644 --- a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutFragment.kt +++ b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutFragment.kt @@ -127,7 +127,7 @@ class SoftLogoutFragment @Inject constructor( MaterialAlertDialogBuilder(requireActivity(), R.style.ThemeOverlay_Vector_MaterialAlertDialog_Destructive) .setTitle(R.string.soft_logout_clear_data_dialog_title) .setMessage(messageResId) - .setNegativeButton(R.string.cancel, null) + .setNegativeButton(R.string.action_cancel, null) .setPositiveButton(R.string.soft_logout_clear_data_submit) { _, _ -> softLogoutViewModel.handle(SoftLogoutAction.ClearData) } diff --git a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryController.kt b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryController.kt index 977294f495..6dadce30e0 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryController.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryController.kt @@ -156,7 +156,7 @@ class SpaceDirectoryController @Inject constructor( when { error != null -> host.stringProvider.getString(R.string.global_retry) isJoined -> host.stringProvider.getString(R.string.action_open) - else -> host.stringProvider.getString(R.string.join) + else -> host.stringProvider.getString(R.string.action_join) } ) apply { diff --git a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt index cd7d6a379a..f630323790 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt @@ -215,7 +215,7 @@ class SpaceDirectoryFragment @Inject constructor( .setPositiveButton(R.string._continue) { _, _ -> openUrlInExternalBrowser(requireContext(), url) } - .setNegativeButton(R.string.cancel, null) + .setNegativeButton(R.string.action_cancel, null) .show() } else { // Open in external browser, in a new Tab diff --git a/vector/src/main/java/im/vector/app/features/spaces/invite/SpaceInviteBottomSheet.kt b/vector/src/main/java/im/vector/app/features/spaces/invite/SpaceInviteBottomSheet.kt index bd6dec7c4b..815175c977 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/invite/SpaceInviteBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/invite/SpaceInviteBottomSheet.kt @@ -120,8 +120,8 @@ class SpaceInviteBottomSheet : VectorBaseBottomSheetDialogFragment { diff --git a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomFragment.kt index 9bf304fa1c..44de356521 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomFragment.kt @@ -138,7 +138,7 @@ class SpaceAddRoomFragment @Inject constructor( .setPositiveButton(R.string.warning_unsaved_change_discard) { _, _ -> sharedViewModel.handle(SpaceManagedSharedAction.HandleBack) } - .setNegativeButton(R.string.cancel, null) + .setNegativeButton(R.string.action_cancel, null) .show() } is SpaceAddRoomsViewEvents.SaveFailed -> { diff --git a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceSettingsFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceSettingsFragment.kt index a0ab055311..a3d0252c19 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceSettingsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceSettingsFragment.kt @@ -165,7 +165,7 @@ class SpaceSettingsFragment @Inject constructor( .setPositiveButton(R.string.warning_unsaved_change_discard) { _, _ -> viewModel.handle(RoomSettingsAction.Cancel) } - .setNegativeButton(R.string.cancel, null) + .setNegativeButton(R.string.action_cancel, null) .show() true } else { diff --git a/vector/src/main/java/im/vector/app/features/widgets/WidgetFragment.kt b/vector/src/main/java/im/vector/app/features/widgets/WidgetFragment.kt index afe34a9b7f..15b9ea61b7 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/WidgetFragment.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/WidgetFragment.kt @@ -309,10 +309,10 @@ class WidgetFragment @Inject constructor() : fun deleteWidget() { MaterialAlertDialogBuilder(requireContext()) .setMessage(R.string.widget_delete_message_confirmation) - .setPositiveButton(R.string.remove) { _, _ -> + .setPositiveButton(R.string.action_remove) { _, _ -> viewModel.handle(WidgetAction.DeleteWidget) } - .setNegativeButton(R.string.cancel, null) + .setNegativeButton(R.string.action_cancel, null) .show() } diff --git a/vector/src/main/java/im/vector/app/features/workers/signout/SignOutUiWorker.kt b/vector/src/main/java/im/vector/app/features/workers/signout/SignOutUiWorker.kt index 59ea37036c..29c094bff4 100644 --- a/vector/src/main/java/im/vector/app/features/workers/signout/SignOutUiWorker.kt +++ b/vector/src/main/java/im/vector/app/features/workers/signout/SignOutUiWorker.kt @@ -43,7 +43,7 @@ class SignOutUiWorker(private val activity: FragmentActivity) { .setPositiveButton(R.string.action_sign_out) { _, _ -> doSignOut() } - .setNegativeButton(R.string.cancel, null) + .setNegativeButton(R.string.action_cancel, null) .show() } } diff --git a/vector/src/main/res/layout/bottom_sheet_leave_space.xml b/vector/src/main/res/layout/bottom_sheet_leave_space.xml index e47639f752..9e5a7c7ebf 100644 --- a/vector/src/main/res/layout/bottom_sheet_leave_space.xml +++ b/vector/src/main/res/layout/bottom_sheet_leave_space.xml @@ -100,6 +100,6 @@ android:layout_marginTop="8dp" android:layout_marginEnd="16dp" android:layout_marginBottom="8dp" - android:text="@string/cancel" /> + android:text="@string/action_cancel" /> \ No newline at end of file diff --git a/vector/src/main/res/layout/bottom_sheet_room_widget_permission.xml b/vector/src/main/res/layout/bottom_sheet_room_widget_permission.xml index 40bb28f3a2..4e5e8392c4 100644 --- a/vector/src/main/res/layout/bottom_sheet_room_widget_permission.xml +++ b/vector/src/main/res/layout/bottom_sheet_room_widget_permission.xml @@ -102,7 +102,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginEnd="@dimen/layout_vertical_margin" - android:text="@string/decline" + android:text="@string/action_decline" android:textAllCaps="true" />