Skip to content

Commit 778e580

Browse files
authored
Merge pull request #413 from synonymdev/feat/time-sheet-polish
Timed sheets
2 parents d8ba756 + ad69db7 commit 778e580

30 files changed

+1104
-218
lines changed

app/src/main/java/to/bitkit/data/SettingsStore.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ data class SettingsData(
8080
val hasSeenShopIntro: Boolean = false,
8181
val hasSeenProfileIntro: Boolean = false,
8282
val quickPayIntroSeen: Boolean = false,
83+
val bgPaymentsIntroSeen: Boolean = false,
8384
val isQuickPayEnabled: Boolean = false,
8485
val quickPayAmount: Int = 5,
8586
val lightningSetupStep: Int = 0,
@@ -99,8 +100,12 @@ data class SettingsData(
99100
val enableAutoReadClipboard: Boolean = false,
100101
val enableSendAmountWarning: Boolean = false,
101102
val backupVerified: Boolean = false,
103+
val notificationsGranted: Boolean = false,
104+
val showNotificationDetails: Boolean = true,
102105
val dismissedSuggestions: List<String> = emptyList(),
103-
val lastTimeAskedBalanceWarningMillis: Long = 0,
106+
val balanceWarningIgnoredMillis: Long = 0,
107+
val backupWarningIgnoredMillis: Long = 0,
108+
val notificationsIgnoredMillis: Long = 0,
104109
val balanceWarningTimes: Int = 0,
105110
val coinSelectAuto: Boolean = true,
106111
val coinSelectPreference: CoinSelectionPreference = CoinSelectionPreference.BranchAndBound,

app/src/main/java/to/bitkit/fcm/WakeNodeWorker.kt

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,17 @@ import androidx.work.workDataOf
88
import dagger.assisted.Assisted
99
import dagger.assisted.AssistedInject
1010
import kotlinx.coroutines.CompletableDeferred
11+
import kotlinx.coroutines.flow.first
1112
import kotlinx.coroutines.withTimeout
1213
import kotlinx.serialization.json.JsonObject
1314
import kotlinx.serialization.json.JsonPrimitive
1415
import kotlinx.serialization.json.contentOrNull
1516
import kotlinx.serialization.json.jsonObject
1617
import org.lightningdevkit.ldknode.Event
18+
import to.bitkit.data.SettingsStore
1719
import to.bitkit.di.json
1820
import to.bitkit.ext.amountOnClose
21+
import to.bitkit.models.BITCOIN_SYMBOL
1922
import to.bitkit.models.BlocktankNotificationType
2023
import to.bitkit.models.BlocktankNotificationType.cjitPaymentArrived
2124
import to.bitkit.models.BlocktankNotificationType.incomingHtlc
@@ -42,6 +45,7 @@ class WakeNodeWorker @AssistedInject constructor(
4245
private val lightningRepo: LightningRepo,
4346
private val blocktankRepo: BlocktankRepo,
4447
private val activityRepo: ActivityRepo,
48+
private val settingsStore: SettingsStore,
4549
) : CoroutineWorker(appContext, workerParams) {
4650
private val self = this
4751

@@ -113,6 +117,8 @@ class WakeNodeWorker @AssistedInject constructor(
113117
* @param event The LDK event to check.
114118
*/
115119
private suspend fun handleLdkEvent(event: Event) {
120+
val showDetails = settingsStore.data.first().showNotificationDetails
121+
val openBitkitMessage = "Open Bitkit to see details"
116122
when (event) {
117123
is Event.PaymentReceived -> {
118124
bestAttemptContent?.title = "Payment Received"
@@ -127,7 +133,8 @@ class WakeNodeWorker @AssistedInject constructor(
127133
sats = sats.toLong(),
128134
)
129135
)
130-
bestAttemptContent?.body = "$sats"
136+
val content = if (showDetails) "$BITCOIN_SYMBOL $sats" else openBitkitMessage
137+
bestAttemptContent?.body = content
131138
if (self.notificationType == incomingHtlc) {
132139
self.deliver()
133140
}
@@ -146,19 +153,17 @@ class WakeNodeWorker @AssistedInject constructor(
146153

147154
lightningRepo.getChannels()?.find { it.channelId == event.channelId }?.let { channel ->
148155
val sats = channel.amountOnClose
149-
self.bestAttemptContent?.title = "Received ⚡ $sats sats"
150-
156+
val content = if (showDetails) "$BITCOIN_SYMBOL $sats" else openBitkitMessage
157+
self.bestAttemptContent?.title = content
151158
val cjitEntry = channel.let { blocktankRepo.getCjitEntry(it) }
152159
if (cjitEntry != null) {
153-
val amount = channel.amountOnClose.toLong()
154-
155160
// Save for UI to pick up
156161
NewTransactionSheetDetails.save(
157162
appContext,
158163
NewTransactionSheetDetails(
159164
type = NewTransactionSheetType.LIGHTNING,
160165
direction = NewTransactionSheetDirection.RECEIVED,
161-
sats = amount,
166+
sats = sats.toLong(),
162167
)
163168
)
164169
activityRepo.insertActivityFromCjit(cjitEntry = cjitEntry, channel = channel)

app/src/main/java/to/bitkit/ui/ContentView.kt

Lines changed: 81 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package to.bitkit.ui
22

3+
import android.content.Intent
34
import androidx.compose.runtime.Composable
45
import androidx.compose.runtime.CompositionLocalProvider
56
import androidx.compose.runtime.DisposableEffect
@@ -12,6 +13,7 @@ import androidx.compose.runtime.remember
1213
import androidx.compose.runtime.rememberCoroutineScope
1314
import androidx.compose.runtime.setValue
1415
import androidx.compose.ui.platform.LocalContext
16+
import androidx.core.net.toUri
1517
import androidx.hilt.navigation.compose.hiltViewModel
1618
import androidx.lifecycle.Lifecycle
1719
import androidx.lifecycle.LifecycleEventObserver
@@ -29,13 +31,16 @@ import androidx.navigation.toRoute
2931
import kotlinx.coroutines.delay
3032
import kotlinx.coroutines.launch
3133
import kotlinx.serialization.Serializable
34+
import to.bitkit.env.Env
3235
import to.bitkit.models.NewTransactionSheetDetails
3336
import to.bitkit.models.NodeLifecycleState
3437
import to.bitkit.models.Toast
3538
import to.bitkit.models.WidgetType
39+
import to.bitkit.ui.Routes.ExternalConnection
3640
import to.bitkit.ui.components.AuthCheckScreen
3741
import to.bitkit.ui.components.Sheet
3842
import to.bitkit.ui.components.SheetHost
43+
import to.bitkit.ui.components.TimedSheetType
3944
import to.bitkit.ui.onboarding.InitializingWalletView
4045
import to.bitkit.ui.onboarding.WalletRestoreErrorView
4146
import to.bitkit.ui.onboarding.WalletRestoreSuccessView
@@ -114,6 +119,8 @@ import to.bitkit.ui.settings.advanced.CoinSelectPreferenceScreen
114119
import to.bitkit.ui.settings.advanced.ElectrumConfigScreen
115120
import to.bitkit.ui.settings.advanced.RgsServerScreen
116121
import to.bitkit.ui.settings.appStatus.AppStatusScreen
122+
import to.bitkit.ui.settings.backgroundPayments.BackgroundPaymentsIntroScreen
123+
import to.bitkit.ui.settings.backgroundPayments.BackgroundPaymentsSettings
117124
import to.bitkit.ui.settings.backups.ResetAndRestoreScreen
118125
import to.bitkit.ui.settings.general.DefaultUnitSettingsScreen
119126
import to.bitkit.ui.settings.general.GeneralSettingsScreen
@@ -136,10 +143,14 @@ import to.bitkit.ui.settings.support.ReportIssueScreen
136143
import to.bitkit.ui.settings.support.SupportScreen
137144
import to.bitkit.ui.settings.transactionSpeed.CustomFeeSettingsScreen
138145
import to.bitkit.ui.settings.transactionSpeed.TransactionSpeedSettingsScreen
146+
import to.bitkit.ui.sheets.BackgroundPaymentsIntroSheet
147+
import to.bitkit.ui.sheets.BackupRoute
139148
import to.bitkit.ui.sheets.BackupSheet
140149
import to.bitkit.ui.sheets.ForceTransferSheet
150+
import to.bitkit.ui.sheets.HighBalanceWarningSheet
141151
import to.bitkit.ui.sheets.LnurlAuthSheet
142152
import to.bitkit.ui.sheets.PinSheet
153+
import to.bitkit.ui.sheets.QuickPayIntroSheet
143154
import to.bitkit.ui.sheets.SendSheet
144155
import to.bitkit.ui.sheets.UpdateSheet
145156
import to.bitkit.ui.theme.TRANSITION_SHEET_MS
@@ -320,7 +331,7 @@ fun ContentView(
320331
) {
321332
AutoReadClipboardHandler()
322333

323-
val currentSheet by appViewModel.currentSheet
334+
val currentSheet by appViewModel.currentSheet.collectAsStateWithLifecycle()
324335
SheetHost(
325336
shouldExpand = currentSheet != null,
326337
onDismiss = { appViewModel.hideSheet() },
@@ -340,7 +351,7 @@ fun ContentView(
340351
ReceiveSheet(
341352
walletState = walletUiState,
342353
navigateToExternalConnection = {
343-
navController.navigate(Routes.ExternalConnection())
354+
navController.navigate(ExternalConnection())
344355
appViewModel.hideSheet()
345356
}
346357
)
@@ -349,10 +360,53 @@ fun ContentView(
349360
is Sheet.ActivityDateRangeSelector -> DateRangeSelectorSheet()
350361
is Sheet.ActivityTagSelector -> TagSelectorSheet()
351362
is Sheet.Pin -> PinSheet(sheet, appViewModel)
352-
is Sheet.Backup -> BackupSheet(sheet, appViewModel)
363+
is Sheet.Backup -> BackupSheet(sheet, onDismiss = { appViewModel.hideSheet() })
353364
is Sheet.LnurlAuth -> LnurlAuthSheet(sheet, appViewModel)
354-
Sheet.Update -> UpdateSheet(onCancel = { appViewModel.hideSheet() })
355365
Sheet.ForceTransfer -> ForceTransferSheet(appViewModel, transferViewModel)
366+
is Sheet.TimedSheet -> {
367+
when (sheet.type) {
368+
TimedSheetType.APP_UPDATE -> {
369+
UpdateSheet(onCancel = { appViewModel.dismissTimedSheet() })
370+
}
371+
372+
TimedSheetType.BACKUP -> {
373+
BackupSheet(
374+
sheet = Sheet.Backup(BackupRoute.Intro),
375+
onDismiss = { appViewModel.dismissTimedSheet() }
376+
)
377+
}
378+
379+
TimedSheetType.NOTIFICATIONS -> {
380+
BackgroundPaymentsIntroSheet(
381+
onContinue = {
382+
appViewModel.dismissTimedSheet(skipQueue = true)
383+
navController.navigate(Routes.BackgroundPaymentsSettings)
384+
settingsViewModel.setBgPaymentsIntroSeen(true)
385+
},
386+
)
387+
}
388+
389+
TimedSheetType.QUICK_PAY -> {
390+
QuickPayIntroSheet(
391+
onContinue = {
392+
appViewModel.dismissTimedSheet(skipQueue = true)
393+
navController.navigate(Routes.QuickPaySettings)
394+
},
395+
)
396+
}
397+
398+
TimedSheetType.HIGH_BALANCE -> {
399+
HighBalanceWarningSheet(
400+
understoodClick = { appViewModel.dismissTimedSheet() },
401+
learnMoreClick = {
402+
val intent = Intent(Intent.ACTION_VIEW, Env.STORING_BITCOINS_URL.toUri())
403+
context.startActivity(intent)
404+
appViewModel.dismissTimedSheet(skipQueue = true)
405+
}
406+
)
407+
}
408+
}
409+
}
356410
}
357411
}
358412
) {
@@ -763,6 +817,22 @@ private fun NavGraphBuilder.generalSettings(navController: NavHostController) {
763817
composableWithDefaultTransitions<Routes.TagsSettings> {
764818
TagsSettingsScreen(navController)
765819
}
820+
composableWithDefaultTransitions<Routes.BackgroundPaymentsSettings> {
821+
BackgroundPaymentsSettings(
822+
onBack = { navController.popBackStack() },
823+
onClose = { navController.navigateToHome() },
824+
)
825+
}
826+
827+
composableWithDefaultTransitions<Routes.BackgroundPaymentsIntro> {
828+
BackgroundPaymentsIntroScreen(
829+
onBack = { navController.popBackStack() },
830+
onClose = { navController.navigateToHome() },
831+
onContinue = {
832+
navController.navigate(Routes.BackgroundPaymentsSettings)
833+
}
834+
)
835+
}
766836
}
767837

768838
private fun NavGraphBuilder.advancedSettings(navController: NavHostController) {
@@ -1031,7 +1101,7 @@ private fun NavGraphBuilder.update() {
10311101

10321102
private fun NavGraphBuilder.recoveryMode(
10331103
navController: NavHostController,
1034-
appViewModel: AppViewModel
1104+
appViewModel: AppViewModel,
10351105
) {
10361106
composableWithDefaultTransitions<Routes.RecoveryMode> {
10371107
RecoveryModeScreen(
@@ -1719,4 +1789,10 @@ sealed interface Routes {
17191789

17201790
@Serializable
17211791
data object RecoveryMnemonic : Routes
1792+
1793+
@Serializable
1794+
data object BackgroundPaymentsIntro : Routes
1795+
1796+
@Serializable
1797+
data object BackgroundPaymentsSettings : Routes
17221798
}

app/src/main/java/to/bitkit/ui/Notifications.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ internal fun Context.notificationBuilder(
4545
val pendingIntent = PendingIntent.getActivity(this, 0, intent, flags)
4646

4747
return NotificationCompat.Builder(this, channelId)
48-
.setSmallIcon(R.drawable.ic_notification)
48+
.setSmallIcon(R.drawable.ic_launcher_fg_regtest)
4949
.setPriority(NotificationCompat.PRIORITY_HIGH)
5050
.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION))
5151
.setContentIntent(pendingIntent) // fired on tap
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package to.bitkit.ui.components
2+
3+
import androidx.compose.animation.AnimatedContent
4+
import androidx.compose.foundation.Image
5+
import androidx.compose.foundation.background
6+
import androidx.compose.foundation.layout.Arrangement
7+
import androidx.compose.foundation.layout.Box
8+
import androidx.compose.foundation.layout.Column
9+
import androidx.compose.foundation.layout.Row
10+
import androidx.compose.foundation.layout.fillMaxSize
11+
import androidx.compose.foundation.layout.fillMaxWidth
12+
import androidx.compose.foundation.layout.padding
13+
import androidx.compose.foundation.layout.size
14+
import androidx.compose.runtime.Composable
15+
import androidx.compose.ui.Modifier
16+
import androidx.compose.ui.draw.clip
17+
import androidx.compose.ui.res.painterResource
18+
import androidx.compose.ui.tooling.preview.Preview
19+
import androidx.compose.ui.unit.dp
20+
import to.bitkit.R
21+
import to.bitkit.ui.theme.AppThemeSurface
22+
import to.bitkit.ui.theme.Colors
23+
import to.bitkit.ui.theme.Shapes
24+
25+
@Composable
26+
fun NotificationPreview(
27+
enabled: Boolean,
28+
title: String,
29+
description: String,
30+
showDetails: Boolean,
31+
modifier: Modifier = Modifier,
32+
) {
33+
Box(modifier = modifier) {
34+
Row(
35+
horizontalArrangement = Arrangement.spacedBy(8.dp),
36+
modifier = Modifier
37+
.clip(Shapes.medium)
38+
.background(Colors.White80)
39+
.padding(9.dp)
40+
) {
41+
Image(
42+
painter = painterResource(R.drawable.ic_notification),
43+
contentDescription = null,
44+
modifier = Modifier
45+
.size(38.dp)
46+
)
47+
48+
Column(
49+
modifier = Modifier.weight(1f),
50+
verticalArrangement = Arrangement.SpaceBetween
51+
) {
52+
BodySSB(text = title, color = Colors.Black)
53+
val textDescription = if (showDetails) description else "Open Bitkit to see details" // TODO Transifex
54+
AnimatedContent(targetState = textDescription) { text ->
55+
Footnote(text = text, color = Colors.Gray3)
56+
}
57+
}
58+
59+
Caption("3m ago", color = Colors.Gray2)
60+
}
61+
62+
if (!enabled) {
63+
Box(
64+
modifier = Modifier
65+
.matchParentSize()
66+
.clip(Shapes.medium)
67+
.background(Colors.Black70)
68+
)
69+
}
70+
}
71+
}
72+
73+
@Preview(showSystemUi = true)
74+
@Composable
75+
private fun Preview() {
76+
AppThemeSurface {
77+
Column(
78+
modifier = Modifier
79+
.fillMaxSize()
80+
.padding(8.dp),
81+
verticalArrangement = Arrangement.Center
82+
) {
83+
NotificationPreview(
84+
enabled = true,
85+
title = "Payment Received",
86+
description = "₿ 21 000",
87+
showDetails = true,
88+
modifier = Modifier.fillMaxWidth()
89+
)
90+
VerticalSpacer(16.dp)
91+
NotificationPreview(
92+
enabled = false,
93+
title = "Payment Received",
94+
description = "₿ 21 000",
95+
showDetails = false,
96+
modifier = Modifier.fillMaxWidth()
97+
)
98+
}
99+
}
100+
}

app/src/main/java/to/bitkit/ui/components/SheetHost.kt

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,18 @@ sealed interface Sheet {
3838
data object ActivityDateRangeSelector : Sheet
3939
data object ActivityTagSelector : Sheet
4040
data class LnurlAuth(val domain: String, val lnurl: String, val k1: String) : Sheet
41-
data object Update : Sheet
4241
data object ForceTransfer : Sheet
42+
43+
data class TimedSheet(val type: TimedSheetType) : Sheet
44+
}
45+
46+
/**@param priority Priority levels for timed sheets (higher number = higher priority)*/
47+
enum class TimedSheetType(val priority: Int) {
48+
APP_UPDATE(priority = 5),
49+
BACKUP(priority = 4),
50+
NOTIFICATIONS(priority = 3),
51+
QUICK_PAY(priority = 2),
52+
HIGH_BALANCE(priority = 1)
4353
}
4454

4555
@OptIn(ExperimentalMaterial3Api::class)

0 commit comments

Comments
 (0)