WIP: Fixing the uglies.

This commit is contained in:
2026-08-30 15:55:13 +09:00
parent 716f726dc0
commit 1d5fe4e784
17 changed files with 418 additions and 220 deletions
+2
View File
@@ -20,6 +20,8 @@
android:name=".MainActivity"
android:exported="true"
android:theme="@style/Theme.App.Starting"
android:screenOrientation="portrait"
android:resizeableActivity="false"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -42,11 +42,11 @@ import swiss.qpq.gajumobile.ui.screens.CreateWalletScreen
import swiss.qpq.gajumobile.ui.screens.DashboardScreen
import swiss.qpq.gajumobile.ui.screens.DeleteConfirmationScreen
import swiss.qpq.gajumobile.ui.screens.EnvironmentInfoScreen
import swiss.qpq.gajumobile.ui.screens.ExplorerScreen
import swiss.qpq.gajumobile.ui.screens.FetchingScreen
import swiss.qpq.gajumobile.ui.screens.LockScreen
import swiss.qpq.gajumobile.ui.screens.ManageAccountScreen
import swiss.qpq.gajumobile.ui.screens.ManageWalletsScreen
import swiss.qpq.gajumobile.ui.screens.ManualGridsEntryScreen
import swiss.qpq.gajumobile.ui.screens.MnemonicRecoveryScreen
import swiss.qpq.gajumobile.ui.screens.MnemonicVerifyScreen
import swiss.qpq.gajumobile.ui.screens.QRReceiveScreen
@@ -56,6 +56,7 @@ import swiss.qpq.gajumobile.ui.screens.SendReviewScreen
import swiss.qpq.gajumobile.ui.screens.SettingsScreen
import swiss.qpq.gajumobile.ui.screens.SetupChoiceScreen
import swiss.qpq.gajumobile.ui.screens.SignRequestScreen
import swiss.qpq.gajumobile.ui.screens.TransactionSuccessScreen
import swiss.qpq.gajumobile.ui.screens.ViewMnemonicScreen
import swiss.qpq.gajumobile.ui.theme.MyApplicationTheme
import swiss.qpq.gajumaru.core.formatting.GajuFormat
@@ -120,16 +121,19 @@ enum class Screen {
MANAGE_ACCOUNT,
MANAGE_WALLETS,
DELETE_CONFIRM,
EXPLORER,
VIEW_MNEMONIC,
VERIFY_MNEMONIC,
ENVIRONMENT_INFO,
SEND_FORM,
SEND_REVIEW,
SIGN_REQUEST,
FETCHING_SIGN_REQUEST
FETCHING_SIGN_REQUEST,
MANUAL_GRIDS_ENTRY,
TRANSACTION_SUCCESS
}
enum class ScannerMode { SEND, GRIDS }
@Composable
fun GajuRouter(
context: android.content.Context,
@@ -182,6 +186,8 @@ fun GajuRouter(
var activeWalletIndex by rememberSaveable { mutableIntStateOf(0) }
var activeAccountIndex by rememberSaveable { mutableIntStateOf(0) }
var currentScannerMode by rememberSaveable { mutableStateOf(ScannerMode.SEND) }
var nodeStatusResult by remember { mutableStateOf<Map<String, Any?>?>(null) }
var nodeStatusError by remember { mutableStateOf<String?>(null) }
@@ -194,10 +200,67 @@ fun GajuRouter(
var pendingGasPrice by rememberSaveable { mutableStateOf("") }
var pendingTTL by rememberSaveable { mutableStateOf("") }
var lastTxHash by rememberSaveable { mutableStateOf("") }
var lastTxAmount by rememberSaveable { mutableStateOf("") }
var lastTxRecipient by rememberSaveable { mutableStateOf("") }
var lastTxPayload by rememberSaveable { mutableStateOf("") }
var lastTxNetworkId by rememberSaveable { mutableStateOf("") }
var lastTxSenderLabel by rememberSaveable { mutableStateOf("") }
var lastTxWalletName by rememberSaveable { mutableStateOf("") }
var pendingSignRequest by remember { mutableStateOf<GridsSignRequest?>(null) }
var pendingSignUrl by rememberSaveable { mutableStateOf("") }
var activeFetchJob by remember { mutableStateOf<Job?>(null) }
fun processGridsUrl(url: String) {
try {
val result = Grids.parse(url)
if (result.verb == Grids.Verb.SIGN) {
pendingSignUrl = result.url
navigateTo(Screen.FETCHING_SIGN_REQUEST)
activeFetchJob = scope.launch {
try {
android.util.Log.i("GajuRouter", "Fetching GRIDS request from: ${result.url}")
@Suppress("UNCHECKED_CAST")
val response = withContext(Dispatchers.IO) {
Http.get(result.url) as? Map<String, Any?>
}
if (response != null) {
pendingSignRequest = response.toGridsSignRequest()
// Remove fetching screen and add request screen
screenStack.removeAt(screenStack.size - 1)
navigateTo(Screen.SIGN_REQUEST)
} else {
onError(context.getString(R.string.err_fetch_sig_failed))
navigateBack()
}
} catch (e: Exception) {
if (activeFetchJob?.isCancelled == false) {
onError(context.getString(R.string.err_fetch_failed, e.message ?: ""))
navigateBack()
}
} finally {
activeFetchJob = null
}
}
} else {
pendingScanResult = result
pendingRecipient = result.recipient ?: ""
pendingAmountStr = if (result.amount != null) {
val type = GajuFormat.Type.valueOf(appState.balanceFormat)
val unit = GajuFormat.Unit.valueOf(appState.balanceUnit)
GajuFormat.amount(GajuFormat.standardSpec(type, unit), result.amount)
} else ""
pendingPayload = if (result.payload != null) String(result.payload, Charsets.UTF_8) else ""
navigateTo(Screen.SEND_FORM)
}
} catch (e: Exception) {
onError(context.getString(R.string.err_invalid_grids_url, e.message ?: ""))
}
}
when (currentScreen) {
Screen.CREATE_WALLET -> {
CreateWalletScreen(
@@ -353,8 +416,28 @@ fun GajuRouter(
onAccountIndexChange = { activeAccountIndex = it },
balanceFormat = GajuFormat.Type.valueOf(appState.balanceFormat),
balanceUnit = GajuFormat.Unit.valueOf(appState.balanceUnit),
onSend = { navigateTo(Screen.QR_SCANNER) },
onGrids = { navigateTo(Screen.QR_SCANNER) },
onSend = {
currentScannerMode = ScannerMode.SEND
pendingRecipient = ""
pendingAmountStr = ""
pendingPayload = ""
pendingGas = "20000"
pendingGasPrice = "1000000000"
pendingTTL = "500"
pendingScanResult = null
navigateTo(Screen.QR_SCANNER)
},
onGrids = {
currentScannerMode = ScannerMode.GRIDS
pendingRecipient = ""
pendingAmountStr = ""
pendingPayload = ""
pendingGas = "20000"
pendingGasPrice = "1000000000"
pendingTTL = "500"
pendingScanResult = null
navigateTo(Screen.QR_SCANNER)
},
onReceive = { navigateTo(Screen.QR_RECEIVE) },
onAddAccount = { walletId ->
selectedWalletId = walletId
@@ -378,7 +461,6 @@ fun GajuRouter(
onNavigate = { dest ->
val screen = when (dest) {
"settings" -> Screen.SETTINGS
"transactions" -> Screen.EXPLORER
"environment_info" -> Screen.ENVIRONMENT_INFO
"manage_wallets" -> Screen.MANAGE_WALLETS
"CREATE_WALLET" -> Screen.CREATE_WALLET
@@ -573,7 +655,6 @@ fun GajuRouter(
} else {
val screen = when (dest) {
"dashboard" -> Screen.DASHBOARD
"transactions" -> Screen.EXPLORER
"environment_info" -> Screen.ENVIRONMENT_INFO
"manage_wallets" -> Screen.MANAGE_WALLETS
else -> null
@@ -598,55 +679,30 @@ fun GajuRouter(
}
)
}
Screen.QR_SCANNER -> QRScannerScreen(
onScanResult = { url ->
try {
val result = Grids.parse(url)
if (result.verb == Grids.Verb.SIGN) {
pendingSignUrl = result.url
navigateTo(Screen.FETCHING_SIGN_REQUEST)
activeFetchJob = scope.launch {
try {
android.util.Log.i("GajuRouter", "Fetching GRIDS request from: ${result.url}")
@Suppress("UNCHECKED_CAST")
val response = withContext(Dispatchers.IO) {
Http.get(result.url) as? Map<String, Any?>
}
if (response != null) {
pendingSignRequest = response.toGridsSignRequest()
// Remove fetching screen and add request screen
screenStack.removeAt(screenStack.size - 1)
navigateTo(Screen.SIGN_REQUEST)
} else {
onError(context.getString(R.string.err_fetch_sig_failed))
navigateBack()
}
} catch (e: Exception) {
if (activeFetchJob?.isCancelled == false) {
onError(context.getString(R.string.err_fetch_failed, e.message ?: ""))
navigateBack()
}
} finally {
activeFetchJob = null
}
}
Screen.QR_SCANNER -> {
QRScannerScreen(
onScanResult = { processGridsUrl(it) },
onManualEntry = {
if (currentScannerMode == ScannerMode.GRIDS) {
navigateTo(Screen.MANUAL_GRIDS_ENTRY)
} else {
pendingScanResult = result
navigateTo(Screen.SEND_FORM)
pendingScanResult = null
navigateTo(Screen.SEND_FORM)
}
} catch (e: Exception) {
onError(context.getString(R.string.err_invalid_grids_url, e.message ?: ""))
}
},
onGenerate = { navigateTo(Screen.QR_RECEIVE) },
onUpload = {},
onManualEntry = {
pendingScanResult = null
navigateTo(Screen.SEND_FORM)
},
onBack = { navigateBack() },
)
},
onBack = { navigateBack() },
)
}
Screen.MANUAL_GRIDS_ENTRY -> {
ManualGridsEntryScreen(
onConfirm = { url ->
// Remove manual entry screen and process result
screenStack.removeAt(screenStack.size - 1)
processGridsUrl(url)
},
onBack = { navigateBack() }
)
}
Screen.SEND_FORM -> {
val wallets = walletRepo.listWallets().mapNotNull { walletRepo.loadWallet(it) }
val activeWallet = wallets.getOrNull(activeWalletIndex)
@@ -658,22 +714,16 @@ fun GajuRouter(
return
}
val result = pendingScanResult
val initialRecipient = result?.recipient ?: ""
val initialAmount = if (result?.amount != null) {
val type = GajuFormat.Type.valueOf(appState.balanceFormat)
val unit = GajuFormat.Unit.valueOf(appState.balanceUnit)
GajuFormat.amount(GajuFormat.standardSpec(type, unit), result.amount)
} else ""
val initialPayload = if (result?.payload != null) String(result.payload, Charsets.UTF_8) else ""
SendFormScreen(
fromAccount = activeAccount,
balanceFormat = GajuFormat.Type.valueOf(appState.balanceFormat),
balanceUnit = GajuFormat.Unit.valueOf(appState.balanceUnit),
initialRecipient = initialRecipient,
initialAmount = initialAmount,
initialPayload = initialPayload,
initialRecipient = pendingRecipient,
initialAmount = pendingAmountStr,
initialPayload = pendingPayload,
initialGas = pendingGas,
initialGasPrice = pendingGasPrice,
initialTTL = pendingTTL,
onReview = { recipient, amountStr, payload, gas, gasPrice, ttl ->
pendingRecipient = recipient
pendingAmountStr = amountStr
@@ -699,9 +749,12 @@ fun GajuRouter(
SendReviewScreen(
senderLabel = activeAccount.label,
walletName = activeWallet.name,
networkId = activeWallet.networkId,
recipientLabel = "Recipient",
recipientId = pendingRecipient,
amountStr = pendingAmountStr,
payload = pendingPayload,
onSend = {
scope.launch {
try {
@@ -739,12 +792,31 @@ fun GajuRouter(
client.postTx(signedTx)
}
val hash = result["hash"] as? String ?: "Unknown"
onError(context.getString(R.string.msg_tx_posted, hash))
val hash = (result["tx_hash"] ?: result["hash"]) as? String ?: "Unknown"
// Success! Go back to dashboard
screenStack.clear()
screenStack.add(Screen.DASHBOARD)
lastTxHash = hash
lastTxAmount = GajuFormat.amount(
GajuFormat.standardSpec(
GajuFormat.Type.valueOf(appState.balanceFormat),
GajuFormat.Unit.valueOf(appState.balanceUnit)
),
amountPucks
)
lastTxRecipient = pendingRecipient
lastTxPayload = pendingPayload
lastTxNetworkId = activeWallet.networkId
lastTxSenderLabel = activeAccount.label
lastTxWalletName = activeWallet.name
// 6. Schedule a balance refresh in 3 seconds
scope.launch {
kotlinx.coroutines.delay(3000)
walletRepo.refreshAccount(activeWallet.id, activeAccount.id)
appStateManager.notifyWalletsChanged()
}
// Success! Go to success screen
navigateTo(Screen.TRANSACTION_SUCCESS)
} catch (e: Throwable) {
val msg = e.message ?: e.toString()
onError(context.getString(R.string.err_send_failed, msg))
@@ -853,19 +925,18 @@ fun GajuRouter(
onDone = { navigateBack() },
)
}
Screen.EXPLORER -> {
ExplorerScreen(
onNavigate = { dest ->
val screen = when (dest) {
"dashboard" -> Screen.DASHBOARD
"settings" -> Screen.SETTINGS
"manage_wallets" -> Screen.MANAGE_WALLETS
else -> null
}
if (screen != null) {
screenStack.clear()
screenStack.add(screen)
}
Screen.TRANSACTION_SUCCESS -> {
TransactionSuccessScreen(
amount = lastTxAmount,
recipient = lastTxRecipient,
payload = lastTxPayload,
txHash = lastTxHash,
senderLabel = lastTxSenderLabel,
walletName = lastTxWalletName,
networkId = lastTxNetworkId,
onExit = {
screenStack.clear()
screenStack.add(Screen.DASHBOARD)
}
)
}
@@ -138,14 +138,6 @@ fun GajuTopBar(
},
leadingIcon = { Icon(painterResource(R.drawable.ic_account_box), null, modifier = Modifier.size(18.dp)) }
)
DropdownMenuItem(
text = { Text(stringResource(R.string.menu_transactions)) },
onClick = {
menuExpanded = false
onNavigate("transactions")
},
leadingIcon = { Icon(painterResource(R.drawable.gajumobile_icon), null, modifier = Modifier.size(18.dp), tint = Color.Unspecified) }
)
DropdownMenuItem(
text = { Text(stringResource(R.string.menu_settings)) },
onClick = {
@@ -296,6 +288,7 @@ fun SuccessLayout(
content: @Composable ColumnScope.() -> Unit,
onProceed: () -> Unit,
proceedText: String = stringResource(R.string.proceed_to_dashboard),
requireRiskAcknowledgment: Boolean = true,
) {
var checked by remember { mutableStateOf(value = false) }
@@ -308,34 +301,37 @@ fun SuccessLayout(
.windowInsetsPadding(WindowInsets.navigationBars)
.padding(24.dp),
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.padding(bottom = 24.dp)
.clickable { checked = !checked },
) {
Checkbox(
checked = checked,
onCheckedChange = { checked = it },
colors = CheckboxDefaults.colors(
checkedColor = MaterialTheme.colorScheme.primary,
uncheckedColor = MaterialTheme.colorScheme.onBackground,
),
)
Text(
text = stringResource(R.string.understand_risks_success_label),
color = MaterialTheme.colorScheme.onBackground,
fontSize = 14.sp,
modifier = Modifier.padding(start = 8.dp),
)
if (requireRiskAcknowledgment) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.padding(bottom = 24.dp)
.clickable { checked = !checked },
) {
Checkbox(
checked = checked,
onCheckedChange = { checked = it },
colors = CheckboxDefaults.colors(
checkedColor = MaterialTheme.colorScheme.primary,
uncheckedColor = MaterialTheme.colorScheme.onBackground,
),
)
Text(
text = stringResource(R.string.understand_risks_success_label),
color = MaterialTheme.colorScheme.onBackground,
fontSize = 14.sp,
modifier = Modifier.padding(start = 8.dp),
)
}
}
GajuButton(
text = proceedText,
onClick = onProceed,
containerColor = if (checked) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.surfaceVariant,
contentColor = if (checked) MaterialTheme.colorScheme.onPrimary else MaterialTheme.colorScheme.onSurfaceVariant,
containerColor = if (!requireRiskAcknowledgment || checked) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.surfaceVariant,
contentColor = if (!requireRiskAcknowledgment || checked) MaterialTheme.colorScheme.onPrimary else MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.fillMaxWidth(),
enabled = !requireRiskAcknowledgment || checked
)
}
},
@@ -8,10 +8,13 @@ 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.imePadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawingPadding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
@@ -57,7 +60,12 @@ fun AccountNamingScreen(
}
Column(
modifier = Modifier.fillMaxSize().padding(24.dp).safeDrawingPadding(),
modifier = Modifier
.fillMaxSize()
.imePadding()
.padding(24.dp)
.safeDrawingPadding()
.verticalScroll(rememberScrollState()),
horizontalAlignment = Alignment.CenterHorizontally
) {
GajuHeader()
@@ -1,6 +1,8 @@
package swiss.qpq.gajumobile.ui.screens
import android.content.ClipData
import android.content.Intent
import android.net.Uri
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@@ -28,6 +30,7 @@ import androidx.compose.material3.TabRowDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -65,6 +68,7 @@ fun DashboardScreen(
onRefresh: (String, String) -> Unit,
onNavigate: (String) -> Unit,
) {
val context = androidx.compose.ui.platform.LocalContext.current
if (wallets.isEmpty()) {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
@@ -107,6 +111,14 @@ fun DashboardScreen(
val currentWallet = wallets[walletPagerState.currentPage]
val currentAccount = currentWallet.accounts.getOrNull(selectedAccountIndex)
// Automatically refresh balance after 1 second of staying on an account
LaunchedEffect(currentAccount?.id) {
currentAccount?.let { account ->
kotlinx.coroutines.delay(1000)
onRefresh(currentWallet.id, account.id)
}
}
Scaffold(
topBar = { GajuTopBar(onNavigate = onNavigate) },
) { innerPadding ->
@@ -249,6 +261,23 @@ fun DashboardScreen(
)
}
}
// View on Explorer Link
androidx.compose.material3.TextButton(
onClick = {
val url = "https://${currentWallet.networkId}.gajumaru.io/account/${account.gajuId}"
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
context.startActivity(intent)
},
modifier = Modifier.padding(bottom = 12.dp)
) {
Text(
text = stringResource(R.string.view_account_in_explorer),
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.primary,
fontWeight = FontWeight.Bold
)
}
}
// Balance Box
@@ -315,6 +344,20 @@ fun DashboardScreen(
fontSize = 24.sp,
fontWeight = FontWeight.Bold,
)
currentAccount?.cachedBalance?.let { balance ->
val timeStr = remember(balance.timestamp) {
val instant = java.time.Instant.ofEpochMilli(balance.timestamp)
val dateTime = java.time.LocalDateTime.ofInstant(instant, java.time.ZoneId.systemDefault())
dateTime.format(java.time.format.DateTimeFormatter.ofPattern("HH:mm:ss"))
}
Text(
text = stringResource(R.string.last_balance_check_label, timeStr),
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.5f),
modifier = Modifier.padding(top = 4.dp)
)
}
}
}
@@ -1,60 +0,0 @@
package swiss.qpq.gajumobile.ui.screens
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
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.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import swiss.qpq.gajumobile.R
import swiss.qpq.gajumobile.ui.components.GajuTopBar
@Composable
fun ExplorerScreen(
onNavigate: (String) -> Unit
) {
Scaffold(
topBar = { GajuTopBar(onNavigate = onNavigate) },
) { innerPadding ->
Column(
modifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colorScheme.background)
.padding(innerPadding),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(
text = stringResource(R.string.explorer_title),
color = MaterialTheme.colorScheme.onBackground,
style = MaterialTheme.typography.headlineMedium,
modifier = Modifier.padding(vertical = 16.dp),
)
Text(stringResource(R.string.loading_groot), color = MaterialTheme.colorScheme.primary, fontStyle = androidx.compose.ui.text.font.FontStyle.Italic)
Spacer(modifier = Modifier.height(16.dp))
Surface(
modifier = Modifier.fillMaxWidth().weight(1f).padding(horizontal = 24.dp, vertical = 8.dp),
color = MaterialTheme.colorScheme.surfaceVariant,
shape = RoundedCornerShape(4.dp),
) {
Box(contentAlignment = Alignment.Center) {
Text(stringResource(R.string.explorer_content_placeholder), color = MaterialTheme.colorScheme.onSurfaceVariant)
}
}
}
}
}
@@ -9,6 +9,7 @@ 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.imePadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
@@ -194,6 +195,7 @@ fun ManageWalletsScreen(
.fillMaxSize()
.background(MaterialTheme.colorScheme.background)
.padding(innerPadding)
.imePadding()
.padding(horizontal = 24.dp)
.verticalScroll(rememberScrollState()),
horizontalAlignment = Alignment.CenterHorizontally,
@@ -0,0 +1,82 @@
package swiss.qpq.gajumobile.ui.screens
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
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.imePadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import swiss.qpq.gajumobile.R
import swiss.qpq.gajumobile.ui.components.GajuButton
import swiss.qpq.gajumobile.ui.components.GajuHeader
import swiss.qpq.gajumobile.ui.components.GajuTextField
@Composable
fun ManualGridsEntryScreen(
onConfirm: (String) -> Unit,
onBack: () -> Unit
) {
var url by remember { mutableStateOf("") }
Column(
modifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colorScheme.background)
.imePadding()
.padding(24.dp)
.verticalScroll(rememberScrollState()),
horizontalAlignment = Alignment.CenterHorizontally
) {
GajuHeader()
Text(
text = "MANUAL GRIDS ENTRY",
color = MaterialTheme.colorScheme.onBackground,
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(vertical = 24.dp)
)
GajuTextField(
value = url,
onValueChange = { url = it },
label = "GRIDS URL",
placeholder = "grids://...",
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.weight(1f))
GajuButton(
text = "CONFIRM",
onClick = { if (url.isNotBlank()) onConfirm(url) },
enabled = url.isNotBlank()
)
Spacer(modifier = Modifier.height(16.dp))
GajuButton(
text = stringResource(id = R.string.back_button_caps),
onClick = onBack,
containerColor = MaterialTheme.colorScheme.secondary,
contentColor = MaterialTheme.colorScheme.onSecondary
)
}
}
@@ -33,14 +33,11 @@ import com.google.mlkit.vision.codescanner.GmsBarcodeScannerOptions
import androidx.compose.ui.res.stringResource
import swiss.qpq.gajumobile.R
import swiss.qpq.gajumobile.ui.components.GajuHeader
import swiss.qpq.gajumobile.ui.components.ScannerAction
import swiss.qpq.gajumobile.ui.components.GajuButton
@Composable
fun QRScannerScreen(
onScanResult: (String) -> Unit,
onGenerate: () -> Unit,
onUpload: () -> Unit,
onManualEntry: () -> Unit,
onBack: () -> Unit,
) {
@@ -114,24 +111,26 @@ fun QRScannerScreen(
Spacer(modifier = Modifier.weight(0.5f))
GajuButton(
text = stringResource(id = R.string.manual_entry_button),
onClick = onManualEntry,
modifier = Modifier.padding(horizontal = 48.dp),
)
Spacer(modifier = Modifier.weight(1f))
Row(
Column(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 48.dp),
horizontalArrangement = Arrangement.SpaceEvenly,
.padding(horizontal = 48.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
ScannerAction(label = stringResource(id = R.string.back_action), icon = R.drawable.ic_home, onClick = onBack)
ScannerAction(label = stringResource(id = R.string.generate_qr_action), icon = R.drawable.ic_qr_code, onClick = onGenerate)
ScannerAction(label = stringResource(id = R.string.upload_qr_action), icon = R.drawable.ic_qr_code, onClick = onUpload)
GajuButton(
text = stringResource(id = R.string.manual_entry_button),
onClick = onManualEntry,
)
GajuButton(
text = stringResource(id = R.string.back_button_caps),
onClick = onBack,
containerColor = MaterialTheme.colorScheme.secondary,
contentColor = MaterialTheme.colorScheme.onSecondary,
)
}
Spacer(modifier = Modifier.weight(1f))
}
}
}
@@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
@@ -47,15 +48,18 @@ fun SendFormScreen(
initialRecipient: String = "",
initialAmount: String = "",
initialPayload: String = "",
initialGas: String = "20000",
initialGasPrice: String = "1000000000",
initialTTL: String = "500",
onReview: (recipient: String, amount: String, payload: String, gas: String, gasPrice: String, ttl: String) -> Unit,
onBack: () -> Unit,
) {
var toAddress by remember { mutableStateOf(value = initialRecipient) }
var amount by remember { mutableStateOf(value = initialAmount) }
var payload by remember { mutableStateOf(value = initialPayload) }
var gas by remember { mutableStateOf(value = "20000") } // Default gas
var gasPrice by remember { mutableStateOf(value = "1000000000") } // Default gas price
var ttl by remember { mutableStateOf(value = "500") } // Default TTL
var gas by remember { mutableStateOf(value = initialGas) }
var gasPrice by remember { mutableStateOf(value = initialGasPrice) }
var ttl by remember { mutableStateOf(value = initialTTL) }
var showAdvanced by remember { mutableStateOf(value = false) }
Scaffold(
@@ -89,6 +93,7 @@ fun SendFormScreen(
.fillMaxSize()
.background(MaterialTheme.colorScheme.background)
.padding(innerPadding)
.imePadding()
.padding(horizontal = 24.dp)
.verticalScroll(rememberScrollState()),
horizontalAlignment = Alignment.CenterHorizontally,
@@ -40,9 +40,12 @@ import swiss.qpq.gajumobile.ui.components.GajuHeader
@Composable
fun SendReviewScreen(
senderLabel: String,
walletName: String,
networkId: String,
recipientLabel: String,
recipientId: String,
amountStr: String,
payload: String = "",
onSend: () -> Unit,
onBack: () -> Unit,
) {
@@ -108,7 +111,16 @@ fun SendReviewScreen(
)
Text(stringResource(id = R.string.sending_from_label), color = MaterialTheme.colorScheme.onSurfaceVariant, fontSize = 12.sp)
Text(senderLabel, color = MaterialTheme.colorScheme.primary, fontWeight = FontWeight.Bold)
Text(
text = "$senderLabel ($walletName)",
color = MaterialTheme.colorScheme.primary,
fontWeight = FontWeight.Bold
)
Text(
text = stringResource(id = R.string.on_network_label, networkId),
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f)
)
Spacer(modifier = Modifier.height(16.dp))
@@ -135,6 +147,12 @@ fun SendReviewScreen(
Text(stringResource(id = R.string.payment_summary_to), fontSize = 14.sp, color = MaterialTheme.colorScheme.onSurfaceVariant)
Text(recipientId.take(12) + "...", fontSize = 12.sp, color = MaterialTheme.colorScheme.onSurfaceVariant)
if (payload.isNotBlank()) {
Spacer(modifier = Modifier.height(16.dp))
Text(stringResource(id = R.string.payload_label), fontSize = 14.sp, color = MaterialTheme.colorScheme.onSurfaceVariant)
Text(payload, fontSize = 12.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onBackground)
}
Spacer(modifier = Modifier.height(16.dp))
Text(stringResource(id = R.string.cannot_be_undone_warning), fontWeight = FontWeight.ExtraBold, color = MaterialTheme.colorScheme.error)
Text(stringResource(id = R.string.proceed_confirmation_question), fontStyle = androidx.compose.ui.text.font.FontStyle.Italic, color = MaterialTheme.colorScheme.onSurfaceVariant)
@@ -1,11 +1,16 @@
package swiss.qpq.gajumobile.ui.screens
import android.content.Intent
import android.net.Uri
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
@@ -16,26 +21,58 @@ import swiss.qpq.gajumobile.ui.components.SuccessLayout
@Composable
fun TransactionSuccessScreen(
amount: String,
recipient: String,
payload: String,
txHash: String,
senderLabel: String,
walletName: String,
networkId: String,
onExit: () -> Unit,
) {
val context = LocalContext.current
SuccessLayout(
title = stringResource(R.string.send_title),
proceedText = stringResource(R.string.exit_button_caps),
onProceed = onExit,
requireRiskAcknowledgment = false,
content = {
Text(stringResource(R.string.transaction_success_title), color = MaterialTheme.colorScheme.primary, fontWeight = FontWeight.Bold)
Spacer(modifier = Modifier.height(32.dp))
Text(stringResource(R.string.transaction_success_payment_prefix), fontSize = 14.sp, color = MaterialTheme.colorScheme.onBackground)
Text("木 22,980.78991", fontSize = 24.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.primary)
Text(amount, fontSize = 24.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.primary)
Text(stringResource(R.string.transaction_success_to), fontSize = 14.sp, color = MaterialTheme.colorScheme.onBackground)
Text("ak_9jzm...", fontSize = 12.sp, color = MaterialTheme.colorScheme.onBackground)
Text(recipient, fontSize = 12.sp, color = MaterialTheme.colorScheme.onBackground, maxLines = 1)
if (payload.isNotBlank()) {
Spacer(modifier = Modifier.height(16.dp))
Text(stringResource(id = R.string.payload_label), fontSize = 12.sp, color = MaterialTheme.colorScheme.onSurfaceVariant)
Text(payload, fontSize = 12.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onBackground)
}
Spacer(modifier = Modifier.height(32.dp))
Text(stringResource(R.string.transaction_success_subtracted_msg), textAlign = TextAlign.Center, fontSize = 12.sp, color = MaterialTheme.colorScheme.onBackground)
Text(
text = stringResource(R.string.transaction_success_subtracted_msg, senderLabel, walletName, networkId),
textAlign = TextAlign.Center,
fontSize = 12.sp,
color = MaterialTheme.colorScheme.onBackground
)
Spacer(modifier = Modifier.height(32.dp))
Text(stringResource(R.string.transaction_success_track_hint), fontSize = 12.sp, color = MaterialTheme.colorScheme.onBackground)
Text(stringResource(R.string.view_transaction_on_chain), color = MaterialTheme.colorScheme.primary, fontWeight = FontWeight.Bold, fontSize = 12.sp)
Text(
text = stringResource(R.string.view_transaction_on_chain),
color = MaterialTheme.colorScheme.primary,
fontWeight = FontWeight.Bold,
fontSize = 12.sp,
modifier = Modifier
.clickable {
val url = "https://$networkId.gajumaru.io/tx/$txHash"
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
context.startActivity(intent)
}
.padding(8.dp)
)
},
)
}
+3 -2
View File
@@ -18,7 +18,6 @@
<!-- General / Shared -->
<string name="menu_manage_wallets">Wallets verwalten</string>
<string name="menu_transactions">Transaktionen</string>
<string name="menu_settings">Einstellungen</string>
<string name="menu_environment_info">Umgebungsinformationen</string>
<string name="tap_to_reveal">ZUM ANZEIGEN TIPPEN</string>
@@ -173,7 +172,7 @@
<string name="transaction_success_title">Transaktion erfolgreich!</string>
<string name="transaction_success_payment_prefix">Sie haben erfolgreich eine Zahlung gesendet von</string>
<string name="transaction_success_to">an</string>
<string name="transaction_success_subtracted_msg">Der Betrag wurde vom TÄGLICHEN Konto in Ihrer PRIMÄREN WALLET abgezogen.</string>
<string name="transaction_success_subtracted_msg">Der Betrag wurde vom %1$s-Konto in Ihrer %2$s-Wallet auf %3$s abgebucht.</string>
<string name="transaction_success_track_hint">Sie können diese Transaktion hier on-chain verfolgen.</string>
<string name="view_transaction_on_chain">TRANSAKTION ON-CHAIN ANZEIGEN</string>
<string name="understand_risks_success_label">Ich verstehe und übernehme alle Risiken.</string>
@@ -246,4 +245,6 @@
<string name="confirm_delete_button">LÖSCHEN\nBESTÄTIGEN</string>
<string name="wallet">WALLET</string>
<string name="account">KONTO</string>
<string name="view_account_in_explorer">KONTO IM EXPLORER ANZEIGEN</string>
<string name="last_balance_check_label">Letzte Prüfung: %1$s</string>
</resources>
+3 -2
View File
@@ -18,7 +18,6 @@
<!-- General / Shared -->
<string name="menu_manage_wallets">Gérer les portefeuilles</string>
<string name="menu_transactions">Transactions</string>
<string name="menu_settings">Paramètres</string>
<string name="menu_environment_info">Informations sur l\'environnement</string>
<string name="tap_to_reveal">APPUYER POUR RÉVÉLER</string>
@@ -173,7 +172,7 @@
<string name="transaction_success_title">Transaction réussie !</string>
<string name="transaction_success_payment_prefix">Vous avez envoyé avec succès un paiement de</string>
<string name="transaction_success_to">à</string>
<string name="transaction_success_subtracted_msg">Le montant a été déduit du compte QUOTIDIEN de votre PORTEFEUILLE PRINCIPAL.</string>
<string name="transaction_success_subtracted_msg">Le montant a été déduit du compte %1$s de votre portefeuille %2$s sur %3$s.</string>
<string name="transaction_success_track_hint">Vous pouvez suivre cette transaction sur la chaîne ici.</string>
<string name="view_transaction_on_chain">VOIR LA TRANSACTION SUR LA CHAÎNE</string>
<string name="understand_risks_success_label">Je comprends et assume pleinement les risques.</string>
@@ -246,4 +245,6 @@
<string name="confirm_delete_button">CONFIRMER LA\nSUPPRESSION</string>
<string name="wallet">PORTEFEUILLE</string>
<string name="account">COMPTE</string>
<string name="view_account_in_explorer">VOIR LE COMPTE DANS L\'EXPLORATEUR</string>
<string name="last_balance_check_label">Dernière vérification: %1$s</string>
</resources>
+3 -2
View File
@@ -18,7 +18,6 @@
<!-- General / Shared -->
<string name="menu_manage_wallets">Gestisci portafogli</string>
<string name="menu_transactions">Transazioni</string>
<string name="menu_settings">Impostazioni</string>
<string name="menu_environment_info">Informazioni sull\'ambiente</string>
<string name="tap_to_reveal">TOCCA PER RIVELARE</string>
@@ -173,7 +172,7 @@
<string name="transaction_success_title">Transazione riuscita!</string>
<string name="transaction_success_payment_prefix">Hai inviato con successo un pagamento di</string>
<string name="transaction_success_to">a</string>
<string name="transaction_success_subtracted_msg">L\'importo è stato detratto dal conto GIORNALIERO nel tuo PORTAFOGLIO PRINCIPALE.</string>
<string name="transaction_success_subtracted_msg">L\'importo è stato detratto dal conto %1$s nel tuo portafoglio %2$s su %3$s.</string>
<string name="transaction_success_track_hint">Puoi tracciare questa transazione on-chain qui.</string>
<string name="view_transaction_on_chain">VISUALIZZA TRANSAZIONE ON-CHAIN</string>
<string name="understand_risks_success_label">Comprendo e mi assumo pienamente i rischi.</string>
@@ -246,4 +245,6 @@
<string name="confirm_delete_button">CONFERMA\nELIMINA</string>
<string name="wallet">PORTAFOGLIO</string>
<string name="account">ACCOUNT</string>
<string name="view_account_in_explorer">VISUALIZZA ACCOUNT NELL\'EXPLORER</string>
<string name="last_balance_check_label">Ultima verifica: %1$s</string>
</resources>
+3 -7
View File
@@ -18,7 +18,6 @@
<!-- General / Shared -->
<string name="menu_manage_wallets">ウォレットを管理</string>
<string name="menu_transactions">トランザクション</string>
<string name="menu_settings">設定</string>
<string name="menu_environment_info">環境情報</string>
<string name="tap_to_reveal">タップして表示</string>
@@ -107,11 +106,6 @@
<string name="network_mainnet">メインネット</string>
<string name="network_testnet">テストネット</string>
<!-- ExplorerScreen -->
<string name="explorer_title">トランザクション</string>
<string name="loading_groot">Grootを読み込み中</string>
<string name="explorer_content_placeholder">Gajuエクスプローラーのコンテンツ</string>
<!-- SetupChoiceScreen -->
<string name="create_account_button">アカウントを作成</string>
<string name="recover_account_button">アカウントを復元</string>
@@ -173,7 +167,7 @@
<string name="transaction_success_title">トランザクション成功!</string>
<string name="transaction_success_payment_prefix">以下の送金に成功しました</string>
<string name="transaction_success_to">送信先</string>
<string name="transaction_success_subtracted_msg">金額はプライマリウォレットの日常アカウントから差し引かれました。</string>
<string name="transaction_success_subtracted_msg">金額は%3$s上の%2$sウォレットの%1$sアカウントから差し引かれました。</string>
<string name="transaction_success_track_hint">ここでチェーン上のトランザクションを追跡できます。</string>
<string name="view_transaction_on_chain">チェーン上でトランザクションを表示</string>
<string name="understand_risks_success_label">私はリスクを理解し、完全に引き受けます。</string>
@@ -246,4 +240,6 @@
<string name="confirm_delete_button">削除を確定</string>
<string name="wallet">ウォレット</string>
<string name="account">アカウント</string>
<string name="view_account_in_explorer">エクスプローラーでアカウントを表示</string>
<string name="last_balance_check_label">最終確認: %1$s</string>
</resources>
+3 -7
View File
@@ -37,7 +37,6 @@
<!-- General / Shared -->
<string name="menu_manage_wallets">Manage Wallets</string>
<string name="menu_transactions">Transactions</string>
<string name="menu_settings">Settings</string>
<string name="menu_environment_info">Environment Info</string>
<string name="tap_to_reveal">TAP TO REVEAL</string>
@@ -89,6 +88,7 @@
<string name="balance_label">BALANCE</string>
<string name="on_network_label">ON %1$s</string>
<string name="refresh_balance_content_desc">Refresh Balance</string>
<string name="last_balance_check_label">Last check at %1$s</string>
<string name="account_settings_content_desc">Account Settings</string>
<string name="send_action">Send</string>
<string name="grids_action">GRIDS</string>
@@ -107,11 +107,6 @@
<string name="network_mainnet">Mainnet</string>
<string name="network_testnet">Testnet</string>
<!-- ExplorerScreen -->
<string name="explorer_title">Transactions</string>
<string name="loading_groot">Loading Groot</string>
<string name="explorer_content_placeholder">Gaju Explorer Content</string>
<!-- SetupChoiceScreen -->
<string name="create_account_button">Create Account</string>
<string name="recover_account_button">Recover Account</string>
@@ -173,9 +168,10 @@
<string name="transaction_success_title">Transaction Successful!</string>
<string name="transaction_success_payment_prefix">You have successfully sent a payment of</string>
<string name="transaction_success_to">to</string>
<string name="transaction_success_subtracted_msg">The amount has been subtracted from the DAILY account in your PRIMARY WALLET.</string>
<string name="transaction_success_subtracted_msg">The amount has been subtracted from the %1$s account in your %2$s wallet on %3$s.</string>
<string name="transaction_success_track_hint">You can track this transaction on-chain here.</string>
<string name="view_transaction_on_chain">VIEW TRANSACTION ON-CHAIN</string>
<string name="view_account_in_explorer">VIEW ACCOUNT IN EXPLORER</string>
<string name="understand_risks_success_label">I understand and fully assume the risks.</string>
<!-- QRScannerScreen -->