* [#1618] About redesign * [#1618] Whats new redesign * [#1618] Delete screen redesign * [#1618] Export private data refactor * [#1618] Seed recovery refactor * [#1618] Seed Redesign * [#1618] Feedback Redesign * [#1618] Popup implementation * [#1618] Localization fixes * [#1618] Code cleanup * [#1618] Code cleanup * [#1618] Code cleanup * [#1618] Documentation update * [#1618] Code cleanup * [#1618] Design hotfixes * [#1618] Code cleanup * [#1618] Test hotfixes * [#1618] Test hotfixes * Code cleanup * Changelogs entries update * Address few review comments * Fix UI tests * Fix bottom widget version name in WhatsNew * Update Spanish texts * Fix ktlint warnings * Test hotfix * Test hotfix * Code cleanup * Design hotfixes for small screens --------- Co-authored-by: Honza <rychnovsky.honza@gmail.com>
This commit is contained in:
parent
af5ed30e8a
commit
425052f1db
|
@ -18,6 +18,7 @@ and this application adheres to [Semantic Versioning](https://semver.org/spec/v2
|
|||
- The in-app update logic has been fixed and is now correctly requested with every app launch
|
||||
- The Not enough space and In-app udpate screens have been redesigned
|
||||
- External links now open in in-app browser
|
||||
- All the Settings screens have been redesigned
|
||||
|
||||
### Fixed
|
||||
- Address book toast now correctly shows on send screen when adding both new and known addresses to text field
|
||||
|
|
|
@ -21,6 +21,7 @@ directly impact users rather than highlighting other key architectural updates.*
|
|||
- The in-app update logic has been fixed and is now correctly requested with every app launch
|
||||
- The Not enough space and In-app udpate screens have been redesigned
|
||||
- External links now open in in-app browser
|
||||
- All the Settings screens have been redesigned
|
||||
|
||||
### Fixed
|
||||
- Address book toast now correctly shows on send screen when adding both new and known addresses to text field
|
||||
|
|
|
@ -21,6 +21,7 @@ directly impact users rather than highlighting other key architectural updates.*
|
|||
- The in-app update logic has been fixed and is now correctly requested with every app launch
|
||||
- The Not enough space and In-app udpate screens have been redesigned
|
||||
- External links now open in in-app browser
|
||||
- All the Settings screens have been redesigned
|
||||
|
||||
### Fixed
|
||||
- Address book toast now correctly shows on send screen when adding both new and known addresses to text field
|
||||
|
|
|
@ -32,6 +32,13 @@ style:
|
|||
ignoreAnnotated:
|
||||
- 'Preview'
|
||||
- 'PreviewScreens'
|
||||
- 'PreviewScreenSizes'
|
||||
MagicNumber:
|
||||
active: true
|
||||
ignoreAnnotated:
|
||||
- 'Preview'
|
||||
- 'PreviewScreens'
|
||||
- 'PreviewScreenSizes'
|
||||
|
||||
complexity:
|
||||
LongMethod:
|
||||
|
@ -39,11 +46,13 @@ complexity:
|
|||
ignoreAnnotated:
|
||||
- 'Preview'
|
||||
- 'PreviewScreens'
|
||||
- 'PreviewScreenSizes'
|
||||
LongParameterList:
|
||||
active: false
|
||||
ignoreAnnotated:
|
||||
- 'Preview'
|
||||
- 'PreviewScreens'
|
||||
- 'PreviewScreenSizes'
|
||||
|
||||
Compose:
|
||||
ModifierMissing:
|
||||
|
|
|
@ -1,102 +0,0 @@
|
|||
package co.electriccoin.zcash.ui.design.component
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.wrapContentWidth
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import cash.z.ecc.android.sdk.fixture.WalletFixture
|
||||
import cash.z.ecc.android.sdk.model.SeedPhrase
|
||||
import co.electriccoin.zcash.spackle.model.Index
|
||||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
|
||||
// TODO [#1001]: Row size should probably change for landscape layouts
|
||||
// TODO [#1001]: https://github.com/Electric-Coin-Company/zashi-android/issues/1001
|
||||
const val CHIP_GRID_COLUMN_SIZE = 12
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun ChipGridPreview() {
|
||||
ZcashTheme(forceDarkMode = false) {
|
||||
BlankSurface {
|
||||
ChipGrid(
|
||||
SeedPhrase.new(WalletFixture.Alice.seedPhrase).split.toPersistentList(),
|
||||
onGridClick = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun ChipGridDarkPreview() {
|
||||
ZcashTheme(forceDarkMode = true) {
|
||||
BlankSurface {
|
||||
ChipGrid(
|
||||
SeedPhrase.new(WalletFixture.Alice.seedPhrase).split.toPersistentList(),
|
||||
onGridClick = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ChipGrid(
|
||||
wordList: ImmutableList<String>,
|
||||
onGridClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
allowCopy: Boolean = false,
|
||||
) {
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
|
||||
Row(
|
||||
modifier = modifier.then(Modifier.fillMaxWidth()),
|
||||
horizontalArrangement = Arrangement.Center
|
||||
) {
|
||||
Row(
|
||||
modifier =
|
||||
Modifier
|
||||
.wrapContentWidth()
|
||||
.testTag(CommonTag.CHIP_LAYOUT)
|
||||
.then(
|
||||
if (allowCopy) {
|
||||
Modifier
|
||||
.clickable(
|
||||
interactionSource = interactionSource,
|
||||
// Disable ripple
|
||||
indication = null,
|
||||
onClick = onGridClick
|
||||
)
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
)
|
||||
) {
|
||||
wordList.chunked(CHIP_GRID_COLUMN_SIZE).forEachIndexed { chunkIndex, chunk ->
|
||||
// TODO [#1043]: Correctly align numbers and words on Recovery screen
|
||||
// TODO [#1043]: https://github.com/Electric-Coin-Company/zashi-android/issues/1043
|
||||
Column(
|
||||
modifier = Modifier.padding(horizontal = ZcashTheme.dimens.spacingDefault)
|
||||
) {
|
||||
chunk.forEachIndexed { subIndex, word ->
|
||||
ChipIndexed(
|
||||
index = Index(chunkIndex * CHIP_GRID_COLUMN_SIZE + subIndex),
|
||||
text = word,
|
||||
modifier = Modifier.padding(ZcashTheme.dimens.spacingXtiny)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
package co.electriccoin.zcash.ui.design.component
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
|
@ -13,12 +14,13 @@ import androidx.compose.foundation.layout.wrapContentWidth
|
|||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
@ -39,7 +41,7 @@ fun ZashiButton(
|
|||
) {
|
||||
ZashiButton(
|
||||
text = state.text.getValue(),
|
||||
leadingIcon = state.leadingIconVector,
|
||||
icon = state.icon,
|
||||
onClick = state.onClick,
|
||||
modifier = modifier,
|
||||
enabled = state.isEnabled,
|
||||
|
@ -55,7 +57,7 @@ fun ZashiButton(
|
|||
text: String,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
leadingIcon: Painter? = null,
|
||||
@DrawableRes icon: Int? = null,
|
||||
enabled: Boolean = true,
|
||||
isLoading: Boolean = false,
|
||||
colors: ZashiButtonColors = ZashiButtonDefaults.primaryColors(),
|
||||
|
@ -65,11 +67,12 @@ fun ZashiButton(
|
|||
object : ZashiButtonScope {
|
||||
@Composable
|
||||
override fun LeadingIcon() {
|
||||
if (leadingIcon != null) {
|
||||
if (icon != null) {
|
||||
Image(
|
||||
painter = leadingIcon,
|
||||
painter = painterResource(icon),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(20.dp)
|
||||
modifier = Modifier.size(20.dp),
|
||||
colorFilter = ColorFilter.tint(LocalContentColor.current)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -98,6 +101,8 @@ fun ZashiButton(
|
|||
}
|
||||
}
|
||||
|
||||
val borderColor = if (enabled) colors.borderColor else colors.disabledBorderColor
|
||||
|
||||
Button(
|
||||
onClick = onClick,
|
||||
modifier = modifier,
|
||||
|
@ -105,7 +110,7 @@ fun ZashiButton(
|
|||
contentPadding = PaddingValues(horizontal = 10.dp),
|
||||
enabled = enabled,
|
||||
colors = colors.toButtonColors(),
|
||||
border = colors.borderColor.takeIf { it != Color.Unspecified }?.let { BorderStroke(1.dp, it) },
|
||||
border = borderColor.takeIf { it != Color.Unspecified }?.let { BorderStroke(1.dp, it) },
|
||||
content = {
|
||||
content(scope)
|
||||
}
|
||||
|
@ -142,9 +147,10 @@ object ZashiButtonDefaults {
|
|||
) = ZashiButtonColors(
|
||||
containerColor = containerColor,
|
||||
contentColor = contentColor,
|
||||
borderColor = Color.Unspecified,
|
||||
disabledContainerColor = disabledContainerColor,
|
||||
disabledContentColor = disabledContentColor,
|
||||
borderColor = Color.Unspecified
|
||||
disabledBorderColor = Color.Unspecified
|
||||
)
|
||||
|
||||
@Composable
|
||||
|
@ -156,9 +162,10 @@ object ZashiButtonDefaults {
|
|||
) = ZashiButtonColors(
|
||||
containerColor = containerColor,
|
||||
contentColor = contentColor,
|
||||
borderColor = Color.Unspecified,
|
||||
disabledContainerColor = disabledContainerColor,
|
||||
disabledContentColor = disabledContentColor,
|
||||
borderColor = Color.Unspecified
|
||||
disabledBorderColor = Color.Unspecified
|
||||
)
|
||||
|
||||
@Composable
|
||||
|
@ -170,9 +177,10 @@ object ZashiButtonDefaults {
|
|||
) = ZashiButtonColors(
|
||||
containerColor = containerColor,
|
||||
contentColor = contentColor,
|
||||
borderColor = Color.Unspecified,
|
||||
disabledContainerColor = disabledContainerColor,
|
||||
disabledContentColor = disabledContentColor,
|
||||
borderColor = Color.Unspecified
|
||||
disabledBorderColor = Color.Unspecified
|
||||
)
|
||||
|
||||
@Composable
|
||||
|
@ -187,7 +195,8 @@ object ZashiButtonDefaults {
|
|||
contentColor = contentColor,
|
||||
disabledContainerColor = disabledContainerColor,
|
||||
disabledContentColor = disabledContentColor,
|
||||
borderColor = borderColor
|
||||
borderColor = borderColor,
|
||||
disabledBorderColor = Color.Unspecified
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -195,15 +204,16 @@ object ZashiButtonDefaults {
|
|||
data class ZashiButtonColors(
|
||||
val containerColor: Color,
|
||||
val contentColor: Color,
|
||||
val borderColor: Color,
|
||||
val disabledContainerColor: Color,
|
||||
val disabledContentColor: Color,
|
||||
val borderColor: Color,
|
||||
val disabledBorderColor: Color,
|
||||
)
|
||||
|
||||
@Immutable
|
||||
data class ButtonState(
|
||||
val text: StringResource,
|
||||
val leadingIconVector: Painter? = null,
|
||||
@DrawableRes val icon: Int? = null,
|
||||
val isEnabled: Boolean = true,
|
||||
val isLoading: Boolean = false,
|
||||
val onClick: () -> Unit = {},
|
||||
|
@ -239,7 +249,7 @@ private fun PrimaryWithIconPreview() =
|
|||
ZashiButton(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = "Primary",
|
||||
leadingIcon = painterResource(id = android.R.drawable.ic_secure),
|
||||
icon = android.R.drawable.ic_secure,
|
||||
onClick = {},
|
||||
)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,128 @@
|
|||
package co.electriccoin.zcash.ui.design.component
|
||||
|
||||
import androidx.compose.animation.core.Spring
|
||||
import androidx.compose.animation.core.spring
|
||||
import androidx.compose.animation.scaleIn
|
||||
import androidx.compose.animation.scaleOut
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import co.electriccoin.zcash.ui.design.R
|
||||
import co.electriccoin.zcash.ui.design.newcomponent.PreviewScreens
|
||||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||
import co.electriccoin.zcash.ui.design.theme.colors.ZashiColors
|
||||
import co.electriccoin.zcash.ui.design.theme.dimensions.ZashiDimensions
|
||||
import co.electriccoin.zcash.ui.design.theme.typography.ZashiTypography
|
||||
import co.electriccoin.zcash.ui.design.util.StringResource
|
||||
import co.electriccoin.zcash.ui.design.util.getValue
|
||||
import co.electriccoin.zcash.ui.design.util.stringRes
|
||||
|
||||
@Composable
|
||||
fun ZashiCheckbox(
|
||||
text: StringResource,
|
||||
isChecked: Boolean,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
ZashiCheckbox(
|
||||
state =
|
||||
CheckboxState(
|
||||
text = text,
|
||||
isChecked = isChecked,
|
||||
onClick = onClick,
|
||||
),
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ZashiCheckbox(
|
||||
state: CheckboxState,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Row(
|
||||
modifier =
|
||||
modifier
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.clickable(onClick = state.onClick)
|
||||
.padding(vertical = 12.dp)
|
||||
) {
|
||||
Box {
|
||||
Image(
|
||||
painter = painterResource(R.drawable.ic_zashi_checkbox),
|
||||
contentDescription = ""
|
||||
)
|
||||
|
||||
androidx.compose.animation.AnimatedVisibility(
|
||||
visible = state.isChecked,
|
||||
enter =
|
||||
scaleIn(
|
||||
spring(
|
||||
stiffness = Spring.StiffnessMedium,
|
||||
dampingRatio = Spring.DampingRatioMediumBouncy
|
||||
)
|
||||
),
|
||||
exit =
|
||||
scaleOut(
|
||||
spring(
|
||||
stiffness = Spring.StiffnessHigh,
|
||||
dampingRatio = Spring.DampingRatioMediumBouncy
|
||||
)
|
||||
)
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(R.drawable.ic_zashi_checkbox_checked),
|
||||
contentDescription = ""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(Modifier.width(ZashiDimensions.Spacing.spacingMd))
|
||||
|
||||
Text(
|
||||
text = state.text.getValue(),
|
||||
style = ZashiTypography.textSm,
|
||||
fontWeight = FontWeight.Medium,
|
||||
color = ZashiColors.Text.textPrimary,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class CheckboxState(
|
||||
val text: StringResource,
|
||||
val isChecked: Boolean,
|
||||
val onClick: () -> Unit,
|
||||
)
|
||||
|
||||
@PreviewScreens
|
||||
@Composable
|
||||
private fun ZashiCheckboxPreview() =
|
||||
ZcashTheme {
|
||||
var isChecked by remember { mutableStateOf(false) }
|
||||
BlankSurface {
|
||||
ZashiCheckbox(
|
||||
state =
|
||||
CheckboxState(
|
||||
text = stringRes("title"),
|
||||
isChecked = isChecked,
|
||||
onClick = { isChecked = isChecked.not() }
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
package co.electriccoin.zcash.ui.design.newcomponent
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.ui.tooling.preview.Devices
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import kotlin.annotation.AnnotationRetention.SOURCE
|
||||
|
||||
|
@ -8,3 +9,15 @@ import kotlin.annotation.AnnotationRetention.SOURCE
|
|||
@Preview(name = "2: Dark preview", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Retention(SOURCE)
|
||||
annotation class PreviewScreens
|
||||
|
||||
@Preview(name = "1: Light preview", showBackground = true)
|
||||
@Preview(name = "2: Light preview small", showBackground = true, device = Devices.NEXUS_5)
|
||||
@Preview(name = "3: Dark preview", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Preview(
|
||||
name = "4: Dark preview small",
|
||||
showBackground = true,
|
||||
uiMode = Configuration.UI_MODE_NIGHT_YES,
|
||||
device = Devices.NEXUS_5
|
||||
)
|
||||
@Retention(SOURCE)
|
||||
annotation class PreviewScreenSizes
|
||||
|
|
|
@ -4,6 +4,7 @@ import androidx.compose.foundation.layout.PaddingValues
|
|||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import co.electriccoin.zcash.ui.design.theme.dimensions.ZashiDimensions
|
||||
|
||||
@Stable
|
||||
|
@ -14,3 +15,11 @@ fun Modifier.scaffoldPadding(paddingValues: PaddingValues) =
|
|||
start = ZashiDimensions.Spacing.spacing3xl,
|
||||
end = ZashiDimensions.Spacing.spacing3xl
|
||||
)
|
||||
|
||||
fun Modifier.scaffoldScrollPadding(paddingValues: PaddingValues) =
|
||||
this.padding(
|
||||
top = paddingValues.calculateTopPadding(),
|
||||
bottom = paddingValues.calculateBottomPadding() + ZashiDimensions.Spacing.spacing3xl,
|
||||
start = 4.dp,
|
||||
end = 4.dp
|
||||
)
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="16dp"
|
||||
android:height="16dp"
|
||||
android:viewportWidth="16"
|
||||
android:viewportHeight="16">
|
||||
<path
|
||||
android:pathData="M4,0.5L12,0.5A3.5,3.5 0,0 1,15.5 4L15.5,12A3.5,3.5 0,0 1,12 15.5L4,15.5A3.5,3.5 0,0 1,0.5 12L0.5,4A3.5,3.5 0,0 1,4 0.5z"
|
||||
android:fillColor="#343031"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M4,0.5L12,0.5A3.5,3.5 0,0 1,15.5 4L15.5,12A3.5,3.5 0,0 1,12 15.5L4,15.5A3.5,3.5 0,0 1,0.5 12L0.5,4A3.5,3.5 0,0 1,4 0.5z"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#939091"/>
|
||||
</vector>
|
|
@ -0,0 +1,21 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="16dp"
|
||||
android:height="16dp"
|
||||
android:viewportWidth="16"
|
||||
android:viewportHeight="16">
|
||||
<path
|
||||
android:pathData="M4,0.5L12,0.5A3.5,3.5 0,0 1,15.5 4L15.5,12A3.5,3.5 0,0 1,12 15.5L4,15.5A3.5,3.5 0,0 1,0.5 12L0.5,4A3.5,3.5 0,0 1,4 0.5z"
|
||||
android:fillColor="#E8E8E8"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M4,0.5L12,0.5A3.5,3.5 0,0 1,15.5 4L15.5,12A3.5,3.5 0,0 1,12 15.5L4,15.5A3.5,3.5 0,0 1,0.5 12L0.5,4A3.5,3.5 0,0 1,4 0.5z"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#E8E8E8"/>
|
||||
<path
|
||||
android:pathData="M12,5L6.5,10.5L4,8"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="1.6666"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#343031"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
|
@ -0,0 +1,14 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="16dp"
|
||||
android:height="16dp"
|
||||
android:viewportWidth="16"
|
||||
android:viewportHeight="16">
|
||||
<path
|
||||
android:pathData="M4,0.5L12,0.5A3.5,3.5 0,0 1,15.5 4L15.5,12A3.5,3.5 0,0 1,12 15.5L4,15.5A3.5,3.5 0,0 1,0.5 12L0.5,4A3.5,3.5 0,0 1,4 0.5z"
|
||||
android:fillColor="#ffffff"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M4,0.5L12,0.5A3.5,3.5 0,0 1,15.5 4L15.5,12A3.5,3.5 0,0 1,12 15.5L4,15.5A3.5,3.5 0,0 1,0.5 12L0.5,4A3.5,3.5 0,0 1,4 0.5z"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#C0BFB1"/>
|
||||
</vector>
|
|
@ -0,0 +1,21 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="16dp"
|
||||
android:height="16dp"
|
||||
android:viewportWidth="16"
|
||||
android:viewportHeight="16">
|
||||
<path
|
||||
android:pathData="M4,0.5L12,0.5A3.5,3.5 0,0 1,15.5 4L15.5,12A3.5,3.5 0,0 1,12 15.5L4,15.5A3.5,3.5 0,0 1,0.5 12L0.5,4A3.5,3.5 0,0 1,4 0.5z"
|
||||
android:fillColor="#231F20"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M4,0.5L12,0.5A3.5,3.5 0,0 1,15.5 4L15.5,12A3.5,3.5 0,0 1,12 15.5L4,15.5A3.5,3.5 0,0 1,0.5 12L0.5,4A3.5,3.5 0,0 1,4 0.5z"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#231F20"/>
|
||||
<path
|
||||
android:pathData="M12,5L6.5,10.5L4,8"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="1.6666"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
|
@ -48,7 +48,6 @@ android {
|
|||
"src/main/res/ui/home",
|
||||
"src/main/res/ui/choose_server",
|
||||
"src/main/res/ui/integrations",
|
||||
"src/main/res/ui/new_wallet_recovery",
|
||||
"src/main/res/ui/onboarding",
|
||||
"src/main/res/ui/payment_request",
|
||||
"src/main/res/ui/qr_code",
|
||||
|
@ -62,7 +61,7 @@ android {
|
|||
"src/main/res/ui/send",
|
||||
"src/main/res/ui/send_confirmation",
|
||||
"src/main/res/ui/settings",
|
||||
"src/main/res/ui/support",
|
||||
"src/main/res/ui/feedback",
|
||||
"src/main/res/ui/update",
|
||||
"src/main/res/ui/update_contact",
|
||||
"src/main/res/ui/wallet_address",
|
||||
|
|
|
@ -27,20 +27,14 @@ class AboutViewTest {
|
|||
assertEquals(0, testSetup.getOnBackCount())
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText(
|
||||
getStringResource(R.string.back_navigation),
|
||||
.onNodeWithContentDescription(
|
||||
getStringResource(R.string.back_navigation_content_description),
|
||||
ignoreCase = true
|
||||
)
|
||||
.also {
|
||||
it.assertExists()
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithContentDescription(
|
||||
label = getStringResource(R.string.zcash_logo_content_description)
|
||||
).also {
|
||||
it.assertExists()
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithText(getStringResource(R.string.about_description)).also {
|
||||
it.assertExists()
|
||||
}
|
||||
|
|
|
@ -30,7 +30,6 @@ class AboutViewTestSetup(
|
|||
snackbarHostState = SnackbarHostState(),
|
||||
topAppBarSubTitleState = TopAppBarSubTitleState.None,
|
||||
versionInfo = versionInfo,
|
||||
onWhatsNew = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -53,12 +53,6 @@ class ExportPrivateDataViewTest : UiTestPrerequisites() {
|
|||
it.assertExists()
|
||||
it.assertIsDisplayed()
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithTag(ExportPrivateDataScreenTag.ADDITIONAL_TEXT_TAG).also {
|
||||
it.performScrollTo()
|
||||
it.assertExists()
|
||||
it.assertIsDisplayed()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -1,47 +0,0 @@
|
|||
package co.electriccoin.zcash.ui.screen.newwalletrecovery.view
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.test.junit4.ComposeContentTestRule
|
||||
import cash.z.ecc.sdk.fixture.PersistableWalletFixture
|
||||
import co.electriccoin.zcash.ui.common.model.VersionInfo
|
||||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
class NewWalletRecoveryTestSetup(
|
||||
private val composeTestRule: ComposeContentTestRule,
|
||||
private val versionInfo: VersionInfo,
|
||||
) {
|
||||
private val onBirthdayCopyCount = AtomicInteger(0)
|
||||
|
||||
private val onCompleteCallbackCount = AtomicInteger(0)
|
||||
|
||||
fun getOnBirthdayCopyCount(): Int {
|
||||
composeTestRule.waitForIdle()
|
||||
return onBirthdayCopyCount.get()
|
||||
}
|
||||
|
||||
fun getOnCompleteCount(): Int {
|
||||
composeTestRule.waitForIdle()
|
||||
return onCompleteCallbackCount.get()
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Suppress("TestFunctionName")
|
||||
fun DefaultContent() {
|
||||
ZcashTheme {
|
||||
NewWalletRecovery(
|
||||
PersistableWalletFixture.new(),
|
||||
onSeedCopy = { /* Not tested - debug mode feature only */ },
|
||||
onBirthdayCopy = { onBirthdayCopyCount.incrementAndGet() },
|
||||
onComplete = { onCompleteCallbackCount.incrementAndGet() },
|
||||
versionInfo = versionInfo,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun setDefaultContent() {
|
||||
composeTestRule.setContent {
|
||||
DefaultContent()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,113 +0,0 @@
|
|||
package co.electriccoin.zcash.ui.screen.newwalletrecovery.view
|
||||
|
||||
import androidx.compose.ui.test.junit4.createComposeRule
|
||||
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||
import androidx.compose.ui.test.onNodeWithTag
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.performClick
|
||||
import androidx.compose.ui.test.performScrollTo
|
||||
import androidx.test.filters.MediumTest
|
||||
import co.electriccoin.zcash.test.UiTestPrerequisites
|
||||
import co.electriccoin.zcash.ui.R
|
||||
import co.electriccoin.zcash.ui.common.test.CommonTag.WALLET_BIRTHDAY
|
||||
import co.electriccoin.zcash.ui.design.component.CommonTag
|
||||
import co.electriccoin.zcash.ui.fixture.VersionInfoFixture
|
||||
import co.electriccoin.zcash.ui.test.getStringResource
|
||||
import org.junit.Rule
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class NewWalletRecoveryViewTest : UiTestPrerequisites() {
|
||||
@get:Rule
|
||||
val composeTestRule = createComposeRule()
|
||||
|
||||
private fun newTestSetup(): NewWalletRecoveryTestSetup {
|
||||
return NewWalletRecoveryTestSetup(
|
||||
composeTestRule,
|
||||
VersionInfoFixture.new()
|
||||
).apply {
|
||||
setDefaultContent()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@MediumTest
|
||||
fun default_ui_state_test() {
|
||||
val testSetup = newTestSetup()
|
||||
|
||||
assertEquals(0, testSetup.getOnBirthdayCopyCount())
|
||||
assertEquals(0, testSetup.getOnCompleteCount())
|
||||
|
||||
composeTestRule.onNodeWithContentDescription(
|
||||
label = getStringResource(R.string.zcash_logo_content_description)
|
||||
).also {
|
||||
it.assertExists()
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithText(getStringResource(R.string.new_wallet_recovery_header)).also {
|
||||
it.assertExists()
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithText(getStringResource(R.string.new_wallet_recovery_description)).also {
|
||||
it.assertExists()
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithTag(CommonTag.CHIP_LAYOUT).also {
|
||||
it.performScrollTo()
|
||||
it.assertExists()
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithTag(WALLET_BIRTHDAY).also {
|
||||
it.performScrollTo()
|
||||
it.assertExists()
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText(
|
||||
getStringResource(R.string.new_wallet_recovery_button_finished),
|
||||
ignoreCase = true
|
||||
)
|
||||
.also {
|
||||
it.performScrollTo()
|
||||
it.assertExists()
|
||||
}
|
||||
|
||||
assertEquals(0, testSetup.getOnBirthdayCopyCount())
|
||||
assertEquals(0, testSetup.getOnCompleteCount())
|
||||
}
|
||||
|
||||
@Test
|
||||
@MediumTest
|
||||
fun copy_birthday_to_clipboard_content_test() {
|
||||
val testSetup = newTestSetup()
|
||||
|
||||
assertEquals(0, testSetup.getOnBirthdayCopyCount())
|
||||
|
||||
composeTestRule.onNodeWithTag(WALLET_BIRTHDAY).also {
|
||||
it.performScrollTo()
|
||||
it.performClick()
|
||||
}
|
||||
|
||||
assertEquals(1, testSetup.getOnBirthdayCopyCount())
|
||||
}
|
||||
|
||||
@Test
|
||||
@MediumTest
|
||||
fun click_finish_test() {
|
||||
val testSetup = newTestSetup()
|
||||
|
||||
assertEquals(0, testSetup.getOnBirthdayCopyCount())
|
||||
assertEquals(0, testSetup.getOnCompleteCount())
|
||||
|
||||
composeTestRule.onNodeWithText(
|
||||
text = getStringResource(R.string.new_wallet_recovery_button_finished),
|
||||
ignoreCase = true
|
||||
).also {
|
||||
it.performScrollTo()
|
||||
it.performClick()
|
||||
}
|
||||
|
||||
assertEquals(0, testSetup.getOnBirthdayCopyCount())
|
||||
assertEquals(1, testSetup.getOnCompleteCount())
|
||||
}
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
package co.electriccoin.zcash.ui.screen.newwalletrecovery.view
|
||||
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.ui.test.junit4.ComposeContentTestRule
|
||||
import androidx.compose.ui.test.junit4.createComposeRule
|
||||
import androidx.test.filters.MediumTest
|
||||
import cash.z.ecc.sdk.fixture.PersistableWalletFixture
|
||||
import co.electriccoin.zcash.test.UiTestPrerequisites
|
||||
import co.electriccoin.zcash.ui.common.compose.LocalScreenSecurity
|
||||
import co.electriccoin.zcash.ui.common.compose.ScreenSecurity
|
||||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||
import co.electriccoin.zcash.ui.fixture.VersionInfoFixture
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class NewWalletRecoveryViewsSecuredScreenTest : UiTestPrerequisites() {
|
||||
@get:Rule
|
||||
val composeTestRule = createComposeRule()
|
||||
|
||||
private fun newTestSetup() =
|
||||
TestSetup(composeTestRule).apply {
|
||||
setContentView()
|
||||
}
|
||||
|
||||
@Test
|
||||
@MediumTest
|
||||
fun acquireScreenSecurity() =
|
||||
runTest {
|
||||
val testSetup = newTestSetup()
|
||||
|
||||
assertEquals(1, testSetup.getSecureScreenCount())
|
||||
}
|
||||
|
||||
private class TestSetup(private val composeTestRule: ComposeContentTestRule) {
|
||||
private val screenSecurity = ScreenSecurity()
|
||||
|
||||
fun getSecureScreenCount() = screenSecurity.referenceCount.value
|
||||
|
||||
fun setContentView() {
|
||||
composeTestRule.setContent {
|
||||
CompositionLocalProvider(LocalScreenSecurity provides screenSecurity) {
|
||||
ZcashTheme {
|
||||
NewWalletRecovery(
|
||||
PersistableWalletFixture.new(),
|
||||
onSeedCopy = {},
|
||||
onBirthdayCopy = {},
|
||||
onComplete = {},
|
||||
versionInfo = VersionInfoFixture.new()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -163,7 +163,7 @@ private fun copyToClipboard(
|
|||
val clipboardManager = context.getSystemService(ClipboardManager::class.java)
|
||||
val data =
|
||||
ClipData.newPlainText(
|
||||
context.getString(R.string.new_wallet_recovery_seed_clipboard_tag),
|
||||
"TAG",
|
||||
text
|
||||
)
|
||||
clipboardManager.setPrimaryClip(data)
|
||||
|
|
|
@ -1,135 +0,0 @@
|
|||
package co.electriccoin.zcash.ui.screen.seedrecovery.view
|
||||
|
||||
import androidx.compose.ui.test.junit4.createComposeRule
|
||||
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||
import androidx.compose.ui.test.onNodeWithTag
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.performClick
|
||||
import androidx.compose.ui.test.performScrollTo
|
||||
import androidx.test.filters.MediumTest
|
||||
import co.electriccoin.zcash.test.UiTestPrerequisites
|
||||
import co.electriccoin.zcash.ui.R
|
||||
import co.electriccoin.zcash.ui.common.test.CommonTag.WALLET_BIRTHDAY
|
||||
import co.electriccoin.zcash.ui.design.component.CommonTag
|
||||
import co.electriccoin.zcash.ui.fixture.VersionInfoFixture
|
||||
import co.electriccoin.zcash.ui.test.getStringResource
|
||||
import org.junit.Rule
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class SeedRecoveryRecoveryViewTest : UiTestPrerequisites() {
|
||||
@get:Rule
|
||||
val composeTestRule = createComposeRule()
|
||||
|
||||
private fun newTestSetup(): SeedRecoveryTestSetup {
|
||||
return SeedRecoveryTestSetup(
|
||||
composeTestRule,
|
||||
VersionInfoFixture.new()
|
||||
).apply {
|
||||
setDefaultContent()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@MediumTest
|
||||
fun default_ui_state_test() {
|
||||
val testSetup = newTestSetup()
|
||||
|
||||
assertEquals(0, testSetup.getOnBirthdayCopyCount())
|
||||
assertEquals(0, testSetup.getOnCompleteCount())
|
||||
assertEquals(0, testSetup.getOnBackCount())
|
||||
|
||||
composeTestRule.onNodeWithContentDescription(getStringResource(R.string.back_navigation_content_description))
|
||||
.also {
|
||||
it.assertExists()
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithContentDescription(
|
||||
label = getStringResource(R.string.zcash_logo_content_description)
|
||||
).also {
|
||||
it.assertExists()
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithText(getStringResource(R.string.seed_recovery_header)).also {
|
||||
it.assertExists()
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithText(getStringResource(R.string.seed_recovery_description)).also {
|
||||
it.assertExists()
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithTag(CommonTag.CHIP_LAYOUT).also {
|
||||
it.performScrollTo()
|
||||
it.assertExists()
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithTag(WALLET_BIRTHDAY).also {
|
||||
it.performScrollTo()
|
||||
it.assertExists()
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText(
|
||||
getStringResource(R.string.seed_recovery_button_finished),
|
||||
ignoreCase = true
|
||||
)
|
||||
.also {
|
||||
it.performScrollTo()
|
||||
it.assertExists()
|
||||
}
|
||||
|
||||
assertEquals(0, testSetup.getOnBirthdayCopyCount())
|
||||
assertEquals(0, testSetup.getOnCompleteCount())
|
||||
assertEquals(0, testSetup.getOnBackCount())
|
||||
}
|
||||
|
||||
@Test
|
||||
@MediumTest
|
||||
fun back_test() {
|
||||
val testSetup = newTestSetup()
|
||||
|
||||
assertEquals(0, testSetup.getOnBackCount())
|
||||
|
||||
composeTestRule.onNodeWithContentDescription(getStringResource(R.string.back_navigation_content_description))
|
||||
.also {
|
||||
it.performClick()
|
||||
}
|
||||
|
||||
assertEquals(1, testSetup.getOnBackCount())
|
||||
}
|
||||
|
||||
@Test
|
||||
@MediumTest
|
||||
fun copy_birthday_to_clipboard_content_test() {
|
||||
val testSetup = newTestSetup()
|
||||
|
||||
assertEquals(0, testSetup.getOnBirthdayCopyCount())
|
||||
|
||||
composeTestRule.onNodeWithTag(WALLET_BIRTHDAY).also {
|
||||
it.performScrollTo()
|
||||
it.performClick()
|
||||
}
|
||||
|
||||
assertEquals(1, testSetup.getOnBirthdayCopyCount())
|
||||
}
|
||||
|
||||
@Test
|
||||
@MediumTest
|
||||
fun click_finish_test() {
|
||||
val testSetup = newTestSetup()
|
||||
|
||||
assertEquals(0, testSetup.getOnBirthdayCopyCount())
|
||||
assertEquals(0, testSetup.getOnCompleteCount())
|
||||
|
||||
composeTestRule.onNodeWithText(
|
||||
text = getStringResource(R.string.seed_recovery_button_finished),
|
||||
ignoreCase = true
|
||||
).also {
|
||||
it.performScrollTo()
|
||||
it.performClick()
|
||||
}
|
||||
|
||||
assertEquals(0, testSetup.getOnBirthdayCopyCount())
|
||||
assertEquals(1, testSetup.getOnCompleteCount())
|
||||
}
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
package co.electriccoin.zcash.ui.screen.seedrecovery.view
|
||||
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.ui.test.junit4.ComposeContentTestRule
|
||||
import androidx.compose.ui.test.junit4.createComposeRule
|
||||
import androidx.test.filters.MediumTest
|
||||
import cash.z.ecc.sdk.fixture.PersistableWalletFixture
|
||||
import co.electriccoin.zcash.test.UiTestPrerequisites
|
||||
import co.electriccoin.zcash.ui.common.compose.LocalScreenSecurity
|
||||
import co.electriccoin.zcash.ui.common.compose.ScreenSecurity
|
||||
import co.electriccoin.zcash.ui.common.model.TopAppBarSubTitleState
|
||||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||
import co.electriccoin.zcash.ui.fixture.VersionInfoFixture
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class SeedRecoveryRecoveryViewsSecuredScreenTest : UiTestPrerequisites() {
|
||||
@get:Rule
|
||||
val composeTestRule = createComposeRule()
|
||||
|
||||
private fun newTestSetup() =
|
||||
TestSetup(composeTestRule).apply {
|
||||
setContentView()
|
||||
}
|
||||
|
||||
@Test
|
||||
@MediumTest
|
||||
fun acquireScreenSecurity() =
|
||||
runTest {
|
||||
val testSetup = newTestSetup()
|
||||
|
||||
assertEquals(1, testSetup.getSecureScreenCount())
|
||||
}
|
||||
|
||||
private class TestSetup(private val composeTestRule: ComposeContentTestRule) {
|
||||
private val screenSecurity = ScreenSecurity()
|
||||
|
||||
fun getSecureScreenCount() = screenSecurity.referenceCount.value
|
||||
|
||||
fun setContentView() {
|
||||
composeTestRule.setContent {
|
||||
CompositionLocalProvider(LocalScreenSecurity provides screenSecurity) {
|
||||
ZcashTheme {
|
||||
SeedRecovery(
|
||||
PersistableWalletFixture.new(),
|
||||
onBack = {},
|
||||
onBirthdayCopy = {},
|
||||
onDone = {},
|
||||
onSeedCopy = {},
|
||||
topAppBarSubTitleState = TopAppBarSubTitleState.None,
|
||||
versionInfo = VersionInfoFixture.new(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
package co.electriccoin.zcash.ui.screen.seedrecovery.view
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.test.junit4.ComposeContentTestRule
|
||||
import cash.z.ecc.sdk.fixture.PersistableWalletFixture
|
||||
import co.electriccoin.zcash.ui.common.model.TopAppBarSubTitleState
|
||||
import co.electriccoin.zcash.ui.common.model.VersionInfo
|
||||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
class SeedRecoveryTestSetup(
|
||||
private val composeTestRule: ComposeContentTestRule,
|
||||
private val versionInfo: VersionInfo,
|
||||
) {
|
||||
private val onBirthdayCopyCount = AtomicInteger(0)
|
||||
|
||||
private val onCompleteCallbackCount = AtomicInteger(0)
|
||||
|
||||
private val onBackCount = AtomicInteger(0)
|
||||
|
||||
fun getOnBirthdayCopyCount(): Int {
|
||||
composeTestRule.waitForIdle()
|
||||
return onBirthdayCopyCount.get()
|
||||
}
|
||||
|
||||
fun getOnCompleteCount(): Int {
|
||||
composeTestRule.waitForIdle()
|
||||
return onCompleteCallbackCount.get()
|
||||
}
|
||||
|
||||
fun getOnBackCount(): Int {
|
||||
composeTestRule.waitForIdle()
|
||||
return onBackCount.get()
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Suppress("TestFunctionName")
|
||||
fun DefaultContent() {
|
||||
ZcashTheme {
|
||||
SeedRecovery(
|
||||
PersistableWalletFixture.new(),
|
||||
onBack = { onBackCount.incrementAndGet() },
|
||||
onBirthdayCopy = { onBirthdayCopyCount.incrementAndGet() },
|
||||
onDone = { onCompleteCallbackCount.incrementAndGet() },
|
||||
onSeedCopy = { /* Not tested - debug mode feature only */ },
|
||||
topAppBarSubTitleState = TopAppBarSubTitleState.None,
|
||||
versionInfo = versionInfo,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun setDefaultContent() {
|
||||
composeTestRule.setContent {
|
||||
DefaultContent()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,85 +0,0 @@
|
|||
package co.electriccoin.zcash.ui.screen.support.view
|
||||
|
||||
import androidx.compose.ui.test.junit4.StateRestorationTester
|
||||
import androidx.compose.ui.test.junit4.createComposeRule
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.performClick
|
||||
import androidx.compose.ui.test.performTextInput
|
||||
import androidx.test.filters.MediumTest
|
||||
import co.electriccoin.zcash.test.UiTestPrerequisites
|
||||
import co.electriccoin.zcash.ui.R
|
||||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||
import co.electriccoin.zcash.ui.test.getStringResource
|
||||
import co.electriccoin.zcash.ui.test.getStringResourceWithArgs
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import kotlin.test.Ignore
|
||||
|
||||
class SupportViewIntegrationTest : UiTestPrerequisites() {
|
||||
@get:Rule
|
||||
val composeTestRule = createComposeRule()
|
||||
|
||||
@Test
|
||||
@MediumTest
|
||||
fun message_state_restoration() {
|
||||
val restorationTester = StateRestorationTester(composeTestRule)
|
||||
val testSetup = newTestSetup()
|
||||
|
||||
restorationTester.setContent {
|
||||
ZcashTheme {
|
||||
testSetup.DefaultContent()
|
||||
}
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithText("I can haz cheezburger?").also {
|
||||
it.assertDoesNotExist()
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithText(getStringResource(R.string.support_hint)).also {
|
||||
it.performTextInput("I can haz cheezburger?")
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithText("I can haz cheezburger?").also {
|
||||
it.assertExists()
|
||||
}
|
||||
|
||||
restorationTester.emulateSavedInstanceStateRestore()
|
||||
|
||||
composeTestRule.onNodeWithText("I can haz cheezburger?").also {
|
||||
it.assertExists()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@MediumTest
|
||||
@Ignore("Will be updated as part of #1275")
|
||||
fun dialog_state_restoration() {
|
||||
val restorationTester = StateRestorationTester(composeTestRule)
|
||||
val testSetup = newTestSetup()
|
||||
|
||||
restorationTester.setContent {
|
||||
testSetup.DefaultContent()
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithText("I can haz cheezburger?").also {
|
||||
it.assertDoesNotExist()
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithText(getStringResource(R.string.support_send), ignoreCase = true).also {
|
||||
it.performClick()
|
||||
}
|
||||
|
||||
restorationTester.emulateSavedInstanceStateRestore()
|
||||
|
||||
val dialogContent =
|
||||
getStringResourceWithArgs(
|
||||
R.string.support_confirmation_explanation,
|
||||
getStringResource(R.string.app_name)
|
||||
)
|
||||
composeTestRule.onNodeWithText(dialogContent).also {
|
||||
it.assertExists()
|
||||
}
|
||||
}
|
||||
|
||||
private fun newTestSetup() = SupportViewTestSetup(composeTestRule)
|
||||
}
|
|
@ -1,134 +0,0 @@
|
|||
package co.electriccoin.zcash.ui.screen.support.view
|
||||
|
||||
import androidx.compose.ui.test.junit4.ComposeContentTestRule
|
||||
import androidx.compose.ui.test.junit4.createComposeRule
|
||||
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.performClick
|
||||
import androidx.compose.ui.test.performTextInput
|
||||
import androidx.test.filters.MediumTest
|
||||
import co.electriccoin.zcash.test.UiTestPrerequisites
|
||||
import co.electriccoin.zcash.ui.R
|
||||
import co.electriccoin.zcash.ui.test.getStringResource
|
||||
import co.electriccoin.zcash.ui.test.getStringResourceWithArgs
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import kotlin.test.Ignore
|
||||
|
||||
class SupportViewTest : UiTestPrerequisites() {
|
||||
@get:Rule
|
||||
val composeTestRule = createComposeRule()
|
||||
|
||||
companion object {
|
||||
internal val DEFAULT_MESSAGE = "I can haz cheezburger?"
|
||||
}
|
||||
|
||||
@Test
|
||||
@MediumTest
|
||||
fun back() {
|
||||
val testSetup = newTestSetup()
|
||||
|
||||
assertEquals(0, testSetup.getOnBackCount())
|
||||
|
||||
composeTestRule.clickBack()
|
||||
|
||||
assertEquals(1, testSetup.getOnBackCount())
|
||||
}
|
||||
|
||||
@Test
|
||||
@MediumTest
|
||||
@Ignore("Will be updated as part of #1275")
|
||||
fun send_shows_dialog() {
|
||||
val testSetup = newTestSetup()
|
||||
|
||||
assertEquals(0, testSetup.getOnSendCount())
|
||||
assertEquals(null, testSetup.getSendMessage())
|
||||
|
||||
composeTestRule.typeMessage()
|
||||
composeTestRule.clickSend()
|
||||
|
||||
assertEquals(0, testSetup.getOnSendCount())
|
||||
|
||||
val dialogContent =
|
||||
getStringResourceWithArgs(
|
||||
R.string.support_confirmation_explanation,
|
||||
getStringResource(R.string.app_name)
|
||||
)
|
||||
composeTestRule.onNodeWithText(dialogContent).also {
|
||||
it.assertExists()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@MediumTest
|
||||
@Ignore("Will be updated as part of #1275")
|
||||
fun dialog_confirm_sends() {
|
||||
val testSetup = newTestSetup()
|
||||
|
||||
assertEquals(0, testSetup.getOnSendCount())
|
||||
assertEquals(null, testSetup.getSendMessage())
|
||||
|
||||
composeTestRule.typeMessage()
|
||||
composeTestRule.clickSend()
|
||||
|
||||
composeTestRule.onNodeWithText(getStringResource(R.string.support_confirmation_dialog_ok)).also {
|
||||
it.performClick()
|
||||
}
|
||||
|
||||
assertEquals(1, testSetup.getOnSendCount())
|
||||
assertEquals(DEFAULT_MESSAGE, testSetup.getSendMessage())
|
||||
}
|
||||
|
||||
@Test
|
||||
@MediumTest
|
||||
@Ignore("Will be updated as part of #1275")
|
||||
fun dialog_cancel() {
|
||||
val testSetup = newTestSetup()
|
||||
|
||||
assertEquals(0, testSetup.getOnSendCount())
|
||||
assertEquals(null, testSetup.getSendMessage())
|
||||
|
||||
composeTestRule.typeMessage()
|
||||
composeTestRule.clickSend()
|
||||
|
||||
composeTestRule.onNodeWithText(getStringResource(R.string.support_confirmation_dialog_cancel)).also {
|
||||
it.performClick()
|
||||
}
|
||||
|
||||
val dialogContent =
|
||||
getStringResourceWithArgs(
|
||||
R.string.support_confirmation_explanation,
|
||||
getStringResource(R.string.app_name)
|
||||
)
|
||||
composeTestRule.onNodeWithText(dialogContent).also {
|
||||
it.assertDoesNotExist()
|
||||
}
|
||||
|
||||
assertEquals(0, testSetup.getOnSendCount())
|
||||
assertEquals(0, testSetup.getOnBackCount())
|
||||
}
|
||||
|
||||
private fun newTestSetup() =
|
||||
SupportViewTestSetup(composeTestRule).apply {
|
||||
setDefaultContent()
|
||||
}
|
||||
}
|
||||
|
||||
private fun ComposeContentTestRule.clickBack() {
|
||||
onNodeWithContentDescription(getStringResource(R.string.back_navigation_content_description)).also {
|
||||
it.performClick()
|
||||
}
|
||||
}
|
||||
|
||||
private fun ComposeContentTestRule.clickSend() {
|
||||
onNodeWithText(getStringResource(R.string.support_send), ignoreCase = true).also {
|
||||
it.performClick()
|
||||
}
|
||||
}
|
||||
|
||||
private fun ComposeContentTestRule.typeMessage() {
|
||||
onNodeWithText(getStringResource(R.string.support_hint)).also {
|
||||
it.performTextInput(SupportViewTest.DEFAULT_MESSAGE)
|
||||
}
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
package co.electriccoin.zcash.ui.screen.support.view
|
||||
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.test.junit4.ComposeContentTestRule
|
||||
import co.electriccoin.zcash.ui.common.model.TopAppBarSubTitleState
|
||||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
|
||||
class SupportViewTestSetup(private val composeTestRule: ComposeContentTestRule) {
|
||||
private val onBackCount = AtomicInteger(0)
|
||||
|
||||
private val onSendCount = AtomicInteger(0)
|
||||
|
||||
private val onSendMessage = AtomicReference<String>(null)
|
||||
|
||||
fun getOnBackCount(): Int {
|
||||
composeTestRule.waitForIdle()
|
||||
return onBackCount.get()
|
||||
}
|
||||
|
||||
fun getOnSendCount(): Int {
|
||||
composeTestRule.waitForIdle()
|
||||
return onSendCount.get()
|
||||
}
|
||||
|
||||
fun getSendMessage(): String? {
|
||||
composeTestRule.waitForIdle()
|
||||
return onSendMessage.get()
|
||||
}
|
||||
|
||||
// TODO [#1275]: Improve SupportView UI tests
|
||||
// TODO [#1275]: https://github.com/Electric-Coin-Company/zashi-android/issues/1275
|
||||
|
||||
@Composable
|
||||
@Suppress("TestFunctionName")
|
||||
fun DefaultContent() {
|
||||
Support(
|
||||
isShowingDialog = false,
|
||||
setShowDialog = {},
|
||||
onBack = {
|
||||
onBackCount.incrementAndGet()
|
||||
},
|
||||
onSend = {
|
||||
onSendCount.incrementAndGet()
|
||||
onSendMessage.set(it)
|
||||
},
|
||||
snackbarHostState = SnackbarHostState(),
|
||||
topAppBarSubTitleState = TopAppBarSubTitleState.None,
|
||||
)
|
||||
}
|
||||
|
||||
fun setDefaultContent() {
|
||||
composeTestRule.setContent {
|
||||
ZcashTheme {
|
||||
DefaultContent()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,20 +4,24 @@ import co.electriccoin.zcash.ui.common.usecase.CopyToClipboardUseCase
|
|||
import co.electriccoin.zcash.ui.common.usecase.DeleteAddressBookUseCase
|
||||
import co.electriccoin.zcash.ui.common.usecase.DeleteContactUseCase
|
||||
import co.electriccoin.zcash.ui.common.usecase.GetAddressesUseCase
|
||||
import co.electriccoin.zcash.ui.common.usecase.GetBackupPersistableWalletUseCase
|
||||
import co.electriccoin.zcash.ui.common.usecase.GetContactByAddressUseCase
|
||||
import co.electriccoin.zcash.ui.common.usecase.GetPersistableWalletUseCase
|
||||
import co.electriccoin.zcash.ui.common.usecase.GetSelectedEndpointUseCase
|
||||
import co.electriccoin.zcash.ui.common.usecase.GetSpendingKeyUseCase
|
||||
import co.electriccoin.zcash.ui.common.usecase.GetSupportUseCase
|
||||
import co.electriccoin.zcash.ui.common.usecase.GetSynchronizerUseCase
|
||||
import co.electriccoin.zcash.ui.common.usecase.GetTransparentAddressUseCase
|
||||
import co.electriccoin.zcash.ui.common.usecase.IsCoinbaseAvailableUseCase
|
||||
import co.electriccoin.zcash.ui.common.usecase.IsFlexaAvailableUseCase
|
||||
import co.electriccoin.zcash.ui.common.usecase.ObserveAddressBookContactsUseCase
|
||||
import co.electriccoin.zcash.ui.common.usecase.ObserveBackupPersistableWalletUseCase
|
||||
import co.electriccoin.zcash.ui.common.usecase.ObserveConfigurationUseCase
|
||||
import co.electriccoin.zcash.ui.common.usecase.ObserveContactByAddressUseCase
|
||||
import co.electriccoin.zcash.ui.common.usecase.ObserveContactPickedUseCase
|
||||
import co.electriccoin.zcash.ui.common.usecase.ObserveFastestServersUseCase
|
||||
import co.electriccoin.zcash.ui.common.usecase.ObserveIsFlexaAvailableUseCase
|
||||
import co.electriccoin.zcash.ui.common.usecase.ObservePersistableWalletUseCase
|
||||
import co.electriccoin.zcash.ui.common.usecase.ObserveSelectedEndpointUseCase
|
||||
import co.electriccoin.zcash.ui.common.usecase.ObserveSynchronizerUseCase
|
||||
import co.electriccoin.zcash.ui.common.usecase.ObserveWalletStateUseCase
|
||||
|
@ -25,6 +29,8 @@ import co.electriccoin.zcash.ui.common.usecase.PersistEndpointUseCase
|
|||
import co.electriccoin.zcash.ui.common.usecase.RefreshFastestServersUseCase
|
||||
import co.electriccoin.zcash.ui.common.usecase.RescanBlockchainUseCase
|
||||
import co.electriccoin.zcash.ui.common.usecase.SaveContactUseCase
|
||||
import co.electriccoin.zcash.ui.common.usecase.SendEmailUseCase
|
||||
import co.electriccoin.zcash.ui.common.usecase.SendSupportEmailUseCase
|
||||
import co.electriccoin.zcash.ui.common.usecase.SensitiveSettingsVisibleUseCase
|
||||
import co.electriccoin.zcash.ui.common.usecase.ShareImageUseCase
|
||||
import co.electriccoin.zcash.ui.common.usecase.UpdateContactUseCase
|
||||
|
@ -34,43 +40,50 @@ import co.electriccoin.zcash.ui.common.usecase.ValidateEndpointUseCase
|
|||
import co.electriccoin.zcash.ui.common.usecase.Zip321BuildUriUseCase
|
||||
import co.electriccoin.zcash.ui.common.usecase.Zip321ParseUriValidationUseCase
|
||||
import co.electriccoin.zcash.ui.common.usecase.Zip321ProposalFromUriUseCase
|
||||
import org.koin.core.module.dsl.factoryOf
|
||||
import org.koin.core.module.dsl.singleOf
|
||||
import org.koin.dsl.module
|
||||
|
||||
val useCaseModule =
|
||||
module {
|
||||
singleOf(::ObserveSynchronizerUseCase)
|
||||
singleOf(::GetSynchronizerUseCase)
|
||||
singleOf(::ObserveFastestServersUseCase)
|
||||
singleOf(::ObserveSelectedEndpointUseCase)
|
||||
singleOf(::RefreshFastestServersUseCase)
|
||||
singleOf(::PersistEndpointUseCase)
|
||||
singleOf(::ValidateEndpointUseCase)
|
||||
singleOf(::GetPersistableWalletUseCase)
|
||||
singleOf(::GetSelectedEndpointUseCase)
|
||||
singleOf(::ObserveConfigurationUseCase)
|
||||
singleOf(::RescanBlockchainUseCase)
|
||||
singleOf(::GetTransparentAddressUseCase)
|
||||
singleOf(::ObserveAddressBookContactsUseCase)
|
||||
singleOf(::DeleteAddressBookUseCase)
|
||||
singleOf(::ValidateContactAddressUseCase)
|
||||
singleOf(::ValidateContactNameUseCase)
|
||||
singleOf(::SaveContactUseCase)
|
||||
singleOf(::UpdateContactUseCase)
|
||||
singleOf(::DeleteContactUseCase)
|
||||
singleOf(::GetContactByAddressUseCase)
|
||||
singleOf(::ObserveContactByAddressUseCase)
|
||||
factoryOf(::ObserveSynchronizerUseCase)
|
||||
factoryOf(::GetSynchronizerUseCase)
|
||||
factoryOf(::ObserveFastestServersUseCase)
|
||||
factoryOf(::ObserveSelectedEndpointUseCase)
|
||||
factoryOf(::RefreshFastestServersUseCase)
|
||||
factoryOf(::PersistEndpointUseCase)
|
||||
factoryOf(::ValidateEndpointUseCase)
|
||||
factoryOf(::GetPersistableWalletUseCase)
|
||||
factoryOf(::GetSelectedEndpointUseCase)
|
||||
factoryOf(::ObserveConfigurationUseCase)
|
||||
factoryOf(::RescanBlockchainUseCase)
|
||||
factoryOf(::GetTransparentAddressUseCase)
|
||||
factoryOf(::ObserveAddressBookContactsUseCase)
|
||||
factoryOf(::DeleteAddressBookUseCase)
|
||||
factoryOf(::ValidateContactAddressUseCase)
|
||||
factoryOf(::ValidateContactNameUseCase)
|
||||
factoryOf(::SaveContactUseCase)
|
||||
factoryOf(::UpdateContactUseCase)
|
||||
factoryOf(::DeleteContactUseCase)
|
||||
factoryOf(::GetContactByAddressUseCase)
|
||||
factoryOf(::ObserveContactByAddressUseCase)
|
||||
singleOf(::ObserveContactPickedUseCase)
|
||||
singleOf(::GetAddressesUseCase)
|
||||
singleOf(::CopyToClipboardUseCase)
|
||||
singleOf(::IsFlexaAvailableUseCase)
|
||||
singleOf(::ObserveIsFlexaAvailableUseCase)
|
||||
singleOf(::ShareImageUseCase)
|
||||
singleOf(::Zip321BuildUriUseCase)
|
||||
singleOf(::Zip321ProposalFromUriUseCase)
|
||||
singleOf(::Zip321ParseUriValidationUseCase)
|
||||
singleOf(::ObserveWalletStateUseCase)
|
||||
singleOf(::IsCoinbaseAvailableUseCase)
|
||||
singleOf(::GetSpendingKeyUseCase)
|
||||
singleOf(::SensitiveSettingsVisibleUseCase)
|
||||
factoryOf(::GetAddressesUseCase)
|
||||
factoryOf(::CopyToClipboardUseCase)
|
||||
factoryOf(::ShareImageUseCase)
|
||||
factoryOf(::Zip321BuildUriUseCase)
|
||||
factoryOf(::Zip321ProposalFromUriUseCase)
|
||||
factoryOf(::Zip321ParseUriValidationUseCase)
|
||||
factoryOf(::ObserveWalletStateUseCase)
|
||||
factoryOf(::IsCoinbaseAvailableUseCase)
|
||||
factoryOf(::GetSpendingKeyUseCase)
|
||||
factoryOf(::ObservePersistableWalletUseCase)
|
||||
factoryOf(::ObserveBackupPersistableWalletUseCase)
|
||||
factoryOf(::GetBackupPersistableWalletUseCase)
|
||||
factoryOf(::GetSupportUseCase)
|
||||
factoryOf(::SendEmailUseCase)
|
||||
factoryOf(::SendSupportEmailUseCase)
|
||||
factoryOf(::IsFlexaAvailableUseCase)
|
||||
factoryOf(::ObserveIsFlexaAvailableUseCase)
|
||||
factoryOf(::SensitiveSettingsVisibleUseCase)
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import co.electriccoin.zcash.ui.screen.advancedsettings.viewmodel.AdvancedSettin
|
|||
import co.electriccoin.zcash.ui.screen.chooseserver.ChooseServerViewModel
|
||||
import co.electriccoin.zcash.ui.screen.contact.viewmodel.AddContactViewModel
|
||||
import co.electriccoin.zcash.ui.screen.contact.viewmodel.UpdateContactViewModel
|
||||
import co.electriccoin.zcash.ui.screen.feedback.viewmodel.FeedbackViewModel
|
||||
import co.electriccoin.zcash.ui.screen.integrations.viewmodel.IntegrationsViewModel
|
||||
import co.electriccoin.zcash.ui.screen.onboarding.viewmodel.OnboardingViewModel
|
||||
import co.electriccoin.zcash.ui.screen.paymentrequest.viewmodel.PaymentRequestViewModel
|
||||
|
@ -21,6 +22,8 @@ import co.electriccoin.zcash.ui.screen.restore.viewmodel.RestoreViewModel
|
|||
import co.electriccoin.zcash.ui.screen.restoresuccess.viewmodel.RestoreSuccessViewModel
|
||||
import co.electriccoin.zcash.ui.screen.scan.ScanNavigationArgs
|
||||
import co.electriccoin.zcash.ui.screen.scan.viewmodel.ScanViewModel
|
||||
import co.electriccoin.zcash.ui.screen.seed.SeedNavigationArgs
|
||||
import co.electriccoin.zcash.ui.screen.seed.viewmodel.SeedViewModel
|
||||
import co.electriccoin.zcash.ui.screen.send.SendViewModel
|
||||
import co.electriccoin.zcash.ui.screen.sendconfirmation.viewmodel.CreateTransactionsViewModel
|
||||
import co.electriccoin.zcash.ui.screen.settings.viewmodel.ScreenBrightnessViewModel
|
||||
|
@ -89,4 +92,13 @@ val viewModelModule =
|
|||
}
|
||||
viewModelOf(::IntegrationsViewModel)
|
||||
viewModelOf(::SendViewModel)
|
||||
viewModel { (args: SeedNavigationArgs) ->
|
||||
SeedViewModel(
|
||||
observePersistableWallet = get(),
|
||||
args = args,
|
||||
walletRepository = get(),
|
||||
observeBackupPersistableWallet = get(),
|
||||
)
|
||||
}
|
||||
viewModelOf(::FeedbackViewModel)
|
||||
}
|
||||
|
|
|
@ -47,10 +47,11 @@ import co.electriccoin.zcash.ui.screen.authentication.RETRY_TRIGGER_DELAY
|
|||
import co.electriccoin.zcash.ui.screen.authentication.WrapAuthentication
|
||||
import co.electriccoin.zcash.ui.screen.authentication.view.AnimationConstants
|
||||
import co.electriccoin.zcash.ui.screen.authentication.view.WelcomeAnimationAutostart
|
||||
import co.electriccoin.zcash.ui.screen.newwalletrecovery.WrapNewWalletRecovery
|
||||
import co.electriccoin.zcash.ui.screen.onboarding.WrapOnboarding
|
||||
import co.electriccoin.zcash.ui.screen.onboarding.persistExistingWalletWithSeedPhrase
|
||||
import co.electriccoin.zcash.ui.screen.securitywarning.WrapSecurityWarning
|
||||
import co.electriccoin.zcash.ui.screen.seed.SeedNavigationArgs
|
||||
import co.electriccoin.zcash.ui.screen.seed.WrapSeed
|
||||
import co.electriccoin.zcash.ui.screen.warning.viewmodel.StorageCheckViewModel
|
||||
import co.electriccoin.zcash.work.WorkIds
|
||||
import kotlinx.coroutines.delay
|
||||
|
@ -320,9 +321,9 @@ class MainActivity : FragmentActivity() {
|
|||
}
|
||||
|
||||
is SecretState.NeedsBackup -> {
|
||||
WrapNewWalletRecovery(
|
||||
secretState.persistableWallet,
|
||||
onBackupComplete = { walletViewModel.persistOnboardingState(OnboardingState.READY) }
|
||||
WrapSeed(
|
||||
args = SeedNavigationArgs.NEW_WALLET,
|
||||
goBackOverride = null
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -76,6 +76,7 @@ import co.electriccoin.zcash.ui.screen.disconnected.WrapDisconnected
|
|||
import co.electriccoin.zcash.ui.screen.exchangerate.optin.AndroidExchangeRateOptIn
|
||||
import co.electriccoin.zcash.ui.screen.exchangerate.settings.AndroidSettingsExchangeRateOptIn
|
||||
import co.electriccoin.zcash.ui.screen.exportdata.WrapExportPrivateData
|
||||
import co.electriccoin.zcash.ui.screen.feedback.WrapFeedback
|
||||
import co.electriccoin.zcash.ui.screen.home.WrapHome
|
||||
import co.electriccoin.zcash.ui.screen.integrations.WrapIntegrations
|
||||
import co.electriccoin.zcash.ui.screen.paymentrequest.WrapPaymentRequest
|
||||
|
@ -85,14 +86,14 @@ import co.electriccoin.zcash.ui.screen.receive.model.ReceiveAddressType
|
|||
import co.electriccoin.zcash.ui.screen.request.WrapRequest
|
||||
import co.electriccoin.zcash.ui.screen.scan.ScanNavigationArgs
|
||||
import co.electriccoin.zcash.ui.screen.scan.WrapScanValidator
|
||||
import co.electriccoin.zcash.ui.screen.seedrecovery.WrapSeedRecovery
|
||||
import co.electriccoin.zcash.ui.screen.seed.SeedNavigationArgs
|
||||
import co.electriccoin.zcash.ui.screen.seed.WrapSeed
|
||||
import co.electriccoin.zcash.ui.screen.send.ext.toSerializableAddress
|
||||
import co.electriccoin.zcash.ui.screen.send.model.SendArguments
|
||||
import co.electriccoin.zcash.ui.screen.sendconfirmation.WrapSendConfirmation
|
||||
import co.electriccoin.zcash.ui.screen.sendconfirmation.model.SendConfirmationArguments
|
||||
import co.electriccoin.zcash.ui.screen.sendconfirmation.model.SendConfirmationStage
|
||||
import co.electriccoin.zcash.ui.screen.settings.WrapSettings
|
||||
import co.electriccoin.zcash.ui.screen.support.WrapSupport
|
||||
import co.electriccoin.zcash.ui.screen.update.WrapCheckForUpdate
|
||||
import co.electriccoin.zcash.ui.screen.warning.WrapNotEnoughSpace
|
||||
import co.electriccoin.zcash.ui.screen.whatsnew.WrapWhatsNew
|
||||
|
@ -204,20 +205,16 @@ internal fun MainActivity.Navigation() {
|
|||
WrapChooseServer()
|
||||
}
|
||||
composable(SEED_RECOVERY) {
|
||||
WrapSeedRecovery(
|
||||
goBack = {
|
||||
WrapSeed(
|
||||
args = SeedNavigationArgs.RECOVERY,
|
||||
goBackOverride = {
|
||||
setSeedRecoveryAuthentication(false)
|
||||
navController.popBackStackJustOnce(SEED_RECOVERY)
|
||||
},
|
||||
onDone = {
|
||||
setSeedRecoveryAuthentication(false)
|
||||
navController.popBackStackJustOnce(SEED_RECOVERY)
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
composable(SUPPORT) {
|
||||
// Pop back stack won't be right if we deep link into support
|
||||
WrapSupport(goBack = { navController.popBackStackJustOnce(SUPPORT) })
|
||||
WrapFeedback()
|
||||
}
|
||||
composable(DELETE_WALLET) {
|
||||
WrapDeleteWallet(
|
||||
|
@ -234,7 +231,6 @@ internal fun MainActivity.Navigation() {
|
|||
composable(ABOUT) {
|
||||
WrapAbout(
|
||||
goBack = { navController.popBackStackJustOnce(ABOUT) },
|
||||
goWhatsNew = { navController.navigateJustOnce(WHATS_NEW) }
|
||||
)
|
||||
}
|
||||
composable(WHATS_NEW) {
|
||||
|
|
|
@ -0,0 +1,167 @@
|
|||
package co.electriccoin.zcash.ui.common.compose
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.core.MutableTransitionState
|
||||
import androidx.compose.animation.core.Spring
|
||||
import androidx.compose.animation.core.spring
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.scaleIn
|
||||
import androidx.compose.animation.scaleOut
|
||||
import androidx.compose.animation.slideInVertically
|
||||
import androidx.compose.animation.slideOutVertically
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.GenericShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import co.electriccoin.zcash.ui.R
|
||||
import co.electriccoin.zcash.ui.design.newcomponent.PreviewScreens
|
||||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||
import co.electriccoin.zcash.ui.design.theme.colors.ZashiColors
|
||||
import co.electriccoin.zcash.ui.design.theme.typography.ZashiTypography
|
||||
import co.electriccoin.zcash.ui.design.util.StringResource
|
||||
import co.electriccoin.zcash.ui.design.util.getValue
|
||||
import co.electriccoin.zcash.ui.design.util.stringRes
|
||||
|
||||
@Composable
|
||||
fun ZashiAnimatedTooltip(
|
||||
isVisible: Boolean,
|
||||
title: StringResource,
|
||||
message: StringResource,
|
||||
onDismissRequest: () -> Unit
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
visible = isVisible,
|
||||
enter = enterTransition(),
|
||||
exit = exitTransition(),
|
||||
) {
|
||||
ZashiTooltip(title, message, onDismissRequest)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ZashiAnimatedTooltip(
|
||||
visibleState: MutableTransitionState<Boolean>,
|
||||
title: StringResource,
|
||||
message: StringResource,
|
||||
onDismissRequest: () -> Unit
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
visibleState = visibleState,
|
||||
enter = enterTransition(),
|
||||
exit = exitTransition(),
|
||||
) {
|
||||
ZashiTooltip(title, message, onDismissRequest)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ZashiTooltip(
|
||||
title: StringResource,
|
||||
message: StringResource,
|
||||
onDismissRequest: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
showCaret: Boolean = true,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier.padding(horizontal = 22.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
if (showCaret) {
|
||||
Box(
|
||||
modifier =
|
||||
Modifier
|
||||
.width(16.dp)
|
||||
.height(8.dp)
|
||||
.background(ZashiColors.HintTooltips.surfacePrimary, TriangleShape)
|
||||
)
|
||||
}
|
||||
Box(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.background(ZashiColors.HintTooltips.surfacePrimary, RoundedCornerShape(8.dp))
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.clickable(onClick = onDismissRequest)
|
||||
.padding(start = 12.dp, bottom = 12.dp),
|
||||
) {
|
||||
Row {
|
||||
Column(
|
||||
modifier = Modifier.weight(1f)
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(top = 12.dp),
|
||||
color = ZashiColors.Text.textLight,
|
||||
style = ZashiTypography.textMd,
|
||||
text = title.getValue()
|
||||
)
|
||||
Spacer(modifier = Modifier.height(6.dp))
|
||||
Text(
|
||||
color = ZashiColors.Text.textLightSupport,
|
||||
style = ZashiTypography.textSm,
|
||||
text = message.getValue()
|
||||
)
|
||||
}
|
||||
IconButton(onClick = onDismissRequest) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_exchange_rate_unavailable_dialog_close),
|
||||
contentDescription = "",
|
||||
tint = ZashiColors.HintTooltips.defaultFg
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun exitTransition() =
|
||||
fadeOut() +
|
||||
scaleOut(animationSpec = spring(stiffness = Spring.StiffnessMedium)) +
|
||||
slideOutVertically()
|
||||
|
||||
@Composable
|
||||
private fun enterTransition() =
|
||||
fadeIn() +
|
||||
slideInVertically(spring(stiffness = Spring.StiffnessHigh)) +
|
||||
scaleIn(spring(stiffness = Spring.StiffnessMedium, dampingRatio = Spring.DampingRatioLowBouncy))
|
||||
|
||||
@PreviewScreens
|
||||
@Composable
|
||||
private fun Preview() =
|
||||
ZcashTheme {
|
||||
ZashiTooltip(
|
||||
title = stringRes(R.string.exchange_rate_unavailable_title),
|
||||
message = stringRes(R.string.exchange_rate_unavailable_subtitle),
|
||||
onDismissRequest = {}
|
||||
)
|
||||
}
|
||||
|
||||
private val TriangleShape =
|
||||
GenericShape { size, _ ->
|
||||
|
||||
// 1) Start at the top center
|
||||
moveTo(size.width / 2f, 0f)
|
||||
|
||||
// 2) Draw a line to the bottom right corner
|
||||
lineTo(size.width, size.height)
|
||||
|
||||
// 3) Draw a line to the bottom left corner and implicitly close the shape
|
||||
lineTo(0f, size.height)
|
||||
}
|
|
@ -0,0 +1,161 @@
|
|||
package co.electriccoin.zcash.ui.common.compose
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.TooltipBox
|
||||
import androidx.compose.material3.TooltipScope
|
||||
import androidx.compose.material3.TooltipState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.CacheDrawScope
|
||||
import androidx.compose.ui.draw.DrawResult
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.Path
|
||||
import androidx.compose.ui.layout.LayoutCoordinates
|
||||
import androidx.compose.ui.layout.boundsInWindow
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.unit.Density
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.DpSize
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.IntRect
|
||||
import androidx.compose.ui.unit.IntSize
|
||||
import androidx.compose.ui.unit.LayoutDirection
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.PopupPositionProvider
|
||||
|
||||
@Composable
|
||||
@ExperimentalMaterial3Api
|
||||
fun ZashiTooltipBox(
|
||||
tooltip: @Composable TooltipScope.() -> Unit,
|
||||
state: TooltipState,
|
||||
modifier: Modifier = Modifier,
|
||||
positionProvider: PopupPositionProvider = rememberTooltipPositionProvider(),
|
||||
focusable: Boolean = true,
|
||||
enableUserInput: Boolean = true,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
TooltipBox(
|
||||
positionProvider = positionProvider,
|
||||
tooltip = tooltip,
|
||||
state = state,
|
||||
modifier = modifier,
|
||||
focusable = focusable,
|
||||
enableUserInput = enableUserInput,
|
||||
content = content
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun rememberTooltipPositionProvider(spacingBetweenTooltipAndAnchor: Dp = 8.dp): PopupPositionProvider {
|
||||
val tooltipAnchorSpacing =
|
||||
with(LocalDensity.current) {
|
||||
spacingBetweenTooltipAndAnchor.roundToPx()
|
||||
}
|
||||
return remember(tooltipAnchorSpacing) {
|
||||
object : PopupPositionProvider {
|
||||
override fun calculatePosition(
|
||||
anchorBounds: IntRect,
|
||||
windowSize: IntSize,
|
||||
layoutDirection: LayoutDirection,
|
||||
popupContentSize: IntSize
|
||||
): IntOffset {
|
||||
val x = anchorBounds.left + (anchorBounds.width - popupContentSize.width) / 2
|
||||
|
||||
// Tooltip prefers to be above the anchor,
|
||||
// but if this causes the tooltip to overlap with the anchor
|
||||
// then we place it below the anchor
|
||||
|
||||
var y = anchorBounds.bottom + tooltipAnchorSpacing
|
||||
if (y + popupContentSize.height > windowSize.height) {
|
||||
y = anchorBounds.top - popupContentSize.height - tooltipAnchorSpacing
|
||||
}
|
||||
return IntOffset(x, y)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ExperimentalMaterial3Api
|
||||
fun CacheDrawScope.drawCaretWithPath(
|
||||
density: Density,
|
||||
configuration: Configuration,
|
||||
containerColor: Color,
|
||||
caretProperties: DpSize = DpSize(height = 8.dp, width = 16.dp),
|
||||
anchorLayoutCoordinates: LayoutCoordinates?
|
||||
): DrawResult {
|
||||
val path = Path()
|
||||
|
||||
if (anchorLayoutCoordinates != null) {
|
||||
val caretHeightPx: Int
|
||||
val caretWidthPx: Int
|
||||
val screenWidthPx: Int
|
||||
val screenHeightPx: Int
|
||||
val tooltipAnchorSpacing: Int
|
||||
with(density) {
|
||||
caretHeightPx = caretProperties.height.roundToPx()
|
||||
caretWidthPx = caretProperties.width.roundToPx()
|
||||
screenWidthPx = configuration.screenWidthDp.dp.roundToPx()
|
||||
screenHeightPx = configuration.screenHeightDp.dp.roundToPx()
|
||||
tooltipAnchorSpacing = 4.dp.roundToPx()
|
||||
}
|
||||
val anchorBounds = anchorLayoutCoordinates.boundsInWindow()
|
||||
val anchorLeft = anchorBounds.left
|
||||
val anchorRight = anchorBounds.right
|
||||
val anchorMid = (anchorRight + anchorLeft) / 2
|
||||
val anchorWidth = anchorRight - anchorLeft
|
||||
val tooltipWidth = this.size.width
|
||||
val tooltipHeight = this.size.height
|
||||
|
||||
val isCaretTop = (anchorBounds.bottom + tooltipAnchorSpacing + tooltipHeight) <= screenHeightPx
|
||||
val caretY =
|
||||
if (isCaretTop) {
|
||||
0f
|
||||
} else {
|
||||
tooltipHeight
|
||||
}
|
||||
|
||||
val position =
|
||||
if (anchorMid + tooltipWidth / 2 > screenWidthPx) {
|
||||
val anchorMidFromRightScreenEdge =
|
||||
screenWidthPx - anchorMid
|
||||
val caretX = tooltipWidth - anchorMidFromRightScreenEdge
|
||||
Offset(caretX, caretY)
|
||||
} else {
|
||||
val tooltipLeft =
|
||||
anchorLeft - (this.size.width / 2 - anchorWidth / 2)
|
||||
val caretX = anchorMid - maxOf(tooltipLeft, 0f)
|
||||
Offset(caretX, caretY)
|
||||
}
|
||||
|
||||
if (isCaretTop) {
|
||||
path.apply {
|
||||
moveTo(x = position.x, y = position.y)
|
||||
lineTo(x = position.x + caretWidthPx / 2, y = position.y)
|
||||
lineTo(x = position.x, y = position.y - caretHeightPx)
|
||||
lineTo(x = position.x - caretWidthPx / 2, y = position.y)
|
||||
close()
|
||||
}
|
||||
} else {
|
||||
path.apply {
|
||||
moveTo(x = position.x, y = position.y)
|
||||
lineTo(x = position.x + caretWidthPx / 2, y = position.y)
|
||||
lineTo(x = position.x, y = position.y + caretHeightPx.toFloat())
|
||||
lineTo(x = position.x - caretWidthPx / 2, y = position.y)
|
||||
close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return onDrawWithContent {
|
||||
if (anchorLayoutCoordinates != null) {
|
||||
drawContent()
|
||||
drawPath(
|
||||
path = path,
|
||||
color = containerColor
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,9 +3,10 @@ package co.electriccoin.zcash.ui.common.usecase
|
|||
import android.content.Context
|
||||
import co.electriccoin.zcash.spackle.ClipboardManagerUtil
|
||||
|
||||
class CopyToClipboardUseCase {
|
||||
class CopyToClipboardUseCase(
|
||||
private val context: Context
|
||||
) {
|
||||
operator fun invoke(
|
||||
context: Context,
|
||||
tag: String,
|
||||
value: String
|
||||
) = ClipboardManagerUtil.copyToClipboard(
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
package co.electriccoin.zcash.ui.common.usecase
|
||||
|
||||
import co.electriccoin.zcash.ui.common.repository.WalletRepository
|
||||
import co.electriccoin.zcash.ui.common.viewmodel.SecretState
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
class GetBackupPersistableWalletUseCase(
|
||||
private val walletRepository: WalletRepository
|
||||
) {
|
||||
suspend operator fun invoke() =
|
||||
walletRepository.secretState
|
||||
.map { (it as? SecretState.NeedsBackup)?.persistableWallet }
|
||||
.filterNotNull()
|
||||
.first()
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package co.electriccoin.zcash.ui.common.usecase
|
||||
|
||||
import android.content.Context
|
||||
import co.electriccoin.zcash.configuration.api.ConfigurationProvider
|
||||
import co.electriccoin.zcash.ui.screen.support.model.SupportInfo
|
||||
|
||||
class GetSupportUseCase(
|
||||
private val context: Context,
|
||||
private val androidConfigurationProvider: ConfigurationProvider
|
||||
) {
|
||||
suspend operator fun invoke() = SupportInfo.new(context, androidConfigurationProvider)
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package co.electriccoin.zcash.ui.common.usecase
|
||||
|
||||
import cash.z.ecc.android.sdk.model.PersistableWallet
|
||||
import co.electriccoin.zcash.ui.common.repository.WalletRepository
|
||||
import co.electriccoin.zcash.ui.common.viewmodel.SecretState
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
class ObserveBackupPersistableWalletUseCase(
|
||||
private val walletRepository: WalletRepository
|
||||
) {
|
||||
operator fun invoke(): Flow<PersistableWallet?> =
|
||||
walletRepository
|
||||
.secretState.map { (it as? SecretState.NeedsBackup)?.persistableWallet }
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
package co.electriccoin.zcash.ui.common.usecase
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import co.electriccoin.zcash.ui.design.util.StringResource
|
||||
import co.electriccoin.zcash.ui.design.util.getString
|
||||
import co.electriccoin.zcash.ui.util.EmailUtil
|
||||
|
||||
class SendEmailUseCase(
|
||||
private val context: Context,
|
||||
) {
|
||||
suspend operator fun invoke(
|
||||
address: StringResource,
|
||||
subject: StringResource,
|
||||
message: StringResource
|
||||
) {
|
||||
val intent =
|
||||
EmailUtil.newMailActivityIntent(
|
||||
recipientAddress = address.getString(context),
|
||||
messageSubject = subject.getString(context),
|
||||
messageBody = message.getString(context)
|
||||
).apply {
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
}
|
||||
|
||||
context.startActivity(intent)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
package co.electriccoin.zcash.ui.common.usecase
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import co.electriccoin.zcash.ui.R
|
||||
import co.electriccoin.zcash.ui.design.util.StringResource
|
||||
import co.electriccoin.zcash.ui.design.util.getString
|
||||
import co.electriccoin.zcash.ui.screen.feedback.model.FeedbackEmoji
|
||||
import co.electriccoin.zcash.ui.screen.support.model.SupportInfoType
|
||||
import co.electriccoin.zcash.ui.util.EmailUtil
|
||||
|
||||
class SendSupportEmailUseCase(
|
||||
private val context: Context,
|
||||
private val getSupport: GetSupportUseCase
|
||||
) {
|
||||
suspend operator fun invoke(
|
||||
emoji: FeedbackEmoji,
|
||||
message: StringResource
|
||||
) {
|
||||
val intent =
|
||||
EmailUtil.newMailActivityIntent(
|
||||
recipientAddress = context.getString(R.string.support_email_address),
|
||||
messageSubject = context.getString(R.string.app_name),
|
||||
messageBody =
|
||||
buildString {
|
||||
appendLine(
|
||||
context.getString(
|
||||
R.string.support_email_part_1,
|
||||
emoji.encoding,
|
||||
emoji.order.toString()
|
||||
)
|
||||
)
|
||||
appendLine()
|
||||
appendLine(context.getString(R.string.support_email_part_2, message.getString(context)))
|
||||
appendLine()
|
||||
appendLine()
|
||||
appendLine(getSupport().toSupportString(SupportInfoType.entries.toSet()))
|
||||
}
|
||||
).apply {
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
}
|
||||
|
||||
context.startActivity(intent)
|
||||
}
|
||||
}
|
|
@ -24,12 +24,8 @@ import kotlinx.coroutines.launch
|
|||
import org.koin.compose.koinInject
|
||||
|
||||
@Composable
|
||||
internal fun WrapAbout(
|
||||
goBack: () -> Unit,
|
||||
goWhatsNew: () -> Unit,
|
||||
) {
|
||||
internal fun WrapAbout(goBack: () -> Unit) {
|
||||
val activity = LocalActivity.current
|
||||
|
||||
val walletViewModel = koinActivityViewModel<WalletViewModel>()
|
||||
|
||||
val walletState = walletViewModel.walletStateInformation.collectAsStateWithLifecycle().value
|
||||
|
@ -64,7 +60,6 @@ internal fun WrapAbout(
|
|||
},
|
||||
snackbarHostState = snackbarHostState,
|
||||
topAppBarSubTitleState = walletState,
|
||||
onWhatsNew = goWhatsNew
|
||||
)
|
||||
}
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
package co.electriccoin.zcash.ui.screen.about.view
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
|
@ -14,6 +14,7 @@ import androidx.compose.material3.DropdownMenu
|
|||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.Text
|
||||
|
@ -22,23 +23,25 @@ import androidx.compose.runtime.getValue
|
|||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.platform.testTag
|
||||
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.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import co.electriccoin.zcash.ui.R
|
||||
import co.electriccoin.zcash.ui.common.model.TopAppBarSubTitleState
|
||||
import co.electriccoin.zcash.ui.common.model.VersionInfo
|
||||
import co.electriccoin.zcash.ui.design.component.BlankBgScaffold
|
||||
import co.electriccoin.zcash.ui.design.component.SmallTopAppBar
|
||||
import co.electriccoin.zcash.ui.design.component.TopAppBarBackNavigation
|
||||
import co.electriccoin.zcash.ui.design.component.ZashiButton
|
||||
import co.electriccoin.zcash.ui.design.component.ZashiSettingsListItem
|
||||
import co.electriccoin.zcash.ui.design.component.ZashiSettingsListItemState
|
||||
import co.electriccoin.zcash.ui.design.component.ZashiSmallTopAppBar
|
||||
import co.electriccoin.zcash.ui.design.component.ZashiTopAppBarBackNavigation
|
||||
import co.electriccoin.zcash.ui.design.component.ZashiVersion
|
||||
import co.electriccoin.zcash.ui.design.newcomponent.PreviewScreens
|
||||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||
import co.electriccoin.zcash.ui.design.util.scaffoldPadding
|
||||
import co.electriccoin.zcash.ui.design.theme.colors.ZashiColors
|
||||
import co.electriccoin.zcash.ui.design.theme.dimensions.ZashiDimensions
|
||||
import co.electriccoin.zcash.ui.design.theme.typography.ZashiTypography
|
||||
import co.electriccoin.zcash.ui.design.util.stringRes
|
||||
import co.electriccoin.zcash.ui.fixture.ConfigInfoFixture
|
||||
import co.electriccoin.zcash.ui.fixture.VersionInfoFixture
|
||||
import co.electriccoin.zcash.ui.screen.support.model.ConfigInfo
|
||||
|
@ -49,12 +52,11 @@ fun About(
|
|||
onBack: () -> Unit,
|
||||
configInfo: ConfigInfo,
|
||||
onPrivacyPolicy: () -> Unit,
|
||||
onWhatsNew: () -> Unit,
|
||||
snackbarHostState: SnackbarHostState,
|
||||
topAppBarSubTitleState: TopAppBarSubTitleState,
|
||||
versionInfo: VersionInfo,
|
||||
) {
|
||||
BlankBgScaffold(
|
||||
Scaffold(
|
||||
topBar = {
|
||||
AboutTopAppBar(
|
||||
onBack = onBack,
|
||||
|
@ -68,14 +70,18 @@ fun About(
|
|||
AboutMainContent(
|
||||
versionInfo = versionInfo,
|
||||
onPrivacyPolicy = onPrivacyPolicy,
|
||||
onWhatsNew = onWhatsNew,
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxHeight()
|
||||
.verticalScroll(
|
||||
rememberScrollState()
|
||||
)
|
||||
.scaffoldPadding(paddingValues)
|
||||
.padding(
|
||||
top = paddingValues.calculateTopPadding() + ZashiDimensions.Spacing.spacingLg,
|
||||
bottom = paddingValues.calculateBottomPadding() + ZashiDimensions.Spacing.spacing3xl,
|
||||
start = 4.dp,
|
||||
end = 4.dp
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -87,20 +93,16 @@ private fun AboutTopAppBar(
|
|||
configInfo: ConfigInfo,
|
||||
subTitleState: TopAppBarSubTitleState
|
||||
) {
|
||||
SmallTopAppBar(
|
||||
subTitle =
|
||||
ZashiSmallTopAppBar(
|
||||
subtitle =
|
||||
when (subTitleState) {
|
||||
TopAppBarSubTitleState.Disconnected -> stringResource(id = R.string.disconnected_label)
|
||||
TopAppBarSubTitleState.Restoring -> stringResource(id = R.string.restoring_wallet_label)
|
||||
TopAppBarSubTitleState.None -> null
|
||||
},
|
||||
titleText = stringResource(id = R.string.about_title),
|
||||
title = stringResource(id = R.string.about_title),
|
||||
navigationAction = {
|
||||
TopAppBarBackNavigation(
|
||||
backText = stringResource(id = R.string.back_navigation),
|
||||
backContentDescriptionText = stringResource(R.string.back_navigation_content_description),
|
||||
onBack = onBack
|
||||
)
|
||||
ZashiTopAppBarBackNavigation(onBack = onBack)
|
||||
},
|
||||
regularActions = {
|
||||
if (versionInfo.isDebuggable && !versionInfo.isRunningUnderTestService) {
|
||||
|
@ -149,84 +151,57 @@ private fun DebugMenu(
|
|||
|
||||
@Composable
|
||||
fun AboutMainContent(
|
||||
onWhatsNew: () -> Unit,
|
||||
onPrivacyPolicy: () -> Unit,
|
||||
versionInfo: VersionInfo,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Column(modifier) {
|
||||
Image(
|
||||
modifier =
|
||||
Modifier
|
||||
.height(ZcashTheme.dimens.inScreenZcashTextLogoHeight)
|
||||
.align(Alignment.CenterHorizontally),
|
||||
painter = painterResource(id = co.electriccoin.zcash.ui.design.R.drawable.zashi_text_logo_small),
|
||||
colorFilter = ColorFilter.tint(color = ZcashTheme.colors.secondaryColor),
|
||||
contentDescription = stringResource(R.string.zcash_logo_content_description)
|
||||
Text(
|
||||
modifier = Modifier.padding(horizontal = ZashiDimensions.Spacing.spacingXl),
|
||||
text = stringResource(id = R.string.about_subtitle),
|
||||
color = ZashiColors.Text.textPrimary,
|
||||
style = ZashiTypography.header6,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingSmall))
|
||||
|
||||
Text(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text =
|
||||
stringResource(
|
||||
R.string.about_version_format,
|
||||
versionInfo.versionName
|
||||
),
|
||||
textAlign = TextAlign.Center,
|
||||
style = ZcashTheme.typography.primary.titleSmall
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingLarge))
|
||||
Spacer(Modifier.height(12.dp))
|
||||
|
||||
Text(
|
||||
modifier = Modifier.padding(horizontal = ZashiDimensions.Spacing.spacingXl),
|
||||
text = stringResource(id = R.string.about_description),
|
||||
color = ZcashTheme.colors.textDescriptionDark,
|
||||
style = ZcashTheme.extendedTypography.aboutText
|
||||
color = ZashiColors.Text.textPrimary,
|
||||
style = ZashiTypography.textSm
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingUpLarge))
|
||||
Spacer(Modifier.height(32.dp))
|
||||
|
||||
ZashiButton(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
onClick = onWhatsNew,
|
||||
text = stringResource(R.string.about_button_whats_new),
|
||||
ZashiSettingsListItem(
|
||||
ZashiSettingsListItemState(
|
||||
icon = R.drawable.ic_settings_info,
|
||||
text = stringRes(R.string.about_button_privacy_policy),
|
||||
onClick = onPrivacyPolicy
|
||||
)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingSmall))
|
||||
Spacer(Modifier.weight(1f))
|
||||
|
||||
ZashiButton(
|
||||
ZashiVersion(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
onClick = onPrivacyPolicy,
|
||||
text = stringResource(R.string.about_button_privacy_policy),
|
||||
version = stringRes(R.string.settings_version, versionInfo.versionName)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@PreviewScreens
|
||||
@Composable
|
||||
private fun AboutPreview() {
|
||||
About(
|
||||
onBack = {},
|
||||
configInfo = ConfigInfoFixture.new(),
|
||||
onPrivacyPolicy = {},
|
||||
onWhatsNew = {},
|
||||
snackbarHostState = SnackbarHostState(),
|
||||
topAppBarSubTitleState = TopAppBarSubTitleState.None,
|
||||
versionInfo = VersionInfoFixture.new(),
|
||||
)
|
||||
}
|
||||
|
||||
@Preview("About")
|
||||
@Composable
|
||||
private fun AboutPreviewLight() =
|
||||
ZcashTheme(forceDarkMode = false) {
|
||||
AboutPreview()
|
||||
}
|
||||
|
||||
@Preview("About")
|
||||
@Composable
|
||||
private fun AboutPreviewDark() =
|
||||
ZcashTheme(forceDarkMode = true) {
|
||||
AboutPreview()
|
||||
private fun AboutPreview() =
|
||||
ZcashTheme {
|
||||
About(
|
||||
onBack = {},
|
||||
configInfo = ConfigInfoFixture.new(),
|
||||
onPrivacyPolicy = {},
|
||||
snackbarHostState = SnackbarHostState(),
|
||||
topAppBarSubTitleState = TopAppBarSubTitleState.None,
|
||||
versionInfo = VersionInfoFixture.new(),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -57,15 +57,15 @@ import co.electriccoin.zcash.ui.design.component.LottieProgress
|
|||
import co.electriccoin.zcash.ui.design.component.RadioButton
|
||||
import co.electriccoin.zcash.ui.design.component.RadioButtonCheckedContent
|
||||
import co.electriccoin.zcash.ui.design.component.RadioButtonState
|
||||
import co.electriccoin.zcash.ui.design.component.SmallTopAppBar
|
||||
import co.electriccoin.zcash.ui.design.component.TextFieldState
|
||||
import co.electriccoin.zcash.ui.design.component.TopAppBarBackNavigation
|
||||
import co.electriccoin.zcash.ui.design.component.ZashiBadge
|
||||
import co.electriccoin.zcash.ui.design.component.ZashiBottomBar
|
||||
import co.electriccoin.zcash.ui.design.component.ZashiButton
|
||||
import co.electriccoin.zcash.ui.design.component.ZashiHorizontalDivider
|
||||
import co.electriccoin.zcash.ui.design.component.ZashiSmallTopAppBar
|
||||
import co.electriccoin.zcash.ui.design.component.ZashiTextField
|
||||
import co.electriccoin.zcash.ui.design.component.ZashiTextFieldDefaults
|
||||
import co.electriccoin.zcash.ui.design.component.ZashiTopAppBarBackNavigation
|
||||
import co.electriccoin.zcash.ui.design.newcomponent.PreviewScreens
|
||||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||
import co.electriccoin.zcash.ui.design.theme.colors.ZashiColors
|
||||
|
@ -208,9 +208,9 @@ private fun ChooseServerTopAppBar(
|
|||
onBack: () -> Unit,
|
||||
subTitleState: TopAppBarSubTitleState
|
||||
) {
|
||||
SmallTopAppBar(
|
||||
titleText = stringResource(id = R.string.choose_server_title),
|
||||
subTitle =
|
||||
ZashiSmallTopAppBar(
|
||||
title = stringResource(id = R.string.choose_server_title),
|
||||
subtitle =
|
||||
when (subTitleState) {
|
||||
TopAppBarSubTitleState.Disconnected -> stringResource(id = R.string.disconnected_label)
|
||||
TopAppBarSubTitleState.Restoring -> stringResource(id = R.string.restoring_wallet_label)
|
||||
|
@ -219,11 +219,7 @@ private fun ChooseServerTopAppBar(
|
|||
modifier = Modifier.testTag(CHOOSE_SERVER_TOP_APP_BAR),
|
||||
showTitleLogo = true,
|
||||
navigationAction = {
|
||||
TopAppBarBackNavigation(
|
||||
backText = stringResource(id = R.string.back_navigation).uppercase(),
|
||||
backContentDescriptionText = stringResource(R.string.back_navigation_content_description),
|
||||
onBack = onBack
|
||||
)
|
||||
ZashiTopAppBarBackNavigation(onBack = onBack)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
@ -16,26 +16,29 @@ import androidx.compose.material3.Text
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import co.electriccoin.zcash.ui.R
|
||||
import co.electriccoin.zcash.ui.common.model.TopAppBarSubTitleState
|
||||
import co.electriccoin.zcash.ui.design.MINIMAL_WEIGHT
|
||||
import co.electriccoin.zcash.ui.design.component.Body
|
||||
import co.electriccoin.zcash.ui.design.component.LabeledCheckBox
|
||||
import co.electriccoin.zcash.ui.design.component.SmallTopAppBar
|
||||
import co.electriccoin.zcash.ui.design.component.TopAppBarBackNavigation
|
||||
import co.electriccoin.zcash.ui.design.component.TopScreenLogoTitle
|
||||
import co.electriccoin.zcash.ui.design.component.ZashiButton
|
||||
import co.electriccoin.zcash.ui.design.component.ZashiButtonDefaults
|
||||
import co.electriccoin.zcash.ui.design.component.ZashiCheckbox
|
||||
import co.electriccoin.zcash.ui.design.component.ZashiSmallTopAppBar
|
||||
import co.electriccoin.zcash.ui.design.component.ZashiTopAppBarBackNavigation
|
||||
import co.electriccoin.zcash.ui.design.newcomponent.PreviewScreens
|
||||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||
import co.electriccoin.zcash.ui.design.theme.colors.ZashiColors
|
||||
import co.electriccoin.zcash.ui.design.theme.dimensions.ZashiDimensions
|
||||
import co.electriccoin.zcash.ui.design.theme.typography.ZashiTypography
|
||||
import co.electriccoin.zcash.ui.design.util.scaffoldPadding
|
||||
import co.electriccoin.zcash.ui.design.util.stringRes
|
||||
|
||||
@Preview("Delete Wallet")
|
||||
@PreviewScreens
|
||||
@Composable
|
||||
private fun ExportPrivateDataPreview() {
|
||||
ZcashTheme(forceDarkMode = false) {
|
||||
private fun ExportPrivateDataPreview() =
|
||||
ZcashTheme {
|
||||
DeleteWallet(
|
||||
snackbarHostState = SnackbarHostState(),
|
||||
onBack = {},
|
||||
|
@ -43,7 +46,6 @@ private fun ExportPrivateDataPreview() {
|
|||
topAppBarSubTitleState = TopAppBarSubTitleState.None,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DeleteWallet(
|
||||
|
@ -77,17 +79,16 @@ private fun DeleteWalletDataTopAppBar(
|
|||
onBack: () -> Unit,
|
||||
subTitleState: TopAppBarSubTitleState
|
||||
) {
|
||||
SmallTopAppBar(
|
||||
subTitle =
|
||||
ZashiSmallTopAppBar(
|
||||
title = stringResource(R.string.delete_wallet_title),
|
||||
subtitle =
|
||||
when (subTitleState) {
|
||||
TopAppBarSubTitleState.Disconnected -> stringResource(id = R.string.disconnected_label)
|
||||
TopAppBarSubTitleState.Restoring -> stringResource(id = R.string.restoring_wallet_label)
|
||||
TopAppBarSubTitleState.None -> null
|
||||
},
|
||||
navigationAction = {
|
||||
TopAppBarBackNavigation(
|
||||
backText = stringResource(id = R.string.back_navigation).uppercase(),
|
||||
backContentDescriptionText = stringResource(R.string.back_navigation_content_description),
|
||||
ZashiTopAppBarBackNavigation(
|
||||
onBack = onBack
|
||||
)
|
||||
}
|
||||
|
@ -99,46 +100,36 @@ private fun DeleteWalletContent(
|
|||
onConfirm: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val appName = stringResource(id = R.string.app_name)
|
||||
|
||||
Column(
|
||||
modifier = modifier,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
modifier = modifier
|
||||
) {
|
||||
TopScreenLogoTitle(
|
||||
title = stringResource(R.string.delete_wallet_title, appName),
|
||||
logoContentDescription = stringResource(R.string.zcash_logo_content_description)
|
||||
Text(
|
||||
text = stringResource(R.string.delete_wallet_title),
|
||||
style = ZashiTypography.header6,
|
||||
color = ZashiColors.Text.textPrimary,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
|
||||
Spacer(Modifier.height(ZcashTheme.dimens.spacingBig))
|
||||
Spacer(Modifier.height(ZashiDimensions.Spacing.spacingXl))
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.delete_wallet_text_1),
|
||||
style = ZcashTheme.extendedTypography.deleteWalletWarnStyle
|
||||
style = ZashiTypography.textMd,
|
||||
color = ZashiColors.Text.textPrimary,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
|
||||
Spacer(Modifier.height(ZcashTheme.dimens.spacingUpLarge))
|
||||
Spacer(Modifier.height(ZashiDimensions.Spacing.spacingXl))
|
||||
|
||||
Body(
|
||||
text =
|
||||
stringResource(
|
||||
R.string.delete_wallet_text_2,
|
||||
appName
|
||||
)
|
||||
Text(
|
||||
text = stringResource(R.string.delete_wallet_text_2),
|
||||
style = ZashiTypography.textSm,
|
||||
color = ZashiColors.Text.textPrimary,
|
||||
)
|
||||
|
||||
Spacer(Modifier.height(ZcashTheme.dimens.spacingDefault))
|
||||
|
||||
val checkedState = rememberSaveable { mutableStateOf(false) }
|
||||
Row(Modifier.fillMaxWidth()) {
|
||||
LabeledCheckBox(
|
||||
checked = checkedState.value,
|
||||
onCheckedChange = {
|
||||
checkedState.value = it
|
||||
},
|
||||
text = stringResource(R.string.delete_wallet_acknowledge),
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(
|
||||
modifier =
|
||||
|
@ -147,13 +138,26 @@ private fun DeleteWalletContent(
|
|||
.weight(MINIMAL_WEIGHT)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingDefault))
|
||||
Spacer(modifier = Modifier.height(ZashiDimensions.Spacing.spacingXl))
|
||||
|
||||
Row(Modifier.fillMaxWidth()) {
|
||||
ZashiCheckbox(
|
||||
isChecked = checkedState.value,
|
||||
onClick = {
|
||||
checkedState.value = checkedState.value.not()
|
||||
},
|
||||
text = stringRes(R.string.delete_wallet_acknowledge),
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(Modifier.height(ZashiDimensions.Spacing.spacingLg))
|
||||
|
||||
ZashiButton(
|
||||
onClick = onConfirm,
|
||||
text = stringResource(R.string.delete_wallet_button, appName),
|
||||
text = stringResource(R.string.delete_wallet_button),
|
||||
enabled = checkedState.value,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
colors = ZashiButtonDefaults.destructive1Colors()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,42 +1,13 @@
|
|||
package co.electriccoin.zcash.ui.screen.exchangerate.widget
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.core.MutableTransitionState
|
||||
import androidx.compose.animation.core.Spring
|
||||
import androidx.compose.animation.core.spring
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.scaleIn
|
||||
import androidx.compose.animation.scaleOut
|
||||
import androidx.compose.animation.slideInVertically
|
||||
import androidx.compose.animation.slideOutVertically
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.GenericShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Popup
|
||||
import co.electriccoin.zcash.ui.R
|
||||
import co.electriccoin.zcash.ui.design.newcomponent.PreviewScreens
|
||||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||
import co.electriccoin.zcash.ui.design.theme.colors.ZashiColors
|
||||
import co.electriccoin.zcash.ui.design.theme.typography.ZashiTypography
|
||||
import co.electriccoin.zcash.ui.common.compose.ZashiAnimatedTooltip
|
||||
import co.electriccoin.zcash.ui.design.util.stringRes
|
||||
|
||||
@Composable
|
||||
internal fun StyledExchangeUnavailablePopup(
|
||||
|
@ -49,88 +20,11 @@ internal fun StyledExchangeUnavailablePopup(
|
|||
onDismissRequest = onDismissRequest,
|
||||
offset = offset
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
ZashiAnimatedTooltip(
|
||||
visibleState = transitionState,
|
||||
enter =
|
||||
fadeIn() +
|
||||
slideInVertically(spring(stiffness = Spring.StiffnessHigh)) +
|
||||
scaleIn(spring(stiffness = Spring.StiffnessMedium, dampingRatio = Spring.DampingRatioLowBouncy)),
|
||||
exit =
|
||||
fadeOut() +
|
||||
scaleOut(animationSpec = spring(stiffness = Spring.StiffnessMedium)) +
|
||||
slideOutVertically(),
|
||||
) {
|
||||
PopupContent(onDismissRequest = onDismissRequest)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
@Composable
|
||||
private fun PopupContent(onDismissRequest: () -> Unit) {
|
||||
Column(
|
||||
modifier = Modifier.padding(horizontal = 22.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Box(
|
||||
modifier =
|
||||
Modifier
|
||||
.width(16.dp)
|
||||
.height(8.dp)
|
||||
.background(ZashiColors.HintTooltips.surfacePrimary, TriangleShape)
|
||||
title = stringRes(R.string.exchange_rate_unavailable_title),
|
||||
message = stringRes(R.string.exchange_rate_unavailable_subtitle),
|
||||
onDismissRequest = onDismissRequest
|
||||
)
|
||||
Box(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.background(ZashiColors.HintTooltips.surfacePrimary, RoundedCornerShape(8.dp))
|
||||
.padding(start = 12.dp, bottom = 12.dp),
|
||||
) {
|
||||
Row {
|
||||
Column(
|
||||
modifier = Modifier.weight(1f)
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(top = 12.dp),
|
||||
color = ZashiColors.Text.textLight,
|
||||
style = ZashiTypography.textMd,
|
||||
text = stringResource(R.string.exchange_rate_unavailable_title)
|
||||
)
|
||||
Spacer(modifier = Modifier.height(6.dp))
|
||||
Text(
|
||||
color = ZashiColors.Text.textLightSupport,
|
||||
style = ZashiTypography.textSm,
|
||||
text = stringResource(id = R.string.exchange_rate_unavailable_subtitle)
|
||||
)
|
||||
}
|
||||
IconButton(onClick = onDismissRequest) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_exchange_rate_unavailable_dialog_close),
|
||||
contentDescription = "",
|
||||
tint = ZashiColors.HintTooltips.defaultBg
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val TriangleShape =
|
||||
GenericShape { size, _ ->
|
||||
|
||||
// 1) Start at the top center
|
||||
moveTo(size.width / 2f, 0f)
|
||||
|
||||
// 2) Draw a line to the bottom right corner
|
||||
lineTo(size.width, size.height)
|
||||
|
||||
// 3) Draw a line to the bottom left corner and implicitly close the shape
|
||||
lineTo(0f, size.height)
|
||||
}
|
||||
|
||||
@Suppress("UnusedPrivateMember")
|
||||
@PreviewScreens
|
||||
@Composable
|
||||
private fun PopupContentPreview() =
|
||||
ZcashTheme {
|
||||
PopupContent(onDismissRequest = {})
|
||||
}
|
||||
|
|
|
@ -6,5 +6,4 @@ package co.electriccoin.zcash.ui.screen.exportdata.view
|
|||
object ExportPrivateDataScreenTag {
|
||||
const val AGREE_CHECKBOX_TAG = "agree_checkbox"
|
||||
const val WARNING_TEXT_TAG = "warning_text"
|
||||
const val ADDITIONAL_TEXT_TAG = "additional_text"
|
||||
}
|
||||
|
|
|
@ -1,52 +1,36 @@
|
|||
package co.electriccoin.zcash.ui.screen.exportdata.view
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import co.electriccoin.zcash.ui.R
|
||||
import co.electriccoin.zcash.ui.common.model.TopAppBarSubTitleState
|
||||
import co.electriccoin.zcash.ui.design.MINIMAL_WEIGHT
|
||||
import co.electriccoin.zcash.ui.design.component.BlankBgScaffold
|
||||
import co.electriccoin.zcash.ui.design.component.Body
|
||||
import co.electriccoin.zcash.ui.design.component.LabeledCheckBox
|
||||
import co.electriccoin.zcash.ui.design.component.SmallTopAppBar
|
||||
import co.electriccoin.zcash.ui.design.component.TopAppBarBackNavigation
|
||||
import co.electriccoin.zcash.ui.design.component.TopScreenLogoTitle
|
||||
import co.electriccoin.zcash.ui.design.component.ZashiButton
|
||||
import co.electriccoin.zcash.ui.design.component.ZashiCheckbox
|
||||
import co.electriccoin.zcash.ui.design.component.ZashiSmallTopAppBar
|
||||
import co.electriccoin.zcash.ui.design.component.ZashiTopAppBarBackNavigation
|
||||
import co.electriccoin.zcash.ui.design.newcomponent.PreviewScreenSizes
|
||||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||
import co.electriccoin.zcash.ui.design.theme.colors.ZashiColors
|
||||
import co.electriccoin.zcash.ui.design.theme.dimensions.ZashiDimensions
|
||||
import co.electriccoin.zcash.ui.design.theme.typography.ZashiTypography
|
||||
import co.electriccoin.zcash.ui.design.util.scaffoldPadding
|
||||
|
||||
@Preview("Export Private Data")
|
||||
@Composable
|
||||
private fun ExportPrivateDataPreview() {
|
||||
ZcashTheme(forceDarkMode = false) {
|
||||
ExportPrivateData(
|
||||
snackbarHostState = SnackbarHostState(),
|
||||
onBack = {},
|
||||
onAgree = {},
|
||||
onConfirm = {},
|
||||
topAppBarSubTitleState = TopAppBarSubTitleState.None,
|
||||
)
|
||||
}
|
||||
}
|
||||
import co.electriccoin.zcash.ui.design.util.stringRes
|
||||
|
||||
@Composable
|
||||
fun ExportPrivateData(
|
||||
|
@ -56,7 +40,7 @@ fun ExportPrivateData(
|
|||
onConfirm: () -> Unit,
|
||||
topAppBarSubTitleState: TopAppBarSubTitleState,
|
||||
) {
|
||||
BlankBgScaffold(
|
||||
Scaffold(
|
||||
topBar = {
|
||||
ExportPrivateDataTopAppBar(
|
||||
onBack = onBack,
|
||||
|
@ -82,19 +66,16 @@ private fun ExportPrivateDataTopAppBar(
|
|||
onBack: () -> Unit,
|
||||
subTitleState: TopAppBarSubTitleState
|
||||
) {
|
||||
SmallTopAppBar(
|
||||
subTitle =
|
||||
ZashiSmallTopAppBar(
|
||||
title = stringResource(R.string.export_data_title),
|
||||
subtitle =
|
||||
when (subTitleState) {
|
||||
TopAppBarSubTitleState.Disconnected -> stringResource(id = R.string.disconnected_label)
|
||||
TopAppBarSubTitleState.Restoring -> stringResource(id = R.string.restoring_wallet_label)
|
||||
TopAppBarSubTitleState.None -> null
|
||||
},
|
||||
navigationAction = {
|
||||
TopAppBarBackNavigation(
|
||||
backText = stringResource(id = R.string.back_navigation).uppercase(),
|
||||
backContentDescriptionText = stringResource(R.string.back_navigation_content_description),
|
||||
onBack = onBack
|
||||
)
|
||||
ZashiTopAppBarBackNavigation(onBack = onBack)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
@ -105,50 +86,35 @@ private fun ExportPrivateDataContent(
|
|||
onConfirm: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
TopScreenLogoTitle(
|
||||
title = stringResource(R.string.export_data_header),
|
||||
logoContentDescription = stringResource(R.string.zcash_logo_content_description)
|
||||
Column(modifier = modifier) {
|
||||
Text(
|
||||
text = stringResource(R.string.export_data_header),
|
||||
style = ZashiTypography.header6,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = ZashiColors.Text.textPrimary
|
||||
)
|
||||
|
||||
Spacer(Modifier.height(ZcashTheme.dimens.spacingLarge))
|
||||
|
||||
Body(
|
||||
modifier = Modifier.testTag(ExportPrivateDataScreenTag.WARNING_TEXT_TAG),
|
||||
text = stringResource(R.string.export_data_text_1)
|
||||
)
|
||||
|
||||
Spacer(Modifier.height(ZcashTheme.dimens.spacingDefault))
|
||||
Spacer(Modifier.height(ZashiDimensions.Spacing.spacingLg))
|
||||
|
||||
Text(
|
||||
modifier = Modifier.testTag(ExportPrivateDataScreenTag.ADDITIONAL_TEXT_TAG),
|
||||
text = stringResource(R.string.export_data_text_2),
|
||||
fontSize = 14.sp
|
||||
modifier = Modifier.testTag(ExportPrivateDataScreenTag.WARNING_TEXT_TAG),
|
||||
text = stringResource(R.string.export_data_text),
|
||||
style = ZashiTypography.textSm,
|
||||
color = ZashiColors.Text.textPrimary
|
||||
)
|
||||
|
||||
Spacer(Modifier.height(ZcashTheme.dimens.spacingDefault))
|
||||
Spacer(Modifier.weight(1f))
|
||||
|
||||
val checkedState = rememberSaveable { mutableStateOf(false) }
|
||||
Row(Modifier.fillMaxWidth()) {
|
||||
LabeledCheckBox(
|
||||
checked = checkedState.value,
|
||||
onCheckedChange = {
|
||||
checkedState.value = it
|
||||
onAgree(it)
|
||||
},
|
||||
text = stringResource(R.string.export_data_agree),
|
||||
checkBoxTestTag = ExportPrivateDataScreenTag.AGREE_CHECKBOX_TAG
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxHeight()
|
||||
.weight(MINIMAL_WEIGHT)
|
||||
ZashiCheckbox(
|
||||
modifier = Modifier.testTag(ExportPrivateDataScreenTag.AGREE_CHECKBOX_TAG),
|
||||
isChecked = checkedState.value,
|
||||
onClick = {
|
||||
val new = checkedState.value.not()
|
||||
checkedState.value = new
|
||||
onAgree(new)
|
||||
},
|
||||
text = stringRes(R.string.export_data_agree),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingLarge))
|
||||
|
@ -161,3 +127,16 @@ private fun ExportPrivateDataContent(
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
@PreviewScreenSizes
|
||||
@Composable
|
||||
private fun ExportPrivateDataPreview() =
|
||||
ZcashTheme {
|
||||
ExportPrivateData(
|
||||
snackbarHostState = SnackbarHostState(),
|
||||
onBack = {},
|
||||
onAgree = {},
|
||||
onConfirm = {},
|
||||
topAppBarSubTitleState = TopAppBarSubTitleState.None,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
@file:Suppress("ktlint:standard:filename")
|
||||
|
||||
package co.electriccoin.zcash.ui.screen.feedback
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import co.electriccoin.zcash.di.koinActivityViewModel
|
||||
import co.electriccoin.zcash.ui.common.compose.LocalNavController
|
||||
import co.electriccoin.zcash.ui.common.viewmodel.WalletViewModel
|
||||
import co.electriccoin.zcash.ui.design.component.AppAlertDialog
|
||||
import co.electriccoin.zcash.ui.screen.feedback.view.FeedbackView
|
||||
import co.electriccoin.zcash.ui.screen.feedback.viewmodel.FeedbackViewModel
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
|
||||
@Composable
|
||||
internal fun WrapFeedback() {
|
||||
val navController = LocalNavController.current
|
||||
val walletViewModel = koinActivityViewModel<WalletViewModel>()
|
||||
val viewModel = koinViewModel<FeedbackViewModel>()
|
||||
|
||||
val walletState by walletViewModel.walletStateInformation.collectAsStateWithLifecycle()
|
||||
val state by viewModel.state.collectAsStateWithLifecycle()
|
||||
val dialogState by viewModel.dialogState.collectAsStateWithLifecycle()
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
viewModel.onBackNavigationCommand.collect {
|
||||
navController.popBackStack()
|
||||
}
|
||||
}
|
||||
|
||||
BackHandler {
|
||||
state?.onBack?.invoke()
|
||||
}
|
||||
|
||||
state?.let {
|
||||
FeedbackView(
|
||||
state = it,
|
||||
topAppBarSubTitleState = walletState
|
||||
)
|
||||
}
|
||||
|
||||
dialogState?.let {
|
||||
AppAlertDialog(
|
||||
state = it
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
package co.electriccoin.zcash.ui.screen.feedback.model
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import co.electriccoin.zcash.ui.R
|
||||
import co.electriccoin.zcash.ui.design.component.ButtonState
|
||||
import co.electriccoin.zcash.ui.design.component.TextFieldState
|
||||
|
||||
data class FeedbackState(
|
||||
val onBack: () -> Unit,
|
||||
val emojiState: FeedbackEmojiState,
|
||||
val feedback: TextFieldState,
|
||||
val sendButton: ButtonState
|
||||
)
|
||||
|
||||
data class FeedbackEmojiState(
|
||||
val selection: FeedbackEmoji,
|
||||
val onSelected: (FeedbackEmoji) -> Unit,
|
||||
)
|
||||
|
||||
enum class FeedbackEmoji(
|
||||
@DrawableRes val res: Int,
|
||||
val order: Int,
|
||||
val encoding: String
|
||||
) {
|
||||
FIRST(
|
||||
res = R.drawable.ic_emoji_1,
|
||||
order = 1,
|
||||
encoding = "😠"
|
||||
),
|
||||
SECOND(
|
||||
res = R.drawable.ic_emoji_2,
|
||||
order = 2,
|
||||
encoding = "😒"
|
||||
),
|
||||
THIRD(
|
||||
res = R.drawable.ic_emoji_3,
|
||||
order = 3,
|
||||
encoding = "😊"
|
||||
),
|
||||
FOURTH(
|
||||
res = R.drawable.ic_emoji_4,
|
||||
order = 4,
|
||||
encoding = "😄"
|
||||
),
|
||||
FIFTH(
|
||||
res = R.drawable.ic_emoji_5,
|
||||
order = 5,
|
||||
encoding = "😍"
|
||||
)
|
||||
}
|
|
@ -0,0 +1,268 @@
|
|||
package co.electriccoin.zcash.ui.screen.feedback.view
|
||||
|
||||
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.Arrangement.spacedBy
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.aspectRatio
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import co.electriccoin.zcash.ui.R
|
||||
import co.electriccoin.zcash.ui.common.model.TopAppBarSubTitleState
|
||||
import co.electriccoin.zcash.ui.design.component.BlankSurface
|
||||
import co.electriccoin.zcash.ui.design.component.ButtonState
|
||||
import co.electriccoin.zcash.ui.design.component.TextFieldState
|
||||
import co.electriccoin.zcash.ui.design.component.ZashiButton
|
||||
import co.electriccoin.zcash.ui.design.component.ZashiSmallTopAppBar
|
||||
import co.electriccoin.zcash.ui.design.component.ZashiTextField
|
||||
import co.electriccoin.zcash.ui.design.component.ZashiTopAppBarBackNavigation
|
||||
import co.electriccoin.zcash.ui.design.newcomponent.PreviewScreens
|
||||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||
import co.electriccoin.zcash.ui.design.theme.colors.ZashiColors
|
||||
import co.electriccoin.zcash.ui.design.theme.dimensions.ZashiDimensions
|
||||
import co.electriccoin.zcash.ui.design.theme.typography.ZashiTypography
|
||||
import co.electriccoin.zcash.ui.design.util.scaffoldPadding
|
||||
import co.electriccoin.zcash.ui.design.util.stringRes
|
||||
import co.electriccoin.zcash.ui.screen.feedback.model.FeedbackEmoji
|
||||
import co.electriccoin.zcash.ui.screen.feedback.model.FeedbackEmojiState
|
||||
import co.electriccoin.zcash.ui.screen.feedback.model.FeedbackState
|
||||
|
||||
@Composable
|
||||
fun FeedbackView(
|
||||
state: FeedbackState,
|
||||
topAppBarSubTitleState: TopAppBarSubTitleState,
|
||||
) {
|
||||
Scaffold(
|
||||
topBar = {
|
||||
SupportTopAppBar(
|
||||
state = state,
|
||||
subTitleState = topAppBarSubTitleState,
|
||||
)
|
||||
},
|
||||
) { paddingValues ->
|
||||
SupportMainContent(
|
||||
state = state,
|
||||
modifier = Modifier.scaffoldPadding(paddingValues)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SupportTopAppBar(
|
||||
state: FeedbackState,
|
||||
subTitleState: TopAppBarSubTitleState
|
||||
) {
|
||||
ZashiSmallTopAppBar(
|
||||
subtitle =
|
||||
when (subTitleState) {
|
||||
TopAppBarSubTitleState.Disconnected -> stringResource(id = R.string.disconnected_label)
|
||||
TopAppBarSubTitleState.Restoring -> stringResource(id = R.string.restoring_wallet_label)
|
||||
TopAppBarSubTitleState.None -> null
|
||||
},
|
||||
title = stringResource(id = R.string.support_header),
|
||||
navigationAction = {
|
||||
ZashiTopAppBarBackNavigation(onBack = state.onBack)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SupportMainContent(
|
||||
state: FeedbackState,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
|
||||
Column(
|
||||
Modifier
|
||||
.fillMaxHeight()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.then(modifier),
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(R.drawable.ic_feedback),
|
||||
contentDescription = null,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(ZashiDimensions.Spacing.spacing3xl))
|
||||
|
||||
Text(
|
||||
text = stringResource(id = R.string.support_title),
|
||||
style = ZashiTypography.header6,
|
||||
color = ZashiColors.Text.textPrimary,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(ZashiDimensions.Spacing.spacingMd))
|
||||
|
||||
Text(
|
||||
text = stringResource(id = R.string.support_information),
|
||||
color = ZashiColors.Text.textPrimary,
|
||||
style = ZashiTypography.textSm
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(ZashiDimensions.Spacing.spacing4xl))
|
||||
|
||||
Text(
|
||||
text = stringResource(id = R.string.support_experience_title),
|
||||
color = ZashiColors.Inputs.Default.label,
|
||||
style = ZashiTypography.textSm,
|
||||
fontWeight = FontWeight.Medium
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(ZashiDimensions.Spacing.spacingLg))
|
||||
|
||||
EmojiRow(state.emojiState)
|
||||
|
||||
Spacer(modifier = Modifier.height(ZashiDimensions.Spacing.spacing3xl))
|
||||
|
||||
Text(
|
||||
text = stringResource(id = R.string.support_help_title),
|
||||
color = ZashiColors.Inputs.Default.label,
|
||||
style = ZashiTypography.textSm,
|
||||
fontWeight = FontWeight.Medium
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(ZashiDimensions.Spacing.spacingLg))
|
||||
|
||||
ZashiTextField(
|
||||
state = state.feedback,
|
||||
minLines = 3,
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.focusRequester(focusRequester),
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringResource(id = R.string.support_hint),
|
||||
style = ZashiTypography.textMd,
|
||||
color = ZashiColors.Inputs.Default.text
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(ZashiDimensions.Spacing.spacingLg))
|
||||
|
||||
Spacer(
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
|
||||
// TODO [#1467]: Support screen - keep button above keyboard
|
||||
// TODO [#1467]: https://github.com/Electric-Coin-Company/zashi-android/issues/1467
|
||||
ZashiButton(
|
||||
state = state.sendButton,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
// Causes the TextField to focus on the first screen visit
|
||||
focusRequester.requestFocus()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun EmojiRow(state: FeedbackEmojiState) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = spacedBy(2.dp),
|
||||
) {
|
||||
listOf(
|
||||
FeedbackEmoji.FIRST,
|
||||
FeedbackEmoji.SECOND,
|
||||
FeedbackEmoji.THIRD,
|
||||
FeedbackEmoji.FOURTH,
|
||||
FeedbackEmoji.FIFTH,
|
||||
).forEach {
|
||||
Emoji(
|
||||
modifier = Modifier.weight(1f),
|
||||
emoji = it,
|
||||
isSelected = state.selection == it,
|
||||
onClick = { state.onSelected(it) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Emoji(
|
||||
emoji: FeedbackEmoji,
|
||||
isSelected: Boolean,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Box(
|
||||
modifier =
|
||||
modifier
|
||||
.aspectRatio(EMOJI_CARD_RATIO)
|
||||
.border(
|
||||
width = 2.5.dp,
|
||||
color = if (isSelected) ZashiColors.Text.textPrimary else Color.Transparent,
|
||||
shape = RoundedCornerShape(ZashiDimensions.Radius.radiusXl)
|
||||
)
|
||||
.padding(4.5.dp)
|
||||
.background(
|
||||
color = ZashiColors.Surfaces.bgSecondary,
|
||||
shape = RoundedCornerShape(8.dp)
|
||||
)
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.clickable(onClick = onClick),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Image(
|
||||
modifier = Modifier.size(24.dp),
|
||||
painter = painterResource(emoji.res),
|
||||
contentDescription = ""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@PreviewScreens
|
||||
@Composable
|
||||
private fun Preview() =
|
||||
ZcashTheme {
|
||||
BlankSurface {
|
||||
FeedbackView(
|
||||
state =
|
||||
FeedbackState(
|
||||
onBack = {},
|
||||
sendButton = ButtonState(stringRes("Button")),
|
||||
feedback = TextFieldState(stringRes("")) {},
|
||||
emojiState =
|
||||
FeedbackEmojiState(
|
||||
selection = FeedbackEmoji.FIRST,
|
||||
onSelected = {}
|
||||
)
|
||||
),
|
||||
topAppBarSubTitleState = TopAppBarSubTitleState.None,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private const val EMOJI_CARD_RATIO = 1.25f
|
|
@ -0,0 +1,93 @@
|
|||
package co.electriccoin.zcash.ui.screen.feedback.viewmodel
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import cash.z.ecc.sdk.ANDROID_STATE_FLOW_TIMEOUT
|
||||
import co.electriccoin.zcash.ui.R
|
||||
import co.electriccoin.zcash.ui.common.usecase.SendSupportEmailUseCase
|
||||
import co.electriccoin.zcash.ui.design.component.AlertDialogState
|
||||
import co.electriccoin.zcash.ui.design.component.ButtonState
|
||||
import co.electriccoin.zcash.ui.design.component.TextFieldState
|
||||
import co.electriccoin.zcash.ui.design.util.stringRes
|
||||
import co.electriccoin.zcash.ui.screen.feedback.model.FeedbackEmoji
|
||||
import co.electriccoin.zcash.ui.screen.feedback.model.FeedbackEmojiState
|
||||
import co.electriccoin.zcash.ui.screen.feedback.model.FeedbackState
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.WhileSubscribed
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class FeedbackViewModel(
|
||||
private val sendSupportEmail: SendSupportEmailUseCase
|
||||
) : ViewModel() {
|
||||
private val feedback = MutableStateFlow("")
|
||||
private val selectedEmoji = MutableStateFlow(FeedbackEmoji.FIFTH)
|
||||
private val isDialogShown = MutableStateFlow(false)
|
||||
|
||||
val onBackNavigationCommand = MutableSharedFlow<Unit>()
|
||||
|
||||
val state =
|
||||
combine(feedback, selectedEmoji) { feedbackText, emoji ->
|
||||
FeedbackState(
|
||||
onBack = ::onBack,
|
||||
emojiState =
|
||||
FeedbackEmojiState(
|
||||
selection = emoji,
|
||||
onSelected = { new -> selectedEmoji.update { new } }
|
||||
),
|
||||
feedback =
|
||||
TextFieldState(
|
||||
value = stringRes(feedbackText),
|
||||
onValueChange = { new -> feedback.update { new } }
|
||||
),
|
||||
sendButton =
|
||||
ButtonState(
|
||||
text = stringRes(R.string.support_send),
|
||||
isEnabled = feedbackText.isNotEmpty(),
|
||||
onClick = ::onSendClicked
|
||||
)
|
||||
)
|
||||
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT), null)
|
||||
|
||||
val dialogState =
|
||||
isDialogShown.map { isShown ->
|
||||
AlertDialogState(
|
||||
title = stringRes(R.string.support_confirmation_dialog_title),
|
||||
text = stringRes(R.string.support_confirmation_explanation),
|
||||
confirmButtonState =
|
||||
ButtonState(
|
||||
text = stringRes(R.string.support_confirmation_dialog_ok),
|
||||
onClick = ::onConfirmSendFeedback
|
||||
),
|
||||
dismissButtonState =
|
||||
ButtonState(
|
||||
text = stringRes(R.string.support_confirmation_dialog_cancel),
|
||||
onClick = { isDialogShown.update { false } }
|
||||
),
|
||||
onDismissRequest = { isDialogShown.update { false } }
|
||||
).takeIf { isShown }
|
||||
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT), null)
|
||||
|
||||
private fun onConfirmSendFeedback() =
|
||||
viewModelScope.launch {
|
||||
isDialogShown.update { false }
|
||||
sendSupportEmail(
|
||||
emoji = selectedEmoji.value,
|
||||
message = stringRes(feedback.value)
|
||||
)
|
||||
}
|
||||
|
||||
private fun onSendClicked() {
|
||||
isDialogShown.update { true }
|
||||
}
|
||||
|
||||
private fun onBack() =
|
||||
viewModelScope.launch {
|
||||
onBackNavigationCommand.emit(Unit)
|
||||
}
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
package co.electriccoin.zcash.ui.screen.newwalletrecovery
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import cash.z.ecc.android.sdk.model.PersistableWallet
|
||||
import co.electriccoin.zcash.spackle.ClipboardManagerUtil
|
||||
import co.electriccoin.zcash.ui.R
|
||||
import co.electriccoin.zcash.ui.common.compose.LocalActivity
|
||||
import co.electriccoin.zcash.ui.common.model.VersionInfo
|
||||
import co.electriccoin.zcash.ui.screen.newwalletrecovery.view.NewWalletRecovery
|
||||
|
||||
@Composable
|
||||
fun WrapNewWalletRecovery(
|
||||
persistableWallet: PersistableWallet,
|
||||
onBackupComplete: () -> Unit
|
||||
) {
|
||||
val activity = LocalActivity.current
|
||||
|
||||
val versionInfo = VersionInfo.new(activity.applicationContext)
|
||||
|
||||
NewWalletRecovery(
|
||||
persistableWallet,
|
||||
onSeedCopy = {
|
||||
ClipboardManagerUtil.copyToClipboard(
|
||||
activity.applicationContext,
|
||||
activity.getString(R.string.new_wallet_recovery_seed_clipboard_tag),
|
||||
persistableWallet.seedPhrase.joinToString()
|
||||
)
|
||||
},
|
||||
onBirthdayCopy = {
|
||||
ClipboardManagerUtil.copyToClipboard(
|
||||
activity.applicationContext,
|
||||
activity.getString(R.string.new_wallet_recovery_birthday_clipboard_tag),
|
||||
persistableWallet.birthday?.value.toString()
|
||||
)
|
||||
},
|
||||
onComplete = onBackupComplete,
|
||||
versionInfo = versionInfo
|
||||
)
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
package co.electriccoin.zcash.ui.screen.newwalletrecovery.view
|
||||
|
||||
/**
|
||||
* These are only used for automated testing.
|
||||
*/
|
||||
object NewWalletRecoveryTag {
|
||||
const val DEBUG_MENU_TAG = "debug_menu"
|
||||
}
|
|
@ -1,266 +0,0 @@
|
|||
package co.electriccoin.zcash.ui.screen.newwalletrecovery.view
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.basicMarquee
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
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.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.MoreVert
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import cash.z.ecc.android.sdk.model.PersistableWallet
|
||||
import cash.z.ecc.sdk.fixture.PersistableWalletFixture
|
||||
import co.electriccoin.zcash.ui.R
|
||||
import co.electriccoin.zcash.ui.common.compose.SecureScreen
|
||||
import co.electriccoin.zcash.ui.common.compose.shouldSecureScreen
|
||||
import co.electriccoin.zcash.ui.common.model.VersionInfo
|
||||
import co.electriccoin.zcash.ui.common.test.CommonTag.WALLET_BIRTHDAY
|
||||
import co.electriccoin.zcash.ui.design.MINIMAL_WEIGHT
|
||||
import co.electriccoin.zcash.ui.design.component.BlankBgScaffold
|
||||
import co.electriccoin.zcash.ui.design.component.BodySmall
|
||||
import co.electriccoin.zcash.ui.design.component.ChipGrid
|
||||
import co.electriccoin.zcash.ui.design.component.SmallTopAppBar
|
||||
import co.electriccoin.zcash.ui.design.component.TopScreenLogoTitle
|
||||
import co.electriccoin.zcash.ui.design.component.ZashiButton
|
||||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||
import co.electriccoin.zcash.ui.design.theme.dimensions.ZashiDimensions
|
||||
import co.electriccoin.zcash.ui.design.util.scaffoldPadding
|
||||
import co.electriccoin.zcash.ui.fixture.VersionInfoFixture
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun NewWalletRecoveryPreview() {
|
||||
ZcashTheme(forceDarkMode = false) {
|
||||
NewWalletRecovery(
|
||||
PersistableWalletFixture.new(),
|
||||
onSeedCopy = {},
|
||||
onBirthdayCopy = {},
|
||||
onComplete = {},
|
||||
versionInfo = VersionInfoFixture.new(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun NewWalletRecoveryDarkPreview() {
|
||||
ZcashTheme(forceDarkMode = true) {
|
||||
NewWalletRecovery(
|
||||
PersistableWalletFixture.new(),
|
||||
onSeedCopy = {},
|
||||
onBirthdayCopy = {},
|
||||
onComplete = {},
|
||||
versionInfo = VersionInfoFixture.new(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param onComplete Callback when the user has confirmed viewing the seed phrase.
|
||||
*/
|
||||
@Composable
|
||||
fun NewWalletRecovery(
|
||||
wallet: PersistableWallet,
|
||||
onSeedCopy: () -> Unit,
|
||||
onBirthdayCopy: () -> Unit,
|
||||
onComplete: () -> Unit,
|
||||
versionInfo: VersionInfo,
|
||||
) {
|
||||
BlankBgScaffold(
|
||||
topBar = {
|
||||
NewWalletRecoveryTopAppBar(
|
||||
onSeedCopy = onSeedCopy,
|
||||
versionInfo = versionInfo,
|
||||
)
|
||||
}
|
||||
) { paddingValues ->
|
||||
NewWalletRecoveryMainContent(
|
||||
wallet = wallet,
|
||||
onComplete = onComplete,
|
||||
onSeedCopy = onSeedCopy,
|
||||
onBirthdayCopy = onBirthdayCopy,
|
||||
versionInfo = versionInfo,
|
||||
// Horizontal paddings will be part of each UI element to minimize a possible truncation on very
|
||||
// small screens
|
||||
modifier =
|
||||
Modifier
|
||||
.scaffoldPadding(paddingValues)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun NewWalletRecoveryTopAppBar(
|
||||
versionInfo: VersionInfo,
|
||||
modifier: Modifier = Modifier,
|
||||
onSeedCopy: () -> Unit
|
||||
) {
|
||||
SmallTopAppBar(
|
||||
modifier = modifier,
|
||||
regularActions = {
|
||||
if (versionInfo.isDebuggable && !versionInfo.isRunningUnderTestService) {
|
||||
DebugMenu(onCopyToClipboard = onSeedCopy)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DebugMenu(onCopyToClipboard: () -> Unit) {
|
||||
Column(
|
||||
modifier = Modifier.testTag(NewWalletRecoveryTag.DEBUG_MENU_TAG)
|
||||
) {
|
||||
var expanded by rememberSaveable { mutableStateOf(false) }
|
||||
IconButton(onClick = { expanded = true }) {
|
||||
Icon(Icons.Default.MoreVert, contentDescription = null)
|
||||
}
|
||||
DropdownMenu(
|
||||
expanded = expanded,
|
||||
onDismissRequest = { expanded = false }
|
||||
) {
|
||||
DropdownMenuItem(
|
||||
text = {
|
||||
Text(stringResource(id = R.string.new_wallet_recovery_copy))
|
||||
},
|
||||
onClick = {
|
||||
onCopyToClipboard()
|
||||
expanded = false
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Suppress("LongParameterList")
|
||||
private fun NewWalletRecoveryMainContent(
|
||||
wallet: PersistableWallet,
|
||||
onSeedCopy: () -> Unit,
|
||||
onBirthdayCopy: () -> Unit,
|
||||
onComplete: () -> Unit,
|
||||
versionInfo: VersionInfo,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.then(modifier),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
TopScreenLogoTitle(
|
||||
title = stringResource(R.string.new_wallet_recovery_header),
|
||||
logoContentDescription = stringResource(R.string.zcash_logo_content_description),
|
||||
modifier = Modifier.padding(horizontal = ZashiDimensions.Spacing.spacing3xl)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingLarge))
|
||||
|
||||
BodySmall(
|
||||
text = stringResource(R.string.new_wallet_recovery_description),
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.padding(horizontal = ZashiDimensions.Spacing.spacing3xl)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingDefault))
|
||||
|
||||
NewWalletRecoverySeedPhrase(
|
||||
persistableWallet = wallet,
|
||||
onSeedCopy = onSeedCopy,
|
||||
onBirthdayCopy = onBirthdayCopy,
|
||||
versionInfo = versionInfo
|
||||
)
|
||||
|
||||
Spacer(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxHeight()
|
||||
.weight(MINIMAL_WEIGHT)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingLarge))
|
||||
|
||||
ZashiButton(
|
||||
onClick = onComplete,
|
||||
text = stringResource(R.string.new_wallet_recovery_button_finished),
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
private fun NewWalletRecoverySeedPhrase(
|
||||
persistableWallet: PersistableWallet,
|
||||
onSeedCopy: () -> Unit,
|
||||
onBirthdayCopy: () -> Unit,
|
||||
versionInfo: VersionInfo,
|
||||
) {
|
||||
if (shouldSecureScreen) {
|
||||
SecureScreen()
|
||||
}
|
||||
|
||||
Column {
|
||||
ChipGrid(
|
||||
wordList = persistableWallet.seedPhrase.split.toPersistentList(),
|
||||
onGridClick = onSeedCopy,
|
||||
allowCopy = versionInfo.isDebuggable && !versionInfo.isRunningUnderTestService,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingDefault))
|
||||
|
||||
persistableWallet.birthday?.let {
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.Center
|
||||
) {
|
||||
BodySmall(
|
||||
text = stringResource(R.string.new_wallet_recovery_birthday_height, it.value),
|
||||
modifier =
|
||||
Modifier
|
||||
.testTag(WALLET_BIRTHDAY)
|
||||
.padding(horizontal = ZcashTheme.dimens.spacingDefault)
|
||||
.basicMarquee()
|
||||
// Apply click callback to the text only as the wrapping layout can be much wider
|
||||
.clickable(
|
||||
interactionSource = interactionSource,
|
||||
// Disable ripple
|
||||
indication = null,
|
||||
onClick = onBirthdayCopy
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -190,7 +190,7 @@ private fun QrCodeBottomBar(
|
|||
ZashiBottomBar {
|
||||
ZashiButton(
|
||||
text = stringResource(id = R.string.qr_code_share_btn),
|
||||
leadingIcon = painterResource(R.drawable.ic_share),
|
||||
icon = R.drawable.ic_share,
|
||||
onClick = { state.onQrCodeShare(qrCodeImage) },
|
||||
modifier =
|
||||
Modifier
|
||||
|
@ -202,7 +202,7 @@ private fun QrCodeBottomBar(
|
|||
|
||||
ZashiButton(
|
||||
text = stringResource(id = R.string.qr_code_copy_btn),
|
||||
leadingIcon = painterResource(R.drawable.ic_copy),
|
||||
icon = R.drawable.ic_copy,
|
||||
onClick = { state.onAddressCopy(state.walletAddress.address) },
|
||||
colors = ZashiButtonDefaults.secondaryColors(),
|
||||
modifier =
|
||||
|
|
|
@ -87,7 +87,6 @@ class QrCodeViewModel(
|
|||
|
||||
private fun onAddressCopyClick(address: String) =
|
||||
copyToClipboard(
|
||||
context = application.applicationContext,
|
||||
tag = application.getString(R.string.qr_code_clipboard_tag),
|
||||
value = address
|
||||
)
|
||||
|
|
|
@ -33,7 +33,6 @@ class ReceiveViewModel(
|
|||
isTestnet = getVersionInfo().isTestnet,
|
||||
onAddressCopy = { address ->
|
||||
copyToClipboard(
|
||||
context = application.applicationContext,
|
||||
tag = application.getString(R.string.receive_clipboard_tag),
|
||||
value = address
|
||||
)
|
||||
|
|
|
@ -16,7 +16,6 @@ import androidx.compose.runtime.mutableStateOf
|
|||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
|
@ -175,7 +174,7 @@ private fun RequestBottomBar(
|
|||
is RequestState.QrCode -> {
|
||||
ZashiButton(
|
||||
text = stringResource(id = R.string.request_qr_share_btn),
|
||||
leadingIcon = painterResource(R.drawable.ic_share),
|
||||
icon = R.drawable.ic_share,
|
||||
enabled = state.request.qrCodeState.isValid(),
|
||||
onClick = { state.onQrCodeShare(state.request.qrCodeState.bitmap!!) },
|
||||
modifier =
|
||||
|
|
|
@ -422,7 +422,7 @@ private fun RestoreSeedMainContent(
|
|||
.then(modifier),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
// Used to calculate necessary scroll to have the seed TextFiled visible
|
||||
// Used to calculate necessary scroll to have the seed TextField visible
|
||||
Column(
|
||||
modifier =
|
||||
Modifier.onSizeChanged { size ->
|
||||
|
@ -478,7 +478,7 @@ private fun RestoreSeedMainContent(
|
|||
}
|
||||
|
||||
LaunchedEffect(parseResult) {
|
||||
// Causes the TextFiled to refocus
|
||||
// Causes the TextField to refocus
|
||||
if (!isSeedValid) {
|
||||
focusRequester.requestFocus()
|
||||
}
|
||||
|
@ -853,7 +853,7 @@ private fun RestoreBirthdayMainContent(
|
|||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
// Causes the TextFiled to focus on the first screen visit
|
||||
// Causes the TextField to focus on the first screen visit
|
||||
focusRequester.requestFocus()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
package co.electriccoin.zcash.ui.screen.seed
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import co.electriccoin.zcash.di.koinActivityViewModel
|
||||
import co.electriccoin.zcash.ui.common.compose.LocalNavController
|
||||
import co.electriccoin.zcash.ui.common.viewmodel.WalletViewModel
|
||||
import co.electriccoin.zcash.ui.screen.seed.view.SeedView
|
||||
import co.electriccoin.zcash.ui.screen.seed.viewmodel.SeedViewModel
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
import org.koin.core.parameter.parametersOf
|
||||
|
||||
@Composable
|
||||
internal fun WrapSeed(
|
||||
args: SeedNavigationArgs,
|
||||
goBackOverride: (() -> Unit)?
|
||||
) {
|
||||
val navController = LocalNavController.current
|
||||
val walletViewModel = koinActivityViewModel<WalletViewModel>()
|
||||
val walletState = walletViewModel.walletStateInformation.collectAsStateWithLifecycle().value
|
||||
val viewModel = koinViewModel<SeedViewModel> { parametersOf(args) }
|
||||
val state by viewModel.state.collectAsStateWithLifecycle()
|
||||
|
||||
val normalizedState =
|
||||
state?.copy(
|
||||
onBack =
|
||||
state?.onBack?.let {
|
||||
{
|
||||
goBackOverride?.invoke()
|
||||
it.invoke()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
viewModel.navigateBack.collect {
|
||||
navController.popBackStack()
|
||||
}
|
||||
}
|
||||
|
||||
BackHandler {
|
||||
normalizedState?.onBack?.invoke()
|
||||
}
|
||||
|
||||
normalizedState?.let {
|
||||
SeedView(
|
||||
state = normalizedState,
|
||||
topAppBarSubTitleState = walletState,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
enum class SeedNavigationArgs {
|
||||
NEW_WALLET,
|
||||
RECOVERY
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package co.electriccoin.zcash.ui.screen.seed.model
|
||||
|
||||
import co.electriccoin.zcash.ui.design.component.ButtonState
|
||||
import co.electriccoin.zcash.ui.design.util.StringResource
|
||||
|
||||
data class SeedState(
|
||||
val seed: SeedSecretState,
|
||||
val birthday: SeedSecretState,
|
||||
val button: ButtonState,
|
||||
val onBack: (() -> Unit)?
|
||||
)
|
||||
|
||||
data class SeedSecretState(
|
||||
val title: StringResource,
|
||||
val text: StringResource,
|
||||
val isRevealed: Boolean,
|
||||
val isRevealPhraseVisible: Boolean,
|
||||
val mode: Mode,
|
||||
val tooltip: SeedSecretStateTooltip?,
|
||||
val onClick: (() -> Unit)?,
|
||||
) {
|
||||
enum class Mode {
|
||||
SEED,
|
||||
BIRTHDAY
|
||||
}
|
||||
}
|
||||
|
||||
data class SeedSecretStateTooltip(
|
||||
val title: StringResource,
|
||||
val message: StringResource,
|
||||
)
|
|
@ -0,0 +1,465 @@
|
|||
package co.electriccoin.zcash.ui.screen.seed.view
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.core.animateDpAsState
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Arrangement.spacedBy
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
import androidx.compose.foundation.layout.FlowColumn
|
||||
import androidx.compose.foundation.layout.Row
|
||||
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.foundation.layout.width
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.rememberTooltipState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.draw.blur
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.util.fastForEachIndexed
|
||||
import co.electriccoin.zcash.spackle.AndroidApiVersion
|
||||
import co.electriccoin.zcash.ui.R
|
||||
import co.electriccoin.zcash.ui.common.compose.SecureScreen
|
||||
import co.electriccoin.zcash.ui.common.compose.ZashiTooltip
|
||||
import co.electriccoin.zcash.ui.common.compose.ZashiTooltipBox
|
||||
import co.electriccoin.zcash.ui.common.compose.drawCaretWithPath
|
||||
import co.electriccoin.zcash.ui.common.compose.shouldSecureScreen
|
||||
import co.electriccoin.zcash.ui.common.model.TopAppBarSubTitleState
|
||||
import co.electriccoin.zcash.ui.design.component.ButtonState
|
||||
import co.electriccoin.zcash.ui.design.component.ZashiButton
|
||||
import co.electriccoin.zcash.ui.design.component.ZashiSmallTopAppBar
|
||||
import co.electriccoin.zcash.ui.design.component.ZashiTopAppBarBackNavigation
|
||||
import co.electriccoin.zcash.ui.design.newcomponent.PreviewScreenSizes
|
||||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||
import co.electriccoin.zcash.ui.design.theme.colors.ZashiColors
|
||||
import co.electriccoin.zcash.ui.design.theme.dimensions.ZashiDimensions
|
||||
import co.electriccoin.zcash.ui.design.theme.typography.ZashiTypography
|
||||
import co.electriccoin.zcash.ui.design.util.getValue
|
||||
import co.electriccoin.zcash.ui.design.util.scaffoldPadding
|
||||
import co.electriccoin.zcash.ui.design.util.stringRes
|
||||
import co.electriccoin.zcash.ui.screen.seed.model.SeedSecretState
|
||||
import co.electriccoin.zcash.ui.screen.seed.model.SeedSecretStateTooltip
|
||||
import co.electriccoin.zcash.ui.screen.seed.model.SeedState
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
fun SeedView(
|
||||
topAppBarSubTitleState: TopAppBarSubTitleState,
|
||||
state: SeedState,
|
||||
) {
|
||||
if (shouldSecureScreen) {
|
||||
SecureScreen()
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
SeedRecoveryTopAppBar(
|
||||
state = state,
|
||||
subTitleState = topAppBarSubTitleState,
|
||||
)
|
||||
}
|
||||
) { paddingValues ->
|
||||
SeedRecoveryMainContent(
|
||||
modifier = Modifier.scaffoldPadding(paddingValues),
|
||||
state = state,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SeedRecoveryTopAppBar(
|
||||
state: SeedState,
|
||||
subTitleState: TopAppBarSubTitleState,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
ZashiSmallTopAppBar(
|
||||
title = stringResource(R.string.seed_recovery_title),
|
||||
subtitle =
|
||||
when (subTitleState) {
|
||||
TopAppBarSubTitleState.Disconnected -> stringResource(id = R.string.disconnected_label)
|
||||
TopAppBarSubTitleState.Restoring -> stringResource(id = R.string.restoring_wallet_label)
|
||||
TopAppBarSubTitleState.None -> null
|
||||
},
|
||||
modifier = modifier,
|
||||
navigationAction = {
|
||||
if (state.onBack != null) {
|
||||
ZashiTopAppBarBackNavigation(onBack = state.onBack)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SeedRecoveryMainContent(
|
||||
state: SeedState,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.then(modifier),
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.seed_recovery_header),
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = ZashiColors.Text.textPrimary,
|
||||
style = ZashiTypography.header6
|
||||
)
|
||||
|
||||
Spacer(Modifier.height(ZashiDimensions.Spacing.spacingMd))
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.seed_recovery_description),
|
||||
color = ZashiColors.Text.textPrimary,
|
||||
style = ZashiTypography.textSm
|
||||
)
|
||||
|
||||
Spacer(Modifier.height(ZashiDimensions.Spacing.spacing4xl))
|
||||
|
||||
SeedSecret(modifier = Modifier.fillMaxWidth(), state = state.seed)
|
||||
|
||||
Spacer(Modifier.height(ZashiDimensions.Spacing.spacing3xl))
|
||||
|
||||
SeedSecret(modifier = Modifier.fillMaxWidth(), state = state.birthday)
|
||||
|
||||
Spacer(Modifier.weight(1f))
|
||||
|
||||
Spacer(Modifier.height(ZashiDimensions.Spacing.spacing3xl))
|
||||
|
||||
Row {
|
||||
Image(
|
||||
painterResource(R.drawable.ic_warning),
|
||||
contentDescription = null,
|
||||
colorFilter = ColorFilter.tint(ZashiColors.Utility.WarningYellow.utilityOrange500)
|
||||
)
|
||||
|
||||
Spacer(Modifier.width(ZashiDimensions.Spacing.spacingLg))
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.seed_recovery_warning),
|
||||
color = ZashiColors.Utility.WarningYellow.utilityOrange500,
|
||||
style = ZashiTypography.textXs,
|
||||
fontWeight = FontWeight.Medium
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(Modifier.height(ZashiDimensions.Spacing.spacing3xl))
|
||||
|
||||
ZashiButton(
|
||||
state = state.button,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun SeedSecret(
|
||||
state: SeedSecretState,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val tooltipState = rememberTooltipState(isPersistent = true)
|
||||
val scope = rememberCoroutineScope()
|
||||
Column(
|
||||
modifier = modifier
|
||||
) {
|
||||
Row(
|
||||
modifier =
|
||||
if (state.tooltip != null) {
|
||||
Modifier.clickable {
|
||||
scope.launch {
|
||||
if (tooltipState.isVisible) {
|
||||
tooltipState.dismiss()
|
||||
} else {
|
||||
tooltipState.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
) {
|
||||
Text(
|
||||
text = state.title.getValue(),
|
||||
color = ZashiColors.Text.textPrimary,
|
||||
style = ZashiTypography.textSm,
|
||||
fontWeight = FontWeight.Medium
|
||||
)
|
||||
if (state.tooltip != null) {
|
||||
val density = LocalDensity.current
|
||||
val configuration = LocalConfiguration.current
|
||||
val containerColor = ZashiColors.HintTooltips.defaultBg
|
||||
Spacer(Modifier.width(2.dp))
|
||||
ZashiTooltipBox(
|
||||
tooltip = {
|
||||
ZashiTooltip(
|
||||
modifier =
|
||||
Modifier.drawCaret {
|
||||
drawCaretWithPath(
|
||||
density = density,
|
||||
configuration = configuration,
|
||||
containerColor = containerColor,
|
||||
anchorLayoutCoordinates = it
|
||||
)
|
||||
},
|
||||
showCaret = false,
|
||||
title = state.tooltip.title,
|
||||
message = state.tooltip.message,
|
||||
onDismissRequest = {
|
||||
scope.launch {
|
||||
tooltipState.dismiss()
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
state = tooltipState,
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = R.drawable.ic_zashi_tooltip),
|
||||
contentDescription = "",
|
||||
colorFilter = ColorFilter.tint(ZashiColors.Inputs.Default.icon)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.height(ZashiDimensions.Spacing.spacingSm))
|
||||
|
||||
SecretContent(state = state)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SecretContent(state: SeedSecretState) {
|
||||
val blur = animateDpAsState(if (state.isRevealed) 0.dp else 14.dp, label = "")
|
||||
Surface(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth(),
|
||||
shape = RoundedCornerShape(10.dp),
|
||||
color = ZashiColors.Inputs.Default.bg
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Box(
|
||||
modifier =
|
||||
Modifier then
|
||||
if (state.onClick != null) {
|
||||
Modifier.clickable(onClick = state.onClick)
|
||||
} else {
|
||||
Modifier
|
||||
} then
|
||||
Modifier
|
||||
.blurCompat(blur.value, 14.dp)
|
||||
.padding(horizontal = 24.dp, vertical = 18.dp)
|
||||
) {
|
||||
if (state.mode == SeedSecretState.Mode.SEED) {
|
||||
SecretSeedContent(state)
|
||||
} else {
|
||||
SecretBirthdayContent(state)
|
||||
}
|
||||
}
|
||||
|
||||
AnimatedVisibility(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
visible =
|
||||
state.isRevealPhraseVisible &&
|
||||
state.isRevealed.not() &&
|
||||
state.mode == SeedSecretState.Mode.SEED,
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut(),
|
||||
) {
|
||||
Column(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 18.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(R.drawable.ic_reveal),
|
||||
contentDescription = "",
|
||||
colorFilter = ColorFilter.tint(ZashiColors.Text.textPrimary)
|
||||
)
|
||||
|
||||
Spacer(Modifier.height(ZashiDimensions.Spacing.spacingMd))
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.seed_recovery_reveal),
|
||||
style = ZashiTypography.textLg,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = ZashiColors.Text.textPrimary
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Modifier.blurCompat(
|
||||
radius: Dp,
|
||||
max: Dp
|
||||
): Modifier {
|
||||
return if (AndroidApiVersion.isAtLeastS) {
|
||||
this.blur(radius)
|
||||
} else {
|
||||
val progression = 1 - (radius.value / max.value)
|
||||
this
|
||||
.alpha(progression)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SecretBirthdayContent(state: SeedSecretState) {
|
||||
Text(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
textAlign = TextAlign.Start,
|
||||
text = state.text.getValue(),
|
||||
style = ZashiTypography.textMd,
|
||||
fontWeight = FontWeight.Medium,
|
||||
color = ZashiColors.Inputs.Filled.text
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
private fun SecretSeedContent(state: SeedSecretState) {
|
||||
FlowColumn(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(end = 8.dp),
|
||||
maxItemsInEachColumn = 8,
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalArrangement = spacedBy(6.dp),
|
||||
maxLines = 8
|
||||
) {
|
||||
state.text.getValue().split(" ").fastForEachIndexed { i, s ->
|
||||
Row(
|
||||
modifier = Modifier
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.width(18.dp),
|
||||
textAlign = TextAlign.End,
|
||||
text = "${i + 1}",
|
||||
style = ZashiTypography.textSm,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = ZashiColors.Text.textPrimary,
|
||||
maxLines = 1
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(ZashiDimensions.Spacing.spacingLg))
|
||||
|
||||
Text(
|
||||
text = s,
|
||||
style = ZashiTypography.textSm,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = ZashiColors.Text.textPrimary,
|
||||
maxLines = 1
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@PreviewScreenSizes
|
||||
private fun RevealedPreview() =
|
||||
ZcashTheme {
|
||||
SeedView(
|
||||
topAppBarSubTitleState = TopAppBarSubTitleState.None,
|
||||
state =
|
||||
SeedState(
|
||||
seed =
|
||||
SeedSecretState(
|
||||
title = stringRes("Seed"),
|
||||
text = stringRes((1..24).joinToString(" ") { "trala" }),
|
||||
tooltip = null,
|
||||
isRevealed = true,
|
||||
mode = SeedSecretState.Mode.SEED,
|
||||
isRevealPhraseVisible = true
|
||||
) {},
|
||||
birthday =
|
||||
SeedSecretState(
|
||||
title = stringRes("Birthday"),
|
||||
text = stringRes(value = "asdads"),
|
||||
tooltip = SeedSecretStateTooltip(title = stringRes(""), message = stringRes("")),
|
||||
isRevealed = true,
|
||||
mode = SeedSecretState.Mode.BIRTHDAY,
|
||||
isRevealPhraseVisible = false
|
||||
) {},
|
||||
button =
|
||||
ButtonState(
|
||||
text = stringRes("Text"),
|
||||
icon = R.drawable.ic_seed_show,
|
||||
onClick = {},
|
||||
),
|
||||
onBack = {}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
@PreviewScreenSizes
|
||||
private fun HiddenPreview() =
|
||||
ZcashTheme {
|
||||
SeedView(
|
||||
topAppBarSubTitleState = TopAppBarSubTitleState.None,
|
||||
state =
|
||||
SeedState(
|
||||
seed =
|
||||
SeedSecretState(
|
||||
title = stringRes("Seed"),
|
||||
text = stringRes((1..24).joinToString(" ") { "trala" }),
|
||||
tooltip = null,
|
||||
isRevealed = false,
|
||||
mode = SeedSecretState.Mode.SEED,
|
||||
isRevealPhraseVisible = true
|
||||
) {},
|
||||
birthday =
|
||||
SeedSecretState(
|
||||
title = stringRes("Birthday"),
|
||||
text = stringRes(value = "asdads"),
|
||||
tooltip = SeedSecretStateTooltip(title = stringRes(""), message = stringRes("")),
|
||||
isRevealed = false,
|
||||
mode = SeedSecretState.Mode.BIRTHDAY,
|
||||
isRevealPhraseVisible = false
|
||||
) {},
|
||||
button =
|
||||
ButtonState(
|
||||
text = stringRes("Text"),
|
||||
icon = R.drawable.ic_seed_show,
|
||||
onClick = {},
|
||||
),
|
||||
onBack = {}
|
||||
)
|
||||
)
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
package co.electriccoin.zcash.ui.screen.seed.viewmodel
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import cash.z.ecc.sdk.ANDROID_STATE_FLOW_TIMEOUT
|
||||
import co.electriccoin.zcash.ui.R
|
||||
import co.electriccoin.zcash.ui.common.model.OnboardingState
|
||||
import co.electriccoin.zcash.ui.common.repository.WalletRepository
|
||||
import co.electriccoin.zcash.ui.common.usecase.ObserveBackupPersistableWalletUseCase
|
||||
import co.electriccoin.zcash.ui.common.usecase.ObservePersistableWalletUseCase
|
||||
import co.electriccoin.zcash.ui.design.component.ButtonState
|
||||
import co.electriccoin.zcash.ui.design.util.stringRes
|
||||
import co.electriccoin.zcash.ui.screen.seed.SeedNavigationArgs
|
||||
import co.electriccoin.zcash.ui.screen.seed.model.SeedSecretState
|
||||
import co.electriccoin.zcash.ui.screen.seed.model.SeedSecretStateTooltip
|
||||
import co.electriccoin.zcash.ui.screen.seed.model.SeedState
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.WhileSubscribed
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class SeedViewModel(
|
||||
observeBackupPersistableWallet: ObserveBackupPersistableWalletUseCase,
|
||||
observePersistableWallet: ObservePersistableWalletUseCase,
|
||||
private val args: SeedNavigationArgs,
|
||||
private val walletRepository: WalletRepository,
|
||||
) : ViewModel() {
|
||||
private val isRevealed = MutableStateFlow(false)
|
||||
|
||||
private val observableWallet =
|
||||
when (args) {
|
||||
SeedNavigationArgs.NEW_WALLET -> observeBackupPersistableWallet()
|
||||
SeedNavigationArgs.RECOVERY -> observePersistableWallet()
|
||||
}
|
||||
|
||||
val navigateBack = MutableSharedFlow<Unit>()
|
||||
|
||||
val state =
|
||||
combine(isRevealed, observableWallet) { isRevealed, wallet ->
|
||||
SeedState(
|
||||
button =
|
||||
ButtonState(
|
||||
text =
|
||||
when {
|
||||
args == SeedNavigationArgs.NEW_WALLET -> stringRes(R.string.seed_recovery_next_button)
|
||||
isRevealed -> stringRes(R.string.seed_recovery_hide_button)
|
||||
else -> stringRes(R.string.seed_recovery_reveal_button)
|
||||
},
|
||||
onClick = ::onPrimaryButtonClicked,
|
||||
isEnabled = wallet != null,
|
||||
isLoading = wallet == null,
|
||||
icon =
|
||||
when {
|
||||
args == SeedNavigationArgs.NEW_WALLET -> null
|
||||
isRevealed -> R.drawable.ic_seed_hide
|
||||
else -> R.drawable.ic_seed_show
|
||||
}
|
||||
),
|
||||
seed =
|
||||
SeedSecretState(
|
||||
title = stringRes(R.string.seed_recovery_phrase_title),
|
||||
text = stringRes(wallet?.seedPhrase?.joinToString().orEmpty()),
|
||||
isRevealed = isRevealed,
|
||||
tooltip = null,
|
||||
onClick =
|
||||
when (args) {
|
||||
SeedNavigationArgs.NEW_WALLET -> ::onNewWalletSeedClicked
|
||||
SeedNavigationArgs.RECOVERY -> null
|
||||
},
|
||||
mode = SeedSecretState.Mode.SEED,
|
||||
isRevealPhraseVisible = args == SeedNavigationArgs.NEW_WALLET,
|
||||
),
|
||||
birthday =
|
||||
SeedSecretState(
|
||||
title = stringRes(R.string.seed_recovery_bday_title),
|
||||
text = stringRes(wallet?.birthday?.value?.toString().orEmpty()),
|
||||
isRevealed = isRevealed,
|
||||
tooltip =
|
||||
SeedSecretStateTooltip(
|
||||
title = stringRes(R.string.seed_recovery_bday_tooltip_title),
|
||||
message = stringRes(R.string.seed_recovery_bday_tooltip_message)
|
||||
),
|
||||
onClick = null,
|
||||
mode = SeedSecretState.Mode.BIRTHDAY,
|
||||
isRevealPhraseVisible = false,
|
||||
),
|
||||
onBack =
|
||||
when (args) {
|
||||
SeedNavigationArgs.NEW_WALLET -> null
|
||||
SeedNavigationArgs.RECOVERY -> ::onBack
|
||||
}
|
||||
)
|
||||
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT), null)
|
||||
|
||||
private fun onBack() {
|
||||
viewModelScope.launch {
|
||||
navigateBack.emit(Unit)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onPrimaryButtonClicked() {
|
||||
when (args) {
|
||||
SeedNavigationArgs.NEW_WALLET -> walletRepository.persistOnboardingState(OnboardingState.READY)
|
||||
SeedNavigationArgs.RECOVERY -> isRevealed.update { !it }
|
||||
}
|
||||
}
|
||||
|
||||
private fun onNewWalletSeedClicked() {
|
||||
viewModelScope.launch {
|
||||
isRevealed.update { !it }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,92 +0,0 @@
|
|||
package co.electriccoin.zcash.ui.screen.seedrecovery
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import cash.z.ecc.android.sdk.Synchronizer
|
||||
import co.electriccoin.zcash.di.koinActivityViewModel
|
||||
import co.electriccoin.zcash.spackle.ClipboardManagerUtil
|
||||
import co.electriccoin.zcash.ui.R
|
||||
import co.electriccoin.zcash.ui.common.compose.LocalActivity
|
||||
import co.electriccoin.zcash.ui.common.model.TopAppBarSubTitleState
|
||||
import co.electriccoin.zcash.ui.common.model.VersionInfo
|
||||
import co.electriccoin.zcash.ui.common.viewmodel.SecretState
|
||||
import co.electriccoin.zcash.ui.common.viewmodel.WalletViewModel
|
||||
import co.electriccoin.zcash.ui.design.component.CircularScreenProgressIndicator
|
||||
import co.electriccoin.zcash.ui.screen.seedrecovery.view.SeedRecovery
|
||||
|
||||
@Composable
|
||||
internal fun WrapSeedRecovery(
|
||||
goBack: () -> Unit,
|
||||
onDone: () -> Unit,
|
||||
) {
|
||||
val walletViewModel = koinActivityViewModel<WalletViewModel>()
|
||||
|
||||
val synchronizer = walletViewModel.synchronizer.collectAsStateWithLifecycle().value
|
||||
|
||||
val secretState = walletViewModel.secretState.collectAsStateWithLifecycle().value
|
||||
|
||||
val walletState = walletViewModel.walletStateInformation.collectAsStateWithLifecycle().value
|
||||
|
||||
WrapSeedRecovery(
|
||||
goBack = goBack,
|
||||
onDone = onDone,
|
||||
secretState = secretState,
|
||||
synchronizer = synchronizer,
|
||||
topAppBarSubTitleState = walletState
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Suppress("LongParameterList")
|
||||
private fun WrapSeedRecovery(
|
||||
goBack: () -> Unit,
|
||||
onDone: () -> Unit,
|
||||
topAppBarSubTitleState: TopAppBarSubTitleState,
|
||||
synchronizer: Synchronizer?,
|
||||
secretState: SecretState,
|
||||
) {
|
||||
val activity = LocalActivity.current
|
||||
|
||||
BackHandler {
|
||||
goBack()
|
||||
}
|
||||
|
||||
val versionInfo = VersionInfo.new(activity.applicationContext)
|
||||
|
||||
val persistableWallet =
|
||||
if (secretState is SecretState.Ready) {
|
||||
secretState.persistableWallet
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
if (null == synchronizer || null == persistableWallet) {
|
||||
// TODO [#1146]: Consider moving CircularScreenProgressIndicator from Android layer to View layer
|
||||
// TODO [#1146]: Improve this by allowing screen composition and updating it after the data is available
|
||||
// TODO [#1146]: https://github.com/Electric-Coin-Company/zashi-android/issues/1146
|
||||
CircularScreenProgressIndicator()
|
||||
} else {
|
||||
SeedRecovery(
|
||||
persistableWallet,
|
||||
onBack = goBack,
|
||||
onSeedCopy = {
|
||||
ClipboardManagerUtil.copyToClipboard(
|
||||
activity.applicationContext,
|
||||
activity.getString(R.string.seed_recovery_seed_clipboard_tag),
|
||||
persistableWallet.seedPhrase.joinToString()
|
||||
)
|
||||
},
|
||||
onBirthdayCopy = {
|
||||
ClipboardManagerUtil.copyToClipboard(
|
||||
activity.applicationContext,
|
||||
activity.getString(R.string.seed_recovery_birthday_clipboard_tag),
|
||||
persistableWallet.birthday?.value.toString()
|
||||
)
|
||||
},
|
||||
onDone = onDone,
|
||||
topAppBarSubTitleState = topAppBarSubTitleState,
|
||||
versionInfo = versionInfo,
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
package co.electriccoin.zcash.ui.screen.seedrecovery.view
|
||||
|
||||
/**
|
||||
* These are only used for automated testing.
|
||||
*/
|
||||
object SeedRecoveryTag {
|
||||
const val DEBUG_MENU_TAG = "debug_menu"
|
||||
}
|
|
@ -1,278 +0,0 @@
|
|||
package co.electriccoin.zcash.ui.screen.seedrecovery.view
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.basicMarquee
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
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.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.MoreVert
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Devices
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import cash.z.ecc.android.sdk.model.PersistableWallet
|
||||
import cash.z.ecc.sdk.fixture.PersistableWalletFixture
|
||||
import co.electriccoin.zcash.ui.R
|
||||
import co.electriccoin.zcash.ui.common.compose.SecureScreen
|
||||
import co.electriccoin.zcash.ui.common.compose.shouldSecureScreen
|
||||
import co.electriccoin.zcash.ui.common.model.TopAppBarSubTitleState
|
||||
import co.electriccoin.zcash.ui.common.model.VersionInfo
|
||||
import co.electriccoin.zcash.ui.common.test.CommonTag.WALLET_BIRTHDAY
|
||||
import co.electriccoin.zcash.ui.design.MINIMAL_WEIGHT
|
||||
import co.electriccoin.zcash.ui.design.component.BlankBgScaffold
|
||||
import co.electriccoin.zcash.ui.design.component.BodySmall
|
||||
import co.electriccoin.zcash.ui.design.component.ChipGrid
|
||||
import co.electriccoin.zcash.ui.design.component.SmallTopAppBar
|
||||
import co.electriccoin.zcash.ui.design.component.TopAppBarBackNavigation
|
||||
import co.electriccoin.zcash.ui.design.component.TopScreenLogoTitle
|
||||
import co.electriccoin.zcash.ui.design.component.ZashiButton
|
||||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||
import co.electriccoin.zcash.ui.design.theme.dimensions.ZashiDimensions
|
||||
import co.electriccoin.zcash.ui.design.util.scaffoldPadding
|
||||
import co.electriccoin.zcash.ui.fixture.VersionInfoFixture
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
|
||||
@Preview(name = "SeedRecovery", device = Devices.PIXEL_4)
|
||||
@Composable
|
||||
private fun ComposablePreview() {
|
||||
ZcashTheme(forceDarkMode = false) {
|
||||
SeedRecovery(
|
||||
PersistableWalletFixture.new(),
|
||||
onBack = {},
|
||||
onBirthdayCopy = {},
|
||||
onDone = {},
|
||||
onSeedCopy = {},
|
||||
versionInfo = VersionInfoFixture.new(),
|
||||
topAppBarSubTitleState = TopAppBarSubTitleState.None,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param onDone Callback when the user has confirmed viewing the seed phrase.
|
||||
*/
|
||||
@Composable
|
||||
@Suppress("LongParameterList")
|
||||
fun SeedRecovery(
|
||||
wallet: PersistableWallet,
|
||||
onBack: () -> Unit,
|
||||
onBirthdayCopy: () -> Unit,
|
||||
onDone: () -> Unit,
|
||||
onSeedCopy: () -> Unit,
|
||||
topAppBarSubTitleState: TopAppBarSubTitleState,
|
||||
versionInfo: VersionInfo,
|
||||
) {
|
||||
BlankBgScaffold(
|
||||
topBar = {
|
||||
SeedRecoveryTopAppBar(
|
||||
onBack = onBack,
|
||||
onSeedCopy = onSeedCopy,
|
||||
versionInfo = versionInfo,
|
||||
subTitleState = topAppBarSubTitleState,
|
||||
)
|
||||
}
|
||||
) { paddingValues ->
|
||||
SeedRecoveryMainContent(
|
||||
wallet = wallet,
|
||||
onDone = onDone,
|
||||
onSeedCopy = onSeedCopy,
|
||||
onBirthdayCopy = onBirthdayCopy,
|
||||
versionInfo = versionInfo,
|
||||
// Horizontal paddings will be part of each UI element to minimize a possible truncation on very
|
||||
// small screens
|
||||
modifier =
|
||||
Modifier.scaffoldPadding(paddingValues)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SeedRecoveryTopAppBar(
|
||||
onBack: () -> Unit,
|
||||
onSeedCopy: () -> Unit,
|
||||
subTitleState: TopAppBarSubTitleState,
|
||||
versionInfo: VersionInfo,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
SmallTopAppBar(
|
||||
subTitle =
|
||||
when (subTitleState) {
|
||||
TopAppBarSubTitleState.Disconnected -> stringResource(id = R.string.disconnected_label)
|
||||
TopAppBarSubTitleState.Restoring -> stringResource(id = R.string.restoring_wallet_label)
|
||||
TopAppBarSubTitleState.None -> null
|
||||
},
|
||||
modifier = modifier,
|
||||
navigationAction = {
|
||||
TopAppBarBackNavigation(
|
||||
backText = stringResource(id = R.string.back_navigation).uppercase(),
|
||||
backContentDescriptionText = stringResource(R.string.back_navigation_content_description),
|
||||
onBack = onBack
|
||||
)
|
||||
},
|
||||
regularActions = {
|
||||
if (versionInfo.isDebuggable && !versionInfo.isRunningUnderTestService) {
|
||||
DebugMenu(
|
||||
onCopyToClipboard = onSeedCopy
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DebugMenu(onCopyToClipboard: () -> Unit) {
|
||||
Column(
|
||||
modifier = Modifier.testTag(SeedRecoveryTag.DEBUG_MENU_TAG)
|
||||
) {
|
||||
var expanded by rememberSaveable { mutableStateOf(false) }
|
||||
IconButton(onClick = { expanded = true }) {
|
||||
Icon(Icons.Default.MoreVert, contentDescription = null)
|
||||
}
|
||||
DropdownMenu(
|
||||
expanded = expanded,
|
||||
onDismissRequest = { expanded = false }
|
||||
) {
|
||||
DropdownMenuItem(
|
||||
text = {
|
||||
Text(stringResource(id = R.string.seed_recovery_copy))
|
||||
},
|
||||
onClick = {
|
||||
onCopyToClipboard()
|
||||
expanded = false
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Suppress("LongParameterList")
|
||||
private fun SeedRecoveryMainContent(
|
||||
wallet: PersistableWallet,
|
||||
onSeedCopy: () -> Unit,
|
||||
onBirthdayCopy: () -> Unit,
|
||||
onDone: () -> Unit,
|
||||
versionInfo: VersionInfo,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.then(modifier),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
TopScreenLogoTitle(
|
||||
title = stringResource(R.string.seed_recovery_header),
|
||||
logoContentDescription = stringResource(R.string.zcash_logo_content_description),
|
||||
modifier = Modifier.padding(horizontal = ZashiDimensions.Spacing.spacing3xl)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingLarge))
|
||||
|
||||
BodySmall(
|
||||
text = stringResource(R.string.seed_recovery_description),
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.padding(horizontal = ZashiDimensions.Spacing.spacing3xl)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingDefault))
|
||||
|
||||
SeedRecoverySeedPhrase(
|
||||
persistableWallet = wallet,
|
||||
onSeedCopy = onSeedCopy,
|
||||
onBirthdayCopy = onBirthdayCopy,
|
||||
versionInfo = versionInfo,
|
||||
)
|
||||
|
||||
Spacer(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxHeight()
|
||||
.weight(MINIMAL_WEIGHT)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingLarge))
|
||||
|
||||
ZashiButton(
|
||||
onClick = onDone,
|
||||
text = stringResource(R.string.seed_recovery_button_finished),
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
private fun SeedRecoverySeedPhrase(
|
||||
persistableWallet: PersistableWallet,
|
||||
onSeedCopy: () -> Unit,
|
||||
onBirthdayCopy: () -> Unit,
|
||||
versionInfo: VersionInfo,
|
||||
) {
|
||||
if (shouldSecureScreen) {
|
||||
SecureScreen()
|
||||
}
|
||||
|
||||
Column {
|
||||
ChipGrid(
|
||||
wordList = persistableWallet.seedPhrase.split.toPersistentList(),
|
||||
onGridClick = onSeedCopy,
|
||||
allowCopy = versionInfo.isDebuggable && !versionInfo.isRunningUnderTestService,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingDefault))
|
||||
|
||||
persistableWallet.birthday?.let {
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.Center
|
||||
) {
|
||||
BodySmall(
|
||||
text = stringResource(R.string.seed_recovery_birthday_height, it.value),
|
||||
modifier =
|
||||
Modifier
|
||||
.testTag(WALLET_BIRTHDAY)
|
||||
.padding(horizontal = ZcashTheme.dimens.spacingDefault)
|
||||
.basicMarquee()
|
||||
// Apply click callback to the text only as the wrapping layout can be much wider
|
||||
.clickable(
|
||||
interactionSource = interactionSource,
|
||||
// Disable ripple
|
||||
indication = null,
|
||||
onClick = onBirthdayCopy
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,6 +9,7 @@ import co.electriccoin.zcash.ui.NavigationTargets.ABOUT
|
|||
import co.electriccoin.zcash.ui.NavigationTargets.ADVANCED_SETTINGS
|
||||
import co.electriccoin.zcash.ui.NavigationTargets.INTEGRATIONS
|
||||
import co.electriccoin.zcash.ui.NavigationTargets.SUPPORT
|
||||
import co.electriccoin.zcash.ui.NavigationTargets.WHATS_NEW
|
||||
import co.electriccoin.zcash.ui.R
|
||||
import co.electriccoin.zcash.ui.common.provider.GetVersionInfoProvider
|
||||
import co.electriccoin.zcash.ui.common.usecase.ObserveConfigurationUseCase
|
||||
|
@ -140,6 +141,11 @@ class SettingsViewModel(
|
|||
icon = R.drawable.ic_advanced_settings,
|
||||
onClick = ::onAdvancedSettingsClick
|
||||
),
|
||||
ZashiSettingsListItemState(
|
||||
text = stringRes(R.string.settings_whats_new),
|
||||
icon = R.drawable.ic_settings_whats_new,
|
||||
onClick = ::onWhatsNewClick
|
||||
),
|
||||
ZashiSettingsListItemState(
|
||||
text = stringRes(R.string.settings_about_us),
|
||||
icon = R.drawable.ic_settings_info,
|
||||
|
@ -214,6 +220,12 @@ class SettingsViewModel(
|
|||
}
|
||||
}
|
||||
|
||||
private fun onWhatsNewClick() {
|
||||
viewModelScope.launch {
|
||||
navigationCommand.emit(WHATS_NEW)
|
||||
}
|
||||
}
|
||||
|
||||
private fun booleanStateFlow(default: BooleanPreferenceDefault): StateFlow<Boolean?> =
|
||||
flow<Boolean?> {
|
||||
emitAll(default.observe(standardPreferenceProvider()))
|
||||
|
|
|
@ -1,97 +0,0 @@
|
|||
@file:Suppress("ktlint:standard:filename")
|
||||
|
||||
package co.electriccoin.zcash.ui.screen.support
|
||||
|
||||
import android.content.Intent
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import co.electriccoin.zcash.di.koinActivityViewModel
|
||||
import co.electriccoin.zcash.ui.R
|
||||
import co.electriccoin.zcash.ui.common.compose.LocalActivity
|
||||
import co.electriccoin.zcash.ui.common.model.TopAppBarSubTitleState
|
||||
import co.electriccoin.zcash.ui.common.viewmodel.WalletViewModel
|
||||
import co.electriccoin.zcash.ui.screen.support.model.SupportInfo
|
||||
import co.electriccoin.zcash.ui.screen.support.model.SupportInfoType
|
||||
import co.electriccoin.zcash.ui.screen.support.view.Support
|
||||
import co.electriccoin.zcash.ui.screen.support.viewmodel.SupportViewModel
|
||||
import co.electriccoin.zcash.ui.util.EmailUtil
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
internal fun WrapSupport(goBack: () -> Unit) {
|
||||
val supportViewModel = koinActivityViewModel<SupportViewModel>()
|
||||
|
||||
val walletViewModel = koinActivityViewModel<WalletViewModel>()
|
||||
|
||||
val supportInfo = supportViewModel.supportInfo.collectAsStateWithLifecycle().value
|
||||
|
||||
val walletState = walletViewModel.walletStateInformation.collectAsStateWithLifecycle().value
|
||||
|
||||
BackHandler {
|
||||
goBack()
|
||||
}
|
||||
|
||||
WrapSupport(
|
||||
goBack = goBack,
|
||||
supportInfo = supportInfo,
|
||||
topAppBarSubTitleState = walletState
|
||||
)
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
@Composable
|
||||
internal fun WrapSupport(
|
||||
goBack: () -> Unit,
|
||||
supportInfo: SupportInfo?,
|
||||
topAppBarSubTitleState: TopAppBarSubTitleState,
|
||||
) {
|
||||
val activity = LocalActivity.current
|
||||
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
val (isShowingDialog, setShowDialog) = rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
Support(
|
||||
snackbarHostState = snackbarHostState,
|
||||
isShowingDialog = isShowingDialog,
|
||||
setShowDialog = setShowDialog,
|
||||
onBack = goBack,
|
||||
onSend = { userMessage ->
|
||||
val fullMessage =
|
||||
EmailUtil.formatMessage(
|
||||
body = userMessage,
|
||||
supportInfo = supportInfo?.toSupportString(SupportInfoType.entries.toSet())
|
||||
)
|
||||
val mailIntent =
|
||||
EmailUtil.newMailActivityIntent(
|
||||
activity.getString(R.string.support_email_address),
|
||||
activity.getString(R.string.app_name),
|
||||
fullMessage
|
||||
).apply {
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
}
|
||||
runCatching {
|
||||
activity.startActivity(mailIntent)
|
||||
}.onSuccess {
|
||||
setShowDialog(false)
|
||||
}.onFailure {
|
||||
setShowDialog(false)
|
||||
scope.launch {
|
||||
snackbarHostState.showSnackbar(
|
||||
message = activity.getString(R.string.unable_to_open_email)
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
topAppBarSubTitleState = topAppBarSubTitleState,
|
||||
)
|
||||
}
|
|
@ -9,7 +9,7 @@ import co.electriccoin.zcash.spackle.io.listFilesSuspend
|
|||
import kotlinx.datetime.Instant
|
||||
import java.io.File
|
||||
|
||||
// TODO [#1301]: Localize support text content
|
||||
// TODO [#1301]: Localize feedback text content
|
||||
// TODO [#1301]: https://github.com/Electric-Coin-Company/zashi-android/issues/1301
|
||||
|
||||
data class CrashInfo(val exceptionClassName: String, val isUncaught: Boolean, val timestamp: Instant) {
|
||||
|
|
|
@ -1,249 +0,0 @@
|
|||
package co.electriccoin.zcash.ui.screen.support.view
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import co.electriccoin.zcash.ui.R
|
||||
import co.electriccoin.zcash.ui.common.model.TopAppBarSubTitleState
|
||||
import co.electriccoin.zcash.ui.design.MINIMAL_WEIGHT
|
||||
import co.electriccoin.zcash.ui.design.component.AppAlertDialog
|
||||
import co.electriccoin.zcash.ui.design.component.BlankBgScaffold
|
||||
import co.electriccoin.zcash.ui.design.component.BlankSurface
|
||||
import co.electriccoin.zcash.ui.design.component.Body
|
||||
import co.electriccoin.zcash.ui.design.component.SmallTopAppBar
|
||||
import co.electriccoin.zcash.ui.design.component.TopAppBarBackNavigation
|
||||
import co.electriccoin.zcash.ui.design.component.ZashiButton
|
||||
import co.electriccoin.zcash.ui.design.component.ZashiTextField
|
||||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||
import co.electriccoin.zcash.ui.design.theme.colors.ZashiColors
|
||||
import co.electriccoin.zcash.ui.design.theme.typography.ZashiTypography
|
||||
import co.electriccoin.zcash.ui.design.util.scaffoldPadding
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun SupportPreview() {
|
||||
ZcashTheme(forceDarkMode = false) {
|
||||
BlankSurface {
|
||||
Support(
|
||||
isShowingDialog = false,
|
||||
setShowDialog = {},
|
||||
onBack = {},
|
||||
onSend = {},
|
||||
snackbarHostState = SnackbarHostState(),
|
||||
topAppBarSubTitleState = TopAppBarSubTitleState.None,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun SupportDarkPreview() {
|
||||
ZcashTheme(forceDarkMode = true) {
|
||||
BlankSurface {
|
||||
Support(
|
||||
isShowingDialog = false,
|
||||
setShowDialog = {},
|
||||
onBack = {},
|
||||
onSend = {},
|
||||
snackbarHostState = SnackbarHostState(),
|
||||
topAppBarSubTitleState = TopAppBarSubTitleState.None,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview("Support-Popup")
|
||||
@Composable
|
||||
private fun PreviewSupportPopup() {
|
||||
ZcashTheme(forceDarkMode = false) {
|
||||
SupportConfirmationDialog(
|
||||
onConfirm = {},
|
||||
onDismiss = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Suppress("LongParameterList")
|
||||
fun Support(
|
||||
isShowingDialog: Boolean,
|
||||
setShowDialog: (Boolean) -> Unit,
|
||||
onBack: () -> Unit,
|
||||
onSend: (String) -> Unit,
|
||||
snackbarHostState: SnackbarHostState,
|
||||
topAppBarSubTitleState: TopAppBarSubTitleState,
|
||||
) {
|
||||
val (message, setMessage) = rememberSaveable { mutableStateOf("") }
|
||||
|
||||
BlankBgScaffold(
|
||||
topBar = {
|
||||
SupportTopAppBar(
|
||||
onBack = onBack,
|
||||
subTitleState = topAppBarSubTitleState,
|
||||
)
|
||||
},
|
||||
snackbarHost = { SnackbarHost(snackbarHostState) }
|
||||
) { paddingValues ->
|
||||
SupportMainContent(
|
||||
message = message,
|
||||
setMessage = setMessage,
|
||||
setShowDialog = setShowDialog,
|
||||
modifier = Modifier.scaffoldPadding(paddingValues)
|
||||
)
|
||||
|
||||
if (isShowingDialog) {
|
||||
SupportConfirmationDialog(
|
||||
onConfirm = { onSend(message) },
|
||||
onDismiss = { setShowDialog(false) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SupportTopAppBar(
|
||||
onBack: () -> Unit,
|
||||
subTitleState: TopAppBarSubTitleState
|
||||
) {
|
||||
SmallTopAppBar(
|
||||
subTitle =
|
||||
when (subTitleState) {
|
||||
TopAppBarSubTitleState.Disconnected -> stringResource(id = R.string.disconnected_label)
|
||||
TopAppBarSubTitleState.Restoring -> stringResource(id = R.string.restoring_wallet_label)
|
||||
TopAppBarSubTitleState.None -> null
|
||||
},
|
||||
titleText = stringResource(id = R.string.support_header),
|
||||
navigationAction = {
|
||||
TopAppBarBackNavigation(
|
||||
backText = stringResource(id = R.string.back_navigation).uppercase(),
|
||||
backContentDescriptionText = stringResource(R.string.back_navigation_content_description),
|
||||
onBack = onBack
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
private fun SupportMainContent(
|
||||
message: String,
|
||||
setMessage: (String) -> Unit,
|
||||
setShowDialog: (Boolean) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
|
||||
Column(
|
||||
Modifier
|
||||
.fillMaxHeight()
|
||||
.verticalScroll(
|
||||
rememberScrollState()
|
||||
)
|
||||
.then(modifier),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingSmall))
|
||||
|
||||
Image(
|
||||
imageVector = ImageVector.vectorResource(R.drawable.zashi_logo_sign),
|
||||
colorFilter = ColorFilter.tint(color = ZcashTheme.colors.secondaryColor),
|
||||
contentDescription = null,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingBig))
|
||||
|
||||
Body(
|
||||
text = stringResource(id = R.string.support_information),
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingLarge))
|
||||
|
||||
ZashiTextField(
|
||||
value = message,
|
||||
onValueChange = setMessage,
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.focusRequester(focusRequester),
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringResource(id = R.string.support_hint),
|
||||
style = ZashiTypography.textMd,
|
||||
color = ZashiColors.Inputs.Default.text
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingLarge))
|
||||
|
||||
Spacer(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxHeight()
|
||||
.weight(MINIMAL_WEIGHT)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingLarge))
|
||||
|
||||
// TODO [#1467]: Support screen - keep button above keyboard
|
||||
// TODO [#1467]: https://github.com/Electric-Coin-Company/zashi-android/issues/1467
|
||||
ZashiButton(
|
||||
onClick = { setShowDialog(true) },
|
||||
text = stringResource(id = R.string.support_send),
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
// Causes the TextFiled to focus on the first screen visit
|
||||
focusRequester.requestFocus()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SupportConfirmationDialog(
|
||||
onConfirm: () -> Unit,
|
||||
onDismiss: () -> Unit
|
||||
) {
|
||||
AppAlertDialog(
|
||||
onConfirmButtonClick = onConfirm,
|
||||
confirmButtonText = stringResource(id = R.string.support_confirmation_dialog_ok),
|
||||
dismissButtonText = stringResource(id = R.string.support_confirmation_dialog_cancel),
|
||||
onDismissButtonClick = onDismiss,
|
||||
onDismissRequest = onDismiss,
|
||||
title = stringResource(id = R.string.support_confirmation_dialog_title),
|
||||
text =
|
||||
stringResource(
|
||||
id = R.string.support_confirmation_explanation,
|
||||
stringResource(id = R.string.app_name)
|
||||
)
|
||||
)
|
||||
}
|
|
@ -7,24 +7,28 @@ import co.electriccoin.zcash.ui.design.util.stringRes
|
|||
import kotlinx.datetime.LocalDate
|
||||
|
||||
data class WhatsNewState(
|
||||
val version: StringResource,
|
||||
val titleVersion: StringResource,
|
||||
val bottomVersion: StringResource,
|
||||
val date: LocalDate,
|
||||
val sections: List<WhatsNewSectionState>
|
||||
) {
|
||||
companion object {
|
||||
fun new(changelog: Changelog) =
|
||||
WhatsNewState(
|
||||
version = stringRes(R.string.whats_new_version, changelog.version),
|
||||
date = changelog.date,
|
||||
sections =
|
||||
listOfNotNull(changelog.added, changelog.changed, changelog.fixed, changelog.removed)
|
||||
.map {
|
||||
WhatsNewSectionState(
|
||||
stringRes(value = it.title),
|
||||
stringRes(it.content)
|
||||
)
|
||||
},
|
||||
)
|
||||
fun new(
|
||||
changelog: Changelog,
|
||||
version: String
|
||||
) = WhatsNewState(
|
||||
titleVersion = stringRes(R.string.whats_new_version, changelog.version),
|
||||
bottomVersion = stringRes(R.string.settings_version, version),
|
||||
date = changelog.date,
|
||||
sections =
|
||||
listOfNotNull(changelog.added, changelog.changed, changelog.fixed, changelog.removed)
|
||||
.map {
|
||||
WhatsNewSectionState(
|
||||
stringRes(value = it.title),
|
||||
stringRes(it.content)
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,34 +4,40 @@ import androidx.compose.foundation.layout.Column
|
|||
import androidx.compose.foundation.layout.Row
|
||||
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.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment.Companion.CenterVertically
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.ParagraphStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.rememberTextMeasurer
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextIndent
|
||||
import androidx.compose.ui.text.withStyle
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.sp
|
||||
import co.electriccoin.zcash.ui.R
|
||||
import co.electriccoin.zcash.ui.common.model.TopAppBarSubTitleState
|
||||
import co.electriccoin.zcash.ui.design.component.BlankBgScaffold
|
||||
import co.electriccoin.zcash.ui.design.component.BlankSurface
|
||||
import co.electriccoin.zcash.ui.design.component.SmallTopAppBar
|
||||
import co.electriccoin.zcash.ui.design.component.TopAppBarBackNavigation
|
||||
import co.electriccoin.zcash.ui.design.component.ZashiTopAppBarBackNavigation
|
||||
import co.electriccoin.zcash.ui.design.component.ZashiVersion
|
||||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||
import co.electriccoin.zcash.ui.design.theme.colors.ZashiColors
|
||||
import co.electriccoin.zcash.ui.design.theme.dimensions.ZashiDimensions
|
||||
import co.electriccoin.zcash.ui.design.theme.typography.ZashiTypography
|
||||
import co.electriccoin.zcash.ui.design.util.getValue
|
||||
import co.electriccoin.zcash.ui.fixture.ChangelogFixture
|
||||
import co.electriccoin.zcash.ui.fixture.VersionInfoFixture
|
||||
import co.electriccoin.zcash.ui.screen.whatsnew.model.WhatsNewSectionState
|
||||
import co.electriccoin.zcash.ui.screen.whatsnew.model.WhatsNewState
|
||||
import kotlinx.datetime.toJavaLocalDate
|
||||
|
@ -62,33 +68,42 @@ fun WhatsNewView(
|
|||
) {
|
||||
Row {
|
||||
Text(
|
||||
text = state.version.getValue(),
|
||||
style = ZcashTheme.typography.primary.titleSmall,
|
||||
fontSize = 13.sp
|
||||
text = state.titleVersion.getValue(),
|
||||
style = ZashiTypography.textXl,
|
||||
color = ZashiColors.Text.textPrimary,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.weight(1f),
|
||||
modifier =
|
||||
Modifier
|
||||
.weight(1f)
|
||||
.align(CenterVertically),
|
||||
text = DateTimeFormatter.ISO_LOCAL_DATE.format(state.date.toJavaLocalDate()),
|
||||
textAlign = TextAlign.End,
|
||||
style = ZcashTheme.typography.primary.titleSmall,
|
||||
fontSize = 13.sp
|
||||
style = ZashiTypography.textSm,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = ZashiColors.Text.textPrimary,
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingLarge))
|
||||
Spacer(modifier = Modifier.height(ZashiDimensions.Spacing.spacingXl))
|
||||
|
||||
state.sections.forEach { section ->
|
||||
WhatsNewSection(section)
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingLarge))
|
||||
Spacer(modifier = Modifier.height(ZashiDimensions.Spacing.spacingXl))
|
||||
}
|
||||
|
||||
Spacer(Modifier.weight(1f))
|
||||
|
||||
ZashiVersion(modifier = Modifier.fillMaxWidth(), version = state.bottomVersion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun WhatsNewSection(state: WhatsNewSectionState) {
|
||||
val bulletString = "\u2022\t\t"
|
||||
val bulletTextStyle = MaterialTheme.typography.bodySmall
|
||||
val bulletString = "\u2022 "
|
||||
val bulletTextStyle = ZashiTypography.textSm
|
||||
val bulletTextMeasurer = rememberTextMeasurer()
|
||||
val bulletStringWidth =
|
||||
remember(bulletTextStyle, bulletTextMeasurer) {
|
||||
|
@ -116,12 +131,15 @@ private fun WhatsNewSection(state: WhatsNewSectionState) {
|
|||
Column {
|
||||
Text(
|
||||
text = state.title.getValue(),
|
||||
style = ZcashTheme.typography.primary.titleSmall,
|
||||
color = ZashiColors.Text.textPrimary,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
style = ZashiTypography.textMd,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingMin))
|
||||
|
||||
Text(
|
||||
modifier = Modifier.padding(start = ZashiDimensions.Spacing.spacingMd),
|
||||
text = bulletStyle,
|
||||
style = bulletTextStyle
|
||||
)
|
||||
|
@ -142,11 +160,7 @@ private fun AppBar(
|
|||
},
|
||||
titleText = stringResource(id = R.string.whats_new_title).uppercase(),
|
||||
navigationAction = {
|
||||
TopAppBarBackNavigation(
|
||||
backText = stringResource(id = R.string.back_navigation).uppercase(),
|
||||
backContentDescriptionText = stringResource(R.string.back_navigation_content_description),
|
||||
onBack = onBack
|
||||
)
|
||||
ZashiTopAppBarBackNavigation(onBack = onBack)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
@ -155,7 +169,11 @@ private fun AppBar(
|
|||
private fun WhatsNewViewPreview() {
|
||||
BlankSurface {
|
||||
WhatsNewView(
|
||||
state = WhatsNewState.new(ChangelogFixture.new()),
|
||||
state =
|
||||
WhatsNewState.new(
|
||||
changelog = ChangelogFixture.new(),
|
||||
version = VersionInfoFixture.new().versionName
|
||||
),
|
||||
walletState = TopAppBarSubTitleState.None,
|
||||
onBack = {}
|
||||
)
|
||||
|
|
|
@ -15,7 +15,12 @@ import kotlinx.coroutines.flow.stateIn
|
|||
class WhatsNewViewModel(application: Application) : AndroidViewModel(application) {
|
||||
val state: StateFlow<WhatsNewState?> =
|
||||
flow {
|
||||
val changelog = VersionInfo.new(application).changelog
|
||||
emit(WhatsNewState.new(changelog))
|
||||
val versionInfo = VersionInfo.new(application)
|
||||
emit(
|
||||
WhatsNewState.new(
|
||||
changelog = versionInfo.changelog,
|
||||
version = versionInfo.versionName
|
||||
)
|
||||
)
|
||||
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT), null)
|
||||
}
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="about_title">Acerca de</string>
|
||||
<string name="about_subtitle">Introducing Zashi</string>
|
||||
<string name="about_version_format" formatted="true">Versión de Zashi <xliff:g example="1" id="version">%s</xliff:g></string>
|
||||
<string name="about_debug_menu_app_name">Nombre de la app:<xliff:g example="Zashi" id="app_name">%1$s</xliff:g></string>
|
||||
<string name="about_debug_menu_build">Compilación: <xliff:g example="635dac0eb9ddc2bc6da5177f0dd495d8b76af4dc" id="git_commit_hash">%1$s</xliff:g></string>
|
||||
<string name="about_description">¡Envía y recibe ZEC en Zashi!\nZashi es una billetera de diseño minimalista y autogestionada, exclusivamente para ZEC, que mantiene tu historial de transacciones y saldo de la billetera privados. Construida por Zcashers, para Zcashers. Desarrollada y mantenida por Electric Coin Co., el inventor de Zcash, Zashi cuenta con un mecanismo de retroalimentación integrado para habilitar más funciones, más rápidamente.</string>
|
||||
<string name="about_description">Send and receive ZEC on Zashi!\nZashi is a minimal-design, self-custody,
|
||||
ZEC-only shielded wallet that keeps your transaction history and wallet balance private.\n\nBuilt by
|
||||
Zcashers, for Zcashers. Developed and maintained by Electric Coin Co., the inventor of Zcash, Zashi features
|
||||
a built-in user-feedback mechanism to enable more features, more quickly.
|
||||
</string>
|
||||
|
||||
<string name="about_pp_part_1">Consulta nuestra Política de Privacidad\u0020</string>
|
||||
<string name="about_pp_part_2">aquí</string>
|
||||
<string name="about_pp_part_3">.</string>
|
||||
<string name="about_unable_to_web_browser">No se pudo encontrar una aplicación de navegador web.</string>
|
||||
<string name="about_button_whats_new">Novedades</string>
|
||||
<string name="about_button_privacy_policy">Política de Privacidad</string>
|
||||
</resources>
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="about_title">About</string>
|
||||
<string name="about_subtitle">Introducing Zashi</string>
|
||||
<string name="about_version_format" formatted="true">Zashi Version <xliff:g example="1" id="version">%s</xliff:g></string>
|
||||
<string name="about_debug_menu_app_name">App name:<xliff:g example="Zashi" id="app_name">%1$s</xliff:g></string>
|
||||
<string name="about_debug_menu_build">Build: <xliff:g example="635dac0eb9ddc2bc6da5177f0dd495d8b76af4dc" id="git_commit_hash">%1$s</xliff:g></string>
|
||||
<string name="about_description">Send and receive ZEC on Zashi!\nZashi is a minimal-design, self-custody, ZEC-only shielded wallet that keeps your transaction history and wallet balance private. Built by Zcashers, for Zcashers. Developed and maintained by Electric Coin Co., the inventor of Zcash, Zashi features a built-in user-feedback mechanism to enable more features, more quickly.</string>
|
||||
<string name="about_description">Send and receive ZEC on Zashi!\nZashi is a minimal-design, self-custody,
|
||||
ZEC-only shielded wallet that keeps your transaction history and wallet balance private.\n\nBuilt by
|
||||
Zcashers, for Zcashers. Developed and maintained by Electric Coin Co., the inventor of Zcash, Zashi features
|
||||
a built-in user-feedback mechanism to enable more features, more quickly.
|
||||
</string>
|
||||
|
||||
<string name="about_pp_part_1">See our Privacy Policy\u0020</string>
|
||||
<string name="about_pp_part_2">here</string>
|
||||
<string name="about_pp_part_3">.</string>
|
||||
<string name="about_unable_to_web_browser">Unable to find a web browser app.</string>
|
||||
<string name="about_button_whats_new">What\'s new</string>
|
||||
<string name="about_button_privacy_policy">Privacy Policy</string>
|
||||
</resources>
|
|
@ -5,5 +5,5 @@
|
|||
<string name="advanced_settings_choose_server">Elegir un servidor</string>
|
||||
<string name="advanced_settings_currency_conversion">Conversión de moneda</string>
|
||||
<string name="advanced_settings_info">Se te pedirá confirmación en la siguiente pantalla</string>
|
||||
<string name="advanced_settings_delete_button">Eliminar Zashi</string>
|
||||
<string name="advanced_settings_delete_button">Reset Zashi</string>
|
||||
</resources>
|
||||
|
|
|
@ -5,5 +5,5 @@
|
|||
<string name="advanced_settings_choose_server">Choose a Server</string>
|
||||
<string name="advanced_settings_currency_conversion">Currency Conversion</string>
|
||||
<string name="advanced_settings_info">You will be asked to confirm on the next screen</string>
|
||||
<string name="advanced_settings_delete_button">Delete Zashi</string>
|
||||
<string name="advanced_settings_delete_button">Reset Zashi</string>
|
||||
</resources>
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="20dp"
|
||||
android:height="20dp"
|
||||
android:viewportWidth="20"
|
||||
android:viewportHeight="20">
|
||||
<group>
|
||||
<clip-path
|
||||
android:pathData="M0,0h20v20h-20z"/>
|
||||
<path
|
||||
android:pathData="M10,6.667V10M10,13.334H10.008M18.333,10C18.333,14.603 14.602,18.334 10,18.334C5.397,18.334 1.666,14.603 1.666,10C1.666,5.398 5.397,1.667 10,1.667C14.602,1.667 18.333,5.398 18.333,10Z"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="1.66667"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#EF6820"
|
||||
android:strokeLineCap="round"/>
|
||||
</group>
|
||||
</vector>
|
|
@ -0,0 +1,17 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="16dp"
|
||||
android:height="16dp"
|
||||
android:viewportWidth="16"
|
||||
android:viewportHeight="16">
|
||||
<group>
|
||||
<clip-path
|
||||
android:pathData="M0,0h16v16h-16z"/>
|
||||
<path
|
||||
android:pathData="M8,10.666V8M8,5.333H8.007M14.667,8C14.667,11.682 11.682,14.666 8,14.666C4.318,14.666 1.334,11.682 1.334,8C1.334,4.318 4.318,1.333 8,1.333C11.682,1.333 14.667,4.318 14.667,8Z"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="1.33"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#A6A391"
|
||||
android:strokeLineCap="round"/>
|
||||
</group>
|
||||
</vector>
|
|
@ -1,23 +1,12 @@
|
|||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="delete_wallet_title">
|
||||
Eliminar <xliff:g id="app_name" example="Zashi">%1$s</xliff:g>
|
||||
</string>
|
||||
|
||||
<string name="delete_wallet_title">Reset Zashi</string>
|
||||
<string name="delete_wallet_text_1">
|
||||
Por favor, no elimine esta aplicación a menos que esté seguro de comprender los efectos.
|
||||
Please don\'t reset this app unless you\'re sure you understand the effects.
|
||||
</string>
|
||||
<string name="delete_wallet_text_2">
|
||||
Eliminar la aplicación <xliff:g id="app_name" example="Zashi">%1$s</xliff:g> eliminará la base de datos y los datos
|
||||
almacenados en caché. Cualquier fondo que tenga en esta billetera se perderá y solo podrá recuperarse utilizando su frase de
|
||||
recuperación secreta de <xliff:g id="app_name" example="Zashi">%1$s</xliff:g> en <xliff:g id="app_name"
|
||||
example="Zashi">%1$s</xliff:g> u otra billetera Zcash.
|
||||
Resetting the Zashi app will delete the database and cached data. Any funds you have in this wallet will be lost and can only be recovered by using your Zashi secret recovery phrase in another Zcash wallet.
|
||||
</string>
|
||||
|
||||
<string name="delete_wallet_acknowledge">Entiendo</string>
|
||||
|
||||
<string name="delete_wallet_button">
|
||||
Eliminar <xliff:g id="app_name" example="Zashi">%1$s</xliff:g>
|
||||
</string>
|
||||
|
||||
<string name="delete_wallet_failed">La eliminación de la billetera falló. Inténtelo de nuevo, por favor.</string>
|
||||
<string name="delete_wallet_acknowledge">I understand</string>
|
||||
<string name="delete_wallet_button">Reset Zashi</string>
|
||||
<string name="delete_wallet_failed">Wallet deletion failed. Try it again, please.</string>
|
||||
</resources>
|
||||
|
|
|
@ -1,23 +1,12 @@
|
|||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="delete_wallet_title">
|
||||
Delete <xliff:g id="app_name" example="Zashi">%1$s</xliff:g>
|
||||
</string>
|
||||
|
||||
<string name="delete_wallet_title">Reset Zashi</string>
|
||||
<string name="delete_wallet_text_1">
|
||||
Please don\'t delete this app unless you\'re sure you understand the effects.
|
||||
Please don\'t reset this app unless you\'re sure you understand the effects.
|
||||
</string>
|
||||
<string name="delete_wallet_text_2">
|
||||
Deleting the <xliff:g id="app_name" example="Zashi">%1$s</xliff:g> app will delete the database and cached
|
||||
data. Any funds you have in this wallet will be lost and can only be recovered by using your <xliff:g
|
||||
id="app_name" example="Zashi">%1$s</xliff:g> secret recovery phrase in <xliff:g id="app_name"
|
||||
example="Zashi">%1$s</xliff:g> or another Zcash wallet.
|
||||
Resetting the Zashi app will delete the database and cached data. Any funds you have in this wallet will be lost and can only be recovered by using your Zashi secret recovery phrase in another Zcash wallet.
|
||||
</string>
|
||||
|
||||
<string name="delete_wallet_acknowledge">I understand</string>
|
||||
|
||||
<string name="delete_wallet_button">
|
||||
Delete <xliff:g id="app_name" example="Zashi">%1$s</xliff:g>
|
||||
</string>
|
||||
|
||||
<string name="delete_wallet_button">Reset Zashi</string>
|
||||
<string name="delete_wallet_failed">Wallet deletion failed. Try it again, please.</string>
|
||||
</resources>
|
||||
|
|
|
@ -1,9 +1,16 @@
|
|||
<resources>
|
||||
<string name="export_data_title">Data Export</string>
|
||||
<string name="export_data_header">Consentimiento para Exportar Datos Privados</string>
|
||||
<string name="export_data_text_1">Al hacer clic en \"Estoy de acuerdo\" a continuación, das tu consentimiento para exportar los datos privados de Zashi, lo cual incluye todo el historial de la billetera, toda la información privada, los memos, los montos y las direcciones de los destinatarios, incluso para tu actividad protegida.*\n\nEstos datos privados también permiten ver ciertas acciones futuras que realices con Zashi.\n\nCompartir estos datos privados es irrevocable: una vez que hayas compartido estos datos privados con alguien, no hay forma de revocar su acceso.</string>
|
||||
<string name="export_data_text_2">*Ten en cuenta que estos datos privados no les dan la capacidad de gastar tus fondos, solo la capacidad de ver lo que haces con tus fondos.</string>
|
||||
<string name="export_data_confirm">Exportar datos privados</string>
|
||||
<string name="export_data_agree">Estoy de acuerdo</string>
|
||||
<string name="export_data_text">
|
||||
By clicking “I Agree” below, you give your consent to export Zashi\’s private data which includes the entire
|
||||
history of the wallet, sll private information, memos, amounts, and recipient addresses, even for your
|
||||
shielded activity.*\n\nThe private data also gives the ability to see certain future actions you take with
|
||||
Zashi.\n\nSharing this private data is irrevocable - once you have shared this private data with someone, there
|
||||
is no way to revoke their access.\n\n*Note that this private data does not give them the ability to spend your
|
||||
funds, only the ability to see what you do with your funds.
|
||||
</string>
|
||||
<string name="export_data_confirm">Export Private Data</string>
|
||||
<string name="export_data_agree">I agree to Zashi\'s Export Private Data Policies and Privacy Policy</string>
|
||||
<string name="export_data_export_data_chooser_title">Compartir datos internos de Zashi con:</string>
|
||||
<string name="export_data_unable_to_share">No se pudo encontrar una aplicación con la cual compartir.</string>
|
||||
</resources>
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
<resources>
|
||||
<string name="export_data_title">Data Export</string>
|
||||
<string name="export_data_header">Consent for Exporting Private Data</string>
|
||||
<string name="export_data_text_1">By clicking \"I Agree\" below, you give your consent to export Zashi’s private
|
||||
data which includes the entire history of the wallet, all private information, memos, amounts and recipient
|
||||
addresses, even for your shielded activity.*\n\nThis private data also gives the ability to see certain future
|
||||
actions you take with Zashi.\n\nSharing this private data is irrevocable — once you have shared this private
|
||||
data with someone, there is no way to revoke their access.</string>
|
||||
<string name="export_data_text_2">*Note that this private data does not give them the ability to spend your
|
||||
funds, only the ability to see what you do with your funds.</string>
|
||||
<string name="export_data_confirm">Export private data</string>
|
||||
<string name="export_data_agree">I agree</string>
|
||||
<string name="export_data_text">
|
||||
By clicking “I Agree” below, you give your consent to export Zashi\’s private data which includes the entire
|
||||
history of the wallet, sll private information, memos, amounts, and recipient addresses, even for your
|
||||
shielded activity.*\n\nThe private data also gives the ability to see certain future actions you take with
|
||||
Zashi.\n\nSharing this private data is irrevocable - once you have shared this private data with someone, there
|
||||
is no way to revoke their access.\n\n*Note that this private data does not give them the ability to spend your
|
||||
funds, only the ability to see what you do with your funds.
|
||||
</string>
|
||||
<string name="export_data_confirm">Export Private Data</string>
|
||||
<string name="export_data_agree">I agree to Zashi\'s Export Private Data Policies and Privacy Policy</string>
|
||||
<string name="export_data_export_data_chooser_title">Share internal Zashi data with:</string>
|
||||
<string name="export_data_unable_to_share">Unable to find an application to share with.</string>
|
||||
</resources>
|
|
@ -0,0 +1,37 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="106dp"
|
||||
android:height="64dp"
|
||||
android:viewportWidth="106"
|
||||
android:viewportHeight="64">
|
||||
<path
|
||||
android:pathData="M8,30C8,16.75 18.75,6 32,6C45.25,6 56,16.75 56,30C56,43.25 45.25,54 32,54C18.75,54 8,43.25 8,30Z"
|
||||
android:fillColor="#454243"/>
|
||||
<path
|
||||
android:pathData="M32,4C17.64,4 6,15.64 6,30C6,44.36 17.64,56 32,56C46.36,56 58,44.36 58,30C58,15.64 46.36,4 32,4Z"
|
||||
android:strokeWidth="4"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#231F20"/>
|
||||
<path
|
||||
android:pathData="M26.09,29.23C26.03,28.83 26,28.42 26,28C26,23.58 29.61,20 34.05,20C38.5,20 42.11,23.58 42.11,28C42.11,29 41.92,29.95 41.59,30.83C41.52,31.02 41.48,31.11 41.46,31.18C41.45,31.25 41.44,31.3 41.44,31.37C41.44,31.45 41.45,31.53 41.47,31.69L41.87,34.96C41.92,35.31 41.94,35.49 41.88,35.62C41.83,35.73 41.74,35.82 41.62,35.87C41.49,35.93 41.31,35.9 40.96,35.85L37.78,35.38C37.61,35.36 37.53,35.34 37.45,35.34C37.38,35.35 37.32,35.35 37.25,35.37C37.18,35.38 37.08,35.42 36.89,35.49C36.01,35.82 35.05,36 34.05,36C33.63,36 33.22,35.97 32.82,35.91M27.63,40C30.6,40 33,37.54 33,34.5C33,31.46 30.6,29 27.63,29C24.67,29 22.26,31.46 22.26,34.5C22.26,35.11 22.36,35.7 22.54,36.25C22.62,36.48 22.65,36.59 22.67,36.67C22.68,36.76 22.68,36.8 22.68,36.89C22.67,36.97 22.65,37.06 22.61,37.24L22,40L24.99,39.59C25.16,39.57 25.24,39.56 25.31,39.56C25.39,39.56 25.43,39.56 25.5,39.58C25.57,39.59 25.67,39.63 25.88,39.7C26.43,39.89 27.02,40 27.63,40Z"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#E8E8E8"
|
||||
android:strokeLineCap="round"/>
|
||||
<group>
|
||||
<clip-path
|
||||
android:pathData="M52,30C52,16.75 62.75,6 76,6C89.25,6 100,16.75 100,30C100,43.25 89.25,54 76,54C62.75,54 52,43.25 52,30Z"/>
|
||||
<path
|
||||
android:pathData="M76,6L76,6A24,24 0,0 1,100 30L100,30A24,24 0,0 1,76 54L76,54A24,24 0,0 1,52 30L52,30A24,24 0,0 1,76 6z"
|
||||
android:fillColor="#231F20"/>
|
||||
<path
|
||||
android:pathData="M52,30C52,16.76 62.76,6 76,6C89.24,6 100,16.76 100,30C100,43.24 89.24,54 76,54C62.76,54 52,43.24 52,30ZM84.56,18.86V22.51L74.4,36.29H84.56V41.14H78.01V45.15H73.99V41.14H67.44V37.48L77.59,23.71H67.44V18.86H73.99V14.84H78.01V18.86H84.56Z"
|
||||
android:fillColor="#FCBB1A"
|
||||
android:fillType="evenOdd"/>
|
||||
</group>
|
||||
<path
|
||||
android:pathData="M76,4.5C61.92,4.5 50.5,15.92 50.5,30C50.5,44.08 61.92,55.5 76,55.5C90.08,55.5 101.5,44.08 101.5,30C101.5,15.92 90.08,4.5 76,4.5Z"
|
||||
android:strokeWidth="3"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#231F20"/>
|
||||
</vector>
|
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
|
@ -0,0 +1,37 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="106dp"
|
||||
android:height="64dp"
|
||||
android:viewportWidth="106"
|
||||
android:viewportHeight="64">
|
||||
<path
|
||||
android:pathData="M8,30C8,16.75 18.75,6 32,6C45.25,6 56,16.75 56,30C56,43.25 45.25,54 32,54C18.75,54 8,43.25 8,30Z"
|
||||
android:fillColor="#EBEBE6"/>
|
||||
<path
|
||||
android:pathData="M32,4C17.64,4 6,15.64 6,30C6,44.36 17.64,56 32,56C46.36,56 58,44.36 58,30C58,15.64 46.36,4 32,4Z"
|
||||
android:strokeWidth="4"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"/>
|
||||
<path
|
||||
android:pathData="M26.09,29.23C26.03,28.83 26,28.42 26,28C26,23.58 29.61,20 34.05,20C38.5,20 42.11,23.58 42.11,28C42.11,29 41.92,29.95 41.59,30.83C41.52,31.02 41.48,31.11 41.46,31.18C41.45,31.25 41.44,31.3 41.44,31.37C41.44,31.45 41.45,31.53 41.47,31.69L41.87,34.96C41.92,35.31 41.94,35.49 41.88,35.62C41.83,35.73 41.74,35.82 41.62,35.87C41.49,35.93 41.31,35.9 40.96,35.85L37.78,35.38C37.61,35.36 37.53,35.34 37.45,35.34C37.38,35.35 37.32,35.35 37.25,35.37C37.18,35.38 37.08,35.42 36.89,35.49C36.01,35.82 35.05,36 34.05,36C33.63,36 33.22,35.97 32.82,35.91M27.63,40C30.6,40 33,37.54 33,34.5C33,31.46 30.6,29 27.63,29C24.67,29 22.26,31.46 22.26,34.5C22.26,35.11 22.36,35.7 22.54,36.25C22.62,36.48 22.65,36.59 22.67,36.67C22.68,36.76 22.68,36.8 22.68,36.89C22.67,36.97 22.65,37.06 22.61,37.24L22,40L24.99,39.59C25.16,39.57 25.24,39.56 25.31,39.56C25.39,39.56 25.43,39.56 25.5,39.58C25.57,39.59 25.67,39.63 25.88,39.7C26.43,39.89 27.02,40 27.63,40Z"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#231F20"
|
||||
android:strokeLineCap="round"/>
|
||||
<group>
|
||||
<clip-path
|
||||
android:pathData="M52,30C52,16.75 62.75,6 76,6C89.25,6 100,16.75 100,30C100,43.25 89.25,54 76,54C62.75,54 52,43.25 52,30Z"/>
|
||||
<path
|
||||
android:pathData="M76,6L76,6A24,24 0,0 1,100 30L100,30A24,24 0,0 1,76 54L76,54A24,24 0,0 1,52 30L52,30A24,24 0,0 1,76 6z"
|
||||
android:fillColor="#ffffff"/>
|
||||
<path
|
||||
android:pathData="M52,30C52,16.76 62.76,6 76,6C89.24,6 100,16.76 100,30C100,43.24 89.24,54 76,54C62.76,54 52,43.24 52,30ZM84.56,18.86V22.51L74.4,36.29H84.56V41.14H78.01V45.15H73.99V41.14H67.44V37.48L77.59,23.71H67.44V18.86H73.99V14.84H78.01V18.86H84.56Z"
|
||||
android:fillColor="#FCBB1A"
|
||||
android:fillType="evenOdd"/>
|
||||
</group>
|
||||
<path
|
||||
android:pathData="M76,4.5C61.92,4.5 50.5,15.92 50.5,30C50.5,44.08 61.92,55.5 76,55.5C90.08,55.5 101.5,44.08 101.5,30C101.5,15.92 90.08,4.5 76,4.5Z"
|
||||
android:strokeWidth="3"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"/>
|
||||
</vector>
|
|
@ -1,12 +1,16 @@
|
|||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="support_header">Support</string>
|
||||
|
||||
<string name="support_hint">How can we help?</string>
|
||||
<string name="support_title">Send Us Feedback</string>
|
||||
<string name="support_experience_title">How is your Zashi experience?</string>
|
||||
<string name="support_help_title">How can we help you?</string>
|
||||
<string name="support_hint">I would like to ask about…</string>
|
||||
<string name="support_send">Send</string>
|
||||
<string name="support_confirmation_dialog_ok">OK</string>
|
||||
<string name="support_confirmation_dialog_cancel">Cancel</string>
|
||||
<string name="support_confirmation_dialog_title">Open e-mail app</string>
|
||||
<string name="support_confirmation_explanation"><xliff:g id="app_name" example="Zcash">%1$s</xliff:g> is about to
|
||||
<string name="support_confirmation_explanation">Zashi is about to
|
||||
open your e-mail app with a pre-filled message.\n\nBe sure to hit send within your e-mail app.</string>
|
||||
<string name="support_information">Please let us know about any problems you have had, or features you want to see in the future.</string>
|
||||
<string name="support_email_part_1">How is your Zashi experience?\n%s %s/5</string>
|
||||
<string name="support_email_part_2">How can we help you?\n%s</string>
|
||||
</resources>
|
|
@ -0,0 +1,16 @@
|
|||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="support_header">Support</string>
|
||||
<string name="support_title">Send Us Feedback</string>
|
||||
<string name="support_experience_title">How is your Zashi experience?</string>
|
||||
<string name="support_help_title">How can we help you?</string>
|
||||
<string name="support_hint">I would like to ask about…</string>
|
||||
<string name="support_send">Send</string>
|
||||
<string name="support_confirmation_dialog_ok">OK</string>
|
||||
<string name="support_confirmation_dialog_cancel">Cancel</string>
|
||||
<string name="support_confirmation_dialog_title">Open e-mail app</string>
|
||||
<string name="support_confirmation_explanation">Zashi is about to
|
||||
open your e-mail app with a pre-filled message.\n\nBe sure to hit send within your e-mail app.</string>
|
||||
<string name="support_information">Please let us know about any problems you have had, or features you want to see in the future.</string>
|
||||
<string name="support_email_part_1">How is your Zashi experience?\n%s %s/5</string>
|
||||
<string name="support_email_part_2">How can we help you?\n%s</string>
|
||||
</resources>
|
|
@ -1,9 +0,0 @@
|
|||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="new_wallet_recovery_header">Tu frase secreta de recuperación</string>
|
||||
<string name="new_wallet_recovery_description">Las siguientes 24 palabras son la clave para tus fondos y constituyen la única forma de recuperarlos si pierdes el acceso o adquieres un nuevo dispositivo. ¡Protege tu ZEC almacenando esta frase en un lugar de confianza y nunca la compartas con nadie!</string>
|
||||
<string name="new_wallet_recovery_birthday_height" formatted="true">Altura de cumpleaños de la billetera: <xliff:g example="419200" id="birthday_height">%1$d</xliff:g></string>
|
||||
<string name="new_wallet_recovery_button_finished">Ya la he guardado</string>
|
||||
<string name="new_wallet_recovery_copy">Toca para copiar</string>
|
||||
<string name="new_wallet_recovery_seed_clipboard_tag">Frase de Semilla de Zcash</string>
|
||||
<string name="new_wallet_recovery_birthday_clipboard_tag">Cumpleaños de la Billetera Zcash</string>
|
||||
</resources>
|
|
@ -1,12 +0,0 @@
|
|||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="new_wallet_recovery_header">Your secret recovery phrase</string>
|
||||
<string name="new_wallet_recovery_description">The following 24 words are the keys to your funds and are the only way to
|
||||
recover your funds if you get locked out or get a new device. Protect your ZEC by storing this phrase in a
|
||||
place you trust and never share it with anyone!</string>
|
||||
<string name="new_wallet_recovery_birthday_height" formatted="true">Wallet birthday height: <xliff:g example="419200"
|
||||
id="birthday_height">%1$d</xliff:g></string>
|
||||
<string name="new_wallet_recovery_button_finished">I\'ve saved it</string>
|
||||
<string name="new_wallet_recovery_copy">Tap to Copy</string>
|
||||
<string name="new_wallet_recovery_seed_clipboard_tag">Zcash Seed Phrase</string>
|
||||
<string name="new_wallet_recovery_birthday_clipboard_tag">Zcash Wallet Birthday</string>
|
||||
</resources>
|
|
@ -0,0 +1,25 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="33dp"
|
||||
android:height="32dp"
|
||||
android:viewportWidth="33"
|
||||
android:viewportHeight="32">
|
||||
<path
|
||||
android:pathData="M16.5,20C18.709,20 20.5,18.209 20.5,16C20.5,13.791 18.709,12 16.5,12C14.291,12 12.5,13.791 12.5,16C12.5,18.209 14.291,20 16.5,20Z"
|
||||
android:strokeAlpha="0.12"
|
||||
android:fillColor="#231F20"
|
||||
android:fillAlpha="0.12"/>
|
||||
<path
|
||||
android:pathData="M3.726,16.951C3.545,16.664 3.454,16.52 3.403,16.298C3.365,16.132 3.365,15.869 3.403,15.702C3.454,15.481 3.545,15.337 3.726,15.049C5.227,12.673 9.693,6.667 16.5,6.667C23.307,6.667 27.773,12.673 29.274,15.049C29.455,15.337 29.546,15.481 29.597,15.702C29.635,15.869 29.635,16.132 29.597,16.298C29.546,16.52 29.455,16.664 29.274,16.951C27.773,19.327 23.307,25.334 16.5,25.334C9.693,25.334 5.227,19.327 3.726,16.951Z"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2.66667"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#231F20"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M16.5,20C18.709,20 20.5,18.209 20.5,16C20.5,13.791 18.709,12 16.5,12C14.291,12 12.5,13.791 12.5,16C12.5,18.209 14.291,20 16.5,20Z"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2.66667"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#231F20"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
|
@ -0,0 +1,13 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="20dp"
|
||||
android:height="18dp"
|
||||
android:viewportWidth="20"
|
||||
android:viewportHeight="18">
|
||||
<path
|
||||
android:pathData="M8.952,3.244C9.291,3.194 9.641,3.167 10,3.167C14.255,3.167 17.046,6.921 17.984,8.406C18.097,8.585 18.154,8.675 18.186,8.814C18.21,8.918 18.21,9.082 18.186,9.186C18.154,9.325 18.097,9.415 17.983,9.596C17.733,9.992 17.352,10.548 16.847,11.15M5.604,4.596C3.802,5.818 2.579,7.516 2.018,8.404C1.904,8.585 1.847,8.675 1.815,8.814C1.791,8.918 1.791,9.082 1.815,9.186C1.847,9.325 1.903,9.415 2.017,9.594C2.955,11.079 5.746,14.833 10,14.833C11.716,14.833 13.193,14.223 14.407,13.397M2.5,1.5L17.5,16.5M8.233,7.232C7.78,7.685 7.5,8.31 7.5,9C7.5,10.381 8.62,11.5 10,11.5C10.691,11.5 11.316,11.22 11.768,10.768"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#231F20"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
|
@ -0,0 +1,20 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="20dp"
|
||||
android:height="14dp"
|
||||
android:viewportWidth="20"
|
||||
android:viewportHeight="14">
|
||||
<path
|
||||
android:pathData="M2.017,7.595C1.903,7.415 1.847,7.325 1.815,7.187C1.791,7.082 1.791,6.918 1.815,6.814C1.847,6.676 1.903,6.586 2.017,6.406C2.955,4.921 5.746,1.167 10,1.167C14.255,1.167 17.046,4.921 17.984,6.406C18.097,6.586 18.154,6.676 18.186,6.814C18.21,6.918 18.21,7.082 18.186,7.187C18.154,7.325 18.097,7.415 17.984,7.595C17.046,9.08 14.255,12.834 10,12.834C5.746,12.834 2.955,9.08 2.017,7.595Z"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#231F20"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M10,9.5C11.381,9.5 12.5,8.381 12.5,7C12.5,5.62 11.381,4.5 10,4.5C8.62,4.5 7.5,5.62 7.5,7C7.5,8.381 8.62,9.5 10,9.5Z"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#231F20"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
|
@ -1,9 +1,18 @@
|
|||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="seed_recovery_header">Tu frase secreta de recuperación</string>
|
||||
<string name="seed_recovery_description">Las siguientes 24 palabras son la clave para tus fondos y constituyen la única forma de recuperarlos si pierdes el acceso o adquieres un nuevo dispositivo. ¡Protege tu ZEC almacenando esta frase en un lugar de confianza y nunca la compartas con nadie!</string>
|
||||
<string name="seed_recovery_birthday_height" formatted="true">Altura de cumpleaños de la billetera: <xliff:g example="419200" id="birthday_height">%1$d</xliff:g></string>
|
||||
<string name="seed_recovery_button_finished">¡Entendido!</string>
|
||||
<string name="seed_recovery_copy">Toca para copiar</string>
|
||||
<string name="seed_recovery_seed_clipboard_tag">Frase de Semilla de Zcash</string>
|
||||
<string name="seed_recovery_birthday_clipboard_tag">Cumpleaños de la Billetera Zcash</string>
|
||||
<string name="seed_recovery_title">Recovery Phrase</string>
|
||||
<string name="seed_recovery_header">Secure Your Wallet</string>
|
||||
<string name="seed_recovery_description">The following 24 words are the keys to your funds and are the only way
|
||||
to recover your funds if you get locked out or get a new device.</string>
|
||||
<string name="seed_recovery_reveal_button">Reveal security details</string>
|
||||
<string name="seed_recovery_hide_button">Hide security details</string>
|
||||
<string name="seed_recovery_next_button">Next</string>
|
||||
<string name="seed_recovery_phrase_title">Recovery Phrase</string>
|
||||
<string name="seed_recovery_bday_title">Wallet Birthday Height</string>
|
||||
<string name="seed_recovery_bday_tooltip_title">Wallet Birthday Height</string>
|
||||
<string name="seed_recovery_bday_tooltip_message">Wallet Birthday Height determines the birth (chain) height of
|
||||
your wallet and facilitates faster wallet restore process. Save this number together with your seed phrase in
|
||||
a safe place.</string>
|
||||
<string name="seed_recovery_warning">Protect your ZEC by storing this phrase in a place you trust and never share
|
||||
it with anyone!</string>
|
||||
<string name="seed_recovery_reveal">Reveal recovery phrase</string>
|
||||
</resources>
|
||||
|
|
|
@ -1,12 +1,18 @@
|
|||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="seed_recovery_header">Your secret recovery phrase</string>
|
||||
<string name="seed_recovery_description">The following 24 words are the keys to your funds and are the only way to
|
||||
recover your funds if you get locked out or get a new device. Protect your ZEC by storing this phrase in a
|
||||
place you trust and never share it with anyone!</string>
|
||||
<string name="seed_recovery_birthday_height" formatted="true">Wallet birthday height: <xliff:g example="419200"
|
||||
id="birthday_height">%1$d</xliff:g></string>
|
||||
<string name="seed_recovery_button_finished">I got it!</string>
|
||||
<string name="seed_recovery_copy">Tap to Copy</string>
|
||||
<string name="seed_recovery_seed_clipboard_tag">Zcash Seed Phrase</string>
|
||||
<string name="seed_recovery_birthday_clipboard_tag">Zcash Wallet Birthday</string>
|
||||
<string name="seed_recovery_title">Recovery Phrase</string>
|
||||
<string name="seed_recovery_header">Secure Your Wallet</string>
|
||||
<string name="seed_recovery_description">The following 24 words are the keys to your funds and are the only way
|
||||
to recover your funds if you get locked out or get a new device.</string>
|
||||
<string name="seed_recovery_reveal_button">Reveal security details</string>
|
||||
<string name="seed_recovery_hide_button">Hide security details</string>
|
||||
<string name="seed_recovery_next_button">Next</string>
|
||||
<string name="seed_recovery_phrase_title">Recovery Phrase</string>
|
||||
<string name="seed_recovery_bday_title">Wallet Birthday Height</string>
|
||||
<string name="seed_recovery_bday_tooltip_title">Wallet Birthday Height</string>
|
||||
<string name="seed_recovery_bday_tooltip_message">Wallet Birthday Height determines the birth (chain) height of
|
||||
your wallet and facilitates faster wallet restore process. Save this number together with your seed phrase in
|
||||
a safe place.</string>
|
||||
<string name="seed_recovery_warning">Protect your ZEC by storing this phrase in a place you trust and never share
|
||||
it with anyone!</string>
|
||||
<string name="seed_recovery_reveal">Reveal recovery phrase</string>
|
||||
</resources>
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:viewportWidth="40"
|
||||
android:viewportHeight="40">
|
||||
<path
|
||||
android:pathData="M0,20C0,8.954 8.954,0 20,0C31.046,0 40,8.954 40,20C40,31.046 31.046,40 20,40C8.954,40 0,31.046 0,20Z"
|
||||
android:fillColor="#454243"/>
|
||||
<path
|
||||
android:pathData="M22.5,13.333V11.667M22.5,23.333V21.667M16.667,17.5H18.333M26.667,17.5H28.333M24.833,19.833L25.833,20.833M24.833,15.167L25.833,14.167M12.5,27.5L20,20M20.167,15.167L19.167,14.167"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="1.66667"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#E8E8E8"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
|
@ -0,0 +1,16 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:viewportWidth="40"
|
||||
android:viewportHeight="40">
|
||||
<path
|
||||
android:pathData="M0,20C0,8.954 8.954,0 20,0C31.046,0 40,8.954 40,20C40,31.046 31.046,40 20,40C8.954,40 0,31.046 0,20Z"
|
||||
android:fillColor="#EBEBE6"/>
|
||||
<path
|
||||
android:pathData="M22.5,13.333V11.667M22.5,23.333V21.667M16.667,17.5H18.333M26.667,17.5H28.333M24.833,19.833L25.833,20.833M24.833,15.167L25.833,14.167M12.5,27.5L20,20M20.167,15.167L19.167,14.167"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="1.66667"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#231F20"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue