Skip to content

Commit 29a99f1

Browse files
authored
Merge pull request #1016 from synonymdev/fix/reset-suggestions-when-empty
fix: reset suggestions widget when re-added empty
2 parents 437c683 + b627b73 commit 29a99f1

6 files changed

Lines changed: 194 additions & 77 deletions

File tree

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package to.bitkit.repositories
2+
3+
import kotlinx.coroutines.CoroutineDispatcher
4+
import kotlinx.coroutines.flow.Flow
5+
import kotlinx.coroutines.flow.combine
6+
import kotlinx.coroutines.flow.first
7+
import kotlinx.coroutines.withContext
8+
import to.bitkit.data.SettingsData
9+
import to.bitkit.data.SettingsStore
10+
import to.bitkit.data.entities.TransferEntity
11+
import to.bitkit.di.BgDispatcher
12+
import to.bitkit.models.Suggestion
13+
import to.bitkit.models.TransferType
14+
import to.bitkit.models.toSuggestionOrNull
15+
import javax.inject.Inject
16+
import javax.inject.Singleton
17+
18+
@Singleton
19+
class SuggestionsRepo @Inject constructor(
20+
@BgDispatcher private val bgDispatcher: CoroutineDispatcher,
21+
private val walletRepo: WalletRepo,
22+
private val settingsStore: SettingsStore,
23+
private val transferRepo: TransferRepo,
24+
private val pubkyRepo: PubkyRepo,
25+
) {
26+
companion object {
27+
private const val MAX_SUGGESTIONS = 4
28+
}
29+
30+
val suggestionsFlow: Flow<List<Suggestion>> = combine(
31+
walletRepo.balanceState,
32+
settingsStore.data,
33+
transferRepo.activeTransfers,
34+
pubkyRepo.isAuthenticated,
35+
) { balanceState, settings, transfers, profileAuthenticated ->
36+
val baseSuggestions = when {
37+
balanceState.totalLightningSats > 0uL ->
38+
spendingSuggestions(settings, profileAuthenticated)
39+
balanceState.totalOnchainSats > 0uL ->
40+
savingsOnlySuggestions(settings, transfers, profileAuthenticated)
41+
else -> emptyWalletSuggestions(settings, transfers, profileAuthenticated)
42+
}
43+
val dismissedList = settings.dismissedSuggestions.mapNotNull { it.toSuggestionOrNull() }
44+
baseSuggestions
45+
.filterNot { it in dismissedList }
46+
.take(MAX_SUGGESTIONS)
47+
}
48+
49+
suspend fun resetDismissedSuggestionsIfEmpty() = withContext(bgDispatcher) {
50+
if (suggestionsFlow.first().isEmpty()) {
51+
settingsStore.update { it.copy(dismissedSuggestions = emptyList()) }
52+
}
53+
}
54+
55+
private fun spendingSuggestions(
56+
settings: SettingsData,
57+
profileAuthenticated: Boolean,
58+
) = listOfNotNull(
59+
Suggestion.QUICK_PAY.takeIf { !settings.isQuickPayEnabled },
60+
Suggestion.NOTIFICATIONS.takeIf { !settings.notificationsGranted },
61+
Suggestion.SHOP,
62+
Suggestion.PROFILE.takeIf { !profileAuthenticated },
63+
Suggestion.SUPPORT,
64+
Suggestion.INVITE,
65+
Suggestion.BUY,
66+
)
67+
68+
private fun savingsOnlySuggestions(
69+
settings: SettingsData,
70+
transfers: List<TransferEntity>,
71+
profileAuthenticated: Boolean,
72+
) = listOfNotNull(
73+
Suggestion.BACK_UP.takeIf { !settings.backupVerified },
74+
Suggestion.SECURE.takeIf { !settings.isPinEnabled },
75+
Suggestion.LIGHTNING.takeIf {
76+
transfers.all { it.type != TransferType.TO_SPENDING }
77+
},
78+
Suggestion.SUPPORT,
79+
Suggestion.PROFILE.takeIf { !profileAuthenticated },
80+
Suggestion.INVITE,
81+
Suggestion.BUY,
82+
)
83+
84+
private fun emptyWalletSuggestions(
85+
settings: SettingsData,
86+
transfers: List<TransferEntity>,
87+
profileAuthenticated: Boolean,
88+
) = listOfNotNull(
89+
Suggestion.BUY,
90+
Suggestion.LIGHTNING.takeIf {
91+
transfers.all { it.type != TransferType.TO_SPENDING }
92+
},
93+
Suggestion.SUPPORT,
94+
Suggestion.BACK_UP.takeIf { !settings.backupVerified },
95+
Suggestion.SECURE.takeIf { !settings.isPinEnabled },
96+
Suggestion.PROFILE.takeIf { !profileAuthenticated },
97+
Suggestion.INVITE,
98+
)
99+
}

app/src/main/java/to/bitkit/ui/screens/wallets/HomeScreen.kt

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ import to.bitkit.ui.screens.widgets.headlines.HeadlineCard
168168
import to.bitkit.ui.screens.widgets.headlines.HeadlineCardSmall
169169
import to.bitkit.ui.screens.widgets.price.PriceCard
170170
import to.bitkit.ui.screens.widgets.price.PriceCardSmall
171+
import to.bitkit.ui.screens.widgets.suggestions.SuggestionsPreviewGrid
171172
import to.bitkit.ui.screens.widgets.weather.WeatherCard
172173
import to.bitkit.ui.screens.widgets.weather.WeatherCardSmall
173174
import to.bitkit.ui.shared.modifiers.clickableAlpha
@@ -1174,15 +1175,20 @@ private fun WidgetCardContent(
11741175
}
11751176

11761177
WidgetType.SUGGESTIONS -> {
1177-
if (homeUiState.suggestions.isEmpty()) {
1178-
WidgetEditPlaceholder(small = false)
1179-
} else {
1180-
SuggestionsSection(
1178+
when {
1179+
homeUiState.suggestions.isNotEmpty() -> SuggestionsSection(
11811180
suggestions = homeUiState.suggestions,
11821181
onRemoveSuggestion = onRemoveSuggestion,
11831182
onClickSuggestion = onClickSuggestion,
11841183
modifier = Modifier.fillMaxWidth()
11851184
)
1185+
1186+
homeUiState.isEditingWidgets -> SuggestionsPreviewGrid(
1187+
onSuggestionClick = {},
1188+
modifier = Modifier.fillMaxWidth()
1189+
)
1190+
1191+
else -> WidgetEditPlaceholder(small = false)
11861192
}
11871193
}
11881194
}

app/src/main/java/to/bitkit/ui/screens/wallets/HomeViewModel.kt

Lines changed: 3 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -18,21 +18,18 @@ import kotlinx.coroutines.flow.mapLatest
1818
import kotlinx.coroutines.flow.update
1919
import kotlinx.coroutines.launch
2020
import to.bitkit.R
21-
import to.bitkit.data.SettingsData
2221
import to.bitkit.data.SettingsStore
23-
import to.bitkit.data.entities.TransferEntity
2422
import to.bitkit.models.ActivityBannerType
2523
import to.bitkit.models.BannerItem
2624
import to.bitkit.models.Suggestion
27-
import to.bitkit.models.TransferType
2825
import to.bitkit.models.WidgetType
29-
import to.bitkit.models.toSuggestionOrNull
3026
import to.bitkit.models.widget.ArticleModel
3127
import to.bitkit.models.widget.toArticleModel
3228
import to.bitkit.models.widget.toBlockModel
3329
import to.bitkit.repositories.ActivityRepo
3430
import to.bitkit.repositories.CurrencyRepo
3531
import to.bitkit.repositories.PubkyRepo
32+
import to.bitkit.repositories.SuggestionsRepo
3633
import to.bitkit.repositories.TransferRepo
3734
import to.bitkit.repositories.WalletRepo
3835
import to.bitkit.repositories.WidgetsRepo
@@ -51,12 +48,9 @@ class HomeViewModel @Inject constructor(
5148
private val transferRepo: TransferRepo,
5249
private val pubkyRepo: PubkyRepo,
5350
private val activityRepo: ActivityRepo,
51+
private val suggestionsRepo: SuggestionsRepo,
5452
) : ViewModel() {
5553

56-
companion object {
57-
private const val MAX_SUGGESTIONS = 4
58-
}
59-
6054
private val _uiState = MutableStateFlow(HomeUiState())
6155
val uiState: StateFlow<HomeUiState> = _uiState.asStateFlow()
6256

@@ -115,7 +109,7 @@ class HomeViewModel @Inject constructor(
115109
}
116110

117111
viewModelScope.launch {
118-
createSuggestionsFlow().collect { suggestions ->
112+
suggestionsRepo.suggestionsFlow.collect { suggestions ->
119113
_uiState.update { it.copy(suggestions = suggestions.toImmutableList()) }
120114
}
121115
}
@@ -306,68 +300,4 @@ class HomeViewModel @Inject constructor(
306300
_uiState.update { it.copy(banners = banners.toImmutableList()) }
307301
}
308302
}
309-
310-
private fun createSuggestionsFlow() = combine(
311-
walletRepo.balanceState,
312-
settingsStore.data,
313-
transferRepo.activeTransfers,
314-
pubkyRepo.isAuthenticated,
315-
) { balanceState, settings, transfers, profileAuthenticated ->
316-
val baseSuggestions = when {
317-
balanceState.totalLightningSats > 0uL ->
318-
spendingSuggestions(settings, profileAuthenticated)
319-
balanceState.totalOnchainSats > 0uL ->
320-
savingsOnlySuggestions(settings, transfers, profileAuthenticated)
321-
else -> emptyWalletSuggestions(settings, transfers, profileAuthenticated)
322-
}
323-
val dismissedList = settings.dismissedSuggestions.mapNotNull { it.toSuggestionOrNull() }
324-
baseSuggestions
325-
.filterNot { it in dismissedList }
326-
.take(MAX_SUGGESTIONS)
327-
}
328-
329-
private fun spendingSuggestions(
330-
settings: SettingsData,
331-
profileAuthenticated: Boolean,
332-
) = listOfNotNull(
333-
Suggestion.QUICK_PAY.takeIf { !settings.isQuickPayEnabled },
334-
Suggestion.NOTIFICATIONS.takeIf { !settings.notificationsGranted },
335-
Suggestion.SHOP,
336-
Suggestion.PROFILE.takeIf { !profileAuthenticated },
337-
Suggestion.SUPPORT,
338-
Suggestion.INVITE,
339-
Suggestion.BUY,
340-
)
341-
342-
private fun savingsOnlySuggestions(
343-
settings: SettingsData,
344-
transfers: List<TransferEntity>,
345-
profileAuthenticated: Boolean,
346-
) = listOfNotNull(
347-
Suggestion.BACK_UP.takeIf { !settings.backupVerified },
348-
Suggestion.SECURE.takeIf { !settings.isPinEnabled },
349-
Suggestion.LIGHTNING.takeIf {
350-
transfers.all { it.type != TransferType.TO_SPENDING }
351-
},
352-
Suggestion.SUPPORT,
353-
Suggestion.PROFILE.takeIf { !profileAuthenticated },
354-
Suggestion.INVITE,
355-
Suggestion.BUY,
356-
)
357-
358-
private fun emptyWalletSuggestions(
359-
settings: SettingsData,
360-
transfers: List<TransferEntity>,
361-
profileAuthenticated: Boolean,
362-
) = listOfNotNull(
363-
Suggestion.BUY,
364-
Suggestion.LIGHTNING.takeIf {
365-
transfers.all { it.type != TransferType.TO_SPENDING }
366-
},
367-
Suggestion.SUPPORT,
368-
Suggestion.BACK_UP.takeIf { !settings.backupVerified },
369-
Suggestion.SECURE.takeIf { !settings.isPinEnabled },
370-
Suggestion.PROFILE.takeIf { !profileAuthenticated },
371-
Suggestion.INVITE,
372-
)
373303
}

app/src/main/java/to/bitkit/ui/screens/widgets/suggestions/SuggestionsViewModel.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@ import kotlinx.coroutines.flow.map
99
import kotlinx.coroutines.flow.stateIn
1010
import kotlinx.coroutines.launch
1111
import to.bitkit.models.WidgetType
12+
import to.bitkit.repositories.SuggestionsRepo
1213
import to.bitkit.repositories.WidgetsRepo
1314
import javax.inject.Inject
1415

1516
@HiltViewModel
1617
class SuggestionsViewModel @Inject constructor(
1718
private val widgetsRepo: WidgetsRepo,
19+
private val suggestionsRepo: SuggestionsRepo,
1820
) : ViewModel() {
1921

2022
companion object {
@@ -29,6 +31,7 @@ class SuggestionsViewModel @Inject constructor(
2931

3032
fun addWidget(onComplete: () -> Unit = {}) {
3133
viewModelScope.launch {
34+
suggestionsRepo.resetDismissedSuggestionsIfEmpty()
3235
widgetsRepo.addWidget(WidgetType.SUGGESTIONS)
3336
onComplete()
3437
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package to.bitkit.repositories
2+
3+
import kotlinx.coroutines.flow.MutableStateFlow
4+
import kotlinx.coroutines.flow.first
5+
import kotlinx.coroutines.flow.flowOf
6+
import org.junit.Before
7+
import org.junit.Test
8+
import org.mockito.kotlin.any
9+
import org.mockito.kotlin.argumentCaptor
10+
import org.mockito.kotlin.mock
11+
import org.mockito.kotlin.never
12+
import org.mockito.kotlin.verify
13+
import org.mockito.kotlin.whenever
14+
import to.bitkit.data.SettingsData
15+
import to.bitkit.data.SettingsStore
16+
import to.bitkit.models.BalanceState
17+
import to.bitkit.models.Suggestion
18+
import to.bitkit.test.BaseUnitTest
19+
import kotlin.test.assertTrue
20+
21+
class SuggestionsRepoTest : BaseUnitTest() {
22+
private lateinit var sut: SuggestionsRepo
23+
24+
private val walletRepo = mock<WalletRepo>()
25+
private val settingsStore = mock<SettingsStore>()
26+
private val transferRepo = mock<TransferRepo>()
27+
private val pubkyRepo = mock<PubkyRepo>()
28+
29+
private fun setUp(settings: SettingsData) {
30+
whenever(walletRepo.balanceState).thenReturn(MutableStateFlow(BalanceState()))
31+
whenever(settingsStore.data).thenReturn(flowOf(settings))
32+
whenever(transferRepo.activeTransfers).thenReturn(flowOf(emptyList()))
33+
whenever(pubkyRepo.isAuthenticated).thenReturn(MutableStateFlow(false))
34+
35+
sut = SuggestionsRepo(
36+
bgDispatcher = testDispatcher,
37+
walletRepo = walletRepo,
38+
settingsStore = settingsStore,
39+
transferRepo = transferRepo,
40+
pubkyRepo = pubkyRepo,
41+
)
42+
}
43+
44+
@Before
45+
fun before() {
46+
setUp(SettingsData())
47+
}
48+
49+
@Test
50+
fun `resetDismissedSuggestionsIfEmpty clears dismissed when no suggestions are visible`() = test {
51+
setUp(SettingsData(dismissedSuggestions = Suggestion.entries.map { it.name }))
52+
53+
sut.resetDismissedSuggestionsIfEmpty()
54+
55+
val captor = argumentCaptor<(SettingsData) -> SettingsData>()
56+
verify(settingsStore).update(captor.capture())
57+
val result = captor.firstValue(SettingsData(dismissedSuggestions = listOf(Suggestion.BUY.name)))
58+
assertTrue(result.dismissedSuggestions.isEmpty())
59+
}
60+
61+
@Test
62+
fun `resetDismissedSuggestionsIfEmpty does nothing when suggestions are visible`() = test {
63+
setUp(SettingsData(dismissedSuggestions = emptyList()))
64+
65+
sut.resetDismissedSuggestionsIfEmpty()
66+
67+
verify(settingsStore, never()).update(any())
68+
}
69+
70+
@Test
71+
fun `suggestionsFlow filters out dismissed suggestions`() = test {
72+
setUp(SettingsData(dismissedSuggestions = listOf(Suggestion.BUY.name)))
73+
74+
val suggestions = sut.suggestionsFlow.first()
75+
76+
assertTrue(Suggestion.BUY !in suggestions)
77+
}
78+
}

changelog.d/next/1016.fixed.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Re-adding the Suggestions widget now restores the default suggestion cards when all of them had been dismissed.

0 commit comments

Comments
 (0)