Move migration screen to within the room list (#2361)
* Rename migration bg drawable and add night variant * Move `migration` package from `ftue` to `messages:impl` module * Update `SunsetPage` with light and dark modes * Fix bloom colors when nested theme is used * Integrate the migration screen in the room list * Fix `WaitListView` cancel button color * Clear migration store when removing the app's cache --------- Co-authored-by: ElementBot <benoitm+elementbot@element.io>
This commit is contained in:
parent
52dfaea741
commit
d06e5c23cb
62 changed files with 274 additions and 291 deletions
1
changelog.d/2310.misc
Normal file
1
changelog.d/2310.misc
Normal file
|
|
@ -0,0 +1 @@
|
|||
Move migration screen to within the room list
|
||||
|
|
@ -33,7 +33,6 @@ import dagger.assisted.AssistedInject
|
|||
import io.element.android.anvilannotations.ContributesNode
|
||||
import io.element.android.features.analytics.api.AnalyticsEntryPoint
|
||||
import io.element.android.features.ftue.api.FtueEntryPoint
|
||||
import io.element.android.features.ftue.impl.migration.MigrationScreenNode
|
||||
import io.element.android.features.ftue.impl.notifications.NotificationsOptInNode
|
||||
import io.element.android.features.ftue.impl.state.DefaultFtueState
|
||||
import io.element.android.features.ftue.impl.state.FtueStep
|
||||
|
|
@ -74,9 +73,6 @@ class FtueFlowNode @AssistedInject constructor(
|
|||
@Parcelize
|
||||
data object Placeholder : NavTarget
|
||||
|
||||
@Parcelize
|
||||
data object MigrationScreen : NavTarget
|
||||
|
||||
@Parcelize
|
||||
data object WelcomeScreen : NavTarget
|
||||
|
||||
|
|
@ -114,14 +110,6 @@ class FtueFlowNode @AssistedInject constructor(
|
|||
NavTarget.Placeholder -> {
|
||||
createNode<PlaceholderNode>(buildContext)
|
||||
}
|
||||
NavTarget.MigrationScreen -> {
|
||||
val callback = object : MigrationScreenNode.Callback {
|
||||
override fun onMigrationFinished() {
|
||||
lifecycleScope.launch { moveToNextStep() }
|
||||
}
|
||||
}
|
||||
createNode<MigrationScreenNode>(buildContext, listOf(callback))
|
||||
}
|
||||
NavTarget.WelcomeScreen -> {
|
||||
val callback = object : WelcomeNode.Callback {
|
||||
override fun onContinueClicked() {
|
||||
|
|
@ -158,9 +146,6 @@ class FtueFlowNode @AssistedInject constructor(
|
|||
|
||||
private fun moveToNextStep() {
|
||||
when (ftueState.getNextStep()) {
|
||||
FtueStep.MigrationScreen -> {
|
||||
backstack.newRoot(NavTarget.MigrationScreen)
|
||||
}
|
||||
FtueStep.WelcomeScreen -> {
|
||||
backstack.newRoot(NavTarget.WelcomeScreen)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,52 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2023 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.features.ftue.impl.migration
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.node.Node
|
||||
import com.bumble.appyx.core.plugin.Plugin
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import io.element.android.anvilannotations.ContributesNode
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
|
||||
@ContributesNode(SessionScope::class)
|
||||
class MigrationScreenNode @AssistedInject constructor(
|
||||
@Assisted buildContext: BuildContext,
|
||||
@Assisted plugins: List<Plugin>,
|
||||
private val presenter: MigrationScreenPresenter,
|
||||
) : Node(buildContext, plugins = plugins) {
|
||||
interface Callback : Plugin {
|
||||
fun onMigrationFinished()
|
||||
}
|
||||
|
||||
private fun onMigrationFinished() {
|
||||
plugins.filterIsInstance<Callback>().forEach { it.onMigrationFinished() }
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
val state = presenter.present()
|
||||
MigrationScreenView(
|
||||
state,
|
||||
onMigrationFinished = ::onMigrationFinished,
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -21,11 +21,9 @@ import android.os.Build
|
|||
import androidx.annotation.VisibleForTesting
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.features.ftue.api.state.FtueState
|
||||
import io.element.android.features.ftue.impl.migration.MigrationScreenStore
|
||||
import io.element.android.features.ftue.impl.welcome.state.WelcomeScreenState
|
||||
import io.element.android.features.lockscreen.api.LockScreenService
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.permissions.api.PermissionStateProvider
|
||||
import io.element.android.services.analytics.api.AnalyticsService
|
||||
import io.element.android.services.toolbox.api.sdk.BuildVersionSdkIntProvider
|
||||
|
|
@ -43,17 +41,14 @@ class DefaultFtueState @Inject constructor(
|
|||
coroutineScope: CoroutineScope,
|
||||
private val analyticsService: AnalyticsService,
|
||||
private val welcomeScreenState: WelcomeScreenState,
|
||||
private val migrationScreenStore: MigrationScreenStore,
|
||||
private val permissionStateProvider: PermissionStateProvider,
|
||||
private val lockScreenService: LockScreenService,
|
||||
private val matrixClient: MatrixClient,
|
||||
) : FtueState {
|
||||
override val shouldDisplayFlow = MutableStateFlow(isAnyStepIncomplete())
|
||||
|
||||
override suspend fun reset() {
|
||||
welcomeScreenState.reset()
|
||||
analyticsService.reset()
|
||||
migrationScreenStore.reset()
|
||||
if (sdkVersionProvider.isAtLeast(Build.VERSION_CODES.TIRAMISU)) {
|
||||
permissionStateProvider.resetPermission(Manifest.permission.POST_NOTIFICATIONS)
|
||||
}
|
||||
|
|
@ -67,12 +62,7 @@ class DefaultFtueState @Inject constructor(
|
|||
|
||||
fun getNextStep(currentStep: FtueStep? = null): FtueStep? =
|
||||
when (currentStep) {
|
||||
null -> if (shouldDisplayMigrationScreen()) {
|
||||
FtueStep.MigrationScreen
|
||||
} else {
|
||||
getNextStep(FtueStep.MigrationScreen)
|
||||
}
|
||||
FtueStep.MigrationScreen -> if (shouldDisplayWelcomeScreen()) {
|
||||
null -> if (shouldDisplayWelcomeScreen()) {
|
||||
FtueStep.WelcomeScreen
|
||||
} else {
|
||||
getNextStep(FtueStep.WelcomeScreen)
|
||||
|
|
@ -97,7 +87,6 @@ class DefaultFtueState @Inject constructor(
|
|||
|
||||
private fun isAnyStepIncomplete(): Boolean {
|
||||
return listOf(
|
||||
{ shouldDisplayMigrationScreen() },
|
||||
{ shouldDisplayWelcomeScreen() },
|
||||
{ shouldAskNotificationPermissions() },
|
||||
{ needsAnalyticsOptIn() },
|
||||
|
|
@ -105,10 +94,6 @@ class DefaultFtueState @Inject constructor(
|
|||
).any { it() }
|
||||
}
|
||||
|
||||
private fun shouldDisplayMigrationScreen(): Boolean {
|
||||
return migrationScreenStore.isMigrationScreenNeeded(matrixClient.sessionId)
|
||||
}
|
||||
|
||||
private fun needsAnalyticsOptIn(): Boolean {
|
||||
// We need this function to not be suspend, so we need to load the value through runBlocking
|
||||
return runBlocking { analyticsService.didAskUserConsent().first().not() }
|
||||
|
|
@ -147,7 +132,6 @@ class DefaultFtueState @Inject constructor(
|
|||
}
|
||||
|
||||
sealed interface FtueStep {
|
||||
data object MigrationScreen : FtueStep
|
||||
data object WelcomeScreen : FtueStep
|
||||
data object NotificationsOptIn : FtueStep
|
||||
data object AnalyticsOptIn : FtueStep
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_migration_message">"Гэта аднаразовы працэс, дзякуем за чаканне."</string>
|
||||
<string name="screen_migration_title">"Налада ўліковага запісу."</string>
|
||||
<string name="screen_notification_optin_subtitle">"Вы можаце змяніць налады пазней."</string>
|
||||
<string name="screen_notification_optin_title">"Дазвольце апавяшчэнні і ніколі не прапускайце іх"</string>
|
||||
<string name="screen_welcome_bullet_1">"Званкі, апытанні, пошук і многае іншае будзе дададзена пазней у гэтым годзе."</string>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_migration_message">"Jedná se o jednorázový proces, prosíme o strpení."</string>
|
||||
<string name="screen_migration_title">"Nastavení vašeho účtu"</string>
|
||||
<string name="screen_notification_optin_subtitle">"Nastavení můžete později změnit."</string>
|
||||
<string name="screen_notification_optin_title">"Povolte oznámení a nezmeškejte žádnou zprávu"</string>
|
||||
<string name="screen_welcome_bullet_1">"Hovory, hlasování, vyhledávání a další budou přidány koncem tohoto roku."</string>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_migration_message">"Dies ist ein einmaliger Vorgang, danke fürs Warten."</string>
|
||||
<string name="screen_migration_title">"Dein Konto wird eingerichtet."</string>
|
||||
<string name="screen_notification_optin_subtitle">"Du kannst deine Einstellungen später ändern."</string>
|
||||
<string name="screen_notification_optin_title">"Erlaube Benachrichtigungen und verpasse keine Nachricht"</string>
|
||||
<string name="screen_welcome_bullet_1">"Anrufe, Umfragen, Suchfunktionen und mehr werden im Laufe des Jahres hinzugefügt."</string>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_migration_message">"Este proceso solo se hace una vez, gracias por esperar."</string>
|
||||
<string name="screen_migration_title">"Configura tu cuenta"</string>
|
||||
<string name="screen_notification_optin_subtitle">"Puedes cambiar la configuración más tarde."</string>
|
||||
<string name="screen_notification_optin_title">"Activa las notificaciones y nunca te pierdas un mensaje"</string>
|
||||
<string name="screen_welcome_bullet_1">"Las llamadas, las encuestas, la búsqueda y más se agregarán más adelante este año."</string>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_migration_message">"Il s’agit d’une opération ponctuelle, merci d’attendre quelques instants."</string>
|
||||
<string name="screen_migration_title">"Configuration de votre compte."</string>
|
||||
<string name="screen_notification_optin_subtitle">"Vous pourrez modifier vos paramètres ultérieurement."</string>
|
||||
<string name="screen_notification_optin_subtitle">"Vous pourrez modifier vos paramètres ultérieurement."</string>
|
||||
<string name="screen_notification_optin_title">"Autorisez les notifications et ne manquez aucun message"</string>
|
||||
<string name="screen_welcome_bullet_1">"Les appels, les sondages, les recherches et plus encore seront ajoutés plus tard cette année."</string>
|
||||
<string name="screen_welcome_bullet_2">"L’historique des messages pour les salons chiffrés ne sera pas disponible dans cette mise à jour."</string>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_migration_message">"Ez egy egyszeri folyamat, köszönjük a türelmét."</string>
|
||||
<string name="screen_migration_title">"A fiók beállítása."</string>
|
||||
<string name="screen_notification_optin_subtitle">"A beállításokat később is módosíthatja."</string>
|
||||
<string name="screen_notification_optin_subtitle">"A beállításokat később is módosíthatja."</string>
|
||||
<string name="screen_notification_optin_title">"Értesítések engedélyezése, hogy soha ne maradjon le egyetlen üzenetről sem"</string>
|
||||
<string name="screen_welcome_bullet_1">"A hívások, szavazások, keresések és egyebek az év további részében kerülnek hozzáadásra."</string>
|
||||
<string name="screen_welcome_bullet_2">"A titkosított szobák üzenetelőzményei nem lesznek elérhetők ebben a frissítésben."</string>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_migration_message">"Ini adalah proses satu kali, terima kasih telah menunggu."</string>
|
||||
<string name="screen_migration_title">"Menyiapkan akun Anda."</string>
|
||||
<string name="screen_notification_optin_subtitle">"Anda dapat mengubah pengaturan Anda nanti."</string>
|
||||
<string name="screen_notification_optin_subtitle">"Anda dapat mengubah pengaturan Anda nanti."</string>
|
||||
<string name="screen_notification_optin_title">"Izinkan pemberitahuan dan jangan pernah melewatkan pesan"</string>
|
||||
<string name="screen_welcome_bullet_1">"Panggilan, pemungutan suara, pencarian, dan lainnya akan ditambahkan di tahun ini."</string>
|
||||
<string name="screen_welcome_bullet_2">"Riwayat pesan untuk ruangan terenkripsi tidak akan tersedia dalam pembaruan ini."</string>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_migration_message">"Si tratta di una procedura che si effettua una sola volta, grazie per l\'attesa."</string>
|
||||
<string name="screen_migration_title">"Configurazione del tuo account."</string>
|
||||
<string name="screen_notification_optin_subtitle">"Potrai modificare le tue impostazioni in seguito."</string>
|
||||
<string name="screen_notification_optin_subtitle">"Potrai modificare le tue impostazioni in seguito."</string>
|
||||
<string name="screen_notification_optin_title">"Consenti le notifiche e non perdere mai un messaggio"</string>
|
||||
<string name="screen_welcome_bullet_1">"Chiamate, sondaggi, ricerche e altro ancora saranno aggiunti nel corso dell\'anno."</string>
|
||||
<string name="screen_welcome_bullet_2">"La cronologia dei messaggi per le stanze crittografate non è ancora disponibile."</string>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_migration_message">"Acesta este un proces care se desfășoară o singură dată, vă mulțumim pentru așteptare."</string>
|
||||
<string name="screen_migration_title">"Contul dumneavoastră se configurează"</string>
|
||||
<string name="screen_welcome_bullet_1">"Apelurile, sondajele, căutare și multe altele vor fi adăugate în cursul acestui an."</string>
|
||||
<string name="screen_welcome_bullet_1">"Apelurile, sondajele, căutare și multe altele vor fi adăugate în cursul acestui an."</string>
|
||||
<string name="screen_welcome_bullet_2">"Istoricul mesajelor pentru camerele criptate nu va fi disponibil în această actualizare."</string>
|
||||
<string name="screen_welcome_bullet_3">"Ne-ar plăcea să auzim de la dumneavoastră, spuneți-ne ce părere aveți prin intermediul paginii de setări."</string>
|
||||
<string name="screen_welcome_button">"Să începem!"</string>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_migration_message">"Это одноразовый процесс, спасибо, что подождали."</string>
|
||||
<string name="screen_migration_title">"Настройка учетной записи."</string>
|
||||
<string name="screen_notification_optin_subtitle">"Вы можете изменить настройки позже."</string>
|
||||
<string name="screen_notification_optin_subtitle">"Вы можете изменить настройки позже."</string>
|
||||
<string name="screen_notification_optin_title">"Разрешите уведомления и никогда не пропустите сообщение"</string>
|
||||
<string name="screen_welcome_bullet_1">"Звонки, опросы, поиск и многое другое будут добавлены позже в этом году."</string>
|
||||
<string name="screen_welcome_bullet_2">"История сообщений для зашифрованных комнат в этом обновлении будет недоступна."</string>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_migration_message">"Ide o jednorazový proces, ďakujeme za trpezlivosť."</string>
|
||||
<string name="screen_migration_title">"Nastavenie vášho účtu."</string>
|
||||
<string name="screen_notification_optin_subtitle">"Svoje nastavenia môžete neskôr zmeniť."</string>
|
||||
<string name="screen_notification_optin_subtitle">"Svoje nastavenia môžete neskôr zmeniť."</string>
|
||||
<string name="screen_notification_optin_title">"Povoľte oznámenia a nikdy nezmeškajte žiadnu správu"</string>
|
||||
<string name="screen_welcome_bullet_1">"Hovory, ankety, vyhľadávanie a ďalšie funkcie pribudnú neskôr v tomto roku."</string>
|
||||
<string name="screen_welcome_bullet_2">"História správ pre zašifrované miestnosti nebude v tejto aktualizácii k dispozícii."</string>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_migration_message">"這是一次性的程序,感謝您耐心等候。"</string>
|
||||
<string name="screen_migration_title">"正在設定您的帳號。"</string>
|
||||
<string name="screen_welcome_bullet_1">"通話、投票、搜尋等更多功能將在今年登場。"</string>
|
||||
<string name="screen_welcome_bullet_1">"通話、投票、搜尋等更多功能將在今年登場。"</string>
|
||||
<string name="screen_welcome_bullet_2">"在這次的更新,您無法查看聊天室內被加密的歷史訊息。"</string>
|
||||
<string name="screen_welcome_bullet_3">"我們很樂意聽取您的意見,請到設定頁面告訴我們您的想法。"</string>
|
||||
<string name="screen_welcome_button">"開始吧!"</string>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_migration_message">"This is a one time process, thanks for waiting."</string>
|
||||
<string name="screen_migration_title">"Setting up your account."</string>
|
||||
<string name="screen_notification_optin_subtitle">"You can change your settings later."</string>
|
||||
<string name="screen_notification_optin_title">"Allow notifications and never miss a message"</string>
|
||||
<string name="screen_welcome_bullet_1">"Calls, polls, search and more will be added later this year."</string>
|
||||
|
|
|
|||
|
|
@ -18,16 +18,11 @@ package io.element.android.features.ftue.impl
|
|||
|
||||
import android.os.Build
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.ftue.impl.migration.InMemoryMigrationScreenStore
|
||||
import io.element.android.features.ftue.impl.migration.MigrationScreenStore
|
||||
import io.element.android.features.ftue.impl.state.DefaultFtueState
|
||||
import io.element.android.features.ftue.impl.state.FtueStep
|
||||
import io.element.android.features.ftue.impl.welcome.state.FakeWelcomeState
|
||||
import io.element.android.features.lockscreen.api.LockScreenService
|
||||
import io.element.android.features.lockscreen.test.FakeLockScreenService
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||
import io.element.android.libraries.permissions.impl.FakePermissionStateProvider
|
||||
import io.element.android.services.analytics.api.AnalyticsService
|
||||
import io.element.android.services.analytics.test.FakeAnalyticsService
|
||||
|
|
@ -54,7 +49,6 @@ class DefaultFtueStateTests {
|
|||
fun `given all checks being true, should display flow is false`() = runTest {
|
||||
val welcomeState = FakeWelcomeState()
|
||||
val analyticsService = FakeAnalyticsService()
|
||||
val migrationScreenStore = InMemoryMigrationScreenStore()
|
||||
val permissionStateProvider = FakePermissionStateProvider(permissionGranted = true)
|
||||
val lockScreenService = FakeLockScreenService()
|
||||
val coroutineScope = CoroutineScope(coroutineContext + SupervisorJob())
|
||||
|
|
@ -63,14 +57,12 @@ class DefaultFtueStateTests {
|
|||
coroutineScope = coroutineScope,
|
||||
welcomeState = welcomeState,
|
||||
analyticsService = analyticsService,
|
||||
migrationScreenStore = migrationScreenStore,
|
||||
permissionStateProvider = permissionStateProvider,
|
||||
lockScreenService = lockScreenService,
|
||||
)
|
||||
|
||||
welcomeState.setWelcomeScreenShown()
|
||||
analyticsService.setDidAskUserConsent()
|
||||
migrationScreenStore.setMigrationScreenShown(A_SESSION_ID)
|
||||
permissionStateProvider.setPermissionGranted()
|
||||
lockScreenService.setIsPinSetup(true)
|
||||
state.updateState()
|
||||
|
|
@ -85,7 +77,6 @@ class DefaultFtueStateTests {
|
|||
fun `traverse flow`() = runTest {
|
||||
val welcomeState = FakeWelcomeState()
|
||||
val analyticsService = FakeAnalyticsService()
|
||||
val migrationScreenStore = InMemoryMigrationScreenStore()
|
||||
val permissionStateProvider = FakePermissionStateProvider(permissionGranted = false)
|
||||
val lockScreenService = FakeLockScreenService()
|
||||
val coroutineScope = CoroutineScope(coroutineContext + SupervisorJob())
|
||||
|
|
@ -94,29 +85,24 @@ class DefaultFtueStateTests {
|
|||
coroutineScope = coroutineScope,
|
||||
welcomeState = welcomeState,
|
||||
analyticsService = analyticsService,
|
||||
migrationScreenStore = migrationScreenStore,
|
||||
permissionStateProvider = permissionStateProvider,
|
||||
lockScreenService = lockScreenService,
|
||||
)
|
||||
val steps = mutableListOf<FtueStep?>()
|
||||
|
||||
// First step, migration screen
|
||||
steps.add(state.getNextStep(steps.lastOrNull()))
|
||||
migrationScreenStore.setMigrationScreenShown(A_SESSION_ID)
|
||||
|
||||
// Second step, welcome screen
|
||||
// First step, welcome screen
|
||||
steps.add(state.getNextStep(steps.lastOrNull()))
|
||||
welcomeState.setWelcomeScreenShown()
|
||||
|
||||
// Third step, notifications opt in
|
||||
// Second step, notifications opt in
|
||||
steps.add(state.getNextStep(steps.lastOrNull()))
|
||||
permissionStateProvider.setPermissionGranted()
|
||||
|
||||
// Fourth step, entering PIN code
|
||||
// Third step, entering PIN code
|
||||
steps.add(state.getNextStep(steps.lastOrNull()))
|
||||
lockScreenService.setIsPinSetup(true)
|
||||
|
||||
// Fifth step, analytics opt in
|
||||
// Fourth step, analytics opt in
|
||||
steps.add(state.getNextStep(steps.lastOrNull()))
|
||||
analyticsService.setDidAskUserConsent()
|
||||
|
||||
|
|
@ -124,7 +110,6 @@ class DefaultFtueStateTests {
|
|||
steps.add(state.getNextStep(steps.lastOrNull()))
|
||||
|
||||
assertThat(steps).containsExactly(
|
||||
FtueStep.MigrationScreen,
|
||||
FtueStep.WelcomeScreen,
|
||||
FtueStep.NotificationsOptIn,
|
||||
FtueStep.LockscreenSetup,
|
||||
|
|
@ -141,19 +126,16 @@ class DefaultFtueStateTests {
|
|||
fun `if a check for a step is true, start from the next one`() = runTest {
|
||||
val coroutineScope = CoroutineScope(coroutineContext + SupervisorJob())
|
||||
val analyticsService = FakeAnalyticsService()
|
||||
val migrationScreenStore = InMemoryMigrationScreenStore()
|
||||
val permissionStateProvider = FakePermissionStateProvider(permissionGranted = false)
|
||||
val lockScreenService = FakeLockScreenService()
|
||||
val state = createState(
|
||||
coroutineScope = coroutineScope,
|
||||
analyticsService = analyticsService,
|
||||
migrationScreenStore = migrationScreenStore,
|
||||
permissionStateProvider = permissionStateProvider,
|
||||
lockScreenService = lockScreenService,
|
||||
)
|
||||
|
||||
// Skip first 4 steps
|
||||
migrationScreenStore.setMigrationScreenShown(A_SESSION_ID)
|
||||
// Skip first 3 steps
|
||||
state.setWelcomeScreenShown()
|
||||
permissionStateProvider.setPermissionGranted()
|
||||
lockScreenService.setIsPinSetup(true)
|
||||
|
|
@ -171,18 +153,15 @@ class DefaultFtueStateTests {
|
|||
fun `if version is older than 13 we don't display the notification opt in screen`() = runTest {
|
||||
val coroutineScope = CoroutineScope(coroutineContext + SupervisorJob())
|
||||
val analyticsService = FakeAnalyticsService()
|
||||
val migrationScreenStore = InMemoryMigrationScreenStore()
|
||||
val lockScreenService = FakeLockScreenService()
|
||||
|
||||
val state = createState(
|
||||
sdkIntVersion = Build.VERSION_CODES.M,
|
||||
coroutineScope = coroutineScope,
|
||||
analyticsService = analyticsService,
|
||||
migrationScreenStore = migrationScreenStore,
|
||||
lockScreenService = lockScreenService,
|
||||
)
|
||||
|
||||
migrationScreenStore.setMigrationScreenShown(A_SESSION_ID)
|
||||
assertThat(state.getNextStep()).isEqualTo(FtueStep.WelcomeScreen)
|
||||
state.setWelcomeScreenShown()
|
||||
lockScreenService.setIsPinSetup(true)
|
||||
|
|
@ -200,9 +179,7 @@ class DefaultFtueStateTests {
|
|||
coroutineScope: CoroutineScope,
|
||||
welcomeState: FakeWelcomeState = FakeWelcomeState(),
|
||||
analyticsService: AnalyticsService = FakeAnalyticsService(),
|
||||
migrationScreenStore: MigrationScreenStore = InMemoryMigrationScreenStore(),
|
||||
permissionStateProvider: FakePermissionStateProvider = FakePermissionStateProvider(permissionGranted = false),
|
||||
matrixClient: MatrixClient = FakeMatrixClient(),
|
||||
lockScreenService: LockScreenService = FakeLockScreenService(),
|
||||
// First version where notification permission is required
|
||||
sdkIntVersion: Int = Build.VERSION_CODES.TIRAMISU,
|
||||
|
|
@ -211,9 +188,7 @@ class DefaultFtueStateTests {
|
|||
coroutineScope = coroutineScope,
|
||||
analyticsService = analyticsService,
|
||||
welcomeScreenState = welcomeState,
|
||||
migrationScreenStore = migrationScreenStore,
|
||||
permissionStateProvider = permissionStateProvider,
|
||||
lockScreenService = lockScreenService,
|
||||
matrixClient = matrixClient,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,11 +25,11 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.features.login.impl.R
|
||||
import io.element.android.features.login.impl.error.isWaitListError
|
||||
import io.element.android.features.login.impl.error.loginError
|
||||
|
|
@ -123,7 +123,7 @@ private fun OverallContent(
|
|||
) {
|
||||
Box(modifier = modifier.fillMaxSize()) {
|
||||
if (state.loginAction !is AsyncData.Success) {
|
||||
CompositionLocalProvider(LocalContentColor provides Color.Black) {
|
||||
CompositionLocalProvider(LocalContentColor provides ElementTheme.colors.textOnSolidPrimary) {
|
||||
TextButton(
|
||||
text = stringResource(CommonStrings.action_cancel),
|
||||
onClick = onCancelClicked,
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ dependencies {
|
|||
implementation(projects.features.analytics.api)
|
||||
implementation(projects.features.ftue.api)
|
||||
implementation(projects.features.logout.api)
|
||||
implementation(projects.features.roomlist.api)
|
||||
implementation(projects.services.analytics.api)
|
||||
implementation(projects.services.toolbox.api)
|
||||
implementation(libs.datetime)
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import coil.annotation.ExperimentalCoilApi
|
|||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.features.ftue.api.state.FtueState
|
||||
import io.element.android.features.preferences.impl.DefaultCacheService
|
||||
import io.element.android.features.roomlist.api.migration.MigrationScreenStore
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
import io.element.android.libraries.di.ApplicationContext
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
|
|
@ -45,6 +46,7 @@ class DefaultClearCacheUseCase @Inject constructor(
|
|||
private val defaultCacheIndexProvider: DefaultCacheService,
|
||||
private val okHttpClient: Provider<OkHttpClient>,
|
||||
private val ftueState: FtueState,
|
||||
private val migrationScreenStore: MigrationScreenStore,
|
||||
) : ClearCacheUseCase {
|
||||
override suspend fun invoke() = withContext(coroutineDispatchers.io) {
|
||||
// Clear Matrix cache
|
||||
|
|
@ -60,6 +62,8 @@ class DefaultClearCacheUseCase @Inject constructor(
|
|||
context.cacheDir.deleteRecursively()
|
||||
// Clear some settings
|
||||
ftueState.reset()
|
||||
// Clear migration screen store
|
||||
migrationScreenStore.reset()
|
||||
// Ensure the app is restarted
|
||||
defaultCacheIndexProvider.onClearedCache(matrixClient.sessionId)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.features.ftue.impl.migration
|
||||
package io.element.android.features.roomlist.api.migration
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
|
||||
|
|
@ -33,6 +33,7 @@ import io.element.android.features.networkmonitor.api.NetworkMonitor
|
|||
import io.element.android.features.networkmonitor.api.NetworkStatus
|
||||
import io.element.android.features.roomlist.impl.datasource.InviteStateDataSource
|
||||
import io.element.android.features.roomlist.impl.datasource.RoomListDataSource
|
||||
import io.element.android.features.roomlist.impl.migration.MigrationScreenPresenter
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
|
||||
|
|
@ -63,6 +64,7 @@ class RoomListPresenter @Inject constructor(
|
|||
private val encryptionService: EncryptionService,
|
||||
private val featureFlagService: FeatureFlagService,
|
||||
private val indicatorService: IndicatorService,
|
||||
private val migrationScreenPresenter: MigrationScreenPresenter,
|
||||
) : Presenter<RoomListState> {
|
||||
@Composable
|
||||
override fun present(): RoomListState {
|
||||
|
|
@ -82,6 +84,8 @@ class RoomListPresenter @Inject constructor(
|
|||
initialLoad(matrixUser)
|
||||
}
|
||||
|
||||
val isMigrating = migrationScreenPresenter.present().isMigrating
|
||||
|
||||
// Session verification status (unknown, not verified, verified)
|
||||
val canVerifySession by sessionVerificationService.canVerifySessionFlow.collectAsState(initial = false)
|
||||
var verificationPromptDismissed by rememberSaveable { mutableStateOf(false) }
|
||||
|
|
@ -148,6 +152,7 @@ class RoomListPresenter @Inject constructor(
|
|||
displaySearchResults = displaySearchResults,
|
||||
contextMenu = contextMenu,
|
||||
leaveRoomState = leaveRoomState,
|
||||
displayMigrationStatus = isMigrating,
|
||||
eventSink = ::handleEvents
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ data class RoomListState(
|
|||
val displaySearchResults: Boolean,
|
||||
val contextMenu: ContextMenu,
|
||||
val leaveRoomState: LeaveRoomState,
|
||||
val displayMigrationStatus: Boolean,
|
||||
val eventSink: (RoomListEvents) -> Unit,
|
||||
) {
|
||||
sealed interface ContextMenu {
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ open class RoomListStateProvider : PreviewParameterProvider<RoomListState> {
|
|||
aRoomListState().copy(displayRecoveryKeyPrompt = true),
|
||||
aRoomListState().copy(roomList = AsyncData.Success(persistentListOf())),
|
||||
aRoomListState().copy(roomList = AsyncData.Loading(prevData = RoomListRoomSummaryFactory.createFakeList())),
|
||||
aRoomListState().copy(matrixUser = null, displayMigrationStatus = true),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -70,6 +71,7 @@ internal fun aRoomListState() = RoomListState(
|
|||
displaySearchResults = false,
|
||||
contextMenu = RoomListState.ContextMenu.Hidden,
|
||||
leaveRoomState = aLeaveRoomState(),
|
||||
displayMigrationStatus = false,
|
||||
eventSink = {}
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ import io.element.android.features.roomlist.impl.components.RequestVerificationH
|
|||
import io.element.android.features.roomlist.impl.components.RoomListMenuAction
|
||||
import io.element.android.features.roomlist.impl.components.RoomListTopBar
|
||||
import io.element.android.features.roomlist.impl.components.RoomSummaryRow
|
||||
import io.element.android.features.roomlist.impl.migration.MigrationScreenView
|
||||
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
|
||||
import io.element.android.features.roomlist.impl.search.RoomListSearchResultView
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
|
|
@ -212,6 +213,7 @@ private fun RoomListContent(
|
|||
onMenuActionClicked = onMenuActionClicked,
|
||||
onOpenSettings = onOpenSettings,
|
||||
scrollBehavior = scrollBehavior,
|
||||
displayMenuItems = !state.displayMigrationStatus,
|
||||
)
|
||||
},
|
||||
content = { padding ->
|
||||
|
|
@ -270,18 +272,22 @@ private fun RoomListContent(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
MigrationScreenView(isMigrating = state.displayMigrationStatus)
|
||||
},
|
||||
floatingActionButton = {
|
||||
FloatingActionButton(
|
||||
// FIXME align on Design system theme
|
||||
containerColor = MaterialTheme.colorScheme.primary,
|
||||
onClick = onCreateRoomClicked
|
||||
) {
|
||||
Icon(
|
||||
// Note cannot use Icons.Outlined.EditSquare, it does not exist :/
|
||||
resourceId = CommonDrawables.ic_new_message,
|
||||
contentDescription = stringResource(id = R.string.screen_roomlist_a11y_create_message)
|
||||
)
|
||||
if (!state.displayMigrationStatus) {
|
||||
FloatingActionButton(
|
||||
// FIXME align on Design system theme
|
||||
containerColor = MaterialTheme.colorScheme.primary,
|
||||
onClick = onCreateRoomClicked
|
||||
) {
|
||||
Icon(
|
||||
// Note cannot use Icons.Outlined.EditSquare, it does not exist :/
|
||||
resourceId = CommonDrawables.ic_new_message,
|
||||
contentDescription = stringResource(id = R.string.screen_roomlist_a11y_create_message)
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
snackbarHost = { SnackbarHost(snackbarHostState) },
|
||||
|
|
|
|||
|
|
@ -21,8 +21,10 @@ import androidx.compose.foundation.layout.Box
|
|||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.statusBars
|
||||
import androidx.compose.foundation.layout.statusBarsPadding
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
|
|
@ -68,6 +70,7 @@ import io.element.android.libraries.designsystem.theme.components.HorizontalDivi
|
|||
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||
import io.element.android.libraries.designsystem.theme.components.IconButton
|
||||
import io.element.android.libraries.designsystem.theme.components.MediumTopAppBar
|
||||
import io.element.android.libraries.designsystem.theme.components.Surface
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
|
|
@ -89,6 +92,7 @@ fun RoomListTopBar(
|
|||
onMenuActionClicked: (RoomListMenuAction) -> Unit,
|
||||
onOpenSettings: () -> Unit,
|
||||
scrollBehavior: TopAppBarScrollBehavior,
|
||||
displayMenuItems: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
fun closeFilter() {
|
||||
|
|
@ -108,6 +112,7 @@ fun RoomListTopBar(
|
|||
onSearchClicked = onToggleSearch,
|
||||
onMenuActionClicked = onMenuActionClicked,
|
||||
scrollBehavior = scrollBehavior,
|
||||
displayMenuItems = displayMenuItems,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
|
@ -122,6 +127,7 @@ private fun DefaultRoomListTopBar(
|
|||
onOpenSettings: () -> Unit,
|
||||
onSearchClicked: () -> Unit,
|
||||
onMenuActionClicked: (RoomListMenuAction) -> Unit,
|
||||
displayMenuItems: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
// We need this to manually clip the top app bar in preview mode
|
||||
|
|
@ -195,79 +201,89 @@ private fun DefaultRoomListTopBar(
|
|||
Text(text = stringResource(id = R.string.screen_roomlist_main_space_title))
|
||||
},
|
||||
navigationIcon = {
|
||||
avatarData?.let {
|
||||
IconButton(
|
||||
modifier = Modifier.testTag(TestTags.homeScreenSettings),
|
||||
onClick = onOpenSettings
|
||||
) {
|
||||
IconButton(
|
||||
modifier = Modifier.testTag(TestTags.homeScreenSettings),
|
||||
onClick = onOpenSettings
|
||||
) {
|
||||
if (avatarData != null) {
|
||||
Avatar(
|
||||
avatarData = it,
|
||||
avatarData = avatarData!!,
|
||||
contentDescription = stringResource(CommonStrings.common_settings),
|
||||
)
|
||||
if (showAvatarIndicator) {
|
||||
RedIndicatorAtom(
|
||||
modifier = Modifier
|
||||
.padding(4.5.dp)
|
||||
.align(Alignment.TopEnd)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
// Placeholder avatar until the avatarData is available
|
||||
Surface(
|
||||
modifier = Modifier.size(AvatarSize.CurrentUserTopBar.dp),
|
||||
shape = CircleShape,
|
||||
color = ElementTheme.colors.iconSecondary,
|
||||
content = {}
|
||||
)
|
||||
}
|
||||
if (showAvatarIndicator) {
|
||||
RedIndicatorAtom(
|
||||
modifier = Modifier
|
||||
.padding(4.5.dp)
|
||||
.align(Alignment.TopEnd)
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
IconButton(
|
||||
onClick = onSearchClicked,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = CompoundIcons.Search,
|
||||
contentDescription = stringResource(CommonStrings.action_search),
|
||||
)
|
||||
}
|
||||
if (RoomListConfig.HAS_DROP_DOWN_MENU) {
|
||||
var showMenu by remember { mutableStateOf(false) }
|
||||
if (displayMenuItems) {
|
||||
IconButton(
|
||||
onClick = { showMenu = !showMenu }
|
||||
onClick = onSearchClicked,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = CompoundIcons.OverflowVertical,
|
||||
contentDescription = null,
|
||||
imageVector = CompoundIcons.Search,
|
||||
contentDescription = stringResource(CommonStrings.action_search),
|
||||
)
|
||||
}
|
||||
DropdownMenu(
|
||||
expanded = showMenu,
|
||||
onDismissRequest = { showMenu = false }
|
||||
) {
|
||||
if (RoomListConfig.SHOW_INVITE_MENU_ITEM) {
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
showMenu = false
|
||||
onMenuActionClicked(RoomListMenuAction.InviteFriends)
|
||||
},
|
||||
text = { Text(stringResource(id = CommonStrings.action_invite)) },
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
imageVector = CompoundIcons.ShareAndroid,
|
||||
tint = ElementTheme.materialColors.secondary,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
if (RoomListConfig.HAS_DROP_DOWN_MENU) {
|
||||
var showMenu by remember { mutableStateOf(false) }
|
||||
IconButton(
|
||||
onClick = { showMenu = !showMenu }
|
||||
) {
|
||||
Icon(
|
||||
imageVector = CompoundIcons.OverflowVertical,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
if (RoomListConfig.SHOW_REPORT_PROBLEM_MENU_ITEM) {
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
showMenu = false
|
||||
onMenuActionClicked(RoomListMenuAction.ReportBug)
|
||||
},
|
||||
text = { Text(stringResource(id = CommonStrings.common_report_a_problem)) },
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
imageVector = CompoundIcons.ChatProblem,
|
||||
tint = ElementTheme.materialColors.secondary,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
)
|
||||
DropdownMenu(
|
||||
expanded = showMenu,
|
||||
onDismissRequest = { showMenu = false }
|
||||
) {
|
||||
if (RoomListConfig.SHOW_INVITE_MENU_ITEM) {
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
showMenu = false
|
||||
onMenuActionClicked(RoomListMenuAction.InviteFriends)
|
||||
},
|
||||
text = { Text(stringResource(id = CommonStrings.action_invite)) },
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
imageVector = CompoundIcons.ShareAndroid,
|
||||
tint = ElementTheme.materialColors.secondary,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
if (RoomListConfig.SHOW_REPORT_PROBLEM_MENU_ITEM) {
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
showMenu = false
|
||||
onMenuActionClicked(RoomListMenuAction.ReportBug)
|
||||
},
|
||||
text = { Text(stringResource(id = CommonStrings.common_report_a_problem)) },
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
imageVector = CompoundIcons.ChatProblem,
|
||||
tint = ElementTheme.materialColors.secondary,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -299,6 +315,7 @@ internal fun DefaultRoomListTopBarPreview() = ElementPreview {
|
|||
scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(rememberTopAppBarState()),
|
||||
onOpenSettings = {},
|
||||
onSearchClicked = {},
|
||||
displayMenuItems = true,
|
||||
onMenuActionClicked = {},
|
||||
)
|
||||
}
|
||||
|
|
@ -314,6 +331,7 @@ internal fun DefaultRoomListTopBarWithIndicatorPreview() = ElementPreview {
|
|||
scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(rememberTopAppBarState()),
|
||||
onOpenSettings = {},
|
||||
onSearchClicked = {},
|
||||
displayMenuItems = true,
|
||||
onMenuActionClicked = {},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2023 New Vector Ltd
|
||||
* Copyright (c) 2024 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
|
@ -14,12 +14,16 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.features.ftue.impl.migration
|
||||
package io.element.android.features.roomlist.impl.migration
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import io.element.android.features.roomlist.api.migration.MigrationScreenStore
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
||||
|
|
@ -32,13 +36,15 @@ class MigrationScreenPresenter @Inject constructor(
|
|||
@Composable
|
||||
override fun present(): MigrationScreenState {
|
||||
val roomListState by matrixClient.roomListService.state.collectAsState()
|
||||
var needsMigration by remember { mutableStateOf(migrationScreenStore.isMigrationScreenNeeded(matrixClient.sessionId)) }
|
||||
if (roomListState == RoomListService.State.Running) {
|
||||
LaunchedEffect(Unit) {
|
||||
needsMigration = false
|
||||
migrationScreenStore.setMigrationScreenShown(matrixClient.sessionId)
|
||||
}
|
||||
}
|
||||
return MigrationScreenState(
|
||||
isMigrating = roomListState != RoomListService.State.Running
|
||||
isMigrating = needsMigration && roomListState != RoomListService.State.Running
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.features.ftue.impl.migration
|
||||
package io.element.android.features.roomlist.impl.migration
|
||||
|
||||
data class MigrationScreenState(
|
||||
val isMigrating: Boolean
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2023 New Vector Ltd
|
||||
* Copyright (c) 2024 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
|
@ -14,45 +14,43 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.features.ftue.impl.migration
|
||||
package io.element.android.features.roomlist.impl.migration
|
||||
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import io.element.android.features.ftue.impl.R
|
||||
import io.element.android.features.roomlist.impl.R
|
||||
import io.element.android.libraries.designsystem.atomic.pages.SunsetPage
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
|
||||
@Composable
|
||||
fun MigrationScreenView(
|
||||
migrationState: MigrationScreenState,
|
||||
onMigrationFinished: () -> Unit,
|
||||
isMigrating: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
if (migrationState.isMigrating.not()) {
|
||||
val latestOnMigrationFinished by rememberUpdatedState(onMigrationFinished)
|
||||
LaunchedEffect(Unit) {
|
||||
latestOnMigrationFinished()
|
||||
}
|
||||
}
|
||||
SunsetPage(
|
||||
modifier = modifier,
|
||||
isLoading = true,
|
||||
title = stringResource(id = R.string.screen_migration_title),
|
||||
subtitle = stringResource(id = R.string.screen_migration_message),
|
||||
overallContent = {}
|
||||
val displayMigrationStatusFadeProgress by animateFloatAsState(
|
||||
targetValue = if (isMigrating) 1f else 0f,
|
||||
animationSpec = tween(durationMillis = 200),
|
||||
label = "Migration view fade"
|
||||
)
|
||||
if (displayMigrationStatusFadeProgress > 0f) {
|
||||
SunsetPage(
|
||||
modifier = modifier.alpha(displayMigrationStatusFadeProgress),
|
||||
isLoading = true,
|
||||
title = stringResource(id = R.string.screen_migration_title),
|
||||
subtitle = stringResource(id = R.string.screen_migration_message),
|
||||
overallContent = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
@PreviewsDayNight
|
||||
internal fun MigrationViewPreview() = ElementPreview {
|
||||
MigrationScreenView(
|
||||
migrationState = MigrationScreenState(isMigrating = true),
|
||||
onMigrationFinished = {}
|
||||
)
|
||||
MigrationScreenView(isMigrating = true)
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2023 New Vector Ltd
|
||||
* Copyright (c) 2024 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
|
@ -14,11 +14,12 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.features.ftue.impl.migration
|
||||
package io.element.android.features.roomlist.impl.migration
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import androidx.core.content.edit
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.features.roomlist.api.migration.MigrationScreenStore
|
||||
import io.element.android.libraries.androidutils.hash.hash
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.di.DefaultPreferences
|
||||
|
|
@ -2,6 +2,8 @@
|
|||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="confirm_recovery_key_banner_message">"Your chat backup is currently out of sync. You need to confirm your recovery key to maintain access to your chat backup."</string>
|
||||
<string name="confirm_recovery_key_banner_title">"Confirm your recovery key"</string>
|
||||
<string name="screen_migration_message">"This is a one time process, thanks for waiting."</string>
|
||||
<string name="screen_migration_title">"Setting up your account."</string>
|
||||
<string name="screen_roomlist_a11y_create_message">"Create a new conversation or room"</string>
|
||||
<string name="screen_roomlist_empty_message">"Get started by messaging someone."</string>
|
||||
<string name="screen_roomlist_empty_title">"No chats yet."</string>
|
||||
|
|
|
|||
|
|
@ -29,6 +29,8 @@ import io.element.android.features.roomlist.impl.datasource.FakeInviteDataSource
|
|||
import io.element.android.features.roomlist.impl.datasource.InviteStateDataSource
|
||||
import io.element.android.features.roomlist.impl.datasource.RoomListDataSource
|
||||
import io.element.android.features.roomlist.impl.datasource.RoomListRoomSummaryFactory
|
||||
import io.element.android.features.roomlist.impl.migration.InMemoryMigrationScreenStore
|
||||
import io.element.android.features.roomlist.impl.migration.MigrationScreenPresenter
|
||||
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
|
||||
import io.element.android.libraries.dateformatter.api.LastMessageTimestampFormatter
|
||||
import io.element.android.libraries.dateformatter.test.FakeLastMessageTimestampFormatter
|
||||
|
|
@ -44,12 +46,14 @@ import io.element.android.libraries.matrix.api.MatrixClient
|
|||
import io.element.android.libraries.matrix.api.encryption.BackupState
|
||||
import io.element.android.libraries.matrix.api.encryption.EncryptionService
|
||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
||||
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
|
||||
import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus
|
||||
import io.element.android.libraries.matrix.test.AN_AVATAR_URL
|
||||
import io.element.android.libraries.matrix.test.AN_EXCEPTION
|
||||
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
|
||||
import io.element.android.libraries.matrix.test.A_USER_NAME
|
||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||
|
|
@ -396,6 +400,36 @@ class RoomListPresenterTests {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - change in migration presenter state modifies isMigrating`() = runTest {
|
||||
val client = FakeMatrixClient(sessionId = A_SESSION_ID)
|
||||
val migrationStore = InMemoryMigrationScreenStore()
|
||||
val migrationScreenPresenter = MigrationScreenPresenter(client, migrationStore)
|
||||
val scope = CoroutineScope(coroutineContext + SupervisorJob())
|
||||
val presenter = createRoomListPresenter(
|
||||
client = client,
|
||||
coroutineScope = scope,
|
||||
migrationScreenPresenter = migrationScreenPresenter,
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
// The migration screen is shown if the migration screen has not been shown before
|
||||
assertThat(initialState.displayMigrationStatus).isTrue()
|
||||
skipItems(2)
|
||||
|
||||
// Set migration as done and set the room list service as running to trigger a refresh of the presenter value
|
||||
(client.roomListService as FakeRoomListService).postState(RoomListService.State.Running)
|
||||
migrationStore.setMigrationScreenShown(A_SESSION_ID)
|
||||
|
||||
// The migration screen is not shown anymore
|
||||
assertThat(awaitItem().displayMigrationStatus).isFalse()
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
scope.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
private fun TestScope.createRoomListPresenter(
|
||||
client: MatrixClient = FakeMatrixClient(),
|
||||
sessionVerificationService: SessionVerificationService = FakeSessionVerificationService(),
|
||||
|
|
@ -409,6 +443,10 @@ class RoomListPresenterTests {
|
|||
roomLastMessageFormatter: RoomLastMessageFormatter = FakeRoomLastMessageFormatter(),
|
||||
encryptionService: EncryptionService = FakeEncryptionService(),
|
||||
coroutineScope: CoroutineScope,
|
||||
migrationScreenPresenter: MigrationScreenPresenter = MigrationScreenPresenter(
|
||||
matrixClient = client,
|
||||
migrationScreenStore = InMemoryMigrationScreenStore(),
|
||||
)
|
||||
) = RoomListPresenter(
|
||||
client = client,
|
||||
sessionVerificationService = sessionVerificationService,
|
||||
|
|
@ -433,6 +471,7 @@ class RoomListPresenterTests {
|
|||
encryptionService = encryptionService,
|
||||
featureFlagService = FakeFeatureFlagService(mapOf(FeatureFlags.SecureStorage.key to true)),
|
||||
),
|
||||
migrationScreenPresenter = migrationScreenPresenter,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2023 New Vector Ltd
|
||||
* Copyright (c) 2024 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
|
@ -14,8 +14,9 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.features.ftue.impl.migration
|
||||
package io.element.android.features.roomlist.impl.migration
|
||||
|
||||
import io.element.android.features.roomlist.api.migration.MigrationScreenStore
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
|
||||
class InMemoryMigrationScreenStore : MigrationScreenStore {
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2023 New Vector Ltd
|
||||
* Copyright (c) 2024 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
|
@ -14,12 +14,13 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.features.ftue.impl.migration
|
||||
package io.element.android.features.roomlist.impl.migration
|
||||
|
||||
import app.cash.molecule.RecompositionMode
|
||||
import app.cash.molecule.moleculeFlow
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.roomlist.api.migration.MigrationScreenStore
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
||||
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||
|
|
@ -61,6 +62,7 @@ class MigrationScreenPresenterTest {
|
|||
val nextState = awaitItem()
|
||||
assertThat(nextState.isMigrating).isFalse()
|
||||
assertThat(migrationScreenStore.isMigrationScreenNeeded(A_SESSION_ID)).isFalse()
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -37,8 +37,10 @@ import androidx.compose.ui.layout.ContentScale
|
|||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.annotations.CoreColorToken
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.compound.theme.ForcedDarkElementTheme
|
||||
import io.element.android.compound.tokens.generated.internal.DarkColorTokens
|
||||
import io.element.android.compound.tokens.generated.internal.LightColorTokens
|
||||
import io.element.android.libraries.designsystem.R
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
|
|
@ -54,7 +56,11 @@ fun SunsetPage(
|
|||
modifier: Modifier = Modifier,
|
||||
overallContent: @Composable () -> Unit,
|
||||
) {
|
||||
ForcedDarkElementTheme(lightStatusBar = true) {
|
||||
ElementTheme(
|
||||
// Always use the opposite value of the current theme
|
||||
darkTheme = ElementTheme.isLightTheme,
|
||||
applySystemBarsUpdate = false,
|
||||
) {
|
||||
Box(
|
||||
modifier = modifier.fillMaxSize()
|
||||
) {
|
||||
|
|
@ -107,21 +113,34 @@ fun SunsetPage(
|
|||
}
|
||||
}
|
||||
|
||||
@OptIn(CoreColorToken::class)
|
||||
@Composable
|
||||
private fun SunsetBackground(
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(modifier = modifier.fillMaxSize()) {
|
||||
// The top background colors are the opposite of the current theme ones
|
||||
val topBackgroundColor = if (ElementTheme.isLightTheme) {
|
||||
DarkColorTokens.colorThemeBg
|
||||
} else {
|
||||
LightColorTokens.colorThemeBg
|
||||
}
|
||||
// The bottom background colors follow the current theme
|
||||
val bottomBackgroundColor = if (ElementTheme.isLightTheme) {
|
||||
LightColorTokens.colorThemeBg
|
||||
} else {
|
||||
// The dark background color doesn't 100% match the image, so we use a custom color
|
||||
Color(0xFF121418)
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.weight(0.3f)
|
||||
.background(Color.White)
|
||||
.background(topBackgroundColor)
|
||||
)
|
||||
Image(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(),
|
||||
painter = painterResource(id = R.drawable.light_dark),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
painter = painterResource(id = R.drawable.bg_migration),
|
||||
contentScale = ContentScale.Crop,
|
||||
contentDescription = null,
|
||||
)
|
||||
|
|
@ -129,7 +148,7 @@ private fun SunsetBackground(
|
|||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.weight(0.7f)
|
||||
.background(Color(0xFF121418))
|
||||
.background(bottomBackgroundColor)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ import android.os.Build
|
|||
import android.text.TextPaint
|
||||
import androidx.annotation.FloatRange
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||
|
|
@ -136,13 +135,13 @@ object BloomDefaults {
|
|||
@Composable
|
||||
fun defaultLayers() = persistentListOf(
|
||||
// Bottom layer
|
||||
if (isSystemInDarkTheme()) {
|
||||
BloomLayer(0.5f, BlendMode.Exclusion)
|
||||
} else {
|
||||
if (ElementTheme.isLightTheme) {
|
||||
BloomLayer(0.2f, BlendMode.Hardlight)
|
||||
} else {
|
||||
BloomLayer(0.5f, BlendMode.Exclusion)
|
||||
},
|
||||
// Top layer
|
||||
BloomLayer(if (isSystemInDarkTheme()) 0.2f else 0.8f, BlendMode.Color),
|
||||
BloomLayer(if (ElementTheme.isLightTheme) 0.8f else 0.2f, BlendMode.Color),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 538 KiB |
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 104 KiB |
|
|
@ -28,6 +28,8 @@ import io.element.android.features.roomlist.impl.RoomListView
|
|||
import io.element.android.features.roomlist.impl.datasource.DefaultInviteStateDataSource
|
||||
import io.element.android.features.roomlist.impl.datasource.RoomListDataSource
|
||||
import io.element.android.features.roomlist.impl.datasource.RoomListRoomSummaryFactory
|
||||
import io.element.android.features.roomlist.impl.migration.MigrationScreenPresenter
|
||||
import io.element.android.features.roomlist.impl.migration.SharedPrefsMigrationScreenStore
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
import io.element.android.libraries.dateformatter.impl.DateFormatters
|
||||
import io.element.android.libraries.dateformatter.impl.DefaultLastMessageTimestampFormatter
|
||||
|
|
@ -103,6 +105,10 @@ class RoomListScreen(
|
|||
featureFlagService = featureFlagService,
|
||||
),
|
||||
featureFlagService = featureFlagService,
|
||||
migrationScreenPresenter = MigrationScreenPresenter(
|
||||
matrixClient = matrixClient,
|
||||
migrationScreenStore = SharedPrefsMigrationScreenStore(context.getSharedPreferences("migration", Context.MODE_PRIVATE))
|
||||
)
|
||||
)
|
||||
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:4c9d9aa75b2b01e9b0106377fbcd88a92fb8b4d6a34323b18264fed8f9a48cab
|
||||
size 133347
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:ce7cb3f4a50912cd7aa96340ff850d438a26e5efc331173c658448fdbecc7b65
|
||||
size 145047
|
||||
oid sha256:9db077d2e2981e9b3d431312b171741505e22816fa4b2bb3699f6ae4e00b48e9
|
||||
size 145160
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:b06c44fcc0afee1a398dbddeb18253f25002c3c8e77929d049edd878b3927e8a
|
||||
size 145776
|
||||
oid sha256:de09324bd0a41e25eff91ae77d5d88d96289a379ecf24e626fa9ea09a5acd43b
|
||||
size 145928
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:30a12c0e23d30c5841fef2f705af3fc4ea691ff06425d84a5c33f34809b78d8c
|
||||
size 65492
|
||||
oid sha256:54f47ca8a687adf5528e5ff8ddd87d901d2aa1280e6252ec962c176d6a207237
|
||||
size 65584
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:ce7cb3f4a50912cd7aa96340ff850d438a26e5efc331173c658448fdbecc7b65
|
||||
size 145047
|
||||
oid sha256:9db077d2e2981e9b3d431312b171741505e22816fa4b2bb3699f6ae4e00b48e9
|
||||
size 145160
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:ce7cb3f4a50912cd7aa96340ff850d438a26e5efc331173c658448fdbecc7b65
|
||||
size 145047
|
||||
oid sha256:5d5c0f2cf39d75b09a8cc1f4ffbd65912cab79ce5881ed5da5fdadeb9f57cb00
|
||||
size 170070
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:b06c44fcc0afee1a398dbddeb18253f25002c3c8e77929d049edd878b3927e8a
|
||||
size 145776
|
||||
oid sha256:c23b11ebcc1e73255714b07cee27c4583a9923112cdf0eda60983925d8074bb7
|
||||
size 170640
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:a6687f1be6e8abab9ae846152cc4edeb5fe1becda4ec9bcf64c255ded9dc937a
|
||||
size 63857
|
||||
oid sha256:026200b29fb2599e1f04ae19ab75cfed41a8c4aff5b0ef0086a6948244733830
|
||||
size 67437
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:ce7cb3f4a50912cd7aa96340ff850d438a26e5efc331173c658448fdbecc7b65
|
||||
size 145047
|
||||
oid sha256:5d5c0f2cf39d75b09a8cc1f4ffbd65912cab79ce5881ed5da5fdadeb9f57cb00
|
||||
size 170070
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:ddf3e62bd00f179fbeed4e432bd9a7a21321e83dd1d5192e3dc213fcfbc8c372
|
||||
size 128199
|
||||
oid sha256:97aa5dbc8ddcd05a44ae56771a13085096f11b2469c1a7edb3281e242d86ad21
|
||||
size 150264
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:fea824bf7f4c74c827fa3c1fa892b9beeb9d11e0863fc9e5c104c725236c7d10
|
||||
size 156506
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:64c4eb481f40871925405ae317cb80927caf31cb552a8aa9549bfb5658ca91e4
|
||||
size 137589
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:a7bf47b0a25c455b108d7b3585a42849f03c6652af73a175fe2147cb1ad62a66
|
||||
size 161125
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:50ca2afda159bdd7f57e78b04fbe3c022a9aeee1b68f72cf4d4269b3e503f0e5
|
||||
size 127335
|
||||
oid sha256:d56b02025c9de6cca3aaa3e4cc5cd82b222790c4efa0013f957307c13e45ee40
|
||||
size 151229
|
||||
|
|
|
|||
|
|
@ -106,7 +106,8 @@
|
|||
"includeRegex" : [
|
||||
"screen_roomlist_.*",
|
||||
"session_verification_banner_.*",
|
||||
"confirm_recovery_key_banner_.*"
|
||||
"confirm_recovery_key_banner_.*",
|
||||
"screen_migration_.*"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -154,7 +155,6 @@
|
|||
"name" : ":features:ftue:impl",
|
||||
"includeRegex" : [
|
||||
"screen_welcome_.*",
|
||||
"screen_migration_.*",
|
||||
"screen_notification_optin_.*"
|
||||
]
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue