diff --git a/android/app/src/main/java/org/bitcoinppl/cove/flows/SendFlow/EnterAmountView.kt b/android/app/src/main/java/org/bitcoinppl/cove/flows/SendFlow/EnterAmountView.kt index 85cfe229..cf6f504b 100644 --- a/android/app/src/main/java/org/bitcoinppl/cove/flows/SendFlow/EnterAmountView.kt +++ b/android/app/src/main/java/org/bitcoinppl/cove/flows/SendFlow/EnterAmountView.kt @@ -121,7 +121,7 @@ fun EnterAmountView( color = if (exceedsBalance) CoveColor.WarningOrange else MaterialTheme.colorScheme.onSurface, fontWeight = FontWeight.Bold, textAlign = TextAlign.Center, - modifier = Modifier.fillMaxWidth().offset(x = amountOffset), + modifier = Modifier.fillMaxWidth().padding(horizontal = 30.dp).offset(x = amountOffset), onTextWidthChanged = { width -> textWidth = width }, onFocusChanged = { focused -> isFocused = focused diff --git a/android/app/src/main/java/org/bitcoinppl/cove/flows/SendFlow/SendFlowConfirmScreen.kt b/android/app/src/main/java/org/bitcoinppl/cove/flows/SendFlow/SendFlowConfirmScreen.kt index e77936cc..62cbfd07 100644 --- a/android/app/src/main/java/org/bitcoinppl/cove/flows/SendFlow/SendFlowConfirmScreen.kt +++ b/android/app/src/main/java/org/bitcoinppl/cove/flows/SendFlow/SendFlowConfirmScreen.kt @@ -167,7 +167,7 @@ fun SendFlowConfirmScreen( text = stringResource(R.string.action_swipe_to_send), sendState = sendState, onComplete = onSwipeToSend, - containerColor = MaterialTheme.colorScheme.surfaceVariant, + containerColor = MaterialTheme.coveColors.swipeTrackBg, targetContainerColor = MaterialTheme.coveColors.midnightBtn, knobColor = MaterialTheme.coveColors.midnightBtn, textColor = MaterialTheme.colorScheme.onSurface, diff --git a/android/app/src/main/java/org/bitcoinppl/cove/flows/SendFlow/SendFlowContainer.kt b/android/app/src/main/java/org/bitcoinppl/cove/flows/SendFlow/SendFlowContainer.kt index 62737fe0..0fc2c2e9 100644 --- a/android/app/src/main/java/org/bitcoinppl/cove/flows/SendFlow/SendFlowContainer.kt +++ b/android/app/src/main/java/org/bitcoinppl/cove/flows/SendFlow/SendFlowContainer.kt @@ -210,6 +210,7 @@ private fun SendFlowRouteToScreen( val exceedsBalance = sendFlowManager.rust.amountExceedsBalance() var previouslyExceeded by remember { mutableStateOf(false) } val snackbarHostState = remember { SnackbarHostState() } + val validationScope = rememberCoroutineScope() LaunchedEffect(exceedsBalance) { if (exceedsBalance && !previouslyExceeded) { @@ -229,8 +230,39 @@ private fun SendFlowRouteToScreen( snackbarHostState = snackbarHostState, onBack = { app.popRoute() }, onNext = { - if (sendFlowManager.validate(displayAlert = true)) { - sendFlowManager.dispatch(SendFlowManagerAction.FinalizeAndGoToNextScreen) + val addressValid = sendFlowManager.rust.validateAddress() + val amountValid = sendFlowManager.rust.validateAmount() + val hasAddress = sendFlowManager.enteringAddress.isNotEmpty() + val hasAmount = sendFlowManager.rust.amount().asSats() > 0uL + + when { + !addressValid -> { + // only show snackbar if address has content (not just empty) + if (hasAddress) { + validationScope.launch { + snackbarHostState.showSnackbar( + message = "Address not valid. Please try again.", + duration = SnackbarDuration.Short, + ) + } + } + presenter.focusField = SetAmountFocusField.ADDRESS + } + !amountValid -> { + // only show snackbar if amount has content (not just empty/zero) + if (hasAmount) { + validationScope.launch { + snackbarHostState.showSnackbar( + message = "Amount not valid. Please try again.", + duration = SnackbarDuration.Short, + ) + } + } + presenter.focusField = SetAmountFocusField.AMOUNT + } + else -> { + sendFlowManager.dispatch(SendFlowManagerAction.FinalizeAndGoToNextScreen) + } } }, onScanQr = { diff --git a/android/app/src/main/java/org/bitcoinppl/cove/ui/theme/Color.kt b/android/app/src/main/java/org/bitcoinppl/cove/ui/theme/Color.kt index ace37161..dd16ab24 100644 --- a/android/app/src/main/java/org/bitcoinppl/cove/ui/theme/Color.kt +++ b/android/app/src/main/java/org/bitcoinppl/cove/ui/theme/Color.kt @@ -33,6 +33,10 @@ object CoveColor { val coveBgDark = ComposeColor(0xFF191919) val midnightBtnDark = ComposeColor(0xFF4A4A4D) + // Swipe track background (matches iOS systemGray5) + val swipeTrackBgLight = ComposeColor(0xFFE5E5EA) + val swipeTrackBgDark = ComposeColor(0xFF2C2C2E) + // Wallet colors - pastel palette (preferred) val beige = ComposeColor(0xFFFFB36E) val lightMint = ComposeColor(0xFFC5E5CD) @@ -80,18 +84,21 @@ object CoveColor { data class CoveColorScheme( val midnightBtn: ComposeColor, val systemGreen: ComposeColor, + val swipeTrackBg: ComposeColor, ) val LightCoveColors = CoveColorScheme( midnightBtn = CoveColor.midnightBlue, // #1C2536 systemGreen = CoveColor.SystemGreenLight, // #34C759 + swipeTrackBg = CoveColor.swipeTrackBgLight, // #E5E5EA (iOS systemGray5) ) val DarkCoveColors = CoveColorScheme( midnightBtn = CoveColor.midnightBtnDark, // #4A4A4D systemGreen = CoveColor.SystemGreenDark, // #30D158 + swipeTrackBg = CoveColor.swipeTrackBgDark, // #2C2C2E (iOS systemGray5) ) val LocalCoveColors = staticCompositionLocalOf { LightCoveColors } diff --git a/ios/Cove/Flows/SendFlow/SendFlowSetAmountScreen.swift b/ios/Cove/Flows/SendFlow/SendFlowSetAmountScreen.swift index 06453160..b9358969 100644 --- a/ios/Cove/Flows/SendFlow/SendFlowSetAmountScreen.swift +++ b/ios/Cove/Flows/SendFlow/SendFlowSetAmountScreen.swift @@ -64,17 +64,51 @@ struct SendFlowSetAmountScreen: View { // MARK: Actions - // validate, create final psbt and send to next screen private func next() { - if validate(true) { sendFlowManager.dispatch(action: .finalizeAndGoToNextScreen) } + Task { + guard await performValidation() else { return } + sendFlowManager.dispatch(action: .finalizeAndGoToNextScreen) + } } private func dismissIfValid() { - if validate(true) { + Task { + guard await performValidation() else { return } presenter.focusField = .none } } + private func performValidation() async -> Bool { + if !validateAddress() { + if !address.wrappedValue.isEmpty { + await FloaterPopup( + text: "Address not valid. Please try again.", + backgroundColor: .orange, + textColor: .white, + iconColor: .white, + icon: "exclamationmark.triangle" + ).dismissAfter(1).present() + } + presenter.focusField = .address + return false + } + if !validateAmount() { + let hasAmount = sendFlowManager.amount != nil && sendFlowManager.amount?.asSats() != 0 + if hasAmount { + await FloaterPopup( + text: "Amount not valid. Please try again.", + backgroundColor: .orange, + textColor: .white, + iconColor: .white, + icon: "exclamationmark.triangle" + ).dismissAfter(1).present() + } + presenter.focusField = .amount + return false + } + return true + } + // doing it this way prevents an alert popping up when the user just goes back private func setAlertState(_ error: SendFlowError) { sendFlowManager.presenter.alertState = .init(.error(error))