Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 99 additions & 0 deletions app/src/main/java/to/bitkit/repositories/SuggestionsRepo.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package to.bitkit.repositories

import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.withContext
import to.bitkit.data.SettingsData
import to.bitkit.data.SettingsStore
import to.bitkit.data.entities.TransferEntity
import to.bitkit.di.BgDispatcher
import to.bitkit.models.Suggestion
import to.bitkit.models.TransferType
import to.bitkit.models.toSuggestionOrNull
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class SuggestionsRepo @Inject constructor(
@BgDispatcher private val bgDispatcher: CoroutineDispatcher,
private val walletRepo: WalletRepo,
private val settingsStore: SettingsStore,
private val transferRepo: TransferRepo,
private val pubkyRepo: PubkyRepo,
) {
companion object {
private const val MAX_SUGGESTIONS = 4
}

val suggestionsFlow: Flow<List<Suggestion>> = combine(
walletRepo.balanceState,
settingsStore.data,
transferRepo.activeTransfers,
pubkyRepo.isAuthenticated,
) { balanceState, settings, transfers, profileAuthenticated ->
val baseSuggestions = when {
balanceState.totalLightningSats > 0uL ->
spendingSuggestions(settings, profileAuthenticated)
balanceState.totalOnchainSats > 0uL ->
savingsOnlySuggestions(settings, transfers, profileAuthenticated)
else -> emptyWalletSuggestions(settings, transfers, profileAuthenticated)
}
val dismissedList = settings.dismissedSuggestions.mapNotNull { it.toSuggestionOrNull() }
baseSuggestions
.filterNot { it in dismissedList }
.take(MAX_SUGGESTIONS)
}

suspend fun resetDismissedSuggestionsIfEmpty() = withContext(bgDispatcher) {
if (suggestionsFlow.first().isEmpty()) {
settingsStore.update { it.copy(dismissedSuggestions = emptyList()) }
}
}

private fun spendingSuggestions(
settings: SettingsData,
profileAuthenticated: Boolean,
) = listOfNotNull(
Suggestion.QUICK_PAY.takeIf { !settings.isQuickPayEnabled },
Suggestion.NOTIFICATIONS.takeIf { !settings.notificationsGranted },
Suggestion.SHOP,
Suggestion.PROFILE.takeIf { !profileAuthenticated },
Suggestion.SUPPORT,
Suggestion.INVITE,
Suggestion.BUY,
)

private fun savingsOnlySuggestions(
settings: SettingsData,
transfers: List<TransferEntity>,
profileAuthenticated: Boolean,
) = listOfNotNull(
Suggestion.BACK_UP.takeIf { !settings.backupVerified },
Suggestion.SECURE.takeIf { !settings.isPinEnabled },
Suggestion.LIGHTNING.takeIf {
transfers.all { it.type != TransferType.TO_SPENDING }
},
Suggestion.SUPPORT,
Suggestion.PROFILE.takeIf { !profileAuthenticated },
Suggestion.INVITE,
Suggestion.BUY,
)

private fun emptyWalletSuggestions(
settings: SettingsData,
transfers: List<TransferEntity>,
profileAuthenticated: Boolean,
) = listOfNotNull(
Suggestion.BUY,
Suggestion.LIGHTNING.takeIf {
transfers.all { it.type != TransferType.TO_SPENDING }
},
Suggestion.SUPPORT,
Suggestion.BACK_UP.takeIf { !settings.backupVerified },
Suggestion.SECURE.takeIf { !settings.isPinEnabled },
Suggestion.PROFILE.takeIf { !profileAuthenticated },
Suggestion.INVITE,
)
}
14 changes: 10 additions & 4 deletions app/src/main/java/to/bitkit/ui/screens/wallets/HomeScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ import to.bitkit.ui.screens.widgets.headlines.HeadlineCard
import to.bitkit.ui.screens.widgets.headlines.HeadlineCardSmall
import to.bitkit.ui.screens.widgets.price.PriceCard
import to.bitkit.ui.screens.widgets.price.PriceCardSmall
import to.bitkit.ui.screens.widgets.suggestions.SuggestionsPreviewGrid
import to.bitkit.ui.screens.widgets.weather.WeatherCard
import to.bitkit.ui.screens.widgets.weather.WeatherCardSmall
import to.bitkit.ui.shared.modifiers.clickableAlpha
Expand Down Expand Up @@ -1174,15 +1175,20 @@ private fun WidgetCardContent(
}

WidgetType.SUGGESTIONS -> {
if (homeUiState.suggestions.isEmpty()) {
WidgetEditPlaceholder(small = false)
} else {
SuggestionsSection(
when {
homeUiState.suggestions.isNotEmpty() -> SuggestionsSection(
suggestions = homeUiState.suggestions,
onRemoveSuggestion = onRemoveSuggestion,
onClickSuggestion = onClickSuggestion,
modifier = Modifier.fillMaxWidth()
)

homeUiState.isEditingWidgets -> SuggestionsPreviewGrid(
onSuggestionClick = {},
modifier = Modifier.fillMaxWidth()
)

else -> WidgetEditPlaceholder(small = false)
}
}
}
Expand Down
76 changes: 3 additions & 73 deletions app/src/main/java/to/bitkit/ui/screens/wallets/HomeViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,18 @@ import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import to.bitkit.R
import to.bitkit.data.SettingsData
import to.bitkit.data.SettingsStore
import to.bitkit.data.entities.TransferEntity
import to.bitkit.models.ActivityBannerType
import to.bitkit.models.BannerItem
import to.bitkit.models.Suggestion
import to.bitkit.models.TransferType
import to.bitkit.models.WidgetType
import to.bitkit.models.toSuggestionOrNull
import to.bitkit.models.widget.ArticleModel
import to.bitkit.models.widget.toArticleModel
import to.bitkit.models.widget.toBlockModel
import to.bitkit.repositories.ActivityRepo
import to.bitkit.repositories.CurrencyRepo
import to.bitkit.repositories.PubkyRepo
import to.bitkit.repositories.SuggestionsRepo
import to.bitkit.repositories.TransferRepo
import to.bitkit.repositories.WalletRepo
import to.bitkit.repositories.WidgetsRepo
Expand All @@ -51,12 +48,9 @@ class HomeViewModel @Inject constructor(
private val transferRepo: TransferRepo,
private val pubkyRepo: PubkyRepo,
private val activityRepo: ActivityRepo,
private val suggestionsRepo: SuggestionsRepo,
) : ViewModel() {

companion object {
private const val MAX_SUGGESTIONS = 4
}

private val _uiState = MutableStateFlow(HomeUiState())
val uiState: StateFlow<HomeUiState> = _uiState.asStateFlow()

Expand Down Expand Up @@ -115,7 +109,7 @@ class HomeViewModel @Inject constructor(
}

viewModelScope.launch {
createSuggestionsFlow().collect { suggestions ->
suggestionsRepo.suggestionsFlow.collect { suggestions ->
_uiState.update { it.copy(suggestions = suggestions.toImmutableList()) }
}
}
Expand Down Expand Up @@ -306,68 +300,4 @@ class HomeViewModel @Inject constructor(
_uiState.update { it.copy(banners = banners.toImmutableList()) }
}
}

private fun createSuggestionsFlow() = combine(
walletRepo.balanceState,
settingsStore.data,
transferRepo.activeTransfers,
pubkyRepo.isAuthenticated,
) { balanceState, settings, transfers, profileAuthenticated ->
val baseSuggestions = when {
balanceState.totalLightningSats > 0uL ->
spendingSuggestions(settings, profileAuthenticated)
balanceState.totalOnchainSats > 0uL ->
savingsOnlySuggestions(settings, transfers, profileAuthenticated)
else -> emptyWalletSuggestions(settings, transfers, profileAuthenticated)
}
val dismissedList = settings.dismissedSuggestions.mapNotNull { it.toSuggestionOrNull() }
baseSuggestions
.filterNot { it in dismissedList }
.take(MAX_SUGGESTIONS)
}

private fun spendingSuggestions(
settings: SettingsData,
profileAuthenticated: Boolean,
) = listOfNotNull(
Suggestion.QUICK_PAY.takeIf { !settings.isQuickPayEnabled },
Suggestion.NOTIFICATIONS.takeIf { !settings.notificationsGranted },
Suggestion.SHOP,
Suggestion.PROFILE.takeIf { !profileAuthenticated },
Suggestion.SUPPORT,
Suggestion.INVITE,
Suggestion.BUY,
)

private fun savingsOnlySuggestions(
settings: SettingsData,
transfers: List<TransferEntity>,
profileAuthenticated: Boolean,
) = listOfNotNull(
Suggestion.BACK_UP.takeIf { !settings.backupVerified },
Suggestion.SECURE.takeIf { !settings.isPinEnabled },
Suggestion.LIGHTNING.takeIf {
transfers.all { it.type != TransferType.TO_SPENDING }
},
Suggestion.SUPPORT,
Suggestion.PROFILE.takeIf { !profileAuthenticated },
Suggestion.INVITE,
Suggestion.BUY,
)

private fun emptyWalletSuggestions(
settings: SettingsData,
transfers: List<TransferEntity>,
profileAuthenticated: Boolean,
) = listOfNotNull(
Suggestion.BUY,
Suggestion.LIGHTNING.takeIf {
transfers.all { it.type != TransferType.TO_SPENDING }
},
Suggestion.SUPPORT,
Suggestion.BACK_UP.takeIf { !settings.backupVerified },
Suggestion.SECURE.takeIf { !settings.isPinEnabled },
Suggestion.PROFILE.takeIf { !profileAuthenticated },
Suggestion.INVITE,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import to.bitkit.models.WidgetType
import to.bitkit.repositories.SuggestionsRepo
import to.bitkit.repositories.WidgetsRepo
import javax.inject.Inject

@HiltViewModel
class SuggestionsViewModel @Inject constructor(
private val widgetsRepo: WidgetsRepo,
private val suggestionsRepo: SuggestionsRepo,
) : ViewModel() {

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

fun addWidget(onComplete: () -> Unit = {}) {
viewModelScope.launch {
suggestionsRepo.resetDismissedSuggestionsIfEmpty()
widgetsRepo.addWidget(WidgetType.SUGGESTIONS)
onComplete()
}
Expand Down
78 changes: 78 additions & 0 deletions app/src/test/java/to/bitkit/repositories/SuggestionsRepoTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package to.bitkit.repositories

import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flowOf
import org.junit.Before
import org.junit.Test
import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
import to.bitkit.data.SettingsData
import to.bitkit.data.SettingsStore
import to.bitkit.models.BalanceState
import to.bitkit.models.Suggestion
import to.bitkit.test.BaseUnitTest
import kotlin.test.assertTrue

class SuggestionsRepoTest : BaseUnitTest() {
private lateinit var sut: SuggestionsRepo

private val walletRepo = mock<WalletRepo>()
private val settingsStore = mock<SettingsStore>()
private val transferRepo = mock<TransferRepo>()
private val pubkyRepo = mock<PubkyRepo>()

private fun setUp(settings: SettingsData) {
whenever(walletRepo.balanceState).thenReturn(MutableStateFlow(BalanceState()))
whenever(settingsStore.data).thenReturn(flowOf(settings))
whenever(transferRepo.activeTransfers).thenReturn(flowOf(emptyList()))
whenever(pubkyRepo.isAuthenticated).thenReturn(MutableStateFlow(false))

sut = SuggestionsRepo(
bgDispatcher = testDispatcher,
walletRepo = walletRepo,
settingsStore = settingsStore,
transferRepo = transferRepo,
pubkyRepo = pubkyRepo,
)
}

@Before
fun before() {
setUp(SettingsData())
}

@Test
fun `resetDismissedSuggestionsIfEmpty clears dismissed when no suggestions are visible`() = test {
setUp(SettingsData(dismissedSuggestions = Suggestion.entries.map { it.name }))

sut.resetDismissedSuggestionsIfEmpty()

val captor = argumentCaptor<(SettingsData) -> SettingsData>()
verify(settingsStore).update(captor.capture())
val result = captor.firstValue(SettingsData(dismissedSuggestions = listOf(Suggestion.BUY.name)))
assertTrue(result.dismissedSuggestions.isEmpty())
}

@Test
fun `resetDismissedSuggestionsIfEmpty does nothing when suggestions are visible`() = test {
setUp(SettingsData(dismissedSuggestions = emptyList()))

sut.resetDismissedSuggestionsIfEmpty()

verify(settingsStore, never()).update(any())
}

@Test
fun `suggestionsFlow filters out dismissed suggestions`() = test {
setUp(SettingsData(dismissedSuggestions = listOf(Suggestion.BUY.name)))

val suggestions = sut.suggestionsFlow.first()

assertTrue(Suggestion.BUY !in suggestions)
}
}
1 change: 1 addition & 0 deletions changelog.d/next/1016.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Re-adding the Suggestions widget now restores the default suggestion cards when all of them had been dismissed.
Loading