Merge branch 'release/26.05.2'
This commit is contained in:
commit
20d0d115d4
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/",
|
||||
],
|
||||
},
|
||||
{
|
||||
// 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
|
||||
"matchPackageNames": [
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ appId: ${MAESTRO_APP_ID}
|
|||
- tapOn:
|
||||
text: ${MAESTRO_INVITEE1_MXID}
|
||||
index: 1
|
||||
- tapOn: "Send invite"
|
||||
- tapOn: "Continue"
|
||||
- takeScreenshot: build/maestro/330-createAndDeleteDM
|
||||
- tapOn: "maestroelement2"
|
||||
- scroll
|
||||
|
|
|
|||
|
|
@ -24,8 +24,16 @@ appId: ${MAESTRO_APP_ID}
|
|||
text: ${MAESTRO_INVITEE2_MXID}
|
||||
index: 1
|
||||
- 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: "aRoomName"
|
||||
- scrollUntilVisible:
|
||||
direction: DOWN
|
||||
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
|
||||
=============================
|
||||
|
||||
|
|
|
|||
|
|
@ -382,9 +382,9 @@ class LoggedInFlowNode(
|
|||
}
|
||||
is NavTarget.Room -> {
|
||||
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 {
|
||||
attachRoom(roomIdOrAlias = roomId.toRoomIdOrAlias(), serverNames = serverNames, clearBackstack = false)
|
||||
attachRoom(roomIdOrAlias = roomId.toRoomIdOrAlias(), serverNames = serverNames, clearBackstack = clearBackStack)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ class JoinedRoomLoadedFlowNode(
|
|||
plugins = plugins,
|
||||
), DependencyInjectionGraphOwner {
|
||||
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 navigateToGlobalNotificationSettings()
|
||||
fun navigateToDeveloperSettings()
|
||||
|
|
@ -150,7 +150,7 @@ class JoinedRoomLoadedFlowNode(
|
|||
callback.navigateToDeveloperSettings()
|
||||
}
|
||||
|
||||
override fun navigateToRoom(roomId: RoomId, serverNames: List<String>) {
|
||||
override fun navigateToRoom(roomId: RoomId, serverNames: List<String>, clearBackStack: Boolean) {
|
||||
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
|
||||
|
||||
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 navigateToGlobalNotificationSettings() = 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_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="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>
|
||||
|
|
|
|||
|
|
@ -3,14 +3,34 @@
|
|||
<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_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_title">"Privat"</string>
|
||||
<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>
|
||||
<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_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_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_title">"Adresă"</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_placeholder">"Adăugați o descriere…"</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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_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_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_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_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>
|
||||
|
|
|
|||
|
|
@ -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_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_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_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>
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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_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_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_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_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>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
<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_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_use_another_device">"Kasuta teist seadet"</string>
|
||||
<string name="screen_identity_confirmation_use_recovery_key">"Kasuta taastevõtit"</string>
|
||||
|
|
|
|||
|
|
@ -83,8 +83,8 @@ class RoomListDataSource(
|
|||
|
||||
val loadingState = roomList.loadingState
|
||||
|
||||
fun launchIn(coroutineScope: CoroutineScope) {
|
||||
roomList
|
||||
fun launchIn(coroutineScope: CoroutineScope): Job {
|
||||
return roomList
|
||||
.summaries
|
||||
.onEach { roomSummaries ->
|
||||
replaceWith(roomSummaries)
|
||||
|
|
@ -212,6 +212,7 @@ class RoomListDataSource(
|
|||
private suspend fun rebuildAllRoomSummaries() {
|
||||
lock.withLock {
|
||||
roomList.summaries.replayCache.firstOrNull()?.let { roomSummaries ->
|
||||
diffCacheUpdater.updateWith(roomSummaries)
|
||||
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_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_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_submit">"Seadista andmete taastamine"</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 taastevõti"</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_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_unread">"Marcați ca necitită"</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_title">"Verificați că sunteți dumneavoastră"</string>
|
||||
</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.dateformatter.test.FakeDateFormatter
|
||||
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.room.aRoomSummary
|
||||
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(
|
||||
roomListService: FakeRoomListService = FakeRoomListService(),
|
||||
roomListRoomSummaryFactory: RoomListRoomSummaryFactory = aRoomListRoomSummaryFactory(),
|
||||
notificationSettingsService: FakeNotificationSettingsService = FakeNotificationSettingsService(),
|
||||
dateTimeObserver: FakeDateTimeObserver = FakeDateTimeObserver(),
|
||||
analyticsService: FakeAnalyticsService = FakeAnalyticsService(),
|
||||
) = RoomListDataSource(
|
||||
roomListService = roomListService,
|
||||
roomListRoomSummaryFactory = roomListRoomSummaryFactory,
|
||||
|
|
@ -112,6 +273,6 @@ class RoomListDataSourceTest {
|
|||
notificationSettingsService = notificationSettingsService,
|
||||
sessionCoroutineScope = backgroundScope,
|
||||
dateTimeObserver = dateTimeObserver,
|
||||
analyticsService = FakeAnalyticsService(),
|
||||
analyticsService = analyticsService,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,4 +11,5 @@ package io.element.android.features.invitepeople.api
|
|||
interface InvitePeopleEvents {
|
||||
data object SendInvites : InvitePeopleEvents
|
||||
data object CloseSearch : InvitePeopleEvents
|
||||
data object ClearError : InvitePeopleEvents
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,10 +9,12 @@
|
|||
package io.element.android.features.invitepeople.api
|
||||
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
|
||||
interface InvitePeopleState {
|
||||
val canInvite: Boolean
|
||||
val isSearchActive: Boolean
|
||||
val sendInvitesAction: AsyncAction<Unit>
|
||||
val createRoomFromDmAction: AsyncAction<RoomId>
|
||||
val eventSink: (InvitePeopleEvents) -> Unit
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ package io.element.android.features.invitepeople.api
|
|||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
|
||||
class InvitePeopleStateProvider : PreviewParameterProvider<InvitePeopleState> {
|
||||
override val values: Sequence<InvitePeopleState>
|
||||
|
|
@ -25,6 +26,7 @@ private data class PreviewInvitePeopleState(
|
|||
override val canInvite: Boolean,
|
||||
override val isSearchActive: Boolean,
|
||||
override val sendInvitesAction: AsyncAction<Unit>,
|
||||
override val createRoomFromDmAction: AsyncAction<RoomId>,
|
||||
override val eventSink: (InvitePeopleEvents) -> Unit,
|
||||
) : InvitePeopleState
|
||||
|
||||
|
|
@ -32,10 +34,12 @@ private fun aPreviewInvitePeopleState(
|
|||
canInvite: Boolean = false,
|
||||
isSearchActive: Boolean = false,
|
||||
sendInvitesAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
||||
createRoomFromDmAction: AsyncAction<RoomId> = AsyncAction.Uninitialized,
|
||||
eventSink: (InvitePeopleEvents) -> Unit = {},
|
||||
) = PreviewInvitePeopleState(
|
||||
canInvite = canInvite,
|
||||
isSearchActive = isSearchActive,
|
||||
sendInvitesAction = sendInvitesAction,
|
||||
createRoomFromDmAction = createRoomFromDmAction,
|
||||
eventSink = eventSink
|
||||
)
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ dependencies {
|
|||
implementation(projects.libraries.uiUtils)
|
||||
implementation(projects.libraries.androidutils)
|
||||
implementation(projects.libraries.usersearch.api)
|
||||
implementation(projects.libraries.testtags)
|
||||
implementation(libs.coil.compose)
|
||||
implementation(projects.services.apperror.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.matrix.api.MatrixClient
|
||||
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.room.JoinedRoom
|
||||
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.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.roomdirectory.RoomVisibility
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import io.element.android.libraries.usersearch.api.UserRepository
|
||||
|
|
@ -88,6 +93,7 @@ class DefaultInvitePeoplePresenter(
|
|||
var searchActive by rememberSaveable { mutableStateOf(false) }
|
||||
val showSearchLoader = rememberSaveable { mutableStateOf(false) }
|
||||
val sendInvitesAction = remember { mutableStateOf<AsyncAction<Unit>>(AsyncAction.Uninitialized) }
|
||||
val createRoomFromDmAction = remember { mutableStateOf<AsyncAction<RoomId>>(AsyncAction.Uninitialized) }
|
||||
|
||||
val recentDirectRooms by produceState(emptyList(), roomMembers.value) {
|
||||
if (roomMembers.value.isSuccess()) {
|
||||
|
|
@ -208,7 +214,13 @@ class DefaultInvitePeoplePresenter(
|
|||
)
|
||||
} else {
|
||||
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
|
||||
queryState.clearText()
|
||||
}
|
||||
is InvitePeopleEvents.ClearError -> {
|
||||
sendInvitesAction.value = AsyncAction.Uninitialized
|
||||
createRoomFromDmAction.value = AsyncAction.Uninitialized
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -228,6 +244,7 @@ class DefaultInvitePeoplePresenter(
|
|||
searchResults = searchResults.value,
|
||||
showSearchLoader = showSearchLoader.value,
|
||||
sendInvitesAction = sendInvitesAction.value,
|
||||
createRoomFromDmAction = createRoomFromDmAction.value,
|
||||
suggestions = suggestions,
|
||||
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")
|
||||
private fun MutableState<ImmutableList<MatrixUser>>.toggleUser(user: MatrixUser) {
|
||||
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.AsyncData
|
||||
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 kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
|
|
@ -26,6 +27,7 @@ data class DefaultInvitePeopleState(
|
|||
val selectedUsers: ImmutableList<MatrixUser>,
|
||||
override val isSearchActive: Boolean,
|
||||
override val sendInvitesAction: AsyncAction<Unit>,
|
||||
override val createRoomFromDmAction: AsyncAction<RoomId>,
|
||||
val suggestions: ImmutableList<InvitableUser>,
|
||||
override val eventSink: (InvitePeopleEvents) -> Unit
|
||||
) : 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_JUSTIN
|
||||
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.ui.components.aMatrixUser
|
||||
import io.element.android.libraries.matrix.ui.components.aMatrixUserList
|
||||
|
|
@ -119,6 +120,7 @@ private fun aDefaultInvitePeopleState(
|
|||
isSearchActive: Boolean = false,
|
||||
showSearchLoader: Boolean = false,
|
||||
sendInvitesAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
||||
createRoomFromDmAction: AsyncAction<RoomId> = AsyncAction.Uninitialized,
|
||||
suggestions: List<InvitableUser> = aMatrixUserList()
|
||||
.take(5)
|
||||
.map { user -> anInvitableUser(matrixUser = user, isSelected = user in selectedUsers) },
|
||||
|
|
@ -132,6 +134,7 @@ private fun aDefaultInvitePeopleState(
|
|||
isSearchActive = isSearchActive,
|
||||
showSearchLoader = showSearchLoader,
|
||||
sendInvitesAction = sendInvitesAction,
|
||||
createRoomFromDmAction = createRoomFromDmAction,
|
||||
suggestions = suggestions.toImmutableList(),
|
||||
eventSink = {},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import androidx.compose.foundation.layout.Column
|
|||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
|
|
@ -23,6 +24,7 @@ import androidx.compose.material3.ExperimentalMaterial3Api
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
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.model.getAvatarData
|
||||
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.utils.strings.simplePluralStringResource
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
|
@ -102,7 +106,7 @@ private fun InvitePeopleContentView(
|
|||
}
|
||||
|
||||
InvitePeopleSearchBar(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
modifier = Modifier.imePadding().fillMaxWidth(),
|
||||
queryState = state.searchQuery,
|
||||
showLoader = state.showSearchLoader,
|
||||
selectedUsers = state.selectedUsers,
|
||||
|
|
@ -298,7 +302,7 @@ private fun InvitePeopleConfirmModal(
|
|||
text = stringResource(CommonStrings.action_remove),
|
||||
onClick = onRemove,
|
||||
leadingIcon = IconSource.Vector(CompoundIcons.Close()),
|
||||
modifier = Modifier.weight(1f)
|
||||
modifier = Modifier.weight(1f).testTag(TestTags.confirmInviteUnknown),
|
||||
)
|
||||
Button(
|
||||
text = stringResource(CommonStrings.action_invite),
|
||||
|
|
|
|||
|
|
@ -2,4 +2,8 @@
|
|||
<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_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>
|
||||
|
|
|
|||
|
|
@ -2,4 +2,8 @@
|
|||
<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_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>
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
users: List<MatrixUser>,
|
||||
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_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_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_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>
|
||||
|
|
@ -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_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_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_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.
|
||||
|
|
|
|||
|
|
@ -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.meta.BuildMeta
|
||||
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.room.CreateTimelineParams
|
||||
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
||||
|
|
@ -66,7 +64,6 @@ class ShareLocationPresenter(
|
|||
private val messageComposerContext: MessageComposerContext,
|
||||
private val locationActions: LocationActions,
|
||||
private val buildMeta: BuildMeta,
|
||||
private val featureFlagService: FeatureFlagService,
|
||||
private val client: MatrixClient,
|
||||
private val durationFormatter: DurationFormatter,
|
||||
private val liveLocationShareManager: ActiveLiveLocationShareManager,
|
||||
|
|
@ -83,9 +80,6 @@ class ShareLocationPresenter(
|
|||
override fun present(): ShareLocationState {
|
||||
val permissionsState: PermissionsState = permissionsPresenter.present()
|
||||
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 } }
|
||||
var dialogState: ShareLocationState.Dialog by remember {
|
||||
mutableStateOf(ShareLocationState.Dialog.None)
|
||||
|
|
@ -171,7 +165,7 @@ class ShareLocationPresenter(
|
|||
dialogState = dialogState,
|
||||
trackUserLocation = trackUserPosition,
|
||||
hasLocationPermission = permissionsState.isAnyGranted,
|
||||
canShareLiveLocation = isLiveLocationSharingEnabled && timelineMode.canShareLiveLocation(),
|
||||
canShareLiveLocation = timelineMode.canShareLiveLocation(),
|
||||
appName = appName,
|
||||
startLiveLocationAction = startLiveLocationAction.value,
|
||||
eventSink = ::handleEvent,
|
||||
|
|
|
|||
|
|
@ -2,4 +2,5 @@
|
|||
<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_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>
|
||||
|
|
|
|||
|
|
@ -1,4 +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">"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_missing_permissions">"Sul pole õigust jagada selles jututoas oma asukohta reaalajas"</string>
|
||||
</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"?>
|
||||
<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_missing_permissions">"你无权在此房内共享实时位置。"</string>
|
||||
</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.messages.test.FakeMessageComposerContext
|
||||
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.test.FakeMatrixClient
|
||||
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
||||
|
|
@ -50,7 +49,6 @@ class DefaultShareLocationEntryPointTest {
|
|||
messageComposerContext = FakeMessageComposerContext(),
|
||||
locationActions = FakeLocationActions(),
|
||||
buildMeta = aBuildMeta(),
|
||||
featureFlagService = FakeFeatureFlagService(),
|
||||
client = FakeMatrixClient(),
|
||||
durationFormatter = FakeDurationFormatter(),
|
||||
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.messages.test.FakeMessageComposerContext
|
||||
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.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
|
|
@ -77,7 +75,6 @@ class ShareLocationPresenterTest {
|
|||
private val fakeMessageComposerContext = FakeMessageComposerContext()
|
||||
private val fakeLocationActions = FakeLocationActions()
|
||||
private val fakeBuildMeta = aBuildMeta(applicationName = "app name")
|
||||
private val fakeFeatureFlagService = FakeFeatureFlagService()
|
||||
private val fakeMatrixClient = FakeMatrixClient(sessionId = A_USER_ID)
|
||||
|
||||
private val durationFormatter = FakeDurationFormatter()
|
||||
|
|
@ -96,7 +93,6 @@ class ShareLocationPresenterTest {
|
|||
messageComposerContext = fakeMessageComposerContext,
|
||||
locationActions = locationActions,
|
||||
buildMeta = fakeBuildMeta,
|
||||
featureFlagService = fakeFeatureFlagService,
|
||||
client = fakeMatrixClient,
|
||||
durationFormatter = durationFormatter,
|
||||
liveLocationShareManager = liveLocationShareManager,
|
||||
|
|
@ -658,21 +654,7 @@ class ShareLocationPresenterTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `canShareLiveLocation is false when the feature is disabled`() = 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)
|
||||
fun `canShareLiveLocation is true in live timeline`() = runTest {
|
||||
val shareLocationPresenter = createShareLocationPresenter(
|
||||
timelineMode = Timeline.Mode.Live,
|
||||
)
|
||||
|
|
@ -685,7 +667,6 @@ class ShareLocationPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `canShareLiveLocation is false in thread timeline`() = runTest {
|
||||
fakeFeatureFlagService.setFeatureEnabled(FeatureFlags.LiveLocationSharing, true)
|
||||
val shareLocationPresenter = createShareLocationPresenter(
|
||||
timelineMode = Timeline.Mode.Thread(A_THREAD_ID),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ class DefaultLockScreenService(
|
|||
private val coroutineScope: CoroutineScope,
|
||||
private val sessionObserver: SessionObserver,
|
||||
private val appForegroundStateService: AppForegroundStateService,
|
||||
biometricAuthenticatorManager: BiometricAuthenticatorManager,
|
||||
private val biometricAuthenticatorManager: BiometricAuthenticatorManager,
|
||||
) : LockScreenService {
|
||||
private val _lockState = MutableStateFlow<LockScreenLockState>(LockScreenLockState.Unlocked)
|
||||
override val lockState: StateFlow<LockScreenLockState> = _lockState
|
||||
|
|
@ -81,6 +81,7 @@ class DefaultLockScreenService(
|
|||
override suspend fun onSessionDeleted(userId: String, wasLastSession: Boolean) {
|
||||
if (wasLastSession) {
|
||||
pinCodeManager.deletePinCode()
|
||||
biometricAuthenticatorManager.disable()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -24,6 +24,11 @@ interface BiometricAuthenticatorManager {
|
|||
fun addCallback(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.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -80,10 +80,7 @@ class DefaultBiometricAuthenticatorManager(
|
|||
|
||||
private val internalCallback = object : DefaultBiometricUnlockCallback() {
|
||||
override fun onBiometricSetupError() {
|
||||
coroutineScope.launch {
|
||||
lockScreenStore.setIsBiometricUnlockAllowed(false)
|
||||
secretKeyRepository.deleteKey(SECRET_KEY_ALIAS)
|
||||
}
|
||||
coroutineScope.launch { disable() }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -120,6 +117,11 @@ class DefaultBiometricAuthenticatorManager(
|
|||
)
|
||||
}
|
||||
|
||||
override suspend fun disable() {
|
||||
lockScreenStore.setIsBiometricUnlockAllowed(false)
|
||||
secretKeyRepository.deleteKey(SECRET_KEY_ALIAS)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun rememberBiometricAuthenticator(
|
||||
isAvailable: Boolean,
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ class LockScreenSettingsPresenter(
|
|||
if (showRemovePinConfirmation) {
|
||||
showRemovePinConfirmation = false
|
||||
pinCodeManager.deletePinCode()
|
||||
biometricAuthenticatorManager.disable()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,5 +34,5 @@ Vali midagi, mis hästi meelde jääb. Kui unustad selle PIN-koodi, siis turvaka
|
|||
</plurals>
|
||||
<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_signout_in_progress_dialog_content">"Logime välja…"</string>
|
||||
<string name="screen_signout_in_progress_dialog_content">"Eemaldan seadet…"</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ class FakeBiometricAuthenticatorManager(
|
|||
override var isDeviceSecured: Boolean = true,
|
||||
override var hasAvailableAuthenticator: Boolean = false,
|
||||
private val createBiometricAuthenticator: () -> BiometricAuthenticator = { FakeBiometricAuthenticator() },
|
||||
private val disableLambda: suspend () -> Unit = { },
|
||||
) : BiometricAuthenticatorManager {
|
||||
override fun addCallback(callback: BiometricAuthenticator.Callback) {
|
||||
// no-op
|
||||
|
|
@ -37,4 +38,8 @@ class FakeBiometricAuthenticatorManager(
|
|||
createBiometricAuthenticator()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun disable() {
|
||||
disableLambda()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,7 +60,6 @@ dependencies {
|
|||
implementation(projects.libraries.core)
|
||||
implementation(projects.libraries.androidutils)
|
||||
implementation(projects.libraries.architecture)
|
||||
implementation(projects.libraries.featureflag.api)
|
||||
implementation(projects.libraries.matrix.api)
|
||||
implementation(projects.libraries.matrix.api)
|
||||
implementation(projects.libraries.designsystem)
|
||||
|
|
@ -81,7 +80,6 @@ dependencies {
|
|||
testImplementation(projects.features.login.test)
|
||||
testImplementation(projects.features.enterprise.test)
|
||||
testImplementation(projects.features.preferences.test)
|
||||
testImplementation(projects.libraries.featureflag.test)
|
||||
testImplementation(projects.libraries.matrix.test)
|
||||
testImplementation(projects.libraries.oauth.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.uri.ensureProtocol
|
||||
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.HomeServerLoginCompatibilityChecker
|
||||
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
|
||||
|
|
@ -71,7 +69,6 @@ class DefaultElementClassicConnection(
|
|||
private val coroutineScope: CoroutineScope,
|
||||
private val matrixAuthenticationService: MatrixAuthenticationService,
|
||||
private val homeServerLoginCompatibilityChecker: HomeServerLoginCompatibilityChecker,
|
||||
private val featureFlagService: FeatureFlagService,
|
||||
) : ElementClassicConnection {
|
||||
// Messenger for communicating with the service.
|
||||
private var messenger: Messenger? = null
|
||||
|
|
@ -119,10 +116,6 @@ class DefaultElementClassicConnection(
|
|||
override fun start() {
|
||||
Timber.tag(loggerTag.value).d("start()")
|
||||
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
|
||||
// class name because there is no reason to be able to let other
|
||||
// applications replace our component.
|
||||
|
|
@ -158,11 +151,6 @@ class DefaultElementClassicConnection(
|
|||
override fun requestSession() {
|
||||
Timber.tag(loggerTag.value).d("requestSession()")
|
||||
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
|
||||
if (finalMessenger == null) {
|
||||
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_title">"Vyberte váš server"</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_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>
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@
|
|||
<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_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_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>
|
||||
|
|
@ -39,7 +39,13 @@
|
|||
<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_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_checking_account">"Kontrollin kasutajakontot"</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_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_title">"Selectați serverul dumneavoastra"</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_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>
|
||||
|
|
@ -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_title">"Bine ați revenit!"</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_checking_account">"Se verifică contul…"</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_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_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_subtitle">"Bun venit în %1$s. Supraalimentat, pentru viteză și simplitate."</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_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_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_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.
|
||||
|
|
|
|||
|
|
@ -15,9 +15,6 @@ import androidx.core.graphics.createBitmap
|
|||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
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.HomeServerLoginCompatibilityChecker
|
||||
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
|
||||
fun `when an error is received, an error is emitted`() = runTest {
|
||||
val connection = createDefaultElementClassicConnection(
|
||||
|
|
@ -514,17 +496,10 @@ class DefaultElementClassicConnectionTest {
|
|||
homeServerLoginCompatibilityChecker: HomeServerLoginCompatibilityChecker = FakeHomeServerLoginCompatibilityChecker(
|
||||
checkResult = { Result.success(true) }
|
||||
),
|
||||
isFeatureEnabled: Boolean = true,
|
||||
featureFlagService: FeatureFlagService = FakeFeatureFlagService(
|
||||
initialState = mapOf(
|
||||
FeatureFlags.SignInWithClassic.key to isFeatureEnabled,
|
||||
)
|
||||
),
|
||||
) = DefaultElementClassicConnection(
|
||||
serviceBinder = serviceBinder,
|
||||
coroutineScope = coroutineScope,
|
||||
matrixAuthenticationService = matrixAuthenticationService,
|
||||
homeServerLoginCompatibilityChecker = homeServerLoginCompatibilityChecker,
|
||||
featureFlagService = featureFlagService,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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_title">"Eemalda see seade"</string>
|
||||
<string name="screen_signout_in_progress_dialog_content">"Logime välja…"</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_title">"Sa oled varukoopiate tegemise välja lülitanud"</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_in_progress_dialog_content">"Eemaldan seadet…"</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 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 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_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_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_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>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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_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_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>
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@
|
|||
<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_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_text_formatting">"Tekstivorming"</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_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_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_gallery">"Zdjęcia i filmy"</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 kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Inject
|
||||
|
|
@ -56,17 +53,8 @@ class AdvancedSettingsPresenter(
|
|||
appPreferencesStore.getThemeFlow().mapToTheme(isBlackThemeAllowed)
|
||||
}.collectAsState(initial = Theme.System)
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
val liveLocationMinimumDistanceUpdate by produceState<Int?>(null) {
|
||||
featureFlagService.isFeatureEnabledFlow(FeatureFlags.LiveLocationSharing)
|
||||
.flatMapLatest { isEnabled ->
|
||||
if (isEnabled) {
|
||||
appPreferencesStore.getLiveLocationMinimumDistanceInMetersUpdateFlow()
|
||||
} else {
|
||||
emptyFlow()
|
||||
}
|
||||
}
|
||||
.collect { value = it }
|
||||
appPreferencesStore.getLiveLocationMinimumDistanceInMetersUpdateFlow().collect { value = it }
|
||||
}
|
||||
|
||||
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_turned_off">"Systémová oznámení byla vypnuta"</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_light">"Světlý"</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_timeline_media_toggle_title">"Peida meedia eelvaated ajajoonel"</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_title">"Andmete uuendused reaalajas asukoha jagamisel"</string>
|
||||
<plurals name="screen_advanced_settings_live_location_update_distance">
|
||||
<item quantity="one">"Iga %1$d meeter"</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_turned_off">"Süsteemi teavitused on välja lülitatud"</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_light">"Hele"</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_turned_off">"A rendszerértesítések ki vannak kapcsolva"</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_light">"Világos"</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_turned_off">"Powiadomienia systemowe wyłączone"</string>
|
||||
<string name="screen_notification_settings_title">"Powiadomienia"</string>
|
||||
<string name="theme_black">"Czarny"</string>
|
||||
<string name="theme_dark">"Ciemny"</string>
|
||||
<string name="theme_light">"Jasny"</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_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_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_title">"Optimizați calitatea media"</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_turned_off">"Notificările de sistem sunt dezactivate"</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_light">"Deschis"</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_mentions">"提及"</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_content_link">"系统设置"</string>
|
||||
<string name="screen_notification_settings_system_notifications_turned_off">"系统通知已关闭"</string>
|
||||
<string name="screen_notification_settings_title">"通知"</string>
|
||||
<string name="theme_black">"纯黑"</string>
|
||||
<string name="theme_dark">"深色"</string>
|
||||
<string name="theme_light">"浅色"</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
|
||||
fun `present - exposes live location minimum distance from app preferences`() = runTest {
|
||||
val appPreferencesStore = InMemoryAppPreferencesStore(
|
||||
liveLocationMinimumDistanceUpdate = 50,
|
||||
)
|
||||
val featureFlagService = FakeFeatureFlagService().apply {
|
||||
setFeatureEnabled(FeatureFlags.LiveLocationSharing, true)
|
||||
}
|
||||
val presenter = createAdvancedSettingsPresenter(appPreferencesStore = appPreferencesStore, featureFlagService = featureFlagService)
|
||||
val presenter = createAdvancedSettingsPresenter(appPreferencesStore = appPreferencesStore)
|
||||
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
|
|
@ -256,10 +233,7 @@ class AdvancedSettingsPresenterTest {
|
|||
val appPreferencesStore = InMemoryAppPreferencesStore(
|
||||
liveLocationMinimumDistanceUpdate = 10,
|
||||
)
|
||||
val featureFlagService = FakeFeatureFlagService().apply {
|
||||
setFeatureEnabled(FeatureFlags.LiveLocationSharing, true)
|
||||
}
|
||||
val presenter = createAdvancedSettingsPresenter(appPreferencesStore = appPreferencesStore, featureFlagService = featureFlagService)
|
||||
val presenter = createAdvancedSettingsPresenter(appPreferencesStore = appPreferencesStore)
|
||||
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
<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_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_title">"举报房间"</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
<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_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_rooms">"Spravovat místnosti"</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_everyone">"Liikmed"</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_rooms">"Halda jututuba"</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_everyone">"Membru"</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_rooms">"Gestionați camerele"</string>
|
||||
<string name="screen_room_change_permissions_member_moderation">"Gestionați membrii"</string>
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ interface RoomDetailsEntryPoint : FeatureEntryPoint {
|
|||
interface Callback : Plugin {
|
||||
fun navigateToGlobalNotificationSettings()
|
||||
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 startForwardEventFlow(eventId: EventId, fromPinnedEvents: Boolean)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -263,7 +263,20 @@ class RoomDetailsFlowNode(
|
|||
}
|
||||
|
||||
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 -> {
|
||||
|
|
|
|||
|
|
@ -180,6 +180,7 @@ fun aDmRoomDetailsState(
|
|||
roomName = roomName,
|
||||
isPublic = false,
|
||||
isEncrypted = isEncrypted,
|
||||
canInvite = true,
|
||||
roomType = RoomDetailsType.Dm(otherMember = aDmRoomMember(isIgnored = isDmMemberIgnored)),
|
||||
roomMemberDetailsState = aUserProfileState(
|
||||
isBlocked = AsyncData.Success(isDmMemberIgnored),
|
||||
|
|
|
|||
|
|
@ -208,8 +208,15 @@ fun RoomDetailsView(
|
|||
onClick = onSecurityAndPrivacyClick
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
state.roomMemberDetailsState?.let { dmMemberDetails ->
|
||||
state.roomMemberDetailsState?.let { dmMemberDetails ->
|
||||
if (state.canInvite) {
|
||||
PreferenceCategory {
|
||||
InviteItem(onClick = invitePeople)
|
||||
}
|
||||
}
|
||||
PreferenceCategory {
|
||||
ProfileItem(
|
||||
verificationState = dmMemberDetails.verificationState,
|
||||
onClick = { onProfileClick(dmMemberDetails.userId) }
|
||||
|
|
@ -374,14 +381,14 @@ private fun MainActionsSection(
|
|||
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.canInvite) {
|
||||
MainActionButton(
|
||||
title = stringResource(CommonStrings.action_invite),
|
||||
imageVector = CompoundIcons.UserAdd(),
|
||||
onClick = onInvitePeople,
|
||||
)
|
||||
}
|
||||
// Share CTA should be hidden for DMs
|
||||
MainActionButton(
|
||||
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
|
||||
private fun PinnedMessagesItem(
|
||||
pinnedMessagesCount: Int?,
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ package io.element.android.features.roomdetails.impl.invite
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.bumble.appyx.core.lifecycle.subscribe
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.node.Node
|
||||
|
|
@ -19,10 +20,16 @@ import dev.zacsweers.metro.Assisted
|
|||
import dev.zacsweers.metro.AssistedInject
|
||||
import im.vector.app.features.analytics.plan.MobileScreen
|
||||
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.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.matrix.api.core.RoomId
|
||||
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
|
||||
|
||||
@ContributesNode(RoomScope::class)
|
||||
|
|
@ -35,6 +42,10 @@ class RoomInviteMembersNode(
|
|||
room: JoinedRoom,
|
||||
invitePeoplePresenterFactory: InvitePeoplePresenter.Factory,
|
||||
) : Node(buildContext, plugins = plugins) {
|
||||
interface Callback : Plugin {
|
||||
fun openCreatedRoom(roomId: RoomId)
|
||||
}
|
||||
|
||||
init {
|
||||
lifecycle.subscribe(
|
||||
onResume = {
|
||||
|
|
@ -48,6 +59,8 @@ class RoomInviteMembersNode(
|
|||
roomId = room.roomId,
|
||||
)
|
||||
|
||||
private val callback = plugins.callback<Callback>()
|
||||
|
||||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
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(
|
||||
state = state,
|
||||
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_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_default_setting_footnote">"Możesz to zmienić w swoim %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">"Możesz to zmienić w %1$s."</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_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>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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_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>
|
||||
|
|
@ -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_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_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_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.
|
||||
|
|
@ -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_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_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_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_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_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_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_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_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_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_selecting_option_title">"Doar pentru membri, după selectarea acestei opțiuni"</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">"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.
|
||||
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>
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ class DefaultRoomDetailsEntryPointTest {
|
|||
val callback = object : RoomDetailsEntryPoint.Callback {
|
||||
override fun navigateToGlobalNotificationSettings() = 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 startForwardEventFlow(eventId: EventId, fromPinnedEvents: Boolean) = lambdaError()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ package io.element.android.features.roomdetails.impl
|
|||
import androidx.activity.ComponentActivity
|
||||
import androidx.compose.ui.test.AndroidComposeUiTest
|
||||
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.onNodeWithTag
|
||||
import androidx.compose.ui.test.performClick
|
||||
|
|
@ -339,6 +341,25 @@ class RoomDetailsViewTest {
|
|||
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(
|
||||
|
|
|
|||
|
|
@ -2,16 +2,17 @@
|
|||
<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_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_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_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_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_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_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>
|
||||
|
|
@ -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_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_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_title">"Kui sa ühtegi muud võimalust ei leia, siis lähtesta oma identiteet."</string>
|
||||
<string name="screen_key_backup_disable_confirmation_action_turn_off">"Lülita välja"</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_title">"Kas sa oled kindel, et soovid varukoopiate tegemise välja lülitada?"</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_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 digitaalne identiteet."</string>
|
||||
<string name="screen_key_backup_disable_confirmation_action_turn_off">"Kustuta"</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 oled kindel, et soovid võtmehoidla kustutada?"</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_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>
|
||||
|
|
@ -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_description">"Ära jaga seda kellegagi"</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_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_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>
|
||||
</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_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_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_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>
|
||||
|
|
|
|||
|
|
@ -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_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_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_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>
|
||||
|
|
|
|||
|
|
@ -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_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_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_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.
|
||||
|
|
@ -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_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_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_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_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_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_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_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_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_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_selecting_option_title">"Doar pentru membri, după selectarea acestei opțiuni"</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">"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.
|
||||
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>
|
||||
|
|
|
|||
|
|
@ -9,10 +9,21 @@
|
|||
</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_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_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_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_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_roles_and_permissions">"Roluri și permisiuni"</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">
|
||||
<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_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_use_another_device">"Kasuta teist seadet"</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_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_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_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>
|
||||
|
|
@ -42,7 +42,7 @@
|
|||
<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_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_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_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_signout_in_progress_dialog_content">"Logime välja…"</string>
|
||||
<string name="screen_signout_in_progress_dialog_content">"Eemaldan seadet…"</string>
|
||||
</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_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_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_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>
|
||||
|
|
@ -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_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_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_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"
|
||||
activity = "1.13.0"
|
||||
media3 = "1.10.0"
|
||||
camera = "1.6.0"
|
||||
camera = "1.6.1"
|
||||
work = "2.11.2"
|
||||
|
||||
# Compose
|
||||
compose_bom = "2026.04.01"
|
||||
compose_bom = "2026.05.00"
|
||||
|
||||
# Coroutines
|
||||
coroutines = "1.10.2"
|
||||
coroutines = "1.11.0"
|
||||
|
||||
# Accompanist
|
||||
accompanist = "0.37.3"
|
||||
|
|
@ -35,7 +35,7 @@ test_core = "1.7.0"
|
|||
roborazzi = "1.60.0"
|
||||
|
||||
# Jetbrain
|
||||
datetime = "0.7.1"
|
||||
datetime = "0.8.0"
|
||||
serialization_json = "1.11.0"
|
||||
|
||||
#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" }
|
||||
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
|
||||
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" }
|
||||
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" }
|
||||
|
|
@ -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_effect = { module = "androidx.media3:media3-effect", 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_activity_activity = { module = "androidx.activity:activity", version.ref = "activity" }
|
||||
androidx_activity_compose = { module = "androidx.activity:activity-compose", version.ref = "activity" }
|
||||
androidx_startup = "androidx.startup:startup-runtime:1.2.0"
|
||||
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_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
|
||||
# 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.
|
||||
matrix_sdk = "org.matrix.rustcomponents:sdk-android:26.05.13"
|
||||
matrix_sdk = "org.matrix.rustcomponents:sdk-android:26.05.18"
|
||||
|
||||
# Others
|
||||
coil = { module = "io.coil-kt.coil3:coil", version.ref = "coil" }
|
||||
|
|
@ -222,7 +223,7 @@ color_picker = "io.mhssn:colorpicker:1.0.0"
|
|||
|
||||
# Analytics
|
||||
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
|
||||
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_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_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_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>
|
||||
|
|
|
|||
|
|
@ -93,13 +93,6 @@ enum class FeatureFlags(
|
|||
defaultValue = { 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(
|
||||
key = "feature.allow_black_theme",
|
||||
title = "Black theme",
|
||||
|
|
@ -107,13 +100,6 @@ enum class FeatureFlags(
|
|||
defaultValue = { false },
|
||||
isFinished = false,
|
||||
),
|
||||
LiveLocationSharing(
|
||||
key = "feature.liveLocationSharing",
|
||||
title = "Live location sharing",
|
||||
description = "Allow sharing live location in rooms.",
|
||||
defaultValue = { false },
|
||||
isFinished = false,
|
||||
),
|
||||
ValidateNetworkWhenSchedulingNotificationFetching(
|
||||
key = "feature.validate_network_when_scheduling_notification_fetching",
|
||||
title = "Validate internet connectivity when scheduling notification fetching",
|
||||
|
|
|
|||
|
|
@ -434,13 +434,30 @@ class RustMatrixAuthenticationService(
|
|||
qrCodeData: QrCodeData,
|
||||
): Client {
|
||||
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
|
||||
.getBaseClientBuilder(
|
||||
sessionPaths = sessionPaths,
|
||||
passphrase = pendingPassphrase,
|
||||
slidingSyncType = ClientBuilderSlidingSync.Discovered,
|
||||
)
|
||||
.serverNameOrHomeserverUrl(qrCodeData.serverName()!!)
|
||||
.serverNameOrHomeserverUrl(baseUrlOrServerName)
|
||||
.build()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,25 +16,25 @@ import org.matrix.rustcomponents.sdk.RoomListEntriesUpdate
|
|||
internal fun RoomListEntriesUpdate.describe(): String {
|
||||
return when (this) {
|
||||
is RoomListEntriesUpdate.Set -> {
|
||||
"Set #$index to '${value.displayName()}'"
|
||||
"Set #$index to '${value.id()}'"
|
||||
}
|
||||
is RoomListEntriesUpdate.Append -> {
|
||||
"Append ${values.map { "'" + it.displayName() + "'" }}"
|
||||
"Append ${values.map { "'" + it.id() + "'" }}"
|
||||
}
|
||||
is RoomListEntriesUpdate.PushBack -> {
|
||||
"PushBack '${value.displayName()}'"
|
||||
"PushBack '${value.id()}'"
|
||||
}
|
||||
is RoomListEntriesUpdate.PushFront -> {
|
||||
"PushFront '${value.displayName()}'"
|
||||
"PushFront '${value.id()}'"
|
||||
}
|
||||
is RoomListEntriesUpdate.Insert -> {
|
||||
"Insert at #$index: '${value.displayName()}'"
|
||||
"Insert at #$index: '${value.id()}'"
|
||||
}
|
||||
is RoomListEntriesUpdate.Remove -> {
|
||||
"Remove #$index"
|
||||
}
|
||||
is RoomListEntriesUpdate.Reset -> {
|
||||
"Reset all to ${values.map { "'" + it.displayName() + "'" }}"
|
||||
"Reset all to ${values.map { "'" + it.id() + "'" }}"
|
||||
}
|
||||
RoomListEntriesUpdate.PopBack -> {
|
||||
"PopBack"
|
||||
|
|
|
|||
|
|
@ -112,6 +112,11 @@ class RoomSummaryListProcessor(
|
|||
private suspend fun updateRoomSummaries(updates: List<RoomListEntriesUpdate>, block: suspend MutableList<RoomSummary>.() -> Unit) = withContext(
|
||||
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 {
|
||||
val current = roomSummaries.replayCache.lastOrNull()
|
||||
val mutableRoomSummaries = current.orEmpty().toMutableList()
|
||||
|
|
@ -126,7 +131,7 @@ class RoomSummaryListProcessor(
|
|||
analyticsService.trackError(
|
||||
IllegalStateException(
|
||||
"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()
|
||||
}
|
||||
|
||||
override fun rooms(): List<SpaceRoom> {
|
||||
override suspend fun rooms(): List<SpaceRoom> {
|
||||
return roomsResult()
|
||||
}
|
||||
|
||||
|
|
@ -53,7 +53,7 @@ class FakeFfiSpaceRoomList(
|
|||
spaceRoomListPaginationStateListener?.onUpdate(state)
|
||||
}
|
||||
|
||||
override fun subscribeToRoomUpdate(listener: SpaceRoomListEntriesListener): TaskHandle {
|
||||
override suspend fun subscribeToRoomUpdate(listener: SpaceRoomListEntriesListener): TaskHandle {
|
||||
spaceRoomListEntriesListener = listener
|
||||
return FakeFfiTaskHandle()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -173,16 +173,68 @@ class RoomSummaryListProcessorTest {
|
|||
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(
|
||||
roomId = roomId,
|
||||
latestEventLambda = { LatestEventValue.None }
|
||||
)
|
||||
|
||||
private fun TestScope.createProcessor() = RoomSummaryListProcessor(
|
||||
private fun TestScope.createProcessor(
|
||||
analyticsService: FakeAnalyticsService = FakeAnalyticsService(),
|
||||
) = RoomSummaryListProcessor(
|
||||
summaries,
|
||||
FakeFfiRoomListService(),
|
||||
coroutineContext = StandardTestDispatcher(testScheduler),
|
||||
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_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_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_invites_invited_you">"%1$s (%2$s) saatis sulle kutse"</string>
|
||||
</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_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_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>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ dependencies {
|
|||
implementation(libs.coroutines.core)
|
||||
implementation(libs.coil.compose)
|
||||
implementation(libs.androidx.media3.exoplayer)
|
||||
implementation(libs.androidx.media3.exoplayer.midi)
|
||||
implementation(libs.androidx.media3.ui)
|
||||
implementation(libs.telephoto.zoomableimage)
|
||||
implementation(libs.vanniktech.blurhash)
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ fun MediaAudioView(
|
|||
modifier: Modifier = Modifier,
|
||||
isDisplayed: Boolean = true,
|
||||
) {
|
||||
val exoPlayer = rememberExoPlayer()
|
||||
val exoPlayer = rememberExoPlayer(forAudioOnly = true)
|
||||
ExoPlayerMediaAudioView(
|
||||
isDisplayed = isDisplayed,
|
||||
localMediaViewState = localMediaViewState,
|
||||
|
|
|
|||
|
|
@ -8,14 +8,18 @@
|
|||
|
||||
package io.element.android.libraries.mediaviewer.impl.local.player
|
||||
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalInspectionMode
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.exoplayer.DefaultRenderersFactory
|
||||
import androidx.media3.exoplayer.ExoPlayer
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
@Composable
|
||||
fun rememberExoPlayer(): ExoPlayer {
|
||||
fun rememberExoPlayer(forAudioOnly: Boolean): ExoPlayer {
|
||||
return if (LocalInspectionMode.current) {
|
||||
remember {
|
||||
ExoPlayerForPreview()
|
||||
|
|
@ -23,7 +27,14 @@ fun rememberExoPlayer(): ExoPlayer {
|
|||
} else {
|
||||
val context = LocalContext.current
|
||||
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?,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val exoPlayer = rememberExoPlayer()
|
||||
val exoPlayer = rememberExoPlayer(forAudioOnly = false)
|
||||
ExoPlayerMediaVideoView(
|
||||
isDisplayed = isDisplayed,
|
||||
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.matrix.api.media.MatrixMediaLoader
|
||||
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.mediaviewer.api.MediaViewerEntryPoint.MediaViewerMode
|
||||
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.withContext
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
class MediaViewerDataSource(
|
||||
mode: MediaViewerMode,
|
||||
|
|
@ -51,7 +53,7 @@ class MediaViewerDataSource(
|
|||
private val pagerKeysHandler: PagerKeysHandler,
|
||||
) {
|
||||
// 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) {
|
||||
MediaViewerMode.SingleMedia,
|
||||
|
|
@ -69,7 +71,7 @@ class MediaViewerDataSource(
|
|||
|
||||
fun dispose() {
|
||||
Timber.d("Disposing MediaViewerDataSource, closing ${mediaFiles.size} media files")
|
||||
mediaFiles.forEach { it.close() }
|
||||
mediaFiles.values.forEach { it.close() }
|
||||
mediaFiles.clear()
|
||||
localMediaStates.clear()
|
||||
}
|
||||
|
|
@ -163,6 +165,12 @@ class MediaViewerDataSource(
|
|||
}
|
||||
|
||||
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}")
|
||||
val localMediaState = localMediaStates.getOrPut(data.mediaSource.safeUrl) {
|
||||
mutableStateOf(AsyncData.Uninitialized)
|
||||
|
|
@ -175,7 +183,7 @@ class MediaViewerDataSource(
|
|||
filename = data.mediaInfo.filename
|
||||
)
|
||||
.onSuccess { mediaFile ->
|
||||
mediaFiles.add(mediaFile)
|
||||
mediaFiles[data.mediaSource] = mediaFile
|
||||
}
|
||||
.mapCatchingExceptions { mediaFile ->
|
||||
localMediaFactory.createFromMediaFile(
|
||||
|
|
@ -190,4 +198,12 @@ class MediaViewerDataSource(
|
|||
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 OnNavigateTo(val index: Int) : 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 -> {
|
||||
coroutineScope.downloadMedia(data = event.data)
|
||||
}
|
||||
is MediaViewerEvent.CancelLoadingMedia -> {
|
||||
dataSource.cancelLoadingMedia(event.data)
|
||||
}
|
||||
is MediaViewerEvent.ClearLoadingError -> {
|
||||
dataSource.clearLoadingError(event.data)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ import androidx.compose.ui.graphics.Brush
|
|||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.layout.onSizeChanged
|
||||
import androidx.compose.ui.layout.onVisibilityChanged
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.LocalInspectionMode
|
||||
import androidx.compose.ui.res.stringResource
|
||||
|
|
@ -208,11 +209,16 @@ fun MediaViewerView(
|
|||
}
|
||||
is MediaViewerPageData.MediaViewerData -> {
|
||||
var bottomPaddingInPixels by remember { mutableIntStateOf(defaultBottomPaddingInPixels) }
|
||||
LaunchedEffect(Unit) {
|
||||
state.eventSink(MediaViewerEvent.LoadMedia(dataForPage))
|
||||
}
|
||||
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) {
|
||||
// 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_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_title">"Informace o souboru"</string>
|
||||
<string name="screen_media_details_uploaded_by">"Nahrál(a)"</string>
|
||||
<string name="screen_media_details_uploaded_on">"Nahráno"</string>
|
||||
</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