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
17 changes: 15 additions & 2 deletions android/app/src/main/java/org/bitcoinppl/cove/WalletManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -224,8 +224,21 @@ class WalletManager :
}

is WalletManagerReconcileMessage.AvailableTransactions -> {
if (loadState is WalletLoadState.LOADING) {
loadState = WalletLoadState.SCANNING(message.v1)
val txns = message.v1
when (val current = loadState) {
is WalletLoadState.LOADING -> {
loadState = WalletLoadState.SCANNING(txns)
}
is WalletLoadState.SCANNING -> {
if (txns.size >= current.txns.size) {
loadState = WalletLoadState.SCANNING(txns)
}
}
is WalletLoadState.LOADED -> {
if (txns.size >= current.txns.size) {
loadState = WalletLoadState.SCANNING(txns)
}
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ internal fun WordInputGrid(
enteredWords: List<List<String>>,
numberOfWords: NumberOfBip39Words,
focusedField: Int,
tabIndex: Int,
onWordsChanged: (List<List<String>>) -> Unit,
onFocusChanged: (Int) -> Unit,
) {
Expand All @@ -64,9 +65,12 @@ internal fun WordInputGrid(

val flatWords = enteredWords.flatten()

val numRows = wordCount / 2
val leftIndices = (0 until numRows)
val rightIndices = (numRows until wordCount)
// always show 12 words per page (6 per column) to match iOS pagination
val pageSize = 12
val wordsPerColumn = 6
val pageStart = tabIndex * pageSize
val leftIndices = (pageStart until pageStart + wordsPerColumn)
val rightIndices = (pageStart + wordsPerColumn until (pageStart + pageSize).coerceAtMost(wordCount))

Card(
modifier = Modifier.fillMaxWidth(),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package org.bitcoinppl.cove.flows.NewWalletFlow.hot_wallet

import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
Expand All @@ -11,6 +13,8 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Nfc
Expand Down Expand Up @@ -38,6 +42,7 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalFocusManager
Expand Down Expand Up @@ -110,6 +115,15 @@ fun HotWalletImportScreen(
var duplicateWalletId by remember { mutableStateOf<WalletId?>(null) }
var genericErrorMessage by remember { mutableStateOf("") }
var focusedField by remember(numberOfWords) { mutableIntStateOf(0) }
var tabIndex by remember(numberOfWords) { mutableIntStateOf(0) }

// auto-switch page when focus changes to a word on a different page
LaunchedEffect(focusedField) {
val newTab = focusedField / GROUPS_OF
if (newTab != tabIndex && newTab < numberOfGroups) {
tabIndex = newTab
}
}

// QR and NFC state
var showQrScanner by remember { mutableStateOf(false) }
Expand Down Expand Up @@ -287,9 +301,37 @@ fun HotWalletImportScreen(
enteredWords = enteredWords,
numberOfWords = numberOfWords,
focusedField = focusedField,
tabIndex = tabIndex,
onWordsChanged = { newWords -> enteredWords = newWords },
onFocusChanged = { field -> focusedField = field },
)

// page indicator dots for 24-word import
if (numberOfWords == NumberOfBip39Words.TWENTY_FOUR) {
Spacer(Modifier.height(16.dp))
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Center,
) {
repeat(numberOfGroups) { i ->
val isSelected = i == tabIndex
Box(
modifier =
Modifier
.padding(horizontal = 4.dp)
.size(8.dp)
.clip(RoundedCornerShape(50))
.background(
if (isSelected) {
Color.White
} else {
Color.White.copy(alpha = 0.33f)
},
).clickable { tabIndex = i },
)
}
}
}
}

Spacer(Modifier.weight(1f))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
Expand Down Expand Up @@ -52,8 +51,10 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import kotlinx.coroutines.launch
import org.bitcoinppl.cove.AppAlertState
import org.bitcoinppl.cove.AppManager
import org.bitcoinppl.cove.R
import org.bitcoinppl.cove.TaggedItem
import org.bitcoinppl.cove.WalletManager
import org.bitcoinppl.cove.ui.theme.CoveColor
import org.bitcoinppl.cove.ui.theme.isLight
Expand Down Expand Up @@ -113,8 +114,8 @@ fun TransactionsCardView(
fontWeight = FontWeight.Bold,
)

// small inline spinner when scanning with existing transactions
if (isScanning && hasTransactions) {
// show inline spinner when scanning, except during initial loading (first scan with no txns yet)
if (isScanning && !(isFirstScan && !hasTransactions)) {
Box(
modifier =
Modifier
Expand Down Expand Up @@ -148,16 +149,30 @@ fun TransactionsCardView(
}
} else {
// scan complete but no transactions
Box(
Column(
modifier =
Modifier
.fillMaxWidth()
.padding(vertical = 32.dp),
contentAlignment = Alignment.Center,
.padding(top = 20.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Icon(
painter =
androidx.compose.ui.res
.painterResource(R.drawable.icon_currency_bitcoin),
contentDescription = null,
modifier = Modifier.size(48.dp),
tint = secondaryText,
)
Spacer(Modifier.height(8.dp))
Text(
text = stringResource(R.string.no_transactions_yet),
color = secondaryText,
fontWeight = FontWeight.Medium,
)
Text(
text = stringResource(R.string.go_buy_some_bitcoin),
color = secondaryText.copy(alpha = 0.7f),
fontSize = 14.sp,
)
}
Expand Down Expand Up @@ -185,7 +200,15 @@ fun TransactionsCardView(
HorizontalDivider(color = dividerColor, thickness = 0.5.dp)
}

itemsIndexed(transactions) { index, txn ->
items(
items = transactions,
key = {
when (it) {
is Transaction.Confirmed -> it.v1.id().toString()
is Transaction.Unconfirmed -> it.v1.id().toString()
}
},
) { txn ->
TransactionItem(
txn = txn,
manager = manager,
Expand All @@ -197,10 +220,7 @@ fun TransactionsCardView(
secondaryText = secondaryText,
)

// add divider between transactions (but not after the last one)
if (index < transactions.size - 1) {
HorizontalDivider(color = dividerColor, thickness = 0.5.dp)
}
HorizontalDivider(color = dividerColor, thickness = 0.5.dp)
}

// add bottom spacing
Expand Down Expand Up @@ -359,12 +379,15 @@ internal fun ConfirmedTransactionWidget(
if (app != null && manager != null) {
scope.launch {
try {
app.alertState = TaggedItem(AppAlertState.Loading)
val details = manager.transactionDetails(transaction.v1.id())
val walletId = manager.walletMetadata?.id
app.alertState = null
if (walletId != null) {
app.pushRoute(Route.TransactionDetails(walletId, details))
}
} catch (e: Exception) {
app.alertState = null
android.util.Log.e("ConfirmedTxWidget", "Failed to load transaction details", e)
}
}
Expand Down Expand Up @@ -460,12 +483,15 @@ internal fun UnconfirmedTransactionWidget(
if (app != null && manager != null) {
scope.launch {
try {
app.alertState = TaggedItem(AppAlertState.Loading)
val details = manager.transactionDetails(transaction.v1.id())
val walletId = manager.walletMetadata?.id
app.alertState = null
if (walletId != null) {
app.pushRoute(Route.TransactionDetails(walletId, details))
}
} catch (e: Exception) {
app.alertState = null
android.util.Log.e("UnconfirmedTxWidget", "Failed to load transaction details", e)
}
}
Expand Down Expand Up @@ -639,7 +665,7 @@ internal fun UnsignedTransactionWidget(
)
Text(
text = stringResource(R.string.pending_signature),
color = Color(0xFFFF9800),
color = Color(0xFFFF9800).copy(alpha = 0.8f),
fontSize = 12.sp,
fontWeight = FontWeight.Normal,
)
Expand All @@ -648,7 +674,7 @@ internal fun UnsignedTransactionWidget(
Column(horizontalAlignment = Alignment.End) {
Text(
text = privateShow(formattedAmount),
color = primaryText.copy(alpha = 0.6f),
color = primaryText,
fontSize = 17.sp,
fontWeight = FontWeight.Normal,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ fun ImageButton(
BasicText(
text = text,
maxLines = 1,
autoSize = TextAutoSize.StepBased(minFontSize = 7.sp, maxFontSize = fontSize, stepSize = 0.5.sp),
autoSize = TextAutoSize.StepBased(minFontSize = 12.sp, maxFontSize = fontSize, stepSize = 0.5.sp),
style =
TextStyle(
fontSize = fontSize,
Expand Down
1 change: 1 addition & 0 deletions android/app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
<string name="label_transaction_receiving">Receiving</string>
<string name="title_transactions">Transactions</string>
<string name="no_transactions_yet">No transactions yet</string>
<string name="go_buy_some_bitcoin">Go buy some bitcoin!</string>
<string name="pending">Pending</string>
<string name="pending_signature">Pending Signature</string>
<string name="unconfirmed">Unconfirmed</string>
Expand Down
8 changes: 6 additions & 2 deletions ios/Cove/WalletManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -159,9 +159,13 @@ extension WeakReconciler: WalletManagerReconciler where Reconciler == WalletMana

case let .availableTransactions(txns):
switch self.loadState {
case .loading, .scanning:
case .loading:
self.loadState = .scanning(txns)
case let .loaded(current) where txns.count > current.count:
case let .scanning(current) where txns.count >= current.count:
self.loadState = .scanning(txns)
case .scanning:
break
case let .loaded(current) where txns.count >= current.count:
self.loadState = .scanning(txns)
case .loaded:
break
Expand Down