diff --git a/demo/android/src/main/kotlin/im/molly/monero/demo/service/SyncService.kt b/demo/android/src/main/kotlin/im/molly/monero/demo/service/SyncService.kt index 5ccf6e7..d2631c9 100644 --- a/demo/android/src/main/kotlin/im/molly/monero/demo/service/SyncService.kt +++ b/demo/android/src/main/kotlin/im/molly/monero/demo/service/SyncService.kt @@ -42,7 +42,7 @@ class SyncService( while (isActive) { val result = wallet.awaitRefresh() if (result.isError()) { - break; + break } delay(10.seconds) } diff --git a/demo/android/src/main/kotlin/im/molly/monero/demo/ui/AddWalletWizard.kt b/demo/android/src/main/kotlin/im/molly/monero/demo/ui/AddWalletWizard.kt index f35c0a0..fcaba9b 100644 --- a/demo/android/src/main/kotlin/im/molly/monero/demo/ui/AddWalletWizard.kt +++ b/demo/android/src/main/kotlin/im/molly/monero/demo/ui/AddWalletWizard.kt @@ -48,7 +48,7 @@ private fun FirstStepScreen( Scaffold( topBar = { Toolbar( - titleRes = R.string.add_wallet, + title = stringResource(R.string.add_wallet), navigationIcon = { IconButton(onClick = onBackClick) { Icon( @@ -125,9 +125,9 @@ private fun SecondStepScreen( ) { Scaffold( topBar = { - val title = if (showRestoreOptions) R.string.restore_wallet else R.string.new_wallet + val titleRes = if (showRestoreOptions) R.string.restore_wallet else R.string.new_wallet Toolbar( - titleRes = title, + title = stringResource(titleRes), navigationIcon = { IconButton(onClick = onBackClick) { Icon( diff --git a/demo/android/src/main/kotlin/im/molly/monero/demo/ui/HomeScreen.kt b/demo/android/src/main/kotlin/im/molly/monero/demo/ui/HomeScreen.kt index 710e46d..1c57c70 100644 --- a/demo/android/src/main/kotlin/im/molly/monero/demo/ui/HomeScreen.kt +++ b/demo/android/src/main/kotlin/im/molly/monero/demo/ui/HomeScreen.kt @@ -19,6 +19,7 @@ import im.molly.monero.demo.ui.component.Toolbar @Composable fun HomeRoute( navigateToAddWalletWizard: () -> Unit, + navigateToWallet: (Long) -> Unit, viewModel: HomeViewModel = viewModel(), ) { val walletListUiState: WalletListUiState by viewModel.walletListUiState.collectAsStateWithLifecycle() @@ -26,6 +27,7 @@ fun HomeRoute( HomeScreen( walletListUiState = walletListUiState, onAddWalletClick = navigateToAddWalletWizard, + onWalletClick = navigateToWallet, ) } @@ -34,13 +36,14 @@ fun HomeRoute( private fun HomeScreen( walletListUiState: WalletListUiState, onAddWalletClick: () -> Unit, + onWalletClick: (Long) -> Unit, ) { val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior() Scaffold( modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), topBar = { Toolbar( - titleRes = R.string.monero_wallets, + title = stringResource(R.string.monero_wallets), scrollBehavior = scrollBehavior, ) }, @@ -57,20 +60,21 @@ private fun HomeScreen( .fillMaxSize() .padding(padding)) { - walletCards(walletListUiState) + walletCards(walletListUiState, onWalletClick) } } } private fun LazyListScope.walletCards( walletListUiState: WalletListUiState, + onWalletClick: (Long) -> Unit, ) { when (walletListUiState) { WalletListUiState.Loading -> item { Text(text = "Loading wallet list...") // TODO } is WalletListUiState.Success -> { - walletCardsItems(walletListUiState.ids) + walletCardsItems(walletListUiState.ids, onWalletClick) } } } diff --git a/demo/android/src/main/kotlin/im/molly/monero/demo/ui/RemoteNodeList.kt b/demo/android/src/main/kotlin/im/molly/monero/demo/ui/RemoteNodeList.kt index 798117e..567df50 100644 --- a/demo/android/src/main/kotlin/im/molly/monero/demo/ui/RemoteNodeList.kt +++ b/demo/android/src/main/kotlin/im/molly/monero/demo/ui/RemoteNodeList.kt @@ -1,8 +1,7 @@ package im.molly.monero.demo.ui import android.net.Uri -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.* import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Modifier @@ -41,9 +40,7 @@ fun RemoteNodeEditableList( onDeleteRemoteNode: (RemoteNode) -> Unit, modifier: Modifier = Modifier, ) { - Column( - modifier = modifier, - ) { + Column(modifier = modifier) { remoteNodes.forEach { remoteNode -> RemoteNodeItem( remoteNode, @@ -70,26 +67,24 @@ private fun RemoteNodeItem( headlineText = { Text(remoteNode.uri.toString()) }, overlineText = { Text(remoteNode.network.name.uppercase()) }, trailingContent = { - Row { - if (showCheckbox) { - Checkbox( - checked = checked, - onCheckedChange = onCheckedChange, - ) - } - if (showMenu) { - KebabMenu( - onEditClick = { onEditClick(remoteNode) }, - onDeleteClick = { onDeleteClick(remoteNode) }, - ) - } + if (showCheckbox) { + Checkbox( + checked = checked, + onCheckedChange = onCheckedChange, + ) + } + if (showMenu) { + WalletKebabMenu( + onEditClick = { onEditClick(remoteNode) }, + onDeleteClick = { onDeleteClick(remoteNode) }, + ) } }, ) } @Composable -private fun KebabMenu( +private fun WalletKebabMenu( onEditClick: () -> Unit, onDeleteClick: () -> Unit, ) { diff --git a/demo/android/src/main/kotlin/im/molly/monero/demo/ui/SettingsScreen.kt b/demo/android/src/main/kotlin/im/molly/monero/demo/ui/SettingsScreen.kt index cb65784..42e30de 100644 --- a/demo/android/src/main/kotlin/im/molly/monero/demo/ui/SettingsScreen.kt +++ b/demo/android/src/main/kotlin/im/molly/monero/demo/ui/SettingsScreen.kt @@ -1,13 +1,14 @@ package im.molly.monero.demo.ui +import android.net.Uri import androidx.annotation.StringRes -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material3.* import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview @@ -16,6 +17,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel import im.molly.monero.demo.R import im.molly.monero.demo.data.model.RemoteNode +import im.molly.monero.demo.ui.theme.AppIcons import im.molly.monero.demo.ui.theme.AppTheme @Composable @@ -46,19 +48,46 @@ private fun SettingsScreen( Column( modifier = modifier .verticalScroll(rememberScrollState()) - .padding(all = 24.dp) ) { - Divider() - SettingsSectionTitle(R.string.remote_nodes) - TextButton(onClick = onAddRemoteNode) { - Text(stringResource(R.string.add_remote_node)) - } + SettingsSection( + header = { + SettingsSectionTitle(R.string.remote_nodes) + IconButton(onClick = onAddRemoteNode) { + Icon( + imageVector = AppIcons.AddRemoteWallet, + contentDescription = stringResource(R.string.add_remote_node), + ) + } + } + ) RemoteNodeEditableList( remoteNodes = remoteNodes, onEditRemoteNode = onEditRemoteNode, onDeleteRemoteNode = onDeleteRemoteNode, + modifier = Modifier.padding(start = 24.dp), + ) + } +} + +@Composable +private fun SettingsSection( + modifier: Modifier = Modifier, + header: @Composable() (RowScope.() -> Unit), + content: @Composable() (RowScope.() -> Unit) = {}, +) { + Column( + modifier = modifier.padding(horizontal = 24.dp), + ) { + Divider(Modifier.padding(top = 24.dp)) + Row( + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + modifier = modifier.fillMaxWidth(), + content = header, + ) + Row( + content = content, ) -// Divider() } } @@ -67,7 +96,6 @@ private fun SettingsSectionTitle(@StringRes titleRes: Int) { Text( text = stringResource(titleRes), style = MaterialTheme.typography.titleMedium, - modifier = Modifier.padding(top = 16.dp, bottom = 8.dp) ) } @@ -75,8 +103,9 @@ private fun SettingsSectionTitle(@StringRes titleRes: Int) { @Composable private fun SettingsScreenPreview() { AppTheme { + val aNode = RemoteNode.EMPTY.copy(uri = Uri.parse("http://node.monero")) SettingsScreen( - remoteNodes = emptyList(), + remoteNodes = listOf(aNode), ) } } diff --git a/demo/android/src/main/kotlin/im/molly/monero/demo/ui/WalletCard.kt b/demo/android/src/main/kotlin/im/molly/monero/demo/ui/WalletCard.kt index e36b6f6..2a18eba 100644 --- a/demo/android/src/main/kotlin/im/molly/monero/demo/ui/WalletCard.kt +++ b/demo/android/src/main/kotlin/im/molly/monero/demo/ui/WalletCard.kt @@ -4,6 +4,7 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding import androidx.compose.material3.Card +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -12,10 +13,12 @@ import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel +@OptIn(ExperimentalMaterial3Api::class) @Composable fun WalletCard( walletId: Long, modifier: Modifier = Modifier, + onClick: (Long) -> Unit, viewModel: WalletViewModel = viewModel( factory = WalletViewModel.factory(walletId), key = walletId.toString(), @@ -24,6 +27,7 @@ fun WalletCard( val walletUiState: WalletUiState by viewModel.walletUiState.collectAsStateWithLifecycle() Card( + onClick = { onClick(walletId) }, modifier = modifier .padding(8.dp) ) { diff --git a/demo/android/src/main/kotlin/im/molly/monero/demo/ui/WalletCardList.kt b/demo/android/src/main/kotlin/im/molly/monero/demo/ui/WalletCardList.kt index e22a789..07920c4 100644 --- a/demo/android/src/main/kotlin/im/molly/monero/demo/ui/WalletCardList.kt +++ b/demo/android/src/main/kotlin/im/molly/monero/demo/ui/WalletCardList.kt @@ -6,6 +6,7 @@ import androidx.compose.ui.Modifier fun LazyListScope.walletCardsItems( items: List, + onItemClick: (Long) -> Unit, itemModifier: Modifier = Modifier, ) = items( items = items, @@ -13,6 +14,7 @@ fun LazyListScope.walletCardsItems( itemContent = { WalletCard( walletId = it, + onClick = onItemClick, modifier = itemModifier, ) }, diff --git a/demo/android/src/main/kotlin/im/molly/monero/demo/ui/WalletScreen.kt b/demo/android/src/main/kotlin/im/molly/monero/demo/ui/WalletScreen.kt index e69de29..ac3983f 100644 --- a/demo/android/src/main/kotlin/im/molly/monero/demo/ui/WalletScreen.kt +++ b/demo/android/src/main/kotlin/im/molly/monero/demo/ui/WalletScreen.kt @@ -0,0 +1,91 @@ +package im.molly.monero.demo.ui + +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.lifecycle.viewmodel.compose.viewModel +import im.molly.monero.demo.R +import im.molly.monero.demo.ui.component.Toolbar +import im.molly.monero.demo.ui.theme.AppIcons + +@Composable +fun WalletRoute( + walletId: Long, + onBackClick: () -> Unit, + modifier: Modifier = Modifier, + viewModel: WalletViewModel = viewModel( + factory = WalletViewModel.factory(walletId), + key = walletId.toString(), + ) +) { + val walletUiState: WalletUiState by viewModel.walletUiState.collectAsStateWithLifecycle() + + WalletScreen( + walletUiState = walletUiState, + onBackClick = onBackClick, + modifier = modifier, + ) +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun WalletScreen( + walletUiState: WalletUiState, + onBackClick: () -> Unit, + modifier: Modifier = Modifier, +) { + Scaffold( + topBar = { + Toolbar( + navigationIcon = { + IconButton(onClick = onBackClick) { + Icon( + imageVector = AppIcons.ArrowBack, + contentDescription = stringResource(R.string.back), + ) + } + }, + actions = { + WalletKebabMenu({}, {}) + } + ) + } + ) { padding -> + } +} + +@Composable +private fun WalletKebabMenu( + onRenameClick: () -> Unit, + onDeleteClick: () -> Unit, +) { + var expanded by remember { mutableStateOf(false) } + + IconButton(onClick = { expanded = true }) { + Icon( + imageVector = AppIcons.MoreVert, + contentDescription = stringResource(R.string.open_menu), + ) + } + DropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false }, + ) { + DropdownMenuItem( + text = { Text(stringResource(R.string.rename)) }, + onClick = { + onRenameClick() + expanded = false + }, + ) + DropdownMenuItem( + text = { Text(stringResource(R.string.delete)) }, + onClick = { + onDeleteClick() + expanded = false + }, + ) + } +} diff --git a/demo/android/src/main/kotlin/im/molly/monero/demo/ui/component/Toolbar.kt b/demo/android/src/main/kotlin/im/molly/monero/demo/ui/component/Toolbar.kt index 2ec41bb..dcf29b1 100644 --- a/demo/android/src/main/kotlin/im/molly/monero/demo/ui/component/Toolbar.kt +++ b/demo/android/src/main/kotlin/im/molly/monero/demo/ui/component/Toolbar.kt @@ -1,6 +1,5 @@ package im.molly.monero.demo.ui.component -import androidx.annotation.StringRes import androidx.compose.foundation.layout.RowScope import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.ExperimentalMaterial3Api @@ -8,19 +7,18 @@ import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource @OptIn(ExperimentalMaterial3Api::class) @Composable fun Toolbar( - @StringRes titleRes: Int, modifier: Modifier = Modifier, + title: String? = null, navigationIcon: @Composable () -> Unit = {}, actions: @Composable RowScope.() -> Unit = {}, scrollBehavior: TopAppBarScrollBehavior? = null, ) { CenterAlignedTopAppBar( - title = { Text(stringResource(id = titleRes), ) }, + title = { Text(title ?: "") }, modifier = modifier, navigationIcon = navigationIcon, actions = actions, diff --git a/demo/android/src/main/kotlin/im/molly/monero/demo/ui/navigation/HomeNavigation.kt b/demo/android/src/main/kotlin/im/molly/monero/demo/ui/navigation/HomeNavigation.kt index 3f4d8e2..3e1588c 100644 --- a/demo/android/src/main/kotlin/im/molly/monero/demo/ui/navigation/HomeNavigation.kt +++ b/demo/android/src/main/kotlin/im/molly/monero/demo/ui/navigation/HomeNavigation.kt @@ -14,10 +14,12 @@ fun NavController.navigateToHome(navOptions: NavOptions? = null) { fun NavGraphBuilder.homeScreen( navigateToAddWalletWizard: () -> Unit, + navigateToWallet: (Long) -> Unit, ) { composable(route = homeNavRoute) { HomeRoute( navigateToAddWalletWizard = navigateToAddWalletWizard, + navigateToWallet = navigateToWallet, ) } } diff --git a/demo/android/src/main/kotlin/im/molly/monero/demo/ui/navigation/NavGraph.kt b/demo/android/src/main/kotlin/im/molly/monero/demo/ui/navigation/NavGraph.kt index 3fc176b..67ab25c 100644 --- a/demo/android/src/main/kotlin/im/molly/monero/demo/ui/navigation/NavGraph.kt +++ b/demo/android/src/main/kotlin/im/molly/monero/demo/ui/navigation/NavGraph.kt @@ -18,6 +18,9 @@ fun NavGraph( modifier, ) { homeScreen( + navigateToWallet = { walletId -> + navController.navigateToWallet(walletId) + }, navigateToAddWalletWizard = { navController.navigateToAddWalletWizardGraph() }, @@ -28,6 +31,9 @@ fun NavGraph( navController.navigateToEditRemoteNode(remoteNodeId) }, ) + walletScreen( + onBackClick = onBackClick, + ) editRemoteNodeDialog( onBackClick = onBackClick, ) diff --git a/demo/android/src/main/kotlin/im/molly/monero/demo/ui/navigation/SettingsNavigation.kt b/demo/android/src/main/kotlin/im/molly/monero/demo/ui/navigation/SettingsNavigation.kt index 51bea16..e105d28 100644 --- a/demo/android/src/main/kotlin/im/molly/monero/demo/ui/navigation/SettingsNavigation.kt +++ b/demo/android/src/main/kotlin/im/molly/monero/demo/ui/navigation/SettingsNavigation.kt @@ -9,19 +9,16 @@ import im.molly.monero.demo.ui.SettingsRoute const val settingsNavRoute = "settings" const val settingsRemoteNodeNavRoute = "$settingsNavRoute/remote_node" -private const val idArg = "id" +private const val queryId = "id" fun NavController.navigateToSettings(navOptions: NavOptions? = null) { navigate(settingsNavRoute, navOptions) } fun NavController.navigateToEditRemoteNode(remoteNodeId: Long?) { - val route = if (remoteNodeId != null) { - "$settingsRemoteNodeNavRoute?$idArg=$remoteNodeId" - } else { - settingsRemoteNodeNavRoute - } - this.navigate(route) + val route = settingsRemoteNodeNavRoute + + if (remoteNodeId != null) "?$queryId=$remoteNodeId" else "" + navigate(route) } fun NavGraphBuilder.settingsScreen( @@ -38,16 +35,16 @@ fun NavGraphBuilder.editRemoteNodeDialog( onBackClick: () -> Unit, ) { dialog( - route = "$settingsRemoteNodeNavRoute?$idArg={$idArg}", + route = "$settingsRemoteNodeNavRoute?$queryId={$queryId}", arguments = listOf( - navArgument(idArg) { + navArgument(queryId) { type = NavType.StringType nullable = true } ) ) { val arguments = requireNotNull(it.arguments) - val remoteNodeId = arguments.getString(idArg)?.toLongOrNull() + val remoteNodeId = arguments.getString(queryId)?.toLongOrNull() EditRemoteNodeRoute( remoteNodeId = remoteNodeId, onBackClick = onBackClick, diff --git a/demo/android/src/main/kotlin/im/molly/monero/demo/ui/navigation/WalletNavigation.kt b/demo/android/src/main/kotlin/im/molly/monero/demo/ui/navigation/WalletNavigation.kt index d1dea43..a99f437 100644 --- a/demo/android/src/main/kotlin/im/molly/monero/demo/ui/navigation/WalletNavigation.kt +++ b/demo/android/src/main/kotlin/im/molly/monero/demo/ui/navigation/WalletNavigation.kt @@ -4,13 +4,22 @@ import androidx.navigation.* import androidx.navigation.compose.composable import im.molly.monero.demo.ui.AddWalletFirstStepRoute import im.molly.monero.demo.ui.AddWalletSecondStepRoute +import im.molly.monero.demo.ui.WalletRoute -const val addWalletWizardNavRoute = "home/add_wallet_wizard" +const val walletNavRoute = "wallet" +const val addWalletWizardNavRoute = "add_wallet_wizard" + +private const val walletIdArg = "id" private const val startNavRoute = "$addWalletWizardNavRoute/start" private const val createNavRoute = "$addWalletWizardNavRoute/create" private const val restoreNavRoute = "$addWalletWizardNavRoute/restore" +fun NavController.navigateToWallet(walletId: Long) { + val route = "$walletNavRoute/$walletId" + navigate(route) +} + fun NavController.navigateToAddWalletWizardGraph(navOptions: NavOptions? = null) { navigate(addWalletWizardNavRoute, navOptions) } @@ -22,6 +31,24 @@ fun NavController.navigateToAddWalletSecondStep(restoreWallet: Boolean) { } } +fun NavGraphBuilder.walletScreen( + onBackClick: () -> Unit, +) { + composable( + route = "$walletNavRoute/{$walletIdArg}", + arguments = listOf( + navArgument(walletIdArg) { type = NavType.LongType } + ) + ) { + val arguments = requireNotNull(it.arguments) + val walletId = arguments.getLong(walletIdArg) + WalletRoute( + walletId = walletId, + onBackClick = onBackClick, + ) + } +} + fun NavGraphBuilder.addWalletWizardGraph( navController: NavHostController, onBackClick: () -> Unit, diff --git a/demo/android/src/main/kotlin/im/molly/monero/demo/ui/theme/Icon.kt b/demo/android/src/main/kotlin/im/molly/monero/demo/ui/theme/Icon.kt index 55ccdd2..17d6046 100644 --- a/demo/android/src/main/kotlin/im/molly/monero/demo/ui/theme/Icon.kt +++ b/demo/android/src/main/kotlin/im/molly/monero/demo/ui/theme/Icon.kt @@ -2,9 +2,7 @@ package im.molly.monero.demo.ui.theme import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.* -import androidx.compose.material.icons.outlined.Home -import androidx.compose.material.icons.outlined.List -import androidx.compose.material.icons.outlined.Settings +import androidx.compose.material.icons.outlined.* object AppIcons { val ArrowBack = Icons.Filled.ArrowBack @@ -16,4 +14,5 @@ object AppIcons { val Settings = Icons.Filled.Settings val SettingsOutlined = Icons.Outlined.Settings val AddWallet = Icons.Filled.Add + val AddRemoteWallet = Icons.Outlined.AddCircle } diff --git a/demo/android/src/main/res/values/strings.xml b/demo/android/src/main/res/values/strings.xml index 3ca3a1b..ec42cd6 100644 --- a/demo/android/src/main/res/values/strings.xml +++ b/demo/android/src/main/res/values/strings.xml @@ -12,6 +12,7 @@ Settings Add remote node Edit + Rename Open menu Delete Save