diff --git a/android/app/src/main/java/org/bitcoinppl/cove/flows/NewWalletFlow/hot_wallet/HotWalletCreateScreen.kt b/android/app/src/main/java/org/bitcoinppl/cove/flows/NewWalletFlow/hot_wallet/HotWalletCreateScreen.kt index e840277a..276e849e 100644 --- a/android/app/src/main/java/org/bitcoinppl/cove/flows/NewWalletFlow/hot_wallet/HotWalletCreateScreen.kt +++ b/android/app/src/main/java/org/bitcoinppl/cove/flows/NewWalletFlow/hot_wallet/HotWalletCreateScreen.kt @@ -343,55 +343,61 @@ private fun WordCardView( words: List, modifier: Modifier = Modifier, ) { - androidx.compose.foundation.layout.Column( - modifier = modifier, - verticalArrangement = Arrangement.spacedBy(18.dp), + val numColumns = 3 + require(words.size % numColumns == 0) { + "Word count (${words.size}) must be divisible by $numColumns" + } + val wordsPerColumn = words.size / numColumns + + Row( + modifier = modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(12.dp), ) { - words.chunked(3).forEach { rowWords -> - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(12.dp), + repeat(numColumns) { col -> + Column( + modifier = Modifier.weight(1f), + verticalArrangement = Arrangement.spacedBy(18.dp), ) { - rowWords.forEach { groupedWord -> - androidx.compose.foundation.layout.Box( - modifier = - Modifier - .weight(1f) - .background( - color = CoveColor.btnPrimary, - shape = - androidx.compose.foundation.shape - .RoundedCornerShape(10.dp), - ).padding(horizontal = 12.dp, vertical = 12.dp), - ) { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, + repeat(wordsPerColumn) { row -> + val index = col * wordsPerColumn + row + if (index < words.size) { + val groupedWord = words[index] + Box( + modifier = + Modifier + .fillMaxWidth() + .background( + color = CoveColor.btnPrimary, + shape = + androidx.compose.foundation.shape + .RoundedCornerShape(10.dp), + ).padding(horizontal = 12.dp, vertical = 12.dp), ) { - AutoSizeText( - text = "${groupedWord.number}.", - color = Color.Black.copy(alpha = 0.5f), - fontWeight = FontWeight.Medium, - maxFontSize = 12.sp, - minimumScaleFactor = 0.8f, - ) - Spacer(Modifier.weight(1f)) - AutoSizeText( - text = groupedWord.word, - color = CoveColor.midnightBlue, - fontWeight = FontWeight.Medium, - maxFontSize = 14.sp, - minimumScaleFactor = 0.2f, - ) - Spacer(Modifier.weight(1f)) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + AutoSizeText( + text = "${groupedWord.number}.", + color = Color.Black.copy(alpha = 0.5f), + fontWeight = FontWeight.Medium, + maxFontSize = 12.sp, + minimumScaleFactor = 0.8f, + ) + Spacer(Modifier.weight(1f)) + AutoSizeText( + text = groupedWord.word, + color = CoveColor.midnightBlue, + fontWeight = FontWeight.Medium, + maxFontSize = 14.sp, + minimumScaleFactor = 0.2f, + ) + Spacer(Modifier.weight(1f)) + } } } } - // fill empty slots if row has less than 3 words - repeat(3 - rowWords.size) { - Spacer(Modifier.weight(1f)) - } } } } diff --git a/android/app/src/main/java/org/bitcoinppl/cove/secret_words/SecretWordsScreen.kt b/android/app/src/main/java/org/bitcoinppl/cove/secret_words/SecretWordsScreen.kt index f58c87ce..ddcd1532 100644 --- a/android/app/src/main/java/org/bitcoinppl/cove/secret_words/SecretWordsScreen.kt +++ b/android/app/src/main/java/org/bitcoinppl/cove/secret_words/SecretWordsScreen.kt @@ -4,14 +4,12 @@ import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer 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.lazy.grid.GridCells -import androidx.compose.foundation.lazy.grid.LazyVerticalGrid -import androidx.compose.foundation.lazy.grid.itemsIndexed import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material3.CenterAlignedTopAppBar @@ -209,26 +207,40 @@ fun SecretWordsScreen( /** * recovery words grid for viewing only (non-selectable) + * uses column-major ordering (words flow down columns first) */ @Composable private fun RecoveryWordsGrid( words: List, modifier: Modifier = Modifier, ) { - LazyVerticalGrid( - columns = GridCells.Fixed(3), + val numColumns = 3 + require(words.size % numColumns == 0) { + "Word count (${words.size}) must be divisible by $numColumns" + } + val wordsPerColumn = words.size / numColumns + + Row( + modifier = modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(12.dp), - verticalArrangement = Arrangement.spacedBy(18.dp), - modifier = modifier, ) { - itemsIndexed(words) { idx, word -> - RecoveryWordChip( - index = idx + 1, - word = word, - selected = false, - // non-clickable - onClick = null, - ) + repeat(numColumns) { col -> + Column( + modifier = Modifier.weight(1f), + verticalArrangement = Arrangement.spacedBy(18.dp), + ) { + repeat(wordsPerColumn) { row -> + val index = col * wordsPerColumn + row + if (index < words.size) { + RecoveryWordChip( + index = index + 1, + word = words[index], + selected = false, + onClick = null, + ) + } + } + } } } } diff --git a/android/app/src/main/java/org/bitcoinppl/cove/views/RecoveryWords.kt b/android/app/src/main/java/org/bitcoinppl/cove/views/RecoveryWords.kt index a2eaca44..52fbbc1c 100644 --- a/android/app/src/main/java/org/bitcoinppl/cove/views/RecoveryWords.kt +++ b/android/app/src/main/java/org/bitcoinppl/cove/views/RecoveryWords.kt @@ -15,9 +15,6 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width -import androidx.compose.foundation.lazy.grid.GridCells -import androidx.compose.foundation.lazy.grid.LazyVerticalGrid -import androidx.compose.foundation.lazy.grid.itemsIndexed import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.shape.RoundedCornerShape @@ -87,20 +84,34 @@ private fun RecoveryWordsGrid( selected: Set = emptySet(), onToggleIndex: ((Int) -> Unit)? = null, ) { - LazyVerticalGrid( - columns = GridCells.Fixed(3), - horizontalArrangement = Arrangement.spacedBy(12.dp), - verticalArrangement = Arrangement.spacedBy(18.dp), + val numColumns = 3 + require(words.size % numColumns == 0) { + "Word count (${words.size}) must be divisible by $numColumns" + } + val wordsPerColumn = words.size / numColumns + + Row( modifier = modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(12.dp), ) { - itemsIndexed(words) { idx, word -> - val globalIndex = startIndexOffset + idx + 1 - RecoveryWordChip( - index = globalIndex, - word = word, - selected = selected.contains(globalIndex), - onClick = { onToggleIndex?.invoke(globalIndex) }, - ) + repeat(numColumns) { col -> + Column( + modifier = Modifier.weight(1f), + verticalArrangement = Arrangement.spacedBy(18.dp), + ) { + repeat(wordsPerColumn) { row -> + val index = col * wordsPerColumn + row + if (index < words.size) { + val globalIndex = startIndexOffset + index + 1 + RecoveryWordChip( + index = globalIndex, + word = words[index], + selected = selected.contains(globalIndex), + onClick = { onToggleIndex?.invoke(globalIndex) }, + ) + } + } + } } } } diff --git a/ios/Cove/Flows/NewWalletFlow/HotWallet/HotWalletCreateScreen.swift b/ios/Cove/Flows/NewWalletFlow/HotWallet/HotWalletCreateScreen.swift index 1d390868..556f81d8 100644 --- a/ios/Cove/Flows/NewWalletFlow/HotWallet/HotWalletCreateScreen.swift +++ b/ios/Cove/Flows/NewWalletFlow/HotWallet/HotWalletCreateScreen.swift @@ -210,8 +210,22 @@ struct WordCardView: View { @Environment(\.sizeCategory) var sizeCategory let words: [GroupedWord] + private let numberOfColumns = 3 + + var numberOfRows: Int { + precondition( + words.count % numberOfColumns == 0, + "Word count (\(words.count)) must be divisible by \(numberOfColumns)" + ) + return words.count / numberOfColumns + } + + var rows: [GridItem] { + Array(repeating: .init(.flexible()), count: numberOfRows) + } + var body: some View { - LazyVGrid(columns: columns, spacing: 20) { + LazyHGrid(rows: rows, spacing: 12) { ForEach(words, id: \.self) { group in HStack(spacing: 0) { Text("\(String(format: "%d", group.number)). ") diff --git a/ios/Cove/Flows/NewWalletFlow/HotWallet/HotWalletImportCard.swift b/ios/Cove/Flows/NewWalletFlow/HotWallet/HotWalletImportCard.swift index 29f6b12c..c675588f 100644 --- a/ios/Cove/Flows/NewWalletFlow/HotWallet/HotWalletImportCard.swift +++ b/ios/Cove/Flows/NewWalletFlow/HotWallet/HotWalletImportCard.swift @@ -55,7 +55,8 @@ private final class TextFieldReturnHandler: NSObject, UITextFieldDelegate { } } -private let numberOfRows = 6 +private let numberOfColumns = 2 +private let numberOfRows = HotWalletImportScreen.GROUPS_OF / numberOfColumns private let groupsOf = HotWalletImportScreen.GROUPS_OF @@ -114,11 +115,12 @@ private struct CardTab: View { let cardSpacing: CGFloat = 20 var rows: [GridItem] { - Array(repeating: .init(.fixed(rowHeight)), count: numberOfRows) + Array(repeating: GridItem(.flexible()), count: numberOfRows) } var body: some View { - GeometryReader { proxy in + GeometryReader { geometry in + let columnWidth = (geometry.size.width - cardSpacing) / CGFloat(numberOfColumns) LazyHGrid(rows: rows, spacing: cardSpacing) { ForEach(Array(fields.enumerated()), id: \.offset) { index, _ in AutocompleteField( @@ -134,8 +136,8 @@ private struct CardTab: View { filteredSuggestions: $filteredSuggestions, focusField: $focusField ) + .frame(width: columnWidth) } - .frame(width: (proxy.size.width / 2) - (cardSpacing / 2)) } .frame(maxWidth: .infinity) } diff --git a/ios/Cove/Flows/SelectedWalletFlow/SecretWordsScreen.swift b/ios/Cove/Flows/SelectedWalletFlow/SecretWordsScreen.swift index f31c7af6..4c3dc9ca 100644 --- a/ios/Cove/Flows/SelectedWalletFlow/SecretWordsScreen.swift +++ b/ios/Cove/Flows/SelectedWalletFlow/SecretWordsScreen.swift @@ -17,17 +17,20 @@ struct SecretWordsScreen: View { @State var words: Mnemonic? @State var errorMessage: String? - var verticalSpacing: CGFloat { - 15 - } - let rowHeight = 30.0 + private let numberOfColumns = 3 + var numberOfRows: Int { - (words?.words().count ?? 24) / 3 + let wordCount = words?.words().count ?? 24 + precondition( + wordCount % numberOfColumns == 0, + "Word count (\(wordCount)) must be divisible by \(numberOfColumns)" + ) + return wordCount / numberOfColumns } var rows: [GridItem] { - Array(repeating: .init(.fixed(rowHeight)), count: numberOfRows) + Array(repeating: GridItem(.flexible()), count: numberOfRows) } var body: some View {