Year-month date picker implementation
This commit is contained in:
parent
cee77ea0f9
commit
43e1c36dfb
|
@ -0,0 +1,18 @@
|
||||||
|
package co.electriccoin.zcash.ui.design.component
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun VerticalSpacer(height: Dp) {
|
||||||
|
Spacer(Modifier.height(height))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun HorizontalSpacer(width: Dp) {
|
||||||
|
Spacer(Modifier.width(width))
|
||||||
|
}
|
|
@ -0,0 +1,292 @@
|
||||||
|
package co.electriccoin.zcash.ui.design.component
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
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.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.LazyItemScope
|
||||||
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.runtime.rememberUpdatedState
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.runtime.snapshotFlow
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.graphicsLayer
|
||||||
|
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||||
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
|
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import co.electriccoin.zcash.spackle.Twig
|
||||||
|
import co.electriccoin.zcash.ui.design.theme.colors.ZashiColors
|
||||||
|
import co.electriccoin.zcash.ui.design.theme.typography.ZashiTypography
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import java.text.DateFormatSymbols
|
||||||
|
import java.time.Month
|
||||||
|
import java.time.Year
|
||||||
|
import java.time.YearMonth
|
||||||
|
import kotlin.math.pow
|
||||||
|
|
||||||
|
@Suppress("MagicNumber")
|
||||||
|
@Composable
|
||||||
|
fun ZashiYearMonthWheelDatePicker(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
verticallyVisibleItems: Int = 3,
|
||||||
|
startYear: Year = Year.of(2016),
|
||||||
|
endYear: Year = Year.now(),
|
||||||
|
selectedYear: YearMonth = YearMonth.now(),
|
||||||
|
onSelectionChanged: (YearMonth) -> Unit,
|
||||||
|
) {
|
||||||
|
val latestOnSelectionChanged by rememberUpdatedState(onSelectionChanged)
|
||||||
|
var selectedDate by remember { mutableStateOf(selectedYear) }
|
||||||
|
val months =
|
||||||
|
listOf(
|
||||||
|
Month.JANUARY,
|
||||||
|
Month.FEBRUARY,
|
||||||
|
Month.MARCH,
|
||||||
|
Month.APRIL,
|
||||||
|
Month.MAY,
|
||||||
|
Month.JUNE,
|
||||||
|
Month.JULY,
|
||||||
|
Month.AUGUST,
|
||||||
|
Month.SEPTEMBER,
|
||||||
|
Month.OCTOBER,
|
||||||
|
Month.NOVEMBER,
|
||||||
|
Month.DECEMBER
|
||||||
|
)
|
||||||
|
val years = (startYear.value..endYear.value).toList()
|
||||||
|
|
||||||
|
LaunchedEffect(selectedDate) {
|
||||||
|
Twig.debug { "Selection changed: $selectedDate" }
|
||||||
|
latestOnSelectionChanged(selectedDate)
|
||||||
|
}
|
||||||
|
|
||||||
|
Box(modifier = modifier) {
|
||||||
|
Column(
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.align(Alignment.Center),
|
||||||
|
) {
|
||||||
|
ZashiHorizontalDivider(color = ZashiColors.Surfaces.bgQuaternary, thickness = .5.dp)
|
||||||
|
VerticalSpacer(31.dp)
|
||||||
|
ZashiHorizontalDivider(color = ZashiColors.Surfaces.bgQuaternary, thickness = .5.dp)
|
||||||
|
}
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.Center
|
||||||
|
) {
|
||||||
|
Spacer(Modifier.weight(.5f))
|
||||||
|
WheelLazyList(
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
selection = maxOf(months.indexOf(selectedDate.month), 0),
|
||||||
|
itemCount = months.size,
|
||||||
|
itemVerticalOffset = verticallyVisibleItems,
|
||||||
|
isInfiniteScroll = true,
|
||||||
|
onFocusItem = { selectedDate = selectedDate.withMonth(months[it].value) },
|
||||||
|
itemContent = {
|
||||||
|
Text(
|
||||||
|
text = DateFormatSymbols().months[months[it].value - 1],
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
modifier = Modifier.fillParentMaxWidth(),
|
||||||
|
style = ZashiTypography.header6,
|
||||||
|
color = ZashiColors.Text.textPrimary,
|
||||||
|
maxLines = 1
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
WheelLazyList(
|
||||||
|
modifier = Modifier.weight(.75f),
|
||||||
|
selection = years.indexOf(selectedDate.year),
|
||||||
|
itemCount = years.size,
|
||||||
|
itemVerticalOffset = verticallyVisibleItems,
|
||||||
|
isInfiniteScroll = false,
|
||||||
|
onFocusItem = { selectedDate = selectedDate.withYear(years[it]) },
|
||||||
|
itemContent = {
|
||||||
|
Text(
|
||||||
|
text = years[it].toString(),
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
modifier = Modifier.fillParentMaxWidth(),
|
||||||
|
style = ZashiTypography.header6,
|
||||||
|
color = ZashiColors.Text.textPrimary,
|
||||||
|
maxLines = 1
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Spacer(Modifier.weight(.5f))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("MagicNumber")
|
||||||
|
@Composable
|
||||||
|
private fun WheelLazyList(
|
||||||
|
itemCount: Int,
|
||||||
|
selection: Int,
|
||||||
|
itemVerticalOffset: Int,
|
||||||
|
onFocusItem: (Int) -> Unit,
|
||||||
|
isInfiniteScroll: Boolean,
|
||||||
|
itemContent: @Composable LazyItemScope.(index: Int) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
val latestOnFocusItem by rememberUpdatedState(onFocusItem)
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
val haptic = LocalHapticFeedback.current
|
||||||
|
val count = if (isInfiniteScroll) itemCount else itemCount + 2 * itemVerticalOffset
|
||||||
|
val rowOffsetCount = maxOf(1, minOf(itemVerticalOffset, 4))
|
||||||
|
val rowCount = (rowOffsetCount * 2) + 1
|
||||||
|
val startIndex = if (isInfiniteScroll) selection + (itemCount * 1000) - itemVerticalOffset else selection
|
||||||
|
val state = rememberLazyListState(startIndex)
|
||||||
|
val itemHeightPx = with(LocalDensity.current) { 27.dp.toPx() }
|
||||||
|
val height = 32.dp * rowCount
|
||||||
|
val isScrollInProgress = state.isScrollInProgress
|
||||||
|
|
||||||
|
LaunchedEffect(itemCount) {
|
||||||
|
coroutineScope.launch {
|
||||||
|
state.scrollToItem(startIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(key1 = isScrollInProgress) {
|
||||||
|
if (!isScrollInProgress) {
|
||||||
|
calculateIndexToFocus(state, height).let {
|
||||||
|
val indexToFocus =
|
||||||
|
if (isInfiniteScroll) {
|
||||||
|
(it + rowOffsetCount) % itemCount
|
||||||
|
} else {
|
||||||
|
((it + rowOffsetCount) % count) - itemVerticalOffset
|
||||||
|
}
|
||||||
|
|
||||||
|
latestOnFocusItem(indexToFocus)
|
||||||
|
|
||||||
|
if (state.firstVisibleItemScrollOffset != 0) {
|
||||||
|
coroutineScope.launch {
|
||||||
|
state.animateScrollToItem(it, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(state) {
|
||||||
|
snapshotFlow { state.firstVisibleItemIndex }
|
||||||
|
.collect {
|
||||||
|
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Box(
|
||||||
|
modifier =
|
||||||
|
modifier
|
||||||
|
.height(height)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
) {
|
||||||
|
LazyColumn(
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.height(height)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
state = state,
|
||||||
|
) {
|
||||||
|
items(if (isInfiniteScroll) Int.MAX_VALUE else count) { index ->
|
||||||
|
val (scale, alpha, translationY) =
|
||||||
|
remember {
|
||||||
|
derivedStateOf {
|
||||||
|
val info = state.layoutInfo
|
||||||
|
val middleOffset = info.viewportSize.height / 2
|
||||||
|
val item = info.visibleItemsInfo.firstOrNull { it.index == index }
|
||||||
|
val scrollOffset = if (item != null) item.offset + item.size / 2 else -1
|
||||||
|
val coefficient = calculateCoefficient(middleOffset = middleOffset, offset = scrollOffset)
|
||||||
|
val scale = calculateScale(coefficient)
|
||||||
|
val alpha = calculateAlpha(coefficient)
|
||||||
|
val translationY =
|
||||||
|
calculateTranslationY(
|
||||||
|
coefficient = coefficient,
|
||||||
|
itemHeightPx = itemHeightPx,
|
||||||
|
middleOffset = middleOffset,
|
||||||
|
offset = scrollOffset
|
||||||
|
)
|
||||||
|
Triple(scale, alpha, translationY)
|
||||||
|
}
|
||||||
|
}.value
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.height(height / rowCount)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.graphicsLayer {
|
||||||
|
this.alpha = alpha
|
||||||
|
this.scaleX = scale
|
||||||
|
this.scaleY = scale
|
||||||
|
this.translationY = translationY
|
||||||
|
},
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
) {
|
||||||
|
if (isInfiniteScroll) {
|
||||||
|
itemContent(index % itemCount)
|
||||||
|
} else if (index >= rowOffsetCount && index < itemCount + rowOffsetCount) {
|
||||||
|
itemContent((index - rowOffsetCount) % itemCount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("MagicNumber")
|
||||||
|
private fun calculateCoefficient(
|
||||||
|
middleOffset: Int,
|
||||||
|
offset: Int
|
||||||
|
): Float {
|
||||||
|
val diff = if (middleOffset > offset) middleOffset - offset else offset - middleOffset
|
||||||
|
return (1f - (diff.toFloat() / middleOffset.toFloat())).coerceAtLeast(0f)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("MagicNumber")
|
||||||
|
private fun calculateScale(coefficient: Float): Float {
|
||||||
|
return coefficient.coerceAtLeast(.6f)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("MagicNumber")
|
||||||
|
private fun calculateAlpha(coefficient: Float): Float {
|
||||||
|
return coefficient.pow(1.1f)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("MagicNumber")
|
||||||
|
private fun calculateTranslationY(
|
||||||
|
coefficient: Float,
|
||||||
|
itemHeightPx: Float,
|
||||||
|
middleOffset: Int,
|
||||||
|
offset: Int
|
||||||
|
): Float {
|
||||||
|
// if (coefficient in 0.66f..1f) return 0f
|
||||||
|
val exponentialCoefficient = 1.2f - 5f.pow(-(coefficient))
|
||||||
|
val offsetBy = (1 - exponentialCoefficient) * itemHeightPx
|
||||||
|
return if (middleOffset > offset) offsetBy else -offsetBy
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("MagicNumber")
|
||||||
|
private fun calculateIndexToFocus(
|
||||||
|
listState: LazyListState,
|
||||||
|
height: Dp
|
||||||
|
): Int {
|
||||||
|
val currentItem = listState.layoutInfo.visibleItemsInfo.firstOrNull()
|
||||||
|
var index = currentItem?.index ?: 0
|
||||||
|
if (currentItem?.offset != 0 && currentItem != null && currentItem.offset <= -height.value * 3 / 10) {
|
||||||
|
index++
|
||||||
|
}
|
||||||
|
return index
|
||||||
|
}
|
|
@ -19,6 +19,7 @@ import co.electriccoin.zcash.ui.screen.integrations.viewmodel.IntegrationsViewMo
|
||||||
import co.electriccoin.zcash.ui.screen.qrcode.viewmodel.QrCodeViewModel
|
import co.electriccoin.zcash.ui.screen.qrcode.viewmodel.QrCodeViewModel
|
||||||
import co.electriccoin.zcash.ui.screen.receive.viewmodel.ReceiveViewModel
|
import co.electriccoin.zcash.ui.screen.receive.viewmodel.ReceiveViewModel
|
||||||
import co.electriccoin.zcash.ui.screen.request.viewmodel.RequestViewModel
|
import co.electriccoin.zcash.ui.screen.request.viewmodel.RequestViewModel
|
||||||
|
import co.electriccoin.zcash.ui.screen.restore.date.RestoreBDDateViewModel
|
||||||
import co.electriccoin.zcash.ui.screen.restore.height.RestoreBDHeightViewModel
|
import co.electriccoin.zcash.ui.screen.restore.height.RestoreBDHeightViewModel
|
||||||
import co.electriccoin.zcash.ui.screen.restore.seed.RestoreSeedViewModel
|
import co.electriccoin.zcash.ui.screen.restore.seed.RestoreSeedViewModel
|
||||||
import co.electriccoin.zcash.ui.screen.restoresuccess.viewmodel.RestoreSuccessViewModel
|
import co.electriccoin.zcash.ui.screen.restoresuccess.viewmodel.RestoreSuccessViewModel
|
||||||
|
@ -156,4 +157,5 @@ val viewModelModule =
|
||||||
viewModelOf(::BalanceViewModel)
|
viewModelOf(::BalanceViewModel)
|
||||||
viewModelOf(::HomeViewModel)
|
viewModelOf(::HomeViewModel)
|
||||||
viewModelOf(::RestoreBDHeightViewModel)
|
viewModelOf(::RestoreBDHeightViewModel)
|
||||||
|
viewModelOf(::RestoreBDDateViewModel)
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,8 @@ import co.electriccoin.zcash.ui.design.animation.ScreenAnimation.popEnterTransit
|
||||||
import co.electriccoin.zcash.ui.design.animation.ScreenAnimation.popExitTransition
|
import co.electriccoin.zcash.ui.design.animation.ScreenAnimation.popExitTransition
|
||||||
import co.electriccoin.zcash.ui.screen.flexa.FlexaViewModel
|
import co.electriccoin.zcash.ui.screen.flexa.FlexaViewModel
|
||||||
import co.electriccoin.zcash.ui.screen.onboarding.view.Onboarding
|
import co.electriccoin.zcash.ui.screen.onboarding.view.Onboarding
|
||||||
|
import co.electriccoin.zcash.ui.screen.restore.date.AndroidRestoreBDDate
|
||||||
|
import co.electriccoin.zcash.ui.screen.restore.date.RestoreBDDate
|
||||||
import co.electriccoin.zcash.ui.screen.restore.height.AndroidRestoreBDHeight
|
import co.electriccoin.zcash.ui.screen.restore.height.AndroidRestoreBDHeight
|
||||||
import co.electriccoin.zcash.ui.screen.restore.height.RestoreBDHeight
|
import co.electriccoin.zcash.ui.screen.restore.height.RestoreBDHeight
|
||||||
import co.electriccoin.zcash.ui.screen.restore.seed.AndroidRestoreSeed
|
import co.electriccoin.zcash.ui.screen.restore.seed.AndroidRestoreSeed
|
||||||
|
@ -101,6 +103,9 @@ fun MainActivity.RestoreNavigation() {
|
||||||
composable<RestoreBDHeight> {
|
composable<RestoreBDHeight> {
|
||||||
AndroidRestoreBDHeight()
|
AndroidRestoreBDHeight()
|
||||||
}
|
}
|
||||||
|
composable<RestoreBDDate> {
|
||||||
|
AndroidRestoreBDDate()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
package co.electriccoin.zcash.ui.screen.restore.date
|
||||||
|
|
||||||
|
import androidx.activity.compose.BackHandler
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import co.electriccoin.zcash.ui.screen.restore.RestoreSeedDialog
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import org.koin.androidx.compose.koinViewModel
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun AndroidRestoreBDDate() {
|
||||||
|
val vm = koinViewModel<RestoreBDDateViewModel>()
|
||||||
|
val state by vm.state.collectAsStateWithLifecycle()
|
||||||
|
val dialogState by vm.dialogState.collectAsStateWithLifecycle()
|
||||||
|
RestoreBDDateView(state)
|
||||||
|
BackHandler { state.onBack() }
|
||||||
|
RestoreSeedDialog(dialogState)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data object RestoreBDDate
|
|
@ -0,0 +1,10 @@
|
||||||
|
package co.electriccoin.zcash.ui.screen.restore.date
|
||||||
|
|
||||||
|
import co.electriccoin.zcash.ui.design.component.ButtonState
|
||||||
|
import co.electriccoin.zcash.ui.design.component.IconButtonState
|
||||||
|
|
||||||
|
data class RestoreBDDateState(
|
||||||
|
val next: ButtonState,
|
||||||
|
val dialogButton: IconButtonState,
|
||||||
|
val onBack: () -> Unit
|
||||||
|
)
|
|
@ -0,0 +1,156 @@
|
||||||
|
@file:Suppress("TooManyFunctions")
|
||||||
|
|
||||||
|
package co.electriccoin.zcash.ui.screen.restore.date
|
||||||
|
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
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.fillMaxSize
|
||||||
|
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.layout.width
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.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.font.FontWeight
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import co.electriccoin.zcash.ui.R
|
||||||
|
import co.electriccoin.zcash.ui.common.appbar.ZashiTopAppBarTags
|
||||||
|
import co.electriccoin.zcash.ui.design.component.BlankBgScaffold
|
||||||
|
import co.electriccoin.zcash.ui.design.component.ButtonState
|
||||||
|
import co.electriccoin.zcash.ui.design.component.IconButtonState
|
||||||
|
import co.electriccoin.zcash.ui.design.component.ZashiButton
|
||||||
|
import co.electriccoin.zcash.ui.design.component.ZashiIconButton
|
||||||
|
import co.electriccoin.zcash.ui.design.component.ZashiSmallTopAppBar
|
||||||
|
import co.electriccoin.zcash.ui.design.component.ZashiTopAppBarBackNavigation
|
||||||
|
import co.electriccoin.zcash.ui.design.component.ZashiYearMonthWheelDatePicker
|
||||||
|
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.orDark
|
||||||
|
import co.electriccoin.zcash.ui.design.util.scaffoldPadding
|
||||||
|
import co.electriccoin.zcash.ui.design.util.stringRes
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun RestoreBDDateView(state: RestoreBDDateState) {
|
||||||
|
BlankBgScaffold(
|
||||||
|
topBar = { AppBar(state) },
|
||||||
|
bottomBar = {},
|
||||||
|
content = { padding ->
|
||||||
|
Content(
|
||||||
|
state = state,
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
|
.scaffoldPadding(padding)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun Content(
|
||||||
|
state: RestoreBDDateState,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = modifier
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.restore_bd_date_subtitle),
|
||||||
|
style = ZashiTypography.header6,
|
||||||
|
color = ZashiColors.Text.textPrimary,
|
||||||
|
fontWeight = FontWeight.SemiBold
|
||||||
|
)
|
||||||
|
Spacer(Modifier.height(8.dp))
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.restore_bd_date_message),
|
||||||
|
style = ZashiTypography.textSm,
|
||||||
|
color = ZashiColors.Text.textPrimary
|
||||||
|
)
|
||||||
|
Spacer(Modifier.height(24.dp))
|
||||||
|
|
||||||
|
ZashiYearMonthWheelDatePicker(
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {}
|
||||||
|
|
||||||
|
Spacer(Modifier.height(24.dp))
|
||||||
|
|
||||||
|
Spacer(Modifier.weight(1f))
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.Center
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
painterResource(R.drawable.ic_info),
|
||||||
|
contentDescription = "",
|
||||||
|
colorFilter = ColorFilter.tint(color = ZashiColors.Utility.Indigo.utilityIndigo700)
|
||||||
|
)
|
||||||
|
Spacer(Modifier.width(8.dp))
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.padding(top = 2.dp),
|
||||||
|
text = stringResource(R.string.restore_bd_date_note),
|
||||||
|
style = ZashiTypography.textXs,
|
||||||
|
fontWeight = FontWeight.Medium,
|
||||||
|
color = ZashiColors.Utility.Indigo.utilityIndigo700
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(Modifier.height(24.dp))
|
||||||
|
|
||||||
|
ZashiButton(
|
||||||
|
state.next,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun AppBar(state: RestoreBDDateState) {
|
||||||
|
ZashiSmallTopAppBar(
|
||||||
|
title = stringResource(R.string.restore_title),
|
||||||
|
navigationAction = {
|
||||||
|
ZashiTopAppBarBackNavigation(
|
||||||
|
onBack = state.onBack,
|
||||||
|
modifier = Modifier.testTag(ZashiTopAppBarTags.BACK)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
regularActions = {
|
||||||
|
ZashiIconButton(state.dialogButton, modifier = Modifier.size(40.dp))
|
||||||
|
Spacer(Modifier.width(20.dp))
|
||||||
|
},
|
||||||
|
colors =
|
||||||
|
ZcashTheme.colors.topAppBarColors orDark
|
||||||
|
ZcashTheme.colors.topAppBarColors.copyColors(
|
||||||
|
containerColor = Color.Transparent
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreviewScreens
|
||||||
|
@Composable
|
||||||
|
private fun Preview() =
|
||||||
|
ZcashTheme {
|
||||||
|
RestoreBDDateView(
|
||||||
|
state =
|
||||||
|
RestoreBDDateState(
|
||||||
|
next = ButtonState(stringRes("Estimate")) {},
|
||||||
|
dialogButton = IconButtonState(R.drawable.ic_restore_dialog) {},
|
||||||
|
onBack = {}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
package co.electriccoin.zcash.ui.screen.restore.date
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import cash.z.ecc.sdk.ANDROID_STATE_FLOW_TIMEOUT
|
||||||
|
import co.electriccoin.zcash.ui.NavigationRouter
|
||||||
|
import co.electriccoin.zcash.ui.R
|
||||||
|
import co.electriccoin.zcash.ui.design.component.ButtonState
|
||||||
|
import co.electriccoin.zcash.ui.design.component.IconButtonState
|
||||||
|
import co.electriccoin.zcash.ui.design.util.stringRes
|
||||||
|
import co.electriccoin.zcash.ui.screen.restore.RestoreSeedDialogState
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.WhileSubscribed
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
|
|
||||||
|
class RestoreBDDateViewModel(
|
||||||
|
private val navigationRouter: NavigationRouter
|
||||||
|
) : ViewModel() {
|
||||||
|
private val isDialogVisible = MutableStateFlow(false)
|
||||||
|
|
||||||
|
val dialogState =
|
||||||
|
isDialogVisible
|
||||||
|
.map { isDialogVisible ->
|
||||||
|
RestoreSeedDialogState(
|
||||||
|
::onCloseDialogClick
|
||||||
|
).takeIf { isDialogVisible }
|
||||||
|
}
|
||||||
|
.stateIn(
|
||||||
|
scope = viewModelScope,
|
||||||
|
started = SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT),
|
||||||
|
initialValue = null
|
||||||
|
)
|
||||||
|
|
||||||
|
val state: StateFlow<RestoreBDDateState> = MutableStateFlow(createState()).asStateFlow()
|
||||||
|
|
||||||
|
private fun createState() =
|
||||||
|
RestoreBDDateState(
|
||||||
|
next = ButtonState(stringRes(R.string.restore_bd_height_btn), onClick = ::onEstimateClick),
|
||||||
|
dialogButton = IconButtonState(icon = R.drawable.ic_info, onClick = ::onInfoButtonClick),
|
||||||
|
onBack = ::onBack,
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun onEstimateClick() {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onBack() {
|
||||||
|
navigationRouter.back()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onInfoButtonClick() {
|
||||||
|
isDialogVisible.update { true }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onCloseDialogClick() {
|
||||||
|
isDialogVisible.update { false }
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,6 +10,7 @@ import co.electriccoin.zcash.ui.design.component.IconButtonState
|
||||||
import co.electriccoin.zcash.ui.design.component.TextFieldState
|
import co.electriccoin.zcash.ui.design.component.TextFieldState
|
||||||
import co.electriccoin.zcash.ui.design.util.stringRes
|
import co.electriccoin.zcash.ui.design.util.stringRes
|
||||||
import co.electriccoin.zcash.ui.screen.restore.RestoreSeedDialogState
|
import co.electriccoin.zcash.ui.screen.restore.RestoreSeedDialogState
|
||||||
|
import co.electriccoin.zcash.ui.screen.restore.date.RestoreBDDate
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
@ -59,7 +60,7 @@ class RestoreBDHeightViewModel(
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun onEstimateClick() {
|
private fun onEstimateClick() {
|
||||||
// do nothing
|
navigationRouter.forward(RestoreBDDate)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onRestoreClick() {
|
private fun onRestoreClick() {
|
||||||
|
|
|
@ -23,7 +23,6 @@ import kotlinx.coroutines.flow.update
|
||||||
class RestoreSeedViewModel(
|
class RestoreSeedViewModel(
|
||||||
private val navigationRouter: NavigationRouter
|
private val navigationRouter: NavigationRouter
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
@Suppress("MagicNumber")
|
@Suppress("MagicNumber")
|
||||||
private val seedWords =
|
private val seedWords =
|
||||||
MutableStateFlow(
|
MutableStateFlow(
|
||||||
|
|
|
@ -26,4 +26,10 @@
|
||||||
<string name="restore_bd_text_field_hint">Enter number</string>
|
<string name="restore_bd_text_field_hint">Enter number</string>
|
||||||
<string name="restore_bd_text_field_note">Wallet Birthday Height is the point in time when your wallet was created.</string>
|
<string name="restore_bd_text_field_note">Wallet Birthday Height is the point in time when your wallet was created.</string>
|
||||||
|
|
||||||
|
<string name="restore_bd_date_subtitle">First Wallet Transaction</string>
|
||||||
|
<string name="restore_bd_date_message">Entering the block height at which your wallet was created reduces the
|
||||||
|
number of blocks that need to be scanned to recover your wallet.</string>
|
||||||
|
<string name="restore_bd_date_note">If you’re not sure, choose an earlier date.</string>
|
||||||
|
<string name="restore_bd_date_next">Next</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
Loading…
Reference in New Issue