Automatic keyboard and bottom sheet handling during navigation
This commit is contained in:
parent
696344f832
commit
33cd056570
|
@ -0,0 +1,80 @@
|
||||||
|
package co.electriccoin.zcash.ui.design
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
|
import androidx.compose.foundation.layout.ime
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
|
import androidx.compose.runtime.State
|
||||||
|
import androidx.compose.runtime.compositionLocalOf
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberUpdatedState
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
|
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||||
|
import androidx.compose.ui.platform.SoftwareKeyboardController
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.filter
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
|
import kotlinx.coroutines.withTimeoutOrNull
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
class KeyboardManager(
|
||||||
|
isOpen: Boolean,
|
||||||
|
private val softwareKeyboardController: SoftwareKeyboardController?
|
||||||
|
) {
|
||||||
|
private var targetState = MutableStateFlow(isOpen)
|
||||||
|
|
||||||
|
var isOpen by mutableStateOf(isOpen)
|
||||||
|
private set
|
||||||
|
|
||||||
|
suspend fun close() {
|
||||||
|
if (targetState.value) {
|
||||||
|
withTimeoutOrNull(.5.seconds) {
|
||||||
|
softwareKeyboardController?.hide()
|
||||||
|
targetState.filter { !it }.first()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onKeyboardOpened() {
|
||||||
|
targetState.update { true }
|
||||||
|
isOpen = true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onKeyboardClosed() {
|
||||||
|
targetState.update { false }
|
||||||
|
isOpen = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun rememberKeyboardManager(): KeyboardManager {
|
||||||
|
val isKeyboardOpen by rememberKeyboardState()
|
||||||
|
val softwareKeyboardController = LocalSoftwareKeyboardController.current
|
||||||
|
val keyboardManager = remember { KeyboardManager(isKeyboardOpen, softwareKeyboardController) }
|
||||||
|
LaunchedEffect(isKeyboardOpen) {
|
||||||
|
if (isKeyboardOpen) {
|
||||||
|
keyboardManager.onKeyboardOpened()
|
||||||
|
} else {
|
||||||
|
keyboardManager.onKeyboardClosed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return keyboardManager
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun rememberKeyboardState(): State<Boolean> {
|
||||||
|
val isImeVisible = WindowInsets.ime.getBottom(LocalDensity.current) > 0
|
||||||
|
return rememberUpdatedState(isImeVisible)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("CompositionLocalAllowlist")
|
||||||
|
val LocalKeyboardManager =
|
||||||
|
compositionLocalOf<KeyboardManager> {
|
||||||
|
error("Keyboard manager not provided")
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
package co.electriccoin.zcash.ui.design
|
||||||
|
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.SheetState
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
|
import androidx.compose.runtime.compositionLocalOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import kotlinx.coroutines.withTimeoutOrNull
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Stable
|
||||||
|
class SheetStateManager {
|
||||||
|
private var sheetState: SheetState? = null
|
||||||
|
|
||||||
|
fun onSheetOpened(sheetState: SheetState) {
|
||||||
|
this.sheetState = sheetState
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onSheetDisposed(sheetState: SheetState) {
|
||||||
|
if (this.sheetState == sheetState) {
|
||||||
|
this.sheetState = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun hide() {
|
||||||
|
withTimeoutOrNull(.5.seconds) {
|
||||||
|
sheetState?.hide()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun rememberSheetStateManager() = remember { SheetStateManager() }
|
||||||
|
|
||||||
|
@Suppress("CompositionLocalAllowlist")
|
||||||
|
val LocalSheetStateManager =
|
||||||
|
compositionLocalOf<SheetStateManager> {
|
||||||
|
error("Sheet state manager not provided")
|
||||||
|
}
|
|
@ -9,6 +9,10 @@ import androidx.compose.material3.RippleConfiguration
|
||||||
import androidx.compose.material3.RippleDefaults
|
import androidx.compose.material3.RippleDefaults
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
|
import co.electriccoin.zcash.ui.design.LocalKeyboardManager
|
||||||
|
import co.electriccoin.zcash.ui.design.LocalSheetStateManager
|
||||||
|
import co.electriccoin.zcash.ui.design.rememberKeyboardManager
|
||||||
|
import co.electriccoin.zcash.ui.design.rememberSheetStateManager
|
||||||
import co.electriccoin.zcash.ui.design.theme.balances.LocalBalancesAvailable
|
import co.electriccoin.zcash.ui.design.theme.balances.LocalBalancesAvailable
|
||||||
import co.electriccoin.zcash.ui.design.theme.colors.DarkZashiColorsInternal
|
import co.electriccoin.zcash.ui.design.theme.colors.DarkZashiColorsInternal
|
||||||
import co.electriccoin.zcash.ui.design.theme.colors.LightZashiColorsInternal
|
import co.electriccoin.zcash.ui.design.theme.colors.LightZashiColorsInternal
|
||||||
|
@ -49,7 +53,9 @@ fun ZcashTheme(
|
||||||
LocalZashiColors provides zashiColors,
|
LocalZashiColors provides zashiColors,
|
||||||
LocalZashiTypography provides ZashiTypographyInternal,
|
LocalZashiTypography provides ZashiTypographyInternal,
|
||||||
LocalRippleConfiguration provides MaterialRippleConfig,
|
LocalRippleConfiguration provides MaterialRippleConfig,
|
||||||
LocalBalancesAvailable provides balancesAvailable
|
LocalBalancesAvailable provides balancesAvailable,
|
||||||
|
LocalKeyboardManager provides rememberKeyboardManager(),
|
||||||
|
LocalSheetStateManager provides rememberSheetStateManager()
|
||||||
) {
|
) {
|
||||||
ProvideDimens {
|
ProvideDimens {
|
||||||
MaterialTheme(
|
MaterialTheme(
|
||||||
|
|
|
@ -118,7 +118,6 @@ class MainActivity : FragmentActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note this condition needs to be kept in sync with the condition in MainContent()
|
|
||||||
SecretState.LOADING == walletViewModel.secretState.value
|
SecretState.LOADING == walletViewModel.secretState.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,6 @@ import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.ui.platform.LocalFocusManager
|
|
||||||
import androidx.compose.ui.window.DialogProperties
|
import androidx.compose.ui.window.DialogProperties
|
||||||
import androidx.lifecycle.LifecycleCoroutineScope
|
import androidx.lifecycle.LifecycleCoroutineScope
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
@ -39,6 +38,8 @@ import co.electriccoin.zcash.ui.NavigationTargets.WHATS_NEW
|
||||||
import co.electriccoin.zcash.ui.common.compose.LocalNavController
|
import co.electriccoin.zcash.ui.common.compose.LocalNavController
|
||||||
import co.electriccoin.zcash.ui.common.provider.ApplicationStateProvider
|
import co.electriccoin.zcash.ui.common.provider.ApplicationStateProvider
|
||||||
import co.electriccoin.zcash.ui.common.provider.isInForeground
|
import co.electriccoin.zcash.ui.common.provider.isInForeground
|
||||||
|
import co.electriccoin.zcash.ui.design.LocalKeyboardManager
|
||||||
|
import co.electriccoin.zcash.ui.design.LocalSheetStateManager
|
||||||
import co.electriccoin.zcash.ui.design.animation.ScreenAnimation.enterTransition
|
import co.electriccoin.zcash.ui.design.animation.ScreenAnimation.enterTransition
|
||||||
import co.electriccoin.zcash.ui.design.animation.ScreenAnimation.exitTransition
|
import co.electriccoin.zcash.ui.design.animation.ScreenAnimation.exitTransition
|
||||||
import co.electriccoin.zcash.ui.design.animation.ScreenAnimation.popEnterTransition
|
import co.electriccoin.zcash.ui.design.animation.ScreenAnimation.popEnterTransition
|
||||||
|
@ -122,9 +123,10 @@ import org.koin.compose.koinInject
|
||||||
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||||
internal fun MainActivity.Navigation() {
|
internal fun MainActivity.Navigation() {
|
||||||
val navController = LocalNavController.current
|
val navController = LocalNavController.current
|
||||||
val focusManager = LocalFocusManager.current
|
val keyboardManager = LocalKeyboardManager.current
|
||||||
val flexaViewModel = koinViewModel<FlexaViewModel>()
|
val flexaViewModel = koinViewModel<FlexaViewModel>()
|
||||||
val navigationRouter = koinInject<NavigationRouter>()
|
val navigationRouter = koinInject<NavigationRouter>()
|
||||||
|
val sheetStateManager = LocalSheetStateManager.current
|
||||||
|
|
||||||
// Helper properties for triggering the system security UI from callbacks
|
// Helper properties for triggering the system security UI from callbacks
|
||||||
val (exportPrivateDataAuthentication, setExportPrivateDataAuthentication) =
|
val (exportPrivateDataAuthentication, setExportPrivateDataAuthentication) =
|
||||||
|
@ -136,13 +138,15 @@ internal fun MainActivity.Navigation() {
|
||||||
remember(
|
remember(
|
||||||
navController,
|
navController,
|
||||||
flexaViewModel,
|
flexaViewModel,
|
||||||
focusManager
|
keyboardManager,
|
||||||
|
sheetStateManager
|
||||||
) {
|
) {
|
||||||
NavigatorImpl(
|
NavigatorImpl(
|
||||||
activity = this@Navigation,
|
activity = this@Navigation,
|
||||||
navController = navController,
|
navController = navController,
|
||||||
flexaViewModel = flexaViewModel,
|
flexaViewModel = flexaViewModel,
|
||||||
focusManager = focusManager
|
keyboardManager = keyboardManager,
|
||||||
|
sheetStateManager = sheetStateManager
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,10 +2,11 @@ package co.electriccoin.zcash.ui
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.compose.ui.focus.FocusManager
|
|
||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
import androidx.navigation.NavOptionsBuilder
|
import androidx.navigation.NavOptionsBuilder
|
||||||
import androidx.navigation.serialization.generateHashCode
|
import androidx.navigation.serialization.generateHashCode
|
||||||
|
import co.electriccoin.zcash.ui.design.KeyboardManager
|
||||||
|
import co.electriccoin.zcash.ui.design.SheetStateManager
|
||||||
import co.electriccoin.zcash.ui.screen.ExternalUrl
|
import co.electriccoin.zcash.ui.screen.ExternalUrl
|
||||||
import co.electriccoin.zcash.ui.screen.about.util.WebBrowserUtil
|
import co.electriccoin.zcash.ui.screen.about.util.WebBrowserUtil
|
||||||
import co.electriccoin.zcash.ui.screen.flexa.FlexaViewModel
|
import co.electriccoin.zcash.ui.screen.flexa.FlexaViewModel
|
||||||
|
@ -15,17 +16,19 @@ import kotlinx.serialization.InternalSerializationApi
|
||||||
import kotlinx.serialization.serializer
|
import kotlinx.serialization.serializer
|
||||||
|
|
||||||
interface Navigator {
|
interface Navigator {
|
||||||
fun executeCommand(command: NavigationCommand)
|
suspend fun executeCommand(command: NavigationCommand)
|
||||||
}
|
}
|
||||||
|
|
||||||
class NavigatorImpl(
|
class NavigatorImpl(
|
||||||
private val activity: ComponentActivity,
|
private val activity: ComponentActivity,
|
||||||
private val navController: NavHostController,
|
private val navController: NavHostController,
|
||||||
private val flexaViewModel: FlexaViewModel,
|
private val flexaViewModel: FlexaViewModel,
|
||||||
private val focusManager: FocusManager,
|
private val keyboardManager: KeyboardManager,
|
||||||
|
private val sheetStateManager: SheetStateManager,
|
||||||
) : Navigator {
|
) : Navigator {
|
||||||
override fun executeCommand(command: NavigationCommand) {
|
override suspend fun executeCommand(command: NavigationCommand) {
|
||||||
focusManager.clearFocus(true)
|
keyboardManager.close()
|
||||||
|
sheetStateManager.hide()
|
||||||
when (command) {
|
when (command) {
|
||||||
is NavigationCommand.Forward -> forward(command)
|
is NavigationCommand.Forward -> forward(command)
|
||||||
is NavigationCommand.Replace -> replace(command)
|
is NavigationCommand.Replace -> replace(command)
|
||||||
|
|
|
@ -8,12 +8,8 @@ class ApplyTransactionFiltersUseCase(
|
||||||
private val transactionFilterRepository: TransactionFilterRepository,
|
private val transactionFilterRepository: TransactionFilterRepository,
|
||||||
private val navigationRouter: NavigationRouter,
|
private val navigationRouter: NavigationRouter,
|
||||||
) {
|
) {
|
||||||
suspend operator fun invoke(
|
operator fun invoke(filters: List<TransactionFilter>) {
|
||||||
filters: List<TransactionFilter>,
|
|
||||||
hideBottomSheet: suspend () -> Unit
|
|
||||||
) {
|
|
||||||
transactionFilterRepository.apply(filters)
|
transactionFilterRepository.apply(filters)
|
||||||
hideBottomSheet()
|
|
||||||
navigationRouter.back()
|
navigationRouter.back()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,11 +9,9 @@ class CreateOrUpdateTransactionNoteUseCase(
|
||||||
) {
|
) {
|
||||||
suspend operator fun invoke(
|
suspend operator fun invoke(
|
||||||
txId: String,
|
txId: String,
|
||||||
note: String,
|
note: String
|
||||||
closeBottomSheet: suspend () -> Unit
|
|
||||||
) {
|
) {
|
||||||
metadataRepository.createOrUpdateTxNote(txId, note.trim())
|
metadataRepository.createOrUpdateTxNote(txId, note.trim())
|
||||||
closeBottomSheet()
|
|
||||||
navigationRouter.back()
|
navigationRouter.back()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,12 +7,8 @@ class DeleteTransactionNoteUseCase(
|
||||||
private val metadataRepository: MetadataRepository,
|
private val metadataRepository: MetadataRepository,
|
||||||
private val navigationRouter: NavigationRouter
|
private val navigationRouter: NavigationRouter
|
||||||
) {
|
) {
|
||||||
suspend operator fun invoke(
|
suspend operator fun invoke(txId: String) {
|
||||||
txId: String,
|
|
||||||
closeBottomSheet: suspend () -> Unit
|
|
||||||
) {
|
|
||||||
metadataRepository.deleteTxNote(txId)
|
metadataRepository.deleteTxNote(txId)
|
||||||
closeBottomSheet()
|
|
||||||
navigationRouter.back()
|
navigationRouter.back()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,12 +8,8 @@ class SelectWalletAccountUseCase(
|
||||||
private val accountDataSource: AccountDataSource,
|
private val accountDataSource: AccountDataSource,
|
||||||
private val navigationRouter: NavigationRouter
|
private val navigationRouter: NavigationRouter
|
||||||
) {
|
) {
|
||||||
suspend operator fun invoke(
|
suspend operator fun invoke(account: WalletAccount) {
|
||||||
account: WalletAccount,
|
|
||||||
hideBottomSheet: suspend () -> Unit
|
|
||||||
) {
|
|
||||||
accountDataSource.selectAccount(account)
|
accountDataSource.selectAccount(account)
|
||||||
hideBottomSheet()
|
|
||||||
navigationRouter.back()
|
navigationRouter.back()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,12 +4,14 @@ import android.view.WindowManager
|
||||||
import androidx.activity.compose.BackHandler
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.DisposableEffect
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.SideEffect
|
import androidx.compose.runtime.SideEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.platform.LocalView
|
import androidx.compose.ui.platform.LocalView
|
||||||
import androidx.compose.ui.window.DialogWindowProvider
|
import androidx.compose.ui.window.DialogWindowProvider
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import co.electriccoin.zcash.ui.design.LocalSheetStateManager
|
||||||
import co.electriccoin.zcash.ui.design.component.rememberModalBottomSheetState
|
import co.electriccoin.zcash.ui.design.component.rememberModalBottomSheetState
|
||||||
import co.electriccoin.zcash.ui.screen.accountlist.view.AccountListView
|
import co.electriccoin.zcash.ui.screen.accountlist.view.AccountListView
|
||||||
import co.electriccoin.zcash.ui.screen.accountlist.viewmodel.AccountListViewModel
|
import co.electriccoin.zcash.ui.screen.accountlist.viewmodel.AccountListViewModel
|
||||||
|
@ -22,6 +24,13 @@ fun AndroidAccountList() {
|
||||||
val state by viewModel.state.collectAsStateWithLifecycle()
|
val state by viewModel.state.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
|
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
|
||||||
|
val sheetManager = LocalSheetStateManager.current
|
||||||
|
DisposableEffect(sheetState) {
|
||||||
|
sheetManager.onSheetOpened(sheetState)
|
||||||
|
onDispose {
|
||||||
|
sheetManager.onSheetDisposed(sheetState)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val parent = LocalView.current.parent
|
val parent = LocalView.current.parent
|
||||||
|
|
||||||
|
@ -43,13 +52,6 @@ fun AndroidAccountList() {
|
||||||
sheetState.show()
|
sheetState.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
viewModel.hideBottomSheetRequest.collect {
|
|
||||||
sheetState.hide()
|
|
||||||
state?.onBottomSheetHidden?.invoke()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
BackHandler {
|
BackHandler {
|
||||||
state?.onBack?.invoke()
|
state?.onBack?.invoke()
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,6 @@ import co.electriccoin.zcash.ui.design.util.StringResource
|
||||||
data class AccountListState(
|
data class AccountListState(
|
||||||
val items: List<AccountListItem>?,
|
val items: List<AccountListItem>?,
|
||||||
val isLoading: Boolean,
|
val isLoading: Boolean,
|
||||||
val onBottomSheetHidden: () -> Unit,
|
|
||||||
val addWalletButton: ButtonState?,
|
val addWalletButton: ButtonState?,
|
||||||
val onBack: () -> Unit,
|
val onBack: () -> Unit,
|
||||||
)
|
)
|
||||||
|
|
|
@ -270,7 +270,6 @@ private fun Preview() =
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
isLoading = false,
|
isLoading = false,
|
||||||
onBottomSheetHidden = {},
|
|
||||||
onBack = {},
|
onBack = {},
|
||||||
addWalletButton = ButtonState(stringRes("Connect Hardware Wallet"))
|
addWalletButton = ButtonState(stringRes("Connect Hardware Wallet"))
|
||||||
),
|
),
|
||||||
|
@ -315,7 +314,6 @@ private fun HardwareWalletAddedPreview() =
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
isLoading = false,
|
isLoading = false,
|
||||||
onBottomSheetHidden = {},
|
|
||||||
onBack = {},
|
onBack = {},
|
||||||
addWalletButton = null
|
addWalletButton = null
|
||||||
),
|
),
|
||||||
|
|
|
@ -19,10 +19,8 @@ import co.electriccoin.zcash.ui.screen.accountlist.model.AccountListState
|
||||||
import co.electriccoin.zcash.ui.screen.accountlist.model.ZashiAccountListItemState
|
import co.electriccoin.zcash.ui.screen.accountlist.model.ZashiAccountListItemState
|
||||||
import co.electriccoin.zcash.ui.screen.addressbook.viewmodel.ADDRESS_MAX_LENGTH
|
import co.electriccoin.zcash.ui.screen.addressbook.viewmodel.ADDRESS_MAX_LENGTH
|
||||||
import co.electriccoin.zcash.ui.screen.connectkeystone.ConnectKeystone
|
import co.electriccoin.zcash.ui.screen.connectkeystone.ConnectKeystone
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.WhileSubscribed
|
import kotlinx.coroutines.flow.WhileSubscribed
|
||||||
import kotlinx.coroutines.flow.first
|
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
@ -32,10 +30,6 @@ class AccountListViewModel(
|
||||||
private val selectWalletAccount: SelectWalletAccountUseCase,
|
private val selectWalletAccount: SelectWalletAccountUseCase,
|
||||||
private val navigationRouter: NavigationRouter,
|
private val navigationRouter: NavigationRouter,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
val hideBottomSheetRequest = MutableSharedFlow<Unit>()
|
|
||||||
|
|
||||||
private val bottomSheetHiddenResponse = MutableSharedFlow<Unit>()
|
|
||||||
|
|
||||||
@Suppress("SpreadOperator")
|
@Suppress("SpreadOperator")
|
||||||
val state =
|
val state =
|
||||||
getWalletAccounts.observe().map { accounts ->
|
getWalletAccounts.observe().map { accounts ->
|
||||||
|
@ -77,7 +71,6 @@ class AccountListViewModel(
|
||||||
AccountListState(
|
AccountListState(
|
||||||
items = items,
|
items = items,
|
||||||
isLoading = accounts == null,
|
isLoading = accounts == null,
|
||||||
onBottomSheetHidden = ::onBottomSheetHidden,
|
|
||||||
onBack = ::onBack,
|
onBack = ::onBack,
|
||||||
addWalletButton =
|
addWalletButton =
|
||||||
ButtonState(
|
ButtonState(
|
||||||
|
@ -94,35 +87,14 @@ class AccountListViewModel(
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun onShowKeystonePromoClicked() =
|
private fun onShowKeystonePromoClicked() =
|
||||||
viewModelScope.launch {
|
navigationRouter.replace(ExternalUrl("https://keyst.one/shop/products/keystone-3-pro?discount=Zashi"))
|
||||||
hideBottomSheet()
|
|
||||||
navigationRouter.replace(ExternalUrl("https://keyst.one/shop/products/keystone-3-pro?discount=Zashi"))
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun hideBottomSheet() {
|
|
||||||
hideBottomSheetRequest.emit(Unit)
|
|
||||||
bottomSheetHiddenResponse.first()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onBottomSheetHidden() =
|
|
||||||
viewModelScope.launch {
|
|
||||||
bottomSheetHiddenResponse.emit(Unit)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onAccountClicked(account: WalletAccount) =
|
private fun onAccountClicked(account: WalletAccount) =
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
selectWalletAccount(account) { hideBottomSheet() }
|
selectWalletAccount(account)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onAddWalletButtonClicked() =
|
private fun onAddWalletButtonClicked() = navigationRouter.forward(ConnectKeystone)
|
||||||
viewModelScope.launch {
|
|
||||||
hideBottomSheet()
|
|
||||||
navigationRouter.forward(ConnectKeystone)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onBack() =
|
private fun onBack() = navigationRouter.back()
|
||||||
viewModelScope.launch {
|
|
||||||
hideBottomSheet()
|
|
||||||
navigationRouter.back()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,12 +4,14 @@ import android.view.WindowManager
|
||||||
import androidx.activity.compose.BackHandler
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.DisposableEffect
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.SideEffect
|
import androidx.compose.runtime.SideEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.platform.LocalView
|
import androidx.compose.ui.platform.LocalView
|
||||||
import androidx.compose.ui.window.DialogWindowProvider
|
import androidx.compose.ui.window.DialogWindowProvider
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import co.electriccoin.zcash.ui.design.LocalSheetStateManager
|
||||||
import co.electriccoin.zcash.ui.design.component.rememberModalBottomSheetState
|
import co.electriccoin.zcash.ui.design.component.rememberModalBottomSheetState
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import org.koin.androidx.compose.koinViewModel
|
import org.koin.androidx.compose.koinViewModel
|
||||||
|
@ -19,6 +21,13 @@ import org.koin.core.parameter.parametersOf
|
||||||
@Composable
|
@Composable
|
||||||
fun AndroidDialogIntegrations() {
|
fun AndroidDialogIntegrations() {
|
||||||
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
|
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
|
||||||
|
val sheetManager = LocalSheetStateManager.current
|
||||||
|
DisposableEffect(sheetState) {
|
||||||
|
sheetManager.onSheetOpened(sheetState)
|
||||||
|
onDispose {
|
||||||
|
sheetManager.onSheetDisposed(sheetState)
|
||||||
|
}
|
||||||
|
}
|
||||||
val parent = LocalView.current.parent
|
val parent = LocalView.current.parent
|
||||||
val viewModel = koinViewModel<IntegrationsViewModel> { parametersOf(true) }
|
val viewModel = koinViewModel<IntegrationsViewModel> { parametersOf(true) }
|
||||||
val state by viewModel.state.collectAsStateWithLifecycle()
|
val state by viewModel.state.collectAsStateWithLifecycle()
|
||||||
|
@ -44,13 +53,6 @@ fun AndroidDialogIntegrations() {
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
sheetState.show()
|
sheetState.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
viewModel.hideBottomSheetRequest.collect {
|
|
||||||
sheetState.hide()
|
|
||||||
state?.onBottomSheetHidden?.invoke()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -132,7 +132,6 @@ private fun IntegrationSettings() =
|
||||||
onClick = {}
|
onClick = {}
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onBottomSheetHidden = {}
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,5 +8,4 @@ data class IntegrationsState(
|
||||||
val disabledInfo: StringResource?,
|
val disabledInfo: StringResource?,
|
||||||
val onBack: () -> Unit,
|
val onBack: () -> Unit,
|
||||||
val items: ImmutableList<ZashiListItemState>,
|
val items: ImmutableList<ZashiListItemState>,
|
||||||
val onBottomSheetHidden: () -> Unit,
|
|
||||||
)
|
)
|
||||||
|
|
|
@ -197,7 +197,6 @@ private fun IntegrationSettings() =
|
||||||
onClick = {}
|
onClick = {}
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onBottomSheetHidden = {}
|
|
||||||
),
|
),
|
||||||
topAppBarSubTitleState = TopAppBarSubTitleState.None,
|
topAppBarSubTitleState = TopAppBarSubTitleState.None,
|
||||||
)
|
)
|
||||||
|
|
|
@ -25,11 +25,9 @@ import co.electriccoin.zcash.ui.design.util.stringRes
|
||||||
import co.electriccoin.zcash.ui.screen.connectkeystone.ConnectKeystone
|
import co.electriccoin.zcash.ui.screen.connectkeystone.ConnectKeystone
|
||||||
import co.electriccoin.zcash.ui.screen.flexa.Flexa
|
import co.electriccoin.zcash.ui.screen.flexa.Flexa
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.WhileSubscribed
|
import kotlinx.coroutines.flow.WhileSubscribed
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.first
|
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
@ -45,10 +43,6 @@ class IntegrationsViewModel(
|
||||||
private val navigationRouter: NavigationRouter,
|
private val navigationRouter: NavigationRouter,
|
||||||
private val navigateToCoinbase: NavigateToCoinbaseUseCase,
|
private val navigateToCoinbase: NavigateToCoinbaseUseCase,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
val hideBottomSheetRequest = MutableSharedFlow<Unit>()
|
|
||||||
|
|
||||||
private val bottomSheetHiddenResponse = MutableSharedFlow<Unit>()
|
|
||||||
|
|
||||||
private val isRestoring = getWalletRestoringState.observe().map { it == WalletRestoringState.RESTORING }
|
private val isRestoring = getWalletRestoringState.observe().map { it == WalletRestoringState.RESTORING }
|
||||||
|
|
||||||
val state =
|
val state =
|
||||||
|
@ -123,43 +117,25 @@ class IntegrationsViewModel(
|
||||||
onClick = ::onConnectKeystoneClick
|
onClick = ::onConnectKeystoneClick
|
||||||
).takeIf { keystoneStatus != UNAVAILABLE },
|
).takeIf { keystoneStatus != UNAVAILABLE },
|
||||||
).toImmutableList(),
|
).toImmutableList(),
|
||||||
onBottomSheetHidden = ::onBottomSheetHidden
|
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun onBack() = navigationRouter.back()
|
private fun onBack() = navigationRouter.back()
|
||||||
|
|
||||||
private suspend fun hideBottomSheet() {
|
|
||||||
if (isDialog) {
|
|
||||||
hideBottomSheetRequest.emit(Unit)
|
|
||||||
bottomSheetHiddenResponse.first()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onBottomSheetHidden() =
|
|
||||||
viewModelScope.launch {
|
|
||||||
bottomSheetHiddenResponse.emit(Unit)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onBuyWithCoinbaseClicked() =
|
private fun onBuyWithCoinbaseClicked() =
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
hideBottomSheet()
|
|
||||||
navigateToCoinbase(isDialog)
|
navigateToCoinbase(isDialog)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onConnectKeystoneClick() =
|
private fun onConnectKeystoneClick() =
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
hideBottomSheet()
|
|
||||||
navigationRouter.replace(ConnectKeystone)
|
navigationRouter.replace(ConnectKeystone)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onFlexaClicked() =
|
private fun onFlexaClicked() {
|
||||||
viewModelScope.launch {
|
if (isDialog) {
|
||||||
if (isDialog) {
|
navigationRouter.replace(Flexa)
|
||||||
hideBottomSheet()
|
} else {
|
||||||
navigationRouter.replace(Flexa)
|
navigationRouter.forward(Flexa)
|
||||||
} else {
|
|
||||||
hideBottomSheet()
|
|
||||||
navigationRouter.forward(Flexa)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,6 @@ import android.content.Context
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.platform.LocalFocusManager
|
|
||||||
import androidx.compose.ui.window.DialogProperties
|
import androidx.compose.ui.window.DialogProperties
|
||||||
import androidx.navigation.compose.NavHost
|
import androidx.navigation.compose.NavHost
|
||||||
import androidx.navigation.compose.composable
|
import androidx.navigation.compose.composable
|
||||||
|
@ -27,6 +26,8 @@ import co.electriccoin.zcash.ui.common.compose.LocalNavController
|
||||||
import co.electriccoin.zcash.ui.common.model.OnboardingState
|
import co.electriccoin.zcash.ui.common.model.OnboardingState
|
||||||
import co.electriccoin.zcash.ui.common.model.WalletRestoringState
|
import co.electriccoin.zcash.ui.common.model.WalletRestoringState
|
||||||
import co.electriccoin.zcash.ui.common.viewmodel.WalletViewModel
|
import co.electriccoin.zcash.ui.common.viewmodel.WalletViewModel
|
||||||
|
import co.electriccoin.zcash.ui.design.LocalKeyboardManager
|
||||||
|
import co.electriccoin.zcash.ui.design.LocalSheetStateManager
|
||||||
import co.electriccoin.zcash.ui.design.animation.ScreenAnimation.enterTransition
|
import co.electriccoin.zcash.ui.design.animation.ScreenAnimation.enterTransition
|
||||||
import co.electriccoin.zcash.ui.design.animation.ScreenAnimation.exitTransition
|
import co.electriccoin.zcash.ui.design.animation.ScreenAnimation.exitTransition
|
||||||
import co.electriccoin.zcash.ui.design.animation.ScreenAnimation.popEnterTransition
|
import co.electriccoin.zcash.ui.design.animation.ScreenAnimation.popEnterTransition
|
||||||
|
@ -51,19 +52,23 @@ fun MainActivity.OnboardingNavigation() {
|
||||||
val activity = LocalActivity.current
|
val activity = LocalActivity.current
|
||||||
val navigationRouter = koinInject<NavigationRouter>()
|
val navigationRouter = koinInject<NavigationRouter>()
|
||||||
val navController = LocalNavController.current
|
val navController = LocalNavController.current
|
||||||
val focusManager = LocalFocusManager.current
|
val keyboardManager = LocalKeyboardManager.current
|
||||||
val flexaViewModel = koinViewModel<FlexaViewModel>()
|
val flexaViewModel = koinViewModel<FlexaViewModel>()
|
||||||
|
val sheetStateManager = LocalSheetStateManager.current
|
||||||
|
|
||||||
val navigator: Navigator =
|
val navigator: Navigator =
|
||||||
remember(
|
remember(
|
||||||
navController,
|
navController,
|
||||||
flexaViewModel,
|
flexaViewModel,
|
||||||
focusManager
|
keyboardManager,
|
||||||
|
sheetStateManager
|
||||||
) {
|
) {
|
||||||
NavigatorImpl(
|
NavigatorImpl(
|
||||||
activity = this@OnboardingNavigation,
|
activity = this@OnboardingNavigation,
|
||||||
navController = navController,
|
navController = navController,
|
||||||
flexaViewModel = flexaViewModel,
|
flexaViewModel = flexaViewModel,
|
||||||
focusManager = focusManager
|
keyboardManager = keyboardManager,
|
||||||
|
sheetStateManager = sheetStateManager
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,12 +3,14 @@ package co.electriccoin.zcash.ui.screen.restore.info
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.DisposableEffect
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.SideEffect
|
import androidx.compose.runtime.SideEffect
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.ui.platform.LocalView
|
import androidx.compose.ui.platform.LocalView
|
||||||
import androidx.compose.ui.window.DialogWindowProvider
|
import androidx.compose.ui.window.DialogWindowProvider
|
||||||
import co.electriccoin.zcash.ui.NavigationRouter
|
import co.electriccoin.zcash.ui.NavigationRouter
|
||||||
|
import co.electriccoin.zcash.ui.design.LocalSheetStateManager
|
||||||
import co.electriccoin.zcash.ui.design.component.rememberModalBottomSheetState
|
import co.electriccoin.zcash.ui.design.component.rememberModalBottomSheetState
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
@ -27,6 +29,14 @@ fun AndroidSeedInfo() {
|
||||||
}
|
}
|
||||||
|
|
||||||
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
|
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
|
||||||
|
val sheetManager = LocalSheetStateManager.current
|
||||||
|
|
||||||
|
DisposableEffect(sheetState) {
|
||||||
|
sheetManager.onSheetOpened(sheetState)
|
||||||
|
onDispose {
|
||||||
|
sheetManager.onSheetDisposed(sheetState)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SeedInfoView(
|
SeedInfoView(
|
||||||
sheetState = sheetState,
|
sheetState = sheetState,
|
||||||
|
@ -34,14 +44,14 @@ fun AndroidSeedInfo() {
|
||||||
SeedInfoState(
|
SeedInfoState(
|
||||||
onBack = {
|
onBack = {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
sheetState.hide()
|
// sheetState.hide()
|
||||||
navigationRouter.back()
|
navigationRouter.back()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
onDismissRequest = {
|
onDismissRequest = {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
sheetState.hide()
|
// sheetState.hide()
|
||||||
navigationRouter.back()
|
navigationRouter.back()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,11 +8,9 @@ import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.ime
|
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
|
@ -25,17 +23,14 @@ import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.State
|
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableIntStateOf
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberUpdatedState
|
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
|
||||||
import androidx.compose.ui.platform.LocalFocusManager
|
import androidx.compose.ui.platform.LocalFocusManager
|
||||||
import androidx.compose.ui.platform.testTag
|
import androidx.compose.ui.platform.testTag
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
@ -44,6 +39,7 @@ import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import co.electriccoin.zcash.ui.R
|
import co.electriccoin.zcash.ui.R
|
||||||
import co.electriccoin.zcash.ui.common.appbar.ZashiTopAppBarTags
|
import co.electriccoin.zcash.ui.common.appbar.ZashiTopAppBarTags
|
||||||
|
import co.electriccoin.zcash.ui.design.LocalKeyboardManager
|
||||||
import co.electriccoin.zcash.ui.design.component.BlankBgScaffold
|
import co.electriccoin.zcash.ui.design.component.BlankBgScaffold
|
||||||
import co.electriccoin.zcash.ui.design.component.ButtonState
|
import co.electriccoin.zcash.ui.design.component.ButtonState
|
||||||
import co.electriccoin.zcash.ui.design.component.IconButtonState
|
import co.electriccoin.zcash.ui.design.component.IconButtonState
|
||||||
|
@ -76,13 +72,12 @@ fun RestoreSeedView(
|
||||||
|
|
||||||
val focusManager = LocalFocusManager.current
|
val focusManager = LocalFocusManager.current
|
||||||
var wasKeyboardOpen by remember { mutableStateOf(false) }
|
var wasKeyboardOpen by remember { mutableStateOf(false) }
|
||||||
val isKeyboardOpen by rememberKeyboardState()
|
val keyboardManager = LocalKeyboardManager.current
|
||||||
|
val isKeyboardOpen = keyboardManager.isOpen
|
||||||
LaunchedEffect(isKeyboardOpen) {
|
LaunchedEffect(isKeyboardOpen) {
|
||||||
if (wasKeyboardOpen && !isKeyboardOpen) {
|
if (wasKeyboardOpen && !isKeyboardOpen) {
|
||||||
focusManager.clearFocus(true)
|
focusManager.clearFocus(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
wasKeyboardOpen = isKeyboardOpen
|
wasKeyboardOpen = isKeyboardOpen
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -278,12 +273,6 @@ private fun getFilteredSuggestions(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun rememberKeyboardState(): State<Boolean> {
|
|
||||||
val isImeVisible = WindowInsets.ime.getBottom(LocalDensity.current) > 0
|
|
||||||
return rememberUpdatedState(isImeVisible)
|
|
||||||
}
|
|
||||||
|
|
||||||
@PreviewScreens
|
@PreviewScreens
|
||||||
@Composable
|
@Composable
|
||||||
private fun Preview() =
|
private fun Preview() =
|
||||||
|
|
|
@ -4,12 +4,14 @@ import android.view.WindowManager
|
||||||
import androidx.activity.compose.BackHandler
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.DisposableEffect
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.SideEffect
|
import androidx.compose.runtime.SideEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.platform.LocalView
|
import androidx.compose.ui.platform.LocalView
|
||||||
import androidx.compose.ui.window.DialogWindowProvider
|
import androidx.compose.ui.window.DialogWindowProvider
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import co.electriccoin.zcash.ui.design.LocalSheetStateManager
|
||||||
import co.electriccoin.zcash.ui.design.component.rememberModalBottomSheetState
|
import co.electriccoin.zcash.ui.design.component.rememberModalBottomSheetState
|
||||||
import co.electriccoin.zcash.ui.screen.transactionfilters.view.TransactionFiltersView
|
import co.electriccoin.zcash.ui.screen.transactionfilters.view.TransactionFiltersView
|
||||||
import co.electriccoin.zcash.ui.screen.transactionfilters.viewmodel.TransactionFiltersViewModel
|
import co.electriccoin.zcash.ui.screen.transactionfilters.viewmodel.TransactionFiltersViewModel
|
||||||
|
@ -22,6 +24,13 @@ fun AndroidTransactionFiltersList() {
|
||||||
val state by viewModel.state.collectAsStateWithLifecycle()
|
val state by viewModel.state.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
|
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
|
||||||
|
val sheetManager = LocalSheetStateManager.current
|
||||||
|
DisposableEffect(sheetState) {
|
||||||
|
sheetManager.onSheetOpened(sheetState)
|
||||||
|
onDispose {
|
||||||
|
sheetManager.onSheetDisposed(sheetState)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val parent = LocalView.current.parent
|
val parent = LocalView.current.parent
|
||||||
|
|
||||||
|
@ -40,13 +49,6 @@ fun AndroidTransactionFiltersList() {
|
||||||
sheetState.show()
|
sheetState.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
viewModel.hideBottomSheetRequest.collect {
|
|
||||||
sheetState.hide()
|
|
||||||
state?.onBottomSheetHidden?.invoke()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
BackHandler(state != null) {
|
BackHandler(state != null) {
|
||||||
state?.onBack?.invoke()
|
state?.onBack?.invoke()
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,14 +55,12 @@ object TransactionFiltersStateFixture {
|
||||||
|
|
||||||
fun new(
|
fun new(
|
||||||
onBack: () -> Unit = {},
|
onBack: () -> Unit = {},
|
||||||
onBottomSheetHidden: () -> Unit = {},
|
|
||||||
filters: List<TransactionFilterState> = FILTERS,
|
filters: List<TransactionFilterState> = FILTERS,
|
||||||
primaryButtonState: ButtonState = PRIMARY_BUTTON_STATE,
|
primaryButtonState: ButtonState = PRIMARY_BUTTON_STATE,
|
||||||
secondaryButtonState: ButtonState = SECONDARY_BUTTON_STATE
|
secondaryButtonState: ButtonState = SECONDARY_BUTTON_STATE
|
||||||
) = TransactionFiltersState(
|
) = TransactionFiltersState(
|
||||||
filters = filters,
|
filters = filters,
|
||||||
onBack = onBack,
|
onBack = onBack,
|
||||||
onBottomSheetHidden = onBottomSheetHidden,
|
|
||||||
primaryButton = primaryButtonState,
|
primaryButton = primaryButtonState,
|
||||||
secondaryButton = secondaryButtonState
|
secondaryButton = secondaryButtonState
|
||||||
)
|
)
|
||||||
|
|
|
@ -6,7 +6,6 @@ import co.electriccoin.zcash.ui.design.util.StringResource
|
||||||
data class TransactionFiltersState(
|
data class TransactionFiltersState(
|
||||||
val filters: List<TransactionFilterState>,
|
val filters: List<TransactionFilterState>,
|
||||||
val onBack: () -> Unit,
|
val onBack: () -> Unit,
|
||||||
val onBottomSheetHidden: () -> Unit,
|
|
||||||
val primaryButton: ButtonState,
|
val primaryButton: ButtonState,
|
||||||
val secondaryButton: ButtonState
|
val secondaryButton: ButtonState
|
||||||
)
|
)
|
||||||
|
|
|
@ -19,26 +19,19 @@ import co.electriccoin.zcash.ui.design.util.stringRes
|
||||||
import co.electriccoin.zcash.ui.screen.transactionfilters.model.TransactionFilterState
|
import co.electriccoin.zcash.ui.screen.transactionfilters.model.TransactionFilterState
|
||||||
import co.electriccoin.zcash.ui.screen.transactionfilters.model.TransactionFiltersState
|
import co.electriccoin.zcash.ui.screen.transactionfilters.model.TransactionFiltersState
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
|
||||||
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
|
||||||
import kotlinx.coroutines.flow.WhileSubscribed
|
import kotlinx.coroutines.flow.WhileSubscribed
|
||||||
import kotlinx.coroutines.flow.first
|
|
||||||
import kotlinx.coroutines.flow.mapLatest
|
import kotlinx.coroutines.flow.mapLatest
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
internal class TransactionFiltersViewModel(
|
internal class TransactionFiltersViewModel(
|
||||||
private val navigationRouter: NavigationRouter,
|
private val navigationRouter: NavigationRouter,
|
||||||
getTransactionFilters: GetTransactionFiltersUseCase,
|
getTransactionFilters: GetTransactionFiltersUseCase,
|
||||||
private val applyTransactionFilters: ApplyTransactionFiltersUseCase,
|
private val applyTransactionFilters: ApplyTransactionFiltersUseCase,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
val hideBottomSheetRequest = MutableSharedFlow<Unit>()
|
|
||||||
|
|
||||||
private val bottomSheetHiddenResponse = MutableSharedFlow<Unit>()
|
|
||||||
|
|
||||||
private val selectedFilters = MutableStateFlow(getTransactionFilters())
|
private val selectedFilters = MutableStateFlow(getTransactionFilters())
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
@ -87,7 +80,6 @@ internal class TransactionFiltersViewModel(
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onBack = ::onBack,
|
onBack = ::onBack,
|
||||||
onBottomSheetHidden = ::onBottomSheetHidden,
|
|
||||||
primaryButton =
|
primaryButton =
|
||||||
ButtonState(
|
ButtonState(
|
||||||
text = stringRes(R.string.transaction_filters_btn_apply),
|
text = stringRes(R.string.transaction_filters_btn_apply),
|
||||||
|
@ -100,9 +92,9 @@ internal class TransactionFiltersViewModel(
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}.stateIn(
|
}.stateIn(
|
||||||
viewModelScope,
|
scope = viewModelScope,
|
||||||
SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT),
|
started = SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT),
|
||||||
null
|
initialValue = null
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun onTransactionFilterClicked(filter: TransactionFilter) {
|
private fun onTransactionFilterClicked(filter: TransactionFilter) {
|
||||||
|
@ -115,29 +107,9 @@ internal class TransactionFiltersViewModel(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun hideBottomSheet() {
|
private fun onBack() = navigationRouter.back()
|
||||||
hideBottomSheetRequest.emit(Unit)
|
|
||||||
bottomSheetHiddenResponse.first()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onBottomSheetHidden() =
|
private fun onApplyTransactionFiltersClick() = applyTransactionFilters(selectedFilters.value)
|
||||||
viewModelScope.launch {
|
|
||||||
bottomSheetHiddenResponse.emit(Unit)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onBack() =
|
private fun onResetTransactionFiltersClick() = selectedFilters.update { emptyList() }
|
||||||
viewModelScope.launch {
|
|
||||||
hideBottomSheet()
|
|
||||||
navigationRouter.back()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onApplyTransactionFiltersClick() =
|
|
||||||
viewModelScope.launch {
|
|
||||||
applyTransactionFilters(selectedFilters.value) { hideBottomSheet() }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onResetTransactionFiltersClick() =
|
|
||||||
viewModelScope.launch {
|
|
||||||
selectedFilters.update { emptyList() }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import androidx.activity.compose.BackHandler
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.SheetValue.Expanded
|
import androidx.compose.material3.SheetValue.Expanded
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.DisposableEffect
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.SideEffect
|
import androidx.compose.runtime.SideEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
@ -12,6 +13,7 @@ import androidx.compose.runtime.snapshotFlow
|
||||||
import androidx.compose.ui.platform.LocalView
|
import androidx.compose.ui.platform.LocalView
|
||||||
import androidx.compose.ui.window.DialogWindowProvider
|
import androidx.compose.ui.window.DialogWindowProvider
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import co.electriccoin.zcash.ui.design.LocalSheetStateManager
|
||||||
import co.electriccoin.zcash.ui.design.component.rememberModalBottomSheetState
|
import co.electriccoin.zcash.ui.design.component.rememberModalBottomSheetState
|
||||||
import co.electriccoin.zcash.ui.screen.transactionnote.view.TransactionNoteView
|
import co.electriccoin.zcash.ui.screen.transactionnote.view.TransactionNoteView
|
||||||
import co.electriccoin.zcash.ui.screen.transactionnote.viewmodel.TransactionNoteViewModel
|
import co.electriccoin.zcash.ui.screen.transactionnote.viewmodel.TransactionNoteViewModel
|
||||||
|
@ -26,7 +28,13 @@ fun AndroidTransactionNote(transactionNote: TransactionNote) {
|
||||||
val state by viewModel.state.collectAsStateWithLifecycle()
|
val state by viewModel.state.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
|
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
|
||||||
|
val sheetManager = LocalSheetStateManager.current
|
||||||
|
DisposableEffect(sheetState) {
|
||||||
|
sheetManager.onSheetOpened(sheetState)
|
||||||
|
onDispose {
|
||||||
|
sheetManager.onSheetDisposed(sheetState)
|
||||||
|
}
|
||||||
|
}
|
||||||
val parent = LocalView.current.parent
|
val parent = LocalView.current.parent
|
||||||
|
|
||||||
SideEffect {
|
SideEffect {
|
||||||
|
@ -52,13 +60,6 @@ fun AndroidTransactionNote(transactionNote: TransactionNote) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
viewModel.hideBottomSheetRequest.collect {
|
|
||||||
sheetState.hide()
|
|
||||||
state.onBottomSheetHidden()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
BackHandler {
|
BackHandler {
|
||||||
state.onBack()
|
state.onBack()
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,6 @@ import co.electriccoin.zcash.ui.design.util.StyledStringResource
|
||||||
|
|
||||||
data class TransactionNoteState(
|
data class TransactionNoteState(
|
||||||
val onBack: () -> Unit,
|
val onBack: () -> Unit,
|
||||||
val onBottomSheetHidden: () -> Unit,
|
|
||||||
val title: StringResource,
|
val title: StringResource,
|
||||||
val note: TextFieldState,
|
val note: TextFieldState,
|
||||||
val noteCharacters: StyledStringResource,
|
val noteCharacters: StyledStringResource,
|
||||||
|
|
|
@ -137,7 +137,6 @@ private fun Preview() =
|
||||||
state =
|
state =
|
||||||
TransactionNoteState(
|
TransactionNoteState(
|
||||||
onBack = {},
|
onBack = {},
|
||||||
onBottomSheetHidden = {},
|
|
||||||
title = stringRes("Title"),
|
title = stringRes("Title"),
|
||||||
note = TextFieldState(stringRes("")) {},
|
note = TextFieldState(stringRes("")) {},
|
||||||
noteCharacters =
|
noteCharacters =
|
||||||
|
|
|
@ -15,13 +15,11 @@ import co.electriccoin.zcash.ui.design.util.StyledStringResource
|
||||||
import co.electriccoin.zcash.ui.design.util.stringRes
|
import co.electriccoin.zcash.ui.design.util.stringRes
|
||||||
import co.electriccoin.zcash.ui.screen.transactionnote.TransactionNote
|
import co.electriccoin.zcash.ui.screen.transactionnote.TransactionNote
|
||||||
import co.electriccoin.zcash.ui.screen.transactionnote.model.TransactionNoteState
|
import co.electriccoin.zcash.ui.screen.transactionnote.model.TransactionNoteState
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
|
||||||
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
|
||||||
import kotlinx.coroutines.flow.WhileSubscribed
|
import kotlinx.coroutines.flow.WhileSubscribed
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.first
|
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
@ -33,10 +31,6 @@ internal class TransactionNoteViewModel(
|
||||||
private val createOrUpdateTransactionNote: CreateOrUpdateTransactionNoteUseCase,
|
private val createOrUpdateTransactionNote: CreateOrUpdateTransactionNoteUseCase,
|
||||||
private val deleteTransactionNote: DeleteTransactionNoteUseCase
|
private val deleteTransactionNote: DeleteTransactionNoteUseCase
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
val hideBottomSheetRequest = MutableSharedFlow<Unit>()
|
|
||||||
|
|
||||||
private val bottomSheetHiddenResponse = MutableSharedFlow<Unit>()
|
|
||||||
|
|
||||||
private val noteText = MutableStateFlow("")
|
private val noteText = MutableStateFlow("")
|
||||||
private val foundNote = MutableStateFlow<String?>(null)
|
private val foundNote = MutableStateFlow<String?>(null)
|
||||||
|
|
||||||
|
@ -54,12 +48,9 @@ internal class TransactionNoteViewModel(
|
||||||
foundNote: String?
|
foundNote: String?
|
||||||
): TransactionNoteState {
|
): TransactionNoteState {
|
||||||
val noteTextNormalized = noteText.trim()
|
val noteTextNormalized = noteText.trim()
|
||||||
|
|
||||||
val isNoteTextTooLong = noteText.length > MAX_NOTE_LENGTH
|
val isNoteTextTooLong = noteText.length > MAX_NOTE_LENGTH
|
||||||
|
|
||||||
return TransactionNoteState(
|
return TransactionNoteState(
|
||||||
onBack = ::onBack,
|
onBack = ::onBack,
|
||||||
onBottomSheetHidden = ::onBottomSheetHidden,
|
|
||||||
title =
|
title =
|
||||||
if (foundNote == null) {
|
if (foundNote == null) {
|
||||||
stringRes(R.string.transaction_note_add_note_title)
|
stringRes(R.string.transaction_note_add_note_title)
|
||||||
|
@ -107,37 +98,19 @@ internal class TransactionNoteViewModel(
|
||||||
|
|
||||||
private fun onAddOrUpdateNoteClick() =
|
private fun onAddOrUpdateNoteClick() =
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
createOrUpdateTransactionNote(txId = transactionNote.txId, note = noteText.value) {
|
createOrUpdateTransactionNote(txId = transactionNote.txId, note = noteText.value)
|
||||||
hideBottomSheet()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onDeleteNoteClick() =
|
private fun onDeleteNoteClick() =
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
deleteTransactionNote(transactionNote.txId) {
|
deleteTransactionNote(transactionNote.txId)
|
||||||
hideBottomSheet()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onNoteTextChanged(newValue: String) {
|
private fun onNoteTextChanged(newValue: String) {
|
||||||
noteText.update { newValue }
|
noteText.update { newValue }
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun hideBottomSheet() {
|
private fun onBack() = navigationRouter.back()
|
||||||
hideBottomSheetRequest.emit(Unit)
|
|
||||||
bottomSheetHiddenResponse.first()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onBottomSheetHidden() =
|
|
||||||
viewModelScope.launch {
|
|
||||||
bottomSheetHiddenResponse.emit(Unit)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onBack() =
|
|
||||||
viewModelScope.launch {
|
|
||||||
hideBottomSheet()
|
|
||||||
navigationRouter.back()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private const val MAX_NOTE_LENGTH = 90
|
private const val MAX_NOTE_LENGTH = 90
|
||||||
|
|
Loading…
Reference in New Issue