Merge branch 'release/25.05.2'
This commit is contained in:
commit
00bc42feb7
94 changed files with 1497 additions and 1171 deletions
15
CHANGES.md
15
CHANGES.md
|
|
@ -1,3 +1,18 @@
|
||||||
|
Changes in Element X v25.05.1
|
||||||
|
=============================
|
||||||
|
|
||||||
|
## What's Changed
|
||||||
|
### 🐛 Bugfixes
|
||||||
|
* Fix broken Element Call in 25.05.0 by @bmarty in https://github.com/element-hq/element-x-android/pull/4694
|
||||||
|
### Dependency upgrades
|
||||||
|
* fix(deps): update android.gradle.plugin to v8.10.0 by @renovate in https://github.com/element-hq/element-x-android/pull/4687
|
||||||
|
* fix(deps): update dependency org.matrix.rustcomponents:sdk-android to v25.5.8 by @renovate in https://github.com/element-hq/element-x-android/pull/4696
|
||||||
|
### Others
|
||||||
|
* Let EnterpriseService prevent usage of homeserver by @bmarty in https://github.com/element-hq/element-x-android/pull/4682
|
||||||
|
|
||||||
|
|
||||||
|
**Full Changelog**: https://github.com/element-hq/element-x-android/compare/v25.05.0...v25.05.1
|
||||||
|
|
||||||
Changes in Element X v25.05.0
|
Changes in Element X v25.05.0
|
||||||
=============================
|
=============================
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ package io.element.android.appconfig
|
||||||
|
|
||||||
object LearnMoreConfig {
|
object LearnMoreConfig {
|
||||||
const val ENCRYPTION_URL: String = "https://element.io/help#encryption"
|
const val ENCRYPTION_URL: String = "https://element.io/help#encryption"
|
||||||
|
const val DEVICE_VERIFICATION_URL: String = "https://element.io/help#encryption-device-verification"
|
||||||
const val SECURE_BACKUP_URL: String = "https://element.io/help#encryption5"
|
const val SECURE_BACKUP_URL: String = "https://element.io/help#encryption5"
|
||||||
const val IDENTITY_CHANGE_URL: String = "https://element.io/help#encryption18"
|
const val IDENTITY_CHANGE_URL: String = "https://element.io/help#encryption18"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
2
fastlane/metadata/android/en-US/changelogs/202505020.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/202505020.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
Main changes in this version: TODO.
|
||||||
|
Full changelog: https://github.com/element-hq/element-x-android/releases
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import extension.setupAnvil
|
import extension.setupAnvil
|
||||||
import org.gradle.kotlin.dsl.test
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright 2023, 2024 New Vector Ltd.
|
* Copyright 2023, 2024 New Vector Ltd.
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ import io.element.android.anvilannotations.ContributesNode
|
||||||
import io.element.android.appconfig.LearnMoreConfig
|
import io.element.android.appconfig.LearnMoreConfig
|
||||||
import io.element.android.features.ftue.impl.sessionverification.choosemode.ChooseSelfVerificationModeNode
|
import io.element.android.features.ftue.impl.sessionverification.choosemode.ChooseSelfVerificationModeNode
|
||||||
import io.element.android.features.securebackup.api.SecureBackupEntryPoint
|
import io.element.android.features.securebackup.api.SecureBackupEntryPoint
|
||||||
import io.element.android.features.verifysession.api.VerifySessionEntryPoint
|
import io.element.android.features.verifysession.api.OutgoingVerificationEntryPoint
|
||||||
import io.element.android.libraries.architecture.BackstackView
|
import io.element.android.libraries.architecture.BackstackView
|
||||||
import io.element.android.libraries.architecture.BaseFlowNode
|
import io.element.android.libraries.architecture.BaseFlowNode
|
||||||
import io.element.android.libraries.architecture.createNode
|
import io.element.android.libraries.architecture.createNode
|
||||||
|
|
@ -40,7 +40,7 @@ import kotlinx.parcelize.Parcelize
|
||||||
class FtueSessionVerificationFlowNode @AssistedInject constructor(
|
class FtueSessionVerificationFlowNode @AssistedInject constructor(
|
||||||
@Assisted buildContext: BuildContext,
|
@Assisted buildContext: BuildContext,
|
||||||
@Assisted plugins: List<Plugin>,
|
@Assisted plugins: List<Plugin>,
|
||||||
private val verifySessionEntryPoint: VerifySessionEntryPoint,
|
private val outgoingVerificationEntryPoint: OutgoingVerificationEntryPoint,
|
||||||
private val secureBackupEntryPoint: SecureBackupEntryPoint,
|
private val secureBackupEntryPoint: SecureBackupEntryPoint,
|
||||||
) : BaseFlowNode<FtueSessionVerificationFlowNode.NavTarget>(
|
) : BaseFlowNode<FtueSessionVerificationFlowNode.NavTarget>(
|
||||||
backstack = BackStack(
|
backstack = BackStack(
|
||||||
|
|
@ -94,19 +94,19 @@ class FtueSessionVerificationFlowNode @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onLearnMoreAboutEncryption() {
|
override fun onLearnMoreAboutEncryption() {
|
||||||
learnMoreUrl.value = LearnMoreConfig.ENCRYPTION_URL
|
learnMoreUrl.value = LearnMoreConfig.DEVICE_VERIFICATION_URL
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
createNode<ChooseSelfVerificationModeNode>(buildContext, plugins = listOf(callback))
|
createNode<ChooseSelfVerificationModeNode>(buildContext, plugins = listOf(callback))
|
||||||
}
|
}
|
||||||
is NavTarget.UseAnotherDevice -> {
|
is NavTarget.UseAnotherDevice -> {
|
||||||
verifySessionEntryPoint.nodeBuilder(this, buildContext)
|
outgoingVerificationEntryPoint.nodeBuilder(this, buildContext)
|
||||||
.params(VerifySessionEntryPoint.Params(
|
.params(OutgoingVerificationEntryPoint.Params(
|
||||||
showDeviceVerifiedScreen = true,
|
showDeviceVerifiedScreen = true,
|
||||||
verificationRequest = VerificationRequest.Outgoing.CurrentSession,
|
verificationRequest = VerificationRequest.Outgoing.CurrentSession,
|
||||||
))
|
))
|
||||||
.callback(object : VerifySessionEntryPoint.Callback {
|
.callback(object : OutgoingVerificationEntryPoint.Callback {
|
||||||
override fun onDone() {
|
override fun onDone() {
|
||||||
plugins<Callback>().forEach { it.onDone() }
|
plugins<Callback>().forEach { it.onDone() }
|
||||||
}
|
}
|
||||||
|
|
@ -116,7 +116,8 @@ class FtueSessionVerificationFlowNode @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onLearnMoreAboutEncryption() {
|
override fun onLearnMoreAboutEncryption() {
|
||||||
learnMoreUrl.value = LearnMoreConfig.ENCRYPTION_URL
|
// Note that this callback is never called. The "Learn more" link is not displayed
|
||||||
|
// for the self session interactive verification.
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.build()
|
.build()
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ data class LoginPasswordState(
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class LoginFormState(
|
data class LoginFormState(
|
||||||
val login: String,
|
val login: String,
|
||||||
val password: String
|
val password: String,
|
||||||
) : Parcelable {
|
) : Parcelable {
|
||||||
companion object {
|
companion object {
|
||||||
val Default = LoginFormState("", "")
|
val Default = LoginFormState("", "")
|
||||||
|
|
|
||||||
|
|
@ -8,23 +8,38 @@
|
||||||
package io.element.android.features.login.impl.screens.loginpassword
|
package io.element.android.features.login.impl.screens.loginpassword
|
||||||
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
|
import io.element.android.features.login.impl.accountprovider.AccountProvider
|
||||||
import io.element.android.features.login.impl.accountprovider.anAccountProvider
|
import io.element.android.features.login.impl.accountprovider.anAccountProvider
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
|
import io.element.android.libraries.matrix.api.core.SessionId
|
||||||
|
|
||||||
open class LoginPasswordStateProvider : PreviewParameterProvider<LoginPasswordState> {
|
open class LoginPasswordStateProvider : PreviewParameterProvider<LoginPasswordState> {
|
||||||
override val values: Sequence<LoginPasswordState>
|
override val values: Sequence<LoginPasswordState>
|
||||||
get() = sequenceOf(
|
get() = sequenceOf(
|
||||||
aLoginPasswordState(),
|
aLoginPasswordState(),
|
||||||
// Loading
|
// Loading
|
||||||
aLoginPasswordState().copy(loginAction = AsyncData.Loading()),
|
aLoginPasswordState(loginAction = AsyncData.Loading()),
|
||||||
// Error
|
// Error
|
||||||
aLoginPasswordState().copy(loginAction = AsyncData.Failure(Exception("An error occurred"))),
|
aLoginPasswordState(loginAction = AsyncData.Failure(Exception("An error occurred"))),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun aLoginPasswordState() = LoginPasswordState(
|
fun aLoginPasswordState(
|
||||||
accountProvider = anAccountProvider(),
|
accountProvider: AccountProvider = anAccountProvider(),
|
||||||
formState = LoginFormState.Default,
|
formState: LoginFormState = LoginFormState.Default,
|
||||||
loginAction = AsyncData.Uninitialized,
|
loginAction: AsyncData<SessionId> = AsyncData.Uninitialized,
|
||||||
eventSink = {}
|
eventSink: (LoginPasswordEvents) -> Unit = {},
|
||||||
|
) = LoginPasswordState(
|
||||||
|
accountProvider = accountProvider,
|
||||||
|
formState = formState,
|
||||||
|
loginAction = loginAction,
|
||||||
|
eventSink = eventSink,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun aLoginFormState(
|
||||||
|
login: String = "",
|
||||||
|
password: String = "",
|
||||||
|
) = LoginFormState(
|
||||||
|
login = login,
|
||||||
|
password = password,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -201,6 +201,7 @@ private fun LoginForm(
|
||||||
{
|
{
|
||||||
Box(Modifier.clickable {
|
Box(Modifier.clickable {
|
||||||
loginFieldState = ""
|
loginFieldState = ""
|
||||||
|
eventSink(LoginPasswordEvents.SetLogin(""))
|
||||||
}) {
|
}) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = CompoundIcons.Close(),
|
imageVector = CompoundIcons.Close(),
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
<string name="screen_change_account_provider_subtitle">"Použijte jiného poskytovatele účtu, například vlastní soukromý server nebo pracovní účet."</string>
|
<string name="screen_change_account_provider_subtitle">"Použijte jiného poskytovatele účtu, například vlastní soukromý server nebo pracovní účet."</string>
|
||||||
<string name="screen_change_account_provider_title">"Změnit poskytovatele účtu"</string>
|
<string name="screen_change_account_provider_title">"Změnit poskytovatele účtu"</string>
|
||||||
<string name="screen_change_server_error_invalid_homeserver">"Nepodařilo se nám připojit k tomuto domovskému serveru. Zkontrolujte prosím, zda jste správně zadali adresu URL domovského serveru. Pokud je adresa URL správná, obraťte se na správce domovského serveru, který vám poskytne další pomoc."</string>
|
<string name="screen_change_server_error_invalid_homeserver">"Nepodařilo se nám připojit k tomuto domovskému serveru. Zkontrolujte prosím, zda jste správně zadali adresu URL domovského serveru. Pokud je adresa URL správná, obraťte se na správce domovského serveru, který vám poskytne další pomoc."</string>
|
||||||
<string name="screen_change_server_error_invalid_well_known">"Klouzavá synchronizace není k dispozici kvůli problému se souborem well-known:
|
<string name="screen_change_server_error_invalid_well_known">"Server není k dispozici kvůli problému se souborem well-known:
|
||||||
%1$s"</string>
|
%1$s"</string>
|
||||||
<string name="screen_change_server_error_no_sliding_sync_message">"Vybraný poskytovatel účtu nepodporuje klouzavou synchronizaci. Pro použití %1$s je nutná aktualizace serveru."</string>
|
<string name="screen_change_server_error_no_sliding_sync_message">"Vybraný poskytovatel účtu nepodporuje klouzavou synchronizaci. Pro použití %1$s je nutná aktualizace serveru."</string>
|
||||||
<string name="screen_change_server_form_header">"Adresa URL domovského serveru"</string>
|
<string name="screen_change_server_form_header">"Adresa URL domovského serveru"</string>
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
<string name="screen_change_server_error_invalid_well_known">"Der Server ist aufgrund eines Problems im \"well-known file\" nicht verfügbar:
|
<string name="screen_change_server_error_invalid_well_known">"Der Server ist aufgrund eines Problems im \"well-known file\" nicht verfügbar:
|
||||||
%1$s"</string>
|
%1$s"</string>
|
||||||
<string name="screen_change_server_error_no_sliding_sync_message">"Der gewählte Kontoanbieter unterstützt Sliding Sync nicht. Für die Verwendung von %1$s ist ein Upgrade des Servers erforderlich."</string>
|
<string name="screen_change_server_error_no_sliding_sync_message">"Der gewählte Kontoanbieter unterstützt Sliding Sync nicht. Für die Verwendung von %1$s ist ein Upgrade des Servers erforderlich."</string>
|
||||||
|
<string name="screen_change_server_error_unauthorized_homeserver">"%1$sdarf keine Verbindung herstellen zu%2$s."</string>
|
||||||
<string name="screen_change_server_form_header">"Homeserver-URL"</string>
|
<string name="screen_change_server_form_header">"Homeserver-URL"</string>
|
||||||
<string name="screen_change_server_form_notice">"Geben Sie eine Domainadresse ein."</string>
|
<string name="screen_change_server_form_notice">"Geben Sie eine Domainadresse ein."</string>
|
||||||
<string name="screen_change_server_subtitle">"Wie lautet die Adresse deines Servers?"</string>
|
<string name="screen_change_server_subtitle">"Wie lautet die Adresse deines Servers?"</string>
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
<string name="screen_change_account_provider_subtitle">"Χρησιμοποίησε διαφορετικό πάροχο λογαριασμού, όπως τον δικό σου ιδιωτικό διακομιστή ή έναν εργασιακό λογαριασμό."</string>
|
<string name="screen_change_account_provider_subtitle">"Χρησιμοποίησε διαφορετικό πάροχο λογαριασμού, όπως τον δικό σου ιδιωτικό διακομιστή ή έναν εργασιακό λογαριασμό."</string>
|
||||||
<string name="screen_change_account_provider_title">"Αλλαγή παρόχου λογαριασμού"</string>
|
<string name="screen_change_account_provider_title">"Αλλαγή παρόχου λογαριασμού"</string>
|
||||||
<string name="screen_change_server_error_invalid_homeserver">"Δεν μπορούσαμε να επικοινωνήσουμε με αυτόν τον οικιακό διακομιστή. Βεβαιώσου ότι έχεις εισαγάγει σωστά τη διεύθυνση URL του αρχικού διακομιστή. Εάν η διεύθυνση URL είναι σωστή, επικοινώνησε με τον διαχειριστή του κεντρικού διακομιστή για περαιτέρω βοήθεια."</string>
|
<string name="screen_change_server_error_invalid_homeserver">"Δεν μπορούσαμε να επικοινωνήσουμε με αυτόν τον οικιακό διακομιστή. Βεβαιώσου ότι έχεις εισαγάγει σωστά τη διεύθυνση URL του αρχικού διακομιστή. Εάν η διεύθυνση URL είναι σωστή, επικοινώνησε με τον διαχειριστή του κεντρικού διακομιστή για περαιτέρω βοήθεια."</string>
|
||||||
<string name="screen_change_server_error_invalid_well_known">"Το Sliding sync δεν είναι διαθέσιμο εξαιτίας ενός ζητήματος σε ένα πολύ γνωστό αρχείο:
|
<string name="screen_change_server_error_invalid_well_known">"Ο διακομιστής δεν είναι διαθέσιμος λόγω προβλήματος στο αρχείο .well-known:
|
||||||
%1$s"</string>
|
%1$s"</string>
|
||||||
<string name="screen_change_server_form_header">"URL οικιακού διακομιστή"</string>
|
<string name="screen_change_server_form_header">"URL οικιακού διακομιστή"</string>
|
||||||
<string name="screen_change_server_subtitle">"Ποια είναι η διεύθυνση του διακομιστή σου;"</string>
|
<string name="screen_change_server_subtitle">"Ποια είναι η διεύθυνση του διακομιστή σου;"</string>
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
<string name="screen_change_server_error_invalid_well_known">"Server pole saadaval vea tõttu well-known failis:
|
<string name="screen_change_server_error_invalid_well_known">"Server pole saadaval vea tõttu well-known failis:
|
||||||
%1$s"</string>
|
%1$s"</string>
|
||||||
<string name="screen_change_server_error_no_sliding_sync_message">"Valitud teenusepakkuja ei toeta „sliding sync“ režiimi. Rakenduse %1$s kasutamiseks on vaja serverit uuendada."</string>
|
<string name="screen_change_server_error_no_sliding_sync_message">"Valitud teenusepakkuja ei toeta „sliding sync“ režiimi. Rakenduse %1$s kasutamiseks on vaja serverit uuendada."</string>
|
||||||
|
<string name="screen_change_server_error_unauthorized_homeserver">"%1$s ei saa kasutada %2$s koduserverit."</string>
|
||||||
<string name="screen_change_server_form_header">"Koduserveri url"</string>
|
<string name="screen_change_server_form_header">"Koduserveri url"</string>
|
||||||
<string name="screen_change_server_form_notice">"Sisesta domeeni aadress."</string>
|
<string name="screen_change_server_form_notice">"Sisesta domeeni aadress."</string>
|
||||||
<string name="screen_change_server_subtitle">"Mis on sinu koduserveri aadress?"</string>
|
<string name="screen_change_server_subtitle">"Mis on sinu koduserveri aadress?"</string>
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
<string name="screen_change_account_provider_subtitle">"Utilisez un autre fournisseur de compte, tel que votre propre serveur privé ou un serveur professionnel."</string>
|
<string name="screen_change_account_provider_subtitle">"Utilisez un autre fournisseur de compte, tel que votre propre serveur privé ou un serveur professionnel."</string>
|
||||||
<string name="screen_change_account_provider_title">"Changer de fournisseur de compte"</string>
|
<string name="screen_change_account_provider_title">"Changer de fournisseur de compte"</string>
|
||||||
<string name="screen_change_server_error_invalid_homeserver">"Nous n’avons pas pu atteindre ce serveur d’accueil. Vérifiez que vous avez correctement saisi l’URL du serveur d’accueil. Si l’URL est correcte, contactez l’administrateur de votre serveur d’accueil pour obtenir de l’aide."</string>
|
<string name="screen_change_server_error_invalid_homeserver">"Nous n’avons pas pu atteindre ce serveur d’accueil. Vérifiez que vous avez correctement saisi l’URL du serveur d’accueil. Si l’URL est correcte, contactez l’administrateur de votre serveur d’accueil pour obtenir de l’aide."</string>
|
||||||
<string name="screen_change_server_error_invalid_well_known">"Sliding sync n’est pas disponible en raison d’un problème dans le well-known file :
|
<string name="screen_change_server_error_invalid_well_known">"Ce fournisseur de compte n’est pas disponible en raison d’un problème dans le fichier .well-known:
|
||||||
%1$s"</string>
|
%1$s"</string>
|
||||||
<string name="screen_change_server_error_no_sliding_sync_message">"Le fournisseur de compte sélectionné ne prend pas en charge le sliding sync. Une mise à jour du serveur est nécessaire pour pouvoir utiliser %1$s."</string>
|
<string name="screen_change_server_error_no_sliding_sync_message">"Le fournisseur de compte sélectionné ne prend pas en charge le sliding sync. Une mise à jour du serveur est nécessaire pour pouvoir utiliser %1$s."</string>
|
||||||
<string name="screen_change_server_form_header">"URL du serveur d’accueil"</string>
|
<string name="screen_change_server_form_header">"URL du serveur d’accueil"</string>
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
<string name="screen_change_account_provider_subtitle">"Másik fiókszolgáltató, például a saját privát kiszolgáló vagy egy munkahelyi fiók használata."</string>
|
<string name="screen_change_account_provider_subtitle">"Másik fiókszolgáltató, például a saját privát kiszolgáló vagy egy munkahelyi fiók használata."</string>
|
||||||
<string name="screen_change_account_provider_title">"Fiókszolgáltató módosítása"</string>
|
<string name="screen_change_account_provider_title">"Fiókszolgáltató módosítása"</string>
|
||||||
<string name="screen_change_server_error_invalid_homeserver">"Nem sikerült elérni ezt a Matrix-kiszolgálót. Ellenőrizze, hogy helyesen adta-e meg a Matrix-kiszolgáló webcímét. Ha a webcím helyes, akkor további segítségért lépjen kapcsolatba a Matrix-kiszolgáló adminisztrátorával."</string>
|
<string name="screen_change_server_error_invalid_homeserver">"Nem sikerült elérni ezt a Matrix-kiszolgálót. Ellenőrizze, hogy helyesen adta-e meg a Matrix-kiszolgáló webcímét. Ha a webcím helyes, akkor további segítségért lépjen kapcsolatba a Matrix-kiszolgáló adminisztrátorával."</string>
|
||||||
<string name="screen_change_server_error_invalid_well_known">"A Sliding sync protokoll a well-known fájl problémája miatt nem érhető el:
|
<string name="screen_change_server_error_invalid_well_known">"A kiszolgáló a well-known fájl problémája miatt nem érhető el:
|
||||||
%1$s"</string>
|
%1$s"</string>
|
||||||
<string name="screen_change_server_error_no_sliding_sync_message">"A kiválasztott fiókszolgáltató nem támogatja a csúszóablakos szinkronizálást. Az %1$s használatához kiszolgálófrissítés szükséges."</string>
|
<string name="screen_change_server_error_no_sliding_sync_message">"A kiválasztott fiókszolgáltató nem támogatja a csúszóablakos szinkronizálást. Az %1$s használatához kiszolgálófrissítés szükséges."</string>
|
||||||
<string name="screen_change_server_form_header">"Matrix-kiszolgáló webcíme"</string>
|
<string name="screen_change_server_form_header">"Matrix-kiszolgáló webcíme"</string>
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,10 @@
|
||||||
<string name="screen_change_account_provider_subtitle">"Użyj innego dostawcy konta, takiego jak własny serwer lub konta służbowego."</string>
|
<string name="screen_change_account_provider_subtitle">"Użyj innego dostawcy konta, takiego jak własny serwer lub konta służbowego."</string>
|
||||||
<string name="screen_change_account_provider_title">"Zmień dostawcę konta"</string>
|
<string name="screen_change_account_provider_title">"Zmień dostawcę konta"</string>
|
||||||
<string name="screen_change_server_error_invalid_homeserver">"Nie mogliśmy połączyć się z tym serwerem domowym. Sprawdź, czy adres URL serwera został wprowadzony poprawnie. Jeśli adres URL jest poprawny, skontaktuj się z administratorem serwera w celu uzyskania dalszej pomocy."</string>
|
<string name="screen_change_server_error_invalid_homeserver">"Nie mogliśmy połączyć się z tym serwerem domowym. Sprawdź, czy adres URL serwera został wprowadzony poprawnie. Jeśli adres URL jest poprawny, skontaktuj się z administratorem serwera w celu uzyskania dalszej pomocy."</string>
|
||||||
<string name="screen_change_server_error_invalid_well_known">"Sliding sync nie jest dostępny z powodu problemu w znanym pliku:
|
<string name="screen_change_server_error_invalid_well_known">"Serwer nie jest dostępny z powodu problemu pliku .well-known:
|
||||||
%1$s"</string>
|
%1$s"</string>
|
||||||
<string name="screen_change_server_error_no_sliding_sync_message">"Wybrany dostawca konta nie wspiera synchronizacji przesuwnej. Wymagana jest aktualizacja serwera %1$s."</string>
|
<string name="screen_change_server_error_no_sliding_sync_message">"Wybrany dostawca konta nie wspiera synchronizacji przesuwnej. Wymagana jest aktualizacja serwera %1$s."</string>
|
||||||
|
<string name="screen_change_server_error_unauthorized_homeserver">"%1$s nie posiada zezwolenia na dołączenie do %2$s."</string>
|
||||||
<string name="screen_change_server_form_header">"URL serwera domowego"</string>
|
<string name="screen_change_server_form_header">"URL serwera domowego"</string>
|
||||||
<string name="screen_change_server_form_notice">"Wprowadź adres domeny."</string>
|
<string name="screen_change_server_form_notice">"Wprowadź adres domeny."</string>
|
||||||
<string name="screen_change_server_subtitle">"Jaki jest adres Twojego serwera?"</string>
|
<string name="screen_change_server_subtitle">"Jaki jest adres Twojego serwera?"</string>
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
<string name="screen_change_server_error_invalid_well_known">"Sliding sync недоступен из-за проблемы в известном файле:
|
<string name="screen_change_server_error_invalid_well_known">"Sliding sync недоступен из-за проблемы в известном файле:
|
||||||
%1$s"</string>
|
%1$s"</string>
|
||||||
<string name="screen_change_server_form_header">"URL-адрес домашнего сервера"</string>
|
<string name="screen_change_server_form_header">"URL-адрес домашнего сервера"</string>
|
||||||
|
<string name="screen_change_server_form_notice">"Введите адрес домена."</string>
|
||||||
<string name="screen_change_server_subtitle">"Какой адрес у вашего сервера?"</string>
|
<string name="screen_change_server_subtitle">"Какой адрес у вашего сервера?"</string>
|
||||||
<string name="screen_change_server_title">"Выберите свой сервер"</string>
|
<string name="screen_change_server_title">"Выберите свой сервер"</string>
|
||||||
<string name="screen_create_account_title">"Создать учетную запись"</string>
|
<string name="screen_create_account_title">"Создать учетную запись"</string>
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
<string name="screen_change_server_error_invalid_well_known">"Server nie je k dispozícii kvôli problému v známom súbore:
|
<string name="screen_change_server_error_invalid_well_known">"Server nie je k dispozícii kvôli problému v známom súbore:
|
||||||
%1$s"</string>
|
%1$s"</string>
|
||||||
<string name="screen_change_server_error_no_sliding_sync_message">"Vybraný poskytovateľ účtu nepodporuje kĺzavú synchronizáciu. Na používanie aplikácie %1$s je potrebná aktualizácia servera,"</string>
|
<string name="screen_change_server_error_no_sliding_sync_message">"Vybraný poskytovateľ účtu nepodporuje kĺzavú synchronizáciu. Na používanie aplikácie %1$s je potrebná aktualizácia servera,"</string>
|
||||||
|
<string name="screen_change_server_error_unauthorized_homeserver">"%1$s nemá dovolené pripojiť sa k %2$s."</string>
|
||||||
<string name="screen_change_server_form_header">"Adresa URL domovského servera"</string>
|
<string name="screen_change_server_form_header">"Adresa URL domovského servera"</string>
|
||||||
<string name="screen_change_server_form_notice">"Zadajte adresu domény."</string>
|
<string name="screen_change_server_form_notice">"Zadajte adresu domény."</string>
|
||||||
<string name="screen_change_server_subtitle">"Aká je adresa vášho servera?"</string>
|
<string name="screen_change_server_subtitle">"Aká je adresa vášho servera?"</string>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,189 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.login.impl.screens.loginpassword
|
||||||
|
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.compose.ui.test.assertIsEnabled
|
||||||
|
import androidx.compose.ui.test.assertIsNotEnabled
|
||||||
|
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
||||||
|
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||||
|
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||||
|
import androidx.compose.ui.test.onNodeWithText
|
||||||
|
import androidx.compose.ui.test.performClick
|
||||||
|
import androidx.compose.ui.test.performTextInput
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import io.element.android.libraries.matrix.test.A_PASSWORD
|
||||||
|
import io.element.android.libraries.matrix.test.A_USER_NAME
|
||||||
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
import io.element.android.tests.testutils.EnsureNeverCalled
|
||||||
|
import io.element.android.tests.testutils.EventsRecorder
|
||||||
|
import io.element.android.tests.testutils.clickOn
|
||||||
|
import io.element.android.tests.testutils.ensureCalledOnce
|
||||||
|
import io.element.android.tests.testutils.pressBack
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.rules.TestRule
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.robolectric.annotation.Config
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
class LoginPasswordViewTest {
|
||||||
|
@get:Rule
|
||||||
|
val rule = createAndroidComposeRule<ComponentActivity>()
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `clicking on back invoke back callback`() {
|
||||||
|
val eventsRecorder = EventsRecorder<LoginPasswordEvents>(expectEvents = false)
|
||||||
|
ensureCalledOnce { callback ->
|
||||||
|
rule.setLoginPasswordView(
|
||||||
|
aLoginPasswordState(
|
||||||
|
eventSink = eventsRecorder
|
||||||
|
),
|
||||||
|
onBackClick = callback,
|
||||||
|
)
|
||||||
|
rule.pressBack()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `changing login invokes the expected event`() {
|
||||||
|
val eventsRecorder = EventsRecorder<LoginPasswordEvents>()
|
||||||
|
rule.setLoginPasswordView(
|
||||||
|
aLoginPasswordState(
|
||||||
|
eventSink = eventsRecorder,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
val userNameHint = rule.activity.getString(CommonStrings.common_username)
|
||||||
|
rule.onNodeWithText(userNameHint).performTextInput(A_USER_NAME)
|
||||||
|
eventsRecorder.assertSingle(
|
||||||
|
LoginPasswordEvents.SetLogin(A_USER_NAME)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `changing login removes new lines the expected event`() {
|
||||||
|
val eventsRecorder = EventsRecorder<LoginPasswordEvents>()
|
||||||
|
rule.setLoginPasswordView(
|
||||||
|
aLoginPasswordState(
|
||||||
|
eventSink = eventsRecorder,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
val userNameHint = rule.activity.getString(CommonStrings.common_username)
|
||||||
|
rule.onNodeWithText(userNameHint).performTextInput("a\nb")
|
||||||
|
eventsRecorder.assertSingle(
|
||||||
|
LoginPasswordEvents.SetLogin("ab")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `clearing login invokes the expected event`() {
|
||||||
|
val eventsRecorder = EventsRecorder<LoginPasswordEvents>()
|
||||||
|
rule.setLoginPasswordView(
|
||||||
|
aLoginPasswordState(
|
||||||
|
formState = aLoginFormState(A_USER_NAME),
|
||||||
|
eventSink = eventsRecorder,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
val a11yClear = rule.activity.getString(CommonStrings.action_clear)
|
||||||
|
rule.onNodeWithContentDescription(a11yClear).performClick()
|
||||||
|
eventsRecorder.assertSingle(
|
||||||
|
LoginPasswordEvents.SetLogin("")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `changing password invokes the expected event`() {
|
||||||
|
val eventsRecorder = EventsRecorder<LoginPasswordEvents>()
|
||||||
|
rule.setLoginPasswordView(
|
||||||
|
aLoginPasswordState(
|
||||||
|
eventSink = eventsRecorder,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
val userNameHint = rule.activity.getString(CommonStrings.common_password)
|
||||||
|
rule.onNodeWithText(userNameHint).performTextInput(A_PASSWORD)
|
||||||
|
eventsRecorder.assertSingle(
|
||||||
|
LoginPasswordEvents.SetPassword(A_PASSWORD)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `reveal password makes the password visible`() {
|
||||||
|
val eventsRecorder = EventsRecorder<LoginPasswordEvents>(expectEvents = false)
|
||||||
|
rule.setLoginPasswordView(
|
||||||
|
aLoginPasswordState(
|
||||||
|
formState = aLoginFormState(password = A_PASSWORD),
|
||||||
|
eventSink = eventsRecorder,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
rule.onNodeWithText(A_PASSWORD).assertDoesNotExist()
|
||||||
|
// Show password
|
||||||
|
val a11yShowPassword = rule.activity.getString(CommonStrings.a11y_show_password)
|
||||||
|
rule.onNodeWithContentDescription(a11yShowPassword).performClick()
|
||||||
|
rule.onNodeWithText(A_PASSWORD).assertExists()
|
||||||
|
// Hide password
|
||||||
|
val a11yHidePassword = rule.activity.getString(CommonStrings.a11y_hide_password)
|
||||||
|
rule.onNodeWithContentDescription(a11yHidePassword).performClick()
|
||||||
|
rule.onNodeWithText(A_PASSWORD).assertDoesNotExist()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when login is empty, continue button is not enabled`() {
|
||||||
|
val eventsRecorder = EventsRecorder<LoginPasswordEvents>(expectEvents = false)
|
||||||
|
rule.setLoginPasswordView(
|
||||||
|
aLoginPasswordState(
|
||||||
|
formState = aLoginFormState(password = A_PASSWORD),
|
||||||
|
eventSink = eventsRecorder,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
val continueStr = rule.activity.getString(CommonStrings.action_continue)
|
||||||
|
rule.onNodeWithText(continueStr).assertIsNotEnabled()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when password is empty, continue button is not enabled`() {
|
||||||
|
val eventsRecorder = EventsRecorder<LoginPasswordEvents>(expectEvents = false)
|
||||||
|
rule.setLoginPasswordView(
|
||||||
|
aLoginPasswordState(
|
||||||
|
formState = aLoginFormState(login = A_USER_NAME),
|
||||||
|
eventSink = eventsRecorder,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
val continueStr = rule.activity.getString(CommonStrings.action_continue)
|
||||||
|
rule.onNodeWithText(continueStr).assertIsNotEnabled()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Config(qualifiers = "h1024dp")
|
||||||
|
@Test
|
||||||
|
fun `clicking on Continue sends expected event`() {
|
||||||
|
val eventsRecorder = EventsRecorder<LoginPasswordEvents>()
|
||||||
|
rule.setLoginPasswordView(
|
||||||
|
aLoginPasswordState(
|
||||||
|
formState = aLoginFormState(login = A_USER_NAME, password = A_PASSWORD),
|
||||||
|
eventSink = eventsRecorder,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
val continueStr = rule.activity.getString(CommonStrings.action_continue)
|
||||||
|
rule.onNodeWithText(continueStr).assertIsEnabled()
|
||||||
|
rule.clickOn(CommonStrings.action_continue)
|
||||||
|
eventsRecorder.assertSingle(
|
||||||
|
LoginPasswordEvents.Submit
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setLoginPasswordView(
|
||||||
|
state: LoginPasswordState,
|
||||||
|
onBackClick: () -> Unit = EnsureNeverCalled(),
|
||||||
|
) {
|
||||||
|
setContent {
|
||||||
|
LoginPasswordView(
|
||||||
|
state = state,
|
||||||
|
onBackClick = onBackClick,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
<string name="screen_onboarding_sign_in_manually">"Zaloguj się ręcznie"</string>
|
<string name="screen_onboarding_sign_in_manually">"Zaloguj się ręcznie"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_to">"Zaloguj się do %1$s"</string>
|
||||||
<string name="screen_onboarding_sign_in_with_qr_code">"Zaloguj się za pomocą kodu QR"</string>
|
<string name="screen_onboarding_sign_in_with_qr_code">"Zaloguj się za pomocą kodu QR"</string>
|
||||||
<string name="screen_onboarding_sign_up">"Utwórz konto"</string>
|
<string name="screen_onboarding_sign_up">"Utwórz konto"</string>
|
||||||
<string name="screen_onboarding_welcome_message">"Witamy w %1$s. Szybszy i prostszy niż kiedykolwiek."</string>
|
<string name="screen_onboarding_welcome_message">"Witamy w %1$s. Szybszy i prostszy niż kiedykolwiek."</string>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
<string name="screen_onboarding_sign_in_manually">"Sign in manually"</string>
|
<string name="screen_onboarding_sign_in_manually">"Sign in manually"</string>
|
||||||
|
<string name="screen_onboarding_sign_in_to">"Sign in to %1$s"</string>
|
||||||
<string name="screen_onboarding_sign_in_with_qr_code">"Sign in with QR code"</string>
|
<string name="screen_onboarding_sign_in_with_qr_code">"Sign in with QR code"</string>
|
||||||
<string name="screen_onboarding_sign_up">"Create account"</string>
|
<string name="screen_onboarding_sign_up">"Create account"</string>
|
||||||
<string name="screen_onboarding_welcome_message">"Welcome to the fastest %1$s ever. Supercharged for speed and simplicity."</string>
|
<string name="screen_onboarding_welcome_message">"Welcome to the fastest %1$s ever. Supercharged for speed and simplicity."</string>
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,6 @@ import io.element.android.libraries.preferences.test.InMemoryAppPreferencesStore
|
||||||
import io.element.android.tests.testutils.WarmUpRule
|
import io.element.android.tests.testutils.WarmUpRule
|
||||||
import io.element.android.tests.testutils.test
|
import io.element.android.tests.testutils.test
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.flow.first
|
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ import io.element.android.features.roomdetails.impl.notificationsettings.RoomNot
|
||||||
import io.element.android.features.roomdetails.impl.rolesandpermissions.RolesAndPermissionsFlowNode
|
import io.element.android.features.roomdetails.impl.rolesandpermissions.RolesAndPermissionsFlowNode
|
||||||
import io.element.android.features.roomdetails.impl.securityandprivacy.SecurityAndPrivacyFlowNode
|
import io.element.android.features.roomdetails.impl.securityandprivacy.SecurityAndPrivacyFlowNode
|
||||||
import io.element.android.features.userprofile.shared.UserProfileNodeHelper
|
import io.element.android.features.userprofile.shared.UserProfileNodeHelper
|
||||||
import io.element.android.features.verifysession.api.VerifySessionEntryPoint
|
import io.element.android.features.verifysession.api.OutgoingVerificationEntryPoint
|
||||||
import io.element.android.libraries.architecture.BackstackWithOverlayBox
|
import io.element.android.libraries.architecture.BackstackWithOverlayBox
|
||||||
import io.element.android.libraries.architecture.BaseFlowNode
|
import io.element.android.libraries.architecture.BaseFlowNode
|
||||||
import io.element.android.libraries.architecture.createNode
|
import io.element.android.libraries.architecture.createNode
|
||||||
|
|
@ -71,7 +71,7 @@ class RoomDetailsFlowNode @AssistedInject constructor(
|
||||||
private val knockRequestsListEntryPoint: KnockRequestsListEntryPoint,
|
private val knockRequestsListEntryPoint: KnockRequestsListEntryPoint,
|
||||||
private val mediaViewerEntryPoint: MediaViewerEntryPoint,
|
private val mediaViewerEntryPoint: MediaViewerEntryPoint,
|
||||||
private val mediaGalleryEntryPoint: MediaGalleryEntryPoint,
|
private val mediaGalleryEntryPoint: MediaGalleryEntryPoint,
|
||||||
private val verifySessionEntryPoint: VerifySessionEntryPoint,
|
private val outgoingVerificationEntryPoint: OutgoingVerificationEntryPoint,
|
||||||
private val reportRoomEntryPoint: ReportRoomEntryPoint,
|
private val reportRoomEntryPoint: ReportRoomEntryPoint,
|
||||||
) : BaseFlowNode<RoomDetailsFlowNode.NavTarget>(
|
) : BaseFlowNode<RoomDetailsFlowNode.NavTarget>(
|
||||||
backstack = BackStack(
|
backstack = BackStack(
|
||||||
|
|
@ -328,13 +328,13 @@ class RoomDetailsFlowNode @AssistedInject constructor(
|
||||||
createNode<SecurityAndPrivacyFlowNode>(buildContext)
|
createNode<SecurityAndPrivacyFlowNode>(buildContext)
|
||||||
}
|
}
|
||||||
is NavTarget.VerifyUser -> {
|
is NavTarget.VerifyUser -> {
|
||||||
val params = VerifySessionEntryPoint.Params(
|
val params = OutgoingVerificationEntryPoint.Params(
|
||||||
showDeviceVerifiedScreen = true,
|
showDeviceVerifiedScreen = true,
|
||||||
verificationRequest = VerificationRequest.Outgoing.User(userId = navTarget.userId,)
|
verificationRequest = VerificationRequest.Outgoing.User(userId = navTarget.userId,)
|
||||||
)
|
)
|
||||||
verifySessionEntryPoint.nodeBuilder(this, buildContext)
|
outgoingVerificationEntryPoint.nodeBuilder(this, buildContext)
|
||||||
.params(params)
|
.params(params)
|
||||||
.callback(object : VerifySessionEntryPoint.Callback {
|
.callback(object : OutgoingVerificationEntryPoint.Callback {
|
||||||
override fun onDone() {
|
override fun onDone() {
|
||||||
backstack.pop()
|
backstack.pop()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ import io.element.android.features.call.api.ElementCallEntryPoint
|
||||||
import io.element.android.features.userprofile.api.UserProfileEntryPoint
|
import io.element.android.features.userprofile.api.UserProfileEntryPoint
|
||||||
import io.element.android.features.userprofile.impl.root.UserProfileNode
|
import io.element.android.features.userprofile.impl.root.UserProfileNode
|
||||||
import io.element.android.features.userprofile.shared.UserProfileNodeHelper
|
import io.element.android.features.userprofile.shared.UserProfileNodeHelper
|
||||||
import io.element.android.features.verifysession.api.VerifySessionEntryPoint
|
import io.element.android.features.verifysession.api.OutgoingVerificationEntryPoint
|
||||||
import io.element.android.libraries.architecture.BackstackView
|
import io.element.android.libraries.architecture.BackstackView
|
||||||
import io.element.android.libraries.architecture.BaseFlowNode
|
import io.element.android.libraries.architecture.BaseFlowNode
|
||||||
import io.element.android.libraries.architecture.createNode
|
import io.element.android.libraries.architecture.createNode
|
||||||
|
|
@ -46,7 +46,7 @@ class UserProfileFlowNode @AssistedInject constructor(
|
||||||
private val elementCallEntryPoint: ElementCallEntryPoint,
|
private val elementCallEntryPoint: ElementCallEntryPoint,
|
||||||
private val sessionIdHolder: CurrentSessionIdHolder,
|
private val sessionIdHolder: CurrentSessionIdHolder,
|
||||||
private val mediaViewerEntryPoint: MediaViewerEntryPoint,
|
private val mediaViewerEntryPoint: MediaViewerEntryPoint,
|
||||||
private val verifySessionEntryPoint: VerifySessionEntryPoint,
|
private val outgoingVerificationEntryPoint: OutgoingVerificationEntryPoint,
|
||||||
) : BaseFlowNode<UserProfileFlowNode.NavTarget>(
|
) : BaseFlowNode<UserProfileFlowNode.NavTarget>(
|
||||||
backstack = BackStack(
|
backstack = BackStack(
|
||||||
initialElement = NavTarget.Root,
|
initialElement = NavTarget.Root,
|
||||||
|
|
@ -110,11 +110,11 @@ class UserProfileFlowNode @AssistedInject constructor(
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
is NavTarget.VerifyUser -> {
|
is NavTarget.VerifyUser -> {
|
||||||
val params = VerifySessionEntryPoint.Params(
|
val params = OutgoingVerificationEntryPoint.Params(
|
||||||
showDeviceVerifiedScreen = false,
|
showDeviceVerifiedScreen = false,
|
||||||
verificationRequest = VerificationRequest.Outgoing.User(userId = navTarget.userId)
|
verificationRequest = VerificationRequest.Outgoing.User(userId = navTarget.userId)
|
||||||
)
|
)
|
||||||
verifySessionEntryPoint.nodeBuilder(this, buildContext)
|
outgoingVerificationEntryPoint.nodeBuilder(this, buildContext)
|
||||||
.params(params)
|
.params(params)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ import io.element.android.libraries.architecture.FeatureEntryPoint
|
||||||
import io.element.android.libraries.architecture.NodeInputs
|
import io.element.android.libraries.architecture.NodeInputs
|
||||||
import io.element.android.libraries.matrix.api.verification.VerificationRequest
|
import io.element.android.libraries.matrix.api.verification.VerificationRequest
|
||||||
|
|
||||||
interface VerifySessionEntryPoint : FeatureEntryPoint {
|
interface OutgoingVerificationEntryPoint : FeatureEntryPoint {
|
||||||
data class Params(
|
data class Params(
|
||||||
val showDeviceVerifiedScreen: Boolean,
|
val showDeviceVerifiedScreen: Boolean,
|
||||||
val verificationRequest: VerificationRequest.Outgoing,
|
val verificationRequest: VerificationRequest.Outgoing,
|
||||||
|
|
@ -11,29 +11,29 @@ import com.bumble.appyx.core.modality.BuildContext
|
||||||
import com.bumble.appyx.core.node.Node
|
import com.bumble.appyx.core.node.Node
|
||||||
import com.bumble.appyx.core.plugin.Plugin
|
import com.bumble.appyx.core.plugin.Plugin
|
||||||
import com.squareup.anvil.annotations.ContributesBinding
|
import com.squareup.anvil.annotations.ContributesBinding
|
||||||
import io.element.android.features.verifysession.api.VerifySessionEntryPoint
|
import io.element.android.features.verifysession.api.OutgoingVerificationEntryPoint
|
||||||
import io.element.android.libraries.architecture.createNode
|
import io.element.android.libraries.architecture.createNode
|
||||||
import io.element.android.libraries.di.AppScope
|
import io.element.android.libraries.di.AppScope
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@ContributesBinding(AppScope::class)
|
@ContributesBinding(AppScope::class)
|
||||||
class DefaultVerifySessionEntryPoint @Inject constructor() : VerifySessionEntryPoint {
|
class DefaultOutgoingVerificationEntryPoint @Inject constructor() : OutgoingVerificationEntryPoint {
|
||||||
override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): VerifySessionEntryPoint.NodeBuilder {
|
override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): OutgoingVerificationEntryPoint.NodeBuilder {
|
||||||
val plugins = ArrayList<Plugin>()
|
val plugins = ArrayList<Plugin>()
|
||||||
|
|
||||||
return object : VerifySessionEntryPoint.NodeBuilder {
|
return object : OutgoingVerificationEntryPoint.NodeBuilder {
|
||||||
override fun callback(callback: VerifySessionEntryPoint.Callback): VerifySessionEntryPoint.NodeBuilder {
|
override fun callback(callback: OutgoingVerificationEntryPoint.Callback): OutgoingVerificationEntryPoint.NodeBuilder {
|
||||||
plugins += callback
|
plugins += callback
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun params(params: VerifySessionEntryPoint.Params): VerifySessionEntryPoint.NodeBuilder {
|
override fun params(params: OutgoingVerificationEntryPoint.Params): OutgoingVerificationEntryPoint.NodeBuilder {
|
||||||
plugins += params
|
plugins += params
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun build(): Node {
|
override fun build(): Node {
|
||||||
return parentNode.createNode<VerifySelfSessionNode>(buildContext, plugins)
|
return parentNode.createNode<OutgoingVerificationNode>(buildContext, plugins)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -16,19 +16,19 @@ import com.bumble.appyx.core.plugin.plugins
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
import io.element.android.anvilannotations.ContributesNode
|
import io.element.android.anvilannotations.ContributesNode
|
||||||
import io.element.android.features.verifysession.api.VerifySessionEntryPoint
|
import io.element.android.features.verifysession.api.OutgoingVerificationEntryPoint
|
||||||
import io.element.android.libraries.architecture.inputs
|
import io.element.android.libraries.architecture.inputs
|
||||||
import io.element.android.libraries.di.SessionScope
|
import io.element.android.libraries.di.SessionScope
|
||||||
|
|
||||||
@ContributesNode(SessionScope::class)
|
@ContributesNode(SessionScope::class)
|
||||||
class VerifySelfSessionNode @AssistedInject constructor(
|
class OutgoingVerificationNode @AssistedInject constructor(
|
||||||
@Assisted buildContext: BuildContext,
|
@Assisted buildContext: BuildContext,
|
||||||
@Assisted plugins: List<Plugin>,
|
@Assisted plugins: List<Plugin>,
|
||||||
presenterFactory: VerifySelfSessionPresenter.Factory,
|
presenterFactory: OutgoingVerificationPresenter.Factory,
|
||||||
) : Node(buildContext, plugins = plugins) {
|
) : Node(buildContext, plugins = plugins) {
|
||||||
private val callback = plugins<VerifySessionEntryPoint.Callback>().first()
|
private val callback = plugins<OutgoingVerificationEntryPoint.Callback>().first()
|
||||||
|
|
||||||
private val inputs = inputs<VerifySessionEntryPoint.Params>()
|
private val inputs = inputs<OutgoingVerificationEntryPoint.Params>()
|
||||||
|
|
||||||
private val presenter = presenterFactory.create(
|
private val presenter = presenterFactory.create(
|
||||||
showDeviceVerifiedScreen = inputs.showDeviceVerifiedScreen,
|
showDeviceVerifiedScreen = inputs.showDeviceVerifiedScreen,
|
||||||
|
|
@ -38,7 +38,7 @@ class VerifySelfSessionNode @AssistedInject constructor(
|
||||||
@Composable
|
@Composable
|
||||||
override fun View(modifier: Modifier) {
|
override fun View(modifier: Modifier) {
|
||||||
val state = presenter.present()
|
val state = presenter.present()
|
||||||
VerifySelfSessionView(
|
OutgoingVerificationView(
|
||||||
state = state,
|
state = state,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
onLearnMoreClick = callback::onLearnMoreAboutEncryption,
|
onLearnMoreClick = callback::onLearnMoreAboutEncryption,
|
||||||
|
|
@ -31,30 +31,30 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import io.element.android.features.verifysession.impl.outgoing.VerifySelfSessionStateMachine.Event as StateMachineEvent
|
import io.element.android.features.verifysession.impl.outgoing.OutgoingVerificationStateMachine.Event as StateMachineEvent
|
||||||
import io.element.android.features.verifysession.impl.outgoing.VerifySelfSessionStateMachine.State as StateMachineState
|
import io.element.android.features.verifysession.impl.outgoing.OutgoingVerificationStateMachine.State as StateMachineState
|
||||||
|
|
||||||
class VerifySelfSessionPresenter @AssistedInject constructor(
|
class OutgoingVerificationPresenter @AssistedInject constructor(
|
||||||
@Assisted private val showDeviceVerifiedScreen: Boolean,
|
@Assisted private val showDeviceVerifiedScreen: Boolean,
|
||||||
@Assisted private val verificationRequest: VerificationRequest.Outgoing,
|
@Assisted private val verificationRequest: VerificationRequest.Outgoing,
|
||||||
private val sessionVerificationService: SessionVerificationService,
|
private val sessionVerificationService: SessionVerificationService,
|
||||||
private val encryptionService: EncryptionService,
|
private val encryptionService: EncryptionService,
|
||||||
) : Presenter<VerifySelfSessionState> {
|
) : Presenter<OutgoingVerificationState> {
|
||||||
@AssistedFactory
|
@AssistedFactory
|
||||||
interface Factory {
|
interface Factory {
|
||||||
fun create(
|
fun create(
|
||||||
verificationRequest: VerificationRequest.Outgoing,
|
verificationRequest: VerificationRequest.Outgoing,
|
||||||
showDeviceVerifiedScreen: Boolean,
|
showDeviceVerifiedScreen: Boolean,
|
||||||
): VerifySelfSessionPresenter
|
): OutgoingVerificationPresenter
|
||||||
}
|
}
|
||||||
|
|
||||||
private val stateMachine = VerifySelfSessionStateMachine(
|
private val stateMachine = OutgoingVerificationStateMachine(
|
||||||
sessionVerificationService = sessionVerificationService,
|
sessionVerificationService = sessionVerificationService,
|
||||||
encryptionService = encryptionService,
|
encryptionService = encryptionService,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun present(): VerifySelfSessionState {
|
override fun present(): OutgoingVerificationState {
|
||||||
val stateAndDispatch = stateMachine.rememberStateAndDispatch()
|
val stateAndDispatch = stateMachine.rememberStateAndDispatch()
|
||||||
|
|
||||||
val sessionVerifiedStatus by sessionVerificationService.sessionVerifiedStatus.collectAsState()
|
val sessionVerifiedStatus by sessionVerificationService.sessionVerifiedStatus.collectAsState()
|
||||||
|
|
@ -63,17 +63,17 @@ class VerifySelfSessionPresenter @AssistedInject constructor(
|
||||||
when (verificationRequest) {
|
when (verificationRequest) {
|
||||||
is VerificationRequest.Outgoing.CurrentSession -> {
|
is VerificationRequest.Outgoing.CurrentSession -> {
|
||||||
when (sessionVerifiedStatus) {
|
when (sessionVerifiedStatus) {
|
||||||
SessionVerifiedStatus.Unknown -> VerifySelfSessionState.Step.Loading
|
SessionVerifiedStatus.Unknown -> OutgoingVerificationState.Step.Loading
|
||||||
SessionVerifiedStatus.NotVerified -> {
|
SessionVerifiedStatus.NotVerified -> {
|
||||||
stateAndDispatch.state.value.toVerificationStep()
|
stateAndDispatch.state.value.toVerificationStep()
|
||||||
}
|
}
|
||||||
SessionVerifiedStatus.Verified -> {
|
SessionVerifiedStatus.Verified -> {
|
||||||
if (stateAndDispatch.state.value != StateMachineState.Initial || showDeviceVerifiedScreen) {
|
if (stateAndDispatch.state.value != StateMachineState.Initial || showDeviceVerifiedScreen) {
|
||||||
// The user has verified the session, we need to show the success screen
|
// The user has verified the session, we need to show the success screen
|
||||||
VerifySelfSessionState.Step.Completed
|
OutgoingVerificationState.Step.Completed
|
||||||
} else {
|
} else {
|
||||||
// Automatic verification, which can happen on freshly created account, in this case, skip the screen
|
// Automatic verification, which can happen on freshly created account, in this case, skip the screen
|
||||||
VerifySelfSessionState.Step.Exit
|
OutgoingVerificationState.Step.Exit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -91,42 +91,44 @@ class VerifySelfSessionPresenter @AssistedInject constructor(
|
||||||
observeVerificationService()
|
observeVerificationService()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun handleEvents(event: VerifySelfSessionViewEvents) {
|
fun handleEvents(event: OutgoingVerificationViewEvents) {
|
||||||
Timber.d("Verification user action: ${event::class.simpleName}")
|
Timber.d("Verification user action: ${event::class.simpleName}")
|
||||||
when (event) {
|
when (event) {
|
||||||
// Just relay the event to the state machine
|
// Just relay the event to the state machine
|
||||||
VerifySelfSessionViewEvents.RequestVerification -> stateAndDispatch.dispatchAction(StateMachineEvent.RequestVerification(verificationRequest))
|
OutgoingVerificationViewEvents.RequestVerification -> StateMachineEvent.RequestVerification(verificationRequest)
|
||||||
VerifySelfSessionViewEvents.StartSasVerification -> stateAndDispatch.dispatchAction(StateMachineEvent.StartSasVerification)
|
OutgoingVerificationViewEvents.StartSasVerification -> StateMachineEvent.StartSasVerification
|
||||||
VerifySelfSessionViewEvents.ConfirmVerification -> stateAndDispatch.dispatchAction(StateMachineEvent.AcceptChallenge)
|
OutgoingVerificationViewEvents.ConfirmVerification -> StateMachineEvent.AcceptChallenge
|
||||||
VerifySelfSessionViewEvents.DeclineVerification -> stateAndDispatch.dispatchAction(StateMachineEvent.DeclineChallenge)
|
OutgoingVerificationViewEvents.DeclineVerification -> StateMachineEvent.DeclineChallenge
|
||||||
VerifySelfSessionViewEvents.Cancel -> stateAndDispatch.dispatchAction(StateMachineEvent.Cancel)
|
OutgoingVerificationViewEvents.Cancel -> StateMachineEvent.Cancel
|
||||||
VerifySelfSessionViewEvents.Reset -> stateAndDispatch.dispatchAction(StateMachineEvent.Reset)
|
OutgoingVerificationViewEvents.Reset -> StateMachineEvent.Reset
|
||||||
|
}.let { stateMachineEvent ->
|
||||||
|
stateAndDispatch.dispatchAction(stateMachineEvent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return VerifySelfSessionState(
|
return OutgoingVerificationState(
|
||||||
step = step,
|
step = step,
|
||||||
request = verificationRequest,
|
request = verificationRequest,
|
||||||
eventSink = ::handleEvents,
|
eventSink = ::handleEvents,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun StateMachineState?.toVerificationStep(): VerifySelfSessionState.Step =
|
private fun StateMachineState?.toVerificationStep(): OutgoingVerificationState.Step =
|
||||||
when (val machineState = this) {
|
when (val machineState = this) {
|
||||||
StateMachineState.Initial, null -> {
|
StateMachineState.Initial, null -> {
|
||||||
VerifySelfSessionState.Step.Initial
|
OutgoingVerificationState.Step.Initial
|
||||||
}
|
}
|
||||||
is StateMachineState.RequestingVerification,
|
is StateMachineState.RequestingVerification,
|
||||||
is StateMachineState.StartingSasVerification,
|
is StateMachineState.StartingSasVerification,
|
||||||
StateMachineState.SasVerificationStarted -> {
|
StateMachineState.SasVerificationStarted -> {
|
||||||
VerifySelfSessionState.Step.AwaitingOtherDeviceResponse
|
OutgoingVerificationState.Step.AwaitingOtherDeviceResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
StateMachineState.VerificationRequestAccepted -> {
|
StateMachineState.VerificationRequestAccepted -> {
|
||||||
VerifySelfSessionState.Step.Ready
|
OutgoingVerificationState.Step.Ready
|
||||||
}
|
}
|
||||||
|
|
||||||
is StateMachineState.Canceled -> {
|
is StateMachineState.Canceled -> {
|
||||||
VerifySelfSessionState.Step.Canceled
|
OutgoingVerificationState.Step.Canceled
|
||||||
}
|
}
|
||||||
|
|
||||||
is StateMachineState.Verifying -> {
|
is StateMachineState.Verifying -> {
|
||||||
|
|
@ -134,15 +136,15 @@ class VerifySelfSessionPresenter @AssistedInject constructor(
|
||||||
is StateMachineState.Verifying.Replying -> AsyncData.Loading()
|
is StateMachineState.Verifying.Replying -> AsyncData.Loading()
|
||||||
else -> AsyncData.Uninitialized
|
else -> AsyncData.Uninitialized
|
||||||
}
|
}
|
||||||
VerifySelfSessionState.Step.Verifying(machineState.data, async)
|
OutgoingVerificationState.Step.Verifying(machineState.data, async)
|
||||||
}
|
}
|
||||||
|
|
||||||
StateMachineState.Completed -> {
|
StateMachineState.Completed -> {
|
||||||
VerifySelfSessionState.Step.Completed
|
OutgoingVerificationState.Step.Completed
|
||||||
}
|
}
|
||||||
|
|
||||||
StateMachineState.Exit -> {
|
StateMachineState.Exit -> {
|
||||||
VerifySelfSessionState.Step.Exit
|
OutgoingVerificationState.Step.Exit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -14,10 +14,10 @@ import io.element.android.libraries.matrix.api.verification.SessionVerificationD
|
||||||
import io.element.android.libraries.matrix.api.verification.VerificationRequest
|
import io.element.android.libraries.matrix.api.verification.VerificationRequest
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
data class VerifySelfSessionState(
|
data class OutgoingVerificationState(
|
||||||
val step: Step,
|
val step: Step,
|
||||||
val request: VerificationRequest.Outgoing,
|
val request: VerificationRequest.Outgoing,
|
||||||
val eventSink: (VerifySelfSessionViewEvents) -> Unit,
|
val eventSink: (OutgoingVerificationViewEvents) -> Unit,
|
||||||
) {
|
) {
|
||||||
@Stable
|
@Stable
|
||||||
sealed interface Step {
|
sealed interface Step {
|
||||||
|
|
@ -29,10 +29,10 @@ import kotlin.time.Duration.Companion.seconds
|
||||||
import com.freeletics.flowredux.dsl.State as MachineState
|
import com.freeletics.flowredux.dsl.State as MachineState
|
||||||
|
|
||||||
@OptIn(FlowPreview::class)
|
@OptIn(FlowPreview::class)
|
||||||
class VerifySelfSessionStateMachine(
|
class OutgoingVerificationStateMachine(
|
||||||
private val sessionVerificationService: SessionVerificationService,
|
private val sessionVerificationService: SessionVerificationService,
|
||||||
private val encryptionService: EncryptionService,
|
private val encryptionService: EncryptionService,
|
||||||
) : FlowReduxStateMachine<VerifySelfSessionStateMachine.State, VerifySelfSessionStateMachine.Event>(
|
) : FlowReduxStateMachine<OutgoingVerificationStateMachine.State, OutgoingVerificationStateMachine.Event>(
|
||||||
initialState = State.Initial,
|
initialState = State.Initial,
|
||||||
) {
|
) {
|
||||||
init {
|
init {
|
||||||
|
|
@ -8,64 +8,64 @@
|
||||||
package io.element.android.features.verifysession.impl.outgoing
|
package io.element.android.features.verifysession.impl.outgoing
|
||||||
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import io.element.android.features.verifysession.impl.outgoing.VerifySelfSessionState.Step
|
import io.element.android.features.verifysession.impl.outgoing.OutgoingVerificationState.Step
|
||||||
import io.element.android.features.verifysession.impl.ui.aDecimalsSessionVerificationData
|
import io.element.android.features.verifysession.impl.ui.aDecimalsSessionVerificationData
|
||||||
import io.element.android.features.verifysession.impl.ui.aEmojisSessionVerificationData
|
import io.element.android.features.verifysession.impl.ui.aEmojisSessionVerificationData
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
import io.element.android.libraries.matrix.api.core.UserId
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
import io.element.android.libraries.matrix.api.verification.VerificationRequest
|
import io.element.android.libraries.matrix.api.verification.VerificationRequest
|
||||||
|
|
||||||
open class VerifySelfSessionStateProvider : PreviewParameterProvider<VerifySelfSessionState> {
|
open class OutgoingVerificationStateProvider : PreviewParameterProvider<OutgoingVerificationState> {
|
||||||
override val values: Sequence<VerifySelfSessionState>
|
override val values: Sequence<OutgoingVerificationState>
|
||||||
get() = sequenceOf(
|
get() = sequenceOf(
|
||||||
aVerifySelfSessionState(
|
anOutgoingVerificationState(
|
||||||
step = Step.Initial,
|
step = Step.Initial,
|
||||||
request = anOutgoingSessionVerificationRequest(),
|
request = anOutgoingSessionVerificationRequest(),
|
||||||
),
|
),
|
||||||
aVerifySelfSessionState(
|
anOutgoingVerificationState(
|
||||||
step = Step.Initial,
|
step = Step.Initial,
|
||||||
request = anOutgoingUserVerificationRequest(),
|
request = anOutgoingUserVerificationRequest(),
|
||||||
),
|
),
|
||||||
aVerifySelfSessionState(
|
anOutgoingVerificationState(
|
||||||
step = Step.AwaitingOtherDeviceResponse,
|
step = Step.AwaitingOtherDeviceResponse,
|
||||||
request = anOutgoingSessionVerificationRequest(),
|
request = anOutgoingSessionVerificationRequest(),
|
||||||
),
|
),
|
||||||
aVerifySelfSessionState(
|
anOutgoingVerificationState(
|
||||||
step = Step.AwaitingOtherDeviceResponse,
|
step = Step.AwaitingOtherDeviceResponse,
|
||||||
request = anOutgoingUserVerificationRequest(),
|
request = anOutgoingUserVerificationRequest(),
|
||||||
),
|
),
|
||||||
aVerifySelfSessionState(
|
anOutgoingVerificationState(
|
||||||
step = Step.Verifying(aEmojisSessionVerificationData(), AsyncData.Uninitialized),
|
step = Step.Verifying(aEmojisSessionVerificationData(), AsyncData.Uninitialized),
|
||||||
request = anOutgoingSessionVerificationRequest(),
|
request = anOutgoingSessionVerificationRequest(),
|
||||||
),
|
),
|
||||||
aVerifySelfSessionState(
|
anOutgoingVerificationState(
|
||||||
step = Step.Verifying(aEmojisSessionVerificationData(), AsyncData.Uninitialized),
|
step = Step.Verifying(aEmojisSessionVerificationData(), AsyncData.Uninitialized),
|
||||||
request = anOutgoingUserVerificationRequest(),
|
request = anOutgoingUserVerificationRequest(),
|
||||||
),
|
),
|
||||||
aVerifySelfSessionState(
|
anOutgoingVerificationState(
|
||||||
step = Step.Verifying(aEmojisSessionVerificationData(), AsyncData.Loading())
|
step = Step.Verifying(aEmojisSessionVerificationData(), AsyncData.Loading())
|
||||||
),
|
),
|
||||||
aVerifySelfSessionState(
|
anOutgoingVerificationState(
|
||||||
step = Step.Canceled
|
step = Step.Canceled
|
||||||
),
|
),
|
||||||
aVerifySelfSessionState(
|
anOutgoingVerificationState(
|
||||||
step = Step.Ready
|
step = Step.Ready
|
||||||
),
|
),
|
||||||
aVerifySelfSessionState(
|
anOutgoingVerificationState(
|
||||||
step = Step.Verifying(aDecimalsSessionVerificationData(), AsyncData.Uninitialized)
|
step = Step.Verifying(aDecimalsSessionVerificationData(), AsyncData.Uninitialized)
|
||||||
),
|
),
|
||||||
aVerifySelfSessionState(
|
anOutgoingVerificationState(
|
||||||
step = Step.Completed,
|
step = Step.Completed,
|
||||||
request = anOutgoingSessionVerificationRequest(),
|
request = anOutgoingSessionVerificationRequest(),
|
||||||
),
|
),
|
||||||
aVerifySelfSessionState(
|
anOutgoingVerificationState(
|
||||||
step = Step.Completed,
|
step = Step.Completed,
|
||||||
request = anOutgoingUserVerificationRequest(),
|
request = anOutgoingUserVerificationRequest(),
|
||||||
),
|
),
|
||||||
aVerifySelfSessionState(
|
anOutgoingVerificationState(
|
||||||
step = Step.Loading
|
step = Step.Loading
|
||||||
),
|
),
|
||||||
aVerifySelfSessionState(
|
anOutgoingVerificationState(
|
||||||
step = Step.Exit
|
step = Step.Exit
|
||||||
),
|
),
|
||||||
// Add other state here
|
// Add other state here
|
||||||
|
|
@ -75,11 +75,11 @@ open class VerifySelfSessionStateProvider : PreviewParameterProvider<VerifySelfS
|
||||||
internal fun anOutgoingUserVerificationRequest() = VerificationRequest.Outgoing.User(userId = UserId("@alice:example.com"))
|
internal fun anOutgoingUserVerificationRequest() = VerificationRequest.Outgoing.User(userId = UserId("@alice:example.com"))
|
||||||
internal fun anOutgoingSessionVerificationRequest() = VerificationRequest.Outgoing.CurrentSession
|
internal fun anOutgoingSessionVerificationRequest() = VerificationRequest.Outgoing.CurrentSession
|
||||||
|
|
||||||
internal fun aVerifySelfSessionState(
|
internal fun anOutgoingVerificationState(
|
||||||
step: Step = Step.Initial,
|
step: Step = Step.Initial,
|
||||||
request: VerificationRequest.Outgoing = anOutgoingSessionVerificationRequest(),
|
request: VerificationRequest.Outgoing = anOutgoingSessionVerificationRequest(),
|
||||||
eventSink: (VerifySelfSessionViewEvents) -> Unit = {},
|
eventSink: (OutgoingVerificationViewEvents) -> Unit = {},
|
||||||
) = VerifySelfSessionState(
|
) = OutgoingVerificationState(
|
||||||
step = step,
|
step = step,
|
||||||
request = request,
|
request = request,
|
||||||
eventSink = eventSink,
|
eventSink = eventSink,
|
||||||
|
|
@ -27,7 +27,7 @@ import androidx.compose.ui.unit.dp
|
||||||
import io.element.android.compound.theme.ElementTheme
|
import io.element.android.compound.theme.ElementTheme
|
||||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||||
import io.element.android.features.verifysession.impl.R
|
import io.element.android.features.verifysession.impl.R
|
||||||
import io.element.android.features.verifysession.impl.outgoing.VerifySelfSessionState.Step
|
import io.element.android.features.verifysession.impl.outgoing.OutgoingVerificationState.Step
|
||||||
import io.element.android.features.verifysession.impl.ui.VerificationBottomMenu
|
import io.element.android.features.verifysession.impl.ui.VerificationBottomMenu
|
||||||
import io.element.android.features.verifysession.impl.ui.VerificationContentVerifying
|
import io.element.android.features.verifysession.impl.ui.VerificationContentVerifying
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
|
|
@ -49,8 +49,8 @@ import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun VerifySelfSessionView(
|
fun OutgoingVerificationView(
|
||||||
state: VerifySelfSessionState,
|
state: OutgoingVerificationState,
|
||||||
onLearnMoreClick: () -> Unit,
|
onLearnMoreClick: () -> Unit,
|
||||||
onFinish: () -> Unit,
|
onFinish: () -> Unit,
|
||||||
onBack: () -> Unit,
|
onBack: () -> Unit,
|
||||||
|
|
@ -59,12 +59,12 @@ fun VerifySelfSessionView(
|
||||||
val step = state.step
|
val step = state.step
|
||||||
fun cancelOrResetFlow() {
|
fun cancelOrResetFlow() {
|
||||||
when (step) {
|
when (step) {
|
||||||
is Step.Canceled -> state.eventSink(VerifySelfSessionViewEvents.Reset)
|
is Step.Canceled -> state.eventSink(OutgoingVerificationViewEvents.Reset)
|
||||||
Step.Initial, Step.Completed -> onBack()
|
Step.Initial, Step.Completed -> onBack()
|
||||||
Step.Ready, is Step.AwaitingOtherDeviceResponse -> state.eventSink(VerifySelfSessionViewEvents.Cancel)
|
Step.Ready, is Step.AwaitingOtherDeviceResponse -> state.eventSink(OutgoingVerificationViewEvents.Cancel)
|
||||||
is Step.Verifying -> {
|
is Step.Verifying -> {
|
||||||
if (!step.state.isLoading()) {
|
if (!step.state.isLoading()) {
|
||||||
state.eventSink(VerifySelfSessionViewEvents.DeclineVerification)
|
state.eventSink(OutgoingVerificationViewEvents.DeclineVerification)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> Unit
|
else -> Unit
|
||||||
|
|
@ -98,10 +98,10 @@ fun VerifySelfSessionView(
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
header = {
|
header = {
|
||||||
VerifySelfSessionHeader(step = step, request = state.request)
|
OutgoingVerificationHeader(step = step, request = state.request)
|
||||||
},
|
},
|
||||||
footer = {
|
footer = {
|
||||||
VerifySelfSessionBottomMenu(
|
OutgoingVerificationViewBottomMenu(
|
||||||
screenState = state,
|
screenState = state,
|
||||||
onCancelClick = ::cancelOrResetFlow,
|
onCancelClick = ::cancelOrResetFlow,
|
||||||
onContinueClick = onFinish,
|
onContinueClick = onFinish,
|
||||||
|
|
@ -109,7 +109,7 @@ fun VerifySelfSessionView(
|
||||||
},
|
},
|
||||||
isScrollable = true,
|
isScrollable = true,
|
||||||
) {
|
) {
|
||||||
VerifySelfSessionContent(
|
OutgoingVerificationContent(
|
||||||
flowState = step,
|
flowState = step,
|
||||||
request = state.request,
|
request = state.request,
|
||||||
onLearnMoreClick = onLearnMoreClick,
|
onLearnMoreClick = onLearnMoreClick,
|
||||||
|
|
@ -119,7 +119,7 @@ fun VerifySelfSessionView(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun VerifySelfSessionHeader(step: Step, request: VerificationRequest.Outgoing) {
|
private fun OutgoingVerificationHeader(step: Step, request: VerificationRequest.Outgoing) {
|
||||||
val iconStyle = when (step) {
|
val iconStyle = when (step) {
|
||||||
Step.Loading -> error("Should not happen")
|
Step.Loading -> error("Should not happen")
|
||||||
Step.Initial -> when (request) {
|
Step.Initial -> when (request) {
|
||||||
|
|
@ -189,7 +189,7 @@ private fun VerifySelfSessionHeader(step: Step, request: VerificationRequest.Out
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun VerifySelfSessionContent(
|
private fun OutgoingVerificationContent(
|
||||||
flowState: Step,
|
flowState: Step,
|
||||||
request: VerificationRequest.Outgoing,
|
request: VerificationRequest.Outgoing,
|
||||||
onLearnMoreClick: () -> Unit,
|
onLearnMoreClick: () -> Unit,
|
||||||
|
|
@ -227,8 +227,8 @@ private fun ContentInitial(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun VerifySelfSessionBottomMenu(
|
private fun OutgoingVerificationViewBottomMenu(
|
||||||
screenState: VerifySelfSessionState,
|
screenState: OutgoingVerificationState,
|
||||||
onCancelClick: () -> Unit,
|
onCancelClick: () -> Unit,
|
||||||
onContinueClick: () -> Unit,
|
onContinueClick: () -> Unit,
|
||||||
) {
|
) {
|
||||||
|
|
@ -244,7 +244,7 @@ private fun VerifySelfSessionBottomMenu(
|
||||||
Button(
|
Button(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
text = stringResource(CommonStrings.action_start_verification),
|
text = stringResource(CommonStrings.action_start_verification),
|
||||||
onClick = { eventSink(VerifySelfSessionViewEvents.RequestVerification) },
|
onClick = { eventSink(OutgoingVerificationViewEvents.RequestVerification) },
|
||||||
)
|
)
|
||||||
InvisibleButton()
|
InvisibleButton()
|
||||||
}
|
}
|
||||||
|
|
@ -264,7 +264,7 @@ private fun VerifySelfSessionBottomMenu(
|
||||||
Button(
|
Button(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
text = stringResource(CommonStrings.action_start),
|
text = stringResource(CommonStrings.action_start),
|
||||||
onClick = { eventSink(VerifySelfSessionViewEvents.StartSasVerification) },
|
onClick = { eventSink(OutgoingVerificationViewEvents.StartSasVerification) },
|
||||||
)
|
)
|
||||||
TextButton(
|
TextButton(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
|
@ -287,14 +287,14 @@ private fun VerifySelfSessionBottomMenu(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
text = stringResource(R.string.screen_session_verification_they_match),
|
text = stringResource(R.string.screen_session_verification_they_match),
|
||||||
onClick = {
|
onClick = {
|
||||||
eventSink(VerifySelfSessionViewEvents.ConfirmVerification)
|
eventSink(OutgoingVerificationViewEvents.ConfirmVerification)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
TextButton(
|
TextButton(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
text = stringResource(R.string.screen_session_verification_they_dont_match),
|
text = stringResource(R.string.screen_session_verification_they_dont_match),
|
||||||
onClick = { eventSink(VerifySelfSessionViewEvents.DeclineVerification) },
|
onClick = { eventSink(OutgoingVerificationViewEvents.DeclineVerification) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -315,8 +315,8 @@ private fun VerifySelfSessionBottomMenu(
|
||||||
|
|
||||||
@PreviewsDayNight
|
@PreviewsDayNight
|
||||||
@Composable
|
@Composable
|
||||||
internal fun VerifySelfSessionViewPreview(@PreviewParameter(VerifySelfSessionStateProvider::class) state: VerifySelfSessionState) = ElementPreview {
|
internal fun OutgoingVerificationViewPreview(@PreviewParameter(OutgoingVerificationStateProvider::class) state: OutgoingVerificationState) = ElementPreview {
|
||||||
VerifySelfSessionView(
|
OutgoingVerificationView(
|
||||||
state = state,
|
state = state,
|
||||||
onLearnMoreClick = {},
|
onLearnMoreClick = {},
|
||||||
onFinish = {},
|
onFinish = {},
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023, 2024 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.verifysession.impl.outgoing
|
||||||
|
|
||||||
|
sealed interface OutgoingVerificationViewEvents {
|
||||||
|
data object RequestVerification : OutgoingVerificationViewEvents
|
||||||
|
data object StartSasVerification : OutgoingVerificationViewEvents
|
||||||
|
data object ConfirmVerification : OutgoingVerificationViewEvents
|
||||||
|
data object DeclineVerification : OutgoingVerificationViewEvents
|
||||||
|
data object Cancel : OutgoingVerificationViewEvents
|
||||||
|
data object Reset : OutgoingVerificationViewEvents
|
||||||
|
}
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2023, 2024 New Vector Ltd.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
|
||||||
* Please see LICENSE files in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.element.android.features.verifysession.impl.outgoing
|
|
||||||
|
|
||||||
sealed interface VerifySelfSessionViewEvents {
|
|
||||||
data object RequestVerification : VerifySelfSessionViewEvents
|
|
||||||
data object StartSasVerification : VerifySelfSessionViewEvents
|
|
||||||
data object ConfirmVerification : VerifySelfSessionViewEvents
|
|
||||||
data object DeclineVerification : VerifySelfSessionViewEvents
|
|
||||||
data object Cancel : VerifySelfSessionViewEvents
|
|
||||||
data object Reset : VerifySelfSessionViewEvents
|
|
||||||
}
|
|
||||||
|
|
@ -9,7 +9,7 @@ package io.element.android.features.verifysession.impl.outgoing
|
||||||
|
|
||||||
import app.cash.turbine.ReceiveTurbine
|
import app.cash.turbine.ReceiveTurbine
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.features.verifysession.impl.outgoing.VerifySelfSessionState.Step
|
import io.element.android.features.verifysession.impl.outgoing.OutgoingVerificationState.Step
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
import io.element.android.libraries.matrix.api.core.UserId
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
import io.element.android.libraries.matrix.api.encryption.EncryptionService
|
import io.element.android.libraries.matrix.api.encryption.EncryptionService
|
||||||
|
|
@ -31,13 +31,13 @@ import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
@ExperimentalCoroutinesApi
|
@ExperimentalCoroutinesApi
|
||||||
class VerifySelfSessionPresenterTest {
|
class OutgoingVerificationPresenterTest {
|
||||||
@get:Rule
|
@get:Rule
|
||||||
val warmUpRule = WarmUpRule()
|
val warmUpRule = WarmUpRule()
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - Initial state is received`() = runTest {
|
fun `present - Initial state is received`() = runTest {
|
||||||
val presenter = createVerifySelfSessionPresenter(
|
val presenter = createOutgoingVerificationPresenter(
|
||||||
service = unverifiedSessionService(),
|
service = unverifiedSessionService(),
|
||||||
)
|
)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
|
|
@ -55,7 +55,7 @@ class VerifySelfSessionPresenterTest {
|
||||||
requestSessionVerificationLambda = requestSessionVerificationRecorder,
|
requestSessionVerificationLambda = requestSessionVerificationRecorder,
|
||||||
startVerificationLambda = startVerificationRecorder,
|
startVerificationLambda = startVerificationRecorder,
|
||||||
)
|
)
|
||||||
val presenter = createVerifySelfSessionPresenter(
|
val presenter = createOutgoingVerificationPresenter(
|
||||||
service = service,
|
service = service,
|
||||||
verificationRequest = anOutgoingSessionVerificationRequest(),
|
verificationRequest = anOutgoingSessionVerificationRequest(),
|
||||||
)
|
)
|
||||||
|
|
@ -75,7 +75,7 @@ class VerifySelfSessionPresenterTest {
|
||||||
requestUserVerificationLambda = requestUserVerificationRecorder,
|
requestUserVerificationLambda = requestUserVerificationRecorder,
|
||||||
startVerificationLambda = startVerificationRecorder,
|
startVerificationLambda = startVerificationRecorder,
|
||||||
)
|
)
|
||||||
val presenter = createVerifySelfSessionPresenter(
|
val presenter = createOutgoingVerificationPresenter(
|
||||||
service = service,
|
service = service,
|
||||||
verificationRequest = anOutgoingUserVerificationRequest(),
|
verificationRequest = anOutgoingUserVerificationRequest(),
|
||||||
)
|
)
|
||||||
|
|
@ -89,14 +89,14 @@ class VerifySelfSessionPresenterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - Cancellation on initial state moves to Exit state`() = runTest {
|
fun `present - Cancellation on initial state moves to Exit state`() = runTest {
|
||||||
val presenter = createVerifySelfSessionPresenter(
|
val presenter = createOutgoingVerificationPresenter(
|
||||||
service = unverifiedSessionService(),
|
service = unverifiedSessionService(),
|
||||||
)
|
)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.step).isEqualTo(Step.Initial)
|
assertThat(initialState.step).isEqualTo(Step.Initial)
|
||||||
val eventSink = initialState.eventSink
|
val eventSink = initialState.eventSink
|
||||||
eventSink(VerifySelfSessionViewEvents.Cancel)
|
eventSink(OutgoingVerificationViewEvents.Cancel)
|
||||||
|
|
||||||
assertThat(awaitItem().step).isEqualTo(Step.Exit)
|
assertThat(awaitItem().step).isEqualTo(Step.Exit)
|
||||||
}
|
}
|
||||||
|
|
@ -109,10 +109,10 @@ class VerifySelfSessionPresenterTest {
|
||||||
startVerificationLambda = { },
|
startVerificationLambda = { },
|
||||||
approveVerificationLambda = { },
|
approveVerificationLambda = { },
|
||||||
)
|
)
|
||||||
val presenter = createVerifySelfSessionPresenter(service)
|
val presenter = createOutgoingVerificationPresenter(service)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
val state = requestVerificationAndAwaitVerifyingState(service)
|
val state = requestVerificationAndAwaitVerifyingState(service)
|
||||||
state.eventSink(VerifySelfSessionViewEvents.ConfirmVerification)
|
state.eventSink(OutgoingVerificationViewEvents.ConfirmVerification)
|
||||||
// Cancelling
|
// Cancelling
|
||||||
assertThat(awaitItem().step).isInstanceOf(Step.Verifying::class.java)
|
assertThat(awaitItem().step).isInstanceOf(Step.Verifying::class.java)
|
||||||
service.emitVerificationFlowState(VerificationFlowState.DidFail)
|
service.emitVerificationFlowState(VerificationFlowState.DidFail)
|
||||||
|
|
@ -126,9 +126,9 @@ class VerifySelfSessionPresenterTest {
|
||||||
val service = unverifiedSessionService(
|
val service = unverifiedSessionService(
|
||||||
requestSessionVerificationLambda = { },
|
requestSessionVerificationLambda = { },
|
||||||
)
|
)
|
||||||
val presenter = createVerifySelfSessionPresenter(service)
|
val presenter = createOutgoingVerificationPresenter(service)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
awaitItem().eventSink(VerifySelfSessionViewEvents.RequestVerification)
|
awaitItem().eventSink(OutgoingVerificationViewEvents.RequestVerification)
|
||||||
service.emitVerificationFlowState(VerificationFlowState.DidFail)
|
service.emitVerificationFlowState(VerificationFlowState.DidFail)
|
||||||
assertThat(awaitItem().step).isInstanceOf(Step.AwaitingOtherDeviceResponse::class.java)
|
assertThat(awaitItem().step).isInstanceOf(Step.AwaitingOtherDeviceResponse::class.java)
|
||||||
assertThat(awaitItem().step).isEqualTo(Step.Canceled)
|
assertThat(awaitItem().step).isEqualTo(Step.Canceled)
|
||||||
|
|
@ -142,10 +142,10 @@ class VerifySelfSessionPresenterTest {
|
||||||
startVerificationLambda = { },
|
startVerificationLambda = { },
|
||||||
cancelVerificationLambda = { },
|
cancelVerificationLambda = { },
|
||||||
)
|
)
|
||||||
val presenter = createVerifySelfSessionPresenter(service)
|
val presenter = createOutgoingVerificationPresenter(service)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
val state = requestVerificationAndAwaitVerifyingState(service)
|
val state = requestVerificationAndAwaitVerifyingState(service)
|
||||||
state.eventSink(VerifySelfSessionViewEvents.Cancel)
|
state.eventSink(OutgoingVerificationViewEvents.Cancel)
|
||||||
assertThat(awaitItem().step).isEqualTo(Step.Canceled)
|
assertThat(awaitItem().step).isEqualTo(Step.Canceled)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -156,7 +156,7 @@ class VerifySelfSessionPresenterTest {
|
||||||
requestSessionVerificationLambda = { },
|
requestSessionVerificationLambda = { },
|
||||||
startVerificationLambda = { },
|
startVerificationLambda = { },
|
||||||
)
|
)
|
||||||
val presenter = createVerifySelfSessionPresenter(service)
|
val presenter = createOutgoingVerificationPresenter(service)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
requestVerificationAndAwaitVerifyingState(service)
|
requestVerificationAndAwaitVerifyingState(service)
|
||||||
service.emitVerificationFlowState(VerificationFlowState.DidReceiveVerificationData(SessionVerificationData.Emojis(emptyList())))
|
service.emitVerificationFlowState(VerificationFlowState.DidReceiveVerificationData(SessionVerificationData.Emojis(emptyList())))
|
||||||
|
|
@ -170,12 +170,12 @@ class VerifySelfSessionPresenterTest {
|
||||||
requestSessionVerificationLambda = { },
|
requestSessionVerificationLambda = { },
|
||||||
startVerificationLambda = { },
|
startVerificationLambda = { },
|
||||||
)
|
)
|
||||||
val presenter = createVerifySelfSessionPresenter(service)
|
val presenter = createOutgoingVerificationPresenter(service)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
val state = requestVerificationAndAwaitVerifyingState(service)
|
val state = requestVerificationAndAwaitVerifyingState(service)
|
||||||
service.emitVerificationFlowState(VerificationFlowState.DidCancel)
|
service.emitVerificationFlowState(VerificationFlowState.DidCancel)
|
||||||
assertThat(awaitItem().step).isEqualTo(Step.Canceled)
|
assertThat(awaitItem().step).isEqualTo(Step.Canceled)
|
||||||
state.eventSink(VerifySelfSessionViewEvents.Reset)
|
state.eventSink(OutgoingVerificationViewEvents.Reset)
|
||||||
// Went back to initial state
|
// Went back to initial state
|
||||||
assertThat(awaitItem().step).isEqualTo(Step.Initial)
|
assertThat(awaitItem().step).isEqualTo(Step.Initial)
|
||||||
cancelAndIgnoreRemainingEvents()
|
cancelAndIgnoreRemainingEvents()
|
||||||
|
|
@ -192,13 +192,13 @@ class VerifySelfSessionPresenterTest {
|
||||||
startVerificationLambda = { },
|
startVerificationLambda = { },
|
||||||
approveVerificationLambda = { },
|
approveVerificationLambda = { },
|
||||||
)
|
)
|
||||||
val presenter = createVerifySelfSessionPresenter(service)
|
val presenter = createOutgoingVerificationPresenter(service)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
val state = requestVerificationAndAwaitVerifyingState(
|
val state = requestVerificationAndAwaitVerifyingState(
|
||||||
service,
|
service,
|
||||||
SessionVerificationData.Emojis(emojis)
|
SessionVerificationData.Emojis(emojis)
|
||||||
)
|
)
|
||||||
state.eventSink(VerifySelfSessionViewEvents.ConfirmVerification)
|
state.eventSink(OutgoingVerificationViewEvents.ConfirmVerification)
|
||||||
assertThat(awaitItem().step).isEqualTo(
|
assertThat(awaitItem().step).isEqualTo(
|
||||||
Step.Verifying(
|
Step.Verifying(
|
||||||
SessionVerificationData.Emojis(emojis),
|
SessionVerificationData.Emojis(emojis),
|
||||||
|
|
@ -217,10 +217,10 @@ class VerifySelfSessionPresenterTest {
|
||||||
startVerificationLambda = { },
|
startVerificationLambda = { },
|
||||||
declineVerificationLambda = { },
|
declineVerificationLambda = { },
|
||||||
)
|
)
|
||||||
val presenter = createVerifySelfSessionPresenter(service)
|
val presenter = createOutgoingVerificationPresenter(service)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
val state = requestVerificationAndAwaitVerifyingState(service)
|
val state = requestVerificationAndAwaitVerifyingState(service)
|
||||||
state.eventSink(VerifySelfSessionViewEvents.DeclineVerification)
|
state.eventSink(OutgoingVerificationViewEvents.DeclineVerification)
|
||||||
assertThat(awaitItem().step).isEqualTo(
|
assertThat(awaitItem().step).isEqualTo(
|
||||||
Step.Verifying(
|
Step.Verifying(
|
||||||
SessionVerificationData.Emojis(emptyList()),
|
SessionVerificationData.Emojis(emptyList()),
|
||||||
|
|
@ -241,7 +241,7 @@ class VerifySelfSessionPresenterTest {
|
||||||
emitVerifiedStatus(SessionVerifiedStatus.Verified)
|
emitVerifiedStatus(SessionVerifiedStatus.Verified)
|
||||||
emitVerificationFlowState(VerificationFlowState.DidFinish)
|
emitVerificationFlowState(VerificationFlowState.DidFinish)
|
||||||
}
|
}
|
||||||
val presenter = createVerifySelfSessionPresenter(
|
val presenter = createOutgoingVerificationPresenter(
|
||||||
service = service,
|
service = service,
|
||||||
showDeviceVerifiedScreen = true,
|
showDeviceVerifiedScreen = true,
|
||||||
)
|
)
|
||||||
|
|
@ -259,7 +259,7 @@ class VerifySelfSessionPresenterTest {
|
||||||
emitVerifiedStatus(SessionVerifiedStatus.Verified)
|
emitVerifiedStatus(SessionVerifiedStatus.Verified)
|
||||||
emitVerificationFlowState(VerificationFlowState.DidFinish)
|
emitVerificationFlowState(VerificationFlowState.DidFinish)
|
||||||
}
|
}
|
||||||
val presenter = createVerifySelfSessionPresenter(
|
val presenter = createOutgoingVerificationPresenter(
|
||||||
service = service,
|
service = service,
|
||||||
showDeviceVerifiedScreen = false,
|
showDeviceVerifiedScreen = false,
|
||||||
)
|
)
|
||||||
|
|
@ -269,13 +269,13 @@ class VerifySelfSessionPresenterTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun ReceiveTurbine<VerifySelfSessionState>.requestVerificationAndAwaitVerifyingState(
|
private suspend fun ReceiveTurbine<OutgoingVerificationState>.requestVerificationAndAwaitVerifyingState(
|
||||||
fakeService: FakeSessionVerificationService,
|
fakeService: FakeSessionVerificationService,
|
||||||
sessionVerificationData: SessionVerificationData = SessionVerificationData.Emojis(emptyList()),
|
sessionVerificationData: SessionVerificationData = SessionVerificationData.Emojis(emptyList()),
|
||||||
): VerifySelfSessionState {
|
): OutgoingVerificationState {
|
||||||
var state = awaitItem()
|
var state = awaitItem()
|
||||||
assertThat(state.step).isEqualTo(Step.Initial)
|
assertThat(state.step).isEqualTo(Step.Initial)
|
||||||
state.eventSink(VerifySelfSessionViewEvents.RequestVerification)
|
state.eventSink(OutgoingVerificationViewEvents.RequestVerification)
|
||||||
// Await for other device response:
|
// Await for other device response:
|
||||||
fakeService.emitVerificationFlowState(VerificationFlowState.DidAcceptVerificationRequest)
|
fakeService.emitVerificationFlowState(VerificationFlowState.DidAcceptVerificationRequest)
|
||||||
state = awaitItem()
|
state = awaitItem()
|
||||||
|
|
@ -283,7 +283,7 @@ class VerifySelfSessionPresenterTest {
|
||||||
// Await for the state to be Ready
|
// Await for the state to be Ready
|
||||||
state = awaitItem()
|
state = awaitItem()
|
||||||
assertThat(state.step).isEqualTo(Step.Ready)
|
assertThat(state.step).isEqualTo(Step.Ready)
|
||||||
state.eventSink(VerifySelfSessionViewEvents.StartSasVerification)
|
state.eventSink(OutgoingVerificationViewEvents.StartSasVerification)
|
||||||
// Await for other device response (again):
|
// Await for other device response (again):
|
||||||
fakeService.emitVerificationFlowState(VerificationFlowState.DidStartSasVerification)
|
fakeService.emitVerificationFlowState(VerificationFlowState.DidStartSasVerification)
|
||||||
state = awaitItem()
|
state = awaitItem()
|
||||||
|
|
@ -321,13 +321,13 @@ class VerifySelfSessionPresenterTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createVerifySelfSessionPresenter(
|
private fun createOutgoingVerificationPresenter(
|
||||||
service: SessionVerificationService,
|
service: SessionVerificationService,
|
||||||
verificationRequest: VerificationRequest.Outgoing = anOutgoingSessionVerificationRequest(),
|
verificationRequest: VerificationRequest.Outgoing = anOutgoingSessionVerificationRequest(),
|
||||||
encryptionService: EncryptionService = FakeEncryptionService(),
|
encryptionService: EncryptionService = FakeEncryptionService(),
|
||||||
showDeviceVerifiedScreen: Boolean = false,
|
showDeviceVerifiedScreen: Boolean = false,
|
||||||
): VerifySelfSessionPresenter {
|
): OutgoingVerificationPresenter {
|
||||||
return VerifySelfSessionPresenter(
|
return OutgoingVerificationPresenter(
|
||||||
showDeviceVerifiedScreen = showDeviceVerifiedScreen,
|
showDeviceVerifiedScreen = showDeviceVerifiedScreen,
|
||||||
verificationRequest = verificationRequest,
|
verificationRequest = verificationRequest,
|
||||||
sessionVerificationService = service,
|
sessionVerificationService = service,
|
||||||
|
|
@ -26,54 +26,54 @@ import org.junit.rules.TestRule
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class VerifySelfSessionViewTest {
|
class OutgoingVerificationViewTest {
|
||||||
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
|
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `back key pressed - when canceled resets the flow`() {
|
fun `back key pressed - when canceled resets the flow`() {
|
||||||
val eventsRecorder = EventsRecorder<VerifySelfSessionViewEvents>()
|
val eventsRecorder = EventsRecorder<OutgoingVerificationViewEvents>()
|
||||||
rule.setVerifySelfSessionView(
|
rule.setOutgoingVerificationView(
|
||||||
aVerifySelfSessionState(
|
anOutgoingVerificationState(
|
||||||
step = VerifySelfSessionState.Step.Canceled,
|
step = OutgoingVerificationState.Step.Canceled,
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.pressBackKey()
|
rule.pressBackKey()
|
||||||
eventsRecorder.assertSingle(VerifySelfSessionViewEvents.Reset)
|
eventsRecorder.assertSingle(OutgoingVerificationViewEvents.Reset)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `back key pressed - when awaiting response cancels the verification`() {
|
fun `back key pressed - when awaiting response cancels the verification`() {
|
||||||
val eventsRecorder = EventsRecorder<VerifySelfSessionViewEvents>()
|
val eventsRecorder = EventsRecorder<OutgoingVerificationViewEvents>()
|
||||||
rule.setVerifySelfSessionView(
|
rule.setOutgoingVerificationView(
|
||||||
aVerifySelfSessionState(
|
anOutgoingVerificationState(
|
||||||
step = VerifySelfSessionState.Step.AwaitingOtherDeviceResponse,
|
step = OutgoingVerificationState.Step.AwaitingOtherDeviceResponse,
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.pressBackKey()
|
rule.pressBackKey()
|
||||||
eventsRecorder.assertSingle(VerifySelfSessionViewEvents.Cancel)
|
eventsRecorder.assertSingle(OutgoingVerificationViewEvents.Cancel)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `back key pressed - when ready to verify cancels the verification`() {
|
fun `back key pressed - when ready to verify cancels the verification`() {
|
||||||
val eventsRecorder = EventsRecorder<VerifySelfSessionViewEvents>()
|
val eventsRecorder = EventsRecorder<OutgoingVerificationViewEvents>()
|
||||||
rule.setVerifySelfSessionView(
|
rule.setOutgoingVerificationView(
|
||||||
aVerifySelfSessionState(
|
anOutgoingVerificationState(
|
||||||
step = VerifySelfSessionState.Step.Ready,
|
step = OutgoingVerificationState.Step.Ready,
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.pressBackKey()
|
rule.pressBackKey()
|
||||||
eventsRecorder.assertSingle(VerifySelfSessionViewEvents.Cancel)
|
eventsRecorder.assertSingle(OutgoingVerificationViewEvents.Cancel)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `back key pressed - when verifying and not loading declines the verification`() {
|
fun `back key pressed - when verifying and not loading declines the verification`() {
|
||||||
val eventsRecorder = EventsRecorder<VerifySelfSessionViewEvents>()
|
val eventsRecorder = EventsRecorder<OutgoingVerificationViewEvents>()
|
||||||
rule.setVerifySelfSessionView(
|
rule.setOutgoingVerificationView(
|
||||||
aVerifySelfSessionState(
|
anOutgoingVerificationState(
|
||||||
step = VerifySelfSessionState.Step.Verifying(
|
step = OutgoingVerificationState.Step.Verifying(
|
||||||
data = aEmojisSessionVerificationData(),
|
data = aEmojisSessionVerificationData(),
|
||||||
state = AsyncData.Uninitialized,
|
state = AsyncData.Uninitialized,
|
||||||
),
|
),
|
||||||
|
|
@ -81,15 +81,15 @@ class VerifySelfSessionViewTest {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.pressBackKey()
|
rule.pressBackKey()
|
||||||
eventsRecorder.assertSingle(VerifySelfSessionViewEvents.DeclineVerification)
|
eventsRecorder.assertSingle(OutgoingVerificationViewEvents.DeclineVerification)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `back key pressed - when verifying and loading does nothing`() {
|
fun `back key pressed - when verifying and loading does nothing`() {
|
||||||
val eventsRecorder = EventsRecorder<VerifySelfSessionViewEvents>()
|
val eventsRecorder = EventsRecorder<OutgoingVerificationViewEvents>()
|
||||||
rule.setVerifySelfSessionView(
|
rule.setOutgoingVerificationView(
|
||||||
aVerifySelfSessionState(
|
anOutgoingVerificationState(
|
||||||
step = VerifySelfSessionState.Step.Verifying(
|
step = OutgoingVerificationState.Step.Verifying(
|
||||||
data = aEmojisSessionVerificationData(),
|
data = aEmojisSessionVerificationData(),
|
||||||
state = AsyncData.Loading(),
|
state = AsyncData.Loading(),
|
||||||
),
|
),
|
||||||
|
|
@ -103,10 +103,10 @@ class VerifySelfSessionViewTest {
|
||||||
@Test
|
@Test
|
||||||
fun `back key pressed - on Completed exits the flow`() {
|
fun `back key pressed - on Completed exits the flow`() {
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setVerifySelfSessionView(
|
rule.setOutgoingVerificationView(
|
||||||
onBack = callback,
|
onBack = callback,
|
||||||
state = aVerifySelfSessionState(
|
state = anOutgoingVerificationState(
|
||||||
step = VerifySelfSessionState.Step.Completed,
|
step = OutgoingVerificationState.Step.Completed,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.pressBackKey()
|
rule.pressBackKey()
|
||||||
|
|
@ -115,11 +115,11 @@ class VerifySelfSessionViewTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when flow is completed and the user clicks on the continue button, the expected callback is invoked`() {
|
fun `when flow is completed and the user clicks on the continue button, the expected callback is invoked`() {
|
||||||
val eventsRecorder = EventsRecorder<VerifySelfSessionViewEvents>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<OutgoingVerificationViewEvents>(expectEvents = false)
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setVerifySelfSessionView(
|
rule.setOutgoingVerificationView(
|
||||||
aVerifySelfSessionState(
|
anOutgoingVerificationState(
|
||||||
step = VerifySelfSessionState.Step.Completed,
|
step = OutgoingVerificationState.Step.Completed,
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
),
|
),
|
||||||
onFinished = callback,
|
onFinished = callback,
|
||||||
|
|
@ -130,10 +130,10 @@ class VerifySelfSessionViewTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on they match emits the expected event`() {
|
fun `clicking on they match emits the expected event`() {
|
||||||
val eventsRecorder = EventsRecorder<VerifySelfSessionViewEvents>()
|
val eventsRecorder = EventsRecorder<OutgoingVerificationViewEvents>()
|
||||||
rule.setVerifySelfSessionView(
|
rule.setOutgoingVerificationView(
|
||||||
aVerifySelfSessionState(
|
anOutgoingVerificationState(
|
||||||
step = VerifySelfSessionState.Step.Verifying(
|
step = OutgoingVerificationState.Step.Verifying(
|
||||||
data = aEmojisSessionVerificationData(),
|
data = aEmojisSessionVerificationData(),
|
||||||
state = AsyncData.Uninitialized,
|
state = AsyncData.Uninitialized,
|
||||||
),
|
),
|
||||||
|
|
@ -141,15 +141,15 @@ class VerifySelfSessionViewTest {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.clickOn(R.string.screen_session_verification_they_match)
|
rule.clickOn(R.string.screen_session_verification_they_match)
|
||||||
eventsRecorder.assertSingle(VerifySelfSessionViewEvents.ConfirmVerification)
|
eventsRecorder.assertSingle(OutgoingVerificationViewEvents.ConfirmVerification)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on they do not match emits the expected event`() {
|
fun `clicking on they do not match emits the expected event`() {
|
||||||
val eventsRecorder = EventsRecorder<VerifySelfSessionViewEvents>()
|
val eventsRecorder = EventsRecorder<OutgoingVerificationViewEvents>()
|
||||||
rule.setVerifySelfSessionView(
|
rule.setOutgoingVerificationView(
|
||||||
aVerifySelfSessionState(
|
anOutgoingVerificationState(
|
||||||
step = VerifySelfSessionState.Step.Verifying(
|
step = OutgoingVerificationState.Step.Verifying(
|
||||||
data = aEmojisSessionVerificationData(),
|
data = aEmojisSessionVerificationData(),
|
||||||
state = AsyncData.Uninitialized,
|
state = AsyncData.Uninitialized,
|
||||||
),
|
),
|
||||||
|
|
@ -157,17 +157,17 @@ class VerifySelfSessionViewTest {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.clickOn(R.string.screen_session_verification_they_dont_match)
|
rule.clickOn(R.string.screen_session_verification_they_dont_match)
|
||||||
eventsRecorder.assertSingle(VerifySelfSessionViewEvents.DeclineVerification)
|
eventsRecorder.assertSingle(OutgoingVerificationViewEvents.DeclineVerification)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setVerifySelfSessionView(
|
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setOutgoingVerificationView(
|
||||||
state: VerifySelfSessionState,
|
state: OutgoingVerificationState,
|
||||||
onLearnMoreClick: () -> Unit = EnsureNeverCalled(),
|
onLearnMoreClick: () -> Unit = EnsureNeverCalled(),
|
||||||
onFinished: () -> Unit = EnsureNeverCalled(),
|
onFinished: () -> Unit = EnsureNeverCalled(),
|
||||||
onBack: () -> Unit = EnsureNeverCalled(),
|
onBack: () -> Unit = EnsureNeverCalled(),
|
||||||
) {
|
) {
|
||||||
setContent {
|
setContent {
|
||||||
VerifySelfSessionView(
|
OutgoingVerificationView(
|
||||||
state = state,
|
state = state,
|
||||||
onLearnMoreClick = onLearnMoreClick,
|
onLearnMoreClick = onLearnMoreClick,
|
||||||
onFinish = onFinished,
|
onFinish = onFinished,
|
||||||
|
|
@ -11,10 +11,10 @@ firebaseAppDistribution = "5.1.1"
|
||||||
|
|
||||||
# AndroidX
|
# AndroidX
|
||||||
core = "1.16.0"
|
core = "1.16.0"
|
||||||
datastore = "1.1.4"
|
datastore = "1.1.6"
|
||||||
constraintlayout = "2.2.1"
|
constraintlayout = "2.2.1"
|
||||||
constraintlayout_compose = "1.1.1"
|
constraintlayout_compose = "1.1.1"
|
||||||
lifecycle = "2.8.7"
|
lifecycle = "2.9.0"
|
||||||
activity = "1.10.1"
|
activity = "1.10.1"
|
||||||
media3 = "1.6.1"
|
media3 = "1.6.1"
|
||||||
camera = "1.4.2"
|
camera = "1.4.2"
|
||||||
|
|
@ -176,7 +176,7 @@ sqldelight-driver-android = { module = "app.cash.sqldelight:android-driver", ver
|
||||||
sqldelight-driver-jvm = { module = "app.cash.sqldelight:sqlite-driver", version.ref = "sqldelight" }
|
sqldelight-driver-jvm = { module = "app.cash.sqldelight:sqlite-driver", version.ref = "sqldelight" }
|
||||||
sqldelight-coroutines = { module = "app.cash.sqldelight:coroutines-extensions", version.ref = "sqldelight" }
|
sqldelight-coroutines = { module = "app.cash.sqldelight:coroutines-extensions", version.ref = "sqldelight" }
|
||||||
sqlcipher = "net.zetetic:android-database-sqlcipher:4.5.4"
|
sqlcipher = "net.zetetic:android-database-sqlcipher:4.5.4"
|
||||||
sqlite = "androidx.sqlite:sqlite-ktx:2.5.0"
|
sqlite = "androidx.sqlite:sqlite-ktx:2.5.1"
|
||||||
unifiedpush = "com.github.UnifiedPush:android-connector:2.4.0"
|
unifiedpush = "com.github.UnifiedPush:android-connector:2.4.0"
|
||||||
otaliastudios_transcoder = "com.otaliastudios:transcoder:0.11.2"
|
otaliastudios_transcoder = "com.otaliastudios:transcoder:0.11.2"
|
||||||
vanniktech_blurhash = "com.vanniktech:blurhash:0.3.0"
|
vanniktech_blurhash = "com.vanniktech:blurhash:0.3.0"
|
||||||
|
|
@ -207,7 +207,7 @@ anvil_compiler_api = { module = "dev.zacsweers.anvil:compiler-api", version.ref
|
||||||
anvil_compiler_utils = { module = "dev.zacsweers.anvil:compiler-utils", version.ref = "anvil" }
|
anvil_compiler_utils = { module = "dev.zacsweers.anvil:compiler-utils", version.ref = "anvil" }
|
||||||
|
|
||||||
# Element Call
|
# Element Call
|
||||||
element_call_embedded = "io.element.android:element-call-embedded:0.10.0"
|
element_call_embedded = "io.element.android:element-call-embedded:0.9.0"
|
||||||
|
|
||||||
# Auto services
|
# Auto services
|
||||||
google_autoservice = { module = "com.google.auto.service:auto-service", version.ref = "autoservice" }
|
google_autoservice = { module = "com.google.auto.service:auto-service", version.ref = "autoservice" }
|
||||||
|
|
|
||||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
|
|
@ -1,7 +1,7 @@
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionSha256Sum=20f1b1176237254a6fc204d8434196fa11a4cfb387567519c61556e8710aed78
|
distributionSha256Sum=61ad310d3c7d3e5da131b76bbf22b5a4c0786e9d892dae8c1658d4b484de3caa
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip
|
||||||
networkTimeout=10000
|
networkTimeout=10000
|
||||||
validateDistributionUrl=true
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
|
|
||||||
4
gradlew
vendored
4
gradlew
vendored
|
|
@ -114,7 +114,7 @@ case "$( uname )" in #(
|
||||||
NONSTOP* ) nonstop=true ;;
|
NONSTOP* ) nonstop=true ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
CLASSPATH="\\\"\\\""
|
||||||
|
|
||||||
|
|
||||||
# Determine the Java command to use to start the JVM.
|
# Determine the Java command to use to start the JVM.
|
||||||
|
|
@ -213,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
set -- \
|
set -- \
|
||||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||||
-classpath "$CLASSPATH" \
|
-classpath "$CLASSPATH" \
|
||||||
org.gradle.wrapper.GradleWrapperMain \
|
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||||
"$@"
|
"$@"
|
||||||
|
|
||||||
# Stop when "xargs" is not available.
|
# Stop when "xargs" is not available.
|
||||||
|
|
|
||||||
4
gradlew.bat
vendored
4
gradlew.bat
vendored
|
|
@ -70,11 +70,11 @@ goto fail
|
||||||
:execute
|
:execute
|
||||||
@rem Setup the command line
|
@rem Setup the command line
|
||||||
|
|
||||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
set CLASSPATH=
|
||||||
|
|
||||||
|
|
||||||
@rem Execute Gradle
|
@rem Execute Gradle
|
||||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
||||||
|
|
||||||
:end
|
:end
|
||||||
@rem End local scope for the variables with windows NT shell
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,6 @@ import androidx.core.text.util.LinkifyCompat
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import kotlin.collections.component1
|
import kotlin.collections.component1
|
||||||
import kotlin.collections.component2
|
import kotlin.collections.component2
|
||||||
import kotlin.collections.isNotEmpty
|
|
||||||
import kotlin.collections.iterator
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper class to linkify text while preserving existing URL spans.
|
* Helper class to linkify text while preserving existing URL spans.
|
||||||
|
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2023, 2024 New Vector Ltd.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
|
||||||
* Please see LICENSE files in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.element.android.libraries.designsystem.components.keyboard
|
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
|
||||||
import androidx.compose.foundation.layout.isImeVisible
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.State
|
|
||||||
import androidx.compose.runtime.rememberUpdatedState
|
|
||||||
import androidx.lifecycle.Lifecycle
|
|
||||||
import androidx.lifecycle.compose.LocalLifecycleOwner
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inspired from https://stackoverflow.com/questions/68847559/how-can-i-detect-keyboard-opening-and-closing-in-jetpack-compose
|
|
||||||
*/
|
|
||||||
enum class Keyboard {
|
|
||||||
Opened,
|
|
||||||
Closed
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note: it does not work as expected...
|
|
||||||
@OptIn(ExperimentalLayoutApi::class)
|
|
||||||
@Composable
|
|
||||||
fun keyboardAsState(): State<Keyboard> {
|
|
||||||
val lifecycle = LocalLifecycleOwner.current.lifecycle
|
|
||||||
val isResumed = lifecycle.currentState == Lifecycle.State.RESUMED
|
|
||||||
return rememberUpdatedState(if (WindowInsets.isImeVisible && isResumed) Keyboard.Opened else Keyboard.Closed)
|
|
||||||
}
|
|
||||||
|
|
@ -9,16 +9,22 @@ package io.element.android.libraries.push.impl.notifications
|
||||||
|
|
||||||
import com.squareup.anvil.annotations.ContributesBinding
|
import com.squareup.anvil.annotations.ContributesBinding
|
||||||
import io.element.android.libraries.di.AppScope
|
import io.element.android.libraries.di.AppScope
|
||||||
|
import io.element.android.libraries.matrix.api.MatrixClientProvider
|
||||||
import io.element.android.libraries.matrix.api.core.SessionId
|
import io.element.android.libraries.matrix.api.core.SessionId
|
||||||
|
import io.element.android.libraries.matrix.api.notification.CallNotifyType
|
||||||
import io.element.android.libraries.matrix.api.notification.NotificationContent
|
import io.element.android.libraries.matrix.api.notification.NotificationContent
|
||||||
import io.element.android.libraries.matrix.api.notification.NotificationData
|
import io.element.android.libraries.matrix.api.notification.NotificationData
|
||||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventType
|
import io.element.android.libraries.matrix.api.timeline.item.event.EventType
|
||||||
import io.element.android.libraries.push.impl.R
|
import io.element.android.libraries.push.impl.R
|
||||||
import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent
|
import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent
|
||||||
import io.element.android.libraries.push.impl.notifications.model.NotifiableRingingCallEvent
|
import io.element.android.libraries.push.impl.notifications.model.NotifiableRingingCallEvent
|
||||||
|
import io.element.android.services.appnavstate.api.AppForegroundStateService
|
||||||
import io.element.android.services.toolbox.api.strings.StringProvider
|
import io.element.android.services.toolbox.api.strings.StringProvider
|
||||||
|
import kotlinx.coroutines.flow.firstOrNull
|
||||||
|
import kotlinx.coroutines.withTimeoutOrNull
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper to resolve a valid [NotifiableEvent] from a [NotificationData].
|
* Helper to resolve a valid [NotifiableEvent] from a [NotificationData].
|
||||||
|
|
@ -31,7 +37,7 @@ interface CallNotificationEventResolver {
|
||||||
* @param forceNotify `true` to force the notification to be non-ringing, `false` to use the default behaviour. Default is `false`.
|
* @param forceNotify `true` to force the notification to be non-ringing, `false` to use the default behaviour. Default is `false`.
|
||||||
* @return a [NotifiableEvent] if the notification data is a call notification, null otherwise
|
* @return a [NotifiableEvent] if the notification data is a call notification, null otherwise
|
||||||
*/
|
*/
|
||||||
fun resolveEvent(
|
suspend fun resolveEvent(
|
||||||
sessionId: SessionId,
|
sessionId: SessionId,
|
||||||
notificationData: NotificationData,
|
notificationData: NotificationData,
|
||||||
forceNotify: Boolean = false,
|
forceNotify: Boolean = false,
|
||||||
|
|
@ -41,8 +47,10 @@ interface CallNotificationEventResolver {
|
||||||
@ContributesBinding(AppScope::class)
|
@ContributesBinding(AppScope::class)
|
||||||
class DefaultCallNotificationEventResolver @Inject constructor(
|
class DefaultCallNotificationEventResolver @Inject constructor(
|
||||||
private val stringProvider: StringProvider,
|
private val stringProvider: StringProvider,
|
||||||
|
private val appForegroundStateService: AppForegroundStateService,
|
||||||
|
private val clientProvider: MatrixClientProvider,
|
||||||
) : CallNotificationEventResolver {
|
) : CallNotificationEventResolver {
|
||||||
override fun resolveEvent(
|
override suspend fun resolveEvent(
|
||||||
sessionId: SessionId,
|
sessionId: SessionId,
|
||||||
notificationData: NotificationData,
|
notificationData: NotificationData,
|
||||||
forceNotify: Boolean
|
forceNotify: Boolean
|
||||||
|
|
@ -50,8 +58,32 @@ class DefaultCallNotificationEventResolver @Inject constructor(
|
||||||
val content = notificationData.content as? NotificationContent.MessageLike.CallNotify
|
val content = notificationData.content as? NotificationContent.MessageLike.CallNotify
|
||||||
?: throw ResolvingException("content is not a call notify")
|
?: throw ResolvingException("content is not a call notify")
|
||||||
|
|
||||||
|
val previousRingingCallStatus = appForegroundStateService.hasRingingCall.value
|
||||||
|
// We need the sync service working to get the updated room info
|
||||||
|
val isRoomCallActive = runCatching {
|
||||||
|
if (content.type == CallNotifyType.RING) {
|
||||||
|
appForegroundStateService.updateHasRingingCall(true)
|
||||||
|
|
||||||
|
val client = clientProvider.getOrRestore(sessionId).getOrNull() ?: throw ResolvingException("Session $sessionId not found")
|
||||||
|
val room = client.getRoom(notificationData.roomId) ?: throw ResolvingException("Room ${notificationData.roomId} not found")
|
||||||
|
// Give a few seconds for the room info flow to catch up with the sync, if needed - this is usually instant
|
||||||
|
val isActive = withTimeoutOrNull(3.seconds) { room.roomInfoFlow.firstOrNull { it.hasRoomCall } }?.hasRoomCall ?: false
|
||||||
|
|
||||||
|
// We no longer need the sync service to be active because of a call notification.
|
||||||
|
appForegroundStateService.updateHasRingingCall(previousRingingCallStatus)
|
||||||
|
|
||||||
|
isActive
|
||||||
|
} else {
|
||||||
|
// If the call notification is not of ringing type, we don't need to check if the call is active
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}.onFailure {
|
||||||
|
// Make sure to reset the hasRingingCall state in case of failure
|
||||||
|
appForegroundStateService.updateHasRingingCall(previousRingingCallStatus)
|
||||||
|
}.getOrDefault(false)
|
||||||
|
|
||||||
notificationData.run {
|
notificationData.run {
|
||||||
if (NotifiableRingingCallEvent.shouldRing(content.type, timestamp) && !forceNotify) {
|
if (content.type == CallNotifyType.RING && isRoomCallActive && !forceNotify) {
|
||||||
NotifiableRingingCallEvent(
|
NotifiableRingingCallEvent(
|
||||||
sessionId = sessionId,
|
sessionId = sessionId,
|
||||||
roomId = roomId,
|
roomId = roomId,
|
||||||
|
|
@ -70,9 +102,7 @@ class DefaultCallNotificationEventResolver @Inject constructor(
|
||||||
senderAvatarUrl = senderAvatarUrl,
|
senderAvatarUrl = senderAvatarUrl,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
val now = System.currentTimeMillis()
|
Timber.d("Event $eventId is call notify but should not ring: $isRoomCallActive, notify: ${content.type}")
|
||||||
val elapsed = now - timestamp
|
|
||||||
Timber.d("Event $eventId is call notify but should not ring: $timestamp vs $now ($elapsed ms elapsed), notify: ${content.type}")
|
|
||||||
// Create a simple message notification event
|
// Create a simple message notification event
|
||||||
buildNotifiableMessageEvent(
|
buildNotifiableMessageEvent(
|
||||||
sessionId = sessionId,
|
sessionId = sessionId,
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,6 @@ import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.libraries.matrix.api.core.SessionId
|
import io.element.android.libraries.matrix.api.core.SessionId
|
||||||
import io.element.android.libraries.matrix.api.core.UserId
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
import io.element.android.libraries.matrix.api.notification.CallNotifyType
|
import io.element.android.libraries.matrix.api.notification.CallNotifyType
|
||||||
import java.time.Instant
|
|
||||||
import kotlin.time.Duration.Companion.seconds
|
|
||||||
|
|
||||||
data class NotifiableRingingCallEvent(
|
data class NotifiableRingingCallEvent(
|
||||||
override val sessionId: SessionId,
|
override val sessionId: SessionId,
|
||||||
|
|
@ -31,13 +29,4 @@ data class NotifiableRingingCallEvent(
|
||||||
val roomAvatarUrl: String? = null,
|
val roomAvatarUrl: String? = null,
|
||||||
val callNotifyType: CallNotifyType,
|
val callNotifyType: CallNotifyType,
|
||||||
val timestamp: Long,
|
val timestamp: Long,
|
||||||
) : NotifiableEvent {
|
) : NotifiableEvent
|
||||||
companion object {
|
|
||||||
fun shouldRing(callNotifyType: CallNotifyType, timestamp: Long): Boolean {
|
|
||||||
val timeout = 10.seconds.inWholeMilliseconds
|
|
||||||
val elapsed = Instant.now().toEpochMilli() - timestamp
|
|
||||||
// Only ring if the type is RING and the elapsed time is less than the timeout
|
|
||||||
return callNotifyType == CallNotifyType.RING && elapsed < timeout
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,171 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.push.impl.notifications
|
||||||
|
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import io.element.android.libraries.matrix.api.notification.CallNotifyType
|
||||||
|
import io.element.android.libraries.matrix.api.notification.NotificationContent
|
||||||
|
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
||||||
|
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||||
|
import io.element.android.libraries.matrix.test.A_ROOM_NAME
|
||||||
|
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||||
|
import io.element.android.libraries.matrix.test.A_USER_ID_2
|
||||||
|
import io.element.android.libraries.matrix.test.A_USER_NAME_2
|
||||||
|
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||||
|
import io.element.android.libraries.matrix.test.FakeMatrixClientProvider
|
||||||
|
import io.element.android.libraries.matrix.test.notification.aNotificationData
|
||||||
|
import io.element.android.libraries.matrix.test.room.FakeBaseRoom
|
||||||
|
import io.element.android.libraries.matrix.test.room.FakeJoinedRoom
|
||||||
|
import io.element.android.libraries.matrix.test.room.aRoomInfo
|
||||||
|
import io.element.android.libraries.push.impl.notifications.model.NotifiableMessageEvent
|
||||||
|
import io.element.android.libraries.push.impl.notifications.model.NotifiableRingingCallEvent
|
||||||
|
import io.element.android.services.appnavstate.test.FakeAppForegroundStateService
|
||||||
|
import io.element.android.services.toolbox.test.strings.FakeStringProvider
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class DefaultCallNotificationEventResolverTest {
|
||||||
|
@Test
|
||||||
|
fun `resolve CallNotify - RING when call is still ongoing`() = runTest {
|
||||||
|
val room = FakeJoinedRoom(
|
||||||
|
baseRoom = FakeBaseRoom(
|
||||||
|
sessionId = A_SESSION_ID,
|
||||||
|
roomId = A_ROOM_ID,
|
||||||
|
// The call is still ongoing
|
||||||
|
initialRoomInfo = aRoomInfo(hasRoomCall = true),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val client = FakeMatrixClient().apply {
|
||||||
|
givenGetRoomResult(A_ROOM_ID, room)
|
||||||
|
}
|
||||||
|
|
||||||
|
val resolver = createDefaultNotifiableEventResolver(
|
||||||
|
clientProvider = FakeMatrixClientProvider(getClient = { Result.success(client) }),
|
||||||
|
)
|
||||||
|
val expectedResult = NotifiableRingingCallEvent(
|
||||||
|
sessionId = A_SESSION_ID,
|
||||||
|
roomId = A_ROOM_ID,
|
||||||
|
eventId = AN_EVENT_ID,
|
||||||
|
senderId = A_USER_ID_2,
|
||||||
|
roomName = A_ROOM_NAME,
|
||||||
|
editedEventId = null,
|
||||||
|
description = "📹 Incoming call",
|
||||||
|
timestamp = 567L,
|
||||||
|
canBeReplaced = true,
|
||||||
|
isRedacted = false,
|
||||||
|
isUpdated = false,
|
||||||
|
senderDisambiguatedDisplayName = A_USER_NAME_2,
|
||||||
|
senderAvatarUrl = null,
|
||||||
|
callNotifyType = CallNotifyType.RING,
|
||||||
|
)
|
||||||
|
|
||||||
|
val notificationData = aNotificationData(
|
||||||
|
content = NotificationContent.MessageLike.CallNotify(A_USER_ID_2, CallNotifyType.RING)
|
||||||
|
)
|
||||||
|
val result = resolver.resolveEvent(A_SESSION_ID, notificationData)
|
||||||
|
assertThat(result.getOrNull()).isEqualTo(expectedResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `resolve CallNotify - NOTIFY`() = runTest {
|
||||||
|
val room = FakeJoinedRoom(
|
||||||
|
baseRoom = FakeBaseRoom(
|
||||||
|
sessionId = A_SESSION_ID,
|
||||||
|
roomId = A_ROOM_ID,
|
||||||
|
// The call already ended
|
||||||
|
initialRoomInfo = aRoomInfo(hasRoomCall = true),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val client = FakeMatrixClient().apply {
|
||||||
|
givenGetRoomResult(A_ROOM_ID, room)
|
||||||
|
}
|
||||||
|
|
||||||
|
val resolver = createDefaultNotifiableEventResolver(
|
||||||
|
clientProvider = FakeMatrixClientProvider(getClient = { Result.success(client) }),
|
||||||
|
)
|
||||||
|
val expectedResult = NotifiableMessageEvent(
|
||||||
|
sessionId = A_SESSION_ID,
|
||||||
|
roomId = A_ROOM_ID,
|
||||||
|
eventId = AN_EVENT_ID,
|
||||||
|
senderId = A_USER_ID_2,
|
||||||
|
roomName = A_ROOM_NAME,
|
||||||
|
editedEventId = null,
|
||||||
|
body = "📹 Incoming call",
|
||||||
|
timestamp = 567L,
|
||||||
|
canBeReplaced = false,
|
||||||
|
isRedacted = false,
|
||||||
|
isUpdated = false,
|
||||||
|
senderDisambiguatedDisplayName = A_USER_NAME_2,
|
||||||
|
noisy = true,
|
||||||
|
imageUriString = null,
|
||||||
|
imageMimeType = null,
|
||||||
|
threadId = null,
|
||||||
|
type = "m.call.notify",
|
||||||
|
)
|
||||||
|
|
||||||
|
val notificationData = aNotificationData(
|
||||||
|
content = NotificationContent.MessageLike.CallNotify(A_USER_ID_2, CallNotifyType.NOTIFY)
|
||||||
|
)
|
||||||
|
val result = resolver.resolveEvent(A_SESSION_ID, notificationData)
|
||||||
|
assertThat(result.getOrNull()).isEqualTo(expectedResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `resolve CallNotify - RING but timed out displays the same as NOTIFY`() = runTest {
|
||||||
|
val room = FakeJoinedRoom(
|
||||||
|
baseRoom = FakeBaseRoom(
|
||||||
|
sessionId = A_SESSION_ID,
|
||||||
|
roomId = A_ROOM_ID,
|
||||||
|
// The call already ended
|
||||||
|
initialRoomInfo = aRoomInfo(hasRoomCall = false),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val client = FakeMatrixClient().apply {
|
||||||
|
givenGetRoomResult(A_ROOM_ID, room)
|
||||||
|
}
|
||||||
|
|
||||||
|
val resolver = createDefaultNotifiableEventResolver(
|
||||||
|
clientProvider = FakeMatrixClientProvider(getClient = { Result.success(client) }),
|
||||||
|
)
|
||||||
|
val expectedResult = NotifiableMessageEvent(
|
||||||
|
sessionId = A_SESSION_ID,
|
||||||
|
roomId = A_ROOM_ID,
|
||||||
|
eventId = AN_EVENT_ID,
|
||||||
|
senderId = A_USER_ID_2,
|
||||||
|
roomName = A_ROOM_NAME,
|
||||||
|
editedEventId = null,
|
||||||
|
body = "📹 Incoming call",
|
||||||
|
timestamp = 567L,
|
||||||
|
canBeReplaced = false,
|
||||||
|
isRedacted = false,
|
||||||
|
isUpdated = false,
|
||||||
|
senderDisambiguatedDisplayName = A_USER_NAME_2,
|
||||||
|
noisy = true,
|
||||||
|
imageUriString = null,
|
||||||
|
imageMimeType = null,
|
||||||
|
threadId = null,
|
||||||
|
type = "m.call.notify",
|
||||||
|
)
|
||||||
|
|
||||||
|
val notificationData = aNotificationData(
|
||||||
|
content = NotificationContent.MessageLike.CallNotify(A_USER_ID_2, CallNotifyType.RING)
|
||||||
|
)
|
||||||
|
val result = resolver.resolveEvent(A_SESSION_ID, notificationData)
|
||||||
|
assertThat(result.getOrNull()).isEqualTo(expectedResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createDefaultNotifiableEventResolver(
|
||||||
|
stringProvider: FakeStringProvider = FakeStringProvider(defaultResult = "\uD83D\uDCF9 Incoming call"),
|
||||||
|
appForegroundStateService: FakeAppForegroundStateService = FakeAppForegroundStateService(),
|
||||||
|
clientProvider: FakeMatrixClientProvider = FakeMatrixClientProvider(),
|
||||||
|
) = DefaultCallNotificationEventResolver(
|
||||||
|
stringProvider = stringProvider,
|
||||||
|
appForegroundStateService = appForegroundStateService,
|
||||||
|
clientProvider = clientProvider,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -49,10 +49,9 @@ import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiable
|
||||||
import io.element.android.libraries.push.impl.notifications.model.FallbackNotifiableEvent
|
import io.element.android.libraries.push.impl.notifications.model.FallbackNotifiableEvent
|
||||||
import io.element.android.libraries.push.impl.notifications.model.InviteNotifiableEvent
|
import io.element.android.libraries.push.impl.notifications.model.InviteNotifiableEvent
|
||||||
import io.element.android.libraries.push.impl.notifications.model.NotifiableMessageEvent
|
import io.element.android.libraries.push.impl.notifications.model.NotifiableMessageEvent
|
||||||
import io.element.android.libraries.push.impl.notifications.model.NotifiableRingingCallEvent
|
|
||||||
import io.element.android.libraries.push.impl.notifications.model.ResolvedPushEvent
|
import io.element.android.libraries.push.impl.notifications.model.ResolvedPushEvent
|
||||||
|
import io.element.android.libraries.push.test.notifications.FakeCallNotificationEventResolver
|
||||||
import io.element.android.services.toolbox.impl.strings.AndroidStringProvider
|
import io.element.android.services.toolbox.impl.strings.AndroidStringProvider
|
||||||
import io.element.android.services.toolbox.impl.systemclock.DefaultSystemClock
|
|
||||||
import io.element.android.services.toolbox.test.systemclock.A_FAKE_TIMESTAMP
|
import io.element.android.services.toolbox.test.systemclock.A_FAKE_TIMESTAMP
|
||||||
import io.element.android.services.toolbox.test.systemclock.FakeSystemClock
|
import io.element.android.services.toolbox.test.systemclock.FakeSystemClock
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
|
|
@ -606,80 +605,8 @@ class DefaultNotifiableEventResolverTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `resolve CallNotify - ringing`() = runTest {
|
fun `resolve CallNotify - goes through CallNotificationEventResolver`() = runTest {
|
||||||
val timestamp = DefaultSystemClock().epochMillis()
|
val callNotificationEventResolver = FakeCallNotificationEventResolver()
|
||||||
val sut = createDefaultNotifiableEventResolver(
|
|
||||||
notificationResult = Result.success(
|
|
||||||
aNotificationData(
|
|
||||||
content = NotificationContent.MessageLike.CallNotify(
|
|
||||||
A_USER_ID_2,
|
|
||||||
CallNotifyType.RING
|
|
||||||
),
|
|
||||||
timestamp = timestamp,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
val expectedResult = ResolvedPushEvent.Event(
|
|
||||||
NotifiableRingingCallEvent(
|
|
||||||
sessionId = A_SESSION_ID,
|
|
||||||
roomId = A_ROOM_ID,
|
|
||||||
eventId = AN_EVENT_ID,
|
|
||||||
senderId = A_USER_ID_2,
|
|
||||||
roomName = A_ROOM_NAME,
|
|
||||||
editedEventId = null,
|
|
||||||
description = "📹 Incoming call",
|
|
||||||
timestamp = timestamp,
|
|
||||||
canBeReplaced = true,
|
|
||||||
isRedacted = false,
|
|
||||||
isUpdated = false,
|
|
||||||
senderDisambiguatedDisplayName = A_USER_NAME_2,
|
|
||||||
senderAvatarUrl = null,
|
|
||||||
callNotifyType = CallNotifyType.RING,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
val result = sut.resolveEvent(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID)
|
|
||||||
assertThat(result.getOrNull()).isEqualTo(expectedResult)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `resolve CallNotify - ring but timed out displays the same as notify`() = runTest {
|
|
||||||
val sut = createDefaultNotifiableEventResolver(
|
|
||||||
notificationResult = Result.success(
|
|
||||||
aNotificationData(
|
|
||||||
content = NotificationContent.MessageLike.CallNotify(
|
|
||||||
A_USER_ID_2,
|
|
||||||
CallNotifyType.RING
|
|
||||||
),
|
|
||||||
timestamp = 0L,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
val expectedResult = ResolvedPushEvent.Event(
|
|
||||||
NotifiableMessageEvent(
|
|
||||||
sessionId = A_SESSION_ID,
|
|
||||||
eventId = AN_EVENT_ID,
|
|
||||||
editedEventId = null,
|
|
||||||
noisy = true,
|
|
||||||
timestamp = 0L,
|
|
||||||
senderDisambiguatedDisplayName = A_USER_NAME_2,
|
|
||||||
senderId = A_USER_ID_2,
|
|
||||||
body = "📹 Incoming call",
|
|
||||||
roomId = A_ROOM_ID,
|
|
||||||
threadId = null,
|
|
||||||
roomName = A_ROOM_NAME,
|
|
||||||
canBeReplaced = false,
|
|
||||||
isRedacted = false,
|
|
||||||
imageUriString = null,
|
|
||||||
imageMimeType = null,
|
|
||||||
type = EventType.CALL_NOTIFY,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
val result = sut.resolveEvent(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID)
|
|
||||||
assertThat(result.getOrNull()).isEqualTo(expectedResult)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `resolve CallNotify - notify`() = runTest {
|
|
||||||
val sut = createDefaultNotifiableEventResolver(
|
val sut = createDefaultNotifiableEventResolver(
|
||||||
notificationResult = Result.success(
|
notificationResult = Result.success(
|
||||||
aNotificationData(
|
aNotificationData(
|
||||||
|
|
@ -688,7 +615,8 @@ class DefaultNotifiableEventResolverTest {
|
||||||
CallNotifyType.NOTIFY
|
CallNotifyType.NOTIFY
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
)
|
),
|
||||||
|
callNotificationEventResolver = callNotificationEventResolver,
|
||||||
)
|
)
|
||||||
val expectedResult = ResolvedPushEvent.Event(
|
val expectedResult = ResolvedPushEvent.Event(
|
||||||
NotifiableMessageEvent(
|
NotifiableMessageEvent(
|
||||||
|
|
@ -710,6 +638,7 @@ class DefaultNotifiableEventResolverTest {
|
||||||
type = EventType.CALL_NOTIFY,
|
type = EventType.CALL_NOTIFY,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
callNotificationEventResolver.resolveEventLambda = { _, _, _ -> Result.success(expectedResult.notifiableEvent) }
|
||||||
val result = sut.resolveEvent(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID)
|
val result = sut.resolveEvent(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID)
|
||||||
assertThat(result.getOrNull()).isEqualTo(expectedResult)
|
assertThat(result.getOrNull()).isEqualTo(expectedResult)
|
||||||
}
|
}
|
||||||
|
|
@ -804,6 +733,7 @@ class DefaultNotifiableEventResolverTest {
|
||||||
notificationService: FakeNotificationService? = FakeNotificationService(),
|
notificationService: FakeNotificationService? = FakeNotificationService(),
|
||||||
notificationResult: Result<NotificationData?> = Result.success(null),
|
notificationResult: Result<NotificationData?> = Result.success(null),
|
||||||
appPreferencesStore: AppPreferencesStore = InMemoryAppPreferencesStore(),
|
appPreferencesStore: AppPreferencesStore = InMemoryAppPreferencesStore(),
|
||||||
|
callNotificationEventResolver: FakeCallNotificationEventResolver = FakeCallNotificationEventResolver(),
|
||||||
): DefaultNotifiableEventResolver {
|
): DefaultNotifiableEventResolver {
|
||||||
val context = RuntimeEnvironment.getApplication() as Context
|
val context = RuntimeEnvironment.getApplication() as Context
|
||||||
notificationService?.givenGetNotificationResult(notificationResult)
|
notificationService?.givenGetNotificationResult(notificationResult)
|
||||||
|
|
@ -824,9 +754,7 @@ class DefaultNotifiableEventResolverTest {
|
||||||
notificationMediaRepoFactory = notificationMediaRepoFactory,
|
notificationMediaRepoFactory = notificationMediaRepoFactory,
|
||||||
context = context,
|
context = context,
|
||||||
permalinkParser = FakePermalinkParser(),
|
permalinkParser = FakePermalinkParser(),
|
||||||
callNotificationEventResolver = DefaultCallNotificationEventResolver(
|
callNotificationEventResolver = callNotificationEventResolver,
|
||||||
stringProvider = AndroidStringProvider(context.resources)
|
|
||||||
),
|
|
||||||
appPreferencesStore = appPreferencesStore,
|
appPreferencesStore = appPreferencesStore,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ class FakeCallNotificationEventResolver(
|
||||||
lambdaError()
|
lambdaError()
|
||||||
},
|
},
|
||||||
) : CallNotificationEventResolver {
|
) : CallNotificationEventResolver {
|
||||||
override fun resolveEvent(sessionId: SessionId, notificationData: NotificationData, forceNotify: Boolean): Result<NotifiableEvent> {
|
override suspend fun resolveEvent(sessionId: SessionId, notificationData: NotificationData, forceNotify: Boolean): Result<NotifiableEvent> {
|
||||||
return resolveEventLambda(sessionId, notificationData, forceNotify)
|
return resolveEventLambda(sessionId, notificationData, forceNotify)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="a11y_avatar">"Аватар"</string>
|
||||||
<string name="a11y_delete">"Выдаліць"</string>
|
<string name="a11y_delete">"Выдаліць"</string>
|
||||||
<plurals name="a11y_digits_entered">
|
<plurals name="a11y_digits_entered">
|
||||||
<item quantity="one">"Уведзеная лічба %1$d"</item>
|
<item quantity="one">"Уведзеная лічба %1$d"</item>
|
||||||
|
|
@ -7,6 +8,7 @@
|
||||||
<item quantity="many">"Уведзена %1$d лічб"</item>
|
<item quantity="many">"Уведзена %1$d лічб"</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="a11y_hide_password">"Схаваць пароль"</string>
|
<string name="a11y_hide_password">"Схаваць пароль"</string>
|
||||||
|
<string name="a11y_join_call">"Далучыцца да выкліку"</string>
|
||||||
<string name="a11y_jump_to_bottom">"Перайсці ўніз"</string>
|
<string name="a11y_jump_to_bottom">"Перайсці ўніз"</string>
|
||||||
<string name="a11y_notifications_mentions_only">"Толькі згадкі"</string>
|
<string name="a11y_notifications_mentions_only">"Толькі згадкі"</string>
|
||||||
<string name="a11y_notifications_muted">"Гук адключаны"</string>
|
<string name="a11y_notifications_muted">"Гук адключаны"</string>
|
||||||
|
|
@ -31,6 +33,7 @@
|
||||||
<string name="a11y_show_password">"Паказаць пароль"</string>
|
<string name="a11y_show_password">"Паказаць пароль"</string>
|
||||||
<string name="a11y_start_call">"Пазваніць"</string>
|
<string name="a11y_start_call">"Пазваніць"</string>
|
||||||
<string name="a11y_user_menu">"Меню карыстальніка"</string>
|
<string name="a11y_user_menu">"Меню карыстальніка"</string>
|
||||||
|
<string name="a11y_view_details">"Паглядзець падрабязнасці"</string>
|
||||||
<string name="a11y_voice_message_record">"Запісаць галасавое паведамленне."</string>
|
<string name="a11y_voice_message_record">"Запісаць галасавое паведамленне."</string>
|
||||||
<string name="a11y_voice_message_stop_recording">"Спыніць запіс"</string>
|
<string name="a11y_voice_message_stop_recording">"Спыніць запіс"</string>
|
||||||
<string name="action_accept">"Прыняць"</string>
|
<string name="action_accept">"Прыняць"</string>
|
||||||
|
|
@ -57,6 +60,7 @@
|
||||||
<string name="action_delete_poll">"Выдаліць апытанне"</string>
|
<string name="action_delete_poll">"Выдаліць апытанне"</string>
|
||||||
<string name="action_disable">"Адключыць"</string>
|
<string name="action_disable">"Адключыць"</string>
|
||||||
<string name="action_discard">"Адмяніць"</string>
|
<string name="action_discard">"Адмяніць"</string>
|
||||||
|
<string name="action_dismiss">"Aдхіліць"</string>
|
||||||
<string name="action_done">"Гатова"</string>
|
<string name="action_done">"Гатова"</string>
|
||||||
<string name="action_edit">"Рэдагаваць"</string>
|
<string name="action_edit">"Рэдагаваць"</string>
|
||||||
<string name="action_edit_poll">"Рэдагаваць апытанне"</string>
|
<string name="action_edit_poll">"Рэдагаваць апытанне"</string>
|
||||||
|
|
|
||||||
|
|
@ -111,6 +111,7 @@
|
||||||
<string name="action_report">"Отчет"</string>
|
<string name="action_report">"Отчет"</string>
|
||||||
<string name="action_report_bug">"Сообщить об ошибке"</string>
|
<string name="action_report_bug">"Сообщить об ошибке"</string>
|
||||||
<string name="action_report_content">"Пожаловаться на содержание"</string>
|
<string name="action_report_content">"Пожаловаться на содержание"</string>
|
||||||
|
<string name="action_report_dm">"Пожаловаться на беседу"</string>
|
||||||
<string name="action_report_room">"Комната отчетов"</string>
|
<string name="action_report_room">"Комната отчетов"</string>
|
||||||
<string name="action_reset">"Сбросить"</string>
|
<string name="action_reset">"Сбросить"</string>
|
||||||
<string name="action_reset_identity">"Сбросить идентификацию"</string>
|
<string name="action_reset_identity">"Сбросить идентификацию"</string>
|
||||||
|
|
@ -264,6 +265,7 @@
|
||||||
<string name="common_shared_location">"Поделился местоположением"</string>
|
<string name="common_shared_location">"Поделился местоположением"</string>
|
||||||
<string name="common_signing_out">"Выход…"</string>
|
<string name="common_signing_out">"Выход…"</string>
|
||||||
<string name="common_something_went_wrong">"Что-то пошло не так"</string>
|
<string name="common_something_went_wrong">"Что-то пошло не так"</string>
|
||||||
|
<string name="common_something_went_wrong_message">"Мы столкнулись с проблемой. Пожалуйста, попробуйте еще раз."</string>
|
||||||
<string name="common_starting_chat">"Чат запускается…"</string>
|
<string name="common_starting_chat">"Чат запускается…"</string>
|
||||||
<string name="common_sticker">"Стикер"</string>
|
<string name="common_sticker">"Стикер"</string>
|
||||||
<string name="common_success">"Успешно"</string>
|
<string name="common_success">"Успешно"</string>
|
||||||
|
|
|
||||||
|
|
@ -351,6 +351,11 @@ Are you sure you want to continue?"</string>
|
||||||
<string name="screen_bottom_sheet_manage_room_member_remove">"Remove from room"</string>
|
<string name="screen_bottom_sheet_manage_room_member_remove">"Remove from room"</string>
|
||||||
<string name="screen_bottom_sheet_manage_room_member_remove_confirmation_title">"Remove member and ban from joining in the future?"</string>
|
<string name="screen_bottom_sheet_manage_room_member_remove_confirmation_title">"Remove member and ban from joining in the future?"</string>
|
||||||
<string name="screen_bottom_sheet_manage_room_member_removing_user">"Removing %1$s…"</string>
|
<string name="screen_bottom_sheet_manage_room_member_removing_user">"Removing %1$s…"</string>
|
||||||
|
<string name="screen_bottom_sheet_manage_room_member_unban">"Unban from room"</string>
|
||||||
|
<string name="screen_bottom_sheet_manage_room_member_unban_member_confirmation_action">"Unban"</string>
|
||||||
|
<string name="screen_bottom_sheet_manage_room_member_unban_member_confirmation_description">"They would be able to join the room again if invited"</string>
|
||||||
|
<string name="screen_bottom_sheet_manage_room_member_unban_member_confirmation_title">"Are you sure you want to unban this member?"</string>
|
||||||
|
<string name="screen_bottom_sheet_manage_room_member_unbanning_user">"Unbanning $1%s"</string>
|
||||||
<string name="screen_media_picker_error_failed_selection">"Failed selecting media, please try again."</string>
|
<string name="screen_media_picker_error_failed_selection">"Failed selecting media, please try again."</string>
|
||||||
<string name="screen_media_upload_preview_caption_warning">"Captions might not be visible to people using older apps."</string>
|
<string name="screen_media_upload_preview_caption_warning">"Captions might not be visible to people using older apps."</string>
|
||||||
<string name="screen_media_upload_preview_error_failed_processing">"Failed processing media to upload, please try again."</string>
|
<string name="screen_media_upload_preview_error_failed_processing">"Failed processing media to upload, please try again."</string>
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ private const val versionYear = 25
|
||||||
private const val versionMonth = 5
|
private const val versionMonth = 5
|
||||||
|
|
||||||
// Note: must be in [0,99]
|
// Note: must be in [0,99]
|
||||||
private const val versionReleaseNumber = 1
|
private const val versionReleaseNumber = 2
|
||||||
|
|
||||||
object Versions {
|
object Versions {
|
||||||
const val VERSION_CODE = (2000 + versionYear) * 10_000 + versionMonth * 100 + versionReleaseNumber
|
const val VERSION_CODE = (2000 + versionYear) * 10_000 + versionMonth * 100 + versionReleaseNumber
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:ac3a523da98d3cf64cd908b83824d75e060b14cb581b4015a75e943524547048
|
||||||
|
size 16399
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -27,8 +27,6 @@ import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlin.collections.component1
|
import kotlin.collections.component1
|
||||||
import kotlin.collections.component2
|
import kotlin.collections.component2
|
||||||
import kotlin.collections.iterator
|
|
||||||
import kotlin.collections.orEmpty
|
|
||||||
|
|
||||||
@ContributesMultibinding(AppScope::class)
|
@ContributesMultibinding(AppScope::class)
|
||||||
class SentryAnalyticsProvider @Inject constructor(
|
class SentryAnalyticsProvider @Inject constructor(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue