diff --git a/app/src/main/java/to/bitkit/data/SettingsStore.kt b/app/src/main/java/to/bitkit/data/SettingsStore.kt index a3cfcef84..0c9fd240f 100644 --- a/app/src/main/java/to/bitkit/data/SettingsStore.kt +++ b/app/src/main/java/to/bitkit/data/SettingsStore.kt @@ -113,6 +113,7 @@ data class SettingsData( val backupWarningIgnoredMillis: Long = 0, val notificationsIgnoredMillis: Long = 0, val balanceWarningTimes: Int = 0, + val widgetsOnboardingHintDismissed: Boolean = false, val coinSelectAuto: Boolean = true, val coinSelectPreference: CoinSelectionPreference = CoinSelectionPreference.BranchAndBound, val electrumServer: String = Env.electrumServerUrl, diff --git a/app/src/main/java/to/bitkit/data/WidgetsStore.kt b/app/src/main/java/to/bitkit/data/WidgetsStore.kt index 13487b950..1f50c447c 100644 --- a/app/src/main/java/to/bitkit/data/WidgetsStore.kt +++ b/app/src/main/java/to/bitkit/data/WidgetsStore.kt @@ -160,9 +160,9 @@ class WidgetsStore @Inject constructor( @Serializable data class WidgetsData( val widgets: List = listOf( - WidgetWithPosition(type = WidgetType.PRICE, position = 0), - WidgetWithPosition(type = WidgetType.BLOCK, position = 1), - WidgetWithPosition(type = WidgetType.NEWS, position = 2), + WidgetWithPosition(type = WidgetType.SUGGESTIONS, position = 0), + WidgetWithPosition(type = WidgetType.PRICE, position = 1), + WidgetWithPosition(type = WidgetType.BLOCK, position = 2), ), val headlinePreferences: HeadlinePreferences = HeadlinePreferences(), val factsPreferences: FactsPreferences = FactsPreferences(), diff --git a/app/src/main/java/to/bitkit/models/WidgetType.kt b/app/src/main/java/to/bitkit/models/WidgetType.kt index 95d0c74bc..00f26bce4 100644 --- a/app/src/main/java/to/bitkit/models/WidgetType.kt +++ b/app/src/main/java/to/bitkit/models/WidgetType.kt @@ -31,5 +31,9 @@ enum class WidgetType( WEATHER( iconRes = R.drawable.widget_cloud, title = R.string.widgets__weather__name + ), + SUGGESTIONS( + iconRes = R.drawable.widget_suggestions, + title = R.string.widgets__suggestions__name, ) } diff --git a/app/src/main/java/to/bitkit/repositories/WidgetsRepo.kt b/app/src/main/java/to/bitkit/repositories/WidgetsRepo.kt index 66f7b2338..fb5746535 100644 --- a/app/src/main/java/to/bitkit/repositories/WidgetsRepo.kt +++ b/app/src/main/java/to/bitkit/repositories/WidgetsRepo.kt @@ -84,7 +84,7 @@ class WidgetsRepo @Inject constructor( private fun updateWidgetJobs(enabledWidgetTypes: Set) { val widgetTypesWithServices = WidgetType.entries.filter { - it != WidgetType.CALCULATOR + it != WidgetType.CALCULATOR && it != WidgetType.SUGGESTIONS } widgetTypesWithServices.forEach { widgetType -> @@ -138,7 +138,9 @@ class WidgetsRepo @Inject constructor( } } - WidgetType.CALCULATOR -> throw NotImplementedError("Calculator widget doesn't need a service") + WidgetType.CALCULATOR, + WidgetType.SUGGESTIONS, + -> throw NotImplementedError("Widget doesn't need a service") } widgetJobs[widgetType] = job @@ -226,8 +228,10 @@ class WidgetsRepo @Inject constructor( widgetsStore.updateBlock(block) } - WidgetType.CALCULATOR -> { - throw NotImplementedError("Calculator widget doesn't need a service") + WidgetType.CALCULATOR, + WidgetType.SUGGESTIONS, + -> { + throw NotImplementedError("Widget doesn't need a service") } WidgetType.FACTS -> updateWidget(factsService) { facts -> diff --git a/app/src/main/java/to/bitkit/ui/ContentView.kt b/app/src/main/java/to/bitkit/ui/ContentView.kt index 45ae8f1d5..1c3f8c9dc 100644 --- a/app/src/main/java/to/bitkit/ui/ContentView.kt +++ b/app/src/main/java/to/bitkit/ui/ContentView.kt @@ -105,6 +105,8 @@ import to.bitkit.ui.screens.wallets.activity.TagSelectorSheet import to.bitkit.ui.screens.wallets.receive.ReceiveSheet import to.bitkit.ui.screens.wallets.suggestion.BuyIntroScreen import to.bitkit.ui.screens.widgets.AddWidgetsScreen +import to.bitkit.ui.screens.widgets.suggestions.SuggestionsPreviewScreen +import to.bitkit.ui.screens.widgets.suggestions.SuggestionsViewModel import to.bitkit.ui.screens.widgets.WidgetsIntroScreen import to.bitkit.ui.screens.widgets.blocks.BlocksEditScreen import to.bitkit.ui.screens.widgets.blocks.BlocksPreviewScreen @@ -1393,10 +1395,19 @@ private fun NavGraphBuilder.widgets( WidgetType.NEWS -> navController.navigate(Routes.HeadlinesPreview) WidgetType.PRICE -> navController.navigate(Routes.PricePreview) WidgetType.WEATHER -> navController.navigate(Routes.WeatherPreview) + WidgetType.SUGGESTIONS -> navController.navigate(Routes.SuggestionsPreview) } }, fiatSymbol = LocalCurrencies.current.currencySymbol, - onBackCLick = { navController.popBackStack() } + onBackCLick = { navController.popBackStack() }, + ) + } + composableWithDefaultTransitions { + val viewModel = hiltViewModel() + SuggestionsPreviewScreen( + suggestionsViewModel = viewModel, + onClose = { navController.navigateToHome() }, + onBack = { navController.popBackStack() }, ) } composableWithDefaultTransitions { @@ -1981,6 +1992,9 @@ sealed interface Routes { @Serializable data object AddWidget : Routes + @Serializable + data object SuggestionsPreview : Routes + @Serializable data object Headlines : Routes diff --git a/app/src/main/java/to/bitkit/ui/components/SuggestionCard.kt b/app/src/main/java/to/bitkit/ui/components/SuggestionCard.kt index 8c1a36fb8..46a7bfe96 100644 --- a/app/src/main/java/to/bitkit/ui/components/SuggestionCard.kt +++ b/app/src/main/java/to/bitkit/ui/components/SuggestionCard.kt @@ -13,6 +13,8 @@ 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.aspectRatio +import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding @@ -25,6 +27,7 @@ import androidx.compose.material3.IconButton import androidx.compose.material3.ShapeDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Brush @@ -54,7 +57,6 @@ fun SuggestionCard( description: String, @DrawableRes icon: Int, onClose: (() -> Unit)? = null, - size: Int = 152, disableGlow: Boolean = false, captionColor: Color = Colors.White64, onClick: () -> Unit, @@ -75,7 +77,8 @@ fun SuggestionCard( Box( modifier = modifier - .size(size.dp) + .fillMaxWidth() + .aspectRatio(1f) .clip(ShapeDefaults.Large) .then( if (isDismissible || disableGlow) { @@ -121,8 +124,11 @@ fun SuggestionCard( Image( painter = painterResource(icon), contentDescription = null, + alignment = Alignment.CenterStart, contentScale = ContentScale.FillHeight, - modifier = Modifier.weight(1f) + modifier = Modifier + .defaultMinSize(minHeight = 96.dp) + .weight(1f) ) if (onClose != null) { @@ -135,7 +141,7 @@ fun SuggestionCard( Icon( painter = painterResource(R.drawable.ic_x), contentDescription = null, - tint = Colors.White, + tint = Colors.White64, ) } } diff --git a/app/src/main/java/to/bitkit/ui/components/TabBar.kt b/app/src/main/java/to/bitkit/ui/components/TabBar.kt index fd629f34e..4a305e789 100644 --- a/app/src/main/java/to/bitkit/ui/components/TabBar.kt +++ b/app/src/main/java/to/bitkit/ui/components/TabBar.kt @@ -50,6 +50,9 @@ import to.bitkit.ui.theme.Colors private val iconToTextGap = 4.dp private val iconSize = 20.dp +const val TAB_BAR_HEIGHT = 56 +const val TAB_BAR_PADDING_BOTTOM = 8 + private val buttonLeftShape = RoundedCornerShape(topStartPercent = 50, bottomStartPercent = 50) private val buttonRightShape = RoundedCornerShape(topEndPercent = 50, bottomEndPercent = 50) @@ -67,7 +70,7 @@ fun BoxScope.TabBar( .align(Alignment.BottomCenter) .fillMaxWidth() .padding(horizontal = 16.dp) - .padding(bottom = 16.dp) + .padding(bottom = TAB_BAR_PADDING_BOTTOM.dp) .navigationBarsPadding() ) { Row( @@ -81,7 +84,7 @@ fun BoxScope.TabBar( contentAlignment = Alignment.Center, modifier = Modifier .weight(1f) - .height(60.dp) + .height(TAB_BAR_HEIGHT.dp) .clip(buttonLeftShape) .clickable { onSendClick() } .testTag("Send") @@ -102,7 +105,7 @@ fun BoxScope.TabBar( contentAlignment = Alignment.Center, modifier = Modifier .weight(1f) - .height(60.dp) + .height(TAB_BAR_HEIGHT.dp) .clip(buttonRightShape) .clickable { onReceiveClick() } .testTag("Receive") diff --git a/app/src/main/java/to/bitkit/ui/components/Text.kt b/app/src/main/java/to/bitkit/ui/components/Text.kt index 8bfa181b4..e30ad3b2d 100644 --- a/app/src/main/java/to/bitkit/ui/components/Text.kt +++ b/app/src/main/java/to/bitkit/ui/components/Text.kt @@ -84,6 +84,23 @@ fun Headline20( ) } +@Composable +fun Headline24( + text: AnnotatedString, + modifier: Modifier = Modifier, + color: Color = MaterialTheme.colorScheme.primary, +) { + Text( + text = text.toUpperCase(), + style = AppTextStyles.Headline.merge( + fontSize = 24.sp, + lineHeight = 24.sp, + color = color, + ), + modifier = modifier, + ) +} + @Composable fun Title( text: String, diff --git a/app/src/main/java/to/bitkit/ui/screens/shop/shopDiscover/ShopDiscoverScreen.kt b/app/src/main/java/to/bitkit/ui/screens/shop/shopDiscover/ShopDiscoverScreen.kt index 8517a76eb..b9fc346c8 100644 --- a/app/src/main/java/to/bitkit/ui/screens/shop/shopDiscover/ShopDiscoverScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/shop/shopDiscover/ShopDiscoverScreen.kt @@ -55,8 +55,6 @@ import to.bitkit.ui.theme.AppThemeSurface import to.bitkit.ui.theme.Colors import to.bitkit.ui.theme.Shapes -private const val SHOP_CARD_SIZE = 164 - @OptIn(ExperimentalMaterial3Api::class) @Composable fun ShopDiscoverScreen( @@ -128,7 +126,7 @@ private fun ShopTabContent( description = stringResource(R.string.other__shop__discover__gift_cards__description), icon = R.drawable.gift, captionColor = Colors.Gray1, - size = SHOP_CARD_SIZE, + disableGlow = true, onClick = { navigateWebView("gift-cards", title) @@ -142,7 +140,7 @@ private fun ShopTabContent( description = stringResource(R.string.other__shop__discover__esims__description), icon = R.drawable.globe, captionColor = Colors.Gray1, - size = SHOP_CARD_SIZE, + disableGlow = true, onClick = { navigateWebView("esims", title2) @@ -163,7 +161,7 @@ private fun ShopTabContent( description = stringResource(R.string.other__shop__discover__refill__description), icon = R.drawable.phone, captionColor = Colors.Gray1, - size = SHOP_CARD_SIZE, + disableGlow = true, onClick = { navigateWebView("refill", title) @@ -176,7 +174,7 @@ private fun ShopTabContent( title = title2, description = stringResource(R.string.other__shop__discover__travel__description), icon = R.drawable.rocket_2, - size = SHOP_CARD_SIZE, + disableGlow = true, captionColor = Colors.Gray1, onClick = { diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/HomeScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/HomeScreen.kt index 5367f742f..ebfb2e42c 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/HomeScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/HomeScreen.kt @@ -1,15 +1,15 @@ package to.bitkit.ui.screens.wallets import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.tween +import androidx.compose.foundation.Image import androidx.compose.foundation.background -import androidx.compose.foundation.gestures.snapping.SnapPosition -import androidx.compose.foundation.gestures.snapping.rememberSnapFlingBehavior import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.fillMaxSize @@ -17,9 +17,11 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.statusBars -import androidx.compose.foundation.lazy.LazyRow -import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.items +import androidx.compose.foundation.pager.VerticalPager +import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material3.DrawerState @@ -44,6 +46,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.painterResource @@ -76,13 +79,16 @@ import to.bitkit.ui.components.ActivityBanner import to.bitkit.ui.components.AppStatus import to.bitkit.ui.components.BalanceHeaderView import to.bitkit.ui.components.EmptyStateView +import to.bitkit.ui.components.FillHeight +import to.bitkit.ui.components.Headline24 import to.bitkit.ui.components.HorizontalSpacer import to.bitkit.ui.components.Sheet import to.bitkit.ui.components.StatusBarSpacer import to.bitkit.ui.components.SuggestionCard +import to.bitkit.ui.components.TAB_BAR_HEIGHT +import to.bitkit.ui.components.TAB_BAR_PADDING_BOTTOM import to.bitkit.ui.components.TabBar import to.bitkit.ui.components.TertiaryButton -import to.bitkit.ui.components.Text13Up import to.bitkit.ui.components.TopBarSpacer import to.bitkit.ui.components.VerticalSpacer import to.bitkit.ui.components.WalletBalanceView @@ -108,12 +114,18 @@ import to.bitkit.ui.sheets.BackupRoute import to.bitkit.ui.sheets.PinRoute import to.bitkit.ui.theme.AppThemeSurface import to.bitkit.ui.theme.Colors +import to.bitkit.ui.theme.Insets import to.bitkit.ui.utils.withAccent import to.bitkit.viewmodels.ActivityListViewModel import to.bitkit.viewmodels.AppViewModel import to.bitkit.viewmodels.SettingsViewModel import to.bitkit.viewmodels.WalletViewModel +private const val SMALL_SCREEN_HEIGHT_DP = 700 +private const val SMALL_SCREEN_ACTIVITY_COUNT = 2 +private const val LARGE_SCREEN_ACTIVITY_COUNT = 3 +private const val ANIMATION_DURATION_MS = 300 + @Suppress("CyclomaticComplexMethod") @Composable fun HomeScreen( @@ -130,7 +142,6 @@ fun HomeScreen( val context = LocalContext.current val hasSeenTransferIntro by settingsViewModel.hasSeenTransferIntro.collectAsStateWithLifecycle() val hasSeenShopIntro by settingsViewModel.hasSeenShopIntro.collectAsStateWithLifecycle() - val hasSeenProfileIntro by settingsViewModel.hasSeenProfileIntro.collectAsStateWithLifecycle() val hasSeenWidgetsIntro: Boolean by settingsViewModel.hasSeenWidgetsIntro.collectAsStateWithLifecycle() val bgPaymentsIntroSeen: Boolean by settingsViewModel.bgPaymentsIntroSeen.collectAsStateWithLifecycle() val quickPayIntroSeen by settingsViewModel.quickPayIntroSeen.collectAsStateWithLifecycle() @@ -248,6 +259,7 @@ fun HomeScreen( WidgetType.NEWS -> rootNavController.navigate(Routes.HeadlinesPreview) WidgetType.PRICE -> rootNavController.navigate(Routes.PricePreview) WidgetType.WEATHER -> rootNavController.navigate(Routes.WeatherPreview) + WidgetType.SUGGESTIONS -> rootNavController.navigate(Routes.SuggestionsPreview) } }, onClickDeleteWidget = { widgetType -> @@ -256,8 +268,8 @@ fun HomeScreen( onMoveWidget = { fromIndex, toIndex -> homeViewModel.moveWidget(fromIndex, toIndex) }, - onDismissEmptyState = homeViewModel::dismissEmptyState, - onClickEmptyActivityRow = { appViewModel.showSheet(Sheet.Receive) }, + onPageChanged = homeViewModel::onPageChanged, + onDismissWidgetsOnboardingHint = homeViewModel::dismissWidgetsOnboardingHint, ) } @@ -280,192 +292,159 @@ private fun Content( onClickEditWidget: (WidgetType) -> Unit = {}, onClickDeleteWidget: (WidgetType) -> Unit = {}, onMoveWidget: (Int, Int) -> Unit = { _, _ -> }, - onDismissEmptyState: () -> Unit = {}, - onClickEmptyActivityRow: () -> Unit = {}, + onPageChanged: (Int) -> Unit = {}, + onDismissWidgetsOnboardingHint: () -> Unit = {}, balances: BalanceState = LocalBalances.current, ) { val scope = rememberCoroutineScope() + val pageCount = if (homeUiState.showWidgets) 2 else 1 + val pagerState = rememberPagerState(pageCount = { pageCount }) + + LaunchedEffect(pagerState.currentPage) { + onPageChanged(pagerState.currentPage) + if (pagerState.currentPage == 1 && !latestActivities.isNullOrEmpty()) { + onDismissWidgetsOnboardingHint() + } + } + + val screenHeightDp = LocalConfiguration.current.screenHeightDp + val activityCount = if (screenHeightDp < SMALL_SCREEN_HEIGHT_DP) { + SMALL_SCREEN_ACTIVITY_COUNT + } else { + LARGE_SCREEN_ACTIVITY_COUNT + } Box { - val heightStatusBar = WindowInsets.statusBars.asPaddingValues().calculateTopPadding() TopBar( hazeState = hazeState, rootNavController = rootNavController, scope = scope, drawerState = drawerState, + showEditWidgets = homeUiState.currentPage == 1 && homeUiState.showWidgets, + isEditingWidgets = homeUiState.isEditingWidgets, + onClickEditWidgetList = onClickEditWidgetList, ) - val pullToRefreshState = rememberPullToRefreshState() - PullToRefreshBox( - state = pullToRefreshState, - isRefreshing = isRefreshing, - onRefresh = onRefresh, - indicator = { - Indicator( - isRefreshing = isRefreshing, - state = pullToRefreshState, - modifier = Modifier - .padding(top = heightStatusBar) - .align(Alignment.TopCenter) - ) - }, + + VerticalPager( + state = pagerState, + userScrollEnabled = homeUiState.showWidgets, modifier = Modifier .fillMaxSize() .hazeSource(state = hazeState) .zIndex(0f) - ) { - Column( - modifier = Modifier - .padding(horizontal = 16.dp) - .fillMaxSize() - .verticalScroll(rememberScrollState()) - .testTag("HomeScrollView") - ) { - StatusBarSpacer() - TopBarSpacer() - VerticalSpacer(16.dp) - BalanceHeaderView( - sats = balances.totalSats.toLong(), - showEyeIcon = true, - testTag = "TotalBalance", - modifier = Modifier - .fillMaxWidth() - .testTag("TotalBalance") + ) { page -> + when (page) { + 0 -> WalletPage( + isRefreshing = isRefreshing, + homeUiState = homeUiState, + latestActivities = latestActivities?.take(activityCount), + balances = balances, + rootNavController = rootNavController, + walletNavController = walletNavController, + onRefresh = onRefresh, ) - if (!homeUiState.showEmptyState) { - Spacer(modifier = Modifier.height(32.dp)) - Row( - modifier = Modifier - .fillMaxWidth() - .height(IntrinsicSize.Min) - ) { - WalletBalanceView( - title = stringResource(R.string.wallet__savings__title), - sats = balances.totalOnchainSats.toLong(), - icon = painterResource(id = R.drawable.ic_btc_circle), - modifier = Modifier - .clickableAlpha { walletNavController.navigate(Routes.Savings) } - .padding(vertical = 4.dp) - .testTag("ActivitySavings") - ) - VerticalDivider() - HorizontalSpacer(16.dp) - WalletBalanceView( - title = stringResource(R.string.wallet__spending__title), - sats = balances.totalLightningSats.toLong(), - icon = painterResource(id = R.drawable.ic_ln_circle), - modifier = Modifier - .clickableAlpha { walletNavController.navigate(Routes.Spending) } - .padding(vertical = 4.dp) - .testTag("ActivitySpending") - ) - } - - AnimatedVisibility(homeUiState.suggestions.isNotEmpty()) { - val state = rememberLazyListState() - val snapBehavior = rememberSnapFlingBehavior( - lazyListState = state, - snapPosition = SnapPosition.Start - ) - Column { - Spacer(modifier = Modifier.height(32.dp)) - Text13Up(stringResource(R.string.cards__suggestions), color = Colors.White64) - Spacer(modifier = Modifier.height(16.dp)) - LazyRow( - horizontalArrangement = Arrangement.spacedBy(16.dp), - state = state, - flingBehavior = snapBehavior, - modifier = Modifier - .fillMaxWidth() - .testTag("Suggestions") - ) { - items(homeUiState.suggestions, key = { it.name }) { item -> - SuggestionCard( - gradientColor = item.color, - title = stringResource(item.title), - description = stringResource(item.description), - icon = item.icon, - onClose = { onRemoveSuggestion(item) }.takeIf { item.dismissible }, - onClick = { onClickSuggestion(item) }, - modifier = Modifier.testTag("Suggestion-${item.name.lowercase()}") - ) - } - } - } - } + 1 -> WidgetsPage( + homeUiState = homeUiState, + onRemoveSuggestion = onRemoveSuggestion, + onClickSuggestion = onClickSuggestion, + onClickAddWidget = onClickAddWidget, + onClickEditWidget = onClickEditWidget, + onClickDeleteWidget = onClickDeleteWidget, + onMoveWidget = onMoveWidget, + ) + } + } + } +} - if (homeUiState.showWidgets) { - Spacer(modifier = Modifier.height(32.dp)) - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Text13Up( - stringResource(R.string.widgets__widgets), - color = Colors.White64 - ) - - IconButton( - onClick = onClickEditWidgetList, - modifier = Modifier.testTag("WidgetsEdit") - ) { - Icon( - painter = when (homeUiState.isEditingWidgets) { - true -> painterResource(R.drawable.ic_check) - else -> painterResource(R.drawable.ic_sort_ascending) - }, - contentDescription = null, - ) - } - } +@Suppress("MagicNumber") +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun WalletPage( + isRefreshing: Boolean, + homeUiState: HomeUiState, + latestActivities: List?, + balances: BalanceState, + rootNavController: NavController, + walletNavController: NavController, + onRefresh: () -> Unit, +) { + val heightStatusBar = WindowInsets.statusBars.asPaddingValues().calculateTopPadding() + val pullToRefreshState = rememberPullToRefreshState() + val hasActivity = !latestActivities.isNullOrEmpty() - Spacer(modifier = Modifier.height(16.dp)) - - if (homeUiState.isEditingWidgets) { - DragDropColumn( - items = homeUiState.widgetsWithPosition, - onMove = onMoveWidget, - modifier = Modifier.fillMaxWidth() - ) { widgetWithPosition, isDragging -> - DragAndDropWidget( - iconRes = widgetWithPosition.type.iconRes, - title = stringResource(widgetWithPosition.type.title), - onClickSettings = { onClickEditWidget(widgetWithPosition.type) }, - onClickDelete = { onClickDeleteWidget(widgetWithPosition.type) }, - modifier = Modifier - .fillMaxWidth() - .graphicsLayer { - alpha = if (isDragging) 0.8f else 1.0f - } - ) - } - } else { - Widgets(homeUiState) - } + PullToRefreshBox( + state = pullToRefreshState, + isRefreshing = isRefreshing, + onRefresh = onRefresh, + indicator = { + Indicator( + isRefreshing = isRefreshing, + state = pullToRefreshState, + modifier = Modifier + .padding(top = heightStatusBar) + .align(Alignment.TopCenter) + ) + }, + modifier = Modifier.fillMaxSize() + ) { + Column( + modifier = Modifier + .padding(horizontal = 16.dp) + .fillMaxSize() + .verticalScroll(rememberScrollState()) + .testTag("HomeScrollView") + ) { + StatusBarSpacer() + TopBarSpacer() + VerticalSpacer(16.dp) + + BalanceHeaderView( + sats = balances.totalSats.toLong(), + showEyeIcon = true, + testTag = "TotalBalance", + modifier = Modifier + .fillMaxWidth() + .testTag("TotalBalance") + ) - Spacer(modifier = Modifier.height(32.dp)) - TertiaryButton( - text = stringResource(R.string.widgets__add), - icon = { - Icon( - painter = painterResource(R.drawable.ic_plus), - contentDescription = null, - tint = Colors.White80, - ) - }, - onClick = onClickAddWidget, - modifier = Modifier.testTag("WidgetsAdd") - ) - } - Spacer(modifier = Modifier.height(32.dp)) + if (!homeUiState.showEmptyState) { + VerticalSpacer(32.dp) + Row( + modifier = Modifier + .fillMaxWidth() + .height(IntrinsicSize.Min) + ) { + WalletBalanceView( + title = stringResource(R.string.wallet__savings__title), + sats = balances.totalOnchainSats.toLong(), + icon = painterResource(id = R.drawable.ic_btc_circle), + modifier = Modifier + .clickableAlpha { walletNavController.navigate(Routes.Savings) } + .padding(vertical = 4.dp) + .testTag("ActivitySavings") + ) + VerticalDivider() + HorizontalSpacer(16.dp) + WalletBalanceView( + title = stringResource(R.string.wallet__spending__title), + sats = balances.totalLightningSats.toLong(), + icon = painterResource(id = R.drawable.ic_ln_circle), + modifier = Modifier + .clickableAlpha { walletNavController.navigate(Routes.Spending) } + .padding(vertical = 4.dp) + .testTag("ActivitySpending") + ) + } + if (hasActivity) { AnimatedVisibility(homeUiState.banners.isNotEmpty()) { Column( verticalArrangement = Arrangement.spacedBy(16.dp), modifier = Modifier .fillMaxWidth() - .padding(bottom = 18.dp) + .padding(top = 32.dp, bottom = 18.dp) ) { homeUiState.banners.forEach { banner -> ActivityBanner( @@ -474,7 +453,9 @@ private fun Content( icon = banner.icon, onClick = { when (banner) { - ActivityBannerType.SPENDING -> rootNavController.navigate(Routes.SettingUp) + ActivityBannerType.SPENDING -> + rootNavController.navigate(Routes.SettingUp) + ActivityBannerType.SAVINGS -> Unit } }, @@ -484,23 +465,141 @@ private fun Content( } } + VerticalSpacer(32.dp) + ActivityListSimple( items = latestActivities, onAllActivityClick = { rootNavController.navigateToAllActivity() }, onActivityItemClick = { rootNavController.navigateToActivityItem(it) }, - onEmptyActivityRowClick = onClickEmptyActivityRow, ) - VerticalSpacer(150.dp) // scrollable empty space behind footer + FillHeight() + + if (homeUiState.showWidgetsOnboardingHint) { + WidgetsOnboardingHint() + } } + + VerticalSpacer(TAB_BAR_HEIGHT.dp + TAB_BAR_PADDING_BOTTOM.dp + 36.dp + Insets.Bottom) + } + } + + if (homeUiState.showEmptyState) { + EmptyStateView( + text = stringResource(R.string.onboarding__empty_wallet).withAccent(), + modifier = Modifier + .align(Alignment.BottomCenter) + ) + } + } +} + +@Suppress("MagicNumber") +@Composable +private fun WidgetsPage( + homeUiState: HomeUiState, + onRemoveSuggestion: (Suggestion) -> Unit, + onClickSuggestion: (Suggestion) -> Unit, + onClickAddWidget: () -> Unit, + onClickEditWidget: (WidgetType) -> Unit, + onClickDeleteWidget: (WidgetType) -> Unit, + onMoveWidget: (Int, Int) -> Unit, +) { + Column( + modifier = Modifier + .padding(horizontal = 16.dp) + .fillMaxSize() + .verticalScroll(rememberScrollState()) + ) { + StatusBarSpacer() + TopBarSpacer() + VerticalSpacer(16.dp) + + if (homeUiState.isEditingWidgets) { + DragDropColumn( + items = homeUiState.widgetsWithPosition, + onMove = onMoveWidget, + modifier = Modifier.fillMaxWidth() + ) { widgetWithPosition, isDragging -> + DragAndDropWidget( + iconRes = widgetWithPosition.type.iconRes, + title = stringResource(widgetWithPosition.type.title), + onClickSettings = { onClickEditWidget(widgetWithPosition.type) }, + onClickDelete = { onClickDeleteWidget(widgetWithPosition.type) }, + modifier = Modifier + .fillMaxWidth() + .graphicsLayer { + alpha = if (isDragging) 0.8f else 1.0f + } + ) } - if (homeUiState.showEmptyState) { - EmptyStateView( - text = stringResource(R.string.onboarding__empty_wallet).withAccent(), - onClose = onDismissEmptyState, + } else { + Widgets( + homeUiState = homeUiState, + onRemoveSuggestion = onRemoveSuggestion, + onClickSuggestion = onClickSuggestion, + ) + } + + VerticalSpacer(32.dp) + + TertiaryButton( + text = stringResource(R.string.widgets__add), + icon = { + Icon( + painter = painterResource(R.drawable.ic_plus), + contentDescription = null, + tint = Colors.White80, + ) + }, + onClick = onClickAddWidget, + modifier = Modifier.testTag("WidgetsAdd") + ) + + VerticalSpacer(150.dp) + } +} + +@Composable +private fun SuggestionsSection( + suggestions: List, + onRemoveSuggestion: (Suggestion) -> Unit, + onClickSuggestion: (Suggestion) -> Unit, + modifier: Modifier = Modifier, +) { + val rows = (suggestions.size + 1) / 2 + + BoxWithConstraints(modifier = modifier.fillMaxWidth()) { + val cardSize = (maxWidth - 16.dp) / 2 + val gridHeight = (cardSize * rows) + (16.dp * (rows - 1)) + LazyVerticalGrid( + columns = GridCells.Fixed(2), + horizontalArrangement = Arrangement.spacedBy(16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp), + userScrollEnabled = false, + modifier = Modifier + .fillMaxWidth() + .height(gridHeight) + .testTag("Suggestions") + ) { + items( + items = suggestions, + key = { it.name } + ) { item -> + SuggestionCard( + gradientColor = item.color, + title = stringResource(item.title), + description = stringResource(item.description), + icon = item.icon, + onClose = { onRemoveSuggestion(item) }.takeIf { item.dismissible }, + onClick = { onClickSuggestion(item) }, modifier = Modifier - .align(Alignment.BottomCenter) - .padding(bottom = 24.dp) + .testTag("Suggestion-${item.name.lowercase()}") + .animateItem( + fadeInSpec = tween(durationMillis = ANIMATION_DURATION_MS), + fadeOutSpec = tween(durationMillis = ANIMATION_DURATION_MS), + placementSpec = tween(durationMillis = ANIMATION_DURATION_MS), + ) ) } } @@ -508,7 +607,33 @@ private fun Content( } @Composable -private fun Widgets(homeUiState: HomeUiState) { +private fun WidgetsOnboardingHint(modifier: Modifier = Modifier) { + Row( + verticalAlignment = Alignment.Bottom, + modifier = modifier + .fillMaxWidth() + .padding(top = 32.dp) + ) { + Headline24( + text = stringResource(R.string.widgets__onboarding_swipe).withAccent(), + modifier = Modifier.weight(1f) + ) + HorizontalSpacer(16.dp) + Image( + painter = painterResource(R.drawable.swipe_instruction), + contentDescription = null, + modifier = Modifier.padding(bottom = 12.dp) + ) + } +} + +@Suppress("CyclomaticComplexMethod") +@Composable +private fun Widgets( + homeUiState: HomeUiState, + onRemoveSuggestion: (Suggestion) -> Unit, + onClickSuggestion: (Suggestion) -> Unit, +) { Column( modifier = Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(16.dp) @@ -599,6 +724,16 @@ private fun Widgets(homeUiState: HomeUiState) { ) } } + + WidgetType.SUGGESTIONS -> { + if (homeUiState.suggestions.isNotEmpty()) { + SuggestionsSection( + suggestions = homeUiState.suggestions, + onRemoveSuggestion = onRemoveSuggestion, + onClickSuggestion = onClickSuggestion, + ) + } + } } } } @@ -611,6 +746,9 @@ private fun TopBar( rootNavController: NavController, scope: CoroutineScope, drawerState: DrawerState, + showEditWidgets: Boolean = false, + isEditingWidgets: Boolean = false, + onClickEditWidgetList: () -> Unit = {}, ) { val topbarGradient = Brush.verticalGradient( colorStops = arrayOf( @@ -631,14 +769,32 @@ private fun TopBar( TopAppBar( title = {}, actions = { - AppStatus(onClick = { rootNavController.navigate(Routes.AppStatus) }) - HorizontalSpacer(4.dp) + AnimatedVisibility(showEditWidgets) { + IconButton( + onClick = onClickEditWidgetList, + modifier = Modifier.testTag("WidgetsEdit") + ) { + Icon( + painter = if (isEditingWidgets) { + painterResource(R.drawable.ic_check) + } else { + painterResource(R.drawable.ic_edit) + }, + tint = Colors.White, + contentDescription = null, + ) + } + } + AppStatus( + onClick = { rootNavController.navigate(Routes.AppStatus) }, + ) IconButton( onClick = { scope.launch { drawerState.open() } }, modifier = Modifier.testTag("HeaderMenu") ) { Icon( painter = painterResource(R.drawable.ic_list), + tint = Colors.White, contentDescription = stringResource(R.string.settings__settings), ) } diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/HomeUiState.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/HomeUiState.kt index 7b086e64e..61530826f 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/HomeUiState.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/HomeUiState.kt @@ -36,4 +36,6 @@ data class HomeUiState( val isEditingWidgets: Boolean = false, val deleteWidgetAlert: WidgetType? = null, val showEmptyState: Boolean = false, + val currentPage: Int = 0, + val showWidgetsOnboardingHint: Boolean = false, ) diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/HomeViewModel.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/HomeViewModel.kt index 5410ad6e3..f28a3e764 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/HomeViewModel.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/HomeViewModel.kt @@ -27,6 +27,7 @@ import to.bitkit.ui.screens.widgets.blocks.toWeatherModel import javax.inject.Inject import kotlin.time.Duration.Companion.seconds +@Suppress("TooManyFunctions") @HiltViewModel class HomeViewModel @Inject constructor( private val walletRepo: WalletRepo, @@ -71,6 +72,8 @@ class HomeViewModel @Inject constructor( currentBlock = widgetsData.block?.toBlockModel(), currentWeather = widgetsData.weather?.toWeatherModel(), currentPrice = widgetsData.price, + showWidgetsOnboardingHint = settings.showWidgets && + !settings.widgetsOnboardingHintDismissed, ) }.collect { newState -> _uiState.update { newState } @@ -80,10 +83,15 @@ class HomeViewModel @Inject constructor( viewModelScope.launch { combine( settingsStore.data, - walletRepo.balanceState - ) { settings, balanceState -> + walletRepo.balanceState, + transferRepo.activeTransfers, + ) { settings, balanceState, activeTransfers -> _uiState.value.copy( - showEmptyState = settings.showEmptyBalanceView && balanceState.totalSats == 0uL + showEmptyState = settings.showEmptyBalanceView && + balanceState.totalSats == 0uL && + balanceState.balanceInTransferToSpending == 0uL && + balanceState.balanceInTransferToSavings == 0uL && + activeTransfers.isEmpty() ) }.collect { newState -> _uiState.update { newState } @@ -142,6 +150,16 @@ class HomeViewModel @Inject constructor( _currentFact.value = null } + fun onPageChanged(page: Int) { + _uiState.update { it.copy(currentPage = page) } + } + + fun dismissWidgetsOnboardingHint() { + viewModelScope.launch { + settingsStore.update { it.copy(widgetsOnboardingHintDismissed = true) } + } + } + fun dismissEmptyState() { viewModelScope.launch { settingsStore.update { it.copy(showEmptyBalanceView = false) } @@ -279,6 +297,10 @@ class HomeViewModel @Inject constructor( } } val dismissedList = settings.dismissedSuggestions.mapNotNull { it.toSuggestionOrNull() } - baseSuggestions.filterNot { it in dismissedList } + baseSuggestions.filterNot { it in dismissedList }.take(MAX_SUGGESTIONS) + } + + companion object { + private const val MAX_SUGGESTIONS = 4 } } diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityListSimple.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityListSimple.kt index cc476b42a..bb5c9d4ab 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityListSimple.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityListSimple.kt @@ -23,28 +23,23 @@ fun ActivityListSimple( items: List?, onAllActivityClick: () -> Unit, onActivityItemClick: (String) -> Unit, - onEmptyActivityRowClick: () -> Unit, ) { + if (items.isNullOrEmpty()) return Column( horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.fillMaxWidth() ) { - if (items != null && items.isNotEmpty()) { - items.forEachIndexed { index, item -> - ActivityRow(item, onActivityItemClick, testTag = "ActivityShort-$index") - VerticalSpacer(16.dp) - } - TertiaryButton( - text = stringResource(R.string.wallet__activity_show_all), - onClick = onAllActivityClick, - modifier = Modifier - .wrapContentWidth() - .padding(top = 8.dp) - .testTag("ActivityShowAll") - ) - } else { - EmptyActivityRow(onClick = onEmptyActivityRowClick) + items.forEachIndexed { index, item -> + ActivityRow(item, onActivityItemClick, testTag = "ActivityShort-$index") + VerticalSpacer(16.dp) } + TertiaryButton( + text = stringResource(R.string.wallet__activity_show_all), + onClick = onAllActivityClick, + modifier = Modifier + .wrapContentWidth() + .testTag("ActivityShowAll") + ) } } @@ -56,20 +51,6 @@ private fun Preview() { items = previewActivityItems, onAllActivityClick = {}, onActivityItemClick = {}, - onEmptyActivityRowClick = {}, - ) - } -} - -@Preview -@Composable -private fun PreviewEmpty() { - AppThemeSurface { - ActivityListSimple( - items = emptyList(), - onAllActivityClick = {}, - onActivityItemClick = {}, - onEmptyActivityRowClick = {}, ) } } diff --git a/app/src/main/java/to/bitkit/ui/screens/widgets/AddWidgetsScreen.kt b/app/src/main/java/to/bitkit/ui/screens/widgets/AddWidgetsScreen.kt index 300d8a5d3..4c37a8008 100644 --- a/app/src/main/java/to/bitkit/ui/screens/widgets/AddWidgetsScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/widgets/AddWidgetsScreen.kt @@ -90,6 +90,15 @@ fun AddWidgetsScreen( onClick = { onWidgetSelected(WidgetType.CALCULATOR) }, modifier = Modifier.testTag("WidgetListItem-calculator") ) + SettingsButtonRow( + title = stringResource(R.string.widgets__suggestions__name), + subtitle = stringResource(R.string.widgets__suggestions__description), + iconRes = R.drawable.widget_suggestions, + iconSize = 48.dp, + maxLinesSubtitle = 1, + onClick = { onWidgetSelected(WidgetType.SUGGESTIONS) }, + modifier = Modifier.testTag("WidgetListItem-suggestions") + ) } } } @@ -101,7 +110,7 @@ private fun Preview() { AddWidgetsScreen( onWidgetSelected = {}, fiatSymbol = "$", - onBackCLick = {} + onBackCLick = {}, ) } } diff --git a/app/src/main/java/to/bitkit/ui/screens/widgets/suggestions/SuggestionsPreviewScreen.kt b/app/src/main/java/to/bitkit/ui/screens/widgets/suggestions/SuggestionsPreviewScreen.kt new file mode 100644 index 000000000..3c9e73d76 --- /dev/null +++ b/app/src/main/java/to/bitkit/ui/screens/widgets/suggestions/SuggestionsPreviewScreen.kt @@ -0,0 +1,191 @@ +package to.bitkit.ui.screens.widgets.suggestions + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.items +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import to.bitkit.R +import to.bitkit.models.Suggestion +import to.bitkit.ui.components.BodyM +import to.bitkit.ui.components.FillHeight +import to.bitkit.ui.components.Headline +import to.bitkit.ui.components.PrimaryButton +import to.bitkit.ui.components.SecondaryButton +import to.bitkit.ui.components.SuggestionCard +import to.bitkit.ui.components.Text13Up +import to.bitkit.ui.components.VerticalSpacer +import to.bitkit.ui.scaffold.AppTopBar +import to.bitkit.ui.scaffold.DrawerNavIcon +import to.bitkit.ui.scaffold.ScreenColumn +import to.bitkit.ui.theme.AppThemeSurface +import to.bitkit.ui.theme.Colors + +private val previewSuggestions = listOf(Suggestion.BUY, Suggestion.BACK_UP) + +@Composable +fun SuggestionsPreviewScreen( + suggestionsViewModel: SuggestionsViewModel, + onClose: () -> Unit, + onBack: () -> Unit, +) { + val isSuggestionsWidgetEnabled by suggestionsViewModel.isSuggestionsWidgetEnabled + .collectAsStateWithLifecycle() + + Content( + onBack = onBack, + isSuggestionsWidgetEnabled = isSuggestionsWidgetEnabled, + onClickDelete = { + suggestionsViewModel.removeWidget() + onClose() + }, + onClickSave = { + suggestionsViewModel.addWidget() + onClose() + }, + ) +} + +@Composable +private fun Content( + onBack: () -> Unit, + isSuggestionsWidgetEnabled: Boolean, + onClickDelete: () -> Unit, + onClickSave: () -> Unit, +) { + ScreenColumn { + AppTopBar( + titleText = stringResource(R.string.widgets__widget__nav_title), + onBackClick = onBack, + actions = { DrawerNavIcon() }, + ) + + Column( + modifier = Modifier.padding(horizontal = 16.dp) + ) { + VerticalSpacer(26.dp) + + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + Headline( + text = AnnotatedString(stringResource(R.string.widgets__suggestions__name)), + modifier = Modifier.width(200.dp) + ) + Icon( + painter = painterResource(R.drawable.widget_suggestions), + contentDescription = null, + tint = Color.Unspecified, + modifier = Modifier.size(64.dp) + ) + } + + BodyM( + text = stringResource(R.string.widgets__suggestions__description), + color = Colors.White64, + modifier = Modifier.padding(vertical = 16.dp) + ) + + HorizontalDivider() + + FillHeight() + + Text13Up( + stringResource(R.string.common__preview), + color = Colors.White64, + modifier = Modifier.padding(vertical = 16.dp) + ) + + LazyVerticalGrid( + columns = GridCells.Fixed(2), + horizontalArrangement = Arrangement.spacedBy(16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp), + userScrollEnabled = false, + modifier = Modifier.fillMaxWidth() + ) { + items( + items = previewSuggestions, + key = { it.name } + ) { item -> + SuggestionCard( + gradientColor = item.color, + title = stringResource(item.title), + description = stringResource(item.description), + icon = item.icon, + disableGlow = true, + onClick = {}, + ) + } + } + + Row( + modifier = Modifier + .padding(vertical = 21.dp) + .fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(16.dp) + ) { + if (isSuggestionsWidgetEnabled) { + SecondaryButton( + text = stringResource(R.string.common__delete), + fullWidth = false, + onClick = onClickDelete, + modifier = Modifier.weight(1f), + ) + } + + PrimaryButton( + text = stringResource(R.string.common__save), + fullWidth = false, + onClick = onClickSave, + modifier = Modifier.weight(1f), + ) + } + } + } +} + +@Preview(showBackground = true) +@Composable +private fun Preview() { + AppThemeSurface { + Content( + onBack = {}, + isSuggestionsWidgetEnabled = false, + onClickDelete = {}, + onClickSave = {}, + ) + } +} + +@Preview(showBackground = true) +@Composable +private fun PreviewWithDelete() { + AppThemeSurface { + Content( + onBack = {}, + isSuggestionsWidgetEnabled = true, + onClickDelete = {}, + onClickSave = {}, + ) + } +} diff --git a/app/src/main/java/to/bitkit/ui/screens/widgets/suggestions/SuggestionsViewModel.kt b/app/src/main/java/to/bitkit/ui/screens/widgets/suggestions/SuggestionsViewModel.kt new file mode 100644 index 000000000..afa8b5aa3 --- /dev/null +++ b/app/src/main/java/to/bitkit/ui/screens/widgets/suggestions/SuggestionsViewModel.kt @@ -0,0 +1,44 @@ +package to.bitkit.ui.screens.widgets.suggestions + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch +import to.bitkit.models.WidgetType +import to.bitkit.repositories.WidgetsRepo +import javax.inject.Inject + +@HiltViewModel +class SuggestionsViewModel @Inject constructor( + private val widgetsRepo: WidgetsRepo, +) : ViewModel() { + + companion object { + private const val SUBSCRIBE_TIMEOUT = 5000L + } + + val isSuggestionsWidgetEnabled: StateFlow = widgetsRepo.widgetsDataFlow + .map { widgetsData -> + widgetsData.widgets.any { it.type == WidgetType.SUGGESTIONS } + } + .stateIn(viewModelScope, SharingStarted.WhileSubscribed(SUBSCRIBE_TIMEOUT), false) + + val showWidgetTitles: StateFlow = widgetsRepo.showWidgetTitles + .stateIn(viewModelScope, SharingStarted.WhileSubscribed(SUBSCRIBE_TIMEOUT), false) + + fun addWidget() { + viewModelScope.launch { + widgetsRepo.addWidget(WidgetType.SUGGESTIONS) + } + } + + fun removeWidget() { + viewModelScope.launch { + widgetsRepo.deleteWidget(WidgetType.SUGGESTIONS) + } + } +} diff --git a/app/src/main/res/drawable/ic_edit.xml b/app/src/main/res/drawable/ic_edit.xml new file mode 100644 index 000000000..bd9ebb0c4 --- /dev/null +++ b/app/src/main/res/drawable/ic_edit.xml @@ -0,0 +1,23 @@ + + + + + + diff --git a/app/src/main/res/drawable/swipe_instruction.xml b/app/src/main/res/drawable/swipe_instruction.xml new file mode 100644 index 000000000..6ec67c2b2 --- /dev/null +++ b/app/src/main/res/drawable/swipe_instruction.xml @@ -0,0 +1,31 @@ + + + + + + + diff --git a/app/src/main/res/drawable/widget_suggestions.xml b/app/src/main/res/drawable/widget_suggestions.xml new file mode 100644 index 000000000..f5ce0bbdd --- /dev/null +++ b/app/src/main/res/drawable/widget_suggestions.xml @@ -0,0 +1,22 @@ + + + + + + diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 7bad49966..1ce6686ad 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -796,7 +796,7 @@ أُزيل من Mempool مُرسل أُرسل لنفسي - عرض كل النشاط + عرض الكل الحالة ناجح الكل diff --git a/app/src/main/res/values-b+es+419/strings.xml b/app/src/main/res/values-b+es+419/strings.xml index 16dcbef26..a619d6363 100644 --- a/app/src/main/res/values-b+es+419/strings.xml +++ b/app/src/main/res/values-b+es+419/strings.xml @@ -796,7 +796,7 @@ Eliminado de Mempool Enviado Enviado a mí mismo - Mostrar toda la actividad + Mostrar todo Estatus Éxito Todas diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index cb5e4ad8c..79d34ae77 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -796,7 +796,7 @@ Eliminat de la Mempool Enviat Enviat a mi mateix - Mostra tota l\'activitat + Mostra-ho tot Estat Exitós Tots diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 7182c5530..48fc95e61 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -796,7 +796,7 @@ Odebráno z Mempoolu Posláno Posláno sobě - Zobrazit veškerou aktivitu + Zobrazit vše Status Úspěch Vše diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 15d9fa57e..949139b26 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -866,7 +866,7 @@ Du wirst empfangen MINIMUM Aktivität - Alle Aktivitäten anzeigen + Alle anzeigen Noch keine Aktivitäten Empfange einige Gelder, um zu starten Gesendet diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index f5f597595..d5cde34c4 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -796,7 +796,7 @@ Αφαιρέθηκε από το Mempool Στάλθηκε Στάλθηκε στον εαυτό μου - Εμφάνιση όλης της δραστηριότητας + Εμφάνιση όλων Κατάσταση Επιτυχής Όλα diff --git a/app/src/main/res/values-es-rES/strings.xml b/app/src/main/res/values-es-rES/strings.xml index 531548579..243569e5d 100644 --- a/app/src/main/res/values-es-rES/strings.xml +++ b/app/src/main/res/values-es-rES/strings.xml @@ -796,7 +796,7 @@ Eliminado De Mempool Enviado Enviado a mí mismo - Mostrar toda la actividad + Mostrar todo Estatus Éxito Todas diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index e83746275..5d6ad056e 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -861,7 +861,7 @@ Recibirás MÍNIMO Actividad - Mostrar toda la actividad + Mostrar todo Aún no hay actividad Reciba algunos fondos para empezar Enviado diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index e225932ff..6f8a88b7c 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -856,7 +856,7 @@ Vous recevrez MINIMUM Activité - Afficher toutes les activités + Tout afficher Pas encore d\'activité Recevoir des fonds pour démarrer Envoyé diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 4820186a7..fe1348e62 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -796,7 +796,7 @@ Rimossa dalla Mempool Inviato Inviato a me stesso - Visualizza tutte le attività + Mostra tutto Stato Successo Tutti diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index d013e28af..2ce90640d 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -796,7 +796,7 @@ Verwijderd uit mempool Verzonden Naar mezelf verzonden - Alle activiteit tonen + Alles tonen Status Succesvol Alles diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 5e471c82d..9a2d808e1 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -856,7 +856,7 @@ Otrzymasz MINIMUM Aktywność - Pokaż całą aktywność + Pokaż wszystko Nie ma jeszcze aktywności Otrzymaj trochę środków aby rozpocząć Wysłane diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 81514d997..304fd5e94 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -796,7 +796,7 @@ Removido da Mempool Enviado Enviado para mim mesmo - Mostrar Todas as Atividades + Mostrar Tudo Status Sucesso Todos diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 3183fda3e..f20ecd545 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -811,7 +811,7 @@ Você receberá MÍNIMO Atividade - Mostrar Todas as Atividades + Mostrar Tudo Nenhuma atividade ainda Receba alguns bitcoins para começar Enviado diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index cac54ff2f..2bfc91051 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -813,7 +813,7 @@ Удалено из мемпула Отправлено Отправлено себе - Показать Все + Показать все Статус Успешно Все diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0d080f226..00f732672 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -798,7 +798,7 @@ Removed from Mempool Sent Sent to myself - Show All Activity + Show All Status Successful All @@ -1000,4 +1000,7 @@ Widget Source Widgets + Swipe down\nto find your\n<accent>widgets</accent> + Discover everything Bitkit has to offer. + Bitkit Suggestions