[#174]added warning screen + StorageChecker.kt (#594)

* [#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:
Alex 2022-10-03 15:59:09 +02:00 committed by GitHub
parent 8b3befadb2
commit 386b400e22
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 220 additions and 22 deletions

View File

@ -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

View File

@ -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
)
}

View File

@ -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",
)
)
}

View File

@ -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()
}
}

View File

@ -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) ||

View File

@ -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
)
}

View File

@ -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
)
}
}
}

View File

@ -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
)
}

View File

@ -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>

View File

@ -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>