From 24571465d46ac151442f3881fd666c7ca03a297a Mon Sep 17 00:00:00 2001 From: Ovi Trif Date: Wed, 14 May 2025 16:22:29 +0200 Subject: [PATCH 01/37] refactor: Move HomeActivityList to its own file --- .../bitkit/ui/screens/wallets/HomeScreen.kt | 2 +- .../wallets/activity/AllActivityScreen.kt | 126 ++++++------------ .../activity/components/HomeActivityList.kt | 74 ++++++++++ 3 files changed, 114 insertions(+), 88 deletions(-) create mode 100644 app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/HomeActivityList.kt 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 f6533e164..46a818d60 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 @@ -59,7 +59,7 @@ import to.bitkit.ui.navigateToTransferSavingsIntro import to.bitkit.ui.navigateToTransferSpendingAmount import to.bitkit.ui.navigateToTransferSpendingIntro import to.bitkit.ui.scaffold.AppScaffold -import to.bitkit.ui.screens.wallets.activity.HomeActivityList +import to.bitkit.ui.screens.wallets.activity.components.HomeActivityList import to.bitkit.ui.screens.wallets.activity.AllActivityScreen import to.bitkit.ui.screens.wallets.activity.DateRangeSelectorSheet import to.bitkit.ui.screens.wallets.activity.TagSelectorSheet diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt index c3db0073b..d83abda6a 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt @@ -137,35 +137,6 @@ fun ActivityListWithHeaders( } } -@Composable -fun HomeActivityList( - items: List?, - onAllActivityClick: () -> Unit, - onActivityItemClick: (String) -> Unit, - onEmptyActivityRowClick: () -> Unit, -) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier.fillMaxWidth() - ) { - if (items != null && items.isNotEmpty()) { - items.forEach { item -> - ActivityRow(item, onActivityItemClick) - HorizontalDivider() - } - TertiaryButton( - text = stringResource(R.string.wallet__activity_show_all), - onClick = onAllActivityClick, - modifier = Modifier - .wrapContentWidth() - .padding(top = 8.dp) - ) - } else { - EmptyActivityRow(onClick = onEmptyActivityRowClick) - } - } -} - // region utils private fun groupActivityItems(activityItems: List): List { val now = Instant.now() @@ -246,41 +217,18 @@ private fun PreviewActivityListWithHeadersView() { } } -@Preview -@Composable -private fun PreviewActivityListItems() { - AppThemeSurface { - HomeActivityList( - testActivityItems, - onAllActivityClick = {}, - onActivityItemClick = {}, - onEmptyActivityRowClick = {}, - ) - } -} -@Preview -@Composable -private fun PreviewActivityListEmpty() { - AppThemeSurface { - HomeActivityList( - items = emptyList(), - onAllActivityClick = {}, - onActivityItemClick = {}, - onEmptyActivityRowClick = {}, - ) - } -} +val testActivityItems = buildList { + val today: Calendar = Calendar.getInstance() + val yesterday: Calendar = Calendar.getInstance().apply { add(Calendar.DATE, -1) } + val thisWeek: Calendar = Calendar.getInstance().apply { add(Calendar.DATE, -3) } + val thisMonth: Calendar = Calendar.getInstance().apply { add(Calendar.DATE, -10) } + val lastYear: Calendar = Calendar.getInstance().apply { add(Calendar.YEAR, -1) } -private val today: Calendar = Calendar.getInstance() -private val yesterday: Calendar = Calendar.getInstance().apply { add(Calendar.DATE, -1) } -private val thisWeek: Calendar = Calendar.getInstance().apply { add(Calendar.DATE, -3) } -private val thisMonth: Calendar = Calendar.getInstance().apply { add(Calendar.DATE, -10) } -private val lastYear: Calendar = Calendar.getInstance().apply { add(Calendar.YEAR, -1) } + fun Calendar.epochSecond() = (timeInMillis / 1000).toULong() -val testActivityItems: List = listOf( // Today - Activity.Onchain( + add(Activity.Onchain( OnchainActivity( id = "1", txType = PaymentType.RECEIVED, @@ -290,19 +238,20 @@ val testActivityItems: List = listOf( feeRate = 1_u, address = "bc1", confirmed = true, - timestamp = today.timeInMillis.toULong() / 1000u, + timestamp = today.epochSecond(), isBoosted = false, isTransfer = true, doesExist = true, - confirmTimestamp = today.timeInMillis.toULong() / 1000u, + confirmTimestamp = today.epochSecond(), channelId = "channelId", transferTxId = "transferTxId", - createdAt = today.timeInMillis.toULong() / 1000u, - updatedAt = today.timeInMillis.toULong() / 1000u, + createdAt = today.epochSecond(), + updatedAt = today.epochSecond(), ) - ), + )) + // Yesterday - Activity.Lightning( + add(Activity.Lightning( LightningActivity( id = "2", txType = PaymentType.SENT, @@ -311,14 +260,15 @@ val testActivityItems: List = listOf( fee = 15_u, invoice = "lnbc2", message = "Custom message", - timestamp = yesterday.timeInMillis.toULong() / 1000u, + timestamp = yesterday.epochSecond(), preimage = "preimage1", - createdAt = yesterday.timeInMillis.toULong() / 1000u, - updatedAt = yesterday.timeInMillis.toULong() / 1000u, + createdAt = yesterday.epochSecond(), + updatedAt = yesterday.epochSecond(), ) - ), + )) + // This Week - Activity.Lightning( + add(Activity.Lightning( LightningActivity( id = "3", txType = PaymentType.RECEIVED, @@ -327,14 +277,15 @@ val testActivityItems: List = listOf( fee = 17_u, invoice = "lnbc3", message = "", - timestamp = thisWeek.timeInMillis.toULong() / 1000u, + timestamp = thisWeek.epochSecond(), preimage = "preimage2", - createdAt = thisWeek.timeInMillis.toULong() / 1000u, - updatedAt = thisWeek.timeInMillis.toULong() / 1000u, + createdAt = thisWeek.epochSecond(), + updatedAt = thisWeek.epochSecond(), ) - ), + )) + // This Month - Activity.Onchain( + add(Activity.Onchain( OnchainActivity( id = "4", txType = PaymentType.RECEIVED, @@ -344,19 +295,20 @@ val testActivityItems: List = listOf( feeRate = 1_u, address = "bc1", confirmed = false, - timestamp = thisMonth.timeInMillis.toULong() / 1000u, + timestamp = thisMonth.epochSecond(), isBoosted = false, isTransfer = true, doesExist = true, - confirmTimestamp = (today.timeInMillis + 3600_000).toULong() / 1000u, + confirmTimestamp = today.epochSecond() + 3600u, channelId = "channelId", transferTxId = "transferTxId", - createdAt = thisMonth.timeInMillis.toULong() / 1000u, - updatedAt = thisMonth.timeInMillis.toULong() / 1000u, + createdAt = thisMonth.epochSecond(), + updatedAt = thisMonth.epochSecond(), ) - ), + )) + // Last Year - Activity.Lightning( + add(Activity.Lightning( LightningActivity( id = "5", txType = PaymentType.SENT, @@ -365,11 +317,11 @@ val testActivityItems: List = listOf( fee = 1_u, invoice = "lnbc…", message = "", - timestamp = (lastYear.timeInMillis.toULong() / 1000u), + timestamp = lastYear.epochSecond(), preimage = null, - createdAt = (lastYear.timeInMillis.toULong() / 1000u), - updatedAt = (lastYear.timeInMillis.toULong() / 1000u), + createdAt = lastYear.epochSecond(), + updatedAt = lastYear.epochSecond(), ) - ), -) + )) +} // endregion diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/HomeActivityList.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/HomeActivityList.kt new file mode 100644 index 000000000..c7c5cb08f --- /dev/null +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/HomeActivityList.kt @@ -0,0 +1,74 @@ +package to.bitkit.ui.screens.wallets.activity.components + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.material3.HorizontalDivider +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import to.bitkit.R +import to.bitkit.ui.components.TertiaryButton +import to.bitkit.ui.screens.wallets.activity.testActivityItems +import to.bitkit.ui.theme.AppThemeSurface +import uniffi.bitkitcore.Activity + +@Composable +fun HomeActivityList( + items: List?, + onAllActivityClick: () -> Unit, + onActivityItemClick: (String) -> Unit, + onEmptyActivityRowClick: () -> Unit, +) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.fillMaxWidth() + ) { + if (items != null && items.isNotEmpty()) { + items.forEach { item -> + ActivityRow(item, onActivityItemClick) + HorizontalDivider() + } + TertiaryButton( + text = stringResource(R.string.wallet__activity_show_all), + onClick = onAllActivityClick, + modifier = Modifier + .wrapContentWidth() + .padding(top = 8.dp) + ) + } else { + EmptyActivityRow(onClick = onEmptyActivityRowClick) + } + } +} + + +@Preview +@Composable +private fun Preview() { + AppThemeSurface { + HomeActivityList( + testActivityItems, + onAllActivityClick = {}, + onActivityItemClick = {}, + onEmptyActivityRowClick = {}, + ) + } +} + +@Preview +@Composable +private fun PreviewEmpty() { + AppThemeSurface { + HomeActivityList( + items = emptyList(), + onAllActivityClick = {}, + onActivityItemClick = {}, + onEmptyActivityRowClick = {}, + ) + } +} From 2f78b802d0afbd38dab4d153bda917aa1db4b900 Mon Sep 17 00:00:00 2001 From: Ovi Trif Date: Wed, 14 May 2025 16:29:43 +0200 Subject: [PATCH 02/37] fix: Refresh activityListViewModel tags after adding tag --- .../bitkit/ui/screens/wallets/activity/ActivityDetailScreen.kt | 1 + .../screens/wallets/activity/components/ActivityAddTagSheet.kt | 3 +++ app/src/main/java/to/bitkit/viewmodels/ActivityViewModel.kt | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/ActivityDetailScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/ActivityDetailScreen.kt index be576c714..fada4ebf8 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/ActivityDetailScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/ActivityDetailScreen.kt @@ -111,6 +111,7 @@ fun ActivityDetailScreen( ) if (showAddTagSheet) { ActivityAddTagSheet( + listViewModel = listViewModel, activityViewModel = detailViewModel, onDismiss = { showAddTagSheet = false }, ) diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityAddTagSheet.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityAddTagSheet.kt index 330263d6c..1ef7c34d3 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityAddTagSheet.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityAddTagSheet.kt @@ -20,11 +20,13 @@ import to.bitkit.ui.shared.util.gradientBackground import to.bitkit.ui.theme.AppShapes import to.bitkit.ui.theme.Colors import to.bitkit.viewmodels.ActivityDetailViewModel +import to.bitkit.viewmodels.ActivityListViewModel import to.bitkit.viewmodels.TagsViewModel @OptIn(ExperimentalMaterial3Api::class) @Composable fun ActivityAddTagSheet( + listViewModel: ActivityListViewModel, activityViewModel: ActivityDetailViewModel, tagsViewModel: TagsViewModel = hiltViewModel(), onDismiss: () -> Unit, @@ -38,6 +40,7 @@ fun ActivityAddTagSheet( DisposableEffect(Unit) { onDispose { + listViewModel.updateAvailableTags() tagsViewModel.onInputUpdated("") } } diff --git a/app/src/main/java/to/bitkit/viewmodels/ActivityViewModel.kt b/app/src/main/java/to/bitkit/viewmodels/ActivityViewModel.kt index 83498e487..addb800e8 100644 --- a/app/src/main/java/to/bitkit/viewmodels/ActivityViewModel.kt +++ b/app/src/main/java/to/bitkit/viewmodels/ActivityViewModel.kt @@ -139,7 +139,7 @@ class ActivityListViewModel @Inject constructor( } } - private fun updateAvailableTags() { + fun updateAvailableTags() { viewModelScope.launch { try { _availableTags.value = coreService.activity.allPossibleTags() From 8d28540e5799946cda796418430f6630aa36a645 Mon Sep 17 00:00:00 2001 From: Ovi Trif Date: Wed, 14 May 2025 17:37:15 +0200 Subject: [PATCH 03/37] feat: Activity filter custom icons --- .../activity/components/ActivityListFilter.kt | 37 ++++++++----------- .../main/res/drawable/ic_magnifying_glass.xml | 9 +++++ 2 files changed, 25 insertions(+), 21 deletions(-) create mode 100644 app/src/main/res/drawable/ic_magnifying_glass.xml diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityListFilter.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityListFilter.kt index 1241e8c1a..9dd46d8a9 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityListFilter.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityListFilter.kt @@ -1,20 +1,16 @@ package to.bitkit.ui.screens.wallets.activity.components import androidx.compose.foundation.background -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.CalendarToday -import androidx.compose.material.icons.filled.Search -import androidx.compose.material.icons.filled.Tag import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Tab import androidx.compose.material3.TabRow import androidx.compose.material3.Text @@ -27,9 +23,11 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import to.bitkit.R +import to.bitkit.ui.shared.util.clickableAlpha import to.bitkit.ui.theme.AppTextFieldDefaults import to.bitkit.ui.theme.Colors import to.bitkit.viewmodels.ActivityListViewModel @@ -46,16 +44,17 @@ fun ActivityListFilter( Column { Row( + verticalAlignment = Alignment.CenterVertically, modifier = Modifier - .background(Colors.White10, RoundedCornerShape(32.dp)) + .background(Colors.White10, MaterialTheme.shapes.large) .padding(horizontal = 16.dp) - .fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically, + .fillMaxWidth() ) { Icon( - imageVector = Icons.Default.Search, + painter = painterResource(R.drawable.ic_magnifying_glass), contentDescription = null, tint = if (searchText.isNotEmpty()) Colors.Brand else Colors.White64, + modifier = Modifier.size(24.dp) ) Spacer(modifier = Modifier.width(8.dp)) @@ -63,33 +62,29 @@ fun ActivityListFilter( value = searchText, onValueChange = { viewModel.setSearchText(it) }, placeholder = { Text(text = stringResource(R.string.common__search)) }, - modifier = Modifier - .weight(1f) - .padding(0.dp), colors = AppTextFieldDefaults.transparent, + modifier = Modifier.weight(1f) ) Spacer(modifier = Modifier.width(12.dp)) Row { Icon( - imageVector = Icons.Default.Tag, + painter = painterResource(R.drawable.ic_tag), contentDescription = null, tint = if (selectedTags.isNotEmpty()) Colors.Brand else Colors.White64, modifier = Modifier - .clickable { - onTagClick() - } + .size(24.dp) + .clickableAlpha { onTagClick() } ) Spacer(modifier = Modifier.width(12.dp)) Icon( - imageVector = Icons.Default.CalendarToday, + painter = painterResource(R.drawable.ic_calendar), contentDescription = null, tint = if (startDate != null) Colors.Brand else Colors.White64, modifier = Modifier - .clickable { - onDateRangeClick() - } + .size(24.dp) + .clickableAlpha { onDateRangeClick() } ) } } diff --git a/app/src/main/res/drawable/ic_magnifying_glass.xml b/app/src/main/res/drawable/ic_magnifying_glass.xml new file mode 100644 index 000000000..6dea006b0 --- /dev/null +++ b/app/src/main/res/drawable/ic_magnifying_glass.xml @@ -0,0 +1,9 @@ + + + + + + + + + From 75beab2c3518ae3796359ddf3851d96c7116c405 Mon Sep 17 00:00:00 2001 From: Ovi Trif Date: Wed, 14 May 2025 18:16:39 +0200 Subject: [PATCH 04/37] refactor: Rename ActivityViewModel file to ActivityListViewModel --- .../viewmodels/{ActivityViewModel.kt => ActivityListViewModel.kt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename app/src/main/java/to/bitkit/viewmodels/{ActivityViewModel.kt => ActivityListViewModel.kt} (100%) diff --git a/app/src/main/java/to/bitkit/viewmodels/ActivityViewModel.kt b/app/src/main/java/to/bitkit/viewmodels/ActivityListViewModel.kt similarity index 100% rename from app/src/main/java/to/bitkit/viewmodels/ActivityViewModel.kt rename to app/src/main/java/to/bitkit/viewmodels/ActivityListViewModel.kt From bfe02801d85a409f36747eba026a479f3100a892 Mon Sep 17 00:00:00 2001 From: Ovi Trif Date: Wed, 14 May 2025 18:23:55 +0200 Subject: [PATCH 05/37] fix: Show 'no items' text when activity filter yields no results --- .../screens/wallets/activity/AllActivityScreen.kt | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt index d83abda6a..733087be2 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt @@ -23,6 +23,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import to.bitkit.R import to.bitkit.ui.appViewModel +import to.bitkit.ui.components.BodyM import to.bitkit.ui.components.BottomSheetType import to.bitkit.ui.components.TertiaryButton import to.bitkit.ui.scaffold.AppTopBar @@ -132,7 +133,17 @@ fun ActivityListWithHeaders( } } } else { - EmptyActivityRow(onClick = onEmptyActivityRowClick) + if (showFooter) { + // In Spending and Savings wallet + EmptyActivityRow(onClick = onEmptyActivityRowClick) + } else { + // On all activity screen when filtered list is empty + BodyM( + text = stringResource(R.string.wallet__activity_no), + color = Colors.White64, + modifier = Modifier.padding(16.dp) + ) + } } } } @@ -217,7 +228,6 @@ private fun PreviewActivityListWithHeadersView() { } } - val testActivityItems = buildList { val today: Calendar = Calendar.getInstance() val yesterday: Calendar = Calendar.getInstance().apply { add(Calendar.DATE, -1) } From 0d059d6ca5e7924b43b934770c0e11bcffadad1d Mon Sep 17 00:00:00 2001 From: Ovi Trif Date: Wed, 14 May 2025 18:55:53 +0200 Subject: [PATCH 06/37] fix: Hide keyboard when opening filter sheets --- .../activity/components/ActivityListFilter.kt | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityListFilter.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityListFilter.kt index 9dd46d8a9..3b351cc3a 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityListFilter.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityListFilter.kt @@ -23,6 +23,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp @@ -42,6 +43,8 @@ fun ActivityListFilter( val selectedTags by viewModel.selectedTags.collectAsState() val startDate by viewModel.startDate.collectAsState() + val focusManager = LocalFocusManager.current + Column { Row( verticalAlignment = Alignment.CenterVertically, @@ -74,7 +77,10 @@ fun ActivityListFilter( tint = if (selectedTags.isNotEmpty()) Colors.Brand else Colors.White64, modifier = Modifier .size(24.dp) - .clickableAlpha { onTagClick() } + .clickableAlpha { + focusManager.clearFocus() + onTagClick() + } ) Spacer(modifier = Modifier.width(12.dp)) @@ -84,7 +90,10 @@ fun ActivityListFilter( tint = if (startDate != null) Colors.Brand else Colors.White64, modifier = Modifier .size(24.dp) - .clickableAlpha { onDateRangeClick() } + .clickableAlpha { + focusManager.clearFocus() + onDateRangeClick() + } ) } } From 43b2366cd75a3804c0b8aa4e8f053e6f9d1cb5a0 Mon Sep 17 00:00:00 2001 From: Ovi Trif Date: Wed, 14 May 2025 19:06:18 +0200 Subject: [PATCH 07/37] refactor: Add preview for ActivityListFilter --- .../activity/components/ActivityListFilter.kt | 82 ++++++++++++++----- 1 file changed, 62 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityListFilter.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityListFilter.kt index 3b351cc3a..6a2e94e42 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityListFilter.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityListFilter.kt @@ -26,10 +26,12 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import to.bitkit.R import to.bitkit.ui.shared.util.clickableAlpha import to.bitkit.ui.theme.AppTextFieldDefaults +import to.bitkit.ui.theme.AppThemeSurface import to.bitkit.ui.theme.Colors import to.bitkit.viewmodels.ActivityListViewModel @@ -38,14 +40,49 @@ fun ActivityListFilter( viewModel: ActivityListViewModel, onTagClick: () -> Unit, onDateRangeClick: () -> Unit, + modifier: Modifier = Modifier, ) { val searchText by viewModel.searchText.collectAsState() val selectedTags by viewModel.selectedTags.collectAsState() val startDate by viewModel.startDate.collectAsState() - val focusManager = LocalFocusManager.current + var selectedTab by remember { mutableStateOf(ActivityTab.ALL) } + + ActivityListFilterContent( + searchText = searchText, + onSearchTextChange = { viewModel.setSearchText(it) }, + hasTagFilter = selectedTags.isNotEmpty(), + onTagClick = { + focusManager.clearFocus() + onTagClick() + }, + hasDateRangeFilter = startDate != null, + onDateRangeClick = { + focusManager.clearFocus() + onDateRangeClick() + }, + selectedTab = selectedTab, + onTabSelected = { + // TODO on tab change: update filtered activities + selectedTab = it + }, + modifier = modifier, + ) +} - Column { +@Composable +fun ActivityListFilterContent( + searchText: String, + onSearchTextChange: (String) -> Unit, + hasTagFilter: Boolean, + onTagClick: () -> Unit, + hasDateRangeFilter: Boolean, + onDateRangeClick: () -> Unit, + selectedTab: ActivityTab, + onTabSelected: (ActivityTab) -> Unit, + modifier: Modifier = Modifier, +) { + Column(modifier = modifier) { Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier @@ -63,7 +100,7 @@ fun ActivityListFilter( TextField( value = searchText, - onValueChange = { viewModel.setSearchText(it) }, + onValueChange = onSearchTextChange, placeholder = { Text(text = stringResource(R.string.common__search)) }, colors = AppTextFieldDefaults.transparent, modifier = Modifier.weight(1f) @@ -74,42 +111,30 @@ fun ActivityListFilter( Icon( painter = painterResource(R.drawable.ic_tag), contentDescription = null, - tint = if (selectedTags.isNotEmpty()) Colors.Brand else Colors.White64, + tint = if (hasTagFilter) Colors.Brand else Colors.White64, modifier = Modifier .size(24.dp) - .clickableAlpha { - focusManager.clearFocus() - onTagClick() - } + .clickableAlpha { onTagClick() } ) Spacer(modifier = Modifier.width(12.dp)) - Icon( painter = painterResource(R.drawable.ic_calendar), contentDescription = null, - tint = if (startDate != null) Colors.Brand else Colors.White64, + tint = if (hasDateRangeFilter) Colors.Brand else Colors.White64, modifier = Modifier .size(24.dp) - .clickableAlpha { - focusManager.clearFocus() - onDateRangeClick() - } + .clickableAlpha { onDateRangeClick() } ) } } Spacer(modifier = Modifier.height(16.dp)) Column { - var selectedTab by remember { mutableStateOf(ActivityTab.ALL) } - TabRow(selectedTabIndex = ActivityTab.entries.indexOf(selectedTab)) { ActivityTab.entries.forEach { tab -> Tab( text = { Text(tab.title) }, selected = selectedTab == tab, - onClick = { - selectedTab = tab - // TODO on tab change: update filtered activities - } + onClick = { onTabSelected(tab) }, ) } } @@ -129,3 +154,20 @@ val ActivityTab.title: String ActivityTab.RECEIVED -> "Received" ActivityTab.OTHER -> "Other" } + +@Preview +@Composable +private fun Preview() { + AppThemeSurface { + ActivityListFilterContent( + searchText = "", + onSearchTextChange = {}, + hasTagFilter = false, + onTagClick = {}, + hasDateRangeFilter = false, + onDateRangeClick = {}, + selectedTab = ActivityTab.ALL, + onTabSelected = {}, + ) + } +} From 15b58a40442004b66787cedb60f1568beec8a007 Mon Sep 17 00:00:00 2001 From: Ovi Trif Date: Wed, 14 May 2025 21:00:25 +0200 Subject: [PATCH 08/37] fix: Make tab container have transparent bg --- .../wallets/activity/components/ActivityListFilter.kt | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityListFilter.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityListFilter.kt index 6a2e94e42..34a9e104d 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityListFilter.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityListFilter.kt @@ -23,6 +23,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource @@ -45,9 +46,10 @@ fun ActivityListFilter( val searchText by viewModel.searchText.collectAsState() val selectedTags by viewModel.selectedTags.collectAsState() val startDate by viewModel.startDate.collectAsState() - val focusManager = LocalFocusManager.current var selectedTab by remember { mutableStateOf(ActivityTab.ALL) } + val focusManager = LocalFocusManager.current + ActivityListFilterContent( searchText = searchText, onSearchTextChange = { viewModel.setSearchText(it) }, @@ -129,7 +131,10 @@ fun ActivityListFilterContent( } Spacer(modifier = Modifier.height(16.dp)) Column { - TabRow(selectedTabIndex = ActivityTab.entries.indexOf(selectedTab)) { + TabRow( + selectedTabIndex = ActivityTab.entries.indexOf(selectedTab), + containerColor = Color.Transparent, + ) { ActivityTab.entries.forEach { tab -> Tab( text = { Text(tab.title) }, From 9fc6f140587fa22fdab1f7eeb4b35e0151cac180 Mon Sep 17 00:00:00 2001 From: Ovi Trif Date: Wed, 14 May 2025 21:48:02 +0200 Subject: [PATCH 09/37] feat: Add linear gradient to header --- .../wallets/activity/AllActivityScreen.kt | 68 +++++++++++++------ 1 file changed, 49 insertions(+), 19 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt index 733087be2..a880f5d55 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt @@ -1,5 +1,8 @@ package to.bitkit.ui.screens.wallets.activity +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize @@ -9,6 +12,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.MaterialTheme @@ -16,8 +20,18 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -27,7 +41,6 @@ import to.bitkit.ui.components.BodyM import to.bitkit.ui.components.BottomSheetType import to.bitkit.ui.components.TertiaryButton import to.bitkit.ui.scaffold.AppTopBar -import to.bitkit.ui.scaffold.ScreenColumn import to.bitkit.ui.screens.wallets.activity.components.ActivityListFilter import to.bitkit.ui.screens.wallets.activity.components.ActivityRow import to.bitkit.ui.screens.wallets.activity.components.EmptyActivityRow @@ -55,38 +68,55 @@ fun AllActivityScreen( onActivityItemClick: (String) -> Unit, ) { val app = appViewModel ?: return + val filteredActivities by viewModel.filteredActivities.collectAsState() - ScreenColumn { - AppTopBar(stringResource(R.string.wallet__activity_all), onBackClick) - - Column(modifier = Modifier.padding(horizontal = 16.dp)) { - ActivityListFilter( - viewModel = viewModel, - onTagClick = { app.showSheet(BottomSheetType.ActivityTagSelector) }, - onDateRangeClick = { app.showSheet(BottomSheetType.ActivityDateRangeSelector) }, - ) - Spacer(modifier = Modifier.height(16.dp)) - val filteredActivities by viewModel.filteredActivities.collectAsState() - ActivityListWithHeaders( - items = filteredActivities, - onActivityItemClick = onActivityItemClick, - onEmptyActivityRowClick = { app.showSheet(BottomSheetType.Receive) }, - ) + Column { + // Header with gradient background + var headerWidth by remember { mutableFloatStateOf(0f) } + Column( + modifier = Modifier + .onSizeChanged { headerWidth = it.width.toFloat() } + .clip(RoundedCornerShape(bottomStart = 16.dp, bottomEnd = 16.dp)) + .background( + brush = Brush.linearGradient( + colors = listOf(Color(0xFF1e1e1e), Color(0xFF161616)), + start = Offset(0f, 0f), + end = Offset(headerWidth, 0f), + ), + ), + ) { + AppTopBar(stringResource(R.string.wallet__activity_all), onBackClick) + Column(modifier = Modifier.padding(horizontal = 16.dp)) { + ActivityListFilter( + viewModel = viewModel, + onTagClick = { app.showSheet(BottomSheetType.ActivityTagSelector) }, + onDateRangeClick = { app.showSheet(BottomSheetType.ActivityDateRangeSelector) }, + ) + Spacer(modifier = Modifier.height(16.dp)) + } } + + ActivityListWithHeaders( + items = filteredActivities, + onActivityItemClick = onActivityItemClick, + onEmptyActivityRowClick = { app.showSheet(BottomSheetType.Receive) }, + modifier = Modifier.padding(horizontal = 16.dp), + ) } } @Composable fun ActivityListWithHeaders( items: List?, + modifier: Modifier = Modifier, showFooter: Boolean = false, - onAllActivityButtonClick: () -> Unit = { }, + onAllActivityButtonClick: () -> Unit = {}, onActivityItemClick: (String) -> Unit, onEmptyActivityRowClick: () -> Unit, ) { Column( horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier.fillMaxSize() + modifier = modifier.fillMaxSize() ) { if (items != null && items.isNotEmpty()) { val groupedItems = groupActivityItems(items) From 1404fd615a4934ecaf4c08b0f8145fabb71b19ae Mon Sep 17 00:00:00 2001 From: Ovi Trif Date: Wed, 14 May 2025 21:50:18 +0200 Subject: [PATCH 10/37] fix: Slide animation on activity detail to full screen --- .../ui/screens/wallets/activity/ActivityDetailScreen.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/ActivityDetailScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/ActivityDetailScreen.kt index fada4ebf8..953f32f7f 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/ActivityDetailScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/ActivityDetailScreen.kt @@ -89,7 +89,9 @@ fun ActivityDetailScreen( detailViewModel.setActivity(item) } - ScreenColumn { + Column( + modifier = Modifier.background(Colors.Black) + ) { AppTopBar( titleText = stringResource(item.getScreenTitleRes()), onBackClick = onBackClick, From 294d3646a2d8dddbd8a8436ce68ed52a737da561 Mon Sep 17 00:00:00 2001 From: Ovi Trif Date: Wed, 14 May 2025 21:58:39 +0200 Subject: [PATCH 11/37] feat: Localizable activity filter tabs text --- .../activity/components/ActivityListFilter.kt | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityListFilter.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityListFilter.kt index 34a9e104d..ef2b7a4fc 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityListFilter.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityListFilter.kt @@ -137,7 +137,7 @@ fun ActivityListFilterContent( ) { ActivityTab.entries.forEach { tab -> Tab( - text = { Text(tab.title) }, + text = { Text(tab.uiText) }, selected = selectedTab == tab, onClick = { onTabSelected(tab) }, ) @@ -149,16 +149,16 @@ fun ActivityListFilterContent( enum class ActivityTab { ALL, SENT, RECEIVED, OTHER; -} -val ActivityTab.title: String - @Composable - get() = when (this) { - ActivityTab.ALL -> "All" - ActivityTab.SENT -> "Sent" - ActivityTab.RECEIVED -> "Received" - ActivityTab.OTHER -> "Other" - } + val uiText: String + @Composable + get() = when (this) { + ALL -> stringResource(R.string.wallet__activity_tabs__all) + SENT -> stringResource(R.string.wallet__activity_tabs__sent) + RECEIVED -> stringResource(R.string.wallet__activity_tabs__received) + OTHER -> stringResource(R.string.wallet__activity_tabs__other) + } +} @Preview @Composable From b77d8842b25343dd66783c18c77a1b0fdcdb57f2 Mon Sep 17 00:00:00 2001 From: Ovi Trif Date: Thu, 15 May 2025 11:50:25 +0200 Subject: [PATCH 12/37] chore: Cleanup and reformat AllActivityScreen code --- .../wallets/activity/ActivityDetailScreen.kt | 1 - .../wallets/activity/AllActivityScreen.kt | 182 +++++++++--------- 2 files changed, 95 insertions(+), 88 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/ActivityDetailScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/ActivityDetailScreen.kt index 953f32f7f..1009f1df0 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/ActivityDetailScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/ActivityDetailScreen.kt @@ -50,7 +50,6 @@ import to.bitkit.ui.components.TagButton import to.bitkit.ui.components.Title import to.bitkit.ui.scaffold.AppTopBar import to.bitkit.ui.scaffold.CloseNavIcon -import to.bitkit.ui.scaffold.ScreenColumn import to.bitkit.ui.screens.wallets.activity.components.ActivityAddTagSheet import to.bitkit.ui.screens.wallets.activity.components.ActivityIcon import to.bitkit.ui.shared.util.clickableAlpha diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt index a880f5d55..e07f3c4b1 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt @@ -1,8 +1,6 @@ package to.bitkit.ui.screens.wallets.activity -import androidx.compose.foundation.Canvas import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize @@ -29,9 +27,7 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color -import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.layout.onSizeChanged -import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -86,7 +82,9 @@ fun AllActivityScreen( ), ) { AppTopBar(stringResource(R.string.wallet__activity_all), onBackClick) - Column(modifier = Modifier.padding(horizontal = 16.dp)) { + Column( + modifier = Modifier.padding(horizontal = 16.dp) + ) { ActivityListFilter( viewModel = viewModel, onTagClick = { app.showSheet(BottomSheetType.ActivityTagSelector) }, @@ -268,100 +266,110 @@ val testActivityItems = buildList { fun Calendar.epochSecond() = (timeInMillis / 1000).toULong() // Today - add(Activity.Onchain( - OnchainActivity( - id = "1", - txType = PaymentType.RECEIVED, - txId = "01", - value = 42_000_000_u, - fee = 200_u, - feeRate = 1_u, - address = "bc1", - confirmed = true, - timestamp = today.epochSecond(), - isBoosted = false, - isTransfer = true, - doesExist = true, - confirmTimestamp = today.epochSecond(), - channelId = "channelId", - transferTxId = "transferTxId", - createdAt = today.epochSecond(), - updatedAt = today.epochSecond(), + add( + Activity.Onchain( + OnchainActivity( + id = "1", + txType = PaymentType.RECEIVED, + txId = "01", + value = 42_000_000_u, + fee = 200_u, + feeRate = 1_u, + address = "bc1", + confirmed = true, + timestamp = today.epochSecond(), + isBoosted = false, + isTransfer = true, + doesExist = true, + confirmTimestamp = today.epochSecond(), + channelId = "channelId", + transferTxId = "transferTxId", + createdAt = today.epochSecond(), + updatedAt = today.epochSecond(), + ) ) - )) + ) // Yesterday - add(Activity.Lightning( - LightningActivity( - id = "2", - txType = PaymentType.SENT, - status = PaymentState.PENDING, - value = 30_000_u, - fee = 15_u, - invoice = "lnbc2", - message = "Custom message", - timestamp = yesterday.epochSecond(), - preimage = "preimage1", - createdAt = yesterday.epochSecond(), - updatedAt = yesterday.epochSecond(), + add( + Activity.Lightning( + LightningActivity( + id = "2", + txType = PaymentType.SENT, + status = PaymentState.PENDING, + value = 30_000_u, + fee = 15_u, + invoice = "lnbc2", + message = "Custom message", + timestamp = yesterday.epochSecond(), + preimage = "preimage1", + createdAt = yesterday.epochSecond(), + updatedAt = yesterday.epochSecond(), + ) ) - )) + ) // This Week - add(Activity.Lightning( - LightningActivity( - id = "3", - txType = PaymentType.RECEIVED, - status = PaymentState.FAILED, - value = 217_000_u, - fee = 17_u, - invoice = "lnbc3", - message = "", - timestamp = thisWeek.epochSecond(), - preimage = "preimage2", - createdAt = thisWeek.epochSecond(), - updatedAt = thisWeek.epochSecond(), + add( + Activity.Lightning( + LightningActivity( + id = "3", + txType = PaymentType.RECEIVED, + status = PaymentState.FAILED, + value = 217_000_u, + fee = 17_u, + invoice = "lnbc3", + message = "", + timestamp = thisWeek.epochSecond(), + preimage = "preimage2", + createdAt = thisWeek.epochSecond(), + updatedAt = thisWeek.epochSecond(), + ) ) - )) + ) // This Month - add(Activity.Onchain( - OnchainActivity( - id = "4", - txType = PaymentType.RECEIVED, - txId = "04", - value = 950_000_u, - fee = 110_u, - feeRate = 1_u, - address = "bc1", - confirmed = false, - timestamp = thisMonth.epochSecond(), - isBoosted = false, - isTransfer = true, - doesExist = true, - confirmTimestamp = today.epochSecond() + 3600u, - channelId = "channelId", - transferTxId = "transferTxId", - createdAt = thisMonth.epochSecond(), - updatedAt = thisMonth.epochSecond(), + add( + Activity.Onchain( + OnchainActivity( + id = "4", + txType = PaymentType.RECEIVED, + txId = "04", + value = 950_000_u, + fee = 110_u, + feeRate = 1_u, + address = "bc1", + confirmed = false, + timestamp = thisMonth.epochSecond(), + isBoosted = false, + isTransfer = true, + doesExist = true, + confirmTimestamp = today.epochSecond() + 3600u, + channelId = "channelId", + transferTxId = "transferTxId", + createdAt = thisMonth.epochSecond(), + updatedAt = thisMonth.epochSecond(), + ) ) - )) + ) // Last Year - add(Activity.Lightning( - LightningActivity( - id = "5", - txType = PaymentType.SENT, - status = PaymentState.SUCCEEDED, - value = 200_000_u, - fee = 1_u, - invoice = "lnbc…", - message = "", - timestamp = lastYear.epochSecond(), - preimage = null, - createdAt = lastYear.epochSecond(), - updatedAt = lastYear.epochSecond(), + add( + Activity.Lightning( + LightningActivity( + id = "5", + txType = PaymentType.SENT, + status = PaymentState.SUCCEEDED, + value = 200_000_u, + fee = 1_u, + invoice = "lnbc…", + message = "", + timestamp = lastYear.epochSecond(), + preimage = null, + createdAt = lastYear.epochSecond(), + updatedAt = lastYear.epochSecond(), + ) ) - )) + ) } // endregion From 4a316326d98d0c204bd145dd2f8c317b696cdcd2 Mon Sep 17 00:00:00 2001 From: Ovi Trif Date: Thu, 15 May 2025 14:14:44 +0200 Subject: [PATCH 13/37] feat: Swipe horizontally to change filter tabs --- .../wallets/activity/AllActivityScreen.kt | 39 ++++++++++++++++++- .../activity/components/ActivityListFilter.kt | 8 ++-- 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt index e07f3c4b1..5b510a19e 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt @@ -19,6 +19,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment @@ -55,6 +56,12 @@ import java.time.temporal.TemporalAdjusters import java.time.temporal.WeekFields import java.util.Calendar import java.util.Locale +import androidx.compose.foundation.gestures.detectHorizontalDragGestures +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.input.pointer.util.VelocityTracker +import to.bitkit.ui.screens.wallets.activity.components.ActivityTab + +const val SwipeVelocityThreshold = 1500f @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -66,6 +73,30 @@ fun AllActivityScreen( val app = appViewModel ?: return val filteredActivities by viewModel.filteredActivities.collectAsState() + var selectedTab by remember { mutableStateOf(ActivityTab.ALL) } + val tabEntries = ActivityTab.entries + val currentTabIndex = tabEntries.indexOf(selectedTab) + val velocityTracker = remember { VelocityTracker() } + val gestureModifier = Modifier.pointerInput(selectedTab) { + detectHorizontalDragGestures( + onHorizontalDrag = { change, dragAmount -> + velocityTracker.addPosition(change.uptimeMillis, change.position) + }, + onDragEnd = { + val velocity = velocityTracker.calculateVelocity().x + if (velocity >= SwipeVelocityThreshold && currentTabIndex > 0) { + selectedTab = tabEntries[currentTabIndex - 1] + } else if (velocity <= -SwipeVelocityThreshold && currentTabIndex < tabEntries.lastIndex) { + selectedTab = tabEntries[currentTabIndex + 1] + } + velocityTracker.resetTracking() + }, + onDragCancel = { + velocityTracker.resetTracking() + }, + ) + } + Column { // Header with gradient background var headerWidth by remember { mutableFloatStateOf(0f) } @@ -89,16 +120,20 @@ fun AllActivityScreen( viewModel = viewModel, onTagClick = { app.showSheet(BottomSheetType.ActivityTagSelector) }, onDateRangeClick = { app.showSheet(BottomSheetType.ActivityDateRangeSelector) }, + selectedTab = selectedTab, + onTabSelected = { + // TODO on tab change: update filtered activities + selectedTab = it + }, ) Spacer(modifier = Modifier.height(16.dp)) } } - ActivityListWithHeaders( items = filteredActivities, onActivityItemClick = onActivityItemClick, onEmptyActivityRowClick = { app.showSheet(BottomSheetType.Receive) }, - modifier = Modifier.padding(horizontal = 16.dp), + modifier = gestureModifier.padding(horizontal = 16.dp), ) } } diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityListFilter.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityListFilter.kt index ef2b7a4fc..89e00a4d3 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityListFilter.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityListFilter.kt @@ -41,12 +41,13 @@ fun ActivityListFilter( viewModel: ActivityListViewModel, onTagClick: () -> Unit, onDateRangeClick: () -> Unit, + selectedTab: ActivityTab, + onTabSelected: (ActivityTab) -> Unit, modifier: Modifier = Modifier, ) { val searchText by viewModel.searchText.collectAsState() val selectedTags by viewModel.selectedTags.collectAsState() val startDate by viewModel.startDate.collectAsState() - var selectedTab by remember { mutableStateOf(ActivityTab.ALL) } val focusManager = LocalFocusManager.current @@ -64,10 +65,7 @@ fun ActivityListFilter( onDateRangeClick() }, selectedTab = selectedTab, - onTabSelected = { - // TODO on tab change: update filtered activities - selectedTab = it - }, + onTabSelected = onTabSelected, modifier = modifier, ) } From b6abd4cf1b6f7752f7eb7ef940cca8573eb58f3d Mon Sep 17 00:00:00 2001 From: Ovi Trif Date: Thu, 15 May 2025 14:30:07 +0200 Subject: [PATCH 14/37] refactor: Extract swipeToChangeTab modifier --- .../wallets/activity/AllActivityScreen.kt | 68 ++++++++++++------- 1 file changed, 45 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt index 5b510a19e..fdc8cfdb7 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt @@ -57,12 +57,11 @@ import java.time.temporal.WeekFields import java.util.Calendar import java.util.Locale import androidx.compose.foundation.gestures.detectHorizontalDragGestures +import androidx.compose.ui.composed import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.input.pointer.util.VelocityTracker import to.bitkit.ui.screens.wallets.activity.components.ActivityTab -const val SwipeVelocityThreshold = 1500f - @OptIn(ExperimentalMaterial3Api::class) @Composable fun AllActivityScreen( @@ -76,26 +75,6 @@ fun AllActivityScreen( var selectedTab by remember { mutableStateOf(ActivityTab.ALL) } val tabEntries = ActivityTab.entries val currentTabIndex = tabEntries.indexOf(selectedTab) - val velocityTracker = remember { VelocityTracker() } - val gestureModifier = Modifier.pointerInput(selectedTab) { - detectHorizontalDragGestures( - onHorizontalDrag = { change, dragAmount -> - velocityTracker.addPosition(change.uptimeMillis, change.position) - }, - onDragEnd = { - val velocity = velocityTracker.calculateVelocity().x - if (velocity >= SwipeVelocityThreshold && currentTabIndex > 0) { - selectedTab = tabEntries[currentTabIndex - 1] - } else if (velocity <= -SwipeVelocityThreshold && currentTabIndex < tabEntries.lastIndex) { - selectedTab = tabEntries[currentTabIndex + 1] - } - velocityTracker.resetTracking() - }, - onDragCancel = { - velocityTracker.resetTracking() - }, - ) - } Column { // Header with gradient background @@ -133,7 +112,50 @@ fun AllActivityScreen( items = filteredActivities, onActivityItemClick = onActivityItemClick, onEmptyActivityRowClick = { app.showSheet(BottomSheetType.Receive) }, - modifier = gestureModifier.padding(horizontal = 16.dp), + modifier = Modifier + .swipeToChangeTab( + currentTabIndex = currentTabIndex, + tabCount = tabEntries.size, + onTabChange = { selectedTab = tabEntries[it] } + ) + .padding(horizontal = 16.dp) + ) + } +} + +fun Modifier.swipeToChangeTab( + currentTabIndex: Int, + tabCount: Int, + onTabChange: (Int) -> Unit, +) = composed( + inspectorInfo = { + name = "swipeToChangeTab" + value = currentTabIndex + } +) { + val threshold = remember { 1500f } + val velocityTracker = remember { VelocityTracker() } + + pointerInput(currentTabIndex) { + detectHorizontalDragGestures( + onHorizontalDrag = { change, _ -> + velocityTracker.addPosition(change.uptimeMillis, change.position) + }, + onDragEnd = { + val velocity = velocityTracker.calculateVelocity().x + when { + velocity >= threshold && currentTabIndex > 0 -> { + onTabChange(currentTabIndex - 1) + } + velocity <= -threshold && currentTabIndex < tabCount - 1 -> { + onTabChange(currentTabIndex + 1) + } + } + velocityTracker.resetTracking() + }, + onDragCancel = { + velocityTracker.resetTracking() + }, ) } } From e637f2d30c342d9c52b911fe65349d622ec3f123 Mon Sep 17 00:00:00 2001 From: Ovi Trif Date: Thu, 15 May 2025 15:05:03 +0200 Subject: [PATCH 15/37] refactor: Lift logic out of ActivityListFilter --- .../wallets/activity/AllActivityScreen.kt | 31 +++++--- .../activity/components/ActivityListFilter.kt | 74 ++++++------------- 2 files changed, 43 insertions(+), 62 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt index fdc8cfdb7..23841f7b2 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt @@ -57,6 +57,7 @@ import java.time.temporal.WeekFields import java.util.Calendar import java.util.Locale import androidx.compose.foundation.gestures.detectHorizontalDragGestures +import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.composed import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.input.pointer.util.VelocityTracker @@ -72,9 +73,18 @@ fun AllActivityScreen( val app = appViewModel ?: return val filteredActivities by viewModel.filteredActivities.collectAsState() + val searchText by viewModel.searchText.collectAsState() + val selectedTags by viewModel.selectedTags.collectAsState() + val startDate by viewModel.startDate.collectAsState() + var selectedTab by remember { mutableStateOf(ActivityTab.ALL) } - val tabEntries = ActivityTab.entries - val currentTabIndex = tabEntries.indexOf(selectedTab) + val tabs = ActivityTab.entries + val currentTabIndex = tabs.indexOf(selectedTab) + + LaunchedEffect(selectedTab) { + // TODO on tab change: update filtered activities + println("Selected filter tab: $selectedTab") + } Column { // Header with gradient background @@ -96,14 +106,15 @@ fun AllActivityScreen( modifier = Modifier.padding(horizontal = 16.dp) ) { ActivityListFilter( - viewModel = viewModel, + searchText = searchText, + onSearchTextChange = { viewModel.setSearchText(it) }, + hasTagFilter = selectedTags.isNotEmpty(), + hasDateRangeFilter = startDate != null, onTagClick = { app.showSheet(BottomSheetType.ActivityTagSelector) }, onDateRangeClick = { app.showSheet(BottomSheetType.ActivityDateRangeSelector) }, - selectedTab = selectedTab, - onTabSelected = { - // TODO on tab change: update filtered activities - selectedTab = it - }, + tabs = tabs, + currentTabIndex = currentTabIndex, + onTabChange = { selectedTab = it }, ) Spacer(modifier = Modifier.height(16.dp)) } @@ -115,8 +126,8 @@ fun AllActivityScreen( modifier = Modifier .swipeToChangeTab( currentTabIndex = currentTabIndex, - tabCount = tabEntries.size, - onTabChange = { selectedTab = tabEntries[it] } + tabCount = tabs.size, + onTabChange = { selectedTab = tabs[it] } ) .padding(horizontal = 16.dp) ) diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityListFilter.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityListFilter.kt index 89e00a4d3..0c02c6690 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityListFilter.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityListFilter.kt @@ -16,11 +16,6 @@ import androidx.compose.material3.TabRow import androidx.compose.material3.Text import androidx.compose.material3.TextField import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -34,54 +29,22 @@ import to.bitkit.ui.shared.util.clickableAlpha import to.bitkit.ui.theme.AppTextFieldDefaults import to.bitkit.ui.theme.AppThemeSurface import to.bitkit.ui.theme.Colors -import to.bitkit.viewmodels.ActivityListViewModel @Composable fun ActivityListFilter( - viewModel: ActivityListViewModel, - onTagClick: () -> Unit, - onDateRangeClick: () -> Unit, - selectedTab: ActivityTab, - onTabSelected: (ActivityTab) -> Unit, - modifier: Modifier = Modifier, -) { - val searchText by viewModel.searchText.collectAsState() - val selectedTags by viewModel.selectedTags.collectAsState() - val startDate by viewModel.startDate.collectAsState() - - val focusManager = LocalFocusManager.current - - ActivityListFilterContent( - searchText = searchText, - onSearchTextChange = { viewModel.setSearchText(it) }, - hasTagFilter = selectedTags.isNotEmpty(), - onTagClick = { - focusManager.clearFocus() - onTagClick() - }, - hasDateRangeFilter = startDate != null, - onDateRangeClick = { - focusManager.clearFocus() - onDateRangeClick() - }, - selectedTab = selectedTab, - onTabSelected = onTabSelected, - modifier = modifier, - ) -} - -@Composable -fun ActivityListFilterContent( searchText: String, onSearchTextChange: (String) -> Unit, hasTagFilter: Boolean, - onTagClick: () -> Unit, hasDateRangeFilter: Boolean, + onTagClick: () -> Unit, onDateRangeClick: () -> Unit, - selectedTab: ActivityTab, - onTabSelected: (ActivityTab) -> Unit, + tabs: List, + currentTabIndex: Int, + onTabChange: (ActivityTab) -> Unit, modifier: Modifier = Modifier, ) { + val focusManager = LocalFocusManager.current + Column(modifier = modifier) { Row( verticalAlignment = Alignment.CenterVertically, @@ -114,7 +77,10 @@ fun ActivityListFilterContent( tint = if (hasTagFilter) Colors.Brand else Colors.White64, modifier = Modifier .size(24.dp) - .clickableAlpha { onTagClick() } + .clickableAlpha { + focusManager.clearFocus() + onTagClick() + } ) Spacer(modifier = Modifier.width(12.dp)) Icon( @@ -123,21 +89,24 @@ fun ActivityListFilterContent( tint = if (hasDateRangeFilter) Colors.Brand else Colors.White64, modifier = Modifier .size(24.dp) - .clickableAlpha { onDateRangeClick() } + .clickableAlpha { + focusManager.clearFocus() + onDateRangeClick() + } ) } } Spacer(modifier = Modifier.height(16.dp)) Column { TabRow( - selectedTabIndex = ActivityTab.entries.indexOf(selectedTab), + selectedTabIndex = currentTabIndex, containerColor = Color.Transparent, ) { - ActivityTab.entries.forEach { tab -> + tabs.map { tab -> Tab( text = { Text(tab.uiText) }, - selected = selectedTab == tab, - onClick = { onTabSelected(tab) }, + selected = tabs[currentTabIndex] == tab, + onClick = { onTabChange(tab) }, ) } } @@ -162,15 +131,16 @@ enum class ActivityTab { @Composable private fun Preview() { AppThemeSurface { - ActivityListFilterContent( + ActivityListFilter( searchText = "", onSearchTextChange = {}, hasTagFilter = false, onTagClick = {}, hasDateRangeFilter = false, onDateRangeClick = {}, - selectedTab = ActivityTab.ALL, - onTabSelected = {}, + tabs = ActivityTab.entries, + currentTabIndex = 0, + onTabChange = {}, ) } } From 96edb9ebf48491ba499be067598d7796771eba65 Mon Sep 17 00:00:00 2001 From: Ovi Trif Date: Thu, 15 May 2025 15:20:12 +0200 Subject: [PATCH 16/37] fix: Activity list top padding & headers style --- .../bitkit/ui/screens/wallets/activity/AllActivityScreen.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt index 23841f7b2..175fbfb7f 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt @@ -57,10 +57,12 @@ import java.time.temporal.WeekFields import java.util.Calendar import java.util.Locale import androidx.compose.foundation.gestures.detectHorizontalDragGestures +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.composed import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.input.pointer.util.VelocityTracker +import to.bitkit.ui.components.Caption13Up import to.bitkit.ui.screens.wallets.activity.components.ActivityTab @OptIn(ExperimentalMaterial3Api::class) @@ -189,14 +191,14 @@ fun ActivityListWithHeaders( LazyColumn( horizontalAlignment = Alignment.CenterHorizontally, + contentPadding = PaddingValues(top = 20.dp), modifier = Modifier.fillMaxWidth() ) { itemsIndexed(groupedItems) { index, item -> when (item) { is String -> { - Text( + Caption13Up( text = item, - style = MaterialTheme.typography.titleSmall, color = Colors.White64, modifier = Modifier .fillMaxWidth() From 6a14e0a83549047e4b21e9079ecf5b73da1d8111 Mon Sep 17 00:00:00 2001 From: Ovi Trif Date: Thu, 15 May 2025 15:35:49 +0200 Subject: [PATCH 17/37] refactor: Extract activity components and unify naming --- .../bitkit/ui/screens/wallets/HomeScreen.kt | 4 +- .../ui/screens/wallets/SavingsWalletScreen.kt | 4 +- .../screens/wallets/SpendingWalletScreen.kt | 4 +- .../wallets/activity/AllActivityScreen.kt | 178 +-------------- .../components/ActivityListGrouped.kt | 210 ++++++++++++++++++ ...eActivityList.kt => ActivityListSimple.kt} | 8 +- 6 files changed, 222 insertions(+), 186 deletions(-) create mode 100644 app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityListGrouped.kt rename app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/{HomeActivityList.kt => ActivityListSimple.kt} (94%) 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 46a818d60..f378e8150 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 @@ -59,7 +59,7 @@ import to.bitkit.ui.navigateToTransferSavingsIntro import to.bitkit.ui.navigateToTransferSpendingAmount import to.bitkit.ui.navigateToTransferSpendingIntro import to.bitkit.ui.scaffold.AppScaffold -import to.bitkit.ui.screens.wallets.activity.components.HomeActivityList +import to.bitkit.ui.screens.wallets.activity.components.ActivityListSimple import to.bitkit.ui.screens.wallets.activity.AllActivityScreen import to.bitkit.ui.screens.wallets.activity.DateRangeSelectorSheet import to.bitkit.ui.screens.wallets.activity.TagSelectorSheet @@ -266,7 +266,7 @@ private fun HomeContentView( Spacer(modifier = Modifier.height(16.dp)) val activity = activityListViewModel ?: return@Column val latestActivities by activity.latestActivities.collectAsStateWithLifecycle() - HomeActivityList( + ActivityListSimple( items = latestActivities, onAllActivityClick = { walletNavController.navigate(HomeRoutes.AllActivity) }, onActivityItemClick = { rootNavController.navigateToActivityItem(it) }, diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/SavingsWalletScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/SavingsWalletScreen.kt index a32b21cda..c0a293192 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/SavingsWalletScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/SavingsWalletScreen.kt @@ -33,7 +33,7 @@ import to.bitkit.ui.components.EmptyStateView import to.bitkit.ui.components.SecondaryButton import to.bitkit.ui.scaffold.AppTopBar import to.bitkit.ui.scaffold.ScreenColumn -import to.bitkit.ui.screens.wallets.activity.ActivityListWithHeaders +import to.bitkit.ui.screens.wallets.activity.components.ActivityListGrouped import to.bitkit.ui.theme.AppThemeSurface import to.bitkit.ui.theme.Colors import to.bitkit.ui.utils.withAccent @@ -91,7 +91,7 @@ fun SavingsWalletScreen( Spacer(modifier = Modifier.height(16.dp)) val activity = activityListViewModel ?: return@Column val onchainActivities by activity.onchainActivities.collectAsState() - ActivityListWithHeaders( + ActivityListGrouped( items = onchainActivities, showFooter = true, onAllActivityButtonClick = onAllActivityButtonClick, diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/SpendingWalletScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/SpendingWalletScreen.kt index 5e636fe58..dbcdce006 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/SpendingWalletScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/SpendingWalletScreen.kt @@ -33,7 +33,7 @@ import to.bitkit.ui.components.EmptyStateView import to.bitkit.ui.components.SecondaryButton import to.bitkit.ui.scaffold.AppTopBar import to.bitkit.ui.scaffold.ScreenColumn -import to.bitkit.ui.screens.wallets.activity.ActivityListWithHeaders +import to.bitkit.ui.screens.wallets.activity.components.ActivityListGrouped import to.bitkit.ui.theme.AppThemeSurface import to.bitkit.ui.theme.Colors import to.bitkit.ui.utils.withAccent @@ -105,7 +105,7 @@ fun SpendingWalletScreen( val activity = activityListViewModel ?: return@Column val lightningActivities by activity.lightningActivities.collectAsState() - ActivityListWithHeaders( + ActivityListGrouped( items = lightningActivities, showFooter = true, onAllActivityButtonClick = onAllActivityButtonClick, diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt index 175fbfb7f..77ba0578a 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt @@ -3,18 +3,10 @@ package to.bitkit.ui.screens.wallets.activity import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.wrapContentWidth -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.HorizontalDivider -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue @@ -22,7 +14,6 @@ import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.geometry.Offset @@ -30,39 +21,25 @@ import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import to.bitkit.R import to.bitkit.ui.appViewModel -import to.bitkit.ui.components.BodyM import to.bitkit.ui.components.BottomSheetType -import to.bitkit.ui.components.TertiaryButton import to.bitkit.ui.scaffold.AppTopBar import to.bitkit.ui.screens.wallets.activity.components.ActivityListFilter -import to.bitkit.ui.screens.wallets.activity.components.ActivityRow -import to.bitkit.ui.screens.wallets.activity.components.EmptyActivityRow -import to.bitkit.ui.theme.AppThemeSurface -import to.bitkit.ui.theme.Colors import to.bitkit.viewmodels.ActivityListViewModel import uniffi.bitkitcore.Activity import uniffi.bitkitcore.LightningActivity import uniffi.bitkitcore.OnchainActivity import uniffi.bitkitcore.PaymentState import uniffi.bitkitcore.PaymentType -import java.time.Instant -import java.time.ZoneId -import java.time.temporal.ChronoUnit -import java.time.temporal.TemporalAdjusters -import java.time.temporal.WeekFields import java.util.Calendar -import java.util.Locale import androidx.compose.foundation.gestures.detectHorizontalDragGestures -import androidx.compose.foundation.layout.PaddingValues import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.composed import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.input.pointer.util.VelocityTracker -import to.bitkit.ui.components.Caption13Up +import to.bitkit.ui.screens.wallets.activity.components.ActivityListGrouped import to.bitkit.ui.screens.wallets.activity.components.ActivityTab @OptIn(ExperimentalMaterial3Api::class) @@ -121,7 +98,7 @@ fun AllActivityScreen( Spacer(modifier = Modifier.height(16.dp)) } } - ActivityListWithHeaders( + ActivityListGrouped( items = filteredActivities, onActivityItemClick = onActivityItemClick, onEmptyActivityRowClick = { app.showSheet(BottomSheetType.Receive) }, @@ -173,159 +150,8 @@ fun Modifier.swipeToChangeTab( } } -@Composable -fun ActivityListWithHeaders( - items: List?, - modifier: Modifier = Modifier, - showFooter: Boolean = false, - onAllActivityButtonClick: () -> Unit = {}, - onActivityItemClick: (String) -> Unit, - onEmptyActivityRowClick: () -> Unit, -) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = modifier.fillMaxSize() - ) { - if (items != null && items.isNotEmpty()) { - val groupedItems = groupActivityItems(items) - - LazyColumn( - horizontalAlignment = Alignment.CenterHorizontally, - contentPadding = PaddingValues(top = 20.dp), - modifier = Modifier.fillMaxWidth() - ) { - itemsIndexed(groupedItems) { index, item -> - when (item) { - is String -> { - Caption13Up( - text = item, - color = Colors.White64, - modifier = Modifier - .fillMaxWidth() - .padding(vertical = 8.dp) - ) - } - - is Activity -> { - ActivityRow(item, onActivityItemClick) - val hasNextItem = index < groupedItems.size - 1 && groupedItems[index + 1] !is String - if (hasNextItem) { - HorizontalDivider() - } - } - } - } - if (showFooter) { - item { - TertiaryButton( - text = stringResource(R.string.wallet__activity_show_all), - onClick = onAllActivityButtonClick, - modifier = Modifier - .wrapContentWidth() - .padding(top = 8.dp) - ) - } - } - item { - Spacer(modifier = Modifier.height(120.dp)) - } - } - } else { - if (showFooter) { - // In Spending and Savings wallet - EmptyActivityRow(onClick = onEmptyActivityRowClick) - } else { - // On all activity screen when filtered list is empty - BodyM( - text = stringResource(R.string.wallet__activity_no), - color = Colors.White64, - modifier = Modifier.padding(16.dp) - ) - } - } - } -} - -// region utils -private fun groupActivityItems(activityItems: List): List { - val now = Instant.now() - val zoneId = ZoneId.systemDefault() - val today = now.atZone(zoneId).truncatedTo(ChronoUnit.DAYS) - - val startOfDay = today.toInstant().epochSecond - val startOfYesterday = today.minusDays(1).toInstant().epochSecond - val startOfWeek = today.with(TemporalAdjusters.previousOrSame(WeekFields.of(Locale.getDefault()).firstDayOfWeek)) - .toInstant().epochSecond - val startOfMonth = today.withDayOfMonth(1).toInstant().epochSecond - val startOfYear = today.withDayOfYear(1).toInstant().epochSecond - - val todayItems = mutableListOf() - val yesterdayItems = mutableListOf() - val weekItems = mutableListOf() - val monthItems = mutableListOf() - val yearItems = mutableListOf() - val earlierItems = mutableListOf() - - for (item in activityItems) { - val timestamp = when (item) { - is Activity.Lightning -> item.v1.timestamp.toLong() - is Activity.Onchain -> item.v1.timestamp.toLong() - } - when { - timestamp >= startOfDay -> todayItems.add(item) - timestamp >= startOfYesterday -> yesterdayItems.add(item) - timestamp >= startOfWeek -> weekItems.add(item) - timestamp >= startOfMonth -> monthItems.add(item) - timestamp >= startOfYear -> yearItems.add(item) - else -> earlierItems.add(item) - } - } - - return buildList { - if (todayItems.isNotEmpty()) { - add("TODAY") - addAll(todayItems) - } - if (yesterdayItems.isNotEmpty()) { - add("YESTERDAY") - addAll(yesterdayItems) - } - if (weekItems.isNotEmpty()) { - add("THIS WEEK") - addAll(weekItems) - } - if (monthItems.isNotEmpty()) { - add("THIS MONTH") - addAll(monthItems) - } - if (yearItems.isNotEmpty()) { - add("THIS YEAR") - addAll(yearItems) - } - if (earlierItems.isNotEmpty()) { - add("EARLIER") - addAll(earlierItems) - } - } -} // endregion -// region preview -@Preview -@Composable -private fun PreviewActivityListWithHeadersView() { - AppThemeSurface { - Column(modifier = Modifier.padding(horizontal = 16.dp)) { - ActivityListWithHeaders( - testActivityItems, - onAllActivityButtonClick = {}, - onActivityItemClick = {}, - onEmptyActivityRowClick = {}, - ) - } - } -} - val testActivityItems = buildList { val today: Calendar = Calendar.getInstance() val yesterday: Calendar = Calendar.getInstance().apply { add(Calendar.DATE, -1) } diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityListGrouped.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityListGrouped.kt new file mode 100644 index 000000000..beeff8b52 --- /dev/null +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityListGrouped.kt @@ -0,0 +1,210 @@ +package to.bitkit.ui.screens.wallets.activity.components + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.material3.HorizontalDivider +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import to.bitkit.R +import to.bitkit.ui.components.BodyM +import to.bitkit.ui.components.Caption13Up +import to.bitkit.ui.components.TertiaryButton +import to.bitkit.ui.screens.wallets.activity.testActivityItems +import to.bitkit.ui.theme.AppThemeSurface +import to.bitkit.ui.theme.Colors +import uniffi.bitkitcore.Activity +import java.time.Instant +import java.time.ZoneId +import java.time.temporal.ChronoUnit +import java.time.temporal.TemporalAdjusters +import java.time.temporal.WeekFields +import java.util.Locale + +@Composable +fun ActivityListGrouped( + items: List?, + onActivityItemClick: (String) -> Unit, + onEmptyActivityRowClick: () -> Unit, + modifier: Modifier = Modifier, + showFooter: Boolean = false, + onAllActivityButtonClick: () -> Unit = {}, +) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = modifier.fillMaxSize() + ) { + if (items != null && items.isNotEmpty()) { + val groupedItems = groupActivityItems(items) + + LazyColumn( + horizontalAlignment = Alignment.CenterHorizontally, + contentPadding = PaddingValues(top = 20.dp), + modifier = Modifier.fillMaxWidth() + ) { + itemsIndexed(groupedItems) { index, item -> + when (item) { + is String -> { + Caption13Up( + text = item, + color = Colors.White64, + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 8.dp) + ) + } + + is Activity -> { + ActivityRow(item, onActivityItemClick) + val hasNextItem = + index < groupedItems.size - 1 && groupedItems[index + 1] !is String + if (hasNextItem) { + HorizontalDivider() + } + } + } + } + if (showFooter) { + item { + TertiaryButton( + text = stringResource(R.string.wallet__activity_show_all), + onClick = onAllActivityButtonClick, + modifier = Modifier + .wrapContentWidth() + .padding(top = 8.dp) + ) + } + } + item { + Spacer(modifier = Modifier.height(120.dp)) + } + } + } else { + if (showFooter) { + // In Spending and Savings wallet + EmptyActivityRow(onClick = onEmptyActivityRowClick) + } else { + // On all activity screen when filtered list is empty + BodyM( + text = stringResource(R.string.wallet__activity_no), + color = Colors.White64, + modifier = Modifier.padding(16.dp) + ) + } + } + } +} + +// region utils +private fun groupActivityItems(activityItems: List): List { + val now = Instant.now() + val zoneId = ZoneId.systemDefault() + val today = now.atZone(zoneId).truncatedTo(ChronoUnit.DAYS) + + val startOfDay = today.toInstant().epochSecond + val startOfYesterday = today.minusDays(1).toInstant().epochSecond + val startOfWeek = today.with(TemporalAdjusters.previousOrSame(WeekFields.of(Locale.getDefault()).firstDayOfWeek)) + .toInstant().epochSecond + val startOfMonth = today.withDayOfMonth(1).toInstant().epochSecond + val startOfYear = today.withDayOfYear(1).toInstant().epochSecond + + val todayItems = mutableListOf() + val yesterdayItems = mutableListOf() + val weekItems = mutableListOf() + val monthItems = mutableListOf() + val yearItems = mutableListOf() + val earlierItems = mutableListOf() + + for (item in activityItems) { + val timestamp = when (item) { + is Activity.Lightning -> item.v1.timestamp.toLong() + is Activity.Onchain -> item.v1.timestamp.toLong() + } + when { + timestamp >= startOfDay -> todayItems.add(item) + timestamp >= startOfYesterday -> yesterdayItems.add(item) + timestamp >= startOfWeek -> weekItems.add(item) + timestamp >= startOfMonth -> monthItems.add(item) + timestamp >= startOfYear -> yearItems.add(item) + else -> earlierItems.add(item) + } + } + + return buildList { + if (todayItems.isNotEmpty()) { + add("TODAY") + addAll(todayItems) + } + if (yesterdayItems.isNotEmpty()) { + add("YESTERDAY") + addAll(yesterdayItems) + } + if (weekItems.isNotEmpty()) { + add("THIS WEEK") + addAll(weekItems) + } + if (monthItems.isNotEmpty()) { + add("THIS MONTH") + addAll(monthItems) + } + if (yearItems.isNotEmpty()) { + add("THIS YEAR") + addAll(yearItems) + } + if (earlierItems.isNotEmpty()) { + add("EARLIER") + addAll(earlierItems) + } + } +} +// endregion + +@Preview +@Composable +private fun Preview() { + AppThemeSurface { + Column(modifier = Modifier.padding(horizontal = 16.dp)) { + ActivityListGrouped( + items = testActivityItems, + onActivityItemClick = {}, + onEmptyActivityRowClick = {}, + ) + } + } +} + +@Preview +@Composable +private fun PreviewEmpty() { + AppThemeSurface { + ActivityListGrouped( + items = emptyList(), + onActivityItemClick = {}, + onEmptyActivityRowClick = {}, + ) + } +} + +@Preview +@Composable +private fun PreviewEmptyWithFooter() { + AppThemeSurface { + ActivityListGrouped( + items = emptyList(), + onActivityItemClick = {}, + onEmptyActivityRowClick = {}, + showFooter = true, + ) + } +} diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/HomeActivityList.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityListSimple.kt similarity index 94% rename from app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/HomeActivityList.kt rename to app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityListSimple.kt index c7c5cb08f..a95967ad9 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/HomeActivityList.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityListSimple.kt @@ -18,7 +18,7 @@ import to.bitkit.ui.theme.AppThemeSurface import uniffi.bitkitcore.Activity @Composable -fun HomeActivityList( +fun ActivityListSimple( items: List?, onAllActivityClick: () -> Unit, onActivityItemClick: (String) -> Unit, @@ -51,8 +51,8 @@ fun HomeActivityList( @Composable private fun Preview() { AppThemeSurface { - HomeActivityList( - testActivityItems, + ActivityListSimple( + items = testActivityItems, onAllActivityClick = {}, onActivityItemClick = {}, onEmptyActivityRowClick = {}, @@ -64,7 +64,7 @@ private fun Preview() { @Composable private fun PreviewEmpty() { AppThemeSurface { - HomeActivityList( + ActivityListSimple( items = emptyList(), onAllActivityClick = {}, onActivityItemClick = {}, From 0aa529a75e26e160fe599960acdb5c95e70f3ced Mon Sep 17 00:00:00 2001 From: Ovi Trif Date: Thu, 15 May 2025 15:39:40 +0200 Subject: [PATCH 18/37] fix: Remove list top padding on savings & spending screens --- .../java/to/bitkit/ui/screens/wallets/SavingsWalletScreen.kt | 2 +- .../java/to/bitkit/ui/screens/wallets/SpendingWalletScreen.kt | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/SavingsWalletScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/SavingsWalletScreen.kt index c0a293192..b08995de7 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/SavingsWalletScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/SavingsWalletScreen.kt @@ -88,7 +88,7 @@ fun SavingsWalletScreen( ) } ) - Spacer(modifier = Modifier.height(16.dp)) + val activity = activityListViewModel ?: return@Column val onchainActivities by activity.onchainActivities.collectAsState() ActivityListGrouped( diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/SpendingWalletScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/SpendingWalletScreen.kt index dbcdce006..f960c276b 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/SpendingWalletScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/SpendingWalletScreen.kt @@ -100,7 +100,6 @@ fun SpendingWalletScreen( ) } ) - Spacer(modifier = Modifier.height(16.dp)) } val activity = activityListViewModel ?: return@Column From e563a4b48169f500df81a8f723014f503cbe7080 Mon Sep 17 00:00:00 2001 From: Ovi Trif Date: Thu, 15 May 2025 16:20:57 +0200 Subject: [PATCH 19/37] refactor: Extract preview activity items to their own file --- .../wallets/activity/AllActivityScreen.kt | 124 ----------------- .../components/ActivityListGrouped.kt | 4 +- .../activity/components/ActivityListSimple.kt | 4 +- .../activity/components/ActivityRow.kt | 4 +- .../wallets/activity/utils/PreviewItems.kt | 125 ++++++++++++++++++ 5 files changed, 131 insertions(+), 130 deletions(-) create mode 100644 app/src/main/java/to/bitkit/ui/screens/wallets/activity/utils/PreviewItems.kt diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt index 77ba0578a..e7b8c8743 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt @@ -28,12 +28,6 @@ import to.bitkit.ui.components.BottomSheetType import to.bitkit.ui.scaffold.AppTopBar import to.bitkit.ui.screens.wallets.activity.components.ActivityListFilter import to.bitkit.viewmodels.ActivityListViewModel -import uniffi.bitkitcore.Activity -import uniffi.bitkitcore.LightningActivity -import uniffi.bitkitcore.OnchainActivity -import uniffi.bitkitcore.PaymentState -import uniffi.bitkitcore.PaymentType -import java.util.Calendar import androidx.compose.foundation.gestures.detectHorizontalDragGestures import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.composed @@ -151,121 +145,3 @@ fun Modifier.swipeToChangeTab( } // endregion - -val testActivityItems = buildList { - val today: Calendar = Calendar.getInstance() - val yesterday: Calendar = Calendar.getInstance().apply { add(Calendar.DATE, -1) } - val thisWeek: Calendar = Calendar.getInstance().apply { add(Calendar.DATE, -3) } - val thisMonth: Calendar = Calendar.getInstance().apply { add(Calendar.DATE, -10) } - val lastYear: Calendar = Calendar.getInstance().apply { add(Calendar.YEAR, -1) } - - fun Calendar.epochSecond() = (timeInMillis / 1000).toULong() - - // Today - add( - Activity.Onchain( - OnchainActivity( - id = "1", - txType = PaymentType.RECEIVED, - txId = "01", - value = 42_000_000_u, - fee = 200_u, - feeRate = 1_u, - address = "bc1", - confirmed = true, - timestamp = today.epochSecond(), - isBoosted = false, - isTransfer = true, - doesExist = true, - confirmTimestamp = today.epochSecond(), - channelId = "channelId", - transferTxId = "transferTxId", - createdAt = today.epochSecond(), - updatedAt = today.epochSecond(), - ) - ) - ) - - // Yesterday - add( - Activity.Lightning( - LightningActivity( - id = "2", - txType = PaymentType.SENT, - status = PaymentState.PENDING, - value = 30_000_u, - fee = 15_u, - invoice = "lnbc2", - message = "Custom message", - timestamp = yesterday.epochSecond(), - preimage = "preimage1", - createdAt = yesterday.epochSecond(), - updatedAt = yesterday.epochSecond(), - ) - ) - ) - - // This Week - add( - Activity.Lightning( - LightningActivity( - id = "3", - txType = PaymentType.RECEIVED, - status = PaymentState.FAILED, - value = 217_000_u, - fee = 17_u, - invoice = "lnbc3", - message = "", - timestamp = thisWeek.epochSecond(), - preimage = "preimage2", - createdAt = thisWeek.epochSecond(), - updatedAt = thisWeek.epochSecond(), - ) - ) - ) - - // This Month - add( - Activity.Onchain( - OnchainActivity( - id = "4", - txType = PaymentType.RECEIVED, - txId = "04", - value = 950_000_u, - fee = 110_u, - feeRate = 1_u, - address = "bc1", - confirmed = false, - timestamp = thisMonth.epochSecond(), - isBoosted = false, - isTransfer = true, - doesExist = true, - confirmTimestamp = today.epochSecond() + 3600u, - channelId = "channelId", - transferTxId = "transferTxId", - createdAt = thisMonth.epochSecond(), - updatedAt = thisMonth.epochSecond(), - ) - ) - ) - - // Last Year - add( - Activity.Lightning( - LightningActivity( - id = "5", - txType = PaymentType.SENT, - status = PaymentState.SUCCEEDED, - value = 200_000_u, - fee = 1_u, - invoice = "lnbc…", - message = "", - timestamp = lastYear.epochSecond(), - preimage = null, - createdAt = lastYear.epochSecond(), - updatedAt = lastYear.epochSecond(), - ) - ) - ) -} -// endregion diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityListGrouped.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityListGrouped.kt index beeff8b52..da7e12fb6 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityListGrouped.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityListGrouped.kt @@ -21,7 +21,7 @@ import to.bitkit.R import to.bitkit.ui.components.BodyM import to.bitkit.ui.components.Caption13Up import to.bitkit.ui.components.TertiaryButton -import to.bitkit.ui.screens.wallets.activity.testActivityItems +import to.bitkit.ui.screens.wallets.activity.utils.previewActivityItems import to.bitkit.ui.theme.AppThemeSurface import to.bitkit.ui.theme.Colors import uniffi.bitkitcore.Activity @@ -176,7 +176,7 @@ private fun Preview() { AppThemeSurface { Column(modifier = Modifier.padding(horizontal = 16.dp)) { ActivityListGrouped( - items = testActivityItems, + items = previewActivityItems, onActivityItemClick = {}, onEmptyActivityRowClick = {}, ) 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 a95967ad9..9e519bb20 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 @@ -13,7 +13,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import to.bitkit.R import to.bitkit.ui.components.TertiaryButton -import to.bitkit.ui.screens.wallets.activity.testActivityItems +import to.bitkit.ui.screens.wallets.activity.utils.previewActivityItems import to.bitkit.ui.theme.AppThemeSurface import uniffi.bitkitcore.Activity @@ -52,7 +52,7 @@ fun ActivityListSimple( private fun Preview() { AppThemeSurface { ActivityListSimple( - items = testActivityItems, + items = previewActivityItems, onAllActivityClick = {}, onActivityItemClick = {}, onEmptyActivityRowClick = {}, diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityRow.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityRow.kt index b96447c5d..1a7e07e94 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityRow.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityRow.kt @@ -24,7 +24,7 @@ import to.bitkit.ui.LocalCurrencies import to.bitkit.ui.components.BodyMSB import to.bitkit.ui.components.CaptionB import to.bitkit.ui.currencyViewModel -import to.bitkit.ui.screens.wallets.activity.testActivityItems +import to.bitkit.ui.screens.wallets.activity.utils.previewActivityItems import to.bitkit.ui.shared.util.clickableAlpha import to.bitkit.ui.theme.AppThemeSurface import to.bitkit.ui.theme.Colors @@ -200,7 +200,7 @@ private fun formattedTime(timestamp: ULong): String { } private class ActivityItemsPreviewProvider : PreviewParameterProvider { - override val values: Sequence get() = testActivityItems.asSequence() + override val values: Sequence get() = previewActivityItems.asSequence() } @Preview(showBackground = true) diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/utils/PreviewItems.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/utils/PreviewItems.kt new file mode 100644 index 000000000..3b1e6a883 --- /dev/null +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/utils/PreviewItems.kt @@ -0,0 +1,125 @@ +package to.bitkit.ui.screens.wallets.activity.utils + +import uniffi.bitkitcore.Activity +import uniffi.bitkitcore.LightningActivity +import uniffi.bitkitcore.OnchainActivity +import uniffi.bitkitcore.PaymentState +import uniffi.bitkitcore.PaymentType +import java.util.Calendar + +val previewActivityItems = buildList { + val today: Calendar = Calendar.getInstance() + val yesterday: Calendar = Calendar.getInstance().apply { add(Calendar.DATE, -1) } + val thisWeek: Calendar = Calendar.getInstance().apply { add(Calendar.DATE, -3) } + val thisMonth: Calendar = Calendar.getInstance().apply { add(Calendar.DATE, -10) } + val lastYear: Calendar = Calendar.getInstance().apply { add(Calendar.YEAR, -1) } + + fun Calendar.epochSecond() = (timeInMillis / 1000).toULong() + + // Today + add( + Activity.Onchain( + OnchainActivity( + id = "1", + txType = PaymentType.RECEIVED, + txId = "01", + value = 42_000_000_u, + fee = 200_u, + feeRate = 1_u, + address = "bc1", + confirmed = true, + timestamp = today.epochSecond(), + isBoosted = false, + isTransfer = true, + doesExist = true, + confirmTimestamp = today.epochSecond(), + channelId = "channelId", + transferTxId = "transferTxId", + createdAt = today.epochSecond(), + updatedAt = today.epochSecond(), + ) + ) + ) + + // Yesterday + add( + Activity.Lightning( + LightningActivity( + id = "2", + txType = PaymentType.SENT, + status = PaymentState.PENDING, + value = 30_000_u, + fee = 15_u, + invoice = "lnbc2", + message = "Custom very long lightning activity message to test truncation", + timestamp = yesterday.epochSecond(), + preimage = "preimage1", + createdAt = yesterday.epochSecond(), + updatedAt = yesterday.epochSecond(), + ) + ) + ) + + // This Week + add( + Activity.Lightning( + LightningActivity( + id = "3", + txType = PaymentType.RECEIVED, + status = PaymentState.FAILED, + value = 217_000_u, + fee = 17_u, + invoice = "lnbc3", + message = "", + timestamp = thisWeek.epochSecond(), + preimage = "preimage2", + createdAt = thisWeek.epochSecond(), + updatedAt = thisWeek.epochSecond(), + ) + ) + ) + + // This Month + add( + Activity.Onchain( + OnchainActivity( + id = "4", + txType = PaymentType.RECEIVED, + txId = "04", + value = 950_000_u, + fee = 110_u, + feeRate = 1_u, + address = "bc1", + confirmed = false, + timestamp = thisMonth.epochSecond(), + isBoosted = false, + isTransfer = true, + doesExist = true, + confirmTimestamp = today.epochSecond() + 3600u, + channelId = "channelId", + transferTxId = "transferTxId", + createdAt = thisMonth.epochSecond(), + updatedAt = thisMonth.epochSecond(), + ) + ) + ) + + // Last Year + add( + Activity.Lightning( + LightningActivity( + id = "5", + txType = PaymentType.SENT, + status = PaymentState.SUCCEEDED, + value = 200_000_u, + fee = 1_u, + invoice = "lnbc…", + message = "", + timestamp = lastYear.epochSecond(), + preimage = null, + createdAt = lastYear.epochSecond(), + updatedAt = lastYear.epochSecond(), + ) + ) + ) +} From 9e7b2395b8ca4f5df3e39bb15c2d43d31250ecbd Mon Sep 17 00:00:00 2001 From: Ovi Trif Date: Thu, 15 May 2025 16:25:46 +0200 Subject: [PATCH 20/37] feat: Truncate long caption in activity list items --- .../java/to/bitkit/services/CoreService.kt | 3 +- .../main/java/to/bitkit/ui/components/Text.kt | 4 + .../activity/components/ActivityRow.kt | 87 ++++++++++++------- 3 files changed, 62 insertions(+), 32 deletions(-) diff --git a/app/src/main/java/to/bitkit/services/CoreService.kt b/app/src/main/java/to/bitkit/services/CoreService.kt index 15f62d40b..a4245ace3 100644 --- a/app/src/main/java/to/bitkit/services/CoreService.kt +++ b/app/src/main/java/to/bitkit/services/CoreService.kt @@ -354,7 +354,8 @@ class ActivityService( "Gift for mom", "Split dinner bill", "Monthly rent", - "Gym membership" + "Gym membership", + "Very long invoice message to test truncation in list", ) repeat(count) { i -> 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 9b4c2c826..1551f17e1 100644 --- a/app/src/main/java/to/bitkit/ui/components/Text.kt +++ b/app/src/main/java/to/bitkit/ui/components/Text.kt @@ -349,6 +349,8 @@ fun CaptionB( text: String, modifier: Modifier = Modifier, color: Color = MaterialTheme.colorScheme.primary, + maxLines: Int = Int.MAX_VALUE, + overflow: TextOverflow = if (maxLines == 1) TextOverflow.Ellipsis else TextOverflow.Clip, ) { Text( text = text, @@ -362,6 +364,8 @@ fun CaptionB( textAlign = TextAlign.Start, ), modifier = modifier, + maxLines = maxLines, + overflow = overflow, ) } diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityRow.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityRow.kt index 1a7e07e94..b723d05e8 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityRow.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityRow.kt @@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.width import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalInspectionMode import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter @@ -20,6 +21,7 @@ import to.bitkit.ext.DatePattern import to.bitkit.ext.formatted import to.bitkit.ext.rawId import to.bitkit.models.PrimaryDisplay +import to.bitkit.models.formatToModernDisplay import to.bitkit.ui.LocalCurrencies import to.bitkit.ui.components.BodyMSB import to.bitkit.ui.components.CaptionB @@ -70,6 +72,7 @@ fun ActivityRow( Spacer(modifier = Modifier.width(16.dp)) Column( verticalArrangement = Arrangement.spacedBy(4.dp), + modifier = Modifier.weight(1f) ) { TransactionStatusText( txType = txType, @@ -91,9 +94,10 @@ fun ActivityRow( CaptionB( text = subtitleText, color = Colors.White64, + maxLines = 1, ) } - Spacer(modifier = Modifier.weight(1f)) + Spacer(modifier = Modifier.width(16.dp)) AmountView( item = item, prefix = amountPrefix, @@ -141,8 +145,6 @@ private fun AmountView( item: Activity, prefix: String, ) { - val currency = currencyViewModel ?: return - val (_, _, _, _, displayUnit, primaryDisplay) = LocalCurrencies.current val amount = when (item) { is Activity.Lightning -> item.v1.value is Activity.Onchain -> when (item.v1.txType) { @@ -150,38 +152,61 @@ private fun AmountView( else -> item.v1.value } } + + val isPreview = LocalInspectionMode.current + if (isPreview) { + AmountViewContent( + title = amount.toLong().formatToModernDisplay(), + titlePrefix = prefix, + subtitle = "$ 123.45", + ) + return + } + + val currency = currencyViewModel ?: return + val (_, _, _, _, displayUnit, primaryDisplay) = LocalCurrencies.current + currency.convert(sats = amount.toLong())?.let { converted -> val btcComponents = converted.bitcoinDisplay(displayUnit) - Column( - horizontalAlignment = Alignment.End, - verticalArrangement = Arrangement.spacedBy(2.dp), + if (primaryDisplay == PrimaryDisplay.BITCOIN) { + AmountViewContent( + title = btcComponents.value, + titlePrefix = prefix, + subtitle = "${converted.symbol} ${converted.formatted}", + ) + } else { + AmountViewContent( + title = "${converted.symbol} ${converted.formatted}", + titlePrefix = prefix, + subtitle = btcComponents.value, + ) + } + } +} + +@Composable +private fun AmountViewContent( + title: String, + titlePrefix: String, + subtitle: String, + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier, + horizontalAlignment = Alignment.End, + verticalArrangement = Arrangement.spacedBy(2.dp), + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(1.dp), ) { - if (primaryDisplay == PrimaryDisplay.BITCOIN) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(1.dp), - ) { - BodyMSB(text = prefix, color = Colors.White64) - BodyMSB(text = btcComponents.value) - } - CaptionB( - text = "${converted.symbol} ${converted.formatted}", - color = Colors.White64, - ) - } else { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(1.dp), - ) { - BodyMSB(text = prefix, color = Colors.White64) - BodyMSB(text = "${converted.symbol} ${converted.formatted}") - } - CaptionB( - text = btcComponents.value, - color = Colors.White64, - ) - } + BodyMSB(text = titlePrefix, color = Colors.White64) + BodyMSB(text = title) } + CaptionB( + text = subtitle, + color = Colors.White64, + ) } } From f8918af92f3091a6eaa3a368027bcba539568d64 Mon Sep 17 00:00:00 2001 From: Ovi Trif Date: Thu, 15 May 2025 16:29:15 +0200 Subject: [PATCH 21/37] chore: Reformat code --- .../bitkit/ui/screens/wallets/activity/ActivityDetailScreen.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/ActivityDetailScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/ActivityDetailScreen.kt index 1009f1df0..0eec630d3 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/ActivityDetailScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/ActivityDetailScreen.kt @@ -112,7 +112,7 @@ fun ActivityDetailScreen( ) if (showAddTagSheet) { ActivityAddTagSheet( - listViewModel = listViewModel, + listViewModel = listViewModel, activityViewModel = detailViewModel, onDismiss = { showAddTagSheet = false }, ) From 77aeaa4e876d3693d6d13e2ea7a454fda5787775 Mon Sep 17 00:00:00 2001 From: Ovi Trif Date: Thu, 15 May 2025 16:40:34 +0200 Subject: [PATCH 22/37] refactor: Cleanup filter header background code --- .../wallets/activity/AllActivityScreen.kt | 47 +++++-------------- 1 file changed, 11 insertions(+), 36 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt index e7b8c8743..b6a543414 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt @@ -1,6 +1,7 @@ package to.bitkit.ui.screens.wallets.activity import androidx.compose.foundation.background +import androidx.compose.foundation.gestures.detectHorizontalDragGestures import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height @@ -8,18 +9,19 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.composed import androidx.compose.ui.draw.clip -import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color -import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.input.pointer.util.VelocityTracker import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import to.bitkit.R @@ -27,14 +29,9 @@ import to.bitkit.ui.appViewModel import to.bitkit.ui.components.BottomSheetType import to.bitkit.ui.scaffold.AppTopBar import to.bitkit.ui.screens.wallets.activity.components.ActivityListFilter -import to.bitkit.viewmodels.ActivityListViewModel -import androidx.compose.foundation.gestures.detectHorizontalDragGestures -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.ui.composed -import androidx.compose.ui.input.pointer.pointerInput -import androidx.compose.ui.input.pointer.util.VelocityTracker import to.bitkit.ui.screens.wallets.activity.components.ActivityListGrouped import to.bitkit.ui.screens.wallets.activity.components.ActivityTab +import to.bitkit.viewmodels.ActivityListViewModel @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -60,19 +57,12 @@ fun AllActivityScreen( } Column { - // Header with gradient background - var headerWidth by remember { mutableFloatStateOf(0f) } Column( modifier = Modifier - .onSizeChanged { headerWidth = it.width.toFloat() } .clip(RoundedCornerShape(bottomStart = 16.dp, bottomEnd = 16.dp)) .background( - brush = Brush.linearGradient( - colors = listOf(Color(0xFF1e1e1e), Color(0xFF161616)), - start = Offset(0f, 0f), - end = Offset(headerWidth, 0f), - ), - ), + Brush.horizontalGradient(listOf(Color(0xFF1e1e1e), Color(0xFF161616))) + ) ) { AppTopBar(stringResource(R.string.wallet__activity_all), onBackClick) Column( @@ -107,16 +97,7 @@ fun AllActivityScreen( } } -fun Modifier.swipeToChangeTab( - currentTabIndex: Int, - tabCount: Int, - onTabChange: (Int) -> Unit, -) = composed( - inspectorInfo = { - name = "swipeToChangeTab" - value = currentTabIndex - } -) { +private fun Modifier.swipeToChangeTab(currentTabIndex: Int, tabCount: Int, onTabChange: (Int) -> Unit) = composed { val threshold = remember { 1500f } val velocityTracker = remember { VelocityTracker() } @@ -128,12 +109,8 @@ fun Modifier.swipeToChangeTab( onDragEnd = { val velocity = velocityTracker.calculateVelocity().x when { - velocity >= threshold && currentTabIndex > 0 -> { - onTabChange(currentTabIndex - 1) - } - velocity <= -threshold && currentTabIndex < tabCount - 1 -> { - onTabChange(currentTabIndex + 1) - } + velocity >= threshold && currentTabIndex > 0 -> onTabChange(currentTabIndex - 1) + velocity <= -threshold && currentTabIndex < tabCount - 1 -> onTabChange(currentTabIndex + 1) } velocityTracker.resetTracking() }, @@ -143,5 +120,3 @@ fun Modifier.swipeToChangeTab( ) } } - -// endregion From 5b44a61456a38df2cd7eb5740d269fa7f4f4b079 Mon Sep 17 00:00:00 2001 From: Ovi Trif Date: Thu, 15 May 2025 16:47:03 +0200 Subject: [PATCH 23/37] chore: Add preview for all activity screen --- .../wallets/activity/AllActivityScreen.kt | 79 +++++++++++++++++-- 1 file changed, 71 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt index b6a543414..354592728 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt @@ -23,6 +23,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.input.pointer.util.VelocityTracker import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import to.bitkit.R import to.bitkit.ui.appViewModel @@ -31,7 +32,10 @@ import to.bitkit.ui.scaffold.AppTopBar import to.bitkit.ui.screens.wallets.activity.components.ActivityListFilter import to.bitkit.ui.screens.wallets.activity.components.ActivityListGrouped import to.bitkit.ui.screens.wallets.activity.components.ActivityTab +import to.bitkit.ui.screens.wallets.activity.utils.previewActivityItems +import to.bitkit.ui.theme.AppThemeSurface import to.bitkit.viewmodels.ActivityListViewModel +import uniffi.bitkitcore.Activity @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -51,11 +55,48 @@ fun AllActivityScreen( val tabs = ActivityTab.entries val currentTabIndex = tabs.indexOf(selectedTab) + val hasTagFilter = selectedTags.isNotEmpty() + val hasDateRangeFilter = startDate != null + LaunchedEffect(selectedTab) { // TODO on tab change: update filtered activities println("Selected filter tab: $selectedTab") } + AllActivityScreenContent( + filteredActivities = filteredActivities, + searchText = searchText, + onSearchTextChange = { viewModel.setSearchText(it) }, + hasTagFilter = hasTagFilter, + hasDateRangeFilter = hasDateRangeFilter, + tabs = tabs, + currentTabIndex = currentTabIndex, + onTabChange = { selectedTab = tabs[it] }, + onBackClick = onBackClick, + onTagClick = { app.showSheet(BottomSheetType.ActivityTagSelector) }, + onDateRangeClick = { app.showSheet(BottomSheetType.ActivityDateRangeSelector) }, + onActivityItemClick = onActivityItemClick, + onEmptyActivityRowClick = { app.showSheet(BottomSheetType.Receive) }, + ) +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun AllActivityScreenContent( + filteredActivities: List?, + searchText: String, + onSearchTextChange: (String) -> Unit, + hasTagFilter: Boolean, + hasDateRangeFilter: Boolean, + tabs: List, + currentTabIndex: Int, + onTabChange: (Int) -> Unit, + onBackClick: () -> Unit, + onTagClick: () -> Unit, + onDateRangeClick: () -> Unit, + onActivityItemClick: (String) -> Unit, + onEmptyActivityRowClick: () -> Unit, +) { Column { Column( modifier = Modifier @@ -70,14 +111,14 @@ fun AllActivityScreen( ) { ActivityListFilter( searchText = searchText, - onSearchTextChange = { viewModel.setSearchText(it) }, - hasTagFilter = selectedTags.isNotEmpty(), - hasDateRangeFilter = startDate != null, - onTagClick = { app.showSheet(BottomSheetType.ActivityTagSelector) }, - onDateRangeClick = { app.showSheet(BottomSheetType.ActivityDateRangeSelector) }, + onSearchTextChange = onSearchTextChange, + hasTagFilter = hasTagFilter, + hasDateRangeFilter = hasDateRangeFilter, + onTagClick = onTagClick, + onDateRangeClick = onDateRangeClick, tabs = tabs, currentTabIndex = currentTabIndex, - onTabChange = { selectedTab = it }, + onTabChange = { onTabChange(tabs.indexOf(it)) }, ) Spacer(modifier = Modifier.height(16.dp)) } @@ -85,12 +126,12 @@ fun AllActivityScreen( ActivityListGrouped( items = filteredActivities, onActivityItemClick = onActivityItemClick, - onEmptyActivityRowClick = { app.showSheet(BottomSheetType.Receive) }, + onEmptyActivityRowClick = onEmptyActivityRowClick, modifier = Modifier .swipeToChangeTab( currentTabIndex = currentTabIndex, tabCount = tabs.size, - onTabChange = { selectedTab = tabs[it] } + onTabChange = onTabChange, ) .padding(horizontal = 16.dp) ) @@ -120,3 +161,25 @@ private fun Modifier.swipeToChangeTab(currentTabIndex: Int, tabCount: Int, onTab ) } } + +@Preview +@Composable +private fun Preview() { + AppThemeSurface { + AllActivityScreenContent( + filteredActivities = previewActivityItems, + searchText = "", + onSearchTextChange = {}, + hasTagFilter = false, + hasDateRangeFilter = false, + tabs = ActivityTab.entries, + currentTabIndex = 0, + onTabChange = {}, + onBackClick = {}, + onTagClick = {}, + onDateRangeClick = {}, + onActivityItemClick = {}, + onEmptyActivityRowClick = {}, + ) + } +} From 88d569d51e2392e4c5ea18782c708e48abc23967 Mon Sep 17 00:00:00 2001 From: Ovi Trif Date: Thu, 15 May 2025 17:20:03 +0200 Subject: [PATCH 24/37] fix: Date range filter zero results --- .../ui/screens/wallets/activity/AllActivityScreen.kt | 7 ++----- .../java/to/bitkit/viewmodels/ActivityListViewModel.kt | 8 ++++---- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt index 354592728..865e166d1 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt @@ -55,9 +55,6 @@ fun AllActivityScreen( val tabs = ActivityTab.entries val currentTabIndex = tabs.indexOf(selectedTab) - val hasTagFilter = selectedTags.isNotEmpty() - val hasDateRangeFilter = startDate != null - LaunchedEffect(selectedTab) { // TODO on tab change: update filtered activities println("Selected filter tab: $selectedTab") @@ -67,8 +64,8 @@ fun AllActivityScreen( filteredActivities = filteredActivities, searchText = searchText, onSearchTextChange = { viewModel.setSearchText(it) }, - hasTagFilter = hasTagFilter, - hasDateRangeFilter = hasDateRangeFilter, + hasTagFilter = selectedTags.isNotEmpty(), + hasDateRangeFilter = startDate != null, tabs = tabs, currentTabIndex = currentTabIndex, onTabChange = { selectedTab = tabs[it] }, diff --git a/app/src/main/java/to/bitkit/viewmodels/ActivityListViewModel.kt b/app/src/main/java/to/bitkit/viewmodels/ActivityListViewModel.kt index addb800e8..dade66162 100644 --- a/app/src/main/java/to/bitkit/viewmodels/ActivityListViewModel.kt +++ b/app/src/main/java/to/bitkit/viewmodels/ActivityListViewModel.kt @@ -129,10 +129,10 @@ class ActivityListViewModel @Inject constructor( try { _filteredActivities.value = coreService.activity.get( filter = ActivityFilter.ALL, - tags = if (_selectedTags.value.isEmpty()) null else _selectedTags.value.toList(), - search = if (_searchText.value.isEmpty()) null else _searchText.value, - minDate = _startDate.value?.toULong(), - maxDate = _endDate.value?.toULong(), + tags = _selectedTags.value.takeIf { it.isNotEmpty() }?.toList(), + search = _searchText.value.takeIf { it.isNotEmpty() }, + minDate = _startDate.value?.let { it / 1000 }?.toULong(), + maxDate = _endDate.value?.let { it / 1000 }?.toULong(), ) } catch (e: Exception) { Logger.error("Failed to filter activities", e) From 8815a5a0cc087b3991efafafe6904099f18caff0 Mon Sep 17 00:00:00 2001 From: Ovi Trif Date: Thu, 15 May 2025 18:30:53 +0200 Subject: [PATCH 25/37] fix: Total amount on activity explore --- .../screens/wallets/activity/ActivityExploreScreen.kt | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/ActivityExploreScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/ActivityExploreScreen.kt index e6fe414ac..9e8269810 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/ActivityExploreScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/ActivityExploreScreen.kt @@ -129,14 +129,17 @@ private fun ActivityExploreContent( .fillMaxWidth() .padding(vertical = 16.dp) ) { - val value = when (item) { - is Activity.Lightning -> item.v1.value - is Activity.Onchain -> item.v1.value - } val isSent = when (item) { is Activity.Lightning -> item.v1.txType == PaymentType.SENT is Activity.Onchain -> item.v1.txType == PaymentType.SENT } + val value = when (item) { + is Activity.Lightning -> item.v1.value + is Activity.Onchain -> when { + isSent -> item.v1.value + item.v1.fee + else -> item.v1.value + } + } val amountPrefix = if (isSent) "-" else "+" BalanceHeaderView( sats = value.toLong(), From 3eb7675057ebee5ee777a51926b893a7fb3527e3 Mon Sep 17 00:00:00 2001 From: Ovi Trif Date: Thu, 15 May 2025 18:57:27 +0200 Subject: [PATCH 26/37] refactor: Run activity details methods on bgDispatcher --- .../to/bitkit/viewmodels/ActivityDetailViewModel.kt | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/to/bitkit/viewmodels/ActivityDetailViewModel.kt b/app/src/main/java/to/bitkit/viewmodels/ActivityDetailViewModel.kt index a2d60e6c0..9699a4c56 100644 --- a/app/src/main/java/to/bitkit/viewmodels/ActivityDetailViewModel.kt +++ b/app/src/main/java/to/bitkit/viewmodels/ActivityDetailViewModel.kt @@ -3,9 +3,11 @@ package to.bitkit.viewmodels import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch +import to.bitkit.di.BgDispatcher import to.bitkit.ext.rawId import to.bitkit.services.CoreService import to.bitkit.utils.AddressChecker @@ -16,6 +18,7 @@ import javax.inject.Inject @HiltViewModel class ActivityDetailViewModel @Inject constructor( + @BgDispatcher private val bgDispatcher: CoroutineDispatcher, private val addressChecker: AddressChecker, private val coreService: CoreService, ) : ViewModel() { @@ -34,7 +37,7 @@ class ActivityDetailViewModel @Inject constructor( fun loadTags() { val id = activity?.rawId() ?: return - viewModelScope.launch { + viewModelScope.launch(bgDispatcher) { try { val activityTags = coreService.activity.tags(forActivityId = id) _tags.value = activityTags @@ -47,7 +50,7 @@ class ActivityDetailViewModel @Inject constructor( fun removeTag(tag: String) { val id = activity?.rawId() ?: return - viewModelScope.launch { + viewModelScope.launch(bgDispatcher) { try { coreService.activity.dropTags(fromActivityId = id, tags = listOf(tag)) loadTags() @@ -59,7 +62,7 @@ class ActivityDetailViewModel @Inject constructor( fun addTag(tag: String) { val id = activity?.rawId() ?: return - viewModelScope.launch { + viewModelScope.launch(bgDispatcher) { try { val result = coreService.activity.appendTags(toActivityId = id, tags = listOf(tag)) if (result.isSuccess) { @@ -72,7 +75,7 @@ class ActivityDetailViewModel @Inject constructor( } fun fetchTransactionDetails(txid: String) { - viewModelScope.launch { + viewModelScope.launch(bgDispatcher) { try { // TODO replace with bitkit-core method when available _txDetails.value = addressChecker.getTransaction(txid) From 8c5f7caf60f6da3088a3ed3949f30ed9c3c13240 Mon Sep 17 00:00:00 2001 From: Ovi Trif Date: Thu, 15 May 2025 19:08:09 +0200 Subject: [PATCH 27/37] fix: Add preimage to LN activity items --- app/src/main/java/to/bitkit/services/CoreService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/to/bitkit/services/CoreService.kt b/app/src/main/java/to/bitkit/services/CoreService.kt index a4245ace3..710e729e0 100644 --- a/app/src/main/java/to/bitkit/services/CoreService.kt +++ b/app/src/main/java/to/bitkit/services/CoreService.kt @@ -240,7 +240,7 @@ class ActivityService( invoice = "lnbc123", // TODO message = "", timestamp = payment.latestUpdateTimestamp, - preimage = null, + preimage = kind.preimage, createdAt = payment.latestUpdateTimestamp, updatedAt = payment.latestUpdateTimestamp, ) From 561acd44b8e5cb01cbbe29f551100b7c56f2826e Mon Sep 17 00:00:00 2001 From: Ovi Trif Date: Thu, 15 May 2025 19:23:45 +0200 Subject: [PATCH 28/37] fix: Add fee to LN activity and calculate it for amount --- app/src/main/java/to/bitkit/ext/Activities.kt | 20 +++++++++++++++++++ .../java/to/bitkit/services/CoreService.kt | 4 ++-- .../wallets/activity/ActivityDetailScreen.kt | 11 +++------- .../wallets/activity/ActivityExploreScreen.kt | 10 ++-------- .../activity/components/ActivityRow.kt | 9 ++------- 5 files changed, 29 insertions(+), 25 deletions(-) diff --git a/app/src/main/java/to/bitkit/ext/Activities.kt b/app/src/main/java/to/bitkit/ext/Activities.kt index d011c2a9c..705d1ed4c 100644 --- a/app/src/main/java/to/bitkit/ext/Activities.kt +++ b/app/src/main/java/to/bitkit/ext/Activities.kt @@ -1,8 +1,28 @@ package to.bitkit.ext import uniffi.bitkitcore.Activity +import uniffi.bitkitcore.PaymentType fun Activity.rawId(): String = when (this) { is Activity.Lightning -> v1.id is Activity.Onchain -> v1.id } + +/** + * Calculates the total value of an activity based on its type. + * + * For `Lightning` activity, the total value = `value + fee`. + * + * For `Onchain` activity: + * - If it is a send, the total value = `value + fee`. + * - Otherwise it's equal to `value`. + * + * @return The total value as an `ULong`. + */ +fun Activity.totalValue() = when(this) { + is Activity.Lightning -> v1.value + (v1.fee ?: 0u) + is Activity.Onchain -> when (v1.txType) { + PaymentType.SENT -> v1.value + v1.fee + else -> v1.value + } +} diff --git a/app/src/main/java/to/bitkit/services/CoreService.kt b/app/src/main/java/to/bitkit/services/CoreService.kt index 710e729e0..4de3dfa5f 100644 --- a/app/src/main/java/to/bitkit/services/CoreService.kt +++ b/app/src/main/java/to/bitkit/services/CoreService.kt @@ -236,8 +236,8 @@ class ActivityService( txType = payment.direction.toPaymentType(), status = state, value = payment.amountSats ?: 0u, - fee = null, // TODO - invoice = "lnbc123", // TODO + fee = (payment.feePaidMsat ?: 0u) / 1000u, + invoice = "lnbc123_todo", // TODO message = "", timestamp = payment.latestUpdateTimestamp, preimage = kind.preimage, diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/ActivityDetailScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/ActivityDetailScreen.kt index 0eec630d3..d8499f543 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/ActivityDetailScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/ActivityDetailScreen.kt @@ -38,6 +38,7 @@ import to.bitkit.ext.ellipsisMiddle import to.bitkit.ext.rawId import to.bitkit.ext.toActivityItemDate import to.bitkit.ext.toActivityItemTime +import to.bitkit.ext.totalValue import to.bitkit.models.Toast import to.bitkit.ui.Routes import to.bitkit.ui.appViewModel @@ -144,13 +145,7 @@ private fun ActivityDetailContent( else -> item.v1.timestamp } } - val value = when (item) { - is Activity.Lightning -> item.v1.value - is Activity.Onchain -> when { - isSent -> item.v1.value + item.v1.fee - else -> item.v1.value - } - } + val totalValue = item.totalValue() val paymentValue = when (item) { is Activity.Lightning -> item.v1.value is Activity.Onchain -> item.v1.value @@ -173,7 +168,7 @@ private fun ActivityDetailContent( .padding(vertical = 16.dp) ) { BalanceHeaderView( - sats = value.toLong(), + sats = totalValue.toLong(), prefix = amountPrefix, showBitcoinSymbol = false, modifier = Modifier.weight(1f) diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/ActivityExploreScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/ActivityExploreScreen.kt index 9e8269810..e33139d6b 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/ActivityExploreScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/ActivityExploreScreen.kt @@ -34,6 +34,7 @@ import to.bitkit.R import to.bitkit.env.Env import to.bitkit.ext.ellipsisMiddle import to.bitkit.ext.rawId +import to.bitkit.ext.totalValue import to.bitkit.models.Toast import to.bitkit.ui.Routes import to.bitkit.ui.appViewModel @@ -133,16 +134,9 @@ private fun ActivityExploreContent( is Activity.Lightning -> item.v1.txType == PaymentType.SENT is Activity.Onchain -> item.v1.txType == PaymentType.SENT } - val value = when (item) { - is Activity.Lightning -> item.v1.value - is Activity.Onchain -> when { - isSent -> item.v1.value + item.v1.fee - else -> item.v1.value - } - } val amountPrefix = if (isSent) "-" else "+" BalanceHeaderView( - sats = value.toLong(), + sats = item.totalValue().toLong(), prefix = amountPrefix, showBitcoinSymbol = false, modifier = Modifier.weight(1f), diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityRow.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityRow.kt index b723d05e8..99c730900 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityRow.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityRow.kt @@ -20,6 +20,7 @@ import to.bitkit.R import to.bitkit.ext.DatePattern import to.bitkit.ext.formatted import to.bitkit.ext.rawId +import to.bitkit.ext.totalValue import to.bitkit.models.PrimaryDisplay import to.bitkit.models.formatToModernDisplay import to.bitkit.ui.LocalCurrencies @@ -145,13 +146,7 @@ private fun AmountView( item: Activity, prefix: String, ) { - val amount = when (item) { - is Activity.Lightning -> item.v1.value - is Activity.Onchain -> when (item.v1.txType) { - PaymentType.SENT -> item.v1.value + item.v1.fee - else -> item.v1.value - } - } + val amount = item.totalValue() val isPreview = LocalInspectionMode.current if (isPreview) { From ff90211462fc35e241c358ec1387e80d07ae96ec Mon Sep 17 00:00:00 2001 From: Ovi Trif Date: Thu, 15 May 2025 19:36:27 +0200 Subject: [PATCH 29/37] fix: Activity list filtered with zero results UI --- .../wallets/activity/AllActivityScreen.kt | 29 +++++++++++++++++-- .../components/ActivityListGrouped.kt | 4 ++- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt index 865e166d1..a1a14213f 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt @@ -34,6 +34,7 @@ import to.bitkit.ui.screens.wallets.activity.components.ActivityListGrouped import to.bitkit.ui.screens.wallets.activity.components.ActivityTab import to.bitkit.ui.screens.wallets.activity.utils.previewActivityItems import to.bitkit.ui.theme.AppThemeSurface +import to.bitkit.ui.theme.Colors import to.bitkit.viewmodels.ActivityListViewModel import uniffi.bitkitcore.Activity @@ -94,7 +95,9 @@ private fun AllActivityScreenContent( onActivityItemClick: (String) -> Unit, onEmptyActivityRowClick: () -> Unit, ) { - Column { + Column( + modifier = Modifier.background(Colors.Black) + ) { Column( modifier = Modifier .clip(RoundedCornerShape(bottomStart = 16.dp, bottomEnd = 16.dp)) @@ -159,7 +162,7 @@ private fun Modifier.swipeToChangeTab(currentTabIndex: Int, tabCount: Int, onTab } } -@Preview +@Preview(showSystemUi = true) @Composable private fun Preview() { AppThemeSurface { @@ -180,3 +183,25 @@ private fun Preview() { ) } } + +@Preview(showSystemUi = true) +@Composable +private fun PreviewEmpty() { + AppThemeSurface { + AllActivityScreenContent( + filteredActivities = emptyList(), + searchText = "", + onSearchTextChange = {}, + hasTagFilter = false, + hasDateRangeFilter = false, + tabs = ActivityTab.entries, + currentTabIndex = 0, + onTabChange = {}, + onBackClick = {}, + onTagClick = {}, + onDateRangeClick = {}, + onActivityItemClick = {}, + onEmptyActivityRowClick = {}, + ) + } +} diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityListGrouped.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityListGrouped.kt index da7e12fb6..e8c7e5e37 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityListGrouped.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityListGrouped.kt @@ -99,7 +99,9 @@ fun ActivityListGrouped( BodyM( text = stringResource(R.string.wallet__activity_no), color = Colors.White64, - modifier = Modifier.padding(16.dp) + modifier = Modifier + .fillMaxWidth() + .padding(16.dp) ) } } From 9731b87da940f4665f50d1dad23ff5347c3b1fae Mon Sep 17 00:00:00 2001 From: Ovi Trif Date: Thu, 15 May 2025 19:58:34 +0200 Subject: [PATCH 30/37] fix: Broken activity details navigation when list is filtered --- .../ui/screens/wallets/activity/ActivityDetailScreen.kt | 2 +- .../ui/screens/wallets/activity/ActivityExploreScreen.kt | 2 +- .../main/java/to/bitkit/viewmodels/ActivityListViewModel.kt | 6 ++++++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/ActivityDetailScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/ActivityDetailScreen.kt index d8499f543..742f3cecd 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/ActivityDetailScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/ActivityDetailScreen.kt @@ -75,7 +75,7 @@ fun ActivityDetailScreen( onBackClick: () -> Unit, onCloseClick: () -> Unit, ) { - val activities by listViewModel.filteredActivities.collectAsStateWithLifecycle() + val activities by listViewModel.allActivityItems.collectAsStateWithLifecycle() val item = activities?.find { it.rawId() == route.id } ?: return diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/ActivityExploreScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/ActivityExploreScreen.kt index e33139d6b..59d4ed46a 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/ActivityExploreScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/ActivityExploreScreen.kt @@ -69,7 +69,7 @@ fun ActivityExploreScreen( onBackClick: () -> Unit, onCloseClick: () -> Unit, ) { - val activities by listViewModel.filteredActivities.collectAsStateWithLifecycle() + val activities by listViewModel.allActivityItems.collectAsStateWithLifecycle() val item = activities?.find { it.rawId() == route.id } ?: return diff --git a/app/src/main/java/to/bitkit/viewmodels/ActivityListViewModel.kt b/app/src/main/java/to/bitkit/viewmodels/ActivityListViewModel.kt index dade66162..b146a581b 100644 --- a/app/src/main/java/to/bitkit/viewmodels/ActivityListViewModel.kt +++ b/app/src/main/java/to/bitkit/viewmodels/ActivityListViewModel.kt @@ -23,6 +23,9 @@ class ActivityListViewModel @Inject constructor( private val lightningRepo: LightningRepo, private val ldkNodeEventBus: LdkNodeEventBus, ) : ViewModel() { + private val _allActivityItems = MutableStateFlow?>(null) + val allActivityItems = _allActivityItems.asStateFlow() + private val _filteredActivities = MutableStateFlow?>(null) val filteredActivities = _filteredActivities.asStateFlow() @@ -112,6 +115,9 @@ class ActivityListViewModel @Inject constructor( val limitLatest = 3u _latestActivities.value = coreService.activity.get(filter = ActivityFilter.ALL, limit = limitLatest) + // Fetch all activities (unfiltered) + _allActivityItems.value = coreService.activity.get(filter = ActivityFilter.ALL) + // Fetch lightning and onchain activities _lightningActivities.value = coreService.activity.get(filter = ActivityFilter.LIGHTNING) _onchainActivities.value = coreService.activity.get(filter = ActivityFilter.ONCHAIN) From 887a871da9d1ef8a7aed3913f08e0afba22d8a54 Mon Sep 17 00:00:00 2001 From: Ovi Trif Date: Thu, 15 May 2025 20:15:09 +0200 Subject: [PATCH 31/37] refactor: Run activity list methods on bgDispatcher --- .../viewmodels/ActivityListViewModel.kt | 42 ++++--------------- 1 file changed, 7 insertions(+), 35 deletions(-) diff --git a/app/src/main/java/to/bitkit/viewmodels/ActivityListViewModel.kt b/app/src/main/java/to/bitkit/viewmodels/ActivityListViewModel.kt index b146a581b..5a006f7cd 100644 --- a/app/src/main/java/to/bitkit/viewmodels/ActivityListViewModel.kt +++ b/app/src/main/java/to/bitkit/viewmodels/ActivityListViewModel.kt @@ -3,12 +3,14 @@ package to.bitkit.viewmodels import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.launch +import to.bitkit.di.BgDispatcher import to.bitkit.repositories.LightningRepo import to.bitkit.services.CoreService import to.bitkit.services.LdkNodeEventBus @@ -19,6 +21,7 @@ import javax.inject.Inject @HiltViewModel class ActivityListViewModel @Inject constructor( + @BgDispatcher private val bgDispatcher: CoroutineDispatcher, private val coreService: CoreService, private val lightningRepo: LightningRepo, private val ldkNodeEventBus: LdkNodeEventBus, @@ -66,7 +69,7 @@ class ActivityListViewModel @Inject constructor( val availableTags = _availableTags.asStateFlow() init { - viewModelScope.launch { + viewModelScope.launch(bgDispatcher) { ldkNodeEventBus.events.collect { // TODO: sync only on specific events for better performance syncLdkNodePayments() @@ -177,7 +180,7 @@ class ActivityListViewModel @Inject constructor( return } - viewModelScope.launch { + viewModelScope.launch(bgDispatcher) { isSyncingLdkNodePayments = true lightningRepo.getPayments() .onSuccess { @@ -190,46 +193,15 @@ class ActivityListViewModel @Inject constructor( } } - fun addTags(activityId: String, tags: List) { - viewModelScope.launch { - try { - coreService.activity.appendTags(toActivityId = activityId, tags = tags) - syncState() - } catch (e: Exception) { - Logger.error("Failed to add tags to activity", e) - } - } - } - - fun removeTags(activityId: String, tags: List) { - viewModelScope.launch { - try { - coreService.activity.dropTags(fromActivityId = activityId, tags = tags) - syncState() - } catch (e: Exception) { - Logger.error("Failed to remove tags from activity", e) - } - } - } - - suspend fun getActivitiesWithTag(tag: String): List { - return try { - coreService.activity.get(tags = listOf(tag)) - } catch (e: Exception) { - Logger.error("Failed get activities by tag", e) - emptyList() - } - } - fun generateRandomTestData() { - viewModelScope.launch { + viewModelScope.launch(bgDispatcher) { coreService.activity.generateRandomTestData() syncState() } } fun removeAllActivities() { - viewModelScope.launch { + viewModelScope.launch(bgDispatcher) { coreService.activity.removeAll() syncState() } From cdfa3b4fa2679ee448dd25b09ad4b868cbd7b5aa Mon Sep 17 00:00:00 2001 From: Ovi Trif Date: Thu, 15 May 2025 20:35:34 +0200 Subject: [PATCH 32/37] feat: Clear filters when leaving activity list screen --- .../wallets/activity/ActivityDetailScreen.kt | 2 +- .../wallets/activity/ActivityExploreScreen.kt | 2 +- .../wallets/activity/AllActivityScreen.kt | 7 ++++ .../viewmodels/ActivityListViewModel.kt | 38 ++++++++++++++----- 4 files changed, 38 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/ActivityDetailScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/ActivityDetailScreen.kt index 742f3cecd..d8499f543 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/ActivityDetailScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/ActivityDetailScreen.kt @@ -75,7 +75,7 @@ fun ActivityDetailScreen( onBackClick: () -> Unit, onCloseClick: () -> Unit, ) { - val activities by listViewModel.allActivityItems.collectAsStateWithLifecycle() + val activities by listViewModel.filteredActivities.collectAsStateWithLifecycle() val item = activities?.find { it.rawId() == route.id } ?: return diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/ActivityExploreScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/ActivityExploreScreen.kt index 59d4ed46a..e33139d6b 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/ActivityExploreScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/ActivityExploreScreen.kt @@ -69,7 +69,7 @@ fun ActivityExploreScreen( onBackClick: () -> Unit, onCloseClick: () -> Unit, ) { - val activities by listViewModel.allActivityItems.collectAsStateWithLifecycle() + val activities by listViewModel.filteredActivities.collectAsStateWithLifecycle() val item = activities?.find { it.rawId() == route.id } ?: return diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt index a1a14213f..4b555c9cb 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt @@ -37,6 +37,7 @@ import to.bitkit.ui.theme.AppThemeSurface import to.bitkit.ui.theme.Colors import to.bitkit.viewmodels.ActivityListViewModel import uniffi.bitkitcore.Activity +import androidx.compose.runtime.DisposableEffect @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -61,6 +62,12 @@ fun AllActivityScreen( println("Selected filter tab: $selectedTab") } + DisposableEffect(Unit) { + onDispose { + viewModel.clearFilters() + } + } + AllActivityScreenContent( filteredActivities = filteredActivities, searchText = searchText, diff --git a/app/src/main/java/to/bitkit/viewmodels/ActivityListViewModel.kt b/app/src/main/java/to/bitkit/viewmodels/ActivityListViewModel.kt index 5a006f7cd..a8f918083 100644 --- a/app/src/main/java/to/bitkit/viewmodels/ActivityListViewModel.kt +++ b/app/src/main/java/to/bitkit/viewmodels/ActivityListViewModel.kt @@ -26,9 +26,6 @@ class ActivityListViewModel @Inject constructor( private val lightningRepo: LightningRepo, private val ldkNodeEventBus: LdkNodeEventBus, ) : ViewModel() { - private val _allActivityItems = MutableStateFlow?>(null) - val allActivityItems = _allActivityItems.asStateFlow() - private val _filteredActivities = MutableStateFlow?>(null) val filteredActivities = _filteredActivities.asStateFlow() @@ -68,6 +65,8 @@ class ActivityListViewModel @Inject constructor( private val _availableTags = MutableStateFlow>(emptyList()) val availableTags = _availableTags.asStateFlow() + private var isClearingFilters = false + init { viewModelScope.launch(bgDispatcher) { ldkNodeEventBus.events.collect { @@ -89,7 +88,9 @@ class ActivityListViewModel @Inject constructor( _searchText .debounce(300) .collect { - updateFilteredActivities() + if (!isClearingFilters) { + updateFilteredActivities() + } } } } @@ -98,7 +99,9 @@ class ActivityListViewModel @Inject constructor( viewModelScope.launch { combine(_startDate, _endDate) { _, _ -> } .collect { - updateFilteredActivities() + if (!isClearingFilters) { + updateFilteredActivities() + } } } } @@ -106,7 +109,9 @@ class ActivityListViewModel @Inject constructor( private fun observeSelectedTags() { viewModelScope.launch { _selectedTags.collect { - updateFilteredActivities() + if (!isClearingFilters) { + updateFilteredActivities() + } } } } @@ -118,9 +123,6 @@ class ActivityListViewModel @Inject constructor( val limitLatest = 3u _latestActivities.value = coreService.activity.get(filter = ActivityFilter.ALL, limit = limitLatest) - // Fetch all activities (unfiltered) - _allActivityItems.value = coreService.activity.get(filter = ActivityFilter.ALL) - // Fetch lightning and onchain activities _lightningActivities.value = coreService.activity.get(filter = ActivityFilter.LIGHTNING) _onchainActivities.value = coreService.activity.get(filter = ActivityFilter.ONCHAIN) @@ -173,6 +175,24 @@ class ActivityListViewModel @Inject constructor( _selectedTags.value = mutableSetOf() } + fun clearFilters() { + viewModelScope.launch(bgDispatcher) { + try { + isClearingFilters = true + + _searchText.value = "" + _selectedTags.value = emptySet() + _startDate.value = null + _endDate.value = null + + updateFilteredActivities() + } finally { + // Always re-enable automatic updates + isClearingFilters = false + } + } + } + var isSyncingLdkNodePayments = false fun syncLdkNodePayments() { if (isSyncingLdkNodePayments) { From 49237a341f24b66690ffb1a74c72c592dc88633c Mon Sep 17 00:00:00 2001 From: Ovi Trif Date: Thu, 15 May 2025 22:22:24 +0200 Subject: [PATCH 33/37] fix: Retain state on activity date filter --- .../wallets/activity/DateRangeSelectorSheet.kt | 11 ++++++++++- .../to/bitkit/viewmodels/ActivityListViewModel.kt | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/DateRangeSelectorSheet.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/DateRangeSelectorSheet.kt index 0c62016fb..c6bd786a4 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/DateRangeSelectorSheet.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/DateRangeSelectorSheet.kt @@ -17,6 +17,8 @@ import androidx.compose.material3.DateRangePickerState import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.rememberDateRangePickerState import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -33,10 +35,17 @@ import to.bitkit.ui.theme.Colors @OptIn(ExperimentalMaterial3Api::class) @Composable fun DateRangeSelectorSheet() { - val dateRangeState = rememberDateRangePickerState() val activity = activityListViewModel ?: return val app = appViewModel ?: return + val startDate by activity.startDate.collectAsState() + val endDate by activity.endDate.collectAsState() + + val dateRangeState = rememberDateRangePickerState( + initialSelectedStartDateMillis = startDate, + initialSelectedEndDateMillis = endDate, + ) + DateRangeSelectorSheetContent( dateRangeState = dateRangeState, onClearClick = { diff --git a/app/src/main/java/to/bitkit/viewmodels/ActivityListViewModel.kt b/app/src/main/java/to/bitkit/viewmodels/ActivityListViewModel.kt index a8f918083..00af35cdb 100644 --- a/app/src/main/java/to/bitkit/viewmodels/ActivityListViewModel.kt +++ b/app/src/main/java/to/bitkit/viewmodels/ActivityListViewModel.kt @@ -46,7 +46,7 @@ class ActivityListViewModel @Inject constructor( val startDate = _startDate.asStateFlow() private val _endDate = MutableStateFlow(null) - // val endDate = _endDate.asStateFlow() + val endDate = _endDate.asStateFlow() private val _selectedTags = MutableStateFlow>(emptySet()) val selectedTags = _selectedTags.asStateFlow() From d5d37d3a625852c7cbd4beee2723c9c63ce3b261 Mon Sep 17 00:00:00 2001 From: Ovi Trif Date: Thu, 15 May 2025 22:23:31 +0200 Subject: [PATCH 34/37] feat: Filter activity by selected tab --- .../wallets/activity/AllActivityScreen.kt | 9 ++----- .../viewmodels/ActivityListViewModel.kt | 26 ++++++++++++++++++- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt index 4b555c9cb..7cebaa939 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt @@ -53,15 +53,10 @@ fun AllActivityScreen( val selectedTags by viewModel.selectedTags.collectAsState() val startDate by viewModel.startDate.collectAsState() - var selectedTab by remember { mutableStateOf(ActivityTab.ALL) } + val selectedTab by viewModel.selectedTab.collectAsState() val tabs = ActivityTab.entries val currentTabIndex = tabs.indexOf(selectedTab) - LaunchedEffect(selectedTab) { - // TODO on tab change: update filtered activities - println("Selected filter tab: $selectedTab") - } - DisposableEffect(Unit) { onDispose { viewModel.clearFilters() @@ -76,7 +71,7 @@ fun AllActivityScreen( hasDateRangeFilter = startDate != null, tabs = tabs, currentTabIndex = currentTabIndex, - onTabChange = { selectedTab = tabs[it] }, + onTabChange = { viewModel.setTab(tabs[it]) }, onBackClick = onBackClick, onTagClick = { app.showSheet(BottomSheetType.ActivityTagSelector) }, onDateRangeClick = { app.showSheet(BottomSheetType.ActivityDateRangeSelector) }, diff --git a/app/src/main/java/to/bitkit/viewmodels/ActivityListViewModel.kt b/app/src/main/java/to/bitkit/viewmodels/ActivityListViewModel.kt index 00af35cdb..504f4c697 100644 --- a/app/src/main/java/to/bitkit/viewmodels/ActivityListViewModel.kt +++ b/app/src/main/java/to/bitkit/viewmodels/ActivityListViewModel.kt @@ -18,6 +18,8 @@ import to.bitkit.utils.Logger import uniffi.bitkitcore.Activity import uniffi.bitkitcore.ActivityFilter import javax.inject.Inject +import to.bitkit.ui.screens.wallets.activity.components.ActivityTab +import uniffi.bitkitcore.PaymentType @HiltViewModel class ActivityListViewModel @Inject constructor( @@ -67,6 +69,16 @@ class ActivityListViewModel @Inject constructor( private var isClearingFilters = false + private val _selectedTab = MutableStateFlow(ActivityTab.ALL) + val selectedTab = _selectedTab.asStateFlow() + + fun setTab(tab: ActivityTab) { + _selectedTab.value = tab + viewModelScope.launch(bgDispatcher) { + updateFilteredActivities() + } + } + init { viewModelScope.launch(bgDispatcher) { ldkNodeEventBus.events.collect { @@ -138,13 +150,25 @@ class ActivityListViewModel @Inject constructor( private suspend fun updateFilteredActivities() { try { - _filteredActivities.value = coreService.activity.get( + var txType: PaymentType? = when (_selectedTab.value) { + ActivityTab.SENT -> PaymentType.SENT + ActivityTab.RECEIVED -> PaymentType.RECEIVED + ActivityTab.OTHER, ActivityTab.ALL -> null + } + + val activities = coreService.activity.get( filter = ActivityFilter.ALL, + txType = txType, tags = _selectedTags.value.takeIf { it.isNotEmpty() }?.toList(), search = _searchText.value.takeIf { it.isNotEmpty() }, minDate = _startDate.value?.let { it / 1000 }?.toULong(), maxDate = _endDate.value?.let { it / 1000 }?.toULong(), ) + + _filteredActivities.value = when (_selectedTab.value) { + ActivityTab.OTHER -> activities.filter { it is Activity.Onchain && it.v1.isTransfer } + else -> activities + } } catch (e: Exception) { Logger.error("Failed to filter activities", e) } From ed8cea6f8c0fd9cb1b6d0f3ca34058878dae8f41 Mon Sep 17 00:00:00 2001 From: Ovi Trif Date: Thu, 15 May 2025 22:52:02 +0200 Subject: [PATCH 35/37] refactor: Use bgDispatcher always in activityListViewModel --- .../viewmodels/ActivityListViewModel.kt | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/to/bitkit/viewmodels/ActivityListViewModel.kt b/app/src/main/java/to/bitkit/viewmodels/ActivityListViewModel.kt index 504f4c697..af693ce83 100644 --- a/app/src/main/java/to/bitkit/viewmodels/ActivityListViewModel.kt +++ b/app/src/main/java/to/bitkit/viewmodels/ActivityListViewModel.kt @@ -10,16 +10,17 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import to.bitkit.di.BgDispatcher import to.bitkit.repositories.LightningRepo import to.bitkit.services.CoreService import to.bitkit.services.LdkNodeEventBus +import to.bitkit.ui.screens.wallets.activity.components.ActivityTab import to.bitkit.utils.Logger import uniffi.bitkitcore.Activity import uniffi.bitkitcore.ActivityFilter -import javax.inject.Inject -import to.bitkit.ui.screens.wallets.activity.components.ActivityTab import uniffi.bitkitcore.PaymentType +import javax.inject.Inject @HiltViewModel class ActivityListViewModel @Inject constructor( @@ -96,7 +97,7 @@ class ActivityListViewModel @Inject constructor( @OptIn(FlowPreview::class) private fun observeSearchText() { - viewModelScope.launch { + viewModelScope.launch(bgDispatcher) { _searchText .debounce(300) .collect { @@ -108,7 +109,7 @@ class ActivityListViewModel @Inject constructor( } private fun observeDateRange() { - viewModelScope.launch { + viewModelScope.launch(bgDispatcher) { combine(_startDate, _endDate) { _, _ -> } .collect { if (!isClearingFilters) { @@ -119,7 +120,7 @@ class ActivityListViewModel @Inject constructor( } private fun observeSelectedTags() { - viewModelScope.launch { + viewModelScope.launch(bgDispatcher) { _selectedTags.collect { if (!isClearingFilters) { updateFilteredActivities() @@ -129,7 +130,7 @@ class ActivityListViewModel @Inject constructor( } private fun syncState() { - viewModelScope.launch { + viewModelScope.launch(bgDispatcher) { try { // Fetch latest activities for the home screen val limitLatest = 3u @@ -148,12 +149,12 @@ class ActivityListViewModel @Inject constructor( } } - private suspend fun updateFilteredActivities() { + private suspend fun updateFilteredActivities() = withContext(bgDispatcher) { try { var txType: PaymentType? = when (_selectedTab.value) { ActivityTab.SENT -> PaymentType.SENT ActivityTab.RECEIVED -> PaymentType.RECEIVED - ActivityTab.OTHER, ActivityTab.ALL -> null + else -> null } val activities = coreService.activity.get( @@ -175,7 +176,7 @@ class ActivityListViewModel @Inject constructor( } fun updateAvailableTags() { - viewModelScope.launch { + viewModelScope.launch(bgDispatcher) { try { _availableTags.value = coreService.activity.allPossibleTags() } catch (e: Exception) { From f1e557ffce555b02a1eeda040b06768232fca1fe Mon Sep 17 00:00:00 2001 From: Ovi Trif Date: Thu, 15 May 2025 22:52:48 +0200 Subject: [PATCH 36/37] chore: Cleanup related to activity.totalValue() --- .../bitkit/ui/screens/wallets/activity/ActivityDetailScreen.kt | 3 +-- .../main/java/to/bitkit/viewmodels/ActivityListViewModel.kt | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/ActivityDetailScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/ActivityDetailScreen.kt index d8499f543..4249a9fcf 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/ActivityDetailScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/ActivityDetailScreen.kt @@ -145,7 +145,6 @@ private fun ActivityDetailContent( else -> item.v1.timestamp } } - val totalValue = item.totalValue() val paymentValue = when (item) { is Activity.Lightning -> item.v1.value is Activity.Onchain -> item.v1.value @@ -168,7 +167,7 @@ private fun ActivityDetailContent( .padding(vertical = 16.dp) ) { BalanceHeaderView( - sats = totalValue.toLong(), + sats = item.totalValue().toLong(), prefix = amountPrefix, showBitcoinSymbol = false, modifier = Modifier.weight(1f) diff --git a/app/src/main/java/to/bitkit/viewmodels/ActivityListViewModel.kt b/app/src/main/java/to/bitkit/viewmodels/ActivityListViewModel.kt index af693ce83..97c9595b9 100644 --- a/app/src/main/java/to/bitkit/viewmodels/ActivityListViewModel.kt +++ b/app/src/main/java/to/bitkit/viewmodels/ActivityListViewModel.kt @@ -212,7 +212,6 @@ class ActivityListViewModel @Inject constructor( updateFilteredActivities() } finally { - // Always re-enable automatic updates isClearingFilters = false } } From 5e7b3e626c705c6f9a7931cd0c6d88de1ca24f7b Mon Sep 17 00:00:00 2001 From: Ovi Trif Date: Thu, 15 May 2025 22:59:52 +0200 Subject: [PATCH 37/37] chore: Cleanup in filter sheets --- .../wallets/activity/AllActivityScreen.kt | 17 +++++++---------- .../wallets/activity/DateRangeSelectorSheet.kt | 13 ++++++++----- .../wallets/activity/TagSelectorSheet.kt | 15 ++++++++------- 3 files changed, 23 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt index 7cebaa939..b9e94591e 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt @@ -9,12 +9,9 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.composed import androidx.compose.ui.draw.clip @@ -25,6 +22,7 @@ import androidx.compose.ui.input.pointer.util.VelocityTracker import androidx.compose.ui.res.stringResource 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.ui.appViewModel import to.bitkit.ui.components.BottomSheetType @@ -37,7 +35,6 @@ import to.bitkit.ui.theme.AppThemeSurface import to.bitkit.ui.theme.Colors import to.bitkit.viewmodels.ActivityListViewModel import uniffi.bitkitcore.Activity -import androidx.compose.runtime.DisposableEffect @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -47,13 +44,13 @@ fun AllActivityScreen( onActivityItemClick: (String) -> Unit, ) { val app = appViewModel ?: return - val filteredActivities by viewModel.filteredActivities.collectAsState() + val filteredActivities by viewModel.filteredActivities.collectAsStateWithLifecycle() - val searchText by viewModel.searchText.collectAsState() - val selectedTags by viewModel.selectedTags.collectAsState() - val startDate by viewModel.startDate.collectAsState() + val searchText by viewModel.searchText.collectAsStateWithLifecycle() + val selectedTags by viewModel.selectedTags.collectAsStateWithLifecycle() + val startDate by viewModel.startDate.collectAsStateWithLifecycle() - val selectedTab by viewModel.selectedTab.collectAsState() + val selectedTab by viewModel.selectedTab.collectAsStateWithLifecycle() val tabs = ActivityTab.entries val currentTabIndex = tabs.indexOf(selectedTab) diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/DateRangeSelectorSheet.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/DateRangeSelectorSheet.kt index c6bd786a4..46e7aeaef 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/DateRangeSelectorSheet.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/DateRangeSelectorSheet.kt @@ -22,8 +22,10 @@ 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.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import to.bitkit.R import to.bitkit.ui.activityListViewModel import to.bitkit.ui.appViewModel import to.bitkit.ui.components.PrimaryButton @@ -80,32 +82,33 @@ private fun DateRangeSelectorSheetContent( ) { DateRangePicker( state = dateRangeState, - modifier = Modifier.weight(1f), showModeToggle = false, colors = DatePickerDefaults.colors( containerColor = Color.Transparent, selectedDayContainerColor = Colors.Brand, dayInSelectionRangeContainerColor = Colors.Brand16, ), + modifier = Modifier.weight(1f) ) - Spacer(modifier = Modifier.height(32.dp)) + + Spacer(modifier = Modifier.height(16.dp)) Row( horizontalArrangement = Arrangement.spacedBy(16.dp), modifier = Modifier - .padding(vertical = 16.dp) .fillMaxWidth(), ) { SecondaryButton( onClick = onClearClick, - text = "Clear", + text = stringResource(R.string.wallet__filter_clear), modifier = Modifier.weight(1f), ) PrimaryButton( onClick = onApplyClick, - text = "Apply", + text = stringResource(R.string.wallet__filter_apply), modifier = Modifier.weight(1f), ) } + Spacer(modifier = Modifier.height(16.dp)) } } diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/TagSelectorSheet.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/TagSelectorSheet.kt index 636bdba81..755b97631 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/TagSelectorSheet.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/TagSelectorSheet.kt @@ -15,13 +15,13 @@ import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState 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 import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle import to.bitkit.R import to.bitkit.ui.activityListViewModel import to.bitkit.ui.appViewModel @@ -37,8 +37,8 @@ import to.bitkit.ui.theme.AppThemeSurface fun TagSelectorSheet() { val activity = activityListViewModel ?: return val app = appViewModel ?: return - val availableTags by activity.availableTags.collectAsState() - val selectedTags by activity.selectedTags.collectAsState() + val availableTags by activity.availableTags.collectAsStateWithLifecycle() + val selectedTags by activity.selectedTags.collectAsStateWithLifecycle() TagSelectorSheetContent( availableTags = availableTags, @@ -92,23 +92,24 @@ private fun TagSelectorSheetContent( } Spacer(modifier = Modifier.weight(1f)) + Spacer(modifier = Modifier.height(16.dp)) Row( horizontalArrangement = Arrangement.spacedBy(16.dp), modifier = Modifier - .padding(vertical = 16.dp) - .fillMaxWidth(), + .fillMaxWidth() ) { SecondaryButton( onClick = onClearClick, - text = "Clear", + text = stringResource(R.string.wallet__filter_clear), modifier = Modifier.weight(1f), ) PrimaryButton( onClick = onApplyClick, - text = "Apply", + text = stringResource(R.string.wallet__filter_apply), modifier = Modifier.weight(1f), ) } + Spacer(modifier = Modifier.height(16.dp)) } }