Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
98 commits
Select commit Hold shift + click to select a range
d6ff00a
chore: organize categories in an enum
jvsena42 Sep 26, 2025
852cb98
feat: high balance rule
jvsena42 Sep 26, 2025
d1f827b
chore: move app update sheet logic to home
jvsena42 Sep 26, 2025
da98bcd
feat: backup sheet logic
jvsena42 Sep 29, 2025
d7ece6e
feat: notifications sheet logic
jvsena42 Sep 29, 2025
d3d8f64
feat: quick pay sheet logic
jvsena42 Sep 29, 2025
f109ad9
fix: notifications interval
jvsena42 Sep 29, 2025
03c12c7
feat: trigger timed sheet check
jvsena42 Sep 29, 2025
c93b4bd
feat: quick pay intro sheet
jvsena42 Sep 29, 2025
5daab99
feat: display backup
jvsena42 Sep 29, 2025
61584ca
feat: display update sheet
jvsena42 Sep 29, 2025
4aee4d5
feat: display quick pay sheet
jvsena42 Sep 29, 2025
7e42de3
feat: save notifications preferences
jvsena42 Sep 29, 2025
f4355e2
feat: BackgroundPaymentsIntroScreen.kt
jvsena42 Sep 29, 2025
36b08b1
feat: BackgroundPaymentsIntroSheet.kt
jvsena42 Sep 29, 2025
4150438
feat: BackgroundPaymentsSettings.kt WIP
jvsena42 Sep 29, 2025
f95acc9
feat: BackgroundPaymentsSettings.kt WIP
jvsena42 Sep 29, 2025
ab4419e
feat: NotificationPreview.kt WIP
jvsena42 Sep 30, 2025
57565b3
feat: text style
jvsena42 Sep 30, 2025
7ed0ce8
feat: overlay disabled
jvsena42 Sep 30, 2025
b9fd508
feat: set notification icon
jvsena42 Sep 30, 2025
576e5ff
feat: warning disabled notifications
jvsena42 Sep 30, 2025
13e7750
feat: settings notification settings button
jvsena42 Sep 30, 2025
37e4a61
feat: save notification details preference
jvsena42 Sep 30, 2025
ae1a214
feat: bell icon
jvsena42 Sep 30, 2025
60bcbf1
feat: navigation
jvsena42 Oct 1, 2025
e89ad71
feat: settings navigation
jvsena42 Oct 1, 2025
b7200ef
feat: save permission change
jvsena42 Oct 1, 2025
f6ba2dd
fix: notification update caching
jvsena42 Oct 1, 2025
015cbce
feat: animate description
jvsena42 Oct 1, 2025
40a29c4
feat: handle continue from sheet
jvsena42 Oct 1, 2025
9974a46
chore: logs
jvsena42 Oct 1, 2025
29e8e10
chore: dismiss sheet before navigate
jvsena42 Oct 1, 2025
60896f1
fix: display backup intro
jvsena42 Oct 1, 2025
11223bc
fix: implement bottom sheet to update
jvsena42 Oct 1, 2025
3510a58
fix: quick pay navigation
jvsena42 Oct 1, 2025
bc95f47
chore: code clean up
jvsena42 Oct 1, 2025
ab9c30d
feat: notification body
jvsena42 Oct 1, 2025
3b5c77a
feat: critical update navigation
jvsena42 Oct 1, 2025
d81844e
fix: check delay 2 seconds
jvsena42 Oct 1, 2025
11b1a1b
Merge branch 'master' into feat/time-sheet-polish
jvsena42 Oct 1, 2025
12f1454
chore: code cleanup
jvsena42 Oct 1, 2025
6139161
chore: lint
jvsena42 Oct 1, 2025
f67c3bf
chore: lint
jvsena42 Oct 1, 2025
a4c96d9
chore: scope lazy initialization
jvsena42 Oct 1, 2025
176b35c
fix: label
jvsena42 Oct 1, 2025
d5def7b
fix: don't show quick pay sheet if already enabled
jvsena42 Oct 1, 2025
0135263
fix: small icon
jvsena42 Oct 1, 2025
bd4ed2e
fix: gradient bg
jvsena42 Oct 1, 2025
5ee1d8b
chore: restore comments
jvsena42 Oct 2, 2025
10242d9
chore: vertical spacer
jvsena42 Oct 2, 2025
699e35a
chore: vertical spacer
jvsena42 Oct 2, 2025
c00024b
chore: vertical spacer
jvsena42 Oct 2, 2025
7dca72f
chore: clean imports
jvsena42 Oct 2, 2025
9475c22
fix: don't show notifications sheet if user have no spending balance
jvsena42 Oct 2, 2025
e4d0601
fix: don't show notifications sheet if user have no spending balance
jvsena42 Oct 2, 2025
8ab0a03
fix: empty state update
jvsena42 Oct 2, 2025
76222b8
fix: check quick pay intro as seen after display for the first time
jvsena42 Oct 3, 2025
fcd32c9
chore: imports
jvsena42 Oct 5, 2025
a393204
fix: route name
jvsena42 Oct 6, 2025
b170713
fix: trigger timed cheats check on balance change
jvsena42 Oct 7, 2025
68debe1
fix: implement queue logic
jvsena42 Oct 7, 2025
1cd10a2
fix: sort
jvsena42 Oct 7, 2025
6dc6ffa
chore: lift dismiss callback
jvsena42 Oct 7, 2025
5d5e3cc
chore: logs
jvsena42 Oct 7, 2025
0ea0bf1
chore: implement sheet host
jvsena42 Oct 8, 2025
aadfdfb
chore: lint
jvsena42 Oct 8, 2025
1918111
fix: add a bottom sheet wrapper to don't be render behind TabBar
jvsena42 Oct 8, 2025
c3c87cd
fix: set as seen after dismiss
jvsena42 Oct 8, 2025
f9420c2
fix: reimplement rememberUpdatedState
jvsena42 Oct 8, 2025
06d07d4
fix: checkTimedSheets on balance change
jvsena42 Oct 8, 2025
20e25e9
Merge branch 'master' into feat/time-sheet-polish
jvsena42 Oct 9, 2025
323c067
chore: solve conflicts
jvsena42 Oct 9, 2025
018eced
fix: quickpay intro seen update
jvsena42 Oct 9, 2025
dd80166
Merge branch 'master' into feat/time-sheet-polish
jvsena42 Oct 9, 2025
eb15d59
Merge branch 'master' into feat/time-sheet-polish
jvsena42 Oct 9, 2025
6606ed3
chore: formating
jvsena42 Oct 9, 2025
26f6bdf
Merge branch 'master' into feat/time-sheet-polish
jvsena42 Oct 9, 2025
321f20f
fix: don't clear processed payment list
jvsena42 Oct 10, 2025
2045d30
chore: replace DisposableEffect with LaunchedEffect
jvsena42 Oct 10, 2025
d85b70b
chore: move timed sheets to parent
jvsena42 Oct 10, 2025
b271df8
chore: remove unnecessary state
jvsena42 Oct 10, 2025
627f3df
fix: timed sheet dismiss
jvsena42 Oct 10, 2025
af409e6
chore: remove sheet wrapper
jvsena42 Oct 10, 2025
a58a1a8
chore: lint
jvsena42 Oct 10, 2025
d1c3f96
chore: lint
jvsena42 Oct 10, 2025
b5e63a9
chore: lint
jvsena42 Oct 10, 2025
df850e6
chore: lint
jvsena42 Oct 10, 2025
dd2aa5d
chore: lint
jvsena42 Oct 10, 2025
208bad8
fix: don't display notifications intro if already granted
jvsena42 Oct 10, 2025
0163101
chore: lint
jvsena42 Oct 10, 2025
0c99fea
chore: add comment
jvsena42 Oct 13, 2025
75b09e8
feat: display notifications settings
jvsena42 Oct 13, 2025
a58768b
chore: remove animation
jvsena42 Oct 13, 2025
84e228d
fix: listen for balance change
jvsena42 Oct 13, 2025
c367549
chore: add log
jvsena42 Oct 13, 2025
90aedea
fix: skip queue check
jvsena42 Oct 13, 2025
ad69db7
Merge pull request #435 from synonymdev/chore/move-to-parent
jvsena42 Oct 13, 2025
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
5 changes: 4 additions & 1 deletion app/src/main/java/to/bitkit/data/SettingsStore.kt
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,11 @@ data class SettingsData(
val enableAutoReadClipboard: Boolean = false,
val enableSendAmountWarning: Boolean = false,
val backupVerified: Boolean = false,
val notificationsVerified: Boolean = false,
val dismissedSuggestions: List<String> = emptyList(),
val lastTimeAskedBalanceWarningMillis: Long = 0,
val balanceWarningIgnoredMillis: Long = 0,
val backupWarningIgnoredMillis: Long = 0,
val notificationsIgnoredMillis: Long = 0,
val balanceWarningTimes: Int = 0,
val coinSelectAuto: Boolean = true,
val coinSelectPreference: CoinSelectionPreference = CoinSelectionPreference.BranchAndBound,
Expand Down
11 changes: 10 additions & 1 deletion app/src/main/java/to/bitkit/ui/screens/wallets/HomeUiState.kt
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,15 @@ data class HomeUiState(
val currentPrice: PriceDTO? = null,
val isEditingWidgets: Boolean = false,
val deleteWidgetAlert: WidgetType? = null,
val highBalanceSheetVisible: Boolean = false,
val showEmptyState: Boolean = false,
val timedSheet: TimedSheets? = null,
)

/**@param priority Priority levels for timed sheets (higher number = higher priority)*/
enum class TimedSheets(val priority: Int) {
APP_UPDATE(2),
BACKUP(3),
NOTIFICATIONS(2),
QUICK_PAY(1),
HIGH_BALANCE(2)
}
175 changes: 129 additions & 46 deletions app/src/main/java/to/bitkit/ui/screens/wallets/HomeViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
Expand All @@ -12,9 +13,12 @@
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.datetime.Clock
import to.bitkit.BuildConfig
import to.bitkit.data.SettingsStore
import to.bitkit.data.dto.TransferType
import to.bitkit.di.BgDispatcher
import to.bitkit.models.Suggestion
import to.bitkit.models.WidgetType
import to.bitkit.models.toSuggestionOrNull
Expand All @@ -25,7 +29,9 @@
import to.bitkit.repositories.CurrencyRepo
import to.bitkit.repositories.WalletRepo
import to.bitkit.repositories.WidgetsRepo
import to.bitkit.services.AppUpdaterService
import to.bitkit.ui.screens.widgets.blocks.toWeatherModel
import to.bitkit.utils.Logger
import java.math.BigDecimal
import javax.inject.Inject
import kotlin.time.Duration.Companion.seconds
Expand All @@ -37,6 +43,8 @@
private val settingsStore: SettingsStore,
private val currencyRepo: CurrencyRepo,
private val activityRepo: ActivityRepo,
private val appUpdaterService: AppUpdaterService,
@BgDispatcher private val bgDispatcher: CoroutineDispatcher,
) : ViewModel() {

private val _uiState = MutableStateFlow(HomeUiState())
Expand Down Expand Up @@ -80,20 +88,6 @@
_uiState.update { newState }
}
}

viewModelScope.launch {
combine(
settingsStore.data,
walletRepo.balanceState
) { settings, balanceState ->
_uiState.value.copy(
showEmptyState = settings.showEmptyBalanceView && balanceState.totalSats == 0uL
)
}.collect { newState ->
checkHighBalance()
_uiState.update { newState }
}
}
}

private fun setupArticleRotation() {
Expand Down Expand Up @@ -146,40 +140,130 @@
_currentFact.value = null
}

private fun checkHighBalance() {
if (_uiState.value.highBalanceSheetVisible) return

fun checkTimedSheets() {
viewModelScope.launch {
delay(CHECK_DELAY_MILLISECONDS)
if (_uiState.value.timedSheet != null) return@launch

delay(CHECK_DELAY_MILLIS)

TimedSheets.entries.sortedByDescending { it.priority }.forEach { sheet ->
val displaySheet = when (sheet) {
TimedSheets.APP_UPDATE -> displayAppUpdate()
TimedSheets.BACKUP -> displayBackupSheet()
TimedSheets.NOTIFICATIONS -> displayNotificationSheet()
TimedSheets.QUICK_PAY -> displayQuickPaySheet()
TimedSheets.HIGH_BALANCE -> displayHighBalance()
}
if (displaySheet) {
_uiState.update { it.copy(timedSheet = sheet) }
return@launch
}
}
}
}

val settings = settingsStore.data.first()
val currentTime = Clock.System.now().toEpochMilliseconds()
suspend fun displayQuickPaySheet() : Boolean {
val settings = settingsStore.data.first()
if (settings.quickPayIntroSeen) return false
return walletRepo.balanceState.value.totalLightningSats > 0U
}
suspend fun displayNotificationSheet() : Boolean {
val settings = settingsStore.data.first()

val totalOnChainSats = walletRepo.balanceState.value.totalSats
val balanceUsd = satsToUsd(totalOnChainSats) ?: return@launch
val thresholdReached = balanceUsd > BigDecimal(BALANCE_THRESHOLD_USD)
if (settings.notificationsVerified) return false

val isTimeOutOver = settings.lastTimeAskedBalanceWarningMillis == 0L ||
(currentTime - settings.lastTimeAskedBalanceWarningMillis > ASK_INTERVAL_MILLIS)
val currentTime = Clock.System.now().toEpochMilliseconds()
val isTimeOutOver = settings.notificationsIgnoredMillis == 0L ||
(currentTime - settings.notificationsIgnoredMillis > ONE_DAY_ASK_INTERVAL_MILLIS)

val belowMaxWarnings = settings.balanceWarningTimes < MAX_WARNINGS
val shouldShow = isTimeOutOver

if (thresholdReached && isTimeOutOver && belowMaxWarnings) {
settingsStore.update {
it.copy(
balanceWarningTimes = it.balanceWarningTimes + 1,
lastTimeAskedBalanceWarningMillis = currentTime
)
}
_uiState.update { it.copy(highBalanceSheetVisible = true) }
if (shouldShow) {
settingsStore.update { it.copy(notificationsIgnoredMillis = currentTime) }
}

return shouldShow
}

suspend fun displayBackupSheet() : Boolean {
val settings = settingsStore.data.first()

if (settings.backupVerified) return false

val currentTime = Clock.System.now().toEpochMilliseconds()
val isTimeOutOver = settings.backupWarningIgnoredMillis == 0L ||
(currentTime - settings.backupWarningIgnoredMillis > ONE_DAY_ASK_INTERVAL_MILLIS)

val hasBalance = walletRepo.balanceState.value.totalSats > 0U

val shouldShow = isTimeOutOver && hasBalance

if (shouldShow) {
settingsStore.update { it.copy(backupWarningIgnoredMillis = currentTime) }
}

return shouldShow
}

private suspend fun displayAppUpdate(): Boolean = withContext(bgDispatcher) {
try {
val androidReleaseInfo = appUpdaterService.getReleaseInfo().platforms.android
val currentBuildNumber = BuildConfig.VERSION_CODE

if (androidReleaseInfo.buildNumber <= currentBuildNumber) return@withContext false

if (androidReleaseInfo.isCritical) {
handleCriticalUpdate()
return@withContext false
}

if (!thresholdReached) {
settingsStore.update {
it.copy(balanceWarningTimes = 0)
}
return@withContext true
} catch (e: Exception) {
Logger.warn("Failure fetching new releases", e = e)
return@withContext false
}
}

private suspend fun handleCriticalUpdate() {
// mainScreenEffect( // TODO
// MainScreenEffect.Navigate(
// route = Routes.CriticalUpdate,
// navOptions = navOptions {
// popUpTo(0) { inclusive = true }
// }
// )
// )
}

private suspend fun displayHighBalance(): Boolean {
val settings = settingsStore.data.first()
val currentTime = Clock.System.now().toEpochMilliseconds()

val totalOnChainSats = walletRepo.balanceState.value.totalSats
val balanceUsd = satsToUsd(totalOnChainSats) ?: return false
val thresholdReached = balanceUsd > BigDecimal(BALANCE_THRESHOLD_USD)

val isTimeOutOver = settings.balanceWarningIgnoredMillis == 0L ||
(currentTime - settings.balanceWarningIgnoredMillis > ONE_DAY_ASK_INTERVAL_MILLIS)

val belowMaxWarnings = settings.balanceWarningTimes < MAX_WARNINGS

if (!thresholdReached) {
settingsStore.update {
it.copy(balanceWarningTimes = 0)
}
}

if (thresholdReached && isTimeOutOver && belowMaxWarnings) {
settingsStore.update {
it.copy(
balanceWarningTimes = it.balanceWarningTimes + 1,
balanceWarningIgnoredMillis = currentTime
)
}
return true
}
return false
}

private fun satsToUsd(sats: ULong): BigDecimal? {
Expand All @@ -193,10 +277,6 @@
}
}

fun dismissHighBalanceSheet() {
_uiState.update { it.copy(highBalanceSheetVisible = false) }
}

fun removeSuggestion(suggestion: Suggestion) {
viewModelScope.launch {
settingsStore.addDismissedSuggestion(suggestion)
Expand Down Expand Up @@ -331,10 +411,13 @@
private const val BALANCE_THRESHOLD_USD = 500L
private const val MAX_WARNINGS = 3

/** 1 day - how long this prompt will be hidden if user taps Later*/
private const val ASK_INTERVAL_MILLIS = 1000 * 60 * 60 * 24
/** how long this prompt will be hidden if user taps Later*/
private const val ONE_DAY_ASK_INTERVAL_MILLIS = 1000 * 60 * 60 * 24

/** how long this prompt will be hidden if user taps Later*/
private const val ONE_WEEK_ASK_INTERVAL_MILLIS = ONE_DAY_ASK_INTERVAL_MILLIS * 7

/**How long user needs to stay on the home screen before he will see this prompt*/
private const val CHECK_DELAY_MILLISECONDS = 2500L
/**How long user needs to stay on the home screen before he see this prompt*/
private const val CHECK_DELAY_MILLIS = 2500L
}
}
28 changes: 0 additions & 28 deletions app/src/main/java/to/bitkit/viewmodels/AppViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,6 @@ class AppViewModel @Inject constructor(
private val blocktankRepo: BlocktankRepo,
private val connectivityRepo: ConnectivityRepo,
private val healthRepo: HealthRepo,
private val appUpdaterService: AppUpdaterService,
) : ViewModel() {
val healthState = healthRepo.healthState

Expand Down Expand Up @@ -197,7 +196,6 @@ class AppViewModel @Inject constructor(

observeLdkNodeEvents()
observeSendEvents()
fetchNewReleases()
}

private fun observeLdkNodeEvents() {
Expand Down Expand Up @@ -1466,32 +1464,6 @@ class AppViewModel @Inject constructor(
}
}

private fun fetchNewReleases() {
viewModelScope.launch(bgDispatcher) {
runCatching {
val androidReleaseInfo = appUpdaterService.getReleaseInfo().platforms.android
val currentBuildNumber = BuildConfig.VERSION_CODE

if (androidReleaseInfo.buildNumber <= currentBuildNumber) return@launch

if (androidReleaseInfo.isCritical) {
mainScreenEffect(
MainScreenEffect.Navigate(
route = Routes.CriticalUpdate,
navOptions = navOptions {
popUpTo(0) { inclusive = true }
}
)
)
} else {
showSheet(sheetType = Sheet.Update)
}
}.onFailure { e ->
Logger.warn("Failure fetching new releases", e = e)
}
}
}

private fun onConfirmAmountWarning(warning: SanityWarning) {
viewModelScope.launch {
_sendUiState.update {
Expand Down