* [#174]added warning screen + StorageChecker.kt * [#174]added warning screen + StorageChecker.kt * added manual test * added manual test * resolved merge conflicts * code review fixes * added vector drawable * Add units Co-authored-by: Carter Jernigan <git@carterjernigan.com>
This commit is contained in:
parent
8b3befadb2
commit
386b400e22
|
@ -0,0 +1,5 @@
|
|||
# Test handling of low device storage
|
||||
1. Open StorageChecker.kt file
|
||||
1. Change REQUIRED_FREE_SPACE_MEGABYTES parameter to 100000
|
||||
1. Build the app
|
||||
1. Verify that the "Not enough space!" screen is displayed
|
|
@ -5,6 +5,7 @@ import androidx.compose.material3.MaterialTheme
|
|||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
|
@ -15,12 +16,13 @@ import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
|||
@Composable
|
||||
fun Header(
|
||||
text: String,
|
||||
modifier: Modifier = Modifier
|
||||
modifier: Modifier = Modifier,
|
||||
color: Color = ZcashTheme.colors.onBackgroundHeader
|
||||
) {
|
||||
Text(
|
||||
text = text,
|
||||
style = MaterialTheme.typography.headlineLarge,
|
||||
color = ZcashTheme.colors.onBackgroundHeader,
|
||||
color = color,
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
|
@ -28,12 +30,16 @@ fun Header(
|
|||
@Composable
|
||||
fun Body(
|
||||
text: String,
|
||||
modifier: Modifier = Modifier
|
||||
modifier: Modifier = Modifier,
|
||||
textAlign: TextAlign = TextAlign.Start,
|
||||
color: Color = MaterialTheme.colorScheme.onBackground
|
||||
|
||||
) {
|
||||
Text(
|
||||
text = text,
|
||||
textAlign = textAlign,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = MaterialTheme.colorScheme.onBackground,
|
||||
color = color,
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
|
|
|
@ -43,7 +43,8 @@ android {
|
|||
"src/main/res/ui/settings",
|
||||
"src/main/res/ui/support",
|
||||
"src/main/res/ui/update",
|
||||
"src/main/res/ui/wallet_address"
|
||||
"src/main/res/ui/wallet_address",
|
||||
"src/main/res/ui/warning",
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
package co.electriccoin.zcash.global
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Environment
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
object StorageChecker {
|
||||
const val REQUIRED_FREE_SPACE_MEGABYTES: Int = 1000
|
||||
|
||||
suspend fun isEnoughSpace() = checkAvailableStorageMegabytes() > REQUIRED_FREE_SPACE_MEGABYTES
|
||||
|
||||
@SuppressLint("UsableSpace")
|
||||
@Suppress("MagicNumber")
|
||||
suspend fun checkAvailableStorageMegabytes(): Int = withContext(Dispatchers.IO) {
|
||||
return@withContext (Environment.getDataDirectory().usableSpace / (1024 * 1024)).toInt()
|
||||
}
|
||||
|
||||
suspend fun spaceRequiredToContinueMegabytes() = withContext(Dispatchers.IO) {
|
||||
return@withContext REQUIRED_FREE_SPACE_MEGABYTES - checkAvailableStorageMegabytes()
|
||||
}
|
||||
}
|
|
@ -9,8 +9,10 @@ import androidx.activity.viewModels
|
|||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
|
@ -31,6 +33,8 @@ import co.electriccoin.zcash.ui.screen.backup.WrapBackup
|
|||
import co.electriccoin.zcash.ui.screen.home.viewmodel.SecretState
|
||||
import co.electriccoin.zcash.ui.screen.home.viewmodel.WalletViewModel
|
||||
import co.electriccoin.zcash.ui.screen.onboarding.WrapOnboarding
|
||||
import co.electriccoin.zcash.ui.screen.warning.WrapNotEnoughSpace
|
||||
import co.electriccoin.zcash.ui.screen.warning.viewmodel.StorageCheckViewModel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
|
@ -43,6 +47,9 @@ class MainActivity : ComponentActivity() {
|
|||
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
||||
val walletViewModel by viewModels<WalletViewModel>()
|
||||
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
||||
val storageCheckViewModel by viewModels<StorageCheckViewModel>()
|
||||
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.NONE)
|
||||
lateinit var navControllerForTesting: NavHostController
|
||||
|
||||
|
@ -100,23 +107,11 @@ class MainActivity : ComponentActivity() {
|
|||
LocalScreenSecurity provides screenSecurity,
|
||||
LocalScreenBrightness provides screenBrightness
|
||||
) {
|
||||
when (val secretState = walletViewModel.secretState.collectAsState().value) {
|
||||
SecretState.Loading -> {
|
||||
// For now, keep displaying splash screen using condition above.
|
||||
// In the future, we might consider displaying something different here.
|
||||
}
|
||||
SecretState.None -> {
|
||||
WrapOnboarding()
|
||||
}
|
||||
is SecretState.NeedsBackup -> {
|
||||
WrapBackup(
|
||||
secretState.persistableWallet,
|
||||
onBackupComplete = { walletViewModel.persistBackupComplete() }
|
||||
)
|
||||
}
|
||||
is SecretState.Ready -> {
|
||||
Navigation()
|
||||
}
|
||||
val isEnoughSpace by storageCheckViewModel.isEnoughSpace.collectAsState()
|
||||
if (isEnoughSpace == false) {
|
||||
WrapNotEnoughSpace()
|
||||
} else {
|
||||
MainContent()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -131,6 +126,28 @@ class MainActivity : ComponentActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MainContent() {
|
||||
when (val secretState = walletViewModel.secretState.collectAsState().value) {
|
||||
SecretState.Loading -> {
|
||||
// For now, keep displaying splash screen using condition above.
|
||||
// In the future, we might consider displaying something different here.
|
||||
}
|
||||
SecretState.None -> {
|
||||
WrapOnboarding()
|
||||
}
|
||||
is SecretState.NeedsBackup -> {
|
||||
WrapBackup(
|
||||
secretState.persistableWallet,
|
||||
onBackupComplete = { walletViewModel.persistBackupComplete() }
|
||||
)
|
||||
}
|
||||
is SecretState.Ready -> {
|
||||
Navigation()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun observeScreenSecurityFlag(screenSecurity: ScreenSecurity) {
|
||||
screenSecurity.referenceCount.map { it > 0 }.collectWith(lifecycleScope) { isSecure ->
|
||||
val isTest = FirebaseTestLabUtil.isFirebaseTestLab(applicationContext) ||
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
@file:Suppress("ktlint:filename")
|
||||
|
||||
package co.electriccoin.zcash.ui.screen.warning
|
||||
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.viewModels
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import co.electriccoin.zcash.ui.MainActivity
|
||||
import co.electriccoin.zcash.ui.screen.warning.view.NotEnoughSpaceView
|
||||
import co.electriccoin.zcash.ui.screen.warning.viewmodel.StorageCheckViewModel
|
||||
|
||||
@Composable
|
||||
fun MainActivity.WrapNotEnoughSpace() {
|
||||
WrapNotEnoughSpace(this)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun WrapNotEnoughSpace(activity: ComponentActivity) {
|
||||
val storageCheckViewModel by activity.viewModels<StorageCheckViewModel>()
|
||||
val spaceRequiredToContinue by storageCheckViewModel.spaceRequiredToContinueMegabytes.collectAsState()
|
||||
|
||||
NotEnoughSpaceView(
|
||||
storageSpaceRequiredGigabytes = storageCheckViewModel.requiredStorageSpaceGigabytes,
|
||||
spaceRequiredToContinueMegabytes = spaceRequiredToContinue ?: 0
|
||||
)
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
package co.electriccoin.zcash.ui.screen.warning.view
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
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.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import co.electriccoin.zcash.ui.R
|
||||
import co.electriccoin.zcash.ui.design.component.Body
|
||||
import co.electriccoin.zcash.ui.design.component.GradientSurface
|
||||
import co.electriccoin.zcash.ui.design.component.Header
|
||||
import co.electriccoin.zcash.ui.design.component.Small
|
||||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||
|
||||
@Composable
|
||||
fun NotEnoughSpaceView(storageSpaceRequiredGigabytes: Int, spaceRequiredToContinueMegabytes: Int) {
|
||||
@Suppress("MagicNumber")
|
||||
val backgroundColor = Color(0xFF1A233A) // TODO should be replaced, once we define colors
|
||||
Column(
|
||||
Modifier
|
||||
.background(backgroundColor)
|
||||
.fillMaxSize()
|
||||
.padding(32.dp),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Image(painterResource(id = R.drawable.not_enough_space), "", Modifier.fillMaxWidth())
|
||||
Spacer(Modifier.height(32.dp))
|
||||
Header(text = stringResource(id = R.string.not_enough_space_title), color = Color.White)
|
||||
Spacer(Modifier.height(32.dp))
|
||||
Body(
|
||||
text = stringResource(id = R.string.not_enough_space_description, storageSpaceRequiredGigabytes),
|
||||
textAlign = TextAlign.Center,
|
||||
color = Color.White
|
||||
)
|
||||
Spacer(Modifier.height(64.dp))
|
||||
Small(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = stringResource(id = R.string.space_required_to_continue, spaceRequiredToContinueMegabytes),
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun NotEnoughSpacePreview() {
|
||||
ZcashTheme {
|
||||
GradientSurface {
|
||||
NotEnoughSpaceView(
|
||||
storageSpaceRequiredGigabytes = 1,
|
||||
spaceRequiredToContinueMegabytes = 300
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package co.electriccoin.zcash.ui.screen.warning.viewmodel
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import co.electriccoin.zcash.global.StorageChecker
|
||||
import co.electriccoin.zcash.ui.common.ANDROID_STATE_FLOW_TIMEOUT
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.WhileSubscribed
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
|
||||
class StorageCheckViewModel : ViewModel() {
|
||||
@Suppress("MagicNumber")
|
||||
val requiredStorageSpaceGigabytes: Int =
|
||||
(StorageChecker.REQUIRED_FREE_SPACE_MEGABYTES / 1000)
|
||||
|
||||
val isEnoughSpace = flow { emit(StorageChecker.isEnoughSpace()) }
|
||||
.stateIn(
|
||||
viewModelScope,
|
||||
SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT),
|
||||
null
|
||||
)
|
||||
|
||||
val spaceRequiredToContinueMegabytes =
|
||||
flow { emit(StorageChecker.spaceRequiredToContinueMegabytes()) }
|
||||
.stateIn(
|
||||
viewModelScope,
|
||||
SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT),
|
||||
null
|
||||
)
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="123dp"
|
||||
android:height="151dp"
|
||||
android:viewportWidth="123"
|
||||
android:viewportHeight="151">
|
||||
<path
|
||||
android:pathData="M61.5,151l-30.75,-16.25a47.25,47.25 0,0 1,-12.8 -10.14,65.56 65.56,0 0,1 -9.68,-14.14 76.37,76.37 0,0 1,-6.13 -16.25,67.59 67.59,0 0,1 -2.14,-16.48v-0.82h33.17L58.88,104l25.71,-27.06h38.41v0.82a67.59,67.59 0,0 1,-2.14 16.48,76.33 76.33,0 0,1 -6.13,16.25 65.52,65.52 0,0 1,-9.68 14.14,47.25 47.25,0 0,1 -12.8,10.14L61.5,151Z"
|
||||
android:fillColor="#891c2f"/>
|
||||
<path
|
||||
android:pathData="M32.22,75.94h0l-32.22,0L0,24.36L61.5,0l61.5,24.36L123,75.94h-37.46L99,61.78 84.95,47l-26.07,27.44 -12.84,-13.51L32,75.71l0.22,0.23Z"
|
||||
android:fillColor="#ff0000"/>
|
||||
</vector>
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="not_enough_space_title">Not enough space!</string>
|
||||
<string name="not_enough_space_description">You need approximately <xliff:g example="1" id="required_gigabytes">%1$s</xliff:g> gig of space while synchronizing the Zcash blockchain, but only 300 megs once done. Syncing will stay paused until more space is available.</string>
|
||||
<string name="space_required_to_continue"><xliff:g example="300" id="required_megabytes">~%1$s</xliff:g> megs required to continue </string>
|
||||
<string name="unknown">Unknown</string>
|
||||
</resources>
|
Loading…
Reference in New Issue