From 7c5cc2c5c3c0a89254cc148c9458e241d5aa8314 Mon Sep 17 00:00:00 2001 From: greenart7c3 Date: Mon, 29 Jul 2024 17:34:53 -0300 Subject: [PATCH] add send screen --- app/build.gradle.kts | 16 +++ app/src/main/AndroidManifest.xml | 4 + .../com/greenart7c3/phoenixd/MainActivity.kt | 10 ++ .../phoenixd/screens/LoginScreen.kt | 2 +- .../phoenixd/screens/MainScreen.kt | 10 +- .../phoenixd/screens/ReceiveScreen.kt | 6 +- .../phoenixd/screens/SendScreen.kt | 111 ++++++++++++++++ .../phoenixd/services/CustomHttpClient.kt | 5 +- .../phoenixd/services/PhoenixdViewModel.kt | 5 + .../phoenixd/services/ReceiveViewModel.kt | 8 +- .../phoenixd/services/SendViewModel.kt | 125 ++++++++++++++++++ .../greenart7c3/phoenixd/ui/QrCodeScanner.kt | 82 ++++++++++++ .../com/greenart7c3/phoenixd/utils/Parser.kt | 90 +++++++++++++ .../res/layout/custom_barcode_scanner.xml | 47 +++++++ app/src/main/res/layout/scan_view.xml | 29 ++++ app/src/main/res/values/dimens.xml | 10 ++ app/src/main/res/values/strings.xml | 3 +- build.gradle.kts | 1 + gradle.properties | 10 +- gradle/libs.versions.toml | 7 + 20 files changed, 570 insertions(+), 11 deletions(-) create mode 100644 app/src/main/java/com/greenart7c3/phoenixd/screens/SendScreen.kt create mode 100644 app/src/main/java/com/greenart7c3/phoenixd/services/SendViewModel.kt create mode 100644 app/src/main/java/com/greenart7c3/phoenixd/ui/QrCodeScanner.kt create mode 100644 app/src/main/java/com/greenart7c3/phoenixd/utils/Parser.kt create mode 100644 app/src/main/res/layout/custom_barcode_scanner.xml create mode 100644 app/src/main/res/layout/scan_view.xml create mode 100644 app/src/main/res/values/dimens.xml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index f27916b..32cc232 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -4,6 +4,7 @@ plugins { alias(libs.plugins.compose.compiler) alias(libs.plugins.serialization) alias(libs.plugins.ktlint) + kotlin("kapt") version "2.0.0" } android { @@ -31,6 +32,13 @@ android { "proguard-rules.pro", ) } + debug { + debug { + applicationIdSuffix = ".debug" + versionNameSuffix = "-DEBUG" + resValue("string", "app_name", "@string/app_name_debug") + } + } } compileOptions { sourceCompatibility = JavaVersion.VERSION_1_8 @@ -41,6 +49,8 @@ android { } buildFeatures { compose = true + dataBinding = true + viewBinding = true } composeOptions { kotlinCompilerExtensionVersion = "1.5.1" @@ -69,6 +79,7 @@ dependencies { implementation(libs.androidx.ui.graphics) implementation(libs.androidx.ui.tooling.preview) implementation(libs.androidx.material3) + implementation(libs.androidx.ui.viewbinding) testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) @@ -86,4 +97,9 @@ dependencies { implementation(libs.ktor.cio) implementation(libs.zxing.core) implementation(libs.zxing.android.embedded) + implementation(libs.jna) { + artifact { type = "aar" } + } + implementation(libs.acinq.lightning.kmp) + implementation(libs.secp256k1.kmp) } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c7f4ce2..6186377 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -26,6 +26,10 @@ + diff --git a/app/src/main/java/com/greenart7c3/phoenixd/MainActivity.kt b/app/src/main/java/com/greenart7c3/phoenixd/MainActivity.kt index 210bd3f..be0170b 100644 --- a/app/src/main/java/com/greenart7c3/phoenixd/MainActivity.kt +++ b/app/src/main/java/com/greenart7c3/phoenixd/MainActivity.kt @@ -21,9 +21,11 @@ import androidx.navigation.compose.rememberNavController import com.greenart7c3.phoenixd.screens.LoginScreen import com.greenart7c3.phoenixd.screens.MainScreen import com.greenart7c3.phoenixd.screens.ReceiveScreen +import com.greenart7c3.phoenixd.screens.SendScreen import com.greenart7c3.phoenixd.services.LocalPreferences import com.greenart7c3.phoenixd.services.PhoenixdViewModel import com.greenart7c3.phoenixd.services.ReceiveViewModel +import com.greenart7c3.phoenixd.services.SendViewModel import com.greenart7c3.phoenixd.services.Settings import com.greenart7c3.phoenixd.ui.theme.PhoenixdTheme import kotlinx.coroutines.Dispatchers @@ -74,6 +76,14 @@ class MainActivity : ComponentActivity() { ) } + composable("send") { + val viewModel by viewModels() + SendScreen( + viewModel = viewModel, + navController = navController, + ) + } + composable("main") { val viewModel by viewModels() MainScreen( diff --git a/app/src/main/java/com/greenart7c3/phoenixd/screens/LoginScreen.kt b/app/src/main/java/com/greenart7c3/phoenixd/screens/LoginScreen.kt index eeb5669..d237dd9 100644 --- a/app/src/main/java/com/greenart7c3/phoenixd/screens/LoginScreen.kt +++ b/app/src/main/java/com/greenart7c3/phoenixd/screens/LoginScreen.kt @@ -53,7 +53,7 @@ fun LoginScreen(navController: NavController) { var password by remember { mutableStateOf(TextFieldValue("")) } - var loading by remember { mutableStateOf(true) } + var loading by remember { mutableStateOf(false) } var useSSL by remember { mutableStateOf(false) } val scope = rememberCoroutineScope() val clipboardManager = LocalClipboardManager.current diff --git a/app/src/main/java/com/greenart7c3/phoenixd/screens/MainScreen.kt b/app/src/main/java/com/greenart7c3/phoenixd/screens/MainScreen.kt index b1f515a..fd6faa8 100644 --- a/app/src/main/java/com/greenart7c3/phoenixd/screens/MainScreen.kt +++ b/app/src/main/java/com/greenart7c3/phoenixd/screens/MainScreen.kt @@ -89,6 +89,7 @@ fun MainScreen( Spacer(modifier = Modifier.size(16.dp)) ElevatedButton( onClick = { + navController.navigate("send") }, ) { Row( @@ -181,8 +182,15 @@ fun MainScreen( Column( horizontalAlignment = Alignment.End, ) { + val fees = payment.fees ?: 0 + val fee = if (fees > 0) fees / 1000 else 0 + val sent = if (fees > 0) it - fee else it Text( - text = "${DecimalFormat("#,###").format(it)} sat", + text = "${DecimalFormat("#,###").format(sent)} sat", + textAlign = TextAlign.End, + ) + Text( + text = "Fee ${DecimalFormat("#,###").format(fee)} sat", textAlign = TextAlign.End, ) formatCreatedAt(payment.createdAt)?.let { diff --git a/app/src/main/java/com/greenart7c3/phoenixd/screens/ReceiveScreen.kt b/app/src/main/java/com/greenart7c3/phoenixd/screens/ReceiveScreen.kt index b6d183c..18193f8 100644 --- a/app/src/main/java/com/greenart7c3/phoenixd/screens/ReceiveScreen.kt +++ b/app/src/main/java/com/greenart7c3/phoenixd/screens/ReceiveScreen.kt @@ -54,7 +54,9 @@ fun ReceiveScreen( Scaffold( topBar = { TopAppBar( - title = { }, + title = { + Text(text = "Receive") + }, navigationIcon = { IconButton( onClick = { @@ -142,7 +144,7 @@ fun ReceiveScreen( } }, ) { - Text(text = "Bolt12") + Text(text = "Offer") } } } diff --git a/app/src/main/java/com/greenart7c3/phoenixd/screens/SendScreen.kt b/app/src/main/java/com/greenart7c3/phoenixd/screens/SendScreen.kt new file mode 100644 index 0000000..c586006 --- /dev/null +++ b/app/src/main/java/com/greenart7c3/phoenixd/screens/SendScreen.kt @@ -0,0 +1,111 @@ +package com.greenart7c3.phoenixd.screens + +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.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.Send +import androidx.compose.material.icons.filled.Send +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.ElevatedButton +import androidx.compose.material3.Icon +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.LaunchedEffect +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.platform.LocalContext +import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.navigation.NavController +import com.greenart7c3.phoenixd.services.SendViewModel +import com.greenart7c3.phoenixd.ui.ScannerView +import com.journeyapps.barcodescanner.DecoratedBarcodeView + +@Composable +fun SendScreen( + viewModel: SendViewModel, + navController: NavController, +) { + val state = viewModel.state.collectAsStateWithLifecycle() + var scanView by remember { mutableStateOf(null) } + val context = LocalContext.current + + DisposableEffect(Unit) { + onDispose { + viewModel.clear() + } + } + + Scaffold { innerPadding -> + Box( + Modifier + .fillMaxSize() + .padding(innerPadding), + ) { + if (state.value.showScanner) { + ScannerView( + onScanViewBinding = { + scanView = it + }, + onScannedText = { + viewModel.onScannedText(it) + }, + ) + } else { + var textInput by remember(state.value.amount) { mutableStateOf(TextFieldValue(state.value.amount.toString())) } + LaunchedEffect(Unit) { + viewModel.processInput() + } + Column( + Modifier + .fillMaxSize() + .padding(16.dp), + Arrangement.Center, + Alignment.CenterHorizontally, + ) { + if (state.value.isLoading) { + CircularProgressIndicator() + } else { + OutlinedTextField( + modifier = Modifier.fillMaxWidth(), + value = textInput, + onValueChange = { + textInput = it + }, + ) + Spacer(modifier = Modifier.size(4.dp)) + ElevatedButton( + onClick = { + viewModel.send(context, navController) + }, + ) { + Row { + Icon( + Icons.AutoMirrored.Filled.Send, + contentDescription = "Send", + ) + Spacer(modifier = Modifier.size(4.dp)) + Text("Send") + } + } + } + } + } + } + } +} diff --git a/app/src/main/java/com/greenart7c3/phoenixd/services/CustomHttpClient.kt b/app/src/main/java/com/greenart7c3/phoenixd/services/CustomHttpClient.kt index d182e34..8f64814 100644 --- a/app/src/main/java/com/greenart7c3/phoenixd/services/CustomHttpClient.kt +++ b/app/src/main/java/com/greenart7c3/phoenixd/services/CustomHttpClient.kt @@ -49,13 +49,16 @@ class CustomHttpClient { suspend fun submitForm( url: String, + parameters: List>, ): HttpResponse { val localUrl = if (url.startsWith("/")) url else "/$url" val protocol = if (Settings.protocol == URLProtocol.HTTP) "http" else "https" return httpClient.submitForm( url = "$protocol://${Settings.host}:${Settings.port}$localUrl", formParameters = parameters { - append("description", "") + parameters.forEach { + append(it.first, it.second) + } }, ) { basicAuth("", Settings.password) diff --git a/app/src/main/java/com/greenart7c3/phoenixd/services/PhoenixdViewModel.kt b/app/src/main/java/com/greenart7c3/phoenixd/services/PhoenixdViewModel.kt index aebe970..bdd2103 100644 --- a/app/src/main/java/com/greenart7c3/phoenixd/services/PhoenixdViewModel.kt +++ b/app/src/main/java/com/greenart7c3/phoenixd/services/PhoenixdViewModel.kt @@ -43,6 +43,11 @@ class PhoenixdViewModel : ViewModel() { val response4 = httpClient.get("payments/outgoing") val outgoingPayments = response4.body>() + outgoingPayments.forEach { + Log.d("outgoingPayments", ((it.fees ?: 1) / 1000).toString()) + Log.d("outgoingPayments", it.sent.toString()) + } + localPayments.addAll(incomingPayments) localPayments.addAll(outgoingPayments) localPayments.sortByDescending { it.createdAt } diff --git a/app/src/main/java/com/greenart7c3/phoenixd/services/ReceiveViewModel.kt b/app/src/main/java/com/greenart7c3/phoenixd/services/ReceiveViewModel.kt index 4441ccd..7d192a6 100644 --- a/app/src/main/java/com/greenart7c3/phoenixd/services/ReceiveViewModel.kt +++ b/app/src/main/java/com/greenart7c3/phoenixd/services/ReceiveViewModel.kt @@ -42,7 +42,13 @@ class ReceiveViewModel : ViewModel() { qrCodeBolt12 = body, ) } - val responseInvoice = httpClient.submitForm("createinvoice") + val responseInvoice = httpClient.submitForm( + url = "createinvoice", + parameters = listOf( + Pair("description", ""), + Pair("amountSat", "1000"), + ), + ) val bodyInvoice = responseInvoice.body() state.value = state.value.copy( qrCode = bodyInvoice.serialized, diff --git a/app/src/main/java/com/greenart7c3/phoenixd/services/SendViewModel.kt b/app/src/main/java/com/greenart7c3/phoenixd/services/SendViewModel.kt new file mode 100644 index 0000000..8daf9cd --- /dev/null +++ b/app/src/main/java/com/greenart7c3/phoenixd/services/SendViewModel.kt @@ -0,0 +1,125 @@ +package com.greenart7c3.phoenixd.services + +import android.content.Context +import android.util.Log +import android.widget.Toast +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import androidx.navigation.NavController +import com.greenart7c3.phoenixd.utils.Parser +import fr.acinq.lightning.payment.Bolt11Invoice +import io.ktor.client.statement.bodyAsText +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.launch + +data class SendState( + val sanitizedInput: String = "", + val showScanner: Boolean = true, + val amount: Long = 0, + val isLoading: Boolean = false, +) + +class SendViewModel : ViewModel() { + private val _state = MutableStateFlow(SendState()) + val state = _state + private val httpClient = CustomHttpClient() + + fun onScannedText(text: String) { + val input = Parser.removePrefix(Parser.removeExcessInput(text)) + state.value = state.value.copy( + sanitizedInput = input, + showScanner = false, + ) + } + + init { + Log.d("SendViewModel", "SendViewModel created") + } + + override fun onCleared() { + httpClient.close() + super.onCleared() + } + + fun clear() { + state.value = SendState() + } + + fun processInput() { + val input = state.value.sanitizedInput + if (input.startsWith("lnbc")) { + val invoice = Bolt11Invoice.read(input) + invoice.get().let { + state.value = state.value.copy( + amount = it.amount?.truncateToSatoshi()?.sat ?: 0L, + ) + } + } + } + + fun send(context: Context, navController: NavController) { + if (state.value.amount <= 0) { + Toast.makeText( + context, + "Amount must be greater than 0", + Toast.LENGTH_SHORT, + ).show() + return + } + state.value = state.value.copy(isLoading = true) + viewModelScope.launch(Dispatchers.IO) { + val input = state.value.sanitizedInput + val response = if (input.startsWith("lnbc")) { + httpClient.submitForm( + "payinvoice", + listOf( + Pair("amountSat", state.value.amount.toString()), + Pair("invoice", input), + ), + ) + } else if (input.startsWith("lno1")) { + httpClient.submitForm( + "payoffer", + listOf( + Pair("amountSat", state.value.amount.toString()), + Pair("offer", input), + ), + ) + } else if (input.contains("@", ignoreCase = true)) { + httpClient.submitForm( + "paylnaddress", + listOf( + Pair("amountSat", state.value.amount.toString()), + Pair("address", input), + ), + ) + } else { + httpClient.submitForm( + "sendtoaddress", + listOf( + Pair("amountSat", state.value.amount.toString()), + Pair("address", input), + ), + ) + } + + Log.d("SendViewModel", response.bodyAsText()) + + state.value = state.value.copy(isLoading = false) + if (response.status.value == 200) { + viewModelScope.launch(Dispatchers.Main) { + navController.navigateUp() + } + } else { + viewModelScope.launch(Dispatchers.Main) { + Toast.makeText( + context, + "Failed to send payment", + Toast.LENGTH_SHORT, + ).show() + } + } + } + } +} diff --git a/app/src/main/java/com/greenart7c3/phoenixd/ui/QrCodeScanner.kt b/app/src/main/java/com/greenart7c3/phoenixd/ui/QrCodeScanner.kt new file mode 100644 index 0000000..5e5f763 --- /dev/null +++ b/app/src/main/java/com/greenart7c3/phoenixd/ui/QrCodeScanner.kt @@ -0,0 +1,82 @@ +package com.greenart7c3.phoenixd.ui + +import android.content.Intent +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.compose.foundation.layout.BoxScope +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.ui.Modifier +import androidx.compose.ui.viewinterop.AndroidViewBinding +import androidx.lifecycle.compose.LocalLifecycleOwner +import com.google.zxing.BarcodeFormat +import com.google.zxing.ResultPoint +import com.google.zxing.client.android.Intents +import com.greenart7c3.phoenixd.databinding.ScanViewBinding +import com.journeyapps.barcodescanner.BarcodeCallback +import com.journeyapps.barcodescanner.BarcodeResult +import com.journeyapps.barcodescanner.DecoratedBarcodeView +import com.journeyapps.barcodescanner.ScanContract +import com.journeyapps.barcodescanner.ScanOptions + +@Composable +fun BoxScope.ScannerView( + onScanViewBinding: (DecoratedBarcodeView) -> Unit, + onScannedText: (String) -> Unit, +) { + // scanner view using a legacy binding + AndroidViewBinding( + modifier = Modifier.fillMaxWidth(), + factory = { inflater, viewGroup, attach -> + val binding = ScanViewBinding.inflate(inflater, viewGroup, attach) + binding.scanView.let { scanView -> + scanView.initializeFromIntent( + Intent().apply { + putExtra(Intents.Scan.SCAN_TYPE, Intents.Scan.MIXED_SCAN) + putExtra(Intents.Scan.FORMATS, BarcodeFormat.QR_CODE.name) + }, + ) + scanView.decodeContinuous(object : BarcodeCallback { + override fun possibleResultPoints(resultPoints: MutableList?) = Unit + override fun barcodeResult(result: BarcodeResult?) { + result?.text?.trim()?.takeIf { it.isNotBlank() }?.let { + scanView.pause() + onScannedText(it) + } + } + }) + onScanViewBinding(scanView) + scanView.resume() + } + binding + }, + ) +} + +@Composable +fun SimpleQrCodeScanner(onScan: (String?) -> Unit) { + val lifecycleOwner = LocalLifecycleOwner.current + + val qrLauncher = + rememberLauncherForActivityResult(ScanContract()) { + if (it.contents != null) { + onScan(it.contents) + } else { + onScan(null) + } + } + + val scanOptions = + ScanOptions().apply { + setDesiredBarcodeFormats(ScanOptions.QR_CODE) + setPrompt("Point to the QR Code") + setBeepEnabled(false) + setOrientationLocked(false) + addExtra(Intents.Scan.SCAN_TYPE, Intents.Scan.MIXED_SCAN) + } + + DisposableEffect(lifecycleOwner) { + qrLauncher.launch(scanOptions) + onDispose { } + } +} diff --git a/app/src/main/java/com/greenart7c3/phoenixd/utils/Parser.kt b/app/src/main/java/com/greenart7c3/phoenixd/utils/Parser.kt new file mode 100644 index 0000000..3656d44 --- /dev/null +++ b/app/src/main/java/com/greenart7c3/phoenixd/utils/Parser.kt @@ -0,0 +1,90 @@ +/* + * Copyright 2022 ACINQ SAS + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.greenart7c3.phoenixd.utils + +import fr.acinq.bitcoin.utils.Try +import fr.acinq.lightning.payment.Bolt11Invoice +import fr.acinq.lightning.wire.OfferTypes + +object Parser { + + /** Order matters, as the prefixes are matched with startsWith. Longest prefixes should be at the beginning to avoid trimming only a part of the prefix. */ + val lightningPrefixes = listOf( + "phoenix:lightning://", + "phoenix:lightning:", + "lightning://", + "lightning:", + ) + + val bitcoinPrefixes = listOf( + "phoenix:bitcoin://", + "phoenix:bitcoin:", + "bitcoin://", + "bitcoin:", + ) + + val lnurlPrefixes = listOf( + "phoenix:lnurl://", + "phoenix:lnurl:", + "lnurl://", + "lnurl:", + ) + + fun removeExcessInput(input: String) = input.lines().firstOrNull { it.isNotBlank() }?.replace("\\u00A0", "")?.trim() ?: "" + + fun removePrefix(input: String): String { + val prefixes = lightningPrefixes + bitcoinPrefixes + lnurlPrefixes + + return prefixes.firstOrNull { input.startsWith(it, ignoreCase = true) }?.let { + input.drop(it.length) + } ?: input + } + + /** + * Remove the prefix from the input, if any. Trimming is done in a case-insensitive manner because often QR codes will + * use upper-case for the prefix, such as LIGHTNING:LNURL1... + */ + fun trimMatchingPrefix( + input: String, + prefixes: List, + ): String { + val matchingPrefix = prefixes.firstOrNull { input.startsWith(it, ignoreCase = true) } + return if (matchingPrefix != null) { + input.drop(matchingPrefix.length) + } else { + input + } + } + + /** Reads a payment request after stripping prefixes. Return null if input is invalid. */ + fun readBolt11Invoice(input: String): Bolt11Invoice? { + return when (val res = Bolt11Invoice.read(trimMatchingPrefix(removeExcessInput(input), lightningPrefixes))) { + is Try.Success -> res.get() + is Try.Failure -> null + } + } + + fun readOffer(input: String): OfferTypes.Offer? { + val cleanInput = trimMatchingPrefix(removeExcessInput(input), lightningPrefixes) + return when (val res = OfferTypes.Offer.decode(cleanInput)) { + is Try.Success -> res.get() + is Try.Failure -> { + null + } + } + } +} diff --git a/app/src/main/res/layout/custom_barcode_scanner.xml b/app/src/main/res/layout/custom_barcode_scanner.xml new file mode 100644 index 0000000..540a3f6 --- /dev/null +++ b/app/src/main/res/layout/custom_barcode_scanner.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + diff --git a/app/src/main/res/layout/scan_view.xml b/app/src/main/res/layout/scan_view.xml new file mode 100644 index 0000000..5cab194 --- /dev/null +++ b/app/src/main/res/layout/scan_view.xml @@ -0,0 +1,29 @@ + + + + + + diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml new file mode 100644 index 0000000..1460e95 --- /dev/null +++ b/app/src/main/res/values/dimens.xml @@ -0,0 +1,10 @@ + + + 4dp + 32dp + + 18dp + + + 1000dp + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6f47147..4888c39 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,3 +1,4 @@ phoenixd - \ No newline at end of file + phoenixd debug + diff --git a/build.gradle.kts b/build.gradle.kts index 19c8186..7002b0e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,6 +7,7 @@ plugins { alias(libs.plugins.compose.compiler) apply false alias(libs.plugins.ktlint) apply false alias(libs.plugins.serialization) apply false + kotlin("kapt") version "2.0.0" apply false } tasks.register("installGitHook") { diff --git a/gradle.properties b/gradle.properties index 20e2a01..d88abd7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,10 +6,10 @@ # http://www.gradle.org/docs/current/userguide/build_environment.html # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. -org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +org.gradle.jvmargs=-Xmx4096m -Dfile.encoding=UTF-8 # When configured, Gradle will run in incubating parallel mode. -# This option should only be used with decoupled projects. For more details, visit -# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true # AndroidX package structure to make it clearer which packages are bundled with the # Android operating system, and which are packaged with your app's APK @@ -20,4 +20,6 @@ kotlin.code.style=official # Enables namespacing of each library's R class so that its R class includes only the # resources declared in the library itself and none from the library's dependencies, # thereby reducing the size of the R class for that library -android.nonTransitiveRClass=true \ No newline at end of file +android.nonTransitiveRClass=true +android.enableR8.fullMode=true +android.nonFinalResIds=false diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a9c9e71..75cc33c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -16,6 +16,9 @@ kotlinxSerialization = "1.7.1" securityCryptoKtx = "1.1.0-alpha06" zxing = "4.3.0" zxingCore = "3.5.3" +lightning-kmp = "1.7.2-FEECREDIT-10" +jna = "5.14.0" +uiViewbinding = "1.6.8" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } @@ -42,6 +45,10 @@ ktor-client = { module = "io.ktor:ktor-client-core", version.ref = "ktor" } ktor-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" } zxing-core = { module = "com.google.zxing:core", version.ref = "zxingCore" } zxing-android-embedded = { module = "com.journeyapps:zxing-android-embedded", version.ref = "zxing" } +acinq-lightning-kmp = { group = "fr.acinq.lightning", name = "lightning-kmp", version.ref = "lightning-kmp" } +secp256k1-kmp = { group = "fr.acinq.secp256k1", name = "secp256k1-kmp-jni-android", version = "0.15.0" } +jna = { module = "net.java.dev.jna:jna", version.ref = "jna" } +androidx-ui-viewbinding = { group = "androidx.compose.ui", name = "ui-viewbinding", version.ref = "uiViewbinding" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" }