Merge branch 'release/26.05.2'
This commit is contained in:
commit
678b203b96
162 changed files with 2089 additions and 1431 deletions
7
.github/renovate.json5
vendored
7
.github/renovate.json5
vendored
|
|
@ -34,6 +34,13 @@
|
||||||
"/^org.jetbrains.kotlinx:kotlinx-datetime/",
|
"/^org.jetbrains.kotlinx:kotlinx-datetime/",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// Keep Guava on the Android variant and ignore jre-only upgrades.
|
||||||
|
"matchPackageNames": [
|
||||||
|
"com.google.guava:guava",
|
||||||
|
],
|
||||||
|
"allowedVersions": "/-android$/",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
// Limit PostHog Android upgrade to one PR per month, the first day of the month
|
// Limit PostHog Android upgrade to one PR per month, the first day of the month
|
||||||
"matchPackageNames": [
|
"matchPackageNames": [
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ appId: ${MAESTRO_APP_ID}
|
||||||
- tapOn:
|
- tapOn:
|
||||||
text: ${MAESTRO_INVITEE1_MXID}
|
text: ${MAESTRO_INVITEE1_MXID}
|
||||||
index: 1
|
index: 1
|
||||||
- tapOn: "Send invite"
|
- tapOn: "Continue"
|
||||||
- takeScreenshot: build/maestro/330-createAndDeleteDM
|
- takeScreenshot: build/maestro/330-createAndDeleteDM
|
||||||
- tapOn: "maestroelement2"
|
- tapOn: "maestroelement2"
|
||||||
- scroll
|
- scroll
|
||||||
|
|
|
||||||
|
|
@ -24,8 +24,16 @@ appId: ${MAESTRO_APP_ID}
|
||||||
text: ${MAESTRO_INVITEE2_MXID}
|
text: ${MAESTRO_INVITEE2_MXID}
|
||||||
index: 1
|
index: 1
|
||||||
- tapOn: "Invite"
|
- tapOn: "Invite"
|
||||||
|
- runFlow:
|
||||||
|
when:
|
||||||
|
visible: 'Invite new contact to this room?'
|
||||||
|
commands:
|
||||||
|
- tapOn:
|
||||||
|
id: "confirm_invite_unknown"
|
||||||
|
# Close the keyboard if it's still open
|
||||||
|
- tapOn: "Back"
|
||||||
|
# Go back to the room details screen
|
||||||
- tapOn: "Back"
|
- tapOn: "Back"
|
||||||
- tapOn: "aRoomName"
|
|
||||||
- scrollUntilVisible:
|
- scrollUntilVisible:
|
||||||
direction: DOWN
|
direction: DOWN
|
||||||
element:
|
element:
|
||||||
|
|
|
||||||
44
CHANGES.md
44
CHANGES.md
|
|
@ -1,3 +1,47 @@
|
||||||
|
Changes in Element X v26.05.1
|
||||||
|
=============================
|
||||||
|
|
||||||
|
<!-- Release notes generated using configuration in .github/release.yml at v26.05.1 -->
|
||||||
|
|
||||||
|
## What's Changed
|
||||||
|
### ✨ Features
|
||||||
|
* Make Element Call screen work edge-to-edge by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6634
|
||||||
|
### 🙌 Improvements
|
||||||
|
* Stop removing the `logs` dir when clearing cache by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6765
|
||||||
|
* Adapt to new DM definition changes in the SDK by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6748
|
||||||
|
* feat: Update call started timeline item + declined support by @BillCarsonFr in https://github.com/element-hq/element-x-android/pull/6649
|
||||||
|
### 🐛 Bugfixes
|
||||||
|
* Improve pin code UX by @bmarty in https://github.com/element-hq/element-x-android/pull/6744
|
||||||
|
* Use just the other user's avatar for DM details by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6738
|
||||||
|
* Improve `FetchPushForegroundService`'s reliability by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6757
|
||||||
|
* Prevent user from starting Live Location Sharing in thread by @bmarty in https://github.com/element-hq/element-x-android/pull/6767
|
||||||
|
* Fix media playback from the timeline broken when exiting a thread by @bmarty in https://github.com/element-hq/element-x-android/pull/6771
|
||||||
|
* Pin code: remove the key if there is no pin code by @bmarty in https://github.com/element-hq/element-x-android/pull/6780
|
||||||
|
### 🗣 Translations
|
||||||
|
* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/6761
|
||||||
|
### 🚧 In development 🚧
|
||||||
|
* Feature : share live location by @ganfra in https://github.com/element-hq/element-x-android/pull/6741
|
||||||
|
### Dependency upgrades
|
||||||
|
* Update dependency org.matrix.rustcomponents:sdk-android to v26.05.7 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6746
|
||||||
|
* Update actions/add-to-project action to v2 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6758
|
||||||
|
* Update dependency io.github.sergio-sastre.ComposablePreviewScanner:android to v0.9.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6759
|
||||||
|
* Update dependency io.element.android:element-call-embedded to v0.19.3 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6766
|
||||||
|
* Update metro to v1 (major) by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6720
|
||||||
|
* Update tspascoal/get-user-teams-membership action to v4.0.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6750
|
||||||
|
* Update plugin sonarqube to v7.3.0.8198 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6743
|
||||||
|
* Update plugin dependencycheck to v12.2.2 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6760
|
||||||
|
* Update dependency com.google.guava:guava to v33.6.0-android by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6646
|
||||||
|
* Update dependency org.matrix.rustcomponents:sdk-android to v26.05.13 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6779
|
||||||
|
### Others
|
||||||
|
* Render media captions formatting in the media viewer by @bxdxnn in https://github.com/element-hq/element-x-android/pull/6729
|
||||||
|
* Reduce FeatureFlag `Knock` effect on room creation and room edition forms by @bmarty in https://github.com/element-hq/element-x-android/pull/6768
|
||||||
|
* Use the right analytics span as a parent in `checkNetworkConnection` by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6751
|
||||||
|
* Add missing strings `theme.black` by @bmarty in https://github.com/element-hq/element-x-android/pull/6772
|
||||||
|
* Map back button in web view to esc (revive fixed version of: https://github.com/element-hq/element-x-android/pull/6724) by @toger5 in https://github.com/element-hq/element-x-android/pull/6725
|
||||||
|
|
||||||
|
|
||||||
|
**Full Changelog**: https://github.com/element-hq/element-x-android/compare/v26.05.0...v26.05.1
|
||||||
|
|
||||||
Changes in Element X v26.05.0
|
Changes in Element X v26.05.0
|
||||||
=============================
|
=============================
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -382,9 +382,9 @@ class LoggedInFlowNode(
|
||||||
}
|
}
|
||||||
is NavTarget.Room -> {
|
is NavTarget.Room -> {
|
||||||
val joinedRoomCallback = object : JoinedRoomLoadedFlowNode.Callback {
|
val joinedRoomCallback = object : JoinedRoomLoadedFlowNode.Callback {
|
||||||
override fun navigateToRoom(roomId: RoomId, serverNames: List<String>) {
|
override fun navigateToRoom(roomId: RoomId, serverNames: List<String>, clearBackStack: Boolean) {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
attachRoom(roomIdOrAlias = roomId.toRoomIdOrAlias(), serverNames = serverNames, clearBackstack = false)
|
attachRoom(roomIdOrAlias = roomId.toRoomIdOrAlias(), serverNames = serverNames, clearBackstack = clearBackStack)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,7 @@ class JoinedRoomLoadedFlowNode(
|
||||||
plugins = plugins,
|
plugins = plugins,
|
||||||
), DependencyInjectionGraphOwner {
|
), DependencyInjectionGraphOwner {
|
||||||
interface Callback : Plugin {
|
interface Callback : Plugin {
|
||||||
fun navigateToRoom(roomId: RoomId, serverNames: List<String>)
|
fun navigateToRoom(roomId: RoomId, serverNames: List<String>, clearBackStack: Boolean = false)
|
||||||
fun handlePermalinkClick(data: PermalinkData, pushToBackstack: Boolean)
|
fun handlePermalinkClick(data: PermalinkData, pushToBackstack: Boolean)
|
||||||
fun navigateToGlobalNotificationSettings()
|
fun navigateToGlobalNotificationSettings()
|
||||||
fun navigateToDeveloperSettings()
|
fun navigateToDeveloperSettings()
|
||||||
|
|
@ -150,7 +150,7 @@ class JoinedRoomLoadedFlowNode(
|
||||||
callback.navigateToDeveloperSettings()
|
callback.navigateToDeveloperSettings()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun navigateToRoom(roomId: RoomId, serverNames: List<String>) {
|
override fun navigateToRoom(roomId: RoomId, serverNames: List<String>, clearBackStack: Boolean) {
|
||||||
callback.navigateToRoom(roomId, serverNames)
|
callback.navigateToRoom(roomId, serverNames)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ import io.element.android.libraries.matrix.api.permalink.PermalinkData
|
||||||
import io.element.android.tests.testutils.lambda.lambdaError
|
import io.element.android.tests.testutils.lambda.lambdaError
|
||||||
|
|
||||||
class FakeJoinedRoomLoadedFlowNodeCallback : JoinedRoomLoadedFlowNode.Callback {
|
class FakeJoinedRoomLoadedFlowNodeCallback : JoinedRoomLoadedFlowNode.Callback {
|
||||||
override fun navigateToRoom(roomId: RoomId, serverNames: List<String>) = lambdaError()
|
override fun navigateToRoom(roomId: RoomId, serverNames: List<String>, clearBackStack: Boolean) = lambdaError()
|
||||||
override fun handlePermalinkClick(data: PermalinkData, pushToBackstack: Boolean) = lambdaError()
|
override fun handlePermalinkClick(data: PermalinkData, pushToBackstack: Boolean) = lambdaError()
|
||||||
override fun navigateToGlobalNotificationSettings() = lambdaError()
|
override fun navigateToGlobalNotificationSettings() = lambdaError()
|
||||||
override fun navigateToDeveloperSettings() = lambdaError()
|
override fun navigateToDeveloperSettings() = lambdaError()
|
||||||
|
|
|
||||||
2
fastlane/metadata/android/en-US/changelogs/202605020.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/202605020.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
Main changes in this version: bug fixes and improvements.
|
||||||
|
Full changelog: https://github.com/element-hq/element-x-android/releases
|
||||||
|
|
@ -4,5 +4,5 @@
|
||||||
<string name="call_foreground_service_message_android">"Kõne juurde naasmiseks klõpsa"</string>
|
<string name="call_foreground_service_message_android">"Kõne juurde naasmiseks klõpsa"</string>
|
||||||
<string name="call_foreground_service_title_android">"☎️ Kõne on pooleli"</string>
|
<string name="call_foreground_service_title_android">"☎️ Kõne on pooleli"</string>
|
||||||
<string name="call_invalid_audio_device_bluetooth_devices_disabled">"Element Call ei võimalda selles Androidi versioonis Bluetoothi heliseadmete kasutamist. Palun vali mõni muu heliseade."</string>
|
<string name="call_invalid_audio_device_bluetooth_devices_disabled">"Element Call ei võimalda selles Androidi versioonis Bluetoothi heliseadmete kasutamist. Palun vali mõni muu heliseade."</string>
|
||||||
<string name="screen_incoming_call_subtitle_android">"Sissetulev Element Calli kõne"</string>
|
<string name="screen_incoming_call_subtitle_android">"Saabuv Element Calli kõne"</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,34 @@
|
||||||
<string name="screen_create_room_action_create_room">"Cameră nouă"</string>
|
<string name="screen_create_room_action_create_room">"Cameră nouă"</string>
|
||||||
<string name="screen_create_room_add_people_title">"Invitați prieteni"</string>
|
<string name="screen_create_room_add_people_title">"Invitați prieteni"</string>
|
||||||
<string name="screen_create_room_error_creating_room">"A apărut o eroare la crearea camerei"</string>
|
<string name="screen_create_room_error_creating_room">"A apărut o eroare la crearea camerei"</string>
|
||||||
|
<string name="screen_create_room_error_creating_space">"Spațiul nu a putut fi creat din cauza unei erori necunoscute. Încercați din nou mai târziu."</string>
|
||||||
|
<string name="screen_create_room_name_placeholder">"Adăugați un nume…"</string>
|
||||||
|
<string name="screen_create_room_new_room_title">"Cameră nouă"</string>
|
||||||
|
<string name="screen_create_room_new_space_title">"Spațiu nou"</string>
|
||||||
<string name="screen_create_room_private_option_description">"Doar persoanele invitate se pot alătura."</string>
|
<string name="screen_create_room_private_option_description">"Doar persoanele invitate se pot alătura."</string>
|
||||||
|
<string name="screen_create_room_private_option_title">"Privat"</string>
|
||||||
<string name="screen_create_room_public_option_description">"Oricine poate găsi această cameră.
|
<string name="screen_create_room_public_option_description">"Oricine poate găsi această cameră.
|
||||||
Puteți modifica acest lucru oricând în setări."</string>
|
Puteți modifica acest lucru oricând în setări."</string>
|
||||||
|
<string name="screen_create_room_public_option_short_description">"Oricine se poate alătura."</string>
|
||||||
|
<string name="screen_create_room_public_option_title">"Public"</string>
|
||||||
<string name="screen_create_room_room_access_section_knocking_option_description">"Oricine poate cere să se alăture camerei, dar un administrator sau un moderator va trebui să accepte cererea"</string>
|
<string name="screen_create_room_room_access_section_knocking_option_description">"Oricine poate cere să se alăture camerei, dar un administrator sau un moderator va trebui să accepte cererea"</string>
|
||||||
<string name="screen_create_room_room_access_section_knocking_option_title">"Permite solicitarea de alăturare"</string>
|
<string name="screen_create_room_room_access_section_knocking_option_title">"Permite solicitarea de alăturare"</string>
|
||||||
|
<string name="screen_create_room_room_access_section_knocking_restricted_option_description">"Oricine din %1$s se poate alătura, dar oricine altcineva trebuie să solicite acces."</string>
|
||||||
|
<string name="screen_create_room_room_access_section_knocking_restricted_option_title">"Solicitați să vă alăturați"</string>
|
||||||
|
<string name="screen_create_room_room_access_section_private_option_description">"Doar persoanele invitate se pot alătura."</string>
|
||||||
|
<string name="screen_create_room_room_access_section_private_option_title">"Privat"</string>
|
||||||
<string name="screen_create_room_room_access_section_public_option_description">"Oricine se poate alătura acestei camere"</string>
|
<string name="screen_create_room_room_access_section_public_option_description">"Oricine se poate alătura acestei camere"</string>
|
||||||
|
<string name="screen_create_room_room_access_section_public_option_title">"Public"</string>
|
||||||
|
<string name="screen_create_room_room_access_section_restricted_option_description">"Oricine din %1$s se poate alătura."</string>
|
||||||
|
<string name="screen_create_room_room_access_section_restricted_option_title">"Standard"</string>
|
||||||
|
<string name="screen_create_room_room_access_section_title">"Cine are acces"</string>
|
||||||
<string name="screen_create_room_room_address_section_footer">"Pentru ca această cameră să fie vizibilă în directorul de camere publice, veți avea nevoie de o adresă de cameră."</string>
|
<string name="screen_create_room_room_address_section_footer">"Pentru ca această cameră să fie vizibilă în directorul de camere publice, veți avea nevoie de o adresă de cameră."</string>
|
||||||
<string name="screen_create_room_room_address_section_title">"Adresă"</string>
|
<string name="screen_create_room_room_address_section_title">"Adresă"</string>
|
||||||
<string name="screen_create_room_room_visibility_section_title">"Vizibilitatea camerei"</string>
|
<string name="screen_create_room_room_visibility_section_title">"Vizibilitatea camerei"</string>
|
||||||
|
<string name="screen_create_room_space_selection_no_space_description">"(nicun spațiu)"</string>
|
||||||
|
<string name="screen_create_room_space_selection_no_space_option">"Nu adăugați la un spațiu"</string>
|
||||||
|
<string name="screen_create_room_space_selection_no_space_title">"Niciun spațiu selectat"</string>
|
||||||
|
<string name="screen_create_room_space_selection_sheet_title">"Adăugați la spațiu"</string>
|
||||||
<string name="screen_create_room_topic_label">"Subiect (opțional)"</string>
|
<string name="screen_create_room_topic_label">"Subiect (opțional)"</string>
|
||||||
|
<string name="screen_create_room_topic_placeholder">"Adăugați o descriere…"</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
<string name="screen_deactivate_account_confirmation_dialog_content">"Potvrďte prosím, že chcete svůj účet deaktivovat. Tuto akci nelze vrátit zpět."</string>
|
<string name="screen_deactivate_account_confirmation_dialog_content">"Potvrďte prosím, že chcete smazat svůj účet. Tuto akci nelze vrátit zpět."</string>
|
||||||
<string name="screen_deactivate_account_delete_all_messages">"Smazat všechny mé zprávy"</string>
|
<string name="screen_deactivate_account_delete_all_messages">"Smazat všechny mé zprávy"</string>
|
||||||
<string name="screen_deactivate_account_delete_all_messages_notice">"Upozornění: Budoucí uživatelé mohou vidět neúplné konverzace."</string>
|
<string name="screen_deactivate_account_delete_all_messages_notice">"Upozornění: Budoucí uživatelé mohou vidět neúplné konverzace."</string>
|
||||||
<string name="screen_deactivate_account_description">"Deaktivace vašeho účtu je %1$s, což způsobí:"</string>
|
<string name="screen_deactivate_account_description">"Smazání účtu je %1$s, dojde k:"</string>
|
||||||
<string name="screen_deactivate_account_description_bold_part">"nezvratná"</string>
|
<string name="screen_deactivate_account_description_bold_part">"nezvratná"</string>
|
||||||
<string name="screen_deactivate_account_list_item_1">"%1$s váš účet (nemůžete se znovu přihlásit a vaše ID nelze znovu použít)."</string>
|
<string name="screen_deactivate_account_list_item_1">"%1$s váš účet (nemůžete se znovu přihlásit a vaše ID nelze znovu použít)."</string>
|
||||||
<string name="screen_deactivate_account_list_item_1_bold_part">"Trvale zakázat"</string>
|
<string name="screen_deactivate_account_list_item_1_bold_part">"Trvale zakázat"</string>
|
||||||
<string name="screen_deactivate_account_list_item_2">"Odebere vás ze všech chatovacích místností."</string>
|
<string name="screen_deactivate_account_list_item_2">"Odebere vás ze všech chatovacích místností."</string>
|
||||||
<string name="screen_deactivate_account_list_item_3">"Odstraní informace o vašem účtu z našeho serveru identit."</string>
|
<string name="screen_deactivate_account_list_item_3">"Odstraní informace o vašem účtu z našeho serveru identit."</string>
|
||||||
<string name="screen_deactivate_account_list_item_4">"Vaše zprávy budou stále viditelné registrovaným uživatelům, ale nebudou dostupné novým ani neregistrovaným uživatelům, pokud se rozhodnete je smazat."</string>
|
<string name="screen_deactivate_account_list_item_4">"Vaše zprávy budou stále viditelné registrovaným uživatelům, ale nebudou dostupné novým ani neregistrovaným uživatelům, pokud se rozhodnete je smazat."</string>
|
||||||
<string name="screen_deactivate_account_title">"Deaktivovat účet"</string>
|
<string name="screen_deactivate_account_title">"Smazat účet"</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
<string name="screen_deactivate_account_confirmation_dialog_content">"Palun kinnita uuesti, et soovid kustutada oma kasutajakonto. Seda tegevust ei saa tagasi pöörata."</string>
|
<string name="screen_deactivate_account_confirmation_dialog_content">"Palun kinnita uuesti, et soovid kustutada oma kasutajakonto. Seda tegevust ei saa tagasi pöörata."</string>
|
||||||
<string name="screen_deactivate_account_delete_all_messages">"Kustuta kõik minu sõnumid"</string>
|
<string name="screen_deactivate_account_delete_all_messages">"Kustuta kõik minu sõnumid"</string>
|
||||||
<string name="screen_deactivate_account_delete_all_messages_notice">"Hoiatus: tulevased kasutajad võivad näha poolikuid vestlusi."</string>
|
<string name="screen_deactivate_account_delete_all_messages_notice">"Hoiatus: tulevased kasutajad võivad näha poolikuid vestlusi."</string>
|
||||||
<string name="screen_deactivate_account_description">"Sinu konto kasutusest eemaldamine on %1$s ja sellega:"</string>
|
<string name="screen_deactivate_account_description">"Sinu konto kustutamine on %1$s ja sellega:"</string>
|
||||||
<string name="screen_deactivate_account_description_bold_part">"pöördumatu"</string>
|
<string name="screen_deactivate_account_description_bold_part">"pöördumatu"</string>
|
||||||
<string name="screen_deactivate_account_list_item_1">"Sinu kasutajakonto %1$s (sa ei saa enam sellega võrku logida ning kasutajatunnust ei saa enam pruukida)."</string>
|
<string name="screen_deactivate_account_list_item_1">"Sinu kasutajakonto %1$s (sa ei saa enam sellega võrku logida ning kasutajatunnust ei saa enam pruukida)."</string>
|
||||||
<string name="screen_deactivate_account_list_item_1_bold_part">"jäädavalt eemaldatakse kasutusest"</string>
|
<string name="screen_deactivate_account_list_item_1_bold_part">"jäädavalt eemaldatakse kasutusest"</string>
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
<string name="screen_deactivate_account_confirmation_dialog_content">"Vă rugăm să confirmați că doriți să vă dezactivați contul. Această acțiune nu poate fi anulată."</string>
|
<string name="screen_deactivate_account_confirmation_dialog_content">"Vă rugăm să confirmați că doriți să vă ștergeți contul. Această acțiune nu poate fi anulată."</string>
|
||||||
<string name="screen_deactivate_account_delete_all_messages">"Ștergeți toate mesajele mele"</string>
|
<string name="screen_deactivate_account_delete_all_messages">"Ștergeți toate mesajele mele"</string>
|
||||||
<string name="screen_deactivate_account_delete_all_messages_notice">"Avertisment: este posibil ca viitorii utilizatori să vadă conversații incomplete."</string>
|
<string name="screen_deactivate_account_delete_all_messages_notice">"Avertisment: este posibil ca viitorii utilizatori să vadă conversații incomplete."</string>
|
||||||
<string name="screen_deactivate_account_description">"Dezactivarea contului dumneavoastră este %1$s, acesta va:"</string>
|
<string name="screen_deactivate_account_description">"Ștergerea contului dumneavoastră este %1$s, acesta va:"</string>
|
||||||
<string name="screen_deactivate_account_description_bold_part">"ireversibilă"</string>
|
<string name="screen_deactivate_account_description_bold_part">"ireversibilă"</string>
|
||||||
<string name="screen_deactivate_account_list_item_1">"%1$s contul dumneavoastră (nu vă puteți conecta din nou, iar ID-ul dvs. nu poate fi reutilizat)."</string>
|
<string name="screen_deactivate_account_list_item_1">"%1$s contul dumneavoastră (nu vă puteți conecta din nou, iar ID-ul dvs. nu poate fi reutilizat)."</string>
|
||||||
<string name="screen_deactivate_account_list_item_1_bold_part">"Dezactivați permanent"</string>
|
<string name="screen_deactivate_account_list_item_1_bold_part">"Dezactivați permanent"</string>
|
||||||
<string name="screen_deactivate_account_list_item_2">"Îndepărta din toate camerele de chat."</string>
|
<string name="screen_deactivate_account_list_item_2">"Îndepărta din toate camerele de chat."</string>
|
||||||
<string name="screen_deactivate_account_list_item_3">"Șterge informațiile contului dumneavoastră de pe serverul nostru de identitate."</string>
|
<string name="screen_deactivate_account_list_item_3">"Șterge informațiile contului dumneavoastră de pe serverul nostru de identitate."</string>
|
||||||
<string name="screen_deactivate_account_list_item_4">"Mesajele dumneavoastră vor fi în continuare vizibile pentru utilizatorii înregistrați, dar nu vor fi disponibile pentru utilizatorii noi sau neînregistrați dacă alegeți să le ștergeți."</string>
|
<string name="screen_deactivate_account_list_item_4">"Mesajele dumneavoastră vor fi în continuare vizibile pentru utilizatorii înregistrați, dar nu vor fi disponibile pentru utilizatorii noi sau neînregistrați dacă alegeți să le ștergeți."</string>
|
||||||
<string name="screen_deactivate_account_title">"Dezactivați contul"</string>
|
<string name="screen_deactivate_account_title">"Ștergeți contul"</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
<string name="screen_identity_confirmation_cannot_confirm">"Kas kinnitamine pole võimalik?"</string>
|
<string name="screen_identity_confirmation_cannot_confirm">"Kas kinnitamine pole võimalik?"</string>
|
||||||
<string name="screen_identity_confirmation_create_new_recovery_key">"Loo uus taastevõti"</string>
|
<string name="screen_identity_confirmation_create_new_recovery_key">"Loo uus taastevõti"</string>
|
||||||
<string name="screen_identity_confirmation_subtitle">"Krüptitud sõnumivahetuse tagamiseks verifitseeri see seade."</string>
|
<string name="screen_identity_confirmation_subtitle">"Turvalise sõnumside seadistamiseks vali verifitseerimise viis."</string>
|
||||||
<string name="screen_identity_confirmation_title">"Kinnita oma digitaalne identiteet"</string>
|
<string name="screen_identity_confirmation_title">"Kinnita oma digitaalne identiteet"</string>
|
||||||
<string name="screen_identity_confirmation_use_another_device">"Kasuta teist seadet"</string>
|
<string name="screen_identity_confirmation_use_another_device">"Kasuta teist seadet"</string>
|
||||||
<string name="screen_identity_confirmation_use_recovery_key">"Kasuta taastevõtit"</string>
|
<string name="screen_identity_confirmation_use_recovery_key">"Kasuta taastevõtit"</string>
|
||||||
|
|
|
||||||
|
|
@ -83,8 +83,8 @@ class RoomListDataSource(
|
||||||
|
|
||||||
val loadingState = roomList.loadingState
|
val loadingState = roomList.loadingState
|
||||||
|
|
||||||
fun launchIn(coroutineScope: CoroutineScope) {
|
fun launchIn(coroutineScope: CoroutineScope): Job {
|
||||||
roomList
|
return roomList
|
||||||
.summaries
|
.summaries
|
||||||
.onEach { roomSummaries ->
|
.onEach { roomSummaries ->
|
||||||
replaceWith(roomSummaries)
|
replaceWith(roomSummaries)
|
||||||
|
|
@ -212,6 +212,7 @@ class RoomListDataSource(
|
||||||
private suspend fun rebuildAllRoomSummaries() {
|
private suspend fun rebuildAllRoomSummaries() {
|
||||||
lock.withLock {
|
lock.withLock {
|
||||||
roomList.summaries.replayCache.firstOrNull()?.let { roomSummaries ->
|
roomList.summaries.replayCache.firstOrNull()?.let { roomSummaries ->
|
||||||
|
diffCacheUpdater.updateWith(roomSummaries)
|
||||||
buildAndEmitAllRooms(roomSummaries, useCache = false)
|
buildAndEmitAllRooms(roomSummaries, useCache = false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,8 @@
|
||||||
<string name="banner_battery_optimization_title_android">"Sa ei näe kõiki teavitusi?"</string>
|
<string name="banner_battery_optimization_title_android">"Sa ei näe kõiki teavitusi?"</string>
|
||||||
<string name="banner_new_sound_message">"Sinu nutiseadme teavituste heli on uuenenud - see on nüüd selgem, kiirem ja vähem häiriv."</string>
|
<string name="banner_new_sound_message">"Sinu nutiseadme teavituste heli on uuenenud - see on nüüd selgem, kiirem ja vähem häiriv."</string>
|
||||||
<string name="banner_new_sound_title">"Oleme sinu helisid värskendanud"</string>
|
<string name="banner_new_sound_title">"Oleme sinu helisid värskendanud"</string>
|
||||||
<string name="banner_set_up_recovery_content">"Loo uus taastevõti, mida saad kasutada oma krüptitud sõnumite ajaloo taastamisel olukorras, kus kaotad ligipääsu oma seadmetele."</string>
|
<string name="banner_set_up_recovery_content">"Sinu vestlused on automaatselt varundatud kasutades läbivat krüptimist. Kui peaksid kaotama ligipääsu kõikidele oma seadmetele, siis selle varukoopia taastamiseks ja oma digitaalse identiteedi säilitamiseks, on vaja taastevõtit."</string>
|
||||||
<string name="banner_set_up_recovery_submit">"Seadista andmete taastamine"</string>
|
<string name="banner_set_up_recovery_submit">"Seadista taastevõti"</string>
|
||||||
<string name="banner_set_up_recovery_title">"Varunda oma vestlused"</string>
|
<string name="banner_set_up_recovery_title">"Varunda oma vestlused"</string>
|
||||||
<string name="confirm_recovery_key_banner_message">"Säilitamaks ligipääsu vestluste ja krüptovõtmete varukoopiale, palun sisesta kinnituseks oma taastevõti."</string>
|
<string name="confirm_recovery_key_banner_message">"Säilitamaks ligipääsu vestluste ja krüptovõtmete varukoopiale, palun sisesta kinnituseks oma taastevõti."</string>
|
||||||
<string name="confirm_recovery_key_banner_primary_button_title">"Sisesta oma taastevõti"</string>
|
<string name="confirm_recovery_key_banner_primary_button_title">"Sisesta oma taastevõti"</string>
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,7 @@ Nu aveți mesaje necitite!"</string>
|
||||||
<string name="screen_roomlist_mark_as_read">"Marcați ca citită"</string>
|
<string name="screen_roomlist_mark_as_read">"Marcați ca citită"</string>
|
||||||
<string name="screen_roomlist_mark_as_unread">"Marcați ca necitită"</string>
|
<string name="screen_roomlist_mark_as_unread">"Marcați ca necitită"</string>
|
||||||
<string name="screen_roomlist_tombstoned_room_description">"Această cameră a fost modernizată."</string>
|
<string name="screen_roomlist_tombstoned_room_description">"Această cameră a fost modernizată."</string>
|
||||||
|
<string name="screen_roomlist_your_spaces">"Spațiile dumneavoastră"</string>
|
||||||
<string name="session_verification_banner_message">"Se pare că folosiți un dispozitiv nou. Verificați-vă identitatea cu un alt dispozitiv pentru a accesa mesajele dumneavoastră criptate."</string>
|
<string name="session_verification_banner_message">"Se pare că folosiți un dispozitiv nou. Verificați-vă identitatea cu un alt dispozitiv pentru a accesa mesajele dumneavoastră criptate."</string>
|
||||||
<string name="session_verification_banner_title">"Verificați că sunteți dumneavoastră"</string>
|
<string name="session_verification_banner_title">"Verificați că sunteți dumneavoastră"</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,9 @@ import io.element.android.features.home.impl.FakeDateTimeObserver
|
||||||
import io.element.android.libraries.androidutils.system.DateTimeObserver
|
import io.element.android.libraries.androidutils.system.DateTimeObserver
|
||||||
import io.element.android.libraries.dateformatter.test.FakeDateFormatter
|
import io.element.android.libraries.dateformatter.test.FakeDateFormatter
|
||||||
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
||||||
|
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||||
|
import io.element.android.libraries.matrix.test.A_ROOM_ID_2
|
||||||
|
import io.element.android.libraries.matrix.test.A_ROOM_ID_3
|
||||||
import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService
|
import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService
|
||||||
import io.element.android.libraries.matrix.test.room.aRoomSummary
|
import io.element.android.libraries.matrix.test.room.aRoomSummary
|
||||||
import io.element.android.libraries.matrix.test.roomlist.FakeDynamicRoomList
|
import io.element.android.libraries.matrix.test.roomlist.FakeDynamicRoomList
|
||||||
|
|
@ -100,11 +103,169 @@ class RoomListDataSourceTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tracking issue #4182: rooms duplicated in the room list around midnight.
|
||||||
|
*
|
||||||
|
* If the SDK ever leaks a list containing the same roomId twice (the suspected cause of #4182),
|
||||||
|
* the UI mapper's `distinctBy` safety net in [RoomListDataSource.buildAndEmitAllRooms] must
|
||||||
|
* remove the duplicate AND `analyticsService.trackError` must fire so the team can root-cause
|
||||||
|
* it via Sentry.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
fun `when SDK summaries source contains duplicate roomIds, UI layer dedupes and reports trackError`() = runTest {
|
||||||
|
val analyticsService = FakeAnalyticsService()
|
||||||
|
val duplicatedSummaries = listOf(
|
||||||
|
aRoomSummary(roomId = A_ROOM_ID),
|
||||||
|
aRoomSummary(roomId = A_ROOM_ID),
|
||||||
|
aRoomSummary(roomId = A_ROOM_ID_2),
|
||||||
|
)
|
||||||
|
val roomList = FakeDynamicRoomList(summaries = MutableStateFlow(duplicatedSummaries))
|
||||||
|
val roomListService = FakeRoomListService(
|
||||||
|
createRoomListLambda = { roomList }
|
||||||
|
).apply {
|
||||||
|
postState(RoomListService.State.Running)
|
||||||
|
}
|
||||||
|
val roomListDataSource = createRoomListDataSource(
|
||||||
|
roomListService = roomListService,
|
||||||
|
analyticsService = analyticsService,
|
||||||
|
)
|
||||||
|
|
||||||
|
roomListDataSource.roomSummariesFlow.test {
|
||||||
|
roomListDataSource.launchIn(backgroundScope)
|
||||||
|
val list = awaitItem()
|
||||||
|
assertThat(list.map { it.roomId }).containsExactly(A_ROOM_ID, A_ROOM_ID_2).inOrder()
|
||||||
|
assertThat(analyticsService.trackedErrors).hasSize(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tracking issue #4182.
|
||||||
|
*
|
||||||
|
* Targeted scenario: a `DateChanged` tick fires after an initial SDK emit, then a follow-up
|
||||||
|
* SDK emit lands (mimicking "midnight, then a new message arrives"). Even though the diffCache
|
||||||
|
* is bypassed during the rebuild (`useCache = false`), the final state must contain each
|
||||||
|
* roomId exactly once and trackError must not fire on a happy path.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
fun `interleaved date change and SDK update with overlapping content does not produce duplicates`() = runTest {
|
||||||
|
val analyticsService = FakeAnalyticsService()
|
||||||
|
val summariesFlow = MutableStateFlow(
|
||||||
|
listOf(
|
||||||
|
aRoomSummary(roomId = A_ROOM_ID),
|
||||||
|
aRoomSummary(roomId = A_ROOM_ID_2),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val roomList = FakeDynamicRoomList(summaries = summariesFlow)
|
||||||
|
val roomListService = FakeRoomListService(
|
||||||
|
createRoomListLambda = { roomList }
|
||||||
|
).apply {
|
||||||
|
postState(RoomListService.State.Running)
|
||||||
|
}
|
||||||
|
val dateTimeObserver = FakeDateTimeObserver()
|
||||||
|
val roomListDataSource = createRoomListDataSource(
|
||||||
|
roomListService = roomListService,
|
||||||
|
dateTimeObserver = dateTimeObserver,
|
||||||
|
analyticsService = analyticsService,
|
||||||
|
)
|
||||||
|
|
||||||
|
roomListDataSource.roomSummariesFlow.test {
|
||||||
|
roomListDataSource.launchIn(backgroundScope)
|
||||||
|
val initial = awaitItem()
|
||||||
|
assertThat(initial.map { it.roomId }).containsExactly(A_ROOM_ID, A_ROOM_ID_2).inOrder()
|
||||||
|
|
||||||
|
// Midnight ticks while the cache holds [A_ROOM_ID, A_ROOM_ID_2]
|
||||||
|
dateTimeObserver.given(DateTimeObserver.Event.DateChanged(Instant.MIN, Instant.now()))
|
||||||
|
val afterMidnight = awaitItem()
|
||||||
|
assertThat(afterMidnight.map { it.roomId }).containsExactly(A_ROOM_ID, A_ROOM_ID_2).inOrder()
|
||||||
|
|
||||||
|
// A new message bumps A_ROOM_ID — different unread count makes the StateFlow see this
|
||||||
|
// as a new value
|
||||||
|
summariesFlow.value = listOf(
|
||||||
|
aRoomSummary(roomId = A_ROOM_ID, numUnreadMessages = 1),
|
||||||
|
aRoomSummary(roomId = A_ROOM_ID_2),
|
||||||
|
)
|
||||||
|
val afterMessage = awaitItem()
|
||||||
|
assertThat(afterMessage.map { it.roomId }).containsExactly(A_ROOM_ID, A_ROOM_ID_2).inOrder()
|
||||||
|
assertThat(afterMessage.map { it.roomId }.toSet()).hasSize(afterMessage.size)
|
||||||
|
|
||||||
|
// Second midnight rebuild after the new message
|
||||||
|
dateTimeObserver.given(DateTimeObserver.Event.DateChanged(Instant.MIN, Instant.now()))
|
||||||
|
val afterSecondMidnight = awaitItem()
|
||||||
|
assertThat(afterSecondMidnight.map { it.roomId }).containsExactly(A_ROOM_ID, A_ROOM_ID_2).inOrder()
|
||||||
|
assertThat(afterSecondMidnight.map { it.roomId }.toSet()).hasSize(afterSecondMidnight.size)
|
||||||
|
|
||||||
|
assertThat(analyticsService.trackedErrors).isEmpty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `regression test for race with DateTimeObserver and new items`() = runTest {
|
||||||
|
val roomList = FakeDynamicRoomList(summaries = MutableStateFlow(listOf(aRoomSummary(), aRoomSummary(A_ROOM_ID_2))))
|
||||||
|
val roomListService = FakeRoomListService(
|
||||||
|
createRoomListLambda = { roomList }
|
||||||
|
).apply {
|
||||||
|
postState(RoomListService.State.Running)
|
||||||
|
}
|
||||||
|
val dateTimeObserver = FakeDateTimeObserver()
|
||||||
|
var dateFormatterResult = "Today"
|
||||||
|
val dateFormatter = FakeDateFormatter({ _, _, _ -> dateFormatterResult })
|
||||||
|
val roomListDataSource = createRoomListDataSource(
|
||||||
|
roomListService = roomListService,
|
||||||
|
roomListRoomSummaryFactory = aRoomListRoomSummaryFactory(
|
||||||
|
dateFormatter = dateFormatter,
|
||||||
|
),
|
||||||
|
dateTimeObserver = dateTimeObserver,
|
||||||
|
)
|
||||||
|
roomListDataSource.roomSummariesFlow.test {
|
||||||
|
// Observe room list items changes
|
||||||
|
val job = roomListDataSource.launchIn(backgroundScope)
|
||||||
|
// Get the initial room list
|
||||||
|
val initialRoomList = awaitItem()
|
||||||
|
assertThat(initialRoomList).hasSize(2)
|
||||||
|
assertThat(initialRoomList[0].roomId).isEqualTo(A_ROOM_ID)
|
||||||
|
assertThat(initialRoomList[0].timestamp).isEqualTo(dateFormatterResult)
|
||||||
|
assertThat(initialRoomList[1].roomId).isEqualTo(A_ROOM_ID_2)
|
||||||
|
assertThat(initialRoomList[1].timestamp).isEqualTo(dateFormatterResult)
|
||||||
|
|
||||||
|
// Stop processing room list updates so we can force a race condition with the date time observer updates
|
||||||
|
job.cancel()
|
||||||
|
|
||||||
|
// Trigger a date change and a new item at the same time
|
||||||
|
dateFormatterResult = "Yesterday"
|
||||||
|
roomList.summaries.tryEmit(listOf(aRoomSummary(roomId = A_ROOM_ID), aRoomSummary(roomId = A_ROOM_ID_3), aRoomSummary(roomId = A_ROOM_ID_2)))
|
||||||
|
dateTimeObserver.given(DateTimeObserver.Event.DateChanged(Instant.MIN, Instant.now()))
|
||||||
|
|
||||||
|
// The race condition would have caused the cache indices to be corrupted and only 2 items would be emitted
|
||||||
|
val rebuiltRoomList = awaitItem()
|
||||||
|
assertThat(rebuiltRoomList).hasSize(3)
|
||||||
|
assertThat(rebuiltRoomList[0].roomId).isEqualTo(A_ROOM_ID)
|
||||||
|
assertThat(rebuiltRoomList[0].timestamp).isEqualTo(dateFormatterResult)
|
||||||
|
assertThat(rebuiltRoomList[1].roomId).isEqualTo(A_ROOM_ID_3)
|
||||||
|
assertThat(rebuiltRoomList[1].timestamp).isEqualTo(dateFormatterResult)
|
||||||
|
assertThat(rebuiltRoomList[2].roomId).isEqualTo(A_ROOM_ID_2)
|
||||||
|
assertThat(rebuiltRoomList[2].timestamp).isEqualTo(dateFormatterResult)
|
||||||
|
|
||||||
|
// Restart processing room list updates
|
||||||
|
roomListDataSource.launchIn(backgroundScope)
|
||||||
|
|
||||||
|
// Check there is a new list and it's not the same as the previous one
|
||||||
|
val newRoomList = awaitItem()
|
||||||
|
assertThat(newRoomList).hasSize(3)
|
||||||
|
assertThat(newRoomList[0].roomId).isEqualTo(A_ROOM_ID)
|
||||||
|
assertThat(newRoomList[0].timestamp).isEqualTo(dateFormatterResult)
|
||||||
|
assertThat(newRoomList[1].roomId).isEqualTo(A_ROOM_ID_3)
|
||||||
|
assertThat(newRoomList[1].timestamp).isEqualTo(dateFormatterResult)
|
||||||
|
assertThat(newRoomList[2].roomId).isEqualTo(A_ROOM_ID_2)
|
||||||
|
assertThat(newRoomList[2].timestamp).isEqualTo(dateFormatterResult)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun TestScope.createRoomListDataSource(
|
private fun TestScope.createRoomListDataSource(
|
||||||
roomListService: FakeRoomListService = FakeRoomListService(),
|
roomListService: FakeRoomListService = FakeRoomListService(),
|
||||||
roomListRoomSummaryFactory: RoomListRoomSummaryFactory = aRoomListRoomSummaryFactory(),
|
roomListRoomSummaryFactory: RoomListRoomSummaryFactory = aRoomListRoomSummaryFactory(),
|
||||||
notificationSettingsService: FakeNotificationSettingsService = FakeNotificationSettingsService(),
|
notificationSettingsService: FakeNotificationSettingsService = FakeNotificationSettingsService(),
|
||||||
dateTimeObserver: FakeDateTimeObserver = FakeDateTimeObserver(),
|
dateTimeObserver: FakeDateTimeObserver = FakeDateTimeObserver(),
|
||||||
|
analyticsService: FakeAnalyticsService = FakeAnalyticsService(),
|
||||||
) = RoomListDataSource(
|
) = RoomListDataSource(
|
||||||
roomListService = roomListService,
|
roomListService = roomListService,
|
||||||
roomListRoomSummaryFactory = roomListRoomSummaryFactory,
|
roomListRoomSummaryFactory = roomListRoomSummaryFactory,
|
||||||
|
|
@ -112,6 +273,6 @@ class RoomListDataSourceTest {
|
||||||
notificationSettingsService = notificationSettingsService,
|
notificationSettingsService = notificationSettingsService,
|
||||||
sessionCoroutineScope = backgroundScope,
|
sessionCoroutineScope = backgroundScope,
|
||||||
dateTimeObserver = dateTimeObserver,
|
dateTimeObserver = dateTimeObserver,
|
||||||
analyticsService = FakeAnalyticsService(),
|
analyticsService = analyticsService,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,4 +11,5 @@ package io.element.android.features.invitepeople.api
|
||||||
interface InvitePeopleEvents {
|
interface InvitePeopleEvents {
|
||||||
data object SendInvites : InvitePeopleEvents
|
data object SendInvites : InvitePeopleEvents
|
||||||
data object CloseSearch : InvitePeopleEvents
|
data object CloseSearch : InvitePeopleEvents
|
||||||
|
data object ClearError : InvitePeopleEvents
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,12 @@
|
||||||
package io.element.android.features.invitepeople.api
|
package io.element.android.features.invitepeople.api
|
||||||
|
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
|
||||||
interface InvitePeopleState {
|
interface InvitePeopleState {
|
||||||
val canInvite: Boolean
|
val canInvite: Boolean
|
||||||
val isSearchActive: Boolean
|
val isSearchActive: Boolean
|
||||||
val sendInvitesAction: AsyncAction<Unit>
|
val sendInvitesAction: AsyncAction<Unit>
|
||||||
|
val createRoomFromDmAction: AsyncAction<RoomId>
|
||||||
val eventSink: (InvitePeopleEvents) -> Unit
|
val eventSink: (InvitePeopleEvents) -> Unit
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ package io.element.android.features.invitepeople.api
|
||||||
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
|
||||||
class InvitePeopleStateProvider : PreviewParameterProvider<InvitePeopleState> {
|
class InvitePeopleStateProvider : PreviewParameterProvider<InvitePeopleState> {
|
||||||
override val values: Sequence<InvitePeopleState>
|
override val values: Sequence<InvitePeopleState>
|
||||||
|
|
@ -25,6 +26,7 @@ private data class PreviewInvitePeopleState(
|
||||||
override val canInvite: Boolean,
|
override val canInvite: Boolean,
|
||||||
override val isSearchActive: Boolean,
|
override val isSearchActive: Boolean,
|
||||||
override val sendInvitesAction: AsyncAction<Unit>,
|
override val sendInvitesAction: AsyncAction<Unit>,
|
||||||
|
override val createRoomFromDmAction: AsyncAction<RoomId>,
|
||||||
override val eventSink: (InvitePeopleEvents) -> Unit,
|
override val eventSink: (InvitePeopleEvents) -> Unit,
|
||||||
) : InvitePeopleState
|
) : InvitePeopleState
|
||||||
|
|
||||||
|
|
@ -32,10 +34,12 @@ private fun aPreviewInvitePeopleState(
|
||||||
canInvite: Boolean = false,
|
canInvite: Boolean = false,
|
||||||
isSearchActive: Boolean = false,
|
isSearchActive: Boolean = false,
|
||||||
sendInvitesAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
sendInvitesAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
||||||
|
createRoomFromDmAction: AsyncAction<RoomId> = AsyncAction.Uninitialized,
|
||||||
eventSink: (InvitePeopleEvents) -> Unit = {},
|
eventSink: (InvitePeopleEvents) -> Unit = {},
|
||||||
) = PreviewInvitePeopleState(
|
) = PreviewInvitePeopleState(
|
||||||
canInvite = canInvite,
|
canInvite = canInvite,
|
||||||
isSearchActive = isSearchActive,
|
isSearchActive = isSearchActive,
|
||||||
sendInvitesAction = sendInvitesAction,
|
sendInvitesAction = sendInvitesAction,
|
||||||
|
createRoomFromDmAction = createRoomFromDmAction,
|
||||||
eventSink = eventSink
|
eventSink = eventSink
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ dependencies {
|
||||||
implementation(projects.libraries.uiUtils)
|
implementation(projects.libraries.uiUtils)
|
||||||
implementation(projects.libraries.androidutils)
|
implementation(projects.libraries.androidutils)
|
||||||
implementation(projects.libraries.usersearch.api)
|
implementation(projects.libraries.usersearch.api)
|
||||||
|
implementation(projects.libraries.testtags)
|
||||||
implementation(libs.coil.compose)
|
implementation(libs.coil.compose)
|
||||||
implementation(projects.services.apperror.api)
|
implementation(projects.services.apperror.api)
|
||||||
implementation(projects.libraries.featureflag.api)
|
implementation(projects.libraries.featureflag.api)
|
||||||
|
|
|
||||||
|
|
@ -38,12 +38,17 @@ import io.element.android.libraries.di.SessionScope
|
||||||
import io.element.android.libraries.di.annotations.SessionCoroutineScope
|
import io.element.android.libraries.di.annotations.SessionCoroutineScope
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
import io.element.android.libraries.matrix.api.createroom.CreateRoomParameters
|
||||||
|
import io.element.android.libraries.matrix.api.createroom.RoomPreset
|
||||||
import io.element.android.libraries.matrix.api.encryption.identity.IdentityState
|
import io.element.android.libraries.matrix.api.encryption.identity.IdentityState
|
||||||
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMembershipState
|
import io.element.android.libraries.matrix.api.room.RoomMembershipState
|
||||||
import io.element.android.libraries.matrix.api.room.filterMembers
|
import io.element.android.libraries.matrix.api.room.filterMembers
|
||||||
|
import io.element.android.libraries.matrix.api.room.history.RoomHistoryVisibility
|
||||||
|
import io.element.android.libraries.matrix.api.room.join.JoinRule
|
||||||
import io.element.android.libraries.matrix.api.room.recent.getRecentDirectRooms
|
import io.element.android.libraries.matrix.api.room.recent.getRecentDirectRooms
|
||||||
|
import io.element.android.libraries.matrix.api.roomdirectory.RoomVisibility
|
||||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
import io.element.android.libraries.usersearch.api.UserRepository
|
import io.element.android.libraries.usersearch.api.UserRepository
|
||||||
|
|
@ -88,6 +93,7 @@ class DefaultInvitePeoplePresenter(
|
||||||
var searchActive by rememberSaveable { mutableStateOf(false) }
|
var searchActive by rememberSaveable { mutableStateOf(false) }
|
||||||
val showSearchLoader = rememberSaveable { mutableStateOf(false) }
|
val showSearchLoader = rememberSaveable { mutableStateOf(false) }
|
||||||
val sendInvitesAction = remember { mutableStateOf<AsyncAction<Unit>>(AsyncAction.Uninitialized) }
|
val sendInvitesAction = remember { mutableStateOf<AsyncAction<Unit>>(AsyncAction.Uninitialized) }
|
||||||
|
val createRoomFromDmAction = remember { mutableStateOf<AsyncAction<RoomId>>(AsyncAction.Uninitialized) }
|
||||||
|
|
||||||
val recentDirectRooms by produceState(emptyList(), roomMembers.value) {
|
val recentDirectRooms by produceState(emptyList(), roomMembers.value) {
|
||||||
if (roomMembers.value.isSuccess()) {
|
if (roomMembers.value.isSuccess()) {
|
||||||
|
|
@ -208,7 +214,13 @@ class DefaultInvitePeoplePresenter(
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
room.dataOrNull()?.let {
|
room.dataOrNull()?.let {
|
||||||
sessionCoroutineScope.sendInvites(it, selectedUsers.value, sendInvitesAction)
|
sessionCoroutineScope.launch {
|
||||||
|
if (it.isDm()) {
|
||||||
|
createRoomFromDm(it, selectedUsers.value, createRoomFromDmAction)
|
||||||
|
} else {
|
||||||
|
sendInvites(it, selectedUsers.value, sendInvitesAction)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -216,6 +228,10 @@ class DefaultInvitePeoplePresenter(
|
||||||
searchActive = false
|
searchActive = false
|
||||||
queryState.clearText()
|
queryState.clearText()
|
||||||
}
|
}
|
||||||
|
is InvitePeopleEvents.ClearError -> {
|
||||||
|
sendInvitesAction.value = AsyncAction.Uninitialized
|
||||||
|
createRoomFromDmAction.value = AsyncAction.Uninitialized
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -228,6 +244,7 @@ class DefaultInvitePeoplePresenter(
|
||||||
searchResults = searchResults.value,
|
searchResults = searchResults.value,
|
||||||
showSearchLoader = showSearchLoader.value,
|
showSearchLoader = showSearchLoader.value,
|
||||||
sendInvitesAction = sendInvitesAction.value,
|
sendInvitesAction = sendInvitesAction.value,
|
||||||
|
createRoomFromDmAction = createRoomFromDmAction.value,
|
||||||
suggestions = suggestions,
|
suggestions = suggestions,
|
||||||
eventSink = ::handleEvent,
|
eventSink = ::handleEvent,
|
||||||
)
|
)
|
||||||
|
|
@ -254,6 +271,35 @@ class DefaultInvitePeoplePresenter(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun CoroutineScope.createRoomFromDm(
|
||||||
|
currentRoom: JoinedRoom,
|
||||||
|
selectedUsers: List<MatrixUser>,
|
||||||
|
createRoomFromDmAction: MutableState<AsyncAction<RoomId>>,
|
||||||
|
) = launch {
|
||||||
|
createRoomFromDmAction.runUpdatingState {
|
||||||
|
val currentUsers = currentRoom.getMembers(limit = 100).getOrNull().orEmpty()
|
||||||
|
.filter { it.membership.isActive() }
|
||||||
|
val invitees = (currentUsers.map { it.userId } + selectedUsers.map { it.userId })
|
||||||
|
.filter { it != matrixClient.sessionId }
|
||||||
|
.distinct()
|
||||||
|
matrixClient.createRoom(
|
||||||
|
CreateRoomParameters(
|
||||||
|
name = null,
|
||||||
|
topic = null,
|
||||||
|
isEncrypted = true,
|
||||||
|
isDirect = false,
|
||||||
|
visibility = RoomVisibility.Private,
|
||||||
|
preset = RoomPreset.PRIVATE_CHAT,
|
||||||
|
invite = invitees,
|
||||||
|
avatar = null,
|
||||||
|
joinRuleOverride = JoinRule.Invite,
|
||||||
|
historyVisibilityOverride = RoomHistoryVisibility.Invited,
|
||||||
|
isSpace = false,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@JvmName("toggleUserInSelectedUsers")
|
@JvmName("toggleUserInSelectedUsers")
|
||||||
private fun MutableState<ImmutableList<MatrixUser>>.toggleUser(user: MatrixUser) {
|
private fun MutableState<ImmutableList<MatrixUser>>.toggleUser(user: MatrixUser) {
|
||||||
value = if (value.contains(user)) {
|
value = if (value.contains(user)) {
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import io.element.android.features.invitepeople.api.InvitePeopleState
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
||||||
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
|
||||||
|
|
@ -26,6 +27,7 @@ data class DefaultInvitePeopleState(
|
||||||
val selectedUsers: ImmutableList<MatrixUser>,
|
val selectedUsers: ImmutableList<MatrixUser>,
|
||||||
override val isSearchActive: Boolean,
|
override val isSearchActive: Boolean,
|
||||||
override val sendInvitesAction: AsyncAction<Unit>,
|
override val sendInvitesAction: AsyncAction<Unit>,
|
||||||
|
override val createRoomFromDmAction: AsyncAction<RoomId>,
|
||||||
val suggestions: ImmutableList<InvitableUser>,
|
val suggestions: ImmutableList<InvitableUser>,
|
||||||
override val eventSink: (InvitePeopleEvents) -> Unit
|
override val eventSink: (InvitePeopleEvents) -> Unit
|
||||||
) : InvitePeopleState
|
) : InvitePeopleState
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ import io.element.android.libraries.designsystem.preview.USER_NAME_CAROL
|
||||||
import io.element.android.libraries.designsystem.preview.USER_NAME_EVE
|
import io.element.android.libraries.designsystem.preview.USER_NAME_EVE
|
||||||
import io.element.android.libraries.designsystem.preview.USER_NAME_JUSTIN
|
import io.element.android.libraries.designsystem.preview.USER_NAME_JUSTIN
|
||||||
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
||||||
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||||
import io.element.android.libraries.matrix.ui.components.aMatrixUser
|
import io.element.android.libraries.matrix.ui.components.aMatrixUser
|
||||||
import io.element.android.libraries.matrix.ui.components.aMatrixUserList
|
import io.element.android.libraries.matrix.ui.components.aMatrixUserList
|
||||||
|
|
@ -119,6 +120,7 @@ private fun aDefaultInvitePeopleState(
|
||||||
isSearchActive: Boolean = false,
|
isSearchActive: Boolean = false,
|
||||||
showSearchLoader: Boolean = false,
|
showSearchLoader: Boolean = false,
|
||||||
sendInvitesAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
sendInvitesAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
||||||
|
createRoomFromDmAction: AsyncAction<RoomId> = AsyncAction.Uninitialized,
|
||||||
suggestions: List<InvitableUser> = aMatrixUserList()
|
suggestions: List<InvitableUser> = aMatrixUserList()
|
||||||
.take(5)
|
.take(5)
|
||||||
.map { user -> anInvitableUser(matrixUser = user, isSelected = user in selectedUsers) },
|
.map { user -> anInvitableUser(matrixUser = user, isSelected = user in selectedUsers) },
|
||||||
|
|
@ -132,6 +134,7 @@ private fun aDefaultInvitePeopleState(
|
||||||
isSearchActive = isSearchActive,
|
isSearchActive = isSearchActive,
|
||||||
showSearchLoader = showSearchLoader,
|
showSearchLoader = showSearchLoader,
|
||||||
sendInvitesAction = sendInvitesAction,
|
sendInvitesAction = sendInvitesAction,
|
||||||
|
createRoomFromDmAction = createRoomFromDmAction,
|
||||||
suggestions = suggestions.toImmutableList(),
|
suggestions = suggestions.toImmutableList(),
|
||||||
eventSink = {},
|
eventSink = {},
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.imePadding
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
|
|
@ -23,6 +24,7 @@ import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.testTag
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
@ -54,6 +56,8 @@ import io.element.android.libraries.matrix.ui.components.MatrixUserRow
|
||||||
import io.element.android.libraries.matrix.ui.components.SelectedUsersRowList
|
import io.element.android.libraries.matrix.ui.components.SelectedUsersRowList
|
||||||
import io.element.android.libraries.matrix.ui.model.getAvatarData
|
import io.element.android.libraries.matrix.ui.model.getAvatarData
|
||||||
import io.element.android.libraries.matrix.ui.model.getBestName
|
import io.element.android.libraries.matrix.ui.model.getBestName
|
||||||
|
import io.element.android.libraries.testtags.TestTags
|
||||||
|
import io.element.android.libraries.testtags.testTag
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
import io.element.android.libraries.ui.utils.strings.simplePluralStringResource
|
import io.element.android.libraries.ui.utils.strings.simplePluralStringResource
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
|
@ -102,7 +106,7 @@ private fun InvitePeopleContentView(
|
||||||
}
|
}
|
||||||
|
|
||||||
InvitePeopleSearchBar(
|
InvitePeopleSearchBar(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.imePadding().fillMaxWidth(),
|
||||||
queryState = state.searchQuery,
|
queryState = state.searchQuery,
|
||||||
showLoader = state.showSearchLoader,
|
showLoader = state.showSearchLoader,
|
||||||
selectedUsers = state.selectedUsers,
|
selectedUsers = state.selectedUsers,
|
||||||
|
|
@ -298,7 +302,7 @@ private fun InvitePeopleConfirmModal(
|
||||||
text = stringResource(CommonStrings.action_remove),
|
text = stringResource(CommonStrings.action_remove),
|
||||||
onClick = onRemove,
|
onClick = onRemove,
|
||||||
leadingIcon = IconSource.Vector(CompoundIcons.Close()),
|
leadingIcon = IconSource.Vector(CompoundIcons.Close()),
|
||||||
modifier = Modifier.weight(1f)
|
modifier = Modifier.weight(1f).testTag(TestTags.confirmInviteUnknown),
|
||||||
)
|
)
|
||||||
Button(
|
Button(
|
||||||
text = stringResource(CommonStrings.action_invite),
|
text = stringResource(CommonStrings.action_invite),
|
||||||
|
|
|
||||||
|
|
@ -2,4 +2,8 @@
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
<string name="screen_invite_users_already_a_member">"Sa juba oled jututoa liige"</string>
|
<string name="screen_invite_users_already_a_member">"Sa juba oled jututoa liige"</string>
|
||||||
<string name="screen_invite_users_already_invited">"Sa juba oled kutse saanud"</string>
|
<string name="screen_invite_users_already_invited">"Sa juba oled kutse saanud"</string>
|
||||||
|
<string name="screen_invite_users_confirm_dialog_subtitle_multiple_users">"Sul pole hetkel nende kontaktidega ühtegi vestlust. Enne jätkamist kinnita neile siia jututuppa kutse saatmine."</string>
|
||||||
|
<string name="screen_invite_users_confirm_dialog_subtitle_one_user">"Sul pole hetkel selle kontaktiga ühtegi vestlust. Enne jätkamist kinnita talle siia jututuppa kutse saatmine."</string>
|
||||||
|
<string name="screen_invite_users_confirm_dialog_title_mutiple_users">"Kas kutsud uued kontaktid siia jututuppa?"</string>
|
||||||
|
<string name="screen_invite_users_confirm_dialog_title_one_user">"Kas kutsud uue kontakti siia jututuppa?"</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -2,4 +2,8 @@
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
<string name="screen_invite_users_already_a_member">"Deja membru"</string>
|
<string name="screen_invite_users_already_a_member">"Deja membru"</string>
|
||||||
<string name="screen_invite_users_already_invited">"Deja invitat"</string>
|
<string name="screen_invite_users_already_invited">"Deja invitat"</string>
|
||||||
|
<string name="screen_invite_users_confirm_dialog_subtitle_multiple_users">"În prezent, nu aveți nicio conversație cu aceste contacte. Confirmați invitarea lor în această cameră înainte de a continua."</string>
|
||||||
|
<string name="screen_invite_users_confirm_dialog_subtitle_one_user">"În prezent, nu aveți nicio conversație cu acest contact. Confirmați invitarea acestuia în cameră înainte de a continua."</string>
|
||||||
|
<string name="screen_invite_users_confirm_dialog_title_mutiple_users">"Invitați contactele noi în această cameră?"</string>
|
||||||
|
<string name="screen_invite_users_confirm_dialog_title_one_user">"Invitați contactul nou în această cameră?"</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -831,6 +831,54 @@ internal class DefaultInvitePeoplePresenterTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - inviting someone to a DM creates a new room`() = runTest {
|
||||||
|
val alice = aMatrixUser("@alice:example.com")
|
||||||
|
|
||||||
|
val matrixClient = FakeMatrixClient(
|
||||||
|
encryptionService = FakeEncryptionService(
|
||||||
|
getUserIdentityResult = lambdaRecorder { userId: UserId ->
|
||||||
|
Result.success(IdentityState.Pinned)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val presenter = createDefaultInvitePeoplePresenter(
|
||||||
|
coroutineDispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true),
|
||||||
|
matrixClient = matrixClient,
|
||||||
|
joinedRoom = FakeJoinedRoom(
|
||||||
|
baseRoom = FakeBaseRoom(
|
||||||
|
initialRoomInfo = aRoomInfo(isDm = true),
|
||||||
|
getMembersResult = { Result.success(listOf(aRoomMember(userId = alice.userId, membership = RoomMembershipState.JOIN))) },
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
presenter.test {
|
||||||
|
val initialState = awaitItem()
|
||||||
|
skipItems(1)
|
||||||
|
|
||||||
|
// We want to add a new user to a DM
|
||||||
|
initialState.eventSink(DefaultInvitePeopleEvents.ToggleUser(alice))
|
||||||
|
|
||||||
|
// And we send the invites
|
||||||
|
initialState.eventSink(InvitePeopleEvents.SendInvites)
|
||||||
|
|
||||||
|
skipItems(1)
|
||||||
|
|
||||||
|
awaitItemAsDefault().run {
|
||||||
|
assertThat(canInvite).isTrue()
|
||||||
|
assertThat(sendInvitesAction.isUninitialized()).isTrue()
|
||||||
|
// Inviting to a DM should trigger the creation of a new room
|
||||||
|
assertThat(createRoomFromDmAction.isLoading()).isTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
awaitItemAsDefault().run {
|
||||||
|
assertThat(sendInvitesAction.isUninitialized()).isTrue()
|
||||||
|
// Once the room is created, the action should be successful
|
||||||
|
assertThat(createRoomFromDmAction.isSuccess()).isTrue()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun FakeUserRepository.emitStateWithUsers(
|
private suspend fun FakeUserRepository.emitStateWithUsers(
|
||||||
users: List<MatrixUser>,
|
users: List<MatrixUser>,
|
||||||
isSearching: Boolean = false
|
isSearching: Boolean = false
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@
|
||||||
<string name="screen_link_new_device_root_loading_qr_code">"Se încarcă codul QR…"</string>
|
<string name="screen_link_new_device_root_loading_qr_code">"Se încarcă codul QR…"</string>
|
||||||
<string name="screen_link_new_device_root_mobile_device">"Dispozitiv mobil"</string>
|
<string name="screen_link_new_device_root_mobile_device">"Dispozitiv mobil"</string>
|
||||||
<string name="screen_link_new_device_root_title">"Ce tip de dispozitiv doriți să conectați?"</string>
|
<string name="screen_link_new_device_root_title">"Ce tip de dispozitiv doriți să conectați?"</string>
|
||||||
|
<string name="screen_link_new_device_wrong_number_subtitle">"Încercați din nou și asigurați-vă că ați introdus corect codul de 2 cifre. Dacă numerele tot nu se potrivesc, contactați furnizorul contului."</string>
|
||||||
<string name="screen_link_new_device_wrong_number_title">"Numerele nu se potrivesc"</string>
|
<string name="screen_link_new_device_wrong_number_title">"Numerele nu se potrivesc"</string>
|
||||||
<string name="screen_qr_code_login_connection_note_secure_state_description">"Nu a putut fi făcută o conexiune sigură la noul dispozitiv. Dispozitivele existente sunt încă în siguranță și nu trebuie să vă faceți griji cu privire la ele."</string>
|
<string name="screen_qr_code_login_connection_note_secure_state_description">"Nu a putut fi făcută o conexiune sigură la noul dispozitiv. Dispozitivele existente sunt încă în siguranță și nu trebuie să vă faceți griji cu privire la ele."</string>
|
||||||
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"Și acum?"</string>
|
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"Și acum?"</string>
|
||||||
|
|
@ -39,6 +40,8 @@
|
||||||
<string name="screen_qr_code_login_error_cancelled_title">"Cererea de autentificare a fost anulată"</string>
|
<string name="screen_qr_code_login_error_cancelled_title">"Cererea de autentificare a fost anulată"</string>
|
||||||
<string name="screen_qr_code_login_error_declined_subtitle">"Autentificarea a fost refuzată pe celălalt dispozitiv."</string>
|
<string name="screen_qr_code_login_error_declined_subtitle">"Autentificarea a fost refuzată pe celălalt dispozitiv."</string>
|
||||||
<string name="screen_qr_code_login_error_declined_title">"Autentificarea a fost refuzată"</string>
|
<string name="screen_qr_code_login_error_declined_title">"Autentificarea a fost refuzată"</string>
|
||||||
|
<string name="screen_qr_code_login_error_device_already_signed_in_subtitle">"Nu trebuie să faceți nimic altceva."</string>
|
||||||
|
<string name="screen_qr_code_login_error_device_already_signed_in_title">"Celălalt dispozitiv este deja conectat"</string>
|
||||||
<string name="screen_qr_code_login_error_expired_subtitle">"Autentificarea a expirat. Vă rugăm să încercați din nou."</string>
|
<string name="screen_qr_code_login_error_expired_subtitle">"Autentificarea a expirat. Vă rugăm să încercați din nou."</string>
|
||||||
<string name="screen_qr_code_login_error_expired_title">"Autentificarea nu a fost finalizată la timp"</string>
|
<string name="screen_qr_code_login_error_expired_title">"Autentificarea nu a fost finalizată la timp"</string>
|
||||||
<string name="screen_qr_code_login_error_linking_not_suported_subtitle">"Celălalt dispozitiv nu acceptă autentificarea la %s cu un cod QR.
|
<string name="screen_qr_code_login_error_linking_not_suported_subtitle">"Celălalt dispozitiv nu acceptă autentificarea la %s cu un cod QR.
|
||||||
|
|
|
||||||
|
|
@ -40,8 +40,6 @@ import io.element.android.libraries.architecture.runUpdatingState
|
||||||
import io.element.android.libraries.core.extensions.flatMap
|
import io.element.android.libraries.core.extensions.flatMap
|
||||||
import io.element.android.libraries.core.meta.BuildMeta
|
import io.element.android.libraries.core.meta.BuildMeta
|
||||||
import io.element.android.libraries.dateformatter.api.DurationFormatter
|
import io.element.android.libraries.dateformatter.api.DurationFormatter
|
||||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
|
||||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
import io.element.android.libraries.matrix.api.room.CreateTimelineParams
|
import io.element.android.libraries.matrix.api.room.CreateTimelineParams
|
||||||
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
||||||
|
|
@ -66,7 +64,6 @@ class ShareLocationPresenter(
|
||||||
private val messageComposerContext: MessageComposerContext,
|
private val messageComposerContext: MessageComposerContext,
|
||||||
private val locationActions: LocationActions,
|
private val locationActions: LocationActions,
|
||||||
private val buildMeta: BuildMeta,
|
private val buildMeta: BuildMeta,
|
||||||
private val featureFlagService: FeatureFlagService,
|
|
||||||
private val client: MatrixClient,
|
private val client: MatrixClient,
|
||||||
private val durationFormatter: DurationFormatter,
|
private val durationFormatter: DurationFormatter,
|
||||||
private val liveLocationShareManager: ActiveLiveLocationShareManager,
|
private val liveLocationShareManager: ActiveLiveLocationShareManager,
|
||||||
|
|
@ -83,9 +80,6 @@ class ShareLocationPresenter(
|
||||||
override fun present(): ShareLocationState {
|
override fun present(): ShareLocationState {
|
||||||
val permissionsState: PermissionsState = permissionsPresenter.present()
|
val permissionsState: PermissionsState = permissionsPresenter.present()
|
||||||
var trackUserPosition: Boolean by remember { mutableStateOf(permissionsState.isAnyGranted && locationActions.isLocationEnabled()) }
|
var trackUserPosition: Boolean by remember { mutableStateOf(permissionsState.isAnyGranted && locationActions.isLocationEnabled()) }
|
||||||
val isLiveLocationSharingEnabled by remember {
|
|
||||||
featureFlagService.isFeatureEnabledFlow(FeatureFlags.LiveLocationSharing)
|
|
||||||
}.collectAsState(false)
|
|
||||||
val appName by remember { derivedStateOf { buildMeta.applicationName } }
|
val appName by remember { derivedStateOf { buildMeta.applicationName } }
|
||||||
var dialogState: ShareLocationState.Dialog by remember {
|
var dialogState: ShareLocationState.Dialog by remember {
|
||||||
mutableStateOf(ShareLocationState.Dialog.None)
|
mutableStateOf(ShareLocationState.Dialog.None)
|
||||||
|
|
@ -171,7 +165,7 @@ class ShareLocationPresenter(
|
||||||
dialogState = dialogState,
|
dialogState = dialogState,
|
||||||
trackUserLocation = trackUserPosition,
|
trackUserLocation = trackUserPosition,
|
||||||
hasLocationPermission = permissionsState.isAnyGranted,
|
hasLocationPermission = permissionsState.isAnyGranted,
|
||||||
canShareLiveLocation = isLiveLocationSharingEnabled && timelineMode.canShareLiveLocation(),
|
canShareLiveLocation = timelineMode.canShareLiveLocation(),
|
||||||
appName = appName,
|
appName = appName,
|
||||||
startLiveLocationAction = startLiveLocationAction.value,
|
startLiveLocationAction = startLiveLocationAction.value,
|
||||||
eventSink = ::handleEvent,
|
eventSink = ::handleEvent,
|
||||||
|
|
|
||||||
|
|
@ -2,4 +2,5 @@
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
<string name="screen_share_location_live_location_disclaimer_title">"Vaše historie aktuální polohy bude uložena v místnosti a bude viditelná pro členy i po skončení relace."</string>
|
<string name="screen_share_location_live_location_disclaimer_title">"Vaše historie aktuální polohy bude uložena v místnosti a bude viditelná pro členy i po skončení relace."</string>
|
||||||
<string name="screen_share_location_live_location_duration_picker_title">"Zvolte, jak dlouho chcete sdílet svou aktuální polohu."</string>
|
<string name="screen_share_location_live_location_duration_picker_title">"Zvolte, jak dlouho chcete sdílet svou aktuální polohu."</string>
|
||||||
|
<string name="screen_share_location_live_location_missing_permissions">"Nemáte oprávnění sdílet svou aktuální polohu v této místnosti."</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_share_location_live_location_disclaimer_title">"Sinu reaalajas jagatud asukoha ajalugu salvestub siin jututoas ja see on liikmetele nähtav ka pärast jagamissessiooni lõppu."</string>
|
||||||
<string name="screen_share_location_live_location_duration_picker_title">"Vali, kui kaua tahad oma reaalajas jagada."</string>
|
<string name="screen_share_location_live_location_duration_picker_title">"Vali, kui kaua tahad oma reaalajas jagada."</string>
|
||||||
|
<string name="screen_share_location_live_location_missing_permissions">"Sul pole õigust jagada selles jututoas oma asukohta reaalajas"</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_share_location_live_location_disclaimer_title">"Istoricul locațiilor dumneavoastră va fi stocat în cameră și va fi vizibil pentru membri după încheierea sesiunii."</string>
|
||||||
|
<string name="screen_share_location_live_location_duration_picker_title">"Alegeți cât timp doriți să vă partajați locația în timp real."</string>
|
||||||
|
<string name="screen_share_location_live_location_missing_permissions">"Nu aveți permisiunea de a vă partaja locația în această cameră."</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
<string name="screen_share_location_live_location_disclaimer_title">"你实时位置历史将存储在房间中,并于会话结束后对其他成员可见。"</string>
|
<string name="screen_share_location_live_location_disclaimer_title">"你的实时位置历史将存储在房间中,并于会话结束后对其他成员可见。"</string>
|
||||||
<string name="screen_share_location_live_location_duration_picker_title">"选择共享实时位置的时长。"</string>
|
<string name="screen_share_location_live_location_duration_picker_title">"选择共享实时位置的时长。"</string>
|
||||||
<string name="screen_share_location_live_location_missing_permissions">"你无权在此房内共享实时位置。"</string>
|
<string name="screen_share_location_live_location_missing_permissions">"你无权在此房内共享实时位置。"</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@ import io.element.android.features.location.impl.live.LiveLocationStore
|
||||||
import io.element.android.features.location.test.FakeActiveLiveLocationShareManager
|
import io.element.android.features.location.test.FakeActiveLiveLocationShareManager
|
||||||
import io.element.android.features.messages.test.FakeMessageComposerContext
|
import io.element.android.features.messages.test.FakeMessageComposerContext
|
||||||
import io.element.android.libraries.dateformatter.test.FakeDurationFormatter
|
import io.element.android.libraries.dateformatter.test.FakeDurationFormatter
|
||||||
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
|
||||||
import io.element.android.libraries.matrix.api.timeline.Timeline
|
import io.element.android.libraries.matrix.api.timeline.Timeline
|
||||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||||
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
||||||
|
|
@ -50,7 +49,6 @@ class DefaultShareLocationEntryPointTest {
|
||||||
messageComposerContext = FakeMessageComposerContext(),
|
messageComposerContext = FakeMessageComposerContext(),
|
||||||
locationActions = FakeLocationActions(),
|
locationActions = FakeLocationActions(),
|
||||||
buildMeta = aBuildMeta(),
|
buildMeta = aBuildMeta(),
|
||||||
featureFlagService = FakeFeatureFlagService(),
|
|
||||||
client = FakeMatrixClient(),
|
client = FakeMatrixClient(),
|
||||||
durationFormatter = FakeDurationFormatter(),
|
durationFormatter = FakeDurationFormatter(),
|
||||||
liveLocationShareManager = FakeActiveLiveLocationShareManager(),
|
liveLocationShareManager = FakeActiveLiveLocationShareManager(),
|
||||||
|
|
|
||||||
|
|
@ -29,8 +29,6 @@ import io.element.android.features.location.impl.live.LiveLocationStore
|
||||||
import io.element.android.features.location.test.FakeActiveLiveLocationShareManager
|
import io.element.android.features.location.test.FakeActiveLiveLocationShareManager
|
||||||
import io.element.android.features.messages.test.FakeMessageComposerContext
|
import io.element.android.features.messages.test.FakeMessageComposerContext
|
||||||
import io.element.android.libraries.dateformatter.test.FakeDurationFormatter
|
import io.element.android.libraries.dateformatter.test.FakeDurationFormatter
|
||||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
|
||||||
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
|
||||||
import io.element.android.libraries.matrix.api.core.EventId
|
import io.element.android.libraries.matrix.api.core.EventId
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.libraries.matrix.api.core.SessionId
|
import io.element.android.libraries.matrix.api.core.SessionId
|
||||||
|
|
@ -77,7 +75,6 @@ class ShareLocationPresenterTest {
|
||||||
private val fakeMessageComposerContext = FakeMessageComposerContext()
|
private val fakeMessageComposerContext = FakeMessageComposerContext()
|
||||||
private val fakeLocationActions = FakeLocationActions()
|
private val fakeLocationActions = FakeLocationActions()
|
||||||
private val fakeBuildMeta = aBuildMeta(applicationName = "app name")
|
private val fakeBuildMeta = aBuildMeta(applicationName = "app name")
|
||||||
private val fakeFeatureFlagService = FakeFeatureFlagService()
|
|
||||||
private val fakeMatrixClient = FakeMatrixClient(sessionId = A_USER_ID)
|
private val fakeMatrixClient = FakeMatrixClient(sessionId = A_USER_ID)
|
||||||
|
|
||||||
private val durationFormatter = FakeDurationFormatter()
|
private val durationFormatter = FakeDurationFormatter()
|
||||||
|
|
@ -96,7 +93,6 @@ class ShareLocationPresenterTest {
|
||||||
messageComposerContext = fakeMessageComposerContext,
|
messageComposerContext = fakeMessageComposerContext,
|
||||||
locationActions = locationActions,
|
locationActions = locationActions,
|
||||||
buildMeta = fakeBuildMeta,
|
buildMeta = fakeBuildMeta,
|
||||||
featureFlagService = fakeFeatureFlagService,
|
|
||||||
client = fakeMatrixClient,
|
client = fakeMatrixClient,
|
||||||
durationFormatter = durationFormatter,
|
durationFormatter = durationFormatter,
|
||||||
liveLocationShareManager = liveLocationShareManager,
|
liveLocationShareManager = liveLocationShareManager,
|
||||||
|
|
@ -658,21 +654,7 @@ class ShareLocationPresenterTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `canShareLiveLocation is false when the feature is disabled`() = runTest {
|
fun `canShareLiveLocation is true in live timeline`() = runTest {
|
||||||
fakeFeatureFlagService.setFeatureEnabled(FeatureFlags.LiveLocationSharing, false)
|
|
||||||
val shareLocationPresenter = createShareLocationPresenter(
|
|
||||||
timelineMode = Timeline.Mode.Live,
|
|
||||||
)
|
|
||||||
shareLocationPresenter.test {
|
|
||||||
skipItems(1)
|
|
||||||
val state = awaitItem()
|
|
||||||
assertThat(state.canShareLiveLocation).isFalse()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `canShareLiveLocation is true when the feature is enabled`() = runTest {
|
|
||||||
fakeFeatureFlagService.setFeatureEnabled(FeatureFlags.LiveLocationSharing, true)
|
|
||||||
val shareLocationPresenter = createShareLocationPresenter(
|
val shareLocationPresenter = createShareLocationPresenter(
|
||||||
timelineMode = Timeline.Mode.Live,
|
timelineMode = Timeline.Mode.Live,
|
||||||
)
|
)
|
||||||
|
|
@ -685,7 +667,6 @@ class ShareLocationPresenterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `canShareLiveLocation is false in thread timeline`() = runTest {
|
fun `canShareLiveLocation is false in thread timeline`() = runTest {
|
||||||
fakeFeatureFlagService.setFeatureEnabled(FeatureFlags.LiveLocationSharing, true)
|
|
||||||
val shareLocationPresenter = createShareLocationPresenter(
|
val shareLocationPresenter = createShareLocationPresenter(
|
||||||
timelineMode = Timeline.Mode.Thread(A_THREAD_ID),
|
timelineMode = Timeline.Mode.Thread(A_THREAD_ID),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ class DefaultLockScreenService(
|
||||||
private val coroutineScope: CoroutineScope,
|
private val coroutineScope: CoroutineScope,
|
||||||
private val sessionObserver: SessionObserver,
|
private val sessionObserver: SessionObserver,
|
||||||
private val appForegroundStateService: AppForegroundStateService,
|
private val appForegroundStateService: AppForegroundStateService,
|
||||||
biometricAuthenticatorManager: BiometricAuthenticatorManager,
|
private val biometricAuthenticatorManager: BiometricAuthenticatorManager,
|
||||||
) : LockScreenService {
|
) : LockScreenService {
|
||||||
private val _lockState = MutableStateFlow<LockScreenLockState>(LockScreenLockState.Unlocked)
|
private val _lockState = MutableStateFlow<LockScreenLockState>(LockScreenLockState.Unlocked)
|
||||||
override val lockState: StateFlow<LockScreenLockState> = _lockState
|
override val lockState: StateFlow<LockScreenLockState> = _lockState
|
||||||
|
|
@ -81,6 +81,7 @@ class DefaultLockScreenService(
|
||||||
override suspend fun onSessionDeleted(userId: String, wasLastSession: Boolean) {
|
override suspend fun onSessionDeleted(userId: String, wasLastSession: Boolean) {
|
||||||
if (wasLastSession) {
|
if (wasLastSession) {
|
||||||
pinCodeManager.deletePinCode()
|
pinCodeManager.deletePinCode()
|
||||||
|
biometricAuthenticatorManager.disable()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,11 @@ interface BiometricAuthenticatorManager {
|
||||||
fun addCallback(callback: BiometricAuthenticator.Callback)
|
fun addCallback(callback: BiometricAuthenticator.Callback)
|
||||||
fun removeCallback(callback: BiometricAuthenticator.Callback)
|
fun removeCallback(callback: BiometricAuthenticator.Callback)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disable using the biometric unlock feature and remove any data associated with it.
|
||||||
|
*/
|
||||||
|
suspend fun disable()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remember a biometric authenticator ready for unlocking the app.
|
* Remember a biometric authenticator ready for unlocking the app.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -80,10 +80,7 @@ class DefaultBiometricAuthenticatorManager(
|
||||||
|
|
||||||
private val internalCallback = object : DefaultBiometricUnlockCallback() {
|
private val internalCallback = object : DefaultBiometricUnlockCallback() {
|
||||||
override fun onBiometricSetupError() {
|
override fun onBiometricSetupError() {
|
||||||
coroutineScope.launch {
|
coroutineScope.launch { disable() }
|
||||||
lockScreenStore.setIsBiometricUnlockAllowed(false)
|
|
||||||
secretKeyRepository.deleteKey(SECRET_KEY_ALIAS)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -120,6 +117,11 @@ class DefaultBiometricAuthenticatorManager(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun disable() {
|
||||||
|
lockScreenStore.setIsBiometricUnlockAllowed(false)
|
||||||
|
secretKeyRepository.deleteKey(SECRET_KEY_ALIAS)
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun rememberBiometricAuthenticator(
|
private fun rememberBiometricAuthenticator(
|
||||||
isAvailable: Boolean,
|
isAvailable: Boolean,
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,7 @@ class LockScreenSettingsPresenter(
|
||||||
if (showRemovePinConfirmation) {
|
if (showRemovePinConfirmation) {
|
||||||
showRemovePinConfirmation = false
|
showRemovePinConfirmation = false
|
||||||
pinCodeManager.deletePinCode()
|
pinCodeManager.deletePinCode()
|
||||||
|
biometricAuthenticatorManager.disable()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,5 +34,5 @@ Vali midagi, mis hästi meelde jääb. Kui unustad selle PIN-koodi, siis turvaka
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="screen_app_lock_use_biometric_android">"Kasuta biomeetriat"</string>
|
<string name="screen_app_lock_use_biometric_android">"Kasuta biomeetriat"</string>
|
||||||
<string name="screen_app_lock_use_pin_android">"Kasuta PIN-koodi"</string>
|
<string name="screen_app_lock_use_pin_android">"Kasuta PIN-koodi"</string>
|
||||||
<string name="screen_signout_in_progress_dialog_content">"Logime välja…"</string>
|
<string name="screen_signout_in_progress_dialog_content">"Eemaldan seadet…"</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ class FakeBiometricAuthenticatorManager(
|
||||||
override var isDeviceSecured: Boolean = true,
|
override var isDeviceSecured: Boolean = true,
|
||||||
override var hasAvailableAuthenticator: Boolean = false,
|
override var hasAvailableAuthenticator: Boolean = false,
|
||||||
private val createBiometricAuthenticator: () -> BiometricAuthenticator = { FakeBiometricAuthenticator() },
|
private val createBiometricAuthenticator: () -> BiometricAuthenticator = { FakeBiometricAuthenticator() },
|
||||||
|
private val disableLambda: suspend () -> Unit = { },
|
||||||
) : BiometricAuthenticatorManager {
|
) : BiometricAuthenticatorManager {
|
||||||
override fun addCallback(callback: BiometricAuthenticator.Callback) {
|
override fun addCallback(callback: BiometricAuthenticator.Callback) {
|
||||||
// no-op
|
// no-op
|
||||||
|
|
@ -37,4 +38,8 @@ class FakeBiometricAuthenticatorManager(
|
||||||
createBiometricAuthenticator()
|
createBiometricAuthenticator()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun disable() {
|
||||||
|
disableLambda()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,6 @@ dependencies {
|
||||||
implementation(projects.libraries.core)
|
implementation(projects.libraries.core)
|
||||||
implementation(projects.libraries.androidutils)
|
implementation(projects.libraries.androidutils)
|
||||||
implementation(projects.libraries.architecture)
|
implementation(projects.libraries.architecture)
|
||||||
implementation(projects.libraries.featureflag.api)
|
|
||||||
implementation(projects.libraries.matrix.api)
|
implementation(projects.libraries.matrix.api)
|
||||||
implementation(projects.libraries.matrix.api)
|
implementation(projects.libraries.matrix.api)
|
||||||
implementation(projects.libraries.designsystem)
|
implementation(projects.libraries.designsystem)
|
||||||
|
|
@ -81,7 +80,6 @@ dependencies {
|
||||||
testImplementation(projects.features.login.test)
|
testImplementation(projects.features.login.test)
|
||||||
testImplementation(projects.features.enterprise.test)
|
testImplementation(projects.features.enterprise.test)
|
||||||
testImplementation(projects.features.preferences.test)
|
testImplementation(projects.features.preferences.test)
|
||||||
testImplementation(projects.libraries.featureflag.test)
|
|
||||||
testImplementation(projects.libraries.matrix.test)
|
testImplementation(projects.libraries.matrix.test)
|
||||||
testImplementation(projects.libraries.oauth.test)
|
testImplementation(projects.libraries.oauth.test)
|
||||||
testImplementation(projects.libraries.permissions.test)
|
testImplementation(projects.libraries.permissions.test)
|
||||||
|
|
|
||||||
|
|
@ -28,8 +28,6 @@ import io.element.android.libraries.androidutils.service.ServiceBinder
|
||||||
import io.element.android.libraries.core.log.logger.LoggerTag
|
import io.element.android.libraries.core.log.logger.LoggerTag
|
||||||
import io.element.android.libraries.core.uri.ensureProtocol
|
import io.element.android.libraries.core.uri.ensureProtocol
|
||||||
import io.element.android.libraries.di.annotations.AppCoroutineScope
|
import io.element.android.libraries.di.annotations.AppCoroutineScope
|
||||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
|
||||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
|
||||||
import io.element.android.libraries.matrix.api.auth.ElementClassicSession
|
import io.element.android.libraries.matrix.api.auth.ElementClassicSession
|
||||||
import io.element.android.libraries.matrix.api.auth.HomeServerLoginCompatibilityChecker
|
import io.element.android.libraries.matrix.api.auth.HomeServerLoginCompatibilityChecker
|
||||||
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
|
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
|
||||||
|
|
@ -71,7 +69,6 @@ class DefaultElementClassicConnection(
|
||||||
private val coroutineScope: CoroutineScope,
|
private val coroutineScope: CoroutineScope,
|
||||||
private val matrixAuthenticationService: MatrixAuthenticationService,
|
private val matrixAuthenticationService: MatrixAuthenticationService,
|
||||||
private val homeServerLoginCompatibilityChecker: HomeServerLoginCompatibilityChecker,
|
private val homeServerLoginCompatibilityChecker: HomeServerLoginCompatibilityChecker,
|
||||||
private val featureFlagService: FeatureFlagService,
|
|
||||||
) : ElementClassicConnection {
|
) : ElementClassicConnection {
|
||||||
// Messenger for communicating with the service.
|
// Messenger for communicating with the service.
|
||||||
private var messenger: Messenger? = null
|
private var messenger: Messenger? = null
|
||||||
|
|
@ -119,10 +116,6 @@ class DefaultElementClassicConnection(
|
||||||
override fun start() {
|
override fun start() {
|
||||||
Timber.tag(loggerTag.value).d("start()")
|
Timber.tag(loggerTag.value).d("start()")
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
if (!featureFlagService.isFeatureEnabled(FeatureFlags.SignInWithClassic)) {
|
|
||||||
Timber.tag(loggerTag.value).d("Login with Element Classic is disabled, not starting connection")
|
|
||||||
return@launch
|
|
||||||
}
|
|
||||||
// Establish a connection with the service. We use an explicit
|
// Establish a connection with the service. We use an explicit
|
||||||
// class name because there is no reason to be able to let other
|
// class name because there is no reason to be able to let other
|
||||||
// applications replace our component.
|
// applications replace our component.
|
||||||
|
|
@ -158,11 +151,6 @@ class DefaultElementClassicConnection(
|
||||||
override fun requestSession() {
|
override fun requestSession() {
|
||||||
Timber.tag(loggerTag.value).d("requestSession()")
|
Timber.tag(loggerTag.value).d("requestSession()")
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
if (!featureFlagService.isFeatureEnabled(FeatureFlags.SignInWithClassic)) {
|
|
||||||
Timber.tag(loggerTag.value).d("Login with Element Classic is disabled")
|
|
||||||
emitState(ElementClassicConnectionState.Error("The feature is disabled"))
|
|
||||||
return@launch
|
|
||||||
}
|
|
||||||
val finalMessenger = messenger
|
val finalMessenger = messenger
|
||||||
if (finalMessenger == null) {
|
if (finalMessenger == null) {
|
||||||
Timber.tag(loggerTag.value).d("The messenger is null, can't request data")
|
Timber.tag(loggerTag.value).d("The messenger is null, can't request data")
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@
|
||||||
<string name="screen_change_server_subtitle">"Jaká je adresa vašeho serveru?"</string>
|
<string name="screen_change_server_subtitle">"Jaká je adresa vašeho serveru?"</string>
|
||||||
<string name="screen_change_server_title">"Vyberte váš server"</string>
|
<string name="screen_change_server_title">"Vyberte váš server"</string>
|
||||||
<string name="screen_create_account_title">"Vytvořit účet"</string>
|
<string name="screen_create_account_title">"Vytvořit účet"</string>
|
||||||
<string name="screen_login_error_deactivated_account">"Tento účet byl deaktivován."</string>
|
<string name="screen_login_error_deactivated_account">"Tento účet byl smazán."</string>
|
||||||
<string name="screen_login_error_invalid_credentials">"Nesprávné uživatelské jméno nebo heslo"</string>
|
<string name="screen_login_error_invalid_credentials">"Nesprávné uživatelské jméno nebo heslo"</string>
|
||||||
<string name="screen_login_error_invalid_user_id">"Toto není platný identifikátor uživatele. Očekávaný formát: \'@user:homeserver.org\'"</string>
|
<string name="screen_login_error_invalid_user_id">"Toto není platný identifikátor uživatele. Očekávaný formát: \'@user:homeserver.org\'"</string>
|
||||||
<string name="screen_login_error_refresh_tokens">"Tento server je nakonfigurován tak, aby používal obnovovací tokeny. Ty nejsou podporovány při použití přihlašovacích údajů založených na hesle."</string>
|
<string name="screen_login_error_refresh_tokens">"Tento server je nakonfigurován tak, aby používal obnovovací tokeny. Ty nejsou podporovány při použití přihlašovacích údajů založených na hesle."</string>
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@
|
||||||
<string name="screen_change_server_subtitle">"Mis on sinu koduserveri aadress?"</string>
|
<string name="screen_change_server_subtitle">"Mis on sinu koduserveri aadress?"</string>
|
||||||
<string name="screen_change_server_title">"Vali oma server"</string>
|
<string name="screen_change_server_title">"Vali oma server"</string>
|
||||||
<string name="screen_create_account_title">"Loo kasutajakonto"</string>
|
<string name="screen_create_account_title">"Loo kasutajakonto"</string>
|
||||||
<string name="screen_login_error_deactivated_account">"Konto on kasutusest eemaldatud."</string>
|
<string name="screen_login_error_deactivated_account">"See kasutajakonto on kustutatud."</string>
|
||||||
<string name="screen_login_error_invalid_credentials">"Vigane kasutajanimi ja/või salasõna"</string>
|
<string name="screen_login_error_invalid_credentials">"Vigane kasutajanimi ja/või salasõna"</string>
|
||||||
<string name="screen_login_error_invalid_user_id">"See ei ole korrektne kasutajanimi. Õige vorming on: „@kasutaja:koduserver.ee“"</string>
|
<string name="screen_login_error_invalid_user_id">"See ei ole korrektne kasutajanimi. Õige vorming on: „@kasutaja:koduserver.ee“"</string>
|
||||||
<string name="screen_login_error_refresh_tokens">"See server on seadistatud kasutama tunnusloa põhist sisselogimist. Salasõnaga sisselogimisel see võimalus aga ei ole toetatud."</string>
|
<string name="screen_login_error_refresh_tokens">"See server on seadistatud kasutama tunnusloa põhist sisselogimist. Salasõnaga sisselogimisel see võimalus aga ei ole toetatud."</string>
|
||||||
|
|
@ -39,7 +39,13 @@
|
||||||
<string name="screen_login_title_with_homeserver">"Logi sisse serverisse %1$s"</string>
|
<string name="screen_login_title_with_homeserver">"Logi sisse serverisse %1$s"</string>
|
||||||
<string name="screen_missing_key_backup_open_element_classic">"Ava Element Classic"</string>
|
<string name="screen_missing_key_backup_open_element_classic">"Ava Element Classic"</string>
|
||||||
<string name="screen_missing_key_backup_step_1">"Ava Element Classic oma seadmes"</string>
|
<string name="screen_missing_key_backup_step_1">"Ava Element Classic oma seadmes"</string>
|
||||||
|
<string name="screen_missing_key_backup_step_2_android">"Ava „Seadistused“ → „Turvalisus ja privaatsus“"</string>
|
||||||
|
<string name="screen_missing_key_backup_step_3_android">"Krüptovõtmete halduses vali „Krüptitud sõnumite taastamine“"</string>
|
||||||
|
<string name="screen_missing_key_backup_step_4">"Võtmehoidla kasutuselevõtmiseks palun järgi juhendit"</string>
|
||||||
|
<string name="screen_missing_key_backup_step_5">"Tule tagasi rakendusse %1$s"</string>
|
||||||
|
<string name="screen_missing_key_backup_title">"Enne jätkamist rakenduses %1$s võta oma võtmehoidla kasutusele"</string>
|
||||||
<string name="screen_onboarding_app_version">"Versioon %1$s"</string>
|
<string name="screen_onboarding_app_version">"Versioon %1$s"</string>
|
||||||
|
<string name="screen_onboarding_checking_account">"Kontrollin kasutajakontot"</string>
|
||||||
<string name="screen_onboarding_sign_in_manually">"Logi sisse käsitsi"</string>
|
<string name="screen_onboarding_sign_in_manually">"Logi sisse käsitsi"</string>
|
||||||
<string name="screen_onboarding_sign_in_to">"Logi sisse serverisse %1$s"</string>
|
<string name="screen_onboarding_sign_in_to">"Logi sisse serverisse %1$s"</string>
|
||||||
<string name="screen_onboarding_sign_in_with_qr_code">"Logi sisse QR-koodi alusel"</string>
|
<string name="screen_onboarding_sign_in_with_qr_code">"Logi sisse QR-koodi alusel"</string>
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@
|
||||||
<string name="screen_change_server_subtitle">"Care este adresa serverului dumneavoastră?"</string>
|
<string name="screen_change_server_subtitle">"Care este adresa serverului dumneavoastră?"</string>
|
||||||
<string name="screen_change_server_title">"Selectați serverul dumneavoastra"</string>
|
<string name="screen_change_server_title">"Selectați serverul dumneavoastra"</string>
|
||||||
<string name="screen_create_account_title">"Creați un cont"</string>
|
<string name="screen_create_account_title">"Creați un cont"</string>
|
||||||
<string name="screen_login_error_deactivated_account">"Acest cont a fost dezactivat."</string>
|
<string name="screen_login_error_deactivated_account">"Acest cont a fost șters."</string>
|
||||||
<string name="screen_login_error_invalid_credentials">"Utilizator și/sau parolă incorecte"</string>
|
<string name="screen_login_error_invalid_credentials">"Utilizator și/sau parolă incorecte"</string>
|
||||||
<string name="screen_login_error_invalid_user_id">"Acesta nu este un identificator de utilizator valid. Format așteptat: „@user:homeserver.org”"</string>
|
<string name="screen_login_error_invalid_user_id">"Acesta nu este un identificator de utilizator valid. Format așteptat: „@user:homeserver.org”"</string>
|
||||||
<string name="screen_login_error_refresh_tokens">"Acest server este configurat pentru a utiliza token-uri de reîmprospătare. Acestea nu sunt acceptate atunci când utilizați autentificare bazată pe parolă."</string>
|
<string name="screen_login_error_refresh_tokens">"Acest server este configurat pentru a utiliza token-uri de reîmprospătare. Acestea nu sunt acceptate atunci când utilizați autentificare bazată pe parolă."</string>
|
||||||
|
|
@ -37,11 +37,20 @@
|
||||||
<string name="screen_login_subtitle">"Matrix este o rețea deschisă pentru o comunicare sigură și descentralizată."</string>
|
<string name="screen_login_subtitle">"Matrix este o rețea deschisă pentru o comunicare sigură și descentralizată."</string>
|
||||||
<string name="screen_login_title">"Bine ați revenit!"</string>
|
<string name="screen_login_title">"Bine ați revenit!"</string>
|
||||||
<string name="screen_login_title_with_homeserver">"Conectați-vă la %1$s"</string>
|
<string name="screen_login_title_with_homeserver">"Conectați-vă la %1$s"</string>
|
||||||
|
<string name="screen_missing_key_backup_open_element_classic">"Deschideți Element Clasic"</string>
|
||||||
|
<string name="screen_missing_key_backup_step_1">"Deschideți Element Classic pe dispozitivul dumneavoastră"</string>
|
||||||
|
<string name="screen_missing_key_backup_step_2_android">"Accesați Setări > Securitate și confidențialitate"</string>
|
||||||
|
<string name="screen_missing_key_backup_step_3_android">"În Gestionarea cheilor criptografice, selectați Recuperarea mesajelor criptate"</string>
|
||||||
|
<string name="screen_missing_key_backup_step_4">"Urmați instrucțiunile pentru a activa stocarea cheilor"</string>
|
||||||
|
<string name="screen_missing_key_backup_step_5">"Reveniți la %1$s"</string>
|
||||||
|
<string name="screen_missing_key_backup_title">"Activați stocarea cheilor înainte de a continua către %1$s"</string>
|
||||||
<string name="screen_onboarding_app_version">"Versiunea %1$s"</string>
|
<string name="screen_onboarding_app_version">"Versiunea %1$s"</string>
|
||||||
|
<string name="screen_onboarding_checking_account">"Se verifică contul…"</string>
|
||||||
<string name="screen_onboarding_sign_in_manually">"Conectați-vă manual"</string>
|
<string name="screen_onboarding_sign_in_manually">"Conectați-vă manual"</string>
|
||||||
<string name="screen_onboarding_sign_in_to">"Conectați-vă la %1$s"</string>
|
<string name="screen_onboarding_sign_in_to">"Conectați-vă la %1$s"</string>
|
||||||
<string name="screen_onboarding_sign_in_with_qr_code">"Conectați-vă cu un cod QR"</string>
|
<string name="screen_onboarding_sign_in_with_qr_code">"Conectați-vă cu un cod QR"</string>
|
||||||
<string name="screen_onboarding_sign_up">"Creați un cont"</string>
|
<string name="screen_onboarding_sign_up">"Creați un cont"</string>
|
||||||
|
<string name="screen_onboarding_welcome_back">"Bine ați revenit"</string>
|
||||||
<string name="screen_onboarding_welcome_message">"Bine ați venit la cel mai rapid %1$s din toate timpurile. Supraalimentat pentru viteză și simplitate."</string>
|
<string name="screen_onboarding_welcome_message">"Bine ați venit la cel mai rapid %1$s din toate timpurile. Supraalimentat pentru viteză și simplitate."</string>
|
||||||
<string name="screen_onboarding_welcome_subtitle">"Bun venit în %1$s. Supraalimentat, pentru viteză și simplitate."</string>
|
<string name="screen_onboarding_welcome_subtitle">"Bun venit în %1$s. Supraalimentat, pentru viteză și simplitate."</string>
|
||||||
<string name="screen_onboarding_welcome_title">"Fii în Elementul tău"</string>
|
<string name="screen_onboarding_welcome_title">"Fii în Elementul tău"</string>
|
||||||
|
|
@ -60,6 +69,8 @@
|
||||||
<string name="screen_qr_code_login_error_cancelled_title">"Cererea de autentificare a fost anulată"</string>
|
<string name="screen_qr_code_login_error_cancelled_title">"Cererea de autentificare a fost anulată"</string>
|
||||||
<string name="screen_qr_code_login_error_declined_subtitle">"Autentificarea a fost refuzată pe celălalt dispozitiv."</string>
|
<string name="screen_qr_code_login_error_declined_subtitle">"Autentificarea a fost refuzată pe celălalt dispozitiv."</string>
|
||||||
<string name="screen_qr_code_login_error_declined_title">"Autentificarea a fost refuzată"</string>
|
<string name="screen_qr_code_login_error_declined_title">"Autentificarea a fost refuzată"</string>
|
||||||
|
<string name="screen_qr_code_login_error_device_already_signed_in_subtitle">"Nu trebuie să faceți nimic altceva."</string>
|
||||||
|
<string name="screen_qr_code_login_error_device_already_signed_in_title">"Celălalt dispozitiv este deja conectat"</string>
|
||||||
<string name="screen_qr_code_login_error_expired_subtitle">"Autentificarea a expirat. Vă rugăm să încercați din nou."</string>
|
<string name="screen_qr_code_login_error_expired_subtitle">"Autentificarea a expirat. Vă rugăm să încercați din nou."</string>
|
||||||
<string name="screen_qr_code_login_error_expired_title">"Autentificarea nu a fost finalizată la timp"</string>
|
<string name="screen_qr_code_login_error_expired_title">"Autentificarea nu a fost finalizată la timp"</string>
|
||||||
<string name="screen_qr_code_login_error_linking_not_suported_subtitle">"Celălalt dispozitiv nu acceptă autentificarea la %s cu un cod QR.
|
<string name="screen_qr_code_login_error_linking_not_suported_subtitle">"Celălalt dispozitiv nu acceptă autentificarea la %s cu un cod QR.
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,6 @@ import androidx.core.graphics.createBitmap
|
||||||
import app.cash.turbine.test
|
import app.cash.turbine.test
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.libraries.androidutils.service.ServiceBinder
|
import io.element.android.libraries.androidutils.service.ServiceBinder
|
||||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
|
||||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
|
||||||
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
|
||||||
import io.element.android.libraries.matrix.api.auth.ElementClassicSession
|
import io.element.android.libraries.matrix.api.auth.ElementClassicSession
|
||||||
import io.element.android.libraries.matrix.api.auth.HomeServerLoginCompatibilityChecker
|
import io.element.android.libraries.matrix.api.auth.HomeServerLoginCompatibilityChecker
|
||||||
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
|
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
|
||||||
|
|
@ -112,21 +109,6 @@ class DefaultElementClassicConnectionTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `requestSession when the feature is disabled emits an error`() = runTest {
|
|
||||||
val connection = createDefaultElementClassicConnection(
|
|
||||||
matrixAuthenticationService = FakeMatrixAuthenticationService(
|
|
||||||
setElementClassicSessionResult = {},
|
|
||||||
),
|
|
||||||
isFeatureEnabled = false,
|
|
||||||
)
|
|
||||||
connection.stateFlow.test {
|
|
||||||
assertThat(awaitItem()).isEqualTo(ElementClassicConnectionState.Idle)
|
|
||||||
connection.requestSession()
|
|
||||||
assertThat(awaitItem()).isInstanceOf(ElementClassicConnectionState.Error::class.java)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when an error is received, an error is emitted`() = runTest {
|
fun `when an error is received, an error is emitted`() = runTest {
|
||||||
val connection = createDefaultElementClassicConnection(
|
val connection = createDefaultElementClassicConnection(
|
||||||
|
|
@ -514,17 +496,10 @@ class DefaultElementClassicConnectionTest {
|
||||||
homeServerLoginCompatibilityChecker: HomeServerLoginCompatibilityChecker = FakeHomeServerLoginCompatibilityChecker(
|
homeServerLoginCompatibilityChecker: HomeServerLoginCompatibilityChecker = FakeHomeServerLoginCompatibilityChecker(
|
||||||
checkResult = { Result.success(true) }
|
checkResult = { Result.success(true) }
|
||||||
),
|
),
|
||||||
isFeatureEnabled: Boolean = true,
|
|
||||||
featureFlagService: FeatureFlagService = FakeFeatureFlagService(
|
|
||||||
initialState = mapOf(
|
|
||||||
FeatureFlags.SignInWithClassic.key to isFeatureEnabled,
|
|
||||||
)
|
|
||||||
),
|
|
||||||
) = DefaultElementClassicConnection(
|
) = DefaultElementClassicConnection(
|
||||||
serviceBinder = serviceBinder,
|
serviceBinder = serviceBinder,
|
||||||
coroutineScope = coroutineScope,
|
coroutineScope = coroutineScope,
|
||||||
matrixAuthenticationService = matrixAuthenticationService,
|
matrixAuthenticationService = matrixAuthenticationService,
|
||||||
homeServerLoginCompatibilityChecker = homeServerLoginCompatibilityChecker,
|
homeServerLoginCompatibilityChecker = homeServerLoginCompatibilityChecker,
|
||||||
featureFlagService = featureFlagService,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,18 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
<string name="screen_signout_confirmation_dialog_content">"Kas sa oled kindel, et soovid välja logida?"</string>
|
<string name="screen_signout_confirmation_dialog_content">"Kas sa oled kindel, et soovid selle seadme eemaldada?"</string>
|
||||||
<string name="screen_signout_confirmation_dialog_submit">"Eemalda see seade"</string>
|
<string name="screen_signout_confirmation_dialog_submit">"Eemalda see seade"</string>
|
||||||
<string name="screen_signout_confirmation_dialog_title">"Eemalda see seade"</string>
|
<string name="screen_signout_confirmation_dialog_title">"Eemalda see seade"</string>
|
||||||
<string name="screen_signout_in_progress_dialog_content">"Logime välja…"</string>
|
<string name="screen_signout_in_progress_dialog_content">"Eemaldan seadet…"</string>
|
||||||
<string name="screen_signout_key_backup_disabled_subtitle">"Oled oma viimasest seansist välja logimas. Kui logid nüüd välja, kaotad ligipääsu oma krüptitud sõnumitele."</string>
|
<string name="screen_signout_key_backup_disabled_subtitle">"See on sinu ainus seade. Kui sa selle eemaldad, vajad taastamisvõtit, et kinnitada oma digitaalset identiteeti ja taastada järgmisel sisselogimisel oma krüptitud vestlused."</string>
|
||||||
<string name="screen_signout_key_backup_disabled_title">"Sa oled varukoopiate tegemise välja lülitanud"</string>
|
<string name="screen_signout_key_backup_disabled_title">"Sa kaotad peagi juurdepääsu oma krüptitud vestlustele"</string>
|
||||||
<string name="screen_signout_key_backup_offline_subtitle">"Kui su võrguühendus katkes, siis sinu krüptovõtmed oli parasjagu varundamisel. Loo võrguühendus uuesti, oota kuni krüptovõtmete varundamine lõppeb ja alles siis logi rakendusest välja."</string>
|
<string name="screen_signout_key_backup_offline_subtitle">"Kui su võrguühendus katkes, siis sinu krüptovõtmed oli parasjagu varundamisel. Loo võrguühendus uuesti, oota kuni krüptovõtmete varundamine lõppeb ja alles siis eemalda see seade."</string>
|
||||||
<string name="screen_signout_key_backup_offline_title">"Sinu krüptovõtmed on veel varundamisel"</string>
|
<string name="screen_signout_key_backup_offline_title">"Sinu krüptovõtmed on veel varundamisel"</string>
|
||||||
<string name="screen_signout_key_backup_ongoing_subtitle">"Enne väljalogimist palun oota, et pooleliolev toiming lõppeb."</string>
|
<string name="screen_signout_key_backup_ongoing_subtitle">"Enne selle seadme eemaldamist palun oota, et pooleliolev toiming lõppeb."</string>
|
||||||
<string name="screen_signout_key_backup_ongoing_title">"Sinu krüptovõtmed on veel varundamisel"</string>
|
<string name="screen_signout_key_backup_ongoing_title">"Sinu krüptovõtmed on veel varundamisel"</string>
|
||||||
<string name="screen_signout_preference_item">"Eemalda see seade"</string>
|
<string name="screen_signout_preference_item">"Eemalda see seade"</string>
|
||||||
<string name="screen_signout_recovery_disabled_subtitle">"Sa oled logimas välja oma viimasest sessioonist. Kui teed seda nüüd, siis kaotad ligipääsu oma krüptitud sõnumitele."</string>
|
<string name="screen_signout_recovery_disabled_subtitle">"See on sinu ainus seade. Kui sa selle eemaldad, vajad taastamisvõtit, et kinnitada oma digitaalset identiteeti ja taastada järgmisel sisselogimisel oma krüptitud vestlused."</string>
|
||||||
<string name="screen_signout_recovery_disabled_title">"Andmete taastamine on seadistamata"</string>
|
<string name="screen_signout_recovery_disabled_title">"Andmete taastamine on seadistamata"</string>
|
||||||
<string name="screen_signout_save_recovery_key_subtitle">"Sa oled logimas välja oma viimasest sessioonist. Kui teed seda nüüd, siis ilmselt kaotad ligipääsu oma krüptitud sõnumitele."</string>
|
<string name="screen_signout_save_recovery_key_subtitle">"Sa oled logimas välja oma viimasest sessioonist. Kui teed seda nüüd, siis ilmselt kaotad ligipääsu oma krüptitud sõnumitele."</string>
|
||||||
<string name="screen_signout_save_recovery_key_title">"Kas sa oled oma taastevõtme salvestanud?"</string>
|
<string name="screen_signout_save_recovery_key_title">"Enne selle seadme eemaldamist veendu, et sul on juurdepääs taastevõtmele"</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
<string name="crypto_event_authenticity_mismatched_sender">"Odesílatel události se neshoduje s vlastníkem zařízení, které ji odeslalo."</string>
|
<string name="crypto_event_authenticity_mismatched_sender">"Odesílatel události se neshoduje s vlastníkem zařízení, které ji odeslalo."</string>
|
||||||
<string name="crypto_event_authenticity_not_guaranteed">"Autenticitu této zašifrované zprávy nelze na tomto zařízení zaručit."</string>
|
<string name="crypto_event_authenticity_not_guaranteed">"Pravost této šifrované zprávy nelze na tomto zařízení zaručit."</string>
|
||||||
<string name="crypto_event_authenticity_previously_verified">"Zašifrováno dříve ověřeným uživatelem."</string>
|
<string name="crypto_event_authenticity_previously_verified">"Zašifrováno dříve ověřeným uživatelem."</string>
|
||||||
<string name="crypto_event_authenticity_sent_in_clear">"Není zašifrováno."</string>
|
<string name="crypto_event_authenticity_sent_in_clear">"Není zašifrováno."</string>
|
||||||
<string name="crypto_event_authenticity_unknown_device">"Šifrováno neznámým nebo smazaným zařízením."</string>
|
<string name="crypto_event_authenticity_unknown_device">"Šifrováno neznámým nebo smazaným zařízením."</string>
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@
|
||||||
<string name="screen_room_attachment_source_camera_video">"Salvesta video"</string>
|
<string name="screen_room_attachment_source_camera_video">"Salvesta video"</string>
|
||||||
<string name="screen_room_attachment_source_files">"Manus"</string>
|
<string name="screen_room_attachment_source_files">"Manus"</string>
|
||||||
<string name="screen_room_attachment_source_gallery">"Fotode ja videote galerii"</string>
|
<string name="screen_room_attachment_source_gallery">"Fotode ja videote galerii"</string>
|
||||||
<string name="screen_room_attachment_source_location">"Asukoht"</string>
|
<string name="screen_room_attachment_source_location">"Jaga asukohta"</string>
|
||||||
<string name="screen_room_attachment_source_poll">"Küsitlus"</string>
|
<string name="screen_room_attachment_source_poll">"Küsitlus"</string>
|
||||||
<string name="screen_room_attachment_text_formatting">"Tekstivorming"</string>
|
<string name="screen_room_attachment_text_formatting">"Tekstivorming"</string>
|
||||||
<string name="screen_room_encrypted_history_banner">"Sõnumite ajalugu pole hetkel saadaval"</string>
|
<string name="screen_room_encrypted_history_banner">"Sõnumite ajalugu pole hetkel saadaval"</string>
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@
|
||||||
<string name="screen_report_content_hint">"Powód zgłoszenia treści"</string>
|
<string name="screen_report_content_hint">"Powód zgłoszenia treści"</string>
|
||||||
<string name="screen_room_attachment_source_camera">"Kamera"</string>
|
<string name="screen_room_attachment_source_camera">"Kamera"</string>
|
||||||
<string name="screen_room_attachment_source_camera_photo">"Zrób zdjęcie"</string>
|
<string name="screen_room_attachment_source_camera_photo">"Zrób zdjęcie"</string>
|
||||||
<string name="screen_room_attachment_source_camera_video">"Nagraj film"</string>
|
<string name="screen_room_attachment_source_camera_video">"Nagraj wideo"</string>
|
||||||
<string name="screen_room_attachment_source_files">"Załącznik"</string>
|
<string name="screen_room_attachment_source_files">"Załącznik"</string>
|
||||||
<string name="screen_room_attachment_source_gallery">"Zdjęcia i filmy"</string>
|
<string name="screen_room_attachment_source_gallery">"Zdjęcia i filmy"</string>
|
||||||
<string name="screen_room_attachment_source_location">"Udostępnij lokalizację"</string>
|
<string name="screen_room_attachment_source_location">"Udostępnij lokalizację"</string>
|
||||||
|
|
|
||||||
|
|
@ -25,11 +25,8 @@ import io.element.android.libraries.preferences.api.store.AppPreferencesStore
|
||||||
import io.element.android.libraries.preferences.api.store.SessionPreferencesStore
|
import io.element.android.libraries.preferences.api.store.SessionPreferencesStore
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
|
||||||
import kotlinx.coroutines.flow.collect
|
import kotlinx.coroutines.flow.collect
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.emptyFlow
|
|
||||||
import kotlinx.coroutines.flow.flatMapLatest
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
|
|
@ -56,17 +53,8 @@ class AdvancedSettingsPresenter(
|
||||||
appPreferencesStore.getThemeFlow().mapToTheme(isBlackThemeAllowed)
|
appPreferencesStore.getThemeFlow().mapToTheme(isBlackThemeAllowed)
|
||||||
}.collectAsState(initial = Theme.System)
|
}.collectAsState(initial = Theme.System)
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
|
||||||
val liveLocationMinimumDistanceUpdate by produceState<Int?>(null) {
|
val liveLocationMinimumDistanceUpdate by produceState<Int?>(null) {
|
||||||
featureFlagService.isFeatureEnabledFlow(FeatureFlags.LiveLocationSharing)
|
appPreferencesStore.getLiveLocationMinimumDistanceInMetersUpdateFlow().collect { value = it }
|
||||||
.flatMapLatest { isEnabled ->
|
|
||||||
if (isEnabled) {
|
|
||||||
appPreferencesStore.getLiveLocationMinimumDistanceInMetersUpdateFlow()
|
|
||||||
} else {
|
|
||||||
emptyFlow()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.collect { value = it }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val mediaPreviewConfigState = mediaPreviewConfigStateStore.state()
|
val mediaPreviewConfigState = mediaPreviewConfigStateStore.state()
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,7 @@ Pokud budete pokračovat, některá nastavení se mohou změnit."</string>
|
||||||
<string name="screen_notification_settings_system_notifications_action_required_content_link">"systémová nastavení"</string>
|
<string name="screen_notification_settings_system_notifications_action_required_content_link">"systémová nastavení"</string>
|
||||||
<string name="screen_notification_settings_system_notifications_turned_off">"Systémová oznámení byla vypnuta"</string>
|
<string name="screen_notification_settings_system_notifications_turned_off">"Systémová oznámení byla vypnuta"</string>
|
||||||
<string name="screen_notification_settings_title">"Oznámení"</string>
|
<string name="screen_notification_settings_title">"Oznámení"</string>
|
||||||
|
<string name="theme_black">"Černý"</string>
|
||||||
<string name="theme_dark">"Tmavé"</string>
|
<string name="theme_dark">"Tmavé"</string>
|
||||||
<string name="theme_light">"Světlý"</string>
|
<string name="theme_light">"Světlý"</string>
|
||||||
<string name="theme_system">"Systém"</string>
|
<string name="theme_system">"Systém"</string>
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,10 @@
|
||||||
<string name="screen_advanced_settings_hide_invite_avatars_toggle_title">"Peida jututubade kutsetest tunnuspildid"</string>
|
<string name="screen_advanced_settings_hide_invite_avatars_toggle_title">"Peida jututubade kutsetest tunnuspildid"</string>
|
||||||
<string name="screen_advanced_settings_hide_timeline_media_toggle_title">"Peida meedia eelvaated ajajoonel"</string>
|
<string name="screen_advanced_settings_hide_timeline_media_toggle_title">"Peida meedia eelvaated ajajoonel"</string>
|
||||||
<string name="screen_advanced_settings_labs">"Katsed"</string>
|
<string name="screen_advanced_settings_labs">"Katsed"</string>
|
||||||
|
<string name="screen_advanced_settings_live_location_section_description">"Vahemaa, mille pead andmete uuenduse käivitamiseks läbima."</string>
|
||||||
|
<string name="screen_advanced_settings_live_location_section_footer">"Veendu, et sellel rakendusel on õigus kasutada funktsionaalsust „Täpne asukoht“. Õiguste muutmiseks ava %1$s."</string>
|
||||||
<string name="screen_advanced_settings_live_location_section_footer_link">"Rakenduse seadistused"</string>
|
<string name="screen_advanced_settings_live_location_section_footer_link">"Rakenduse seadistused"</string>
|
||||||
|
<string name="screen_advanced_settings_live_location_section_title">"Andmete uuendused reaalajas asukoha jagamisel"</string>
|
||||||
<plurals name="screen_advanced_settings_live_location_update_distance">
|
<plurals name="screen_advanced_settings_live_location_update_distance">
|
||||||
<item quantity="one">"Iga %1$d meeter"</item>
|
<item quantity="one">"Iga %1$d meeter"</item>
|
||||||
<item quantity="other">"Iga %1$d meetrit"</item>
|
<item quantity="other">"Iga %1$d meetrit"</item>
|
||||||
|
|
@ -81,6 +84,7 @@ Kui sa jätkad muutmist, siis võivad muutuda ka need peidetud eelistused."</str
|
||||||
<string name="screen_notification_settings_system_notifications_action_required_content_link">"süsteemi seadistusi"</string>
|
<string name="screen_notification_settings_system_notifications_action_required_content_link">"süsteemi seadistusi"</string>
|
||||||
<string name="screen_notification_settings_system_notifications_turned_off">"Süsteemi teavitused on välja lülitatud"</string>
|
<string name="screen_notification_settings_system_notifications_turned_off">"Süsteemi teavitused on välja lülitatud"</string>
|
||||||
<string name="screen_notification_settings_title">"Teavitused"</string>
|
<string name="screen_notification_settings_title">"Teavitused"</string>
|
||||||
|
<string name="theme_black">"Süsimust kujundus"</string>
|
||||||
<string name="theme_dark">"Tume"</string>
|
<string name="theme_dark">"Tume"</string>
|
||||||
<string name="theme_light">"Hele"</string>
|
<string name="theme_light">"Hele"</string>
|
||||||
<string name="theme_system">"Süsteem"</string>
|
<string name="theme_system">"Süsteem"</string>
|
||||||
|
|
|
||||||
|
|
@ -84,6 +84,7 @@ Ha folytatja, egyes beállítások megváltozhatnak."</string>
|
||||||
<string name="screen_notification_settings_system_notifications_action_required_content_link">"rendszerbeállításokat"</string>
|
<string name="screen_notification_settings_system_notifications_action_required_content_link">"rendszerbeállításokat"</string>
|
||||||
<string name="screen_notification_settings_system_notifications_turned_off">"A rendszerértesítések ki vannak kapcsolva"</string>
|
<string name="screen_notification_settings_system_notifications_turned_off">"A rendszerértesítések ki vannak kapcsolva"</string>
|
||||||
<string name="screen_notification_settings_title">"Értesítések"</string>
|
<string name="screen_notification_settings_title">"Értesítések"</string>
|
||||||
|
<string name="theme_black">"Fekete"</string>
|
||||||
<string name="theme_dark">"Sötét"</string>
|
<string name="theme_dark">"Sötét"</string>
|
||||||
<string name="theme_light">"Világos"</string>
|
<string name="theme_light">"Világos"</string>
|
||||||
<string name="theme_system">"Rendszer"</string>
|
<string name="theme_system">"Rendszer"</string>
|
||||||
|
|
|
||||||
|
|
@ -85,6 +85,7 @@ Niektóre ustawienia mogą ulec zmianie, jeśli kontynuujesz."</string>
|
||||||
<string name="screen_notification_settings_system_notifications_action_required_content_link">"ustawienia systemowe"</string>
|
<string name="screen_notification_settings_system_notifications_action_required_content_link">"ustawienia systemowe"</string>
|
||||||
<string name="screen_notification_settings_system_notifications_turned_off">"Powiadomienia systemowe wyłączone"</string>
|
<string name="screen_notification_settings_system_notifications_turned_off">"Powiadomienia systemowe wyłączone"</string>
|
||||||
<string name="screen_notification_settings_title">"Powiadomienia"</string>
|
<string name="screen_notification_settings_title">"Powiadomienia"</string>
|
||||||
|
<string name="theme_black">"Czarny"</string>
|
||||||
<string name="theme_dark">"Ciemny"</string>
|
<string name="theme_dark">"Ciemny"</string>
|
||||||
<string name="theme_light">"Jasny"</string>
|
<string name="theme_light">"Jasny"</string>
|
||||||
<string name="theme_system">"System"</string>
|
<string name="theme_system">"System"</string>
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,15 @@
|
||||||
<string name="screen_advanced_settings_hide_invite_avatars_toggle_title">"Ascundeți avatarele din invitațiile pentru camere"</string>
|
<string name="screen_advanced_settings_hide_invite_avatars_toggle_title">"Ascundeți avatarele din invitațiile pentru camere"</string>
|
||||||
<string name="screen_advanced_settings_hide_timeline_media_toggle_title">"Ascundeți previzualizările media în lista de mesaje"</string>
|
<string name="screen_advanced_settings_hide_timeline_media_toggle_title">"Ascundeți previzualizările media în lista de mesaje"</string>
|
||||||
<string name="screen_advanced_settings_labs">"Laboratoare"</string>
|
<string name="screen_advanced_settings_labs">"Laboratoare"</string>
|
||||||
|
<string name="screen_advanced_settings_live_location_section_description">"Distanța pe care trebuie să o parcurgeți pentru a declanșa o actualizare."</string>
|
||||||
|
<string name="screen_advanced_settings_live_location_section_footer">"Asigurați-vă că este activată opțiunea „Locație precisă” pentru această aplicație. Pentru a schimba permisiunea, accesați %1$s."</string>
|
||||||
|
<string name="screen_advanced_settings_live_location_section_footer_link">"Setările aplicației"</string>
|
||||||
|
<string name="screen_advanced_settings_live_location_section_title">"Actualizări in timp real ale locației"</string>
|
||||||
|
<plurals name="screen_advanced_settings_live_location_update_distance">
|
||||||
|
<item quantity="one">"La fiecare %1$d metru"</item>
|
||||||
|
<item quantity="few">"La fiecare %1$d metri"</item>
|
||||||
|
<item quantity="other">"La fiecare %1$d metri"</item>
|
||||||
|
</plurals>
|
||||||
<string name="screen_advanced_settings_media_compression_description">"Încărcați fotografii și videoclipuri mai rapid și reduceți consumul de date"</string>
|
<string name="screen_advanced_settings_media_compression_description">"Încărcați fotografii și videoclipuri mai rapid și reduceți consumul de date"</string>
|
||||||
<string name="screen_advanced_settings_media_compression_title">"Optimizați calitatea media"</string>
|
<string name="screen_advanced_settings_media_compression_title">"Optimizați calitatea media"</string>
|
||||||
<string name="screen_advanced_settings_moderation_and_safety_section_title">"Moderare și siguranță"</string>
|
<string name="screen_advanced_settings_moderation_and_safety_section_title">"Moderare și siguranță"</string>
|
||||||
|
|
@ -78,6 +87,7 @@ Dacă continuați, unele dintre setările dumneavoastră pot fi modificate."</st
|
||||||
<string name="screen_notification_settings_system_notifications_action_required_content_link">"Setări de sistem"</string>
|
<string name="screen_notification_settings_system_notifications_action_required_content_link">"Setări de sistem"</string>
|
||||||
<string name="screen_notification_settings_system_notifications_turned_off">"Notificările de sistem sunt dezactivate"</string>
|
<string name="screen_notification_settings_system_notifications_turned_off">"Notificările de sistem sunt dezactivate"</string>
|
||||||
<string name="screen_notification_settings_title">"Notificări"</string>
|
<string name="screen_notification_settings_title">"Notificări"</string>
|
||||||
|
<string name="theme_black">"Negru"</string>
|
||||||
<string name="theme_dark">"Întunecat"</string>
|
<string name="theme_dark">"Întunecat"</string>
|
||||||
<string name="theme_light">"Deschis"</string>
|
<string name="theme_light">"Deschis"</string>
|
||||||
<string name="theme_system">"Sistem"</string>
|
<string name="theme_system">"Sistem"</string>
|
||||||
|
|
|
||||||
|
|
@ -78,11 +78,12 @@
|
||||||
<string name="screen_notification_settings_mode_all">"全部"</string>
|
<string name="screen_notification_settings_mode_all">"全部"</string>
|
||||||
<string name="screen_notification_settings_mode_mentions">"提及"</string>
|
<string name="screen_notification_settings_mode_mentions">"提及"</string>
|
||||||
<string name="screen_notification_settings_notification_section_title">"通知我以下类型"</string>
|
<string name="screen_notification_settings_notification_section_title">"通知我以下类型"</string>
|
||||||
<string name="screen_notification_settings_room_mention_label">"我在房间中被提及时通知我"</string>
|
<string name="screen_notification_settings_room_mention_label">"提及所有成员(@room)时通知我"</string>
|
||||||
<string name="screen_notification_settings_system_notifications_action_required">"要接收通知,请更改 %1$s。"</string>
|
<string name="screen_notification_settings_system_notifications_action_required">"要接收通知,请更改 %1$s。"</string>
|
||||||
<string name="screen_notification_settings_system_notifications_action_required_content_link">"系统设置"</string>
|
<string name="screen_notification_settings_system_notifications_action_required_content_link">"系统设置"</string>
|
||||||
<string name="screen_notification_settings_system_notifications_turned_off">"系统通知已关闭"</string>
|
<string name="screen_notification_settings_system_notifications_turned_off">"系统通知已关闭"</string>
|
||||||
<string name="screen_notification_settings_title">"通知"</string>
|
<string name="screen_notification_settings_title">"通知"</string>
|
||||||
|
<string name="theme_black">"纯黑"</string>
|
||||||
<string name="theme_dark">"深色"</string>
|
<string name="theme_dark">"深色"</string>
|
||||||
<string name="theme_light">"浅色"</string>
|
<string name="theme_light">"浅色"</string>
|
||||||
<string name="theme_system">"系统"</string>
|
<string name="theme_system">"系统"</string>
|
||||||
|
|
|
||||||
|
|
@ -210,35 +210,12 @@ class AdvancedSettingsPresenterTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `present - live location minimum distance is null when feature is disabled`() = runTest {
|
|
||||||
val appPreferencesStore = InMemoryAppPreferencesStore(
|
|
||||||
liveLocationMinimumDistanceUpdate = 50,
|
|
||||||
)
|
|
||||||
val featureFlagService = FakeFeatureFlagService().apply {
|
|
||||||
setFeatureEnabled(FeatureFlags.LiveLocationSharing, false)
|
|
||||||
}
|
|
||||||
val presenter = createAdvancedSettingsPresenter(appPreferencesStore = appPreferencesStore, featureFlagService = featureFlagService)
|
|
||||||
|
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
|
||||||
presenter.present()
|
|
||||||
}.test {
|
|
||||||
skipItems(1)
|
|
||||||
with(awaitItem()) {
|
|
||||||
assertThat(liveLocationMinimumDistanceUpdate).isNull()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - exposes live location minimum distance from app preferences`() = runTest {
|
fun `present - exposes live location minimum distance from app preferences`() = runTest {
|
||||||
val appPreferencesStore = InMemoryAppPreferencesStore(
|
val appPreferencesStore = InMemoryAppPreferencesStore(
|
||||||
liveLocationMinimumDistanceUpdate = 50,
|
liveLocationMinimumDistanceUpdate = 50,
|
||||||
)
|
)
|
||||||
val featureFlagService = FakeFeatureFlagService().apply {
|
val presenter = createAdvancedSettingsPresenter(appPreferencesStore = appPreferencesStore)
|
||||||
setFeatureEnabled(FeatureFlags.LiveLocationSharing, true)
|
|
||||||
}
|
|
||||||
val presenter = createAdvancedSettingsPresenter(appPreferencesStore = appPreferencesStore, featureFlagService = featureFlagService)
|
|
||||||
|
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
|
|
@ -256,10 +233,7 @@ class AdvancedSettingsPresenterTest {
|
||||||
val appPreferencesStore = InMemoryAppPreferencesStore(
|
val appPreferencesStore = InMemoryAppPreferencesStore(
|
||||||
liveLocationMinimumDistanceUpdate = 10,
|
liveLocationMinimumDistanceUpdate = 10,
|
||||||
)
|
)
|
||||||
val featureFlagService = FakeFeatureFlagService().apply {
|
val presenter = createAdvancedSettingsPresenter(appPreferencesStore = appPreferencesStore)
|
||||||
setFeatureEnabled(FeatureFlags.LiveLocationSharing, true)
|
|
||||||
}
|
|
||||||
val presenter = createAdvancedSettingsPresenter(appPreferencesStore = appPreferencesStore, featureFlagService = featureFlagService)
|
|
||||||
|
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
<string name="screen_report_room_leave_failed_alert_message">"你的举报已成功提交,但在尝试退出房间时遇到问题。请重试。"</string>
|
<string name="screen_report_room_leave_failed_alert_message">"你的举报已成功提交,但在尝试退出房间时遇到问题。请重试。"</string>
|
||||||
<string name="screen_report_room_leave_failed_alert_title">"无法离开房间"</string>
|
<string name="screen_report_room_leave_failed_alert_title">"无法离开房间"</string>
|
||||||
<string name="screen_report_room_reason_footer">"向管理员举报此房间。如果信息已加密,管理员将无法读取。"</string>
|
<string name="screen_report_room_reason_footer">"向管理员举报此房间。如果消息已加密,管理员将无法读取。"</string>
|
||||||
<string name="screen_report_room_reason_placeholder">"描述举报的理由…"</string>
|
<string name="screen_report_room_reason_placeholder">"描述举报的理由…"</string>
|
||||||
<string name="screen_report_room_title">"举报房间"</string>
|
<string name="screen_report_room_title">"举报房间"</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
<string name="screen_room_change_permissions_delete_messages">"Odstranit zprávy"</string>
|
<string name="screen_room_change_permissions_delete_messages">"Odstranit zprávy"</string>
|
||||||
<string name="screen_room_change_permissions_everyone">"Člen"</string>
|
<string name="screen_room_change_permissions_everyone">"Člen"</string>
|
||||||
<string name="screen_room_change_permissions_invite_people">"Pozvat přátele"</string>
|
<string name="screen_room_change_permissions_invite_people">"Pozvat přátele"</string>
|
||||||
|
<string name="screen_room_change_permissions_live_location">"Sdílet aktuální polohu"</string>
|
||||||
<string name="screen_room_change_permissions_manage_space">"Správa prostoru"</string>
|
<string name="screen_room_change_permissions_manage_space">"Správa prostoru"</string>
|
||||||
<string name="screen_room_change_permissions_manage_space_rooms">"Spravovat místnosti"</string>
|
<string name="screen_room_change_permissions_manage_space_rooms">"Spravovat místnosti"</string>
|
||||||
<string name="screen_room_change_permissions_member_moderation">"Spravovat členy"</string>
|
<string name="screen_room_change_permissions_member_moderation">"Spravovat členy"</string>
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
<string name="screen_room_change_permissions_delete_messages">"Eemalda sõnumid"</string>
|
<string name="screen_room_change_permissions_delete_messages">"Eemalda sõnumid"</string>
|
||||||
<string name="screen_room_change_permissions_everyone">"Liikmed"</string>
|
<string name="screen_room_change_permissions_everyone">"Liikmed"</string>
|
||||||
<string name="screen_room_change_permissions_invite_people">"Osalejate kutsumine"</string>
|
<string name="screen_room_change_permissions_invite_people">"Osalejate kutsumine"</string>
|
||||||
|
<string name="screen_room_change_permissions_live_location">"Jaga asukohta reaalajas"</string>
|
||||||
<string name="screen_room_change_permissions_manage_space">"Halda kogukonda"</string>
|
<string name="screen_room_change_permissions_manage_space">"Halda kogukonda"</string>
|
||||||
<string name="screen_room_change_permissions_manage_space_rooms">"Halda jututuba"</string>
|
<string name="screen_room_change_permissions_manage_space_rooms">"Halda jututuba"</string>
|
||||||
<string name="screen_room_change_permissions_member_moderation">"Liikmete haldus"</string>
|
<string name="screen_room_change_permissions_member_moderation">"Liikmete haldus"</string>
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
<string name="screen_room_change_permissions_delete_messages">"Ștergeți mesajele"</string>
|
<string name="screen_room_change_permissions_delete_messages">"Ștergeți mesajele"</string>
|
||||||
<string name="screen_room_change_permissions_everyone">"Membru"</string>
|
<string name="screen_room_change_permissions_everyone">"Membru"</string>
|
||||||
<string name="screen_room_change_permissions_invite_people">"Invitați persoane"</string>
|
<string name="screen_room_change_permissions_invite_people">"Invitați persoane"</string>
|
||||||
|
<string name="screen_room_change_permissions_live_location">"Partajați locația în timp real"</string>
|
||||||
<string name="screen_room_change_permissions_manage_space">"Gestionați spațiul"</string>
|
<string name="screen_room_change_permissions_manage_space">"Gestionați spațiul"</string>
|
||||||
<string name="screen_room_change_permissions_manage_space_rooms">"Gestionați camerele"</string>
|
<string name="screen_room_change_permissions_manage_space_rooms">"Gestionați camerele"</string>
|
||||||
<string name="screen_room_change_permissions_member_moderation">"Gestionați membrii"</string>
|
<string name="screen_room_change_permissions_member_moderation">"Gestionați membrii"</string>
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ interface RoomDetailsEntryPoint : FeatureEntryPoint {
|
||||||
interface Callback : Plugin {
|
interface Callback : Plugin {
|
||||||
fun navigateToGlobalNotificationSettings()
|
fun navigateToGlobalNotificationSettings()
|
||||||
fun navigateToDeveloperSettings()
|
fun navigateToDeveloperSettings()
|
||||||
fun navigateToRoom(roomId: RoomId, serverNames: List<String>)
|
fun navigateToRoom(roomId: RoomId, serverNames: List<String>, clearBackStack: Boolean = false)
|
||||||
fun handlePermalinkClick(data: PermalinkData, pushToBackstack: Boolean)
|
fun handlePermalinkClick(data: PermalinkData, pushToBackstack: Boolean)
|
||||||
fun startForwardEventFlow(eventId: EventId, fromPinnedEvents: Boolean)
|
fun startForwardEventFlow(eventId: EventId, fromPinnedEvents: Boolean)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -263,7 +263,20 @@ class RoomDetailsFlowNode(
|
||||||
}
|
}
|
||||||
|
|
||||||
NavTarget.InviteMembers -> {
|
NavTarget.InviteMembers -> {
|
||||||
createNode<RoomInviteMembersNode>(buildContext)
|
val callback = object : RoomInviteMembersNode.Callback {
|
||||||
|
override fun openCreatedRoom(roomId: RoomId) {
|
||||||
|
navigateUp()
|
||||||
|
room.roomCoroutineScope.launch {
|
||||||
|
callback.navigateToRoom(
|
||||||
|
roomId = roomId,
|
||||||
|
serverNames = emptyList(),
|
||||||
|
// Remove the invite screen from the backstack to avoid navigating back to it after the new room has been created
|
||||||
|
clearBackStack = true,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
createNode<RoomInviteMembersNode>(buildContext, plugins = listOf(callback))
|
||||||
}
|
}
|
||||||
|
|
||||||
is NavTarget.RoomNotificationSettings -> {
|
is NavTarget.RoomNotificationSettings -> {
|
||||||
|
|
|
||||||
|
|
@ -180,6 +180,7 @@ fun aDmRoomDetailsState(
|
||||||
roomName = roomName,
|
roomName = roomName,
|
||||||
isPublic = false,
|
isPublic = false,
|
||||||
isEncrypted = isEncrypted,
|
isEncrypted = isEncrypted,
|
||||||
|
canInvite = true,
|
||||||
roomType = RoomDetailsType.Dm(otherMember = aDmRoomMember(isIgnored = isDmMemberIgnored)),
|
roomType = RoomDetailsType.Dm(otherMember = aDmRoomMember(isIgnored = isDmMemberIgnored)),
|
||||||
roomMemberDetailsState = aUserProfileState(
|
roomMemberDetailsState = aUserProfileState(
|
||||||
isBlocked = AsyncData.Success(isDmMemberIgnored),
|
isBlocked = AsyncData.Success(isDmMemberIgnored),
|
||||||
|
|
|
||||||
|
|
@ -208,8 +208,15 @@ fun RoomDetailsView(
|
||||||
onClick = onSecurityAndPrivacyClick
|
onClick = onSecurityAndPrivacyClick
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
state.roomMemberDetailsState?.let { dmMemberDetails ->
|
state.roomMemberDetailsState?.let { dmMemberDetails ->
|
||||||
|
if (state.canInvite) {
|
||||||
|
PreferenceCategory {
|
||||||
|
InviteItem(onClick = invitePeople)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PreferenceCategory {
|
||||||
ProfileItem(
|
ProfileItem(
|
||||||
verificationState = dmMemberDetails.verificationState,
|
verificationState = dmMemberDetails.verificationState,
|
||||||
onClick = { onProfileClick(dmMemberDetails.userId) }
|
onClick = { onProfileClick(dmMemberDetails.userId) }
|
||||||
|
|
@ -374,14 +381,14 @@ private fun MainActionsSection(
|
||||||
onClick = { onCall(CallIntent.VIDEO) },
|
onClick = { onCall(CallIntent.VIDEO) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
if (state.canInvite && state.roomType !is RoomDetailsType.Dm) {
|
||||||
|
MainActionButton(
|
||||||
|
title = stringResource(CommonStrings.action_invite),
|
||||||
|
imageVector = CompoundIcons.UserAdd(),
|
||||||
|
onClick = onInvitePeople,
|
||||||
|
)
|
||||||
|
}
|
||||||
if (state.roomType is RoomDetailsType.Room) {
|
if (state.roomType is RoomDetailsType.Room) {
|
||||||
if (state.canInvite) {
|
|
||||||
MainActionButton(
|
|
||||||
title = stringResource(CommonStrings.action_invite),
|
|
||||||
imageVector = CompoundIcons.UserAdd(),
|
|
||||||
onClick = onInvitePeople,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
// Share CTA should be hidden for DMs
|
// Share CTA should be hidden for DMs
|
||||||
MainActionButton(
|
MainActionButton(
|
||||||
title = stringResource(CommonStrings.action_share),
|
title = stringResource(CommonStrings.action_share),
|
||||||
|
|
@ -693,6 +700,17 @@ private fun MembersItem(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun InviteItem(
|
||||||
|
onClick: () -> Unit,
|
||||||
|
) {
|
||||||
|
ListItem(
|
||||||
|
headlineContent = { Text(stringResource(R.string.screen_room_details_invite_title)) },
|
||||||
|
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.UserAdd())),
|
||||||
|
onClick = onClick,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun PinnedMessagesItem(
|
private fun PinnedMessagesItem(
|
||||||
pinnedMessagesCount: Int?,
|
pinnedMessagesCount: Int?,
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ package io.element.android.features.roomdetails.impl.invite
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import com.bumble.appyx.core.lifecycle.subscribe
|
import com.bumble.appyx.core.lifecycle.subscribe
|
||||||
import com.bumble.appyx.core.modality.BuildContext
|
import com.bumble.appyx.core.modality.BuildContext
|
||||||
import com.bumble.appyx.core.node.Node
|
import com.bumble.appyx.core.node.Node
|
||||||
|
|
@ -19,10 +20,16 @@ import dev.zacsweers.metro.Assisted
|
||||||
import dev.zacsweers.metro.AssistedInject
|
import dev.zacsweers.metro.AssistedInject
|
||||||
import im.vector.app.features.analytics.plan.MobileScreen
|
import im.vector.app.features.analytics.plan.MobileScreen
|
||||||
import io.element.android.annotations.ContributesNode
|
import io.element.android.annotations.ContributesNode
|
||||||
|
import io.element.android.features.invitepeople.api.InvitePeopleEvents
|
||||||
import io.element.android.features.invitepeople.api.InvitePeoplePresenter
|
import io.element.android.features.invitepeople.api.InvitePeoplePresenter
|
||||||
import io.element.android.features.invitepeople.api.InvitePeopleRenderer
|
import io.element.android.features.invitepeople.api.InvitePeopleRenderer
|
||||||
|
import io.element.android.libraries.architecture.callback
|
||||||
|
import io.element.android.libraries.designsystem.components.ProgressDialog
|
||||||
|
import io.element.android.libraries.designsystem.components.async.AsyncActionView
|
||||||
import io.element.android.libraries.di.RoomScope
|
import io.element.android.libraries.di.RoomScope
|
||||||
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
||||||
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
import io.element.android.services.analytics.api.AnalyticsService
|
import io.element.android.services.analytics.api.AnalyticsService
|
||||||
|
|
||||||
@ContributesNode(RoomScope::class)
|
@ContributesNode(RoomScope::class)
|
||||||
|
|
@ -35,6 +42,10 @@ class RoomInviteMembersNode(
|
||||||
room: JoinedRoom,
|
room: JoinedRoom,
|
||||||
invitePeoplePresenterFactory: InvitePeoplePresenter.Factory,
|
invitePeoplePresenterFactory: InvitePeoplePresenter.Factory,
|
||||||
) : Node(buildContext, plugins = plugins) {
|
) : Node(buildContext, plugins = plugins) {
|
||||||
|
interface Callback : Plugin {
|
||||||
|
fun openCreatedRoom(roomId: RoomId)
|
||||||
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
lifecycle.subscribe(
|
lifecycle.subscribe(
|
||||||
onResume = {
|
onResume = {
|
||||||
|
|
@ -48,6 +59,8 @@ class RoomInviteMembersNode(
|
||||||
roomId = room.roomId,
|
roomId = room.roomId,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private val callback = plugins.callback<Callback>()
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun View(modifier: Modifier) {
|
override fun View(modifier: Modifier) {
|
||||||
val state = invitePeoplePresenter.present()
|
val state = invitePeoplePresenter.present()
|
||||||
|
|
@ -59,6 +72,19 @@ class RoomInviteMembersNode(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AsyncActionView(
|
||||||
|
async = state.createRoomFromDmAction,
|
||||||
|
onSuccess = { roomId ->
|
||||||
|
callback.openCreatedRoom(roomId)
|
||||||
|
},
|
||||||
|
progressDialog = {
|
||||||
|
ProgressDialog(text = stringResource(CommonStrings.common_creating_room))
|
||||||
|
},
|
||||||
|
onErrorDismiss = {
|
||||||
|
state.eventSink(InvitePeopleEvents.ClearError)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
RoomInviteMembersView(
|
RoomInviteMembersView(
|
||||||
state = state,
|
state = state,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
|
|
|
||||||
|
|
@ -108,8 +108,8 @@
|
||||||
<string name="screen_room_notification_settings_allow_custom">"Zezwalaj na ustawienia niestandardowe"</string>
|
<string name="screen_room_notification_settings_allow_custom">"Zezwalaj na ustawienia niestandardowe"</string>
|
||||||
<string name="screen_room_notification_settings_allow_custom_footnote">"Włączenie tej opcji nadpisze ustawienie domyślne"</string>
|
<string name="screen_room_notification_settings_allow_custom_footnote">"Włączenie tej opcji nadpisze ustawienie domyślne"</string>
|
||||||
<string name="screen_room_notification_settings_custom_settings_title">"Powiadamiaj mnie o tym czacie przez"</string>
|
<string name="screen_room_notification_settings_custom_settings_title">"Powiadamiaj mnie o tym czacie przez"</string>
|
||||||
<string name="screen_room_notification_settings_default_setting_footnote">"Możesz to zmienić w swoim %1$s."</string>
|
<string name="screen_room_notification_settings_default_setting_footnote">"Możesz to zmienić w %1$s."</string>
|
||||||
<string name="screen_room_notification_settings_default_setting_footnote_content_link">"ustawienia globalne"</string>
|
<string name="screen_room_notification_settings_default_setting_footnote_content_link">"ustawieniach globalnych"</string>
|
||||||
<string name="screen_room_notification_settings_default_setting_title">"Ustawienie domyślne"</string>
|
<string name="screen_room_notification_settings_default_setting_title">"Ustawienie domyślne"</string>
|
||||||
<string name="screen_room_notification_settings_edit_remove_setting">"Usuń ustawienia własne"</string>
|
<string name="screen_room_notification_settings_edit_remove_setting">"Usuń ustawienia własne"</string>
|
||||||
<string name="screen_room_notification_settings_error_loading_settings">"Wystąpił błąd podczas ładowania ustawień powiadomień."</string>
|
<string name="screen_room_notification_settings_error_loading_settings">"Wystąpił błąd podczas ładowania ustawień powiadomień."</string>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="crypto_history_sharing_room_info_hidden_badge_content">"Membrii noi nu pot vedea istoricul"</string>
|
||||||
|
<string name="crypto_history_sharing_room_info_shared_badge_content">"Membrii noi pot vedea istoricul"</string>
|
||||||
|
<string name="crypto_history_sharing_room_info_world_readable_badge_content">"Oricine poate vedea istoricul"</string>
|
||||||
<string name="screen_edit_room_address_room_address_section_footer">"Veți avea nevoie de o adresă pentru a o face vizibilă în directorul public."</string>
|
<string name="screen_edit_room_address_room_address_section_footer">"Veți avea nevoie de o adresă pentru a o face vizibilă în directorul public."</string>
|
||||||
<string name="screen_edit_room_address_title">"Editați adresa"</string>
|
<string name="screen_edit_room_address_title">"Editați adresa"</string>
|
||||||
<string name="screen_notification_settings_edit_failed_updating_default_mode">"A apărut o eroare în timpul actualizării setărilor pentru notificari."</string>
|
<string name="screen_notification_settings_edit_failed_updating_default_mode">"A apărut o eroare în timpul actualizării setărilor pentru notificari."</string>
|
||||||
|
|
@ -135,6 +138,7 @@
|
||||||
<string name="screen_security_and_privacy_add_room_address_action">"Adăugați o adresă"</string>
|
<string name="screen_security_and_privacy_add_room_address_action">"Adăugați o adresă"</string>
|
||||||
<string name="screen_security_and_privacy_ask_to_join_multiple_spaces_members_option_description">"Oricine se află în spațiile autorizate se poate alătura, dar toți ceilalți trebuie să solicite accesul."</string>
|
<string name="screen_security_and_privacy_ask_to_join_multiple_spaces_members_option_description">"Oricine se află în spațiile autorizate se poate alătura, dar toți ceilalți trebuie să solicite accesul."</string>
|
||||||
<string name="screen_security_and_privacy_ask_to_join_option_description">"Toată lumea trebuie să solicite acces."</string>
|
<string name="screen_security_and_privacy_ask_to_join_option_description">"Toată lumea trebuie să solicite acces."</string>
|
||||||
|
<string name="screen_security_and_privacy_ask_to_join_option_title">"Solicitați să vă alăturați"</string>
|
||||||
<string name="screen_security_and_privacy_ask_to_join_single_space_members_option_description">"Oricine în %1$s se poate alătura, dar toți ceilalți trebuie să solicite acces."</string>
|
<string name="screen_security_and_privacy_ask_to_join_single_space_members_option_description">"Oricine în %1$s se poate alătura, dar toți ceilalți trebuie să solicite acces."</string>
|
||||||
<string name="screen_security_and_privacy_enable_encryption_alert_confirm_button_title">"Da, activați criptarea"</string>
|
<string name="screen_security_and_privacy_enable_encryption_alert_confirm_button_title">"Da, activați criptarea"</string>
|
||||||
<string name="screen_security_and_privacy_enable_encryption_alert_description">"Odată activată, criptarea pentru o cameră nu poate fi dezactivată. Mesajele anterioare vor fi vizibile numai pentru membrii camerei de la momentul la care au fost invitați sau de la momentul la care s-au alăturat camerei.
|
<string name="screen_security_and_privacy_enable_encryption_alert_description">"Odată activată, criptarea pentru o cameră nu poate fi dezactivată. Mesajele anterioare vor fi vizibile numai pentru membrii camerei de la momentul la care au fost invitați sau de la momentul la care s-au alăturat camerei.
|
||||||
|
|
@ -145,22 +149,26 @@ Nu recomandăm activarea criptării pentru camerele pe care oricine le poate gă
|
||||||
<string name="screen_security_and_privacy_encryption_section_header">"Criptare"</string>
|
<string name="screen_security_and_privacy_encryption_section_header">"Criptare"</string>
|
||||||
<string name="screen_security_and_privacy_encryption_toggle_title">"Activați criptarea end-to-end"</string>
|
<string name="screen_security_and_privacy_encryption_toggle_title">"Activați criptarea end-to-end"</string>
|
||||||
<string name="screen_security_and_privacy_room_access_anyone_option_description">"Oricine se poate alătura."</string>
|
<string name="screen_security_and_privacy_room_access_anyone_option_description">"Oricine se poate alătura."</string>
|
||||||
<string name="screen_security_and_privacy_room_access_footer">"Alegeți membrii căror spații se pot alătura acestei camere fără invitație. %1$s"</string>
|
<string name="screen_security_and_privacy_room_access_anyone_option_title">"Oricine"</string>
|
||||||
|
<string name="screen_security_and_privacy_room_access_footer">"Alegeți membrii căror spații se pot alătura acestei cameră fără invitație. %1$s"</string>
|
||||||
<string name="screen_security_and_privacy_room_access_footer_manage_spaces_action">"Gestionați spațiile"</string>
|
<string name="screen_security_and_privacy_room_access_footer_manage_spaces_action">"Gestionați spațiile"</string>
|
||||||
<string name="screen_security_and_privacy_room_access_invite_only_option_description">"Doar persoanele invitate se pot alătura."</string>
|
<string name="screen_security_and_privacy_room_access_invite_only_option_description">"Doar persoanele invitate se pot alătura."</string>
|
||||||
<string name="screen_security_and_privacy_room_access_invite_only_option_title">"Doar pe bază de invitație"</string>
|
<string name="screen_security_and_privacy_room_access_invite_only_option_title">"Doar pe bază de invitație"</string>
|
||||||
<string name="screen_security_and_privacy_room_access_section_header">"Acces"</string>
|
<string name="screen_security_and_privacy_room_access_section_header">"Acces"</string>
|
||||||
<string name="screen_security_and_privacy_room_access_space_members_option_multiple_parents_description">"Oricine se află într-un spațiu autorizat poate participa."</string>
|
<string name="screen_security_and_privacy_room_access_space_members_option_multiple_parents_description">"Oricine se află într-un spațiu autorizat poate participa."</string>
|
||||||
<string name="screen_security_and_privacy_room_access_space_members_option_single_parent_description">"Oricine din %1$s se poate alătura."</string>
|
<string name="screen_security_and_privacy_room_access_space_members_option_single_parent_description">"Oricine din %1$s se poate alătura."</string>
|
||||||
|
<string name="screen_security_and_privacy_room_access_space_members_option_title">"Membrii spațiului"</string>
|
||||||
<string name="screen_security_and_privacy_room_access_space_members_option_unavailable_description">"Spațiile nu sunt momentan suportate."</string>
|
<string name="screen_security_and_privacy_room_access_space_members_option_unavailable_description">"Spațiile nu sunt momentan suportate."</string>
|
||||||
<string name="screen_security_and_privacy_room_address_section_footer">"Veți avea nevoie de o adresă pentru a o face vizibilă în directorul public."</string>
|
<string name="screen_security_and_privacy_room_address_section_footer">"Veți avea nevoie de o adresă pentru a o face vizibilă în directorul public."</string>
|
||||||
<string name="screen_security_and_privacy_room_address_section_header">"Adresă"</string>
|
<string name="screen_security_and_privacy_room_address_section_header">"Adresă"</string>
|
||||||
<string name="screen_security_and_privacy_room_directory_visibility_section_footer">"Permiteți găsirea acestei camere prin căutarea în directorul de camere publice al %1$s"</string>
|
<string name="screen_security_and_privacy_room_directory_visibility_section_footer">"Permiteți găsirea acestei camere prin căutarea în directorul de camere publice al %1$s"</string>
|
||||||
<string name="screen_security_and_privacy_room_directory_visibility_toggle_description">"Permiteți găsirea prin căutarea în directorul public."</string>
|
<string name="screen_security_and_privacy_room_directory_visibility_toggle_description">"Permiteți găsirea prin căutarea în directorul public."</string>
|
||||||
<string name="screen_security_and_privacy_room_directory_visibility_toggle_title">"Vizibilă în directorul de camere publice"</string>
|
<string name="screen_security_and_privacy_room_directory_visibility_toggle_title">"Vizibilă în directorul de camere publice"</string>
|
||||||
|
<string name="screen_security_and_privacy_room_history_anyone_option_title">"Oricine (istoricul este public)"</string>
|
||||||
|
<string name="screen_security_and_privacy_room_history_section_footer">"Modificările nu vor afecta mesajele anterioare, ci doar pe cele noi. %1$s"</string>
|
||||||
<string name="screen_security_and_privacy_room_history_section_header">"Cine poate citi mesajele anterioare"</string>
|
<string name="screen_security_and_privacy_room_history_section_header">"Cine poate citi mesajele anterioare"</string>
|
||||||
<string name="screen_security_and_privacy_room_history_since_invite_option_title">"Doar pentru membri, de la momentul în care au fost invitați"</string>
|
<string name="screen_security_and_privacy_room_history_since_invite_option_title">"Membri de la momentul invitației"</string>
|
||||||
<string name="screen_security_and_privacy_room_history_since_selecting_option_title">"Doar pentru membri, după selectarea acestei opțiuni"</string>
|
<string name="screen_security_and_privacy_room_history_since_selecting_option_title">"Membri (istoric complet)"</string>
|
||||||
<string name="screen_security_and_privacy_room_publishing_section_footer">"Adresele camerelor sunt modalități de a găsi și accesa camere. Acest lucru vă asigură, de asemenea, că puteți partaja cu ușurință camera dumneavoastră cu alte persoane.
|
<string name="screen_security_and_privacy_room_publishing_section_footer">"Adresele camerelor sunt modalități de a găsi și accesa camere. Acest lucru vă asigură, de asemenea, că puteți partaja cu ușurință camera dumneavoastră cu alte persoane.
|
||||||
Puteți alege să publicați camera în directorul public al camerelor serverului dumneavoastră."</string>
|
Puteți alege să publicați camera în directorul public al camerelor serverului dumneavoastră."</string>
|
||||||
<string name="screen_security_and_privacy_room_publishing_section_header">"Publicare cameră"</string>
|
<string name="screen_security_and_privacy_room_publishing_section_header">"Publicare cameră"</string>
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,7 @@ class DefaultRoomDetailsEntryPointTest {
|
||||||
val callback = object : RoomDetailsEntryPoint.Callback {
|
val callback = object : RoomDetailsEntryPoint.Callback {
|
||||||
override fun navigateToGlobalNotificationSettings() = lambdaError()
|
override fun navigateToGlobalNotificationSettings() = lambdaError()
|
||||||
override fun navigateToDeveloperSettings() = lambdaError()
|
override fun navigateToDeveloperSettings() = lambdaError()
|
||||||
override fun navigateToRoom(roomId: RoomId, serverNames: List<String>) = lambdaError()
|
override fun navigateToRoom(roomId: RoomId, serverNames: List<String>, clearBackStack: Boolean) = lambdaError()
|
||||||
override fun handlePermalinkClick(data: PermalinkData, pushToBackstack: Boolean) = lambdaError()
|
override fun handlePermalinkClick(data: PermalinkData, pushToBackstack: Boolean) = lambdaError()
|
||||||
override fun startForwardEventFlow(eventId: EventId, fromPinnedEvents: Boolean) = lambdaError()
|
override fun startForwardEventFlow(eventId: EventId, fromPinnedEvents: Boolean) = lambdaError()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,8 @@ package io.element.android.features.roomdetails.impl
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.compose.ui.test.AndroidComposeUiTest
|
import androidx.compose.ui.test.AndroidComposeUiTest
|
||||||
import androidx.compose.ui.test.ExperimentalTestApi
|
import androidx.compose.ui.test.ExperimentalTestApi
|
||||||
|
import androidx.compose.ui.test.onAllNodesWithText
|
||||||
|
import androidx.compose.ui.test.onLast
|
||||||
import androidx.compose.ui.test.onNodeWithContentDescription
|
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||||
import androidx.compose.ui.test.onNodeWithTag
|
import androidx.compose.ui.test.onNodeWithTag
|
||||||
import androidx.compose.ui.test.performClick
|
import androidx.compose.ui.test.performClick
|
||||||
|
|
@ -339,6 +341,25 @@ class RoomDetailsViewTest {
|
||||||
clickOn(R.string.screen_room_details_profile_row_title)
|
clickOn(R.string.screen_room_details_profile_row_title)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Config(qualifiers = "h1024dp")
|
||||||
|
@Test
|
||||||
|
fun `click on invite invokes the expected callback`() = runAndroidComposeUiTest {
|
||||||
|
ensureCalledOnce { callback ->
|
||||||
|
setRoomDetailView(
|
||||||
|
state = aRoomDetailsState(
|
||||||
|
eventSink = EventsRecorder(expectEvents = false),
|
||||||
|
roomType = RoomDetailsType.Dm(
|
||||||
|
aDmRoomMember(userId = UserId("@other:local.org")),
|
||||||
|
),
|
||||||
|
roomMemberDetailsState = aUserProfileState(userId = A_USER_ID),
|
||||||
|
canInvite = true,
|
||||||
|
),
|
||||||
|
invitePeople = callback,
|
||||||
|
)
|
||||||
|
onAllNodesWithText(activity!!.getString(R.string.screen_room_details_invite_title)).onLast().performClick()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun AndroidComposeUiTest<ComponentActivity>.setRoomDetailView(
|
private fun AndroidComposeUiTest<ComponentActivity>.setRoomDetailView(
|
||||||
|
|
|
||||||
|
|
@ -2,16 +2,17 @@
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
<string name="screen_chat_backup_key_backup_action_disable">"Lülita võtmete varundamine välja"</string>
|
<string name="screen_chat_backup_key_backup_action_disable">"Lülita võtmete varundamine välja"</string>
|
||||||
<string name="screen_chat_backup_key_backup_action_enable">"Lülita võtmete varundamine sisse"</string>
|
<string name="screen_chat_backup_key_backup_action_enable">"Lülita võtmete varundamine sisse"</string>
|
||||||
<string name="screen_chat_backup_key_backup_description">"Salvesta oma krüptoidentiteet ja sõnumite krüptovõtmed turvaliselt serveris. See tagab, et sinu sõnumite ajalugu on alati loetav, ka kõikides uutes seadmetes. %1$s."</string>
|
<string name="screen_chat_backup_key_backup_description">"Salvesta oma digitaalne identiteet ja sõnumite krüptovõtmed turvaliselt serveris. See tagab, et sinu sõnumite ajalugu on alati loetav, ka kõikides uutes seadmetes. %1$s."</string>
|
||||||
<string name="screen_chat_backup_key_backup_title">"Krüptovõtmete varundus"</string>
|
<string name="screen_chat_backup_key_backup_title">"Krüptovõtmete varundus"</string>
|
||||||
<string name="screen_chat_backup_key_storage_disabled_error">"Taastamise seadistamiseks peab võtmehoidla olema sisselülitatud."</string>
|
<string name="screen_chat_backup_key_storage_disabled_error">"Sinu vestluste varundamiseks peab võtmehoidla olema sisselülitatud."</string>
|
||||||
<string name="screen_chat_backup_key_storage_toggle_description">"Laadi siin seadmes leiduvad võtmed üles"</string>
|
<string name="screen_chat_backup_key_storage_toggle_description">"Laadi siin seadmes leiduvad võtmed üles"</string>
|
||||||
<string name="screen_chat_backup_key_storage_toggle_title">"Luba krüptovõtmete salvestamine"</string>
|
<string name="screen_chat_backup_key_storage_toggle_title">"Luba krüptovõtmete salvestamine"</string>
|
||||||
<string name="screen_chat_backup_recovery_action_change">"Muuda taastevõtit"</string>
|
<string name="screen_chat_backup_recovery_action_change">"Muuda taastevõtit"</string>
|
||||||
<string name="screen_chat_backup_recovery_action_change_description">"Kui sa oled kaotanud ligipääsu kõikidele oma olemasolevatele seadmetele, siis sa saad taastevõtme abil taastada ligipääsu oma krüptoidentiteedile ja sõnumite ajaloole."</string>
|
<string name="screen_chat_backup_recovery_action_change_description">"Kui sa oled kaotanud ligipääsu kõikidele oma olemasolevatele seadmetele, siis sa saad taastevõtme abil taastada ligipääsu oma digitaalsele identiteedile ja sõnumite ajaloole."</string>
|
||||||
<string name="screen_chat_backup_recovery_action_confirm">"Sisesta taastevõti"</string>
|
<string name="screen_chat_backup_recovery_action_confirm">"Sisesta taastevõti"</string>
|
||||||
<string name="screen_chat_backup_recovery_action_confirm_description">"Sinu krüptovõtmete varundus pole hetkel enam sünkroonis."</string>
|
<string name="screen_chat_backup_recovery_action_confirm_description">"Sinu krüptovõtmete varundus pole hetkel enam sünkroonis."</string>
|
||||||
<string name="screen_chat_backup_recovery_action_setup">"Seadista andmete taastamine"</string>
|
<string name="screen_chat_backup_recovery_action_setup">"Seadista taastevõti"</string>
|
||||||
|
<string name="screen_chat_backup_recovery_action_setup_description">"Sinu vestlused on automaatselt varundatud kasutades läbivat krüptimist. Kui peaksid kaotama ligipääsu kõikidele oma seadmetele, siis selle varukoopia taastamiseks ja oma digitaalse identiteedi säilitamiseks, on vaja taastevõtit."</string>
|
||||||
<string name="screen_create_new_recovery_key_list_item_1">"Ava %1$s töölauaga seadmes"</string>
|
<string name="screen_create_new_recovery_key_list_item_1">"Ava %1$s töölauaga seadmes"</string>
|
||||||
<string name="screen_create_new_recovery_key_list_item_2">"Logi uuesti sisse oma kasutajakontole"</string>
|
<string name="screen_create_new_recovery_key_list_item_2">"Logi uuesti sisse oma kasutajakontole"</string>
|
||||||
<string name="screen_create_new_recovery_key_list_item_3">"Kui sul palutakse seadet verifitseerida, vali %1$s"</string>
|
<string name="screen_create_new_recovery_key_list_item_3">"Kui sul palutakse seadet verifitseerida, vali %1$s"</string>
|
||||||
|
|
@ -23,12 +24,12 @@
|
||||||
<string name="screen_encryption_reset_bullet_1">"Sinu kasutajakonto andmed, kontaktid, eelistused ja vestluste loend säiluvad"</string>
|
<string name="screen_encryption_reset_bullet_1">"Sinu kasutajakonto andmed, kontaktid, eelistused ja vestluste loend säiluvad"</string>
|
||||||
<string name="screen_encryption_reset_bullet_2">"Sa kaotad seniste sõnumite ajaloo"</string>
|
<string name="screen_encryption_reset_bullet_2">"Sa kaotad seniste sõnumite ajaloo"</string>
|
||||||
<string name="screen_encryption_reset_bullet_3">"Sa pead kõik oma olemasolevad seadmed ja kontaktid uuesti verifitseerima"</string>
|
<string name="screen_encryption_reset_bullet_3">"Sa pead kõik oma olemasolevad seadmed ja kontaktid uuesti verifitseerima"</string>
|
||||||
<string name="screen_encryption_reset_footer">"Lähtesta oma identiteet vaid siis, kui sul pole ligipääsu mitte ühelegi oma seadmele ja sa oled kaotanud oma taastevõtme."</string>
|
<string name="screen_encryption_reset_footer">"Lähtesta oma digitaalne identiteet vaid siis, kui sul pole ligipääsu mitte ühelegi oma seadmele ja sa oled kaotanud oma taastevõtme."</string>
|
||||||
<string name="screen_encryption_reset_title">"Kui sa ühtegi muud võimalust ei leia, siis lähtesta oma identiteet."</string>
|
<string name="screen_encryption_reset_title">"Kui sa ühtegi muud võimalust ei leia, siis lähtesta oma digitaalne identiteet."</string>
|
||||||
<string name="screen_key_backup_disable_confirmation_action_turn_off">"Lülita välja"</string>
|
<string name="screen_key_backup_disable_confirmation_action_turn_off">"Kustuta"</string>
|
||||||
<string name="screen_key_backup_disable_confirmation_description">"Kui sa logid välja kõikidest oma seadmetest, siis sa kaotad ligipääsu oma krüptitud sõnumitele."</string>
|
<string name="screen_key_backup_disable_confirmation_description">"Kui sa eemaldad kõik oma seadmed, siis sa kaotad ligipääsu oma krüptitud sõnumitele ja pead oma digitaalse identiteedi lähtestama."</string>
|
||||||
<string name="screen_key_backup_disable_confirmation_title">"Kas sa oled kindel, et soovid varukoopiate tegemise välja lülitada?"</string>
|
<string name="screen_key_backup_disable_confirmation_title">"Kas oled kindel, et soovid võtmehoidla kustutada?"</string>
|
||||||
<string name="screen_key_backup_disable_description">"Varunduse väljalülitamisel kustutatakse hetkel olemasolev sinu krüptovõtmete varukoopia ning lülituvad välja veel mõned turvafunktsionaalsused. Sellisel juhul sul:"</string>
|
<string name="screen_key_backup_disable_description">"Võtmehoidla kustutamine eemaldab sinu digitaalse identiteedi ja sõnumivõtmed serverist ning lülitab välja järgmised turvafunktsionaalsused:"</string>
|
||||||
<string name="screen_key_backup_disable_description_point_1">"sul ei ole krüptitud sõnumite ajalugu uutes seadmetes"</string>
|
<string name="screen_key_backup_disable_description_point_1">"sul ei ole krüptitud sõnumite ajalugu uutes seadmetes"</string>
|
||||||
<string name="screen_key_backup_disable_description_point_2">"sa kaotad ligipääsu oma krüptitud sõnumitele, kui sa logid kõikjal välja rakendusest %1$s"</string>
|
<string name="screen_key_backup_disable_description_point_2">"sa kaotad ligipääsu oma krüptitud sõnumitele, kui sa logid kõikjal välja rakendusest %1$s"</string>
|
||||||
<string name="screen_key_backup_disable_title">"Kas sa oled kindel, et soovid varunduse välja lülitada?"</string>
|
<string name="screen_key_backup_disable_title">"Kas sa oled kindel, et soovid varunduse välja lülitada?"</string>
|
||||||
|
|
@ -58,12 +59,12 @@
|
||||||
<string name="screen_recovery_key_setup_generate_key">"Loo oma taastevõti"</string>
|
<string name="screen_recovery_key_setup_generate_key">"Loo oma taastevõti"</string>
|
||||||
<string name="screen_recovery_key_setup_generate_key_description">"Ära jaga seda kellegagi"</string>
|
<string name="screen_recovery_key_setup_generate_key_description">"Ära jaga seda kellegagi"</string>
|
||||||
<string name="screen_recovery_key_setup_success">"Andmete taastamise seadistamine õnnestus"</string>
|
<string name="screen_recovery_key_setup_success">"Andmete taastamise seadistamine õnnestus"</string>
|
||||||
<string name="screen_recovery_key_setup_title">"Seadista andmete taastamine"</string>
|
<string name="screen_recovery_key_setup_title">"Seadista taastevõti"</string>
|
||||||
<string name="screen_reset_encryption_confirmation_alert_action">"Jah, lähtesta nüüd"</string>
|
<string name="screen_reset_encryption_confirmation_alert_action">"Jah, lähtesta nüüd"</string>
|
||||||
<string name="screen_reset_encryption_confirmation_alert_subtitle">"See tegevus on tagasipöördumatu."</string>
|
<string name="screen_reset_encryption_confirmation_alert_subtitle">"See tegevus on tagasipöördumatu."</string>
|
||||||
<string name="screen_reset_encryption_confirmation_alert_title">"Kas sa oled kindel, et soovid oma võrguidentiteeti lähtestada?"</string>
|
<string name="screen_reset_encryption_confirmation_alert_title">"Kas sa oled kindel, et soovid oma digitaalse identiteedi lähtestada?"</string>
|
||||||
<string name="screen_reset_encryption_password_error">"Tekkis teadmata viga. Palun kontrolli, kas sinu kasutajakonto salasõna on õige ja proovi uuesti."</string>
|
<string name="screen_reset_encryption_password_error">"Tekkis teadmata viga. Palun kontrolli, kas sinu kasutajakonto salasõna on õige ja proovi uuesti."</string>
|
||||||
<string name="screen_reset_encryption_password_placeholder">"Sisesta…"</string>
|
<string name="screen_reset_encryption_password_placeholder">"Sisesta…"</string>
|
||||||
<string name="screen_reset_encryption_password_subtitle">"Palun kinnita, et soovid oma võrguidentiteedi lähtestada."</string>
|
<string name="screen_reset_encryption_password_subtitle">"Palun kinnita, et soovid oma digitaalse identiteedi lähtestada."</string>
|
||||||
<string name="screen_reset_encryption_password_title">"Jätkamaks sisesta oma kasutajakonto salasõna"</string>
|
<string name="screen_reset_encryption_password_title">"Jätkamaks sisesta oma kasutajakonto salasõna"</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@
|
||||||
<string name="screen_key_backup_disable_confirmation_description">"Jeśli usuniesz wszystkie swoje urządzenia, stracisz zaszyfrowaną historię wiadomości i będziesz musiał zresetować swoją tożsamość cyfrową."</string>
|
<string name="screen_key_backup_disable_confirmation_description">"Jeśli usuniesz wszystkie swoje urządzenia, stracisz zaszyfrowaną historię wiadomości i będziesz musiał zresetować swoją tożsamość cyfrową."</string>
|
||||||
<string name="screen_key_backup_disable_confirmation_title">"Czy na pewno chcesz usunąć magazyn kluczy?"</string>
|
<string name="screen_key_backup_disable_confirmation_title">"Czy na pewno chcesz usunąć magazyn kluczy?"</string>
|
||||||
<string name="screen_key_backup_disable_description">"Usunięcie magazynu kluczy usunie Twoją tożsamość cyfrową i klucze wiadomości z serwera, wyłączając następujące funkcje bezpieczeństwa:"</string>
|
<string name="screen_key_backup_disable_description">"Usunięcie magazynu kluczy usunie Twoją tożsamość cyfrową i klucze wiadomości z serwera, wyłączając następujące funkcje bezpieczeństwa:"</string>
|
||||||
<string name="screen_key_backup_disable_description_point_1">"Posiadał historii wiadomości szyfrowanych na nowych urządzeniach"</string>
|
<string name="screen_key_backup_disable_description_point_1">"Stracisz dostęp do zaszyfrowanej historii wiadomości na nowych urządzeniach"</string>
|
||||||
<string name="screen_key_backup_disable_description_point_2">"Utracisz dostęp do wiadomości szyfrowanych, jeśli zostaniesz wszędzie wylogowany z %1$s"</string>
|
<string name="screen_key_backup_disable_description_point_2">"Utracisz dostęp do wiadomości szyfrowanych, jeśli zostaniesz wszędzie wylogowany z %1$s"</string>
|
||||||
<string name="screen_key_backup_disable_title">"Czy na pewno chcesz wyłączyć backup?"</string>
|
<string name="screen_key_backup_disable_title">"Czy na pewno chcesz wyłączyć backup?"</string>
|
||||||
<string name="screen_recovery_key_change_description">"Uzyskaj nowy klucz przywracania, jeśli straciłeś dostęp do obecnego. Po zmianie klucza przywracania stary nie będzie już działał."</string>
|
<string name="screen_recovery_key_change_description">"Uzyskaj nowy klucz przywracania, jeśli straciłeś dostęp do obecnego. Po zmianie klucza przywracania stary nie będzie już działał."</string>
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
<string name="screen_chat_backup_recovery_action_confirm">"Introduceți cheia de recuperare"</string>
|
<string name="screen_chat_backup_recovery_action_confirm">"Introduceți cheia de recuperare"</string>
|
||||||
<string name="screen_chat_backup_recovery_action_confirm_description">"Backup-ul pentru chat nu este sincronizat în prezent."</string>
|
<string name="screen_chat_backup_recovery_action_confirm_description">"Backup-ul pentru chat nu este sincronizat în prezent."</string>
|
||||||
<string name="screen_chat_backup_recovery_action_setup">"Obțineți cheia de recuperare"</string>
|
<string name="screen_chat_backup_recovery_action_setup">"Obțineți cheia de recuperare"</string>
|
||||||
|
<string name="screen_chat_backup_recovery_action_setup_description">"Chaturile dumneavoastră sunt salvate automat cu criptare end-to-end. Pentru a restaura această copie de rezervă și a vă păstra identitatea digitală atunci când pierdeți accesul la toate dispozitivele dumneavoastră, veți avea nevoie de cheia de recuperare."</string>
|
||||||
<string name="screen_create_new_recovery_key_list_item_1">"Deschideți %1$s pe un dispozitiv desktop"</string>
|
<string name="screen_create_new_recovery_key_list_item_1">"Deschideți %1$s pe un dispozitiv desktop"</string>
|
||||||
<string name="screen_create_new_recovery_key_list_item_2">"Conectați-vă din nou la contul dumneavoastră"</string>
|
<string name="screen_create_new_recovery_key_list_item_2">"Conectați-vă din nou la contul dumneavoastră"</string>
|
||||||
<string name="screen_create_new_recovery_key_list_item_3">"Când vi se cere să vă verificați dispozitivul, selectați%1$s"</string>
|
<string name="screen_create_new_recovery_key_list_item_3">"Când vi se cere să vă verificați dispozitivul, selectați%1$s"</string>
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
<string name="screen_security_and_privacy_add_room_address_action">"Adăugați o adresă"</string>
|
<string name="screen_security_and_privacy_add_room_address_action">"Adăugați o adresă"</string>
|
||||||
<string name="screen_security_and_privacy_ask_to_join_multiple_spaces_members_option_description">"Oricine se află în spațiile autorizate se poate alătura, dar toți ceilalți trebuie să solicite accesul."</string>
|
<string name="screen_security_and_privacy_ask_to_join_multiple_spaces_members_option_description">"Oricine se află în spațiile autorizate se poate alătura, dar toți ceilalți trebuie să solicite accesul."</string>
|
||||||
<string name="screen_security_and_privacy_ask_to_join_option_description">"Toată lumea trebuie să solicite acces."</string>
|
<string name="screen_security_and_privacy_ask_to_join_option_description">"Toată lumea trebuie să solicite acces."</string>
|
||||||
|
<string name="screen_security_and_privacy_ask_to_join_option_title">"Solicitați să vă alăturați"</string>
|
||||||
<string name="screen_security_and_privacy_ask_to_join_single_space_members_option_description">"Oricine în %1$s se poate alătura, dar toți ceilalți trebuie să solicite acces."</string>
|
<string name="screen_security_and_privacy_ask_to_join_single_space_members_option_description">"Oricine în %1$s se poate alătura, dar toți ceilalți trebuie să solicite acces."</string>
|
||||||
<string name="screen_security_and_privacy_enable_encryption_alert_confirm_button_title">"Da, activați criptarea"</string>
|
<string name="screen_security_and_privacy_enable_encryption_alert_confirm_button_title">"Da, activați criptarea"</string>
|
||||||
<string name="screen_security_and_privacy_enable_encryption_alert_description">"Odată activată, criptarea pentru o cameră nu poate fi dezactivată. Mesajele anterioare vor fi vizibile numai pentru membrii camerei de la momentul la care au fost invitați sau de la momentul la care s-au alăturat camerei.
|
<string name="screen_security_and_privacy_enable_encryption_alert_description">"Odată activată, criptarea pentru o cameră nu poate fi dezactivată. Mesajele anterioare vor fi vizibile numai pentru membrii camerei de la momentul la care au fost invitați sau de la momentul la care s-au alăturat camerei.
|
||||||
|
|
@ -20,22 +21,26 @@ Nu recomandăm activarea criptării pentru camerele pe care oricine le poate gă
|
||||||
<string name="screen_security_and_privacy_encryption_section_header">"Criptare"</string>
|
<string name="screen_security_and_privacy_encryption_section_header">"Criptare"</string>
|
||||||
<string name="screen_security_and_privacy_encryption_toggle_title">"Activați criptarea end-to-end"</string>
|
<string name="screen_security_and_privacy_encryption_toggle_title">"Activați criptarea end-to-end"</string>
|
||||||
<string name="screen_security_and_privacy_room_access_anyone_option_description">"Oricine se poate alătura."</string>
|
<string name="screen_security_and_privacy_room_access_anyone_option_description">"Oricine se poate alătura."</string>
|
||||||
<string name="screen_security_and_privacy_room_access_footer">"Alegeți membrii căror spații se pot alătura acestei camere fără invitație. %1$s"</string>
|
<string name="screen_security_and_privacy_room_access_anyone_option_title">"Oricine"</string>
|
||||||
|
<string name="screen_security_and_privacy_room_access_footer">"Alegeți membrii căror spații se pot alătura acestei cameră fără invitație. %1$s"</string>
|
||||||
<string name="screen_security_and_privacy_room_access_footer_manage_spaces_action">"Gestionați spațiile"</string>
|
<string name="screen_security_and_privacy_room_access_footer_manage_spaces_action">"Gestionați spațiile"</string>
|
||||||
<string name="screen_security_and_privacy_room_access_invite_only_option_description">"Doar persoanele invitate se pot alătura."</string>
|
<string name="screen_security_and_privacy_room_access_invite_only_option_description">"Doar persoanele invitate se pot alătura."</string>
|
||||||
<string name="screen_security_and_privacy_room_access_invite_only_option_title">"Doar pe bază de invitație"</string>
|
<string name="screen_security_and_privacy_room_access_invite_only_option_title">"Doar pe bază de invitație"</string>
|
||||||
<string name="screen_security_and_privacy_room_access_section_header">"Acces"</string>
|
<string name="screen_security_and_privacy_room_access_section_header">"Acces"</string>
|
||||||
<string name="screen_security_and_privacy_room_access_space_members_option_multiple_parents_description">"Oricine se află într-un spațiu autorizat poate participa."</string>
|
<string name="screen_security_and_privacy_room_access_space_members_option_multiple_parents_description">"Oricine se află într-un spațiu autorizat poate participa."</string>
|
||||||
<string name="screen_security_and_privacy_room_access_space_members_option_single_parent_description">"Oricine din %1$s se poate alătura."</string>
|
<string name="screen_security_and_privacy_room_access_space_members_option_single_parent_description">"Oricine din %1$s se poate alătura."</string>
|
||||||
|
<string name="screen_security_and_privacy_room_access_space_members_option_title">"Membrii spațiului"</string>
|
||||||
<string name="screen_security_and_privacy_room_access_space_members_option_unavailable_description">"Spațiile nu sunt momentan suportate."</string>
|
<string name="screen_security_and_privacy_room_access_space_members_option_unavailable_description">"Spațiile nu sunt momentan suportate."</string>
|
||||||
<string name="screen_security_and_privacy_room_address_section_footer">"Veți avea nevoie de o adresă pentru a o face vizibilă în directorul public."</string>
|
<string name="screen_security_and_privacy_room_address_section_footer">"Veți avea nevoie de o adresă pentru a o face vizibilă în directorul public."</string>
|
||||||
<string name="screen_security_and_privacy_room_address_section_header">"Adresă"</string>
|
<string name="screen_security_and_privacy_room_address_section_header">"Adresă"</string>
|
||||||
<string name="screen_security_and_privacy_room_directory_visibility_section_footer">"Permiteți găsirea acestei camere prin căutarea în directorul de camere publice al %1$s"</string>
|
<string name="screen_security_and_privacy_room_directory_visibility_section_footer">"Permiteți găsirea acestei camere prin căutarea în directorul de camere publice al %1$s"</string>
|
||||||
<string name="screen_security_and_privacy_room_directory_visibility_toggle_description">"Permiteți găsirea prin căutarea în directorul public."</string>
|
<string name="screen_security_and_privacy_room_directory_visibility_toggle_description">"Permiteți găsirea prin căutarea în directorul public."</string>
|
||||||
<string name="screen_security_and_privacy_room_directory_visibility_toggle_title">"Vizibilă în directorul de camere publice"</string>
|
<string name="screen_security_and_privacy_room_directory_visibility_toggle_title">"Vizibilă în directorul de camere publice"</string>
|
||||||
|
<string name="screen_security_and_privacy_room_history_anyone_option_title">"Oricine (istoricul este public)"</string>
|
||||||
|
<string name="screen_security_and_privacy_room_history_section_footer">"Modificările nu vor afecta mesajele anterioare, ci doar pe cele noi. %1$s"</string>
|
||||||
<string name="screen_security_and_privacy_room_history_section_header">"Cine poate citi mesajele anterioare"</string>
|
<string name="screen_security_and_privacy_room_history_section_header">"Cine poate citi mesajele anterioare"</string>
|
||||||
<string name="screen_security_and_privacy_room_history_since_invite_option_title">"Doar pentru membri, de la momentul în care au fost invitați"</string>
|
<string name="screen_security_and_privacy_room_history_since_invite_option_title">"Membri de la momentul invitației"</string>
|
||||||
<string name="screen_security_and_privacy_room_history_since_selecting_option_title">"Doar pentru membri, după selectarea acestei opțiuni"</string>
|
<string name="screen_security_and_privacy_room_history_since_selecting_option_title">"Membri (istoric complet)"</string>
|
||||||
<string name="screen_security_and_privacy_room_publishing_section_footer">"Adresele camerelor sunt modalități de a găsi și accesa camere. Acest lucru vă asigură, de asemenea, că puteți partaja cu ușurință camera dumneavoastră cu alte persoane.
|
<string name="screen_security_and_privacy_room_publishing_section_footer">"Adresele camerelor sunt modalități de a găsi și accesa camere. Acest lucru vă asigură, de asemenea, că puteți partaja cu ușurință camera dumneavoastră cu alte persoane.
|
||||||
Puteți alege să publicați camera în directorul public al camerelor serverului dumneavoastră."</string>
|
Puteți alege să publicați camera în directorul public al camerelor serverului dumneavoastră."</string>
|
||||||
<string name="screen_security_and_privacy_room_publishing_section_header">"Publicare cameră"</string>
|
<string name="screen_security_and_privacy_room_publishing_section_header">"Publicare cameră"</string>
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,21 @@
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="screen_leave_space_subtitle">"Selectați camerele pe care doriți să le părăsiți și în care nu sunteți singurul administrator:"</string>
|
<string name="screen_leave_space_subtitle">"Selectați camerele pe care doriți să le părăsiți și în care nu sunteți singurul administrator:"</string>
|
||||||
<string name="screen_leave_space_subtitle_last_admin">"Trebuie să desemnați un alt administrator pentru acest spațiu înainte de a-l părăsi."</string>
|
<string name="screen_leave_space_subtitle_last_admin">"Trebuie să desemnați un alt administrator pentru acest spațiu înainte de a-l părăsi."</string>
|
||||||
|
<string name="screen_leave_space_subtitle_last_owner">"Sunteți singurul proprietar al %1$s. Trebuie să transferați dreptul de proprietate către altcineva înainte de a parăsi camera."</string>
|
||||||
<string name="screen_leave_space_subtitle_only_last_admin">"Nu veți părăsi următoarele camere deoarece sunteți singurul administrator:"</string>
|
<string name="screen_leave_space_subtitle_only_last_admin">"Nu veți părăsi următoarele camere deoarece sunteți singurul administrator:"</string>
|
||||||
<string name="screen_leave_space_title">"Părăsiți %1$s?"</string>
|
<string name="screen_leave_space_title">"Părăsiți %1$s?"</string>
|
||||||
<string name="screen_leave_space_title_last_admin">"Sunteți singurul administrator pentru %1$s"</string>
|
<string name="screen_leave_space_title_last_admin">"Sunteți singurul administrator pentru %1$s"</string>
|
||||||
|
<string name="screen_leave_space_title_last_owner">"Transferați proprietatea"</string>
|
||||||
|
<string name="screen_space_add_room_action">"Cameră"</string>
|
||||||
|
<string name="screen_space_add_rooms_room_access_description">"Adăugarea unei camere nu va afecta accesul la cameră. Pentru a modifica accesul, accesați Setări cameră > Securitate și confidențialitate."</string>
|
||||||
|
<string name="screen_space_empty_state_title">"Adăugați prima dumneavoastră cameră"</string>
|
||||||
<string name="screen_space_menu_action_members">"Vizualizați membrii"</string>
|
<string name="screen_space_menu_action_members">"Vizualizați membrii"</string>
|
||||||
|
<string name="screen_space_remove_rooms_confirmation_content">"Eliminarea unei camere nu va afecta accesul la aceasta. Pentru a modifica accesul, accesați Informații despre cameră > Confidențialitate și securitate."</string>
|
||||||
|
<plurals name="screen_space_remove_rooms_confirmation_title">
|
||||||
|
<item quantity="one">"Eliminați camera %1$d din %2$s"</item>
|
||||||
|
<item quantity="few">"Eliminați camerele %1$d din %2$s"</item>
|
||||||
|
<item quantity="other">"Eliminați camerele %1$d din %2$s"</item>
|
||||||
|
</plurals>
|
||||||
<string name="screen_space_settings_leave_space">"Părăsiți spațiul"</string>
|
<string name="screen_space_settings_leave_space">"Părăsiți spațiul"</string>
|
||||||
<string name="screen_space_settings_roles_and_permissions">"Roluri și permisiuni"</string>
|
<string name="screen_space_settings_roles_and_permissions">"Roluri și permisiuni"</string>
|
||||||
<string name="screen_space_settings_security_and_privacy">"Securitate & confidențialitate"</string>
|
<string name="screen_space_settings_security_and_privacy">"Securitate & confidențialitate"</string>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
<string name="screen_identity_confirmation_cannot_confirm">"Kas kinnitamine pole võimalik?"</string>
|
<string name="screen_identity_confirmation_cannot_confirm">"Kas kinnitamine pole võimalik?"</string>
|
||||||
<string name="screen_identity_confirmation_create_new_recovery_key">"Loo uus taastevõti"</string>
|
<string name="screen_identity_confirmation_create_new_recovery_key">"Loo uus taastevõti"</string>
|
||||||
<string name="screen_identity_confirmation_subtitle">"Krüptitud sõnumivahetuse tagamiseks verifitseeri see seade."</string>
|
<string name="screen_identity_confirmation_subtitle">"Turvalise sõnumside seadistamiseks vali verifitseerimise viis."</string>
|
||||||
<string name="screen_identity_confirmation_title">"Kinnita oma digitaalne identiteet"</string>
|
<string name="screen_identity_confirmation_title">"Kinnita oma digitaalne identiteet"</string>
|
||||||
<string name="screen_identity_confirmation_use_another_device">"Kasuta teist seadet"</string>
|
<string name="screen_identity_confirmation_use_another_device">"Kasuta teist seadet"</string>
|
||||||
<string name="screen_identity_confirmation_use_recovery_key">"Kasuta taastevõtit"</string>
|
<string name="screen_identity_confirmation_use_recovery_key">"Kasuta taastevõtit"</string>
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
<string name="screen_session_verification_compare_numbers_subtitle">"Kinnita, et kõik järgnevalt kuvatud numbrid on täpselt samad, mida sa näed oma teises sessioonis."</string>
|
<string name="screen_session_verification_compare_numbers_subtitle">"Kinnita, et kõik järgnevalt kuvatud numbrid on täpselt samad, mida sa näed oma teises sessioonis."</string>
|
||||||
<string name="screen_session_verification_compare_numbers_title">"Võrdle numbreid"</string>
|
<string name="screen_session_verification_compare_numbers_title">"Võrdle numbreid"</string>
|
||||||
<string name="screen_session_verification_complete_subtitle">"Võid nüüd sõnumeid oma teises seadmes turvaliselt saata ja vastu võtta."</string>
|
<string name="screen_session_verification_complete_subtitle">"Võid nüüd sõnumeid oma teises seadmes turvaliselt saata ja vastu võtta."</string>
|
||||||
<string name="screen_session_verification_complete_user_subtitle">"Nüüd sa võid sõnumite vastuvõtmisel ja saatmisel selle kasutaja identiteeti usaldada."</string>
|
<string name="screen_session_verification_complete_user_subtitle">"Nüüd sa võid sõnumite vastuvõtmisel ja saatmisel selle kasutaja digitaalset identiteeti usaldada."</string>
|
||||||
<string name="screen_session_verification_device_verified">"Seade on verifitseeritud"</string>
|
<string name="screen_session_verification_device_verified">"Seade on verifitseeritud"</string>
|
||||||
<string name="screen_session_verification_enter_recovery_key">"Sisesta taastevõti"</string>
|
<string name="screen_session_verification_enter_recovery_key">"Sisesta taastevõti"</string>
|
||||||
<string name="screen_session_verification_failed_subtitle">"Kas verifitseerimine aegus, teine osapool keeldus vastamast või tekkis vastuste mittevastavus."</string>
|
<string name="screen_session_verification_failed_subtitle">"Kas verifitseerimine aegus, teine osapool keeldus vastamast või tekkis vastuste mittevastavus."</string>
|
||||||
|
|
@ -42,7 +42,7 @@
|
||||||
<string name="screen_session_verification_use_another_device_title">"Ava rakendus teises verifitseeritud seadmes"</string>
|
<string name="screen_session_verification_use_another_device_title">"Ava rakendus teises verifitseeritud seadmes"</string>
|
||||||
<string name="screen_session_verification_user_initiator_subtitle">"Lisaturvalisuse nimel verifitseeri seee kasutaja, võrreldes oma seadmetes olevaid emojisid. Tee seda, kasutades usaldusväärset suhtlusviisi."</string>
|
<string name="screen_session_verification_user_initiator_subtitle">"Lisaturvalisuse nimel verifitseeri seee kasutaja, võrreldes oma seadmetes olevaid emojisid. Tee seda, kasutades usaldusväärset suhtlusviisi."</string>
|
||||||
<string name="screen_session_verification_user_initiator_title">"Kas verifitseerime selle kasutaja?"</string>
|
<string name="screen_session_verification_user_initiator_title">"Kas verifitseerime selle kasutaja?"</string>
|
||||||
<string name="screen_session_verification_user_responder_subtitle">"Lisaturvalisuse nimel soovib teine kasutaja sinu identiteeti verifitseerida. Järgmiseks näed sa emojisid, mida peate omavahel võrdlema."</string>
|
<string name="screen_session_verification_user_responder_subtitle">"Lisaturvalisuse nimel soovib teine kasutaja sinu digitaalse identiteeti verifitseerida. Järgmiseks näed sa emojisid, mida peate omavahel võrdlema."</string>
|
||||||
<string name="screen_session_verification_waiting_another_device_subtitle">"Sa peaksid teises seadmes nägema hüpikakent. Palun alusta sealt verifitseerimist."</string>
|
<string name="screen_session_verification_waiting_another_device_subtitle">"Sa peaksid teises seadmes nägema hüpikakent. Palun alusta sealt verifitseerimist."</string>
|
||||||
<string name="screen_session_verification_waiting_another_device_title">"Alusta verifitseerimist teises seadmes"</string>
|
<string name="screen_session_verification_waiting_another_device_title">"Alusta verifitseerimist teises seadmes"</string>
|
||||||
<string name="screen_session_verification_waiting_other_device_title">"Alusta verifitseerimist teises seadmes"</string>
|
<string name="screen_session_verification_waiting_other_device_title">"Alusta verifitseerimist teises seadmes"</string>
|
||||||
|
|
@ -50,5 +50,5 @@
|
||||||
<string name="screen_session_verification_waiting_subtitle">"Kui oled nõustunud, siis saad sa verifitseerimist jätkata."</string>
|
<string name="screen_session_verification_waiting_subtitle">"Kui oled nõustunud, siis saad sa verifitseerimist jätkata."</string>
|
||||||
<string name="screen_session_verification_waiting_to_accept_subtitle">"Jätkamaks nõustu verifitseerimisprotsessi alustamisega oma teises sessioonis."</string>
|
<string name="screen_session_verification_waiting_to_accept_subtitle">"Jätkamaks nõustu verifitseerimisprotsessi alustamisega oma teises sessioonis."</string>
|
||||||
<string name="screen_session_verification_waiting_to_accept_title">"Ootame nõustumist verifitseerimispäringuga"</string>
|
<string name="screen_session_verification_waiting_to_accept_title">"Ootame nõustumist verifitseerimispäringuga"</string>
|
||||||
<string name="screen_signout_in_progress_dialog_content">"Logime välja…"</string>
|
<string name="screen_signout_in_progress_dialog_content">"Eemaldan seadet…"</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
<string name="screen_session_verification_compare_numbers_subtitle">"Confirmați că numerele de mai jos se potrivesc cu cele afișate în cealaltă sesiune."</string>
|
<string name="screen_session_verification_compare_numbers_subtitle">"Confirmați că numerele de mai jos se potrivesc cu cele afișate în cealaltă sesiune."</string>
|
||||||
<string name="screen_session_verification_compare_numbers_title">"Comparați numerele"</string>
|
<string name="screen_session_verification_compare_numbers_title">"Comparați numerele"</string>
|
||||||
<string name="screen_session_verification_complete_subtitle">"Noua dumneavoastră sesiune este acum verificată. Are acces la mesajele dumneavoastră criptate, iar ceilalti utilizatori vă vor vedea ca fiind de încredere."</string>
|
<string name="screen_session_verification_complete_subtitle">"Noua dumneavoastră sesiune este acum verificată. Are acces la mesajele dumneavoastră criptate, iar ceilalti utilizatori vă vor vedea ca fiind de încredere."</string>
|
||||||
<string name="screen_session_verification_complete_user_subtitle">"Acum puteți avea încredere în identitatea acestui utilizator atunci când trimiteți sau primiți mesaje."</string>
|
<string name="screen_session_verification_complete_user_subtitle">"Acum puteți avea încredere în identitatea digitală a acestui utilizator atunci când trimiteți sau primiți mesaje."</string>
|
||||||
<string name="screen_session_verification_device_verified">"Dispozitiv verificat"</string>
|
<string name="screen_session_verification_device_verified">"Dispozitiv verificat"</string>
|
||||||
<string name="screen_session_verification_enter_recovery_key">"Introduceți cheia de recuperare"</string>
|
<string name="screen_session_verification_enter_recovery_key">"Introduceți cheia de recuperare"</string>
|
||||||
<string name="screen_session_verification_failed_subtitle">"Fie cererea a expirat, cererea a fost respinsă, fie a existat o nepotrivire de verificare."</string>
|
<string name="screen_session_verification_failed_subtitle">"Fie cererea a expirat, cererea a fost respinsă, fie a existat o nepotrivire de verificare."</string>
|
||||||
|
|
@ -42,7 +42,7 @@
|
||||||
<string name="screen_session_verification_use_another_device_title">"Deschideți aplicația pe un alt dispozitiv verificat"</string>
|
<string name="screen_session_verification_use_another_device_title">"Deschideți aplicația pe un alt dispozitiv verificat"</string>
|
||||||
<string name="screen_session_verification_user_initiator_subtitle">"Pentru securitate suplimentară, verificați acest utilizator comparând un set de emoji-uri pe dispozitivele dvs. Faceți acest lucru utilizând o metodă de comunicare de încredere."</string>
|
<string name="screen_session_verification_user_initiator_subtitle">"Pentru securitate suplimentară, verificați acest utilizator comparând un set de emoji-uri pe dispozitivele dvs. Faceți acest lucru utilizând o metodă de comunicare de încredere."</string>
|
||||||
<string name="screen_session_verification_user_initiator_title">"Verificați acest utilizator?"</string>
|
<string name="screen_session_verification_user_initiator_title">"Verificați acest utilizator?"</string>
|
||||||
<string name="screen_session_verification_user_responder_subtitle">"Pentru o securitate suplimentară, un alt utilizator dorește să vă verifice identitatea. Vi se va afișa un set de emoji-uri pentru comparație."</string>
|
<string name="screen_session_verification_user_responder_subtitle">"Pentru o securitate sporită, un alt utilizator dorește să vă verifice identitatea digitală. Vi se va afișa un set de emoji-uri pentru comparație."</string>
|
||||||
<string name="screen_session_verification_waiting_another_device_subtitle">"Ar trebui să vedeți o fereastră pop-up pe celălalt dispozitiv. Începeți verificarea de acolo acum."</string>
|
<string name="screen_session_verification_waiting_another_device_subtitle">"Ar trebui să vedeți o fereastră pop-up pe celălalt dispozitiv. Începeți verificarea de acolo acum."</string>
|
||||||
<string name="screen_session_verification_waiting_another_device_title">"Începeți verificarea pe celălalt dispozitiv"</string>
|
<string name="screen_session_verification_waiting_another_device_title">"Începeți verificarea pe celălalt dispozitiv"</string>
|
||||||
<string name="screen_session_verification_waiting_other_device_title">"Începeți verificarea pe celălalt dispozitiv"</string>
|
<string name="screen_session_verification_waiting_other_device_title">"Începeți verificarea pe celălalt dispozitiv"</string>
|
||||||
|
|
|
||||||
|
|
@ -18,14 +18,14 @@ constraintlayout_compose = "1.1.1"
|
||||||
lifecycle = "2.10.0"
|
lifecycle = "2.10.0"
|
||||||
activity = "1.13.0"
|
activity = "1.13.0"
|
||||||
media3 = "1.10.0"
|
media3 = "1.10.0"
|
||||||
camera = "1.6.0"
|
camera = "1.6.1"
|
||||||
work = "2.11.2"
|
work = "2.11.2"
|
||||||
|
|
||||||
# Compose
|
# Compose
|
||||||
compose_bom = "2026.04.01"
|
compose_bom = "2026.05.00"
|
||||||
|
|
||||||
# Coroutines
|
# Coroutines
|
||||||
coroutines = "1.10.2"
|
coroutines = "1.11.0"
|
||||||
|
|
||||||
# Accompanist
|
# Accompanist
|
||||||
accompanist = "0.37.3"
|
accompanist = "0.37.3"
|
||||||
|
|
@ -35,7 +35,7 @@ test_core = "1.7.0"
|
||||||
roborazzi = "1.60.0"
|
roborazzi = "1.60.0"
|
||||||
|
|
||||||
# Jetbrain
|
# Jetbrain
|
||||||
datetime = "0.7.1"
|
datetime = "0.8.0"
|
||||||
serialization_json = "1.11.0"
|
serialization_json = "1.11.0"
|
||||||
|
|
||||||
#other
|
#other
|
||||||
|
|
@ -80,7 +80,7 @@ kotlinpoet-ksp = { module = "com.squareup:kotlinpoet-ksp", version.ref = "kotlin
|
||||||
kover_gradle_plugin = { module = "org.jetbrains.kotlinx:kover-gradle-plugin", version.ref = "kover" }
|
kover_gradle_plugin = { module = "org.jetbrains.kotlinx:kover-gradle-plugin", version.ref = "kover" }
|
||||||
ksp_gradle_plugin = { module = "com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin", version.ref = "ksp" }
|
ksp_gradle_plugin = { module = "com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin", version.ref = "ksp" }
|
||||||
# https://firebase.google.com/docs/android/setup#available-libraries
|
# https://firebase.google.com/docs/android/setup#available-libraries
|
||||||
google_firebase_bom = "com.google.firebase:firebase-bom:34.12.0"
|
google_firebase_bom = "com.google.firebase:firebase-bom:34.13.0"
|
||||||
firebase_appdistribution_gradle = { module = "com.google.firebase:firebase-appdistribution-gradle", version.ref = "firebaseAppDistribution" }
|
firebase_appdistribution_gradle = { module = "com.google.firebase:firebase-appdistribution-gradle", version.ref = "firebaseAppDistribution" }
|
||||||
autonomousapps_dependencyanalysis_plugin = { module = "com.autonomousapps:dependency-analysis-gradle-plugin", version.ref = "dependencyAnalysis" }
|
autonomousapps_dependencyanalysis_plugin = { module = "com.autonomousapps:dependency-analysis-gradle-plugin", version.ref = "dependencyAnalysis" }
|
||||||
ksp_plugin = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "ksp" }
|
ksp_plugin = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "ksp" }
|
||||||
|
|
@ -111,13 +111,14 @@ androidx_media3_ui = { module = "androidx.media3:media3-ui", version.ref = "medi
|
||||||
androidx_media3_transformer = { module = "androidx.media3:media3-transformer", version.ref = "media3" }
|
androidx_media3_transformer = { module = "androidx.media3:media3-transformer", version.ref = "media3" }
|
||||||
androidx_media3_effect = { module = "androidx.media3:media3-effect", version.ref = "media3" }
|
androidx_media3_effect = { module = "androidx.media3:media3-effect", version.ref = "media3" }
|
||||||
androidx_media3_common = { module = "androidx.media3:media3-common", version.ref = "media3" }
|
androidx_media3_common = { module = "androidx.media3:media3-common", version.ref = "media3" }
|
||||||
|
androidx_media3_exoplayer_midi = { module = "androidx.media3:media3-exoplayer-midi", version.ref = "media3" }
|
||||||
androidx_biometric = "androidx.biometric:biometric-ktx:1.4.0-alpha02"
|
androidx_biometric = "androidx.biometric:biometric-ktx:1.4.0-alpha02"
|
||||||
|
|
||||||
androidx_activity_activity = { module = "androidx.activity:activity", version.ref = "activity" }
|
androidx_activity_activity = { module = "androidx.activity:activity", version.ref = "activity" }
|
||||||
androidx_activity_compose = { module = "androidx.activity:activity-compose", version.ref = "activity" }
|
androidx_activity_compose = { module = "androidx.activity:activity-compose", version.ref = "activity" }
|
||||||
androidx_startup = "androidx.startup:startup-runtime:1.2.0"
|
androidx_startup = "androidx.startup:startup-runtime:1.2.0"
|
||||||
androidx_preference = "androidx.preference:preference:1.2.1"
|
androidx_preference = "androidx.preference:preference:1.2.1"
|
||||||
androidx_webkit = "androidx.webkit:webkit:1.15.0"
|
androidx_webkit = "androidx.webkit:webkit:1.16.0"
|
||||||
|
|
||||||
androidx_compose_bom = { module = "androidx.compose:compose-bom", version.ref = "compose_bom" }
|
androidx_compose_bom = { module = "androidx.compose:compose-bom", version.ref = "compose_bom" }
|
||||||
androidx_compose_material3 = { module = "androidx.compose.material3:material3", version = '1.5.0-alpha15' }
|
androidx_compose_material3 = { module = "androidx.compose.material3:material3", version = '1.5.0-alpha15' }
|
||||||
|
|
@ -178,7 +179,7 @@ test_detekt_test = { module = "io.gitlab.arturbosch.detekt:detekt-test", version
|
||||||
# https://github.com/matrix-org/matrix-rust-components-kotlin/commits/main/sdk/sdk-android/src/main/kotlin/org/matrix/rustcomponents/sdk/matrix_sdk_ffi.kt
|
# https://github.com/matrix-org/matrix-rust-components-kotlin/commits/main/sdk/sdk-android/src/main/kotlin/org/matrix/rustcomponents/sdk/matrix_sdk_ffi.kt
|
||||||
# All new features should not be implemented in the pull request that upgrades the version, developers should
|
# All new features should not be implemented in the pull request that upgrades the version, developers should
|
||||||
# only fix API breaks and may add some TODOs.
|
# only fix API breaks and may add some TODOs.
|
||||||
matrix_sdk = "org.matrix.rustcomponents:sdk-android:26.05.13"
|
matrix_sdk = "org.matrix.rustcomponents:sdk-android:26.05.18"
|
||||||
|
|
||||||
# Others
|
# Others
|
||||||
coil = { module = "io.coil-kt.coil3:coil", version.ref = "coil" }
|
coil = { module = "io.coil-kt.coil3:coil", version.ref = "coil" }
|
||||||
|
|
@ -222,7 +223,7 @@ color_picker = "io.mhssn:colorpicker:1.0.0"
|
||||||
|
|
||||||
# Analytics
|
# Analytics
|
||||||
posthog = "com.posthog:posthog-android:3.43.0"
|
posthog = "com.posthog:posthog-android:3.43.0"
|
||||||
sentry = "io.sentry:sentry-android:8.40.0"
|
sentry = "io.sentry:sentry-android:8.41.0"
|
||||||
# main branch can be tested replacing the version with main-SNAPSHOT
|
# main branch can be tested replacing the version with main-SNAPSHOT
|
||||||
matrix_analytics_events = "com.github.matrix-org:matrix-analytics-events:0.33.2"
|
matrix_analytics_events = "com.github.matrix-org:matrix-analytics-events:0.33.2"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@
|
||||||
<string name="state_event_room_invite_by_you">"Zaprosiłeś %1$s"</string>
|
<string name="state_event_room_invite_by_you">"Zaprosiłeś %1$s"</string>
|
||||||
<string name="state_event_room_invite_you">"%1$s zaprosił Cię"</string>
|
<string name="state_event_room_invite_you">"%1$s zaprosił Cię"</string>
|
||||||
<string name="state_event_room_join">"%1$s dołączył do pokoju"</string>
|
<string name="state_event_room_join">"%1$s dołączył do pokoju"</string>
|
||||||
<string name="state_event_room_join_by_you">"Dołączyłeś(aś) do pokoju"</string>
|
<string name="state_event_room_join_by_you">"Dołączyłeś do pokoju"</string>
|
||||||
<string name="state_event_room_knock">"%1$s prosi o możliwość dołączenia"</string>
|
<string name="state_event_room_knock">"%1$s prosi o możliwość dołączenia"</string>
|
||||||
<string name="state_event_room_knock_accepted">"%1$s zezwolił %2$s na dołączenie"</string>
|
<string name="state_event_room_knock_accepted">"%1$s zezwolił %2$s na dołączenie"</string>
|
||||||
<string name="state_event_room_knock_accepted_by_you">"Zezwoliłeś %1$s na dołączenie"</string>
|
<string name="state_event_room_knock_accepted_by_you">"Zezwoliłeś %1$s na dołączenie"</string>
|
||||||
|
|
|
||||||
|
|
@ -93,13 +93,6 @@ enum class FeatureFlags(
|
||||||
defaultValue = { false },
|
defaultValue = { false },
|
||||||
isFinished = false,
|
isFinished = false,
|
||||||
),
|
),
|
||||||
SignInWithClassic(
|
|
||||||
key = "feature.signin_with_classic",
|
|
||||||
title = "Sign in with Element Classic",
|
|
||||||
description = "Allow the application to sign in to the current Element Classic account.",
|
|
||||||
defaultValue = { false },
|
|
||||||
isFinished = false,
|
|
||||||
),
|
|
||||||
AllowBlackTheme(
|
AllowBlackTheme(
|
||||||
key = "feature.allow_black_theme",
|
key = "feature.allow_black_theme",
|
||||||
title = "Black theme",
|
title = "Black theme",
|
||||||
|
|
@ -107,13 +100,6 @@ enum class FeatureFlags(
|
||||||
defaultValue = { false },
|
defaultValue = { false },
|
||||||
isFinished = false,
|
isFinished = false,
|
||||||
),
|
),
|
||||||
LiveLocationSharing(
|
|
||||||
key = "feature.liveLocationSharing",
|
|
||||||
title = "Live location sharing",
|
|
||||||
description = "Allow sharing live location in rooms.",
|
|
||||||
defaultValue = { false },
|
|
||||||
isFinished = false,
|
|
||||||
),
|
|
||||||
ValidateNetworkWhenSchedulingNotificationFetching(
|
ValidateNetworkWhenSchedulingNotificationFetching(
|
||||||
key = "feature.validate_network_when_scheduling_notification_fetching",
|
key = "feature.validate_network_when_scheduling_notification_fetching",
|
||||||
title = "Validate internet connectivity when scheduling notification fetching",
|
title = "Validate internet connectivity when scheduling notification fetching",
|
||||||
|
|
|
||||||
|
|
@ -434,13 +434,30 @@ class RustMatrixAuthenticationService(
|
||||||
qrCodeData: QrCodeData,
|
qrCodeData: QrCodeData,
|
||||||
): Client {
|
): Client {
|
||||||
Timber.d("Creating client for QR Code login with simplified sliding sync")
|
Timber.d("Creating client for QR Code login with simplified sliding sync")
|
||||||
|
// The 2025 version of MSC4108 provides baseUrl; the 2024 version has null baseUrl and uses
|
||||||
|
// serverName instead, which can be null or malformed. We only enforce presence/non-blankness
|
||||||
|
// here and rely on serverNameOrHomeserverUrl()/the Rust builder layer to validate structure.
|
||||||
|
val baseUrlOrServerName = qrCodeData.baseUrl() ?: qrCodeData.serverName()
|
||||||
|
|
||||||
|
if (baseUrlOrServerName == null) {
|
||||||
|
// With the 2024 version of MSC4108 we treat the absence of serverName as meaning that
|
||||||
|
// the other device is not signed in.
|
||||||
|
Timber.e("The QR code is from a device that is not yet signed in")
|
||||||
|
throw HumanQrLoginException.OtherDeviceNotSignedIn()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (baseUrlOrServerName.isBlank()) {
|
||||||
|
Timber.e("The QR code contains an empty base URL or server name, which is invalid")
|
||||||
|
throw HumanQrLoginException.Unknown()
|
||||||
|
}
|
||||||
|
|
||||||
return rustMatrixClientFactory
|
return rustMatrixClientFactory
|
||||||
.getBaseClientBuilder(
|
.getBaseClientBuilder(
|
||||||
sessionPaths = sessionPaths,
|
sessionPaths = sessionPaths,
|
||||||
passphrase = pendingPassphrase,
|
passphrase = pendingPassphrase,
|
||||||
slidingSyncType = ClientBuilderSlidingSync.Discovered,
|
slidingSyncType = ClientBuilderSlidingSync.Discovered,
|
||||||
)
|
)
|
||||||
.serverNameOrHomeserverUrl(qrCodeData.serverName()!!)
|
.serverNameOrHomeserverUrl(baseUrlOrServerName)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,25 +16,25 @@ import org.matrix.rustcomponents.sdk.RoomListEntriesUpdate
|
||||||
internal fun RoomListEntriesUpdate.describe(): String {
|
internal fun RoomListEntriesUpdate.describe(): String {
|
||||||
return when (this) {
|
return when (this) {
|
||||||
is RoomListEntriesUpdate.Set -> {
|
is RoomListEntriesUpdate.Set -> {
|
||||||
"Set #$index to '${value.displayName()}'"
|
"Set #$index to '${value.id()}'"
|
||||||
}
|
}
|
||||||
is RoomListEntriesUpdate.Append -> {
|
is RoomListEntriesUpdate.Append -> {
|
||||||
"Append ${values.map { "'" + it.displayName() + "'" }}"
|
"Append ${values.map { "'" + it.id() + "'" }}"
|
||||||
}
|
}
|
||||||
is RoomListEntriesUpdate.PushBack -> {
|
is RoomListEntriesUpdate.PushBack -> {
|
||||||
"PushBack '${value.displayName()}'"
|
"PushBack '${value.id()}'"
|
||||||
}
|
}
|
||||||
is RoomListEntriesUpdate.PushFront -> {
|
is RoomListEntriesUpdate.PushFront -> {
|
||||||
"PushFront '${value.displayName()}'"
|
"PushFront '${value.id()}'"
|
||||||
}
|
}
|
||||||
is RoomListEntriesUpdate.Insert -> {
|
is RoomListEntriesUpdate.Insert -> {
|
||||||
"Insert at #$index: '${value.displayName()}'"
|
"Insert at #$index: '${value.id()}'"
|
||||||
}
|
}
|
||||||
is RoomListEntriesUpdate.Remove -> {
|
is RoomListEntriesUpdate.Remove -> {
|
||||||
"Remove #$index"
|
"Remove #$index"
|
||||||
}
|
}
|
||||||
is RoomListEntriesUpdate.Reset -> {
|
is RoomListEntriesUpdate.Reset -> {
|
||||||
"Reset all to ${values.map { "'" + it.displayName() + "'" }}"
|
"Reset all to ${values.map { "'" + it.id() + "'" }}"
|
||||||
}
|
}
|
||||||
RoomListEntriesUpdate.PopBack -> {
|
RoomListEntriesUpdate.PopBack -> {
|
||||||
"PopBack"
|
"PopBack"
|
||||||
|
|
|
||||||
|
|
@ -112,6 +112,11 @@ class RoomSummaryListProcessor(
|
||||||
private suspend fun updateRoomSummaries(updates: List<RoomListEntriesUpdate>, block: suspend MutableList<RoomSummary>.() -> Unit) = withContext(
|
private suspend fun updateRoomSummaries(updates: List<RoomListEntriesUpdate>, block: suspend MutableList<RoomSummary>.() -> Unit) = withContext(
|
||||||
coroutineContext
|
coroutineContext
|
||||||
) {
|
) {
|
||||||
|
// Capture the description before applying updates: applyUpdate consumes each Room via
|
||||||
|
// `entry.use { ... }` which destroys it, and the duplicate-detection branch below reads
|
||||||
|
// id() through `describe()`. Without this capture the trackError call crashes before it
|
||||||
|
// can be reported.
|
||||||
|
val updatesDescription = updates.description()
|
||||||
mutex.withLock {
|
mutex.withLock {
|
||||||
val current = roomSummaries.replayCache.lastOrNull()
|
val current = roomSummaries.replayCache.lastOrNull()
|
||||||
val mutableRoomSummaries = current.orEmpty().toMutableList()
|
val mutableRoomSummaries = current.orEmpty().toMutableList()
|
||||||
|
|
@ -126,7 +131,7 @@ class RoomSummaryListProcessor(
|
||||||
analyticsService.trackError(
|
analyticsService.trackError(
|
||||||
IllegalStateException(
|
IllegalStateException(
|
||||||
"Found duplicates in room summaries after a list update from the SDK: $duplicates. " +
|
"Found duplicates in room summaries after a list update from the SDK: $duplicates. " +
|
||||||
"Updates: ${updates.description()}"
|
"Updates: $updatesDescription"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ class FakeFfiSpaceRoomList(
|
||||||
return paginationStateResult()
|
return paginationStateResult()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun rooms(): List<SpaceRoom> {
|
override suspend fun rooms(): List<SpaceRoom> {
|
||||||
return roomsResult()
|
return roomsResult()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -53,7 +53,7 @@ class FakeFfiSpaceRoomList(
|
||||||
spaceRoomListPaginationStateListener?.onUpdate(state)
|
spaceRoomListPaginationStateListener?.onUpdate(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun subscribeToRoomUpdate(listener: SpaceRoomListEntriesListener): TaskHandle {
|
override suspend fun subscribeToRoomUpdate(listener: SpaceRoomListEntriesListener): TaskHandle {
|
||||||
spaceRoomListEntriesListener = listener
|
spaceRoomListEntriesListener = listener
|
||||||
return FakeFfiTaskHandle()
|
return FakeFfiTaskHandle()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -173,16 +173,68 @@ class RoomSummaryListProcessorTest {
|
||||||
assertThat(summaries.value[index].roomId).isEqualTo(A_ROOM_ID_3)
|
assertThat(summaries.value[index].roomId).isEqualTo(A_ROOM_ID_3)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tracking issue #4182 / #5031: rooms duplicated in the room list.
|
||||||
|
*
|
||||||
|
* If duplicates are present in the upstream summaries flow, the dedupe safety net in
|
||||||
|
* [RoomSummaryListProcessor.updateRoomSummaries] must remove them and report the incident via
|
||||||
|
* [analyticsService.trackError]. Uses an empty update to drive the dedupe path without
|
||||||
|
* passing a Rust Room through the destroy-on-use path.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
fun `pre-existing duplicates in summaries are deduped on next update and trackError fires`() = runTest {
|
||||||
|
summaries.value = listOf(
|
||||||
|
aRoomSummary(roomId = A_ROOM_ID),
|
||||||
|
aRoomSummary(roomId = A_ROOM_ID), // simulated SDK-side leak
|
||||||
|
aRoomSummary(roomId = A_ROOM_ID_2),
|
||||||
|
)
|
||||||
|
val analyticsService = FakeAnalyticsService()
|
||||||
|
val processor = createProcessor(analyticsService = analyticsService)
|
||||||
|
|
||||||
|
processor.postUpdate(emptyList())
|
||||||
|
|
||||||
|
assertThat(summaries.value.map { it.roomId }).containsExactly(A_ROOM_ID, A_ROOM_ID_2).inOrder()
|
||||||
|
assertThat(analyticsService.trackedErrors).hasSize(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tracking issue #4182 / #5031.
|
||||||
|
*
|
||||||
|
* Insert is the most likely Rust-SDK trigger for a duplicate-room report: it blindly inserts
|
||||||
|
* a new entry at an index without checking whether the roomId already exists. Before the
|
||||||
|
* describe-capture fix, the dedupe branch in [updateRoomSummaries] would call `Room.id()`
|
||||||
|
* on an already-destroyed Room (because [applyUpdate] consumes each value via
|
||||||
|
* `entry.use { ... }`) and crash before [trackError] could be invoked. This test guards the
|
||||||
|
* fix: the Insert is processed, the list is emitted deduplicated, and the tracked error
|
||||||
|
* message carries the human-readable description of the offending update.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
fun `Insert that triggers dedupe is reported via trackError without crashing`() = runTest {
|
||||||
|
summaries.value = listOf(aRoomSummary(roomId = A_ROOM_ID))
|
||||||
|
val analyticsService = FakeAnalyticsService()
|
||||||
|
val processor = createProcessor(analyticsService = analyticsService)
|
||||||
|
|
||||||
|
processor.postUpdate(listOf(RoomListEntriesUpdate.Insert(0u, aRustRoom(A_ROOM_ID))))
|
||||||
|
|
||||||
|
assertThat(summaries.value.map { it.roomId }).containsExactly(A_ROOM_ID)
|
||||||
|
assertThat(analyticsService.trackedErrors).hasSize(1)
|
||||||
|
val message = analyticsService.trackedErrors.single().message.orEmpty()
|
||||||
|
assertThat(message).contains("Found duplicates")
|
||||||
|
assertThat(message).contains("Insert at #0")
|
||||||
|
}
|
||||||
|
|
||||||
private fun aRustRoom(roomId: RoomId = A_ROOM_ID) = FakeFfiRoom(
|
private fun aRustRoom(roomId: RoomId = A_ROOM_ID) = FakeFfiRoom(
|
||||||
roomId = roomId,
|
roomId = roomId,
|
||||||
latestEventLambda = { LatestEventValue.None }
|
latestEventLambda = { LatestEventValue.None }
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun TestScope.createProcessor() = RoomSummaryListProcessor(
|
private fun TestScope.createProcessor(
|
||||||
|
analyticsService: FakeAnalyticsService = FakeAnalyticsService(),
|
||||||
|
) = RoomSummaryListProcessor(
|
||||||
summaries,
|
summaries,
|
||||||
FakeFfiRoomListService(),
|
FakeFfiRoomListService(),
|
||||||
coroutineContext = StandardTestDispatcher(testScheduler),
|
coroutineContext = StandardTestDispatcher(testScheduler),
|
||||||
roomSummaryFactory = RoomSummaryFactory(),
|
roomSummaryFactory = RoomSummaryFactory(),
|
||||||
analyticsService = FakeAnalyticsService(),
|
analyticsService = analyticsService,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
<string name="screen_bottom_sheet_create_dm_confirmation_button_title">"Saada kutse"</string>
|
<string name="screen_bottom_sheet_create_dm_confirmation_button_title">"Saada kutse"</string>
|
||||||
<string name="screen_bottom_sheet_create_dm_message">"Kas sa soovid alustada vestlust kasutajaga %1$s?"</string>
|
<string name="screen_bottom_sheet_create_dm_message">"Kas sa soovid alustada vestlust kasutajaga %1$s?"</string>
|
||||||
<string name="screen_bottom_sheet_create_dm_title">"Kas saadame kutse?"</string>
|
<string name="screen_bottom_sheet_create_dm_title">"Kas saadame kutse?"</string>
|
||||||
|
<string name="screen_bottom_sheet_create_dm_unknown_user_content">"Sul pole hetkel selle inimesega ühtegi vestlust. Enne jätkamist kinnita talle kutse saatmine."</string>
|
||||||
<string name="screen_bottom_sheet_create_dm_unknown_user_title">"Kas alustad vestlust selle uue kontaktiga?"</string>
|
<string name="screen_bottom_sheet_create_dm_unknown_user_title">"Kas alustad vestlust selle uue kontaktiga?"</string>
|
||||||
<string name="screen_invites_invited_you">"%1$s (%2$s) saatis sulle kutse"</string>
|
<string name="screen_invites_invited_you">"%1$s (%2$s) saatis sulle kutse"</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -3,5 +3,7 @@
|
||||||
<string name="screen_bottom_sheet_create_dm_confirmation_button_title">"Trimiteți invitația"</string>
|
<string name="screen_bottom_sheet_create_dm_confirmation_button_title">"Trimiteți invitația"</string>
|
||||||
<string name="screen_bottom_sheet_create_dm_message">"Doriți să începeți o discuție cu %1$s?"</string>
|
<string name="screen_bottom_sheet_create_dm_message">"Doriți să începeți o discuție cu %1$s?"</string>
|
||||||
<string name="screen_bottom_sheet_create_dm_title">"Trimiteți invitația?"</string>
|
<string name="screen_bottom_sheet_create_dm_title">"Trimiteți invitația?"</string>
|
||||||
|
<string name="screen_bottom_sheet_create_dm_unknown_user_content">"În prezent, nu aveți nicio chat cu această persoană. Confirmați invitația înainte de a continua."</string>
|
||||||
|
<string name="screen_bottom_sheet_create_dm_unknown_user_title">"Începeți o conversație cu acest nou contact?"</string>
|
||||||
<string name="screen_invites_invited_you">"%1$s (%2$s) v-a invitat."</string>
|
<string name="screen_invites_invited_you">"%1$s (%2$s) v-a invitat."</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ dependencies {
|
||||||
implementation(libs.coroutines.core)
|
implementation(libs.coroutines.core)
|
||||||
implementation(libs.coil.compose)
|
implementation(libs.coil.compose)
|
||||||
implementation(libs.androidx.media3.exoplayer)
|
implementation(libs.androidx.media3.exoplayer)
|
||||||
|
implementation(libs.androidx.media3.exoplayer.midi)
|
||||||
implementation(libs.androidx.media3.ui)
|
implementation(libs.androidx.media3.ui)
|
||||||
implementation(libs.telephoto.zoomableimage)
|
implementation(libs.telephoto.zoomableimage)
|
||||||
implementation(libs.vanniktech.blurhash)
|
implementation(libs.vanniktech.blurhash)
|
||||||
|
|
|
||||||
|
|
@ -88,7 +88,7 @@ fun MediaAudioView(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
isDisplayed: Boolean = true,
|
isDisplayed: Boolean = true,
|
||||||
) {
|
) {
|
||||||
val exoPlayer = rememberExoPlayer()
|
val exoPlayer = rememberExoPlayer(forAudioOnly = true)
|
||||||
ExoPlayerMediaAudioView(
|
ExoPlayerMediaAudioView(
|
||||||
isDisplayed = isDisplayed,
|
isDisplayed = isDisplayed,
|
||||||
localMediaViewState = localMediaViewState,
|
localMediaViewState = localMediaViewState,
|
||||||
|
|
|
||||||
|
|
@ -8,14 +8,18 @@
|
||||||
|
|
||||||
package io.element.android.libraries.mediaviewer.impl.local.player
|
package io.element.android.libraries.mediaviewer.impl.local.player
|
||||||
|
|
||||||
|
import androidx.annotation.OptIn
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalInspectionMode
|
import androidx.compose.ui.platform.LocalInspectionMode
|
||||||
|
import androidx.media3.common.util.UnstableApi
|
||||||
|
import androidx.media3.exoplayer.DefaultRenderersFactory
|
||||||
import androidx.media3.exoplayer.ExoPlayer
|
import androidx.media3.exoplayer.ExoPlayer
|
||||||
|
|
||||||
|
@OptIn(UnstableApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun rememberExoPlayer(): ExoPlayer {
|
fun rememberExoPlayer(forAudioOnly: Boolean): ExoPlayer {
|
||||||
return if (LocalInspectionMode.current) {
|
return if (LocalInspectionMode.current) {
|
||||||
remember {
|
remember {
|
||||||
ExoPlayerForPreview()
|
ExoPlayerForPreview()
|
||||||
|
|
@ -23,7 +27,14 @@ fun rememberExoPlayer(): ExoPlayer {
|
||||||
} else {
|
} else {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
remember {
|
remember {
|
||||||
ExoPlayer.Builder(context).build()
|
if (forAudioOnly) {
|
||||||
|
// Required for media3-exoplayer-midi to decode MIDI samples produced by DefaultExtractorsFactory.
|
||||||
|
val renderersFactory = DefaultRenderersFactory(context)
|
||||||
|
.setExtensionRendererMode(DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON)
|
||||||
|
ExoPlayer.Builder(context, renderersFactory).build()
|
||||||
|
} else {
|
||||||
|
ExoPlayer.Builder(context).build()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,7 @@ fun MediaVideoView(
|
||||||
audioFocus: AudioFocus?,
|
audioFocus: AudioFocus?,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val exoPlayer = rememberExoPlayer()
|
val exoPlayer = rememberExoPlayer(forAudioOnly = false)
|
||||||
ExoPlayerMediaVideoView(
|
ExoPlayerMediaVideoView(
|
||||||
isDisplayed = isDisplayed,
|
isDisplayed = isDisplayed,
|
||||||
localMediaViewState = localMediaViewState,
|
localMediaViewState = localMediaViewState,
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ import io.element.android.libraries.architecture.AsyncData
|
||||||
import io.element.android.libraries.core.extensions.mapCatchingExceptions
|
import io.element.android.libraries.core.extensions.mapCatchingExceptions
|
||||||
import io.element.android.libraries.matrix.api.media.MatrixMediaLoader
|
import io.element.android.libraries.matrix.api.media.MatrixMediaLoader
|
||||||
import io.element.android.libraries.matrix.api.media.MediaFile
|
import io.element.android.libraries.matrix.api.media.MediaFile
|
||||||
|
import io.element.android.libraries.matrix.api.media.MediaSource
|
||||||
import io.element.android.libraries.matrix.api.timeline.Timeline
|
import io.element.android.libraries.matrix.api.timeline.Timeline
|
||||||
import io.element.android.libraries.mediaviewer.api.MediaViewerEntryPoint.MediaViewerMode
|
import io.element.android.libraries.mediaviewer.api.MediaViewerEntryPoint.MediaViewerMode
|
||||||
import io.element.android.libraries.mediaviewer.api.local.LocalMedia
|
import io.element.android.libraries.mediaviewer.api.local.LocalMedia
|
||||||
|
|
@ -40,6 +41,7 @@ import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
class MediaViewerDataSource(
|
class MediaViewerDataSource(
|
||||||
mode: MediaViewerMode,
|
mode: MediaViewerMode,
|
||||||
|
|
@ -51,7 +53,7 @@ class MediaViewerDataSource(
|
||||||
private val pagerKeysHandler: PagerKeysHandler,
|
private val pagerKeysHandler: PagerKeysHandler,
|
||||||
) {
|
) {
|
||||||
// List of media files that are currently being loaded
|
// List of media files that are currently being loaded
|
||||||
private val mediaFiles: MutableList<MediaFile> = mutableListOf()
|
private val mediaFiles: ConcurrentHashMap<MediaSource, MediaFile> = ConcurrentHashMap()
|
||||||
|
|
||||||
private val galleryMode = when (mode) {
|
private val galleryMode = when (mode) {
|
||||||
MediaViewerMode.SingleMedia,
|
MediaViewerMode.SingleMedia,
|
||||||
|
|
@ -69,7 +71,7 @@ class MediaViewerDataSource(
|
||||||
|
|
||||||
fun dispose() {
|
fun dispose() {
|
||||||
Timber.d("Disposing MediaViewerDataSource, closing ${mediaFiles.size} media files")
|
Timber.d("Disposing MediaViewerDataSource, closing ${mediaFiles.size} media files")
|
||||||
mediaFiles.forEach { it.close() }
|
mediaFiles.values.forEach { it.close() }
|
||||||
mediaFiles.clear()
|
mediaFiles.clear()
|
||||||
localMediaStates.clear()
|
localMediaStates.clear()
|
||||||
}
|
}
|
||||||
|
|
@ -163,6 +165,12 @@ class MediaViewerDataSource(
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun loadMedia(data: MediaViewerPageData.MediaViewerData) {
|
suspend fun loadMedia(data: MediaViewerPageData.MediaViewerData) {
|
||||||
|
val currentState = localMediaStates[data.mediaSource.safeUrl]?.value
|
||||||
|
// If the media is already loading or has been loaded successfully, do nothing
|
||||||
|
if (currentState?.isLoading() == true || currentState?.isSuccess() == true) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
Timber.d("loadMedia for ${data.eventId}")
|
Timber.d("loadMedia for ${data.eventId}")
|
||||||
val localMediaState = localMediaStates.getOrPut(data.mediaSource.safeUrl) {
|
val localMediaState = localMediaStates.getOrPut(data.mediaSource.safeUrl) {
|
||||||
mutableStateOf(AsyncData.Uninitialized)
|
mutableStateOf(AsyncData.Uninitialized)
|
||||||
|
|
@ -175,7 +183,7 @@ class MediaViewerDataSource(
|
||||||
filename = data.mediaInfo.filename
|
filename = data.mediaInfo.filename
|
||||||
)
|
)
|
||||||
.onSuccess { mediaFile ->
|
.onSuccess { mediaFile ->
|
||||||
mediaFiles.add(mediaFile)
|
mediaFiles[data.mediaSource] = mediaFile
|
||||||
}
|
}
|
||||||
.mapCatchingExceptions { mediaFile ->
|
.mapCatchingExceptions { mediaFile ->
|
||||||
localMediaFactory.createFromMediaFile(
|
localMediaFactory.createFromMediaFile(
|
||||||
|
|
@ -190,4 +198,12 @@ class MediaViewerDataSource(
|
||||||
localMediaState.value = AsyncData.Failure(it)
|
localMediaState.value = AsyncData.Failure(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun cancelLoadingMedia(data: MediaViewerPageData.MediaViewerData) {
|
||||||
|
if (localMediaStates[data.mediaSource.safeUrl]?.value?.isLoading() == true) {
|
||||||
|
Timber.d("cancelLoadingMedia for ${data.eventId}")
|
||||||
|
mediaFiles.remove(data.mediaSource)?.close()
|
||||||
|
localMediaStates[data.mediaSource.safeUrl]?.value = AsyncData.Uninitialized
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,4 +29,5 @@ sealed interface MediaViewerEvent {
|
||||||
data class Delete(val eventId: EventId) : MediaViewerEvent
|
data class Delete(val eventId: EventId) : MediaViewerEvent
|
||||||
data class OnNavigateTo(val index: Int) : MediaViewerEvent
|
data class OnNavigateTo(val index: Int) : MediaViewerEvent
|
||||||
data class LoadMore(val direction: Timeline.PaginationDirection) : MediaViewerEvent
|
data class LoadMore(val direction: Timeline.PaginationDirection) : MediaViewerEvent
|
||||||
|
data class CancelLoadingMedia(val data: MediaViewerPageData.MediaViewerData) : MediaViewerEvent
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -100,6 +100,9 @@ class MediaViewerPresenter(
|
||||||
is MediaViewerEvent.LoadMedia -> {
|
is MediaViewerEvent.LoadMedia -> {
|
||||||
coroutineScope.downloadMedia(data = event.data)
|
coroutineScope.downloadMedia(data = event.data)
|
||||||
}
|
}
|
||||||
|
is MediaViewerEvent.CancelLoadingMedia -> {
|
||||||
|
dataSource.cancelLoadingMedia(event.data)
|
||||||
|
}
|
||||||
is MediaViewerEvent.ClearLoadingError -> {
|
is MediaViewerEvent.ClearLoadingError -> {
|
||||||
dataSource.clearLoadingError(event.data)
|
dataSource.clearLoadingError(event.data)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,7 @@ import androidx.compose.ui.graphics.Brush
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.layout.onSizeChanged
|
import androidx.compose.ui.layout.onSizeChanged
|
||||||
|
import androidx.compose.ui.layout.onVisibilityChanged
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.platform.LocalInspectionMode
|
import androidx.compose.ui.platform.LocalInspectionMode
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
|
@ -208,11 +209,16 @@ fun MediaViewerView(
|
||||||
}
|
}
|
||||||
is MediaViewerPageData.MediaViewerData -> {
|
is MediaViewerPageData.MediaViewerData -> {
|
||||||
var bottomPaddingInPixels by remember { mutableIntStateOf(defaultBottomPaddingInPixels) }
|
var bottomPaddingInPixels by remember { mutableIntStateOf(defaultBottomPaddingInPixels) }
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
state.eventSink(MediaViewerEvent.LoadMedia(dataForPage))
|
|
||||||
}
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier.fillMaxSize()
|
modifier = Modifier
|
||||||
|
.onVisibilityChanged(minDurationMs = 200L) { isVisible ->
|
||||||
|
if (isVisible) {
|
||||||
|
state.eventSink(MediaViewerEvent.LoadMedia(dataForPage))
|
||||||
|
} else {
|
||||||
|
state.eventSink(MediaViewerEvent.CancelLoadingMedia(dataForPage))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.fillMaxSize()
|
||||||
) {
|
) {
|
||||||
val isDisplayed = remember(pagerState.settledPage) {
|
val isDisplayed = remember(pagerState.settledPage) {
|
||||||
// This 'item provider' lambda will be called when the data source changes with an outdated `settlePage` value
|
// This 'item provider' lambda will be called when the data source changes with an outdated `settlePage` value
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
<string name="screen_media_details_filename">"Název souboru"</string>
|
<string name="screen_media_details_filename">"Název souboru"</string>
|
||||||
<string name="screen_media_details_no_more_files_to_show">"Žádné další soubory k zobrazení"</string>
|
<string name="screen_media_details_no_more_files_to_show">"Žádné další soubory k zobrazení"</string>
|
||||||
<string name="screen_media_details_no_more_media_to_show">"Žádná další média k zobrazení"</string>
|
<string name="screen_media_details_no_more_media_to_show">"Žádná další média k zobrazení"</string>
|
||||||
|
<string name="screen_media_details_title">"Informace o souboru"</string>
|
||||||
<string name="screen_media_details_uploaded_by">"Nahrál(a)"</string>
|
<string name="screen_media_details_uploaded_by">"Nahrál(a)"</string>
|
||||||
<string name="screen_media_details_uploaded_on">"Nahráno"</string>
|
<string name="screen_media_details_uploaded_on">"Nahráno"</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue