Skip to content

Commit 6de2a1c

Browse files
committed
Merge branch 'feat/nav3' into fix/skip-autorefresh-for-disabled-widgets
# Conflicts: # app/src/main/java/to/bitkit/ui/components/DrawerMenu.kt # app/src/main/java/to/bitkit/ui/components/SheetHost.kt # app/src/main/java/to/bitkit/ui/nav/DeepLinks.kt # app/src/main/java/to/bitkit/ui/nav/Routes.kt # app/src/main/java/to/bitkit/ui/nav/entries/HomeEntries.kt # app/src/main/java/to/bitkit/ui/nav/entries/OnboardingEntries.kt # app/src/main/java/to/bitkit/ui/nav/entries/SettingsEntries.kt # app/src/main/java/to/bitkit/ui/nav/entries/TransferEntries.kt # app/src/main/java/to/bitkit/ui/nav/entries/WidgetEntries.kt # app/src/main/java/to/bitkit/ui/settings/SecuritySettingsScreen.kt # app/src/main/java/to/bitkit/ui/settings/pin/ChangePinConfirmScreen.kt # app/src/main/java/to/bitkit/ui/settings/pin/ChangePinNewScreen.kt # app/src/main/java/to/bitkit/ui/settings/pin/ChangePinScreen.kt
2 parents 4dfdbde + 1065fac commit 6de2a1c

File tree

18 files changed

+182
-181
lines changed

18 files changed

+182
-181
lines changed

AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ suspend fun getData(): Result<Data> = withContext(Dispatchers.IO) {
192192
- ALWAYS be mindful of thread safety when working with mutable lists & state
193193
- ALWAYS split screen composables into parent accepting viewmodel + inner private child accepting state and callbacks `Content()`
194194
- ALWAYS name lambda parameters in a composable function using present tense, NEVER use past tense
195+
- ALWAYS use a single root layout node in composables that emit UI
195196
- ALWAYS list 3 suggested commit messages after implementation work for the entire set of uncommitted changes
196197
- NEVER use `wheneverBlocking` in unit test expression body functions wrapped in a `= test {}` lambda
197198
- ALWAYS wrap unit tests `setUp` methods mocking suspending calls with `runBlocking`, e.g `setUp() = runBlocking { }`

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

Lines changed: 73 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -63,69 +63,70 @@ fun DrawerMenu(
6363
) {
6464
val scope = rememberCoroutineScope()
6565

66-
Scrim(
67-
visible = drawerState.currentValue == DrawerValue.Open,
68-
onClick = {
69-
scope.launch {
70-
drawerState.close()
71-
}
72-
},
73-
modifier = Modifier
74-
.fillMaxSize()
75-
.zIndex(zIndexScrim)
76-
)
66+
Box(modifier = modifier.fillMaxSize()) {
67+
Scrim(
68+
visible = drawerState.currentValue == DrawerValue.Open,
69+
onClick = {
70+
scope.launch {
71+
drawerState.close()
72+
}
73+
},
74+
modifier = Modifier
75+
.fillMaxSize()
76+
.zIndex(zIndexScrim)
77+
)
7778

78-
AnimatedVisibility(
79-
visible = drawerState.currentValue == DrawerValue.Open,
80-
enter = slideInHorizontally(
81-
initialOffsetX = { it }
82-
),
83-
exit = slideOutHorizontally(
84-
targetOffsetX = { it }
85-
),
86-
modifier = modifier.then(
87-
Modifier
79+
AnimatedVisibility(
80+
visible = drawerState.currentValue == DrawerValue.Open,
81+
enter = slideInHorizontally(
82+
initialOffsetX = { it }
83+
),
84+
exit = slideOutHorizontally(
85+
targetOffsetX = { it }
86+
),
87+
modifier = Modifier
88+
.align(Alignment.TopEnd)
8889
.fillMaxHeight()
8990
.zIndex(zIndexMenu)
9091
.blockPointerInputPassthrough()
91-
)
92-
) {
93-
MenuContent(
94-
onWalletClick = {
95-
if (!navigator.isAtHome()) navigator.navigateToHome()
96-
scope.launch { drawerState.close() }
97-
},
98-
onActivityClick = {
99-
navigator.navigate(Routes.Activity.All)
100-
scope.launch { drawerState.close() }
101-
},
102-
onContactsClick = null, // TODO IMPLEMENT CONTACTS
103-
onProfileClick = null, // TODO IMPLEMENT PROFILE
104-
onWidgetsClick = {
105-
if (!hasSeenWidgetsIntro) {
106-
navigator.navigate(Routes.Widgets.Intro)
107-
} else {
108-
navigator.navigate(Routes.Widgets.Add)
109-
}
110-
scope.launch { drawerState.close() }
111-
},
112-
onShopClick = {
113-
if (!hasSeenShopIntro) {
114-
navigator.navigate(Routes.Shop.Intro)
115-
} else {
116-
navigator.navigate(Routes.Shop.Discover)
117-
}
118-
scope.launch { drawerState.close() }
119-
},
120-
onSettingsClick = {
121-
navigator.navigate(Routes.Settings.Main)
122-
scope.launch { drawerState.close() }
123-
},
124-
onAppStatusClick = {
125-
navigator.navigate(Routes.AppStatus)
126-
scope.launch { drawerState.close() }
127-
},
128-
)
92+
) {
93+
MenuContent(
94+
onWalletClick = {
95+
if (!navigator.isAtHome()) navigator.navigateToHome()
96+
scope.launch { drawerState.close() }
97+
},
98+
onActivityClick = {
99+
navigator.navigate(Routes.Activity.All)
100+
scope.launch { drawerState.close() }
101+
},
102+
onContactsClick = null, // TODO IMPLEMENT CONTACTS
103+
onProfileClick = null, // TODO IMPLEMENT PROFILE
104+
onWidgetsClick = {
105+
if (!hasSeenWidgetsIntro) {
106+
navigator.navigate(Routes.Widgets.Intro)
107+
} else {
108+
navigator.navigate(Routes.Widgets.Add)
109+
}
110+
scope.launch { drawerState.close() }
111+
},
112+
onShopClick = {
113+
if (!hasSeenShopIntro) {
114+
navigator.navigate(Routes.Shop.Intro)
115+
} else {
116+
navigator.navigate(Routes.Shop.Discover)
117+
}
118+
scope.launch { drawerState.close() }
119+
},
120+
onSettingsClick = {
121+
navigator.navigate(Routes.Settings.Main)
122+
scope.launch { drawerState.close() }
123+
},
124+
onAppStatusClick = {
125+
navigator.navigate(Routes.AppStatus)
126+
scope.launch { drawerState.close() }
127+
},
128+
)
129+
}
129130
}
130131
}
131132

@@ -294,17 +295,19 @@ private fun DrawerItem(
294295
@Composable
295296
private fun Preview() {
296297
AppThemeSurface {
297-
Box {
298-
MenuContent(
299-
onWalletClick = {},
300-
onActivityClick = {},
301-
onContactsClick = null,
302-
onProfileClick = null,
303-
onWidgetsClick = {},
304-
onShopClick = {},
305-
onSettingsClick = {},
306-
onAppStatusClick = {},
307-
)
298+
Box(modifier = Modifier.fillMaxSize()) {
299+
Box(modifier = Modifier.align(Alignment.TopEnd)) {
300+
MenuContent(
301+
onWalletClick = {},
302+
onActivityClick = {},
303+
onContactsClick = null,
304+
onProfileClick = null,
305+
onWidgetsClick = {},
306+
onShopClick = {},
307+
onSettingsClick = {},
308+
onAppStatusClick = {},
309+
)
310+
}
308311
}
309312
}
310313
}

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

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import androidx.compose.foundation.layout.Box
1616
import androidx.compose.foundation.layout.Column
1717
import androidx.compose.foundation.layout.fillMaxSize
1818
import androidx.compose.foundation.layout.fillMaxWidth
19+
import androidx.compose.foundation.layout.height
1920
import androidx.compose.foundation.layout.navigationBarsPadding
2021
import androidx.compose.foundation.layout.offset
2122
import androidx.compose.runtime.Composable
@@ -29,18 +30,23 @@ import androidx.compose.runtime.setValue
2930
import androidx.compose.ui.Alignment
3031
import androidx.compose.ui.Modifier
3132
import androidx.compose.ui.draw.clip
33+
import androidx.compose.ui.geometry.Offset
3234
import androidx.compose.ui.graphics.Color
3335
import androidx.compose.ui.input.pointer.pointerInput
36+
import androidx.compose.ui.input.pointer.util.VelocityTracker
3437
import androidx.compose.ui.layout.onSizeChanged
38+
import androidx.compose.ui.platform.LocalDensity
3539
import androidx.compose.ui.unit.IntOffset
3640
import kotlinx.coroutines.launch
3741
import to.bitkit.ui.shared.modifiers.sheetHeight
3842
import to.bitkit.ui.theme.AppShapes
3943
import to.bitkit.ui.theme.Colors
44+
import kotlin.math.abs
4045
import kotlin.math.roundToInt
4146

4247
private const val MS_DURATION_ANIM = 300
43-
private const val THRESHOLD_DISMISS = 0.45f
48+
private const val THRESHOLD_DISMISS = 0.33f
49+
private const val VELOCITY_DISMISS = 2500f
4450
private const val OFFSET_MIN_DRAG = -0.1f
4551
private val sheetContainerColor = Color.Black
4652

@@ -65,6 +71,7 @@ fun SheetHost(
6571
var sheetHeightPx by remember { mutableFloatStateOf(0f) }
6672
val offsetY = remember { Animatable(1f) }
6773
var sheetVisible by remember { mutableStateOf(false) }
74+
val velocityTracker = remember { VelocityTracker() }
6875

6976
LaunchedEffect(Unit) {
7077
sheetVisible = true
@@ -90,6 +97,18 @@ fun SheetHost(
9097
onClick = { dismiss() },
9198
)
9299

100+
// Fixed background extension - covers gap when sheet drags up
101+
val density = LocalDensity.current
102+
if (sheetVisible && sheetHeightPx > 0f) {
103+
Box(
104+
modifier = Modifier
105+
.align(Alignment.BottomCenter)
106+
.fillMaxWidth()
107+
.height(with(density) { (sheetHeightPx * abs(OFFSET_MIN_DRAG)).toDp() })
108+
.background(sheetContainerColor)
109+
)
110+
}
111+
93112
AnimatedVisibility(
94113
visible = sheetVisible,
95114
enter = slideInVertically { it } + fadeIn(),
@@ -105,10 +124,18 @@ fun SheetHost(
105124
.background(sheetContainerColor)
106125
.fillMaxWidth()
107126
.pointerInput(Unit) {
127+
var dragPosition = 0f
108128
detectVerticalDragGestures(
129+
onDragStart = {
130+
velocityTracker.resetTracking()
131+
dragPosition = 0f
132+
},
109133
onDragEnd = {
134+
val velocity = velocityTracker.calculateVelocity().y
135+
val shouldDismiss = velocity > VELOCITY_DISMISS ||
136+
offsetY.value > THRESHOLD_DISMISS
110137
scope.launch {
111-
if (offsetY.value > THRESHOLD_DISMISS) {
138+
if (shouldDismiss) {
112139
offsetY.animateTo(
113140
1f,
114141
animationSpec = tween(MS_DURATION_ANIM)
@@ -123,7 +150,12 @@ fun SheetHost(
123150
}
124151
}
125152
},
126-
onVerticalDrag = { _, dragAmount ->
153+
onVerticalDrag = { change, dragAmount ->
154+
dragPosition += dragAmount
155+
velocityTracker.addPosition(
156+
change.uptimeMillis,
157+
Offset(0f, dragPosition)
158+
)
127159
val newOffset = offsetY.value + (dragAmount / sheetHeightPx)
128160
scope.launch {
129161
offsetY.snapTo(newOffset.coerceIn(OFFSET_MIN_DRAG, 1f))

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ import to.bitkit.models.Toast
5656
import to.bitkit.ui.scaffold.ScreenColumn
5757
import to.bitkit.ui.theme.AppThemeSurface
5858
import to.bitkit.ui.theme.Colors
59+
import kotlin.math.abs
5960
import kotlin.math.roundToInt
6061

6162
private const val DISMISS_THRESHOLD_DP = 40
@@ -120,8 +121,8 @@ fun ToastView(
120121
}
121122

122123
coroutineScope.launch {
123-
val horizontalSwipeDistance = kotlin.math.abs(dragOffsetX.value)
124-
val verticalSwipeDistance = kotlin.math.abs(dragOffsetY.value)
124+
val horizontalSwipeDistance = abs(dragOffsetX.value)
125+
val verticalSwipeDistance = abs(dragOffsetY.value)
125126

126127
// Determine if this is primarily horizontal or vertical swipe
127128
val isHorizontalSwipe = horizontalSwipeDistance > verticalSwipeDistance

app/src/main/java/to/bitkit/ui/nav/DeepLinks.kt

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,7 @@ data class DeepLinkRequest(
3838
val scheme: String? = uri.scheme?.lowercase()
3939
val host: String? = uri.host?.lowercase()
4040
val pathSegments: List<String> = uri.pathSegments
41-
val queryParams: Map<String, String> = uri.queryParameterNames
42-
.associateWith { uri.getQueryParameter(it) ?: "" }
41+
val queryParams: Map<String, String> = uri.queryParameterNames.associateWith { uri.getQueryParameter(it) ?: "" }
4342
}
4443

4544
/**
@@ -51,7 +50,7 @@ data class DeepLinkMatchResult<T : Routes>(
5150
)
5251

5352
/**
54-
* Matches a DeepLinkRequest against a DeepLinkPattern.
53+
* Matches a [DeepLinkRequest] against a [DeepLinkPattern].
5554
*/
5655
class DeepLinkMatcher<T : Routes>(
5756
private val request: DeepLinkRequest,
@@ -99,11 +98,9 @@ class DeepLinkMatcher<T : Routes>(
9998
}
10099

101100
/**
102-
* Registry of all supported deep link patterns.
101+
* Registry of supported deep link patterns for type-safe documentation and early pattern matching.
103102
*
104-
* Note: The Rust-based bitkitcore.decode() remains the primary parser for complex
105-
* Bitcoin/Lightning URIs. These patterns provide type-safe documentation and
106-
* early pattern matching for known routes.
103+
* Note: [com.synonym.bitkitcore.decode] remains the primary parser for complex Bitcoin/Lightning URIs.
107104
*/
108105
object DeepLinkPatterns {
109106
// bitkit:// scheme patterns
@@ -147,9 +144,6 @@ object DeepLinkPatterns {
147144
uriPattern = "${UriScheme.HTTPS.withSlashes}www.bitkit.to/treasure-hunt".toUri()
148145
)
149146

150-
/**
151-
* All registered patterns for matching.
152-
*/
153147
val all: List<DeepLinkPattern<out Routes>> = listOf(
154148
RECOVERY_MODE,
155149
SEND_BITCOIN,
@@ -172,7 +166,7 @@ object DeepLinkPatterns {
172166
}
173167
}
174168

175-
// TODO Temporary fix while these schemes can't be decoded
169+
// TODO Temporary fix while these schemes can't be decoded via bitkit-core
176170
fun String.removeLightningSchemes(): String = listOf(
177171
UriScheme.LNURL,
178172
UriScheme.LNURL_PAY,

app/src/main/java/to/bitkit/ui/nav/Routes.kt

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import com.synonym.bitkitcore.Activity as BitkitCoreActivity
77

88
@Stable
99
sealed interface Routes : NavKey {
10+
1011
@Serializable
1112
data object Home : Routes
1213

@@ -254,20 +255,6 @@ sealed interface Routes : NavKey {
254255
}
255256
}
256257

257-
object ChangePin {
258-
@Serializable
259-
data object Start : Routes
260-
261-
@Serializable
262-
data object New : Routes
263-
264-
@Serializable
265-
data class Confirm(val newPin: String) : Routes
266-
267-
@Serializable
268-
data object Result : Routes
269-
}
270-
271258
object Send {
272259
@Serializable
273260
data object Recipient : Routes
@@ -362,6 +349,20 @@ sealed interface Routes : NavKey {
362349

363350
@Serializable
364351
data class Result(val isBioOn: Boolean) : Routes
352+
353+
object Change {
354+
@Serializable
355+
data object Start : Routes
356+
357+
@Serializable
358+
data object New : Routes
359+
360+
@Serializable
361+
data class Confirm(val newPin: String) : Routes
362+
363+
@Serializable
364+
data object Result : Routes
365+
}
365366
}
366367

367368
object Backup {

0 commit comments

Comments
 (0)