mirror of
https://github.com/anyproto/anytype-kotlin.git
synced 2025-06-08 05:47:05 +09:00
App | Enhancement | Logout warning (#2121)
This commit is contained in:
parent
793f0cc180
commit
3d2239bee3
14 changed files with 306 additions and 64 deletions
|
@ -11,6 +11,7 @@ import com.anytypeio.anytype.di.feature.sets.viewer.ViewerCardSizeSelectModule
|
|||
import com.anytypeio.anytype.di.feature.sets.viewer.ViewerImagePreviewSelectModule
|
||||
import com.anytypeio.anytype.di.feature.settings.AboutAppModule
|
||||
import com.anytypeio.anytype.di.feature.settings.AccountAndDataModule
|
||||
import com.anytypeio.anytype.di.feature.settings.LogoutWarningModule
|
||||
import com.anytypeio.anytype.di.feature.wallpaper.WallpaperSelectModule
|
||||
import com.anytypeio.anytype.di.main.MainComponent
|
||||
|
||||
|
@ -607,10 +608,15 @@ class ComponentManager(private val main: MainComponent) {
|
|||
val aboutAppComponent = Component {
|
||||
main.aboutAppComponent().module(AboutAppModule).build()
|
||||
}
|
||||
|
||||
val accountAndDataComponent = Component {
|
||||
main.accountAndDataComponent().module(AccountAndDataModule).build()
|
||||
}
|
||||
|
||||
val logoutWarningComponent = Component {
|
||||
main.logoutWarningComponent().module(LogoutWarningModule).build()
|
||||
}
|
||||
|
||||
class Component<T>(private val builder: () -> T) {
|
||||
|
||||
private var instance: T? = null
|
||||
|
|
|
@ -30,11 +30,9 @@ object AccountAndDataModule {
|
|||
@Provides
|
||||
@PerScreen
|
||||
fun provideViewModelFactory(
|
||||
clearFileCache: ClearFileCache,
|
||||
logout: Logout
|
||||
clearFileCache: ClearFileCache
|
||||
): AccountAndDataViewModel.Factory = AccountAndDataViewModel.Factory(
|
||||
clearFileCache = clearFileCache,
|
||||
logout = logout
|
||||
clearFileCache = clearFileCache
|
||||
)
|
||||
|
||||
@JvmStatic
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
package com.anytypeio.anytype.di.feature.settings
|
||||
|
||||
import com.anytypeio.anytype.core_utils.di.scope.PerScreen
|
||||
import com.anytypeio.anytype.domain.auth.interactor.Logout
|
||||
import com.anytypeio.anytype.domain.auth.repo.AuthRepository
|
||||
import com.anytypeio.anytype.ui.settings.LogoutWarningFragment
|
||||
import com.anytypeio.anytype.ui_settings.account.LogoutWarningViewModel
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.Subcomponent
|
||||
|
||||
@Subcomponent(modules = [LogoutWarningModule::class])
|
||||
@PerScreen
|
||||
interface LogoutWarningSubComponent {
|
||||
|
||||
@Subcomponent.Builder
|
||||
interface Builder {
|
||||
fun module(module: LogoutWarningModule): Builder
|
||||
fun build(): LogoutWarningSubComponent
|
||||
}
|
||||
|
||||
fun inject(fragment: LogoutWarningFragment)
|
||||
}
|
||||
|
||||
@Module
|
||||
object LogoutWarningModule {
|
||||
|
||||
@JvmStatic
|
||||
@Provides
|
||||
@PerScreen
|
||||
fun provideViewModelFactory(
|
||||
logout: Logout
|
||||
): LogoutWarningViewModel.Factory = LogoutWarningViewModel.Factory(logout = logout)
|
||||
|
||||
@JvmStatic
|
||||
@Provides
|
||||
@PerScreen
|
||||
fun logout(repo: AuthRepository): Logout = Logout(repo)
|
||||
}
|
|
@ -4,6 +4,7 @@ import com.anytypeio.anytype.app.AndroidApplication
|
|||
import com.anytypeio.anytype.di.feature.*
|
||||
import com.anytypeio.anytype.di.feature.settings.AboutAppSubComponent
|
||||
import com.anytypeio.anytype.di.feature.settings.AccountAndDataSubComponent
|
||||
import com.anytypeio.anytype.di.feature.settings.LogoutWarningSubComponent
|
||||
import com.anytypeio.anytype.di.feature.wallpaper.WallpaperSelectSubComponent
|
||||
import dagger.Component
|
||||
import javax.inject.Singleton
|
||||
|
@ -54,6 +55,7 @@ interface MainComponent {
|
|||
fun debugSettingsBuilder(): DebugSettingsSubComponent.Builder
|
||||
fun keychainPhraseComponentBuilder(): KeychainPhraseSubComponent.Builder
|
||||
fun otherSettingsComponentBuilder(): OtherSettingsSubComponent.Builder
|
||||
fun logoutWarningComponent() : LogoutWarningSubComponent.Builder
|
||||
|
||||
//endregion
|
||||
}
|
|
@ -9,9 +9,6 @@ import androidx.compose.runtime.collectAsState
|
|||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.compose.ui.platform.ViewCompositionStrategy
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import com.anytypeio.anytype.R
|
||||
import com.anytypeio.anytype.core_utils.ext.toast
|
||||
|
@ -20,7 +17,6 @@ import com.anytypeio.anytype.di.common.componentManager
|
|||
import com.anytypeio.anytype.ui.dashboard.ClearCacheAlertFragment
|
||||
import com.anytypeio.anytype.ui_settings.account.AccountAndDataScreen
|
||||
import com.anytypeio.anytype.ui_settings.account.AccountAndDataViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
class AccountAndDataFragment : BaseBottomSheetComposeFragment() {
|
||||
|
@ -34,6 +30,10 @@ class AccountAndDataFragment : BaseBottomSheetComposeFragment() {
|
|||
findNavController().navigate(R.id.keychainDialog)
|
||||
}
|
||||
|
||||
private val onLogoutClicked = {
|
||||
findNavController().navigate(R.id.logoutWarningScreen)
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
|
@ -48,7 +48,7 @@ class AccountAndDataFragment : BaseBottomSheetComposeFragment() {
|
|||
onClearFileCachedClicked = { proceedWithClearFileCacheWarning() },
|
||||
onDeleteAccountClicked = { toast(resources.getString(R.string.coming_soon)) },
|
||||
onResetAccountClicked = { toast(resources.getString(R.string.coming_soon)) },
|
||||
onLogoutClicked = { vm.onLogoutClicked() },
|
||||
onLogoutClicked = onLogoutClicked,
|
||||
onPinCodeClicked = { toast(resources.getString(R.string.coming_soon)) },
|
||||
isLogoutInProgress = vm.isLoggingOut.collectAsState().value,
|
||||
isClearCacheInProgress = vm.isClearFileCacheInProgress.collectAsState().value
|
||||
|
@ -58,21 +58,6 @@ class AccountAndDataFragment : BaseBottomSheetComposeFragment() {
|
|||
}
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
vm.commands.collect { command ->
|
||||
when(command) {
|
||||
AccountAndDataViewModel.Command.Logout -> {
|
||||
findNavController().navigate(R.id.actionLogout)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun proceedWithClearFileCacheWarning() {
|
||||
val dialog = ClearCacheAlertFragment.new()
|
||||
dialog.onClearAccepted = { vm.onClearFileCacheAccepted() }
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
package com.anytypeio.anytype.ui.settings
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.compose.ui.platform.ViewCompositionStrategy
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import com.anytypeio.anytype.R
|
||||
import com.anytypeio.anytype.core_ui.foundation.Warning
|
||||
import com.anytypeio.anytype.core_utils.ui.BaseBottomSheetComposeFragment
|
||||
import com.anytypeio.anytype.di.common.componentManager
|
||||
import com.anytypeio.anytype.ui_settings.account.LogoutWarningViewModel
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
class LogoutWarningFragment : BaseBottomSheetComposeFragment() {
|
||||
|
||||
@Inject
|
||||
lateinit var factory: LogoutWarningViewModel.Factory
|
||||
|
||||
private val vm by viewModels<LogoutWarningViewModel> { factory }
|
||||
|
||||
private val onBackupPhraseClicked = {
|
||||
findNavController().navigate(R.id.keychainDialog)
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
return ComposeView(requireContext()).apply {
|
||||
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
|
||||
setContent {
|
||||
MaterialTheme(typography = typography) {
|
||||
Warning(
|
||||
actionButtonText = stringResource(R.string.log_out),
|
||||
cancelButtonText = stringResource(R.string.back_up_your_phrase),
|
||||
title = stringResource(R.string.have_you_back_up_your_keychain),
|
||||
subtitle = stringResource(R.string.you_will_need_to_sign_in),
|
||||
onNegativeClick = onBackupPhraseClicked,
|
||||
onPositiveClick = { vm.onLogoutClicked() },
|
||||
isInProgress = vm.isLoggingOut.collectAsState().value
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
lifecycleScope.launch {
|
||||
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
launch {
|
||||
vm.commands.collect { command ->
|
||||
when (command) {
|
||||
LogoutWarningViewModel.Command.Logout -> {
|
||||
findNavController().navigate(R.id.actionLogout)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
vm.isLoggingOut.collect { isLoggingOut ->
|
||||
isCancelable = isLoggingOut == false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun setCancelable(cancelable: Boolean) {
|
||||
super.setCancelable(cancelable)
|
||||
dialog?.let { d ->
|
||||
d.setCanceledOnTouchOutside(cancelable)
|
||||
d.window?.decorView?.findViewById<View>(R.id.design_bottom_sheet)?.let {
|
||||
BottomSheetBehavior.from(it).isHideable = cancelable
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun injectDependencies() {
|
||||
componentManager().logoutWarningComponent.get().inject(this)
|
||||
}
|
||||
|
||||
override fun releaseDependencies() {
|
||||
componentManager().logoutWarningComponent.release()
|
||||
}
|
||||
}
|
|
@ -4,6 +4,6 @@
|
|||
android:viewportWidth="36"
|
||||
android:viewportHeight="4">
|
||||
<path
|
||||
android:fillColor="#ACA996"
|
||||
android:fillColor="@color/shape_primary"
|
||||
android:pathData="M2,0L34,0A2,2 0,0 1,36 2L36,2A2,2 0,0 1,34 4L2,4A2,2 0,0 1,0 2L0,2A2,2 0,0 1,2 0z" />
|
||||
</vector>
|
||||
|
|
|
@ -2,5 +2,5 @@
|
|||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<corners android:radius="8dp" />
|
||||
<solid android:color="#F3F2EC" />
|
||||
<solid android:color="@color/shape_transparent" />
|
||||
</shape>
|
|
@ -189,6 +189,16 @@
|
|||
android:id="@+id/aboutAppScreen"
|
||||
android:name="com.anytypeio.anytype.ui.settings.AboutAppFragment" />
|
||||
|
||||
<dialog
|
||||
android:id="@+id/logoutWarningScreen"
|
||||
android:name="com.anytypeio.anytype.ui.settings.LogoutWarningFragment" >
|
||||
<action
|
||||
android:id="@+id/actionLogout"
|
||||
app:destination="@+id/main_navigation"
|
||||
app:popUpTo="@+id/main_navigation"
|
||||
app:popUpToInclusive="true" />
|
||||
</dialog>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/createAccountScreen"
|
||||
android:name="com.anytypeio.anytype.ui.auth.account.CreateAccountFragment"
|
||||
|
|
|
@ -263,4 +263,5 @@ Do the computation of an expensive paragraph of text on a background thread:
|
|||
<string name="download_from_node">All files will be deleted from your current device. They can be downloaded again from a backup node or another device.</string>
|
||||
|
||||
<string name="new_profile">new profile</string>
|
||||
<string name="back_up_your_phrase">Back up</string>
|
||||
</resources>
|
||||
|
|
|
@ -3,18 +3,23 @@ package com.anytypeio.anytype.core_ui.foundation
|
|||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.CircularProgressIndicator
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.anytypeio.anytype.core_ui.R
|
||||
|
||||
@Composable
|
||||
|
@ -100,4 +105,82 @@ fun Arrow() {
|
|||
end = 20.dp
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Warning(
|
||||
title: String,
|
||||
subtitle: String,
|
||||
actionButtonText: String,
|
||||
cancelButtonText: String,
|
||||
onNegativeClick: () -> Unit,
|
||||
onPositiveClick: () -> Unit,
|
||||
isInProgress: Boolean = false
|
||||
) {
|
||||
Column {
|
||||
Text(
|
||||
style = MaterialTheme.typography.h2,
|
||||
text = title,
|
||||
modifier = Modifier.padding(
|
||||
top = 24.dp,
|
||||
start = 20.dp,
|
||||
end = 20.dp
|
||||
),
|
||||
color = colorResource(R.color.text_primary)
|
||||
)
|
||||
Text(
|
||||
text = subtitle,
|
||||
modifier = Modifier.padding(
|
||||
top = 12.dp,
|
||||
start = 20.dp,
|
||||
end = 20.dp
|
||||
),
|
||||
color = colorResource(R.color.text_primary)
|
||||
)
|
||||
Row(
|
||||
modifier = Modifier.height(68.dp).padding(
|
||||
top = 8.dp,
|
||||
start = 20.dp,
|
||||
end = 20.dp
|
||||
).fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier.height(48.dp).border(
|
||||
width = 1.dp,
|
||||
color = colorResource(R.color.shape_primary),
|
||||
shape = RoundedCornerShape(10.dp)
|
||||
).weight(1.0f, true).clickable(onClick = onNegativeClick),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
text = cancelButtonText,
|
||||
color = colorResource(R.color.text_primary),
|
||||
fontSize = 17.sp
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.width(10.dp))
|
||||
Box(
|
||||
modifier = Modifier.height(48.dp).background(
|
||||
color = colorResource(R.color.anytype_text_red),
|
||||
shape = RoundedCornerShape(10.dp)
|
||||
).weight(1.0f, true).clickable(onClick = onPositiveClick),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
text = actionButtonText,
|
||||
color = Color.White,
|
||||
fontSize = 17.sp,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
if (isInProgress) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.align(Alignment.CenterEnd).padding(end = 20.dp).size(16.dp),
|
||||
color = Color.White,
|
||||
strokeWidth = 2.dp
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,21 +3,14 @@ package com.anytypeio.anytype.ui_settings.account
|
|||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.anytypeio.anytype.domain.auth.interactor.Logout
|
||||
import com.anytypeio.anytype.domain.base.BaseUseCase
|
||||
import com.anytypeio.anytype.domain.base.Interactor
|
||||
import com.anytypeio.anytype.domain.device.ClearFileCache
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
|
||||
class AccountAndDataViewModel(
|
||||
private val clearFileCache: ClearFileCache,
|
||||
private val logout: Logout,
|
||||
) : ViewModel() {
|
||||
|
||||
val commands = MutableSharedFlow<Command>(replay = 0)
|
||||
class AccountAndDataViewModel(private val clearFileCache: ClearFileCache) : ViewModel() {
|
||||
|
||||
val isClearFileCacheInProgress = MutableStateFlow(false)
|
||||
val isLoggingOut = MutableStateFlow(false)
|
||||
|
@ -43,40 +36,12 @@ class AccountAndDataViewModel(
|
|||
}
|
||||
}
|
||||
|
||||
fun onLogoutClicked() {
|
||||
viewModelScope.launch {
|
||||
logout(params = BaseUseCase.None).collect { status ->
|
||||
when (status) {
|
||||
is Interactor.Status.Started -> {
|
||||
isLoggingOut.value = true
|
||||
}
|
||||
is Interactor.Status.Success -> {
|
||||
isLoggingOut.value = false
|
||||
commands.emit(Command.Logout)
|
||||
}
|
||||
is Interactor.Status.Error -> {
|
||||
isLoggingOut.value = true
|
||||
Timber.e(status.throwable, "Error while logging out")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Factory(
|
||||
private val clearFileCache: ClearFileCache,
|
||||
private val logout: Logout,
|
||||
) : ViewModelProvider.Factory {
|
||||
class Factory(private val clearFileCache: ClearFileCache) : ViewModelProvider.Factory {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
return AccountAndDataViewModel(
|
||||
clearFileCache = clearFileCache,
|
||||
logout = logout
|
||||
clearFileCache = clearFileCache
|
||||
) as T
|
||||
}
|
||||
}
|
||||
|
||||
sealed class Command {
|
||||
object Logout : Command()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
package com.anytypeio.anytype.ui_settings.account
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.anytypeio.anytype.domain.auth.interactor.Logout
|
||||
import com.anytypeio.anytype.domain.base.BaseUseCase
|
||||
import com.anytypeio.anytype.domain.base.Interactor
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
|
||||
class LogoutWarningViewModel(private val logout: Logout) : ViewModel() {
|
||||
|
||||
val commands = MutableSharedFlow<Command>(replay = 0)
|
||||
val isLoggingOut = MutableStateFlow(false)
|
||||
|
||||
fun onLogoutClicked() {
|
||||
viewModelScope.launch {
|
||||
logout(params = BaseUseCase.None).collect { status ->
|
||||
when (status) {
|
||||
is Interactor.Status.Started -> {
|
||||
isLoggingOut.value = true
|
||||
}
|
||||
is Interactor.Status.Success -> {
|
||||
isLoggingOut.value = false
|
||||
commands.emit(Command.Logout)
|
||||
}
|
||||
is Interactor.Status.Error -> {
|
||||
isLoggingOut.value = true
|
||||
Timber.e(status.throwable, "Error while logging out")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Factory(private val logout: Logout) : ViewModelProvider.Factory {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
return LogoutWarningViewModel(logout = logout) as T
|
||||
}
|
||||
}
|
||||
|
||||
sealed class Command {
|
||||
object Logout : Command()
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<string name="about">About</string>
|
||||
<string name="app_version">App version</string>
|
||||
<string name="library">Library</string>
|
||||
|
@ -17,4 +18,8 @@
|
|||
<string name="pin_code">Pin code</string>
|
||||
<string name="access">Access</string>
|
||||
<string name="wallpaper">Wallpaper</string>
|
||||
|
||||
<string name="have_you_back_up_your_keychain">Have you backed up your keychain phrase?</string>
|
||||
<string name="you_will_need_to_sign_in">You will need it to sign in. Keep it in a safe place. If you lose it, you can no longer access your account.</string>
|
||||
|
||||
</resources>
|
Loading…
Add table
Add a link
Reference in a new issue