├── .editorconfig ├── .github └── workflows │ ├── docs.yaml │ └── ktlint.yaml ├── .gitignore ├── LICENSE ├── README.md ├── app ├── build.gradle.kts ├── proguard-rules.pro └── src │ ├── main │ ├── AndroidManifest.xml │ ├── assets │ │ └── bip39-english.txt │ ├── ic_launcher_bdk-playstore.png │ ├── java │ │ └── org │ │ │ └── bitcoindevkit │ │ │ └── devkitwallet │ │ │ ├── data │ │ │ ├── TxDetails.kt │ │ │ ├── UserPreferencesSerializer.kt │ │ │ └── WalletConfigs.kt │ │ │ ├── domain │ │ │ ├── BlockchainClient.kt │ │ │ ├── BlockchainClientsConfig.kt │ │ │ ├── Constants.kt │ │ │ ├── CurrencyUnit.kt │ │ │ ├── DwLogger.kt │ │ │ ├── UserPreferencesRepository.kt │ │ │ ├── Wallet.kt │ │ │ └── utils │ │ │ │ ├── FormatInBtc.kt │ │ │ │ ├── ProtobufExtensions.kt │ │ │ │ └── Timestamps.kt │ │ │ └── presentation │ │ │ ├── DevkitWalletActivity.kt │ │ │ ├── navigation │ │ │ ├── CreateWalletNavigation.kt │ │ │ ├── Destinations.kt │ │ │ ├── HomeNavigation.kt │ │ │ └── WalletNavigation.kt │ │ │ ├── theme │ │ │ ├── DevkitWalletColors.kt │ │ │ ├── Fonts.kt │ │ │ ├── Theme.kt │ │ │ └── Type.kt │ │ │ ├── ui │ │ │ ├── components │ │ │ │ ├── CustomSnackbar.kt │ │ │ │ ├── LoadingAnimation.kt │ │ │ │ ├── NeutralButton.kt │ │ │ │ ├── RadioButtonWithLabel.kt │ │ │ │ ├── SecondaryScreensAppBar.kt │ │ │ │ ├── TransactionCards.kt │ │ │ │ └── WalletOptionsCard.kt │ │ │ └── screens │ │ │ │ ├── WalletRoot.kt │ │ │ │ ├── drawer │ │ │ │ ├── AboutScreen.kt │ │ │ │ ├── BlockchainClientScreen.kt │ │ │ │ ├── LogsScreen.kt │ │ │ │ └── RecoveryDataScreen.kt │ │ │ │ ├── intro │ │ │ │ ├── ActiveWalletsScreen.kt │ │ │ │ ├── CreateNewWallet.kt │ │ │ │ ├── OnboardingScreen.kt │ │ │ │ ├── RecoverWalletScreen.kt │ │ │ │ └── WalletChoiceScreen.kt │ │ │ │ └── wallet │ │ │ │ ├── RBFScreen.kt │ │ │ │ ├── ReceiveScreen.kt │ │ │ │ ├── SendScreen.kt │ │ │ │ ├── TransactionHistoryScreen.kt │ │ │ │ ├── TransactionScreen.kt │ │ │ │ └── WalletHomeScreen.kt │ │ │ └── viewmodels │ │ │ ├── AddressViewModel.kt │ │ │ ├── SendViewModel.kt │ │ │ ├── WalletViewModel.kt │ │ │ └── mvi │ │ │ ├── MviReceiveScreen.kt │ │ │ ├── MviSendScreen.kt │ │ │ └── MviWalletScreen.kt │ ├── proto │ │ └── wallets.proto │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── bdk_logo.xml │ │ ├── ic_bitcoin_logo.xml │ │ ├── ic_launcher_bdk_background.xml │ │ ├── ic_launcher_bdk_foreground.xml │ │ ├── ic_testnet_logo.xml │ │ └── launch_screen.xml │ │ ├── font │ │ ├── ia_writer_mono_bold.ttf │ │ ├── ia_writer_mono_bold_italic.ttf │ │ ├── ia_writer_mono_regular.ttf │ │ ├── ia_writer_mono_regular_italic.ttf │ │ ├── ia_writer_quattro_bold.ttf │ │ ├── ia_writer_quattro_bold_italic.ttf │ │ ├── ia_writer_quattro_regular.ttf │ │ └── ia_writer_quattro_regular_italic.ttf │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ ├── ic_launcher_bdk.xml │ │ ├── ic_launcher_bdk_round.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_bdk.png │ │ ├── ic_launcher_bdk_round.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_bdk.png │ │ ├── ic_launcher_bdk_round.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_bdk.png │ │ ├── ic_launcher_bdk_round.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_bdk.png │ │ ├── ic_launcher_bdk_round.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_bdk.png │ │ ├── ic_launcher_bdk_round.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── splash.xml │ │ ├── strings.xml │ │ └── themes.xml │ └── test │ └── java │ └── org │ └── bitcoindevkit │ └── devkitwallet │ └── ExampleUnitTest.kt ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── images └── screenshots.png ├── justfile └── settings.gradle.kts /.editorconfig: -------------------------------------------------------------------------------- 1 | # Root .editorconfig file 2 | root = true 3 | 4 | [*.{kt,kts}] 5 | indent_style = space 6 | max_line_length = 120 7 | 8 | ktlint_standard_trailing-comma-on-call-site = disabled 9 | ktlint_standard_multiline-expression-wrapping = disabled 10 | ktlint_standard_string-template-indent = disabled 11 | ktlint_function_signature_rule_force_multiline_when_parameter_count_greater_or_equal_than = 5 12 | ktlint_function_naming_ignore_when_annotated_with=Composable 13 | ktlint_standard_function-expression-body = disabled 14 | ktlint_standard_class-signature = disabled 15 | -------------------------------------------------------------------------------- /.github/workflows/docs.yaml: -------------------------------------------------------------------------------- 1 | name: Deploy Companion Documentation Website 2 | 3 | on: workflow_dispatch 4 | 5 | jobs: 6 | deploy: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | - uses: actions/setup-python@v2 11 | with: 12 | python-version: 3.x 13 | - run: pip install mkdocs-material 14 | - run: mkdocs gh-deploy --force 15 | -------------------------------------------------------------------------------- /.github/workflows/ktlint.yaml: -------------------------------------------------------------------------------- 1 | name: Ktlint Check 2 | 3 | on: [workflow_dispatch, pull_request] 4 | 5 | jobs: 6 | ktlint: 7 | name: "Run Ktlint Check" 8 | runs-on: ubuntu-22.04 9 | 10 | steps: 11 | - name: "Checkout branch" 12 | uses: actions/checkout@v4 13 | 14 | - name: "Set up JDK 17" 15 | uses: actions/setup-java@v4 16 | with: 17 | distribution: temurin 18 | java-version: 17 19 | 20 | - name: "Run Ktlint Check" 21 | run: ./gradlew ktlintCheck 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /.idea/ 4 | .DS_Store 5 | /build 6 | /app/build/ 7 | /captures 8 | .externalNativeBuild 9 | .cxx 10 | app-simple-wallet/local.properties 11 | app-advanced-features/local.properties 12 | app-ui-only/local.properties 13 | app-simple-wallet/app/build/ 14 | app/build/ 15 | app-ui-only/app/build/ 16 | app-clean/ 17 | .idea/ 18 | local.properties 19 | app.run.xml 20 | release/ 21 | .kotlin/ 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bitcoindevkit Android Example Wallet 2 | 3 |

4 | 5 |

6 | 7 | The _Devkit Wallet_ is a wallet built as a reference app for the [bitcoindevkit](https://github.com/bitcoindevkit) on Android. It is a fork of the long-standing [Devkit Wallet](https://github.com/thunderbiscuit/devkit-wallet), a repository showcasing the bitcoindevkit library for beginner and advanced Android developers. This repository is not intended to be a production-ready wallet, and only works on Testnet3, Testnet4, Signet, and Regtest. 8 | 9 | This demo app is a departure of the Devkit Wallet approach and is built with the following goals in mind: 10 | 1. Be a reference application for the bitcoindevkit API on Android. 11 | 2. Showcase some of the more advanced features of the bitcoindevkit library. 12 | 13 | ## Variants 14 | 15 | The app is available in a few variants, each showcasing different features of the bitcoindevkit library. The variants live on different branches and are as follows: 16 | - **[variant/esplora](https://github.com/bitcoindevkit/devkit-wallet/tree/variant/esplora):** The default branch. This app receives its bitcoin data from a public Esplora instance. 17 | - **[variant/kyoto](https://github.com/bitcoindevkit/devkit-wallet/tree/variant/kyoto):** This app uses Compact Block Filters to sync its wallet. 18 | - **[variant/0.30.0](https://github.com/bitcoindevkit/devkit-wallet/tree/variant/0.30.0):** This app uses the `0.30.0` version of the bitcoindevkit library, showcasing the pre-1.0 API. 19 | -------------------------------------------------------------------------------- /app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jlleitschuh.gradle.ktlint.reporter.ReporterType 2 | 3 | plugins { 4 | id("com.android.application") version "8.7.1" 5 | id("org.jetbrains.kotlin.android") version "2.1.10" 6 | id("org.jetbrains.kotlin.plugin.compose") version "2.1.10" 7 | id("org.jetbrains.kotlin.plugin.serialization") version "2.1.10" 8 | id("com.google.protobuf") version "0.9.4" 9 | id("org.jlleitschuh.gradle.ktlint") version "12.1.2" 10 | } 11 | 12 | // This is the version of the app that is displayed in the UI on the drawer. 13 | val variantName = "Version 0.1.0/Esplora" 14 | 15 | android { 16 | namespace = "org.bitcoindevkit.devkitwallet" 17 | compileSdk = 35 18 | 19 | buildFeatures { 20 | viewBinding = true 21 | compose = true 22 | buildConfig = true 23 | } 24 | 25 | defaultConfig { 26 | applicationId = "org.bitcoindevkit.devkitwallet" 27 | minSdk = 26 28 | targetSdk = 35 29 | versionCode = 1 30 | versionName = "v0.1.0" 31 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 32 | 33 | buildConfigField("String", "VARIANT_NAME", "\"$variantName\"") 34 | } 35 | 36 | buildTypes { 37 | getByName("debug") { 38 | isDebuggable = true 39 | } 40 | } 41 | 42 | compileOptions { 43 | sourceCompatibility = JavaVersion.VERSION_17 44 | targetCompatibility = JavaVersion.VERSION_17 45 | } 46 | 47 | kotlinOptions { 48 | jvmTarget = "17" 49 | } 50 | } 51 | 52 | dependencies { 53 | // Basic android dependencies 54 | implementation("org.jetbrains.kotlin:kotlin-stdlib:1.9.23") 55 | implementation("androidx.core:core-ktx:1.13.1") 56 | implementation("com.google.android.material:material:1.12.0") 57 | implementation("androidx.datastore:datastore:1.1.1") 58 | implementation("com.google.protobuf:protobuf-javalite:3.18.0") 59 | implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3") 60 | implementation("androidx.core:core-splashscreen:1.0.1") 61 | 62 | // Jetpack Compose 63 | // Adding the Bill of Materials synchronizes dependencies in the androidx.compose namespace 64 | // You can remove the library version in your dependency declarations 65 | implementation(platform("androidx.compose:compose-bom:2025.02.00")) 66 | implementation("androidx.compose.animation:animation") 67 | implementation("androidx.compose.ui:ui-tooling") 68 | implementation("androidx.compose.ui:ui") 69 | implementation("androidx.compose.material3:material3") 70 | implementation("androidx.activity:activity-compose") 71 | implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.5") 72 | implementation("androidx.constraintlayout:constraintlayout-compose:1.0.1") 73 | implementation("androidx.navigation:navigation-compose:2.8.0") 74 | implementation("com.google.accompanist:accompanist-systemuicontroller:0.23.1") 75 | 76 | // Icons 77 | implementation("androidx.compose.material:material-icons-extended:1.7.8") 78 | implementation("com.composables:icons-lucide:1.0.0") 79 | 80 | // Toolbar 81 | implementation("androidx.appcompat:appcompat:1.7.0") 82 | 83 | // Bitcoin Development Kit 84 | implementation("org.bitcoindevkit:bdk-android:1.2.0") 85 | 86 | // QR codes 87 | implementation("com.google.zxing:core:3.5.3") 88 | 89 | // Tests 90 | testImplementation("junit:junit:4.13.2") 91 | androidTestImplementation("androidx.test.ext:junit:1.2.1") 92 | androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1") 93 | } 94 | 95 | protobuf { 96 | protoc { 97 | artifact = "com.google.protobuf:protoc:3.25.0" 98 | } 99 | 100 | generateProtoTasks { 101 | all().forEach { task -> 102 | task.builtins { 103 | create("java") { 104 | option("lite") 105 | } 106 | } 107 | } 108 | } 109 | } 110 | 111 | ktlint { 112 | version = "1.5.0" 113 | ignoreFailures = false 114 | reporters { 115 | reporter(ReporterType.PLAIN).apply { outputToConsole = true } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle.kts. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 13 | 14 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/ic_launcher_bdk-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcoindevkit/devkit-wallet/8dcdea6c64754d94773334ec5980bb64f789f079/app/src/main/ic_launcher_bdk-playstore.png -------------------------------------------------------------------------------- /app/src/main/java/org/bitcoindevkit/devkitwallet/data/TxDetails.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 thunderbiscuit and contributors. 3 | * Use of this source code is governed by the Apache 2.0 license that can be found in the ./LICENSE file. 4 | */ 5 | 6 | package org.bitcoindevkit.devkitwallet.data 7 | 8 | import org.bitcoindevkit.FeeRate 9 | import org.bitcoindevkit.Transaction 10 | 11 | data class TxDetails( 12 | val transaction: Transaction, 13 | val txid: String, 14 | val sent: ULong, 15 | val received: ULong, 16 | val fee: ULong, 17 | val feeRate: FeeRate?, 18 | val pending: Boolean, 19 | val confirmationBlock: ConfirmationBlock?, 20 | val confirmationTimestamp: Timestamp?, 21 | ) 22 | 23 | @JvmInline 24 | value class Timestamp(val timestamp: ULong) 25 | 26 | @JvmInline 27 | value class ConfirmationBlock(val height: UInt) 28 | -------------------------------------------------------------------------------- /app/src/main/java/org/bitcoindevkit/devkitwallet/data/UserPreferencesSerializer.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 thunderbiscuit and contributors. 3 | * Use of this source code is governed by the Apache 2.0 license that can be found in the ./LICENSE file. 4 | */ 5 | 6 | package org.bitcoindevkit.devkitwallet.data 7 | 8 | import androidx.datastore.core.CorruptionException 9 | import androidx.datastore.core.Serializer 10 | import com.google.protobuf.InvalidProtocolBufferException 11 | import java.io.InputStream 12 | import java.io.OutputStream 13 | 14 | object UserPreferencesSerializer : Serializer { 15 | override val defaultValue: UserPreferences = UserPreferences.getDefaultInstance() 16 | 17 | override suspend fun readFrom(input: InputStream): UserPreferences { 18 | try { 19 | return UserPreferences.parseFrom(input) 20 | } catch (exception: InvalidProtocolBufferException) { 21 | throw CorruptionException("Cannot read proto.", exception) 22 | } 23 | } 24 | 25 | override suspend fun writeTo(t: UserPreferences, output: OutputStream) { 26 | t.writeTo(output) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/org/bitcoindevkit/devkitwallet/data/WalletConfigs.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 thunderbiscuit and contributors. 3 | * Use of this source code is governed by the Apache 2.0 license that can be found in the ./LICENSE file. 4 | */ 5 | 6 | package org.bitcoindevkit.devkitwallet.data 7 | 8 | import org.bitcoindevkit.Descriptor 9 | import org.bitcoindevkit.Network 10 | 11 | data class NewWalletConfig( 12 | val name: String, 13 | val network: Network, 14 | val scriptType: ActiveWalletScriptType, 15 | ) 16 | 17 | data class RecoverWalletConfig( 18 | val name: String, 19 | val network: Network, 20 | val scriptType: ActiveWalletScriptType?, 21 | val recoveryPhrase: String?, 22 | val descriptor: Descriptor, 23 | val changeDescriptor: Descriptor, 24 | ) 25 | -------------------------------------------------------------------------------- /app/src/main/java/org/bitcoindevkit/devkitwallet/domain/BlockchainClient.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 thunderbiscuit and contributors. 3 | * Use of this source code is governed by the Apache 2.0 license that can be found in the ./LICENSE file. 4 | */ 5 | 6 | package org.bitcoindevkit.devkitwallet.domain 7 | 8 | import org.bitcoindevkit.FullScanRequest 9 | import org.bitcoindevkit.SyncRequest 10 | import org.bitcoindevkit.Transaction 11 | import org.bitcoindevkit.Update 12 | import org.bitcoindevkit.EsploraClient as BdkEsploraClient 13 | 14 | interface BlockchainClient { 15 | fun clientId(): String 16 | 17 | fun fullScan(fullScanRequest: FullScanRequest, stopGap: ULong): Update 18 | 19 | fun sync(syncRequest: SyncRequest): Update 20 | 21 | fun broadcast(transaction: Transaction): Unit 22 | 23 | fun endpoint(): String 24 | } 25 | 26 | class EsploraClient(private val url: String) : BlockchainClient { 27 | private val client = BdkEsploraClient(url) 28 | 29 | override fun clientId(): String { 30 | return url 31 | } 32 | 33 | override fun fullScan(fullScanRequest: FullScanRequest, stopGap: ULong): Update { 34 | return client.fullScan(fullScanRequest, stopGap, parallelRequests = 2u) 35 | } 36 | 37 | override fun sync(syncRequest: SyncRequest): Update { 38 | return client.sync(syncRequest, parallelRequests = 2u) 39 | } 40 | 41 | override fun broadcast(transaction: Transaction) { 42 | client.broadcast(transaction) 43 | } 44 | 45 | override fun endpoint(): String { 46 | return url 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/java/org/bitcoindevkit/devkitwallet/domain/BlockchainClientsConfig.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 thunderbiscuit and contributors. 3 | * Use of this source code is governed by the Apache 2.0 license that can be found in the ./LICENSE file. 4 | */ 5 | 6 | package org.bitcoindevkit.devkitwallet.domain 7 | 8 | import org.bitcoindevkit.Network 9 | 10 | class BlockchainClientsConfig { 11 | private var defaultClient: BlockchainClient? = null 12 | private val allClients: MutableList = mutableListOf() 13 | 14 | fun getClient(): BlockchainClient? { 15 | return defaultClient 16 | } 17 | 18 | fun addClient(client: BlockchainClient, setDefault: Boolean) { 19 | allClients.forEach { 20 | if (it.clientId() == client.clientId()) { 21 | throw IllegalArgumentException( 22 | "Client with url ${client.clientId()} already exists" 23 | ) 24 | } 25 | } 26 | if (allClients.size >= 8) throw IllegalArgumentException("Maximum number of clients (8) reached") 27 | allClients.add(client) 28 | if (setDefault) { 29 | defaultClient = client 30 | } 31 | } 32 | 33 | fun setDefaultClient(clientId: String) { 34 | val client = allClients.find { it.clientId() == clientId } 35 | if (client == null) throw IllegalArgumentException("Client with url $clientId not found") 36 | defaultClient = client 37 | } 38 | 39 | companion object { 40 | fun createDefaultConfig(network: Network): BlockchainClientsConfig { 41 | val config = BlockchainClientsConfig() 42 | when (network) { 43 | Network.REGTEST -> { 44 | config.addClient(EsploraClient("http://10.0.2.2:3002"), true) 45 | } 46 | Network.TESTNET -> { 47 | config.addClient(EsploraClient("https://blockstream.info/testnet/api/"), true) 48 | } 49 | Network.TESTNET4 -> throw IllegalArgumentException("This app does not support testnet 4 yet") 50 | Network.SIGNET -> { 51 | config.addClient(EsploraClient("http://signet.bitcoindevkit.net"), true) 52 | } 53 | Network.BITCOIN -> throw IllegalArgumentException("This app does not support mainnet") 54 | } 55 | return config 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /app/src/main/java/org/bitcoindevkit/devkitwallet/domain/CurrencyUnit.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 thunderbiscuit and contributors. 3 | * Use of this source code is governed by the Apache 2.0 license that can be found in the ./LICENSE file. 4 | */ 5 | 6 | package org.bitcoindevkit.devkitwallet.domain 7 | 8 | enum class CurrencyUnit { 9 | Bitcoin, 10 | Satoshi, 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/java/org/bitcoindevkit/devkitwallet/domain/DwLogger.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 thunderbiscuit and contributors. 3 | * Use of this source code is governed by the Apache 2.0 license that can be found in the ./LICENSE file. 4 | */ 5 | 6 | package org.bitcoindevkit.devkitwallet.domain 7 | 8 | import java.time.Instant 9 | import java.time.ZoneId 10 | import java.time.temporal.ChronoUnit 11 | 12 | object DwLogger { 13 | private const val MAX_LOGS = 5000 14 | private val logEntries = ArrayDeque(MAX_LOGS) 15 | private val lock = Any() 16 | 17 | fun log(tag: LogLevel, message: String) { 18 | synchronized(lock) { 19 | if (logEntries.size >= MAX_LOGS) { 20 | logEntries.removeLast() 21 | } 22 | val millis = System.currentTimeMillis() 23 | val dateTime = Instant.ofEpochMilli(millis) 24 | .atZone(ZoneId.systemDefault()) 25 | .toLocalDateTime() 26 | .truncatedTo(ChronoUnit.SECONDS) 27 | 28 | logEntries.addFirst("$dateTime $tag $message") 29 | } 30 | } 31 | 32 | fun getLogs(): List { 33 | synchronized(lock) { 34 | return logEntries.toList() 35 | } 36 | } 37 | 38 | enum class LogLevel { 39 | INFO, 40 | WARN, 41 | ERROR; 42 | 43 | override fun toString(): String { 44 | return when (this) { 45 | INFO -> "[INFO] " 46 | WARN -> "[WARN] " 47 | ERROR -> "[ERROR]" 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/src/main/java/org/bitcoindevkit/devkitwallet/domain/UserPreferencesRepository.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 thunderbiscuit and contributors. 3 | * Use of this source code is governed by the Apache 2.0 license that can be found in the ./LICENSE file. 4 | */ 5 | 6 | package org.bitcoindevkit.devkitwallet.domain 7 | 8 | import androidx.datastore.core.DataStore 9 | import kotlinx.coroutines.flow.first 10 | import org.bitcoindevkit.devkitwallet.data.SingleWallet 11 | import org.bitcoindevkit.devkitwallet.data.UserPreferences 12 | 13 | class UserPreferencesRepository( 14 | private val userPreferencesStore: DataStore, 15 | ) { 16 | suspend fun fetchIntroDone(): Boolean { 17 | return userPreferencesStore.data.first().introDone 18 | } 19 | 20 | suspend fun setIntroDone() { 21 | userPreferencesStore.updateData { currentPreferences -> 22 | currentPreferences.toBuilder().setIntroDone(true).build() 23 | } 24 | } 25 | 26 | suspend fun fetchActiveWallets(): List { 27 | return userPreferencesStore.data.first().walletsList 28 | } 29 | 30 | suspend fun updateActiveWallets(singleWallet: SingleWallet) { 31 | userPreferencesStore.updateData { currentPreferences -> 32 | currentPreferences.toBuilder().addWallets(singleWallet).build() 33 | } 34 | } 35 | 36 | suspend fun setFullScanCompleted(walletId: String) { 37 | userPreferencesStore.updateData { currentPreferences -> 38 | val updatedWalletsList = currentPreferences.walletsList.map { wallet -> 39 | if (wallet.id == walletId) { 40 | wallet.toBuilder().setFullScanCompleted(true).build() 41 | } else { 42 | wallet 43 | } 44 | } 45 | currentPreferences 46 | .toBuilder() 47 | .clearWallets() 48 | .addAllWallets(updatedWalletsList) 49 | .build() 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/src/main/java/org/bitcoindevkit/devkitwallet/domain/utils/FormatInBtc.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 thunderbiscuit and contributors. 3 | * Use of this source code is governed by the Apache 2.0 license that can be found in the ./LICENSE file. 4 | */ 5 | 6 | package org.bitcoindevkit.devkitwallet.domain.utils 7 | 8 | import java.text.DecimalFormat 9 | 10 | fun ULong?.formatInBtc(): String { 11 | val balanceInSats = 12 | if (this == 0UL || this == null) { 13 | 0F 14 | } else { 15 | this.toDouble().div(100_000_000) 16 | } 17 | return DecimalFormat("0.00000000").format(balanceInSats) 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/org/bitcoindevkit/devkitwallet/domain/utils/ProtobufExtensions.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 thunderbiscuit and contributors. 3 | * Use of this source code is governed by the Apache 2.0 license that can be found in the ./LICENSE file. 4 | */ 5 | 6 | package org.bitcoindevkit.devkitwallet.domain.utils 7 | 8 | import org.bitcoindevkit.Network 9 | import org.bitcoindevkit.devkitwallet.data.ActiveWalletNetwork 10 | 11 | fun Network.intoProto(): ActiveWalletNetwork { 12 | return when (this) { 13 | Network.REGTEST -> ActiveWalletNetwork.REGTEST 14 | Network.TESTNET -> ActiveWalletNetwork.TESTNET 15 | Network.TESTNET4 -> throw IllegalArgumentException("Bitcoin testnet 4 network is not supported") 16 | Network.SIGNET -> ActiveWalletNetwork.SIGNET 17 | Network.BITCOIN -> throw IllegalArgumentException("Bitcoin mainnet network is not supported") 18 | } 19 | } 20 | 21 | fun ActiveWalletNetwork.intoDomain(): Network { 22 | return when (this) { 23 | ActiveWalletNetwork.TESTNET -> Network.TESTNET 24 | ActiveWalletNetwork.SIGNET -> Network.SIGNET 25 | ActiveWalletNetwork.REGTEST -> Network.REGTEST 26 | ActiveWalletNetwork.UNRECOGNIZED -> throw IllegalArgumentException("Unrecognized network") 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/org/bitcoindevkit/devkitwallet/domain/utils/Timestamps.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 thunderbiscuit and contributors. 3 | * Use of this source code is governed by the Apache 2.0 license that can be found in the ./LICENSE file. 4 | */ 5 | 6 | package org.bitcoindevkit.devkitwallet.domain.utils 7 | 8 | import android.text.format.DateFormat 9 | import java.util.Calendar 10 | import java.util.Locale 11 | 12 | // extension function on the ULong timestamp provided in the Transaction.Confirmed type 13 | fun ULong.timestampToString(): String { 14 | val calendar = Calendar.getInstance(Locale.ENGLISH) 15 | calendar.timeInMillis = (this * 1000u).toLong() 16 | return DateFormat.format("MMMM d yyyy HH:mm", calendar).toString() 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/org/bitcoindevkit/devkitwallet/presentation/DevkitWalletActivity.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 thunderbiscuit and contributors. 3 | * Use of this source code is governed by the Apache 2.0 license that can be found in the ./LICENSE file. 4 | */ 5 | 6 | package org.bitcoindevkit.devkitwallet.presentation 7 | 8 | import android.content.Context 9 | import android.os.Bundle 10 | import android.util.Log 11 | import androidx.activity.compose.setContent 12 | import androidx.appcompat.app.AppCompatActivity 13 | import androidx.datastore.core.DataStore 14 | import androidx.datastore.dataStore 15 | import androidx.lifecycle.lifecycleScope 16 | import kotlinx.coroutines.async 17 | import kotlinx.coroutines.launch 18 | import org.bitcoindevkit.devkitwallet.data.NewWalletConfig 19 | import org.bitcoindevkit.devkitwallet.data.RecoverWalletConfig 20 | import org.bitcoindevkit.devkitwallet.data.SingleWallet 21 | import org.bitcoindevkit.devkitwallet.data.UserPreferences 22 | import org.bitcoindevkit.devkitwallet.data.UserPreferencesSerializer 23 | import org.bitcoindevkit.devkitwallet.domain.DwLogger 24 | import org.bitcoindevkit.devkitwallet.domain.DwLogger.LogLevel.INFO 25 | import org.bitcoindevkit.devkitwallet.domain.UserPreferencesRepository 26 | import org.bitcoindevkit.devkitwallet.domain.Wallet 27 | import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen 28 | import org.bitcoindevkit.devkitwallet.presentation.navigation.CreateWalletNavigation 29 | import org.bitcoindevkit.devkitwallet.presentation.navigation.HomeNavigation 30 | import org.bitcoindevkit.devkitwallet.presentation.theme.DevkitTheme 31 | import org.bitcoindevkit.devkitwallet.presentation.ui.screens.intro.OnboardingScreen 32 | 33 | private const val TAG = "DevkitWalletActivity" 34 | private val Context.userPreferencesStore: DataStore by dataStore( 35 | fileName = "user_preferences.pb", 36 | serializer = UserPreferencesSerializer 37 | ) 38 | 39 | class DevkitWalletActivity : AppCompatActivity() { 40 | override fun onCreate(savedInstanceState: Bundle?) { 41 | installSplashScreen() 42 | 43 | super.onCreate(savedInstanceState) 44 | 45 | // Initialize Devkit Wallet Logger (used in the LogsScreen) 46 | DwLogger.log(INFO, "Devkit Wallet app started") 47 | 48 | val userPreferencesRepository = UserPreferencesRepository(userPreferencesStore) 49 | val onBuildWalletButtonClicked: (WalletCreateType) -> Unit = { walletCreateType -> 50 | try { 51 | val activeWallet = when (walletCreateType) { 52 | is WalletCreateType.FROMSCRATCH -> Wallet.createWallet( 53 | newWalletConfig = walletCreateType.newWalletConfig, 54 | internalAppFilesPath = filesDir.absolutePath, 55 | userPreferencesRepository = userPreferencesRepository 56 | ) 57 | is WalletCreateType.LOADEXISTING -> Wallet.loadActiveWallet( 58 | activeWallet = walletCreateType.activeWallet, 59 | internalAppFilesPath = filesDir.absolutePath, 60 | userPreferencesRepository = userPreferencesRepository, 61 | ) 62 | is WalletCreateType.RECOVER -> Wallet.recoverWallet( 63 | recoverWalletConfig = walletCreateType.recoverWalletConfig, 64 | internalAppFilesPath = filesDir.absolutePath, 65 | userPreferencesRepository = userPreferencesRepository, 66 | ) 67 | } 68 | setContent { 69 | DevkitTheme { 70 | HomeNavigation(activeWallet) 71 | } 72 | } 73 | } catch (e: Throwable) { 74 | Log.i(TAG, "Could not build wallet: $e") 75 | } 76 | } 77 | 78 | lifecycleScope.launch { 79 | val activeWallets = 80 | async { 81 | userPreferencesRepository.fetchActiveWallets() 82 | }.await() 83 | 84 | val onboardingDone = 85 | async { 86 | userPreferencesRepository.fetchIntroDone() 87 | }.await() 88 | 89 | val onFinishOnboarding: () -> Unit = { 90 | lifecycleScope.launch { userPreferencesRepository.setIntroDone() } 91 | setContent { 92 | DevkitTheme { 93 | CreateWalletNavigation(onBuildWalletButtonClicked, activeWallets) 94 | } 95 | } 96 | } 97 | 98 | setContent { 99 | if (!onboardingDone) { 100 | DwLogger.log(INFO, "First time opening the app, triggering onboarding screen") 101 | OnboardingScreen(onFinishOnboarding) 102 | } else { 103 | DevkitTheme { 104 | CreateWalletNavigation(onBuildWalletButtonClicked, activeWallets) 105 | } 106 | } 107 | } 108 | } 109 | } 110 | } 111 | 112 | sealed class WalletCreateType { 113 | data class FROMSCRATCH(val newWalletConfig: NewWalletConfig) : WalletCreateType() 114 | 115 | data class LOADEXISTING(val activeWallet: SingleWallet) : WalletCreateType() 116 | 117 | data class RECOVER(val recoverWalletConfig: RecoverWalletConfig) : WalletCreateType() 118 | } 119 | -------------------------------------------------------------------------------- /app/src/main/java/org/bitcoindevkit/devkitwallet/presentation/navigation/CreateWalletNavigation.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 thunderbiscuit and contributors. 3 | * Use of this source code is governed by the Apache 2.0 license that can be found in the ./LICENSE file. 4 | */ 5 | 6 | package org.bitcoindevkit.devkitwallet.presentation.navigation 7 | 8 | import androidx.compose.animation.AnimatedContentTransitionScope 9 | import androidx.compose.animation.core.tween 10 | import androidx.compose.runtime.Composable 11 | import androidx.navigation.NavHostController 12 | import androidx.navigation.compose.NavHost 13 | import androidx.navigation.compose.composable 14 | import androidx.navigation.compose.rememberNavController 15 | import org.bitcoindevkit.devkitwallet.data.SingleWallet 16 | import org.bitcoindevkit.devkitwallet.presentation.WalletCreateType 17 | import org.bitcoindevkit.devkitwallet.presentation.ui.screens.intro.ActiveWalletsScreen 18 | import org.bitcoindevkit.devkitwallet.presentation.ui.screens.intro.CreateNewWalletScreen 19 | import org.bitcoindevkit.devkitwallet.presentation.ui.screens.intro.RecoverWalletScreen 20 | import org.bitcoindevkit.devkitwallet.presentation.ui.screens.intro.WalletChoiceScreen 21 | 22 | @Composable 23 | fun CreateWalletNavigation(onBuildWalletButtonClicked: (WalletCreateType) -> Unit, activeWallets: List) { 24 | val navController: NavHostController = rememberNavController() 25 | val animationDuration = 400 26 | 27 | NavHost( 28 | navController = navController, 29 | startDestination = WalletChoiceScreen, 30 | ) { 31 | composable( 32 | exitTransition = { 33 | slideOutOfContainer( 34 | AnimatedContentTransitionScope.SlideDirection.Start, 35 | animationSpec = tween(animationDuration) 36 | ) 37 | }, 38 | popEnterTransition = { 39 | slideIntoContainer( 40 | AnimatedContentTransitionScope.SlideDirection.End, 41 | animationSpec = tween(animationDuration) 42 | ) 43 | }, 44 | ) { WalletChoiceScreen(navController = navController) } 45 | 46 | composable( 47 | enterTransition = { 48 | slideIntoContainer( 49 | AnimatedContentTransitionScope.SlideDirection.Start, 50 | animationSpec = tween(animationDuration) 51 | ) 52 | }, 53 | exitTransition = { 54 | slideOutOfContainer( 55 | AnimatedContentTransitionScope.SlideDirection.Start, 56 | animationSpec = tween(animationDuration) 57 | ) 58 | }, 59 | popEnterTransition = { 60 | slideIntoContainer( 61 | AnimatedContentTransitionScope.SlideDirection.End, 62 | animationSpec = tween(animationDuration) 63 | ) 64 | }, 65 | popExitTransition = { 66 | slideOutOfContainer( 67 | AnimatedContentTransitionScope.SlideDirection.End, 68 | animationSpec = tween(animationDuration) 69 | ) 70 | } 71 | ) { 72 | ActiveWalletsScreen( 73 | activeWallets = activeWallets, 74 | navController = navController, 75 | onBuildWalletButtonClicked 76 | ) 77 | } 78 | 79 | composable( 80 | enterTransition = { 81 | slideIntoContainer( 82 | AnimatedContentTransitionScope.SlideDirection.Start, 83 | animationSpec = tween(animationDuration) 84 | ) 85 | }, 86 | exitTransition = { 87 | slideOutOfContainer( 88 | AnimatedContentTransitionScope.SlideDirection.Start, 89 | animationSpec = tween(animationDuration) 90 | ) 91 | }, 92 | popEnterTransition = { 93 | slideIntoContainer( 94 | AnimatedContentTransitionScope.SlideDirection.End, 95 | animationSpec = tween(animationDuration) 96 | ) 97 | }, 98 | popExitTransition = { 99 | slideOutOfContainer( 100 | AnimatedContentTransitionScope.SlideDirection.End, 101 | animationSpec = tween(animationDuration) 102 | ) 103 | } 104 | ) { CreateNewWalletScreen(navController = navController, onBuildWalletButtonClicked) } 105 | 106 | composable( 107 | enterTransition = { 108 | slideIntoContainer( 109 | AnimatedContentTransitionScope.SlideDirection.Start, 110 | animationSpec = tween(animationDuration) 111 | ) 112 | }, 113 | exitTransition = { 114 | slideOutOfContainer( 115 | AnimatedContentTransitionScope.SlideDirection.Start, 116 | animationSpec = tween(animationDuration) 117 | ) 118 | }, 119 | popEnterTransition = { 120 | slideIntoContainer( 121 | AnimatedContentTransitionScope.SlideDirection.End, 122 | animationSpec = tween(animationDuration) 123 | ) 124 | }, 125 | popExitTransition = { 126 | slideOutOfContainer( 127 | AnimatedContentTransitionScope.SlideDirection.End, 128 | animationSpec = tween(animationDuration) 129 | ) 130 | } 131 | ) { RecoverWalletScreen(onAction = onBuildWalletButtonClicked, navController = navController) } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /app/src/main/java/org/bitcoindevkit/devkitwallet/presentation/navigation/Destinations.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 thunderbiscuit and contributors. 3 | * Use of this source code is governed by the Apache 2.0 license that can be found in the ./LICENSE file. 4 | */ 5 | 6 | package org.bitcoindevkit.devkitwallet.presentation.navigation 7 | 8 | import kotlinx.serialization.Serializable 9 | 10 | // Create wallet navigation destinations 11 | @Serializable 12 | object WalletChoiceScreen 13 | 14 | @Serializable 15 | object ActiveWalletsScreen 16 | 17 | @Serializable 18 | object CreateNewWalletScreen 19 | 20 | @Serializable 21 | object WalletRecoveryScreen 22 | 23 | // Home navigation destinations 24 | @Serializable 25 | object WalletScreen 26 | 27 | @Serializable 28 | object AboutScreen 29 | 30 | @Serializable 31 | object RecoveryPhraseScreen 32 | 33 | @Serializable 34 | object BlockchainClientScreen 35 | 36 | @Serializable 37 | object LogsScreen 38 | 39 | // Wallet navigation destinations 40 | @Serializable 41 | object HomeScreen 42 | 43 | @Serializable 44 | object ReceiveScreen 45 | 46 | @Serializable 47 | object SendScreen 48 | 49 | @Serializable 50 | object TransactionHistoryScreen 51 | 52 | @Serializable 53 | data class TransactionScreen(val txid: String) 54 | 55 | @Serializable 56 | data class RbfScreen(val txid: String) 57 | -------------------------------------------------------------------------------- /app/src/main/java/org/bitcoindevkit/devkitwallet/presentation/navigation/HomeNavigation.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 thunderbiscuit and contributors. 3 | * Use of this source code is governed by the Apache 2.0 license that can be found in the ./LICENSE file. 4 | */ 5 | 6 | package org.bitcoindevkit.devkitwallet.presentation.navigation 7 | 8 | import androidx.compose.animation.AnimatedContentTransitionScope 9 | import androidx.compose.animation.core.tween 10 | import androidx.compose.runtime.Composable 11 | import androidx.navigation.NavHostController 12 | import androidx.navigation.compose.NavHost 13 | import androidx.navigation.compose.composable 14 | import androidx.navigation.compose.rememberNavController 15 | import org.bitcoindevkit.devkitwallet.domain.Wallet 16 | import org.bitcoindevkit.devkitwallet.presentation.ui.screens.WalletRoot 17 | import org.bitcoindevkit.devkitwallet.presentation.ui.screens.drawer.AboutScreen 18 | import org.bitcoindevkit.devkitwallet.presentation.ui.screens.drawer.BlockchainClientScreen 19 | import org.bitcoindevkit.devkitwallet.presentation.ui.screens.drawer.LogsScreen 20 | import org.bitcoindevkit.devkitwallet.presentation.ui.screens.drawer.RecoveryDataScreen 21 | import org.bitcoindevkit.devkitwallet.presentation.viewmodels.WalletViewModel 22 | 23 | private const val ANIMATION_DURATION: Int = 400 24 | 25 | @Composable 26 | fun HomeNavigation(activeWallet: Wallet) { 27 | val navController: NavHostController = rememberNavController() 28 | val walletViewModel = WalletViewModel(activeWallet) 29 | 30 | NavHost( 31 | navController = navController, 32 | startDestination = WalletScreen, 33 | ) { 34 | composable( 35 | exitTransition = { 36 | slideOutOfContainer( 37 | AnimatedContentTransitionScope.SlideDirection.Start, 38 | animationSpec = tween(ANIMATION_DURATION) 39 | ) 40 | }, 41 | popEnterTransition = { 42 | slideIntoContainer( 43 | AnimatedContentTransitionScope.SlideDirection.End, 44 | animationSpec = tween(ANIMATION_DURATION) 45 | ) 46 | }, 47 | ) { WalletRoot(navController = navController, activeWallet = activeWallet, walletViewModel = walletViewModel) } 48 | 49 | composable( 50 | enterTransition = { 51 | slideIntoContainer( 52 | AnimatedContentTransitionScope.SlideDirection.Start, 53 | animationSpec = tween(ANIMATION_DURATION) 54 | ) 55 | }, 56 | popEnterTransition = { 57 | slideIntoContainer( 58 | AnimatedContentTransitionScope.SlideDirection.Start, 59 | animationSpec = tween(ANIMATION_DURATION) 60 | ) 61 | }, 62 | exitTransition = { 63 | slideOutOfContainer( 64 | AnimatedContentTransitionScope.SlideDirection.Start, 65 | animationSpec = tween(ANIMATION_DURATION) 66 | ) 67 | }, 68 | popExitTransition = { 69 | slideOutOfContainer( 70 | AnimatedContentTransitionScope.SlideDirection.Start, 71 | animationSpec = tween(ANIMATION_DURATION) 72 | ) 73 | }, 74 | ) { AboutScreen(navController = navController) } 75 | 76 | composable( 77 | enterTransition = { 78 | slideIntoContainer( 79 | AnimatedContentTransitionScope.SlideDirection.Start, 80 | animationSpec = tween(ANIMATION_DURATION) 81 | ) 82 | }, 83 | popEnterTransition = { 84 | slideIntoContainer( 85 | AnimatedContentTransitionScope.SlideDirection.Start, 86 | animationSpec = tween(ANIMATION_DURATION) 87 | ) 88 | }, 89 | exitTransition = { 90 | slideOutOfContainer( 91 | AnimatedContentTransitionScope.SlideDirection.Start, 92 | animationSpec = tween(ANIMATION_DURATION) 93 | ) 94 | }, 95 | popExitTransition = { 96 | slideOutOfContainer( 97 | AnimatedContentTransitionScope.SlideDirection.Start, 98 | animationSpec = tween(ANIMATION_DURATION) 99 | ) 100 | } 101 | ) { RecoveryDataScreen(activeWallet.getWalletSecrets(), navController = navController) } 102 | 103 | composable( 104 | enterTransition = { 105 | slideIntoContainer( 106 | AnimatedContentTransitionScope.SlideDirection.Start, 107 | animationSpec = tween(ANIMATION_DURATION) 108 | ) 109 | }, 110 | popEnterTransition = { 111 | slideIntoContainer( 112 | AnimatedContentTransitionScope.SlideDirection.Start, 113 | animationSpec = tween(ANIMATION_DURATION) 114 | ) 115 | }, 116 | exitTransition = { 117 | slideOutOfContainer( 118 | AnimatedContentTransitionScope.SlideDirection.Start, 119 | animationSpec = tween(ANIMATION_DURATION) 120 | ) 121 | }, 122 | popExitTransition = { 123 | slideOutOfContainer( 124 | AnimatedContentTransitionScope.SlideDirection.Start, 125 | animationSpec = tween(ANIMATION_DURATION) 126 | ) 127 | } 128 | ) { 129 | BlockchainClientScreen( 130 | state = walletViewModel.state, 131 | navController = navController 132 | ) 133 | } 134 | 135 | composable( 136 | enterTransition = { 137 | slideIntoContainer( 138 | AnimatedContentTransitionScope.SlideDirection.Start, 139 | animationSpec = tween(ANIMATION_DURATION) 140 | ) 141 | }, 142 | popEnterTransition = { 143 | slideIntoContainer( 144 | AnimatedContentTransitionScope.SlideDirection.Start, 145 | animationSpec = tween(ANIMATION_DURATION) 146 | ) 147 | }, 148 | exitTransition = { 149 | slideOutOfContainer( 150 | AnimatedContentTransitionScope.SlideDirection.Start, 151 | animationSpec = tween(ANIMATION_DURATION) 152 | ) 153 | }, 154 | popExitTransition = { 155 | slideOutOfContainer( 156 | AnimatedContentTransitionScope.SlideDirection.Start, 157 | animationSpec = tween(ANIMATION_DURATION) 158 | ) 159 | } 160 | ) { LogsScreen(navController = navController) } 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /app/src/main/java/org/bitcoindevkit/devkitwallet/presentation/navigation/WalletNavigation.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 thunderbiscuit and contributors. 3 | * Use of this source code is governed by the Apache 2.0 license that can be found in the ./LICENSE file. 4 | */ 5 | 6 | package org.bitcoindevkit.devkitwallet.presentation.navigation 7 | 8 | import androidx.compose.animation.AnimatedContentTransitionScope 9 | import androidx.compose.animation.core.tween 10 | import androidx.compose.material3.DrawerState 11 | import androidx.compose.runtime.Composable 12 | import androidx.navigation.NavHostController 13 | import androidx.navigation.compose.NavHost 14 | import androidx.navigation.compose.composable 15 | import androidx.navigation.compose.rememberNavController 16 | import androidx.navigation.toRoute 17 | import org.bitcoindevkit.devkitwallet.domain.Wallet 18 | import org.bitcoindevkit.devkitwallet.presentation.ui.screens.wallet.RBFScreen 19 | import org.bitcoindevkit.devkitwallet.presentation.ui.screens.wallet.ReceiveScreen 20 | import org.bitcoindevkit.devkitwallet.presentation.ui.screens.wallet.SendScreen 21 | import org.bitcoindevkit.devkitwallet.presentation.ui.screens.wallet.TransactionHistoryScreen 22 | import org.bitcoindevkit.devkitwallet.presentation.ui.screens.wallet.TransactionScreen 23 | import org.bitcoindevkit.devkitwallet.presentation.ui.screens.wallet.WalletHomeScreen 24 | import org.bitcoindevkit.devkitwallet.presentation.viewmodels.AddressViewModel 25 | import org.bitcoindevkit.devkitwallet.presentation.viewmodels.SendViewModel 26 | import org.bitcoindevkit.devkitwallet.presentation.viewmodels.WalletViewModel 27 | 28 | private const val ANIMATION_DURATION: Int = 400 29 | 30 | @Composable 31 | fun WalletNavigation(drawerState: DrawerState, activeWallet: Wallet, walletViewModel: WalletViewModel) { 32 | val navController: NavHostController = rememberNavController() 33 | val addressViewModel = AddressViewModel(activeWallet) 34 | val sendViewModel = SendViewModel(activeWallet) 35 | 36 | NavHost( 37 | navController = navController, 38 | startDestination = HomeScreen, 39 | ) { 40 | composable( 41 | exitTransition = { 42 | slideOutOfContainer( 43 | AnimatedContentTransitionScope.SlideDirection.Start, 44 | animationSpec = tween(ANIMATION_DURATION) 45 | ) 46 | }, 47 | popEnterTransition = { 48 | slideIntoContainer( 49 | AnimatedContentTransitionScope.SlideDirection.End, 50 | animationSpec = tween(ANIMATION_DURATION) 51 | ) 52 | }, 53 | ) { WalletHomeScreen(navController, drawerState, walletViewModel) } 54 | 55 | composable( 56 | enterTransition = { 57 | slideIntoContainer( 58 | AnimatedContentTransitionScope.SlideDirection.Start, 59 | animationSpec = tween(ANIMATION_DURATION) 60 | ) 61 | }, 62 | exitTransition = { 63 | slideOutOfContainer( 64 | AnimatedContentTransitionScope.SlideDirection.End, 65 | animationSpec = tween(ANIMATION_DURATION) 66 | ) 67 | }, 68 | popEnterTransition = { 69 | slideIntoContainer( 70 | AnimatedContentTransitionScope.SlideDirection.Start, 71 | animationSpec = tween(ANIMATION_DURATION) 72 | ) 73 | }, 74 | popExitTransition = { 75 | slideOutOfContainer( 76 | AnimatedContentTransitionScope.SlideDirection.End, 77 | animationSpec = tween(ANIMATION_DURATION) 78 | ) 79 | } 80 | ) { 81 | ReceiveScreen( 82 | state = addressViewModel.state, 83 | onAction = addressViewModel::onAction, 84 | navController 85 | ) 86 | } 87 | 88 | composable( 89 | enterTransition = { 90 | slideIntoContainer( 91 | AnimatedContentTransitionScope.SlideDirection.Start, 92 | animationSpec = tween(ANIMATION_DURATION) 93 | ) 94 | }, 95 | exitTransition = { 96 | slideOutOfContainer( 97 | AnimatedContentTransitionScope.SlideDirection.End, 98 | animationSpec = tween(ANIMATION_DURATION) 99 | ) 100 | }, 101 | popEnterTransition = { 102 | slideIntoContainer( 103 | AnimatedContentTransitionScope.SlideDirection.Start, 104 | animationSpec = tween(ANIMATION_DURATION) 105 | ) 106 | }, 107 | popExitTransition = { 108 | slideOutOfContainer( 109 | AnimatedContentTransitionScope.SlideDirection.End, 110 | animationSpec = tween(ANIMATION_DURATION) 111 | ) 112 | } 113 | ) { SendScreen(navController, sendViewModel) } 114 | 115 | composable( 116 | enterTransition = { 117 | slideIntoContainer( 118 | AnimatedContentTransitionScope.SlideDirection.Start, 119 | animationSpec = tween(ANIMATION_DURATION) 120 | ) 121 | }, 122 | exitTransition = { 123 | slideOutOfContainer( 124 | AnimatedContentTransitionScope.SlideDirection.End, 125 | animationSpec = tween(ANIMATION_DURATION) 126 | ) 127 | }, 128 | popEnterTransition = { 129 | slideIntoContainer( 130 | AnimatedContentTransitionScope.SlideDirection.Start, 131 | animationSpec = tween(ANIMATION_DURATION) 132 | ) 133 | }, 134 | popExitTransition = { 135 | slideOutOfContainer( 136 | AnimatedContentTransitionScope.SlideDirection.End, 137 | animationSpec = tween(ANIMATION_DURATION) 138 | ) 139 | } 140 | ) { 141 | val args = it.toRoute() 142 | RBFScreen(args.txid, navController) 143 | } 144 | 145 | composable( 146 | enterTransition = { 147 | slideIntoContainer( 148 | AnimatedContentTransitionScope.SlideDirection.Start, 149 | animationSpec = tween(ANIMATION_DURATION) 150 | ) 151 | }, 152 | exitTransition = { 153 | slideOutOfContainer( 154 | AnimatedContentTransitionScope.SlideDirection.End, 155 | animationSpec = tween(ANIMATION_DURATION) 156 | ) 157 | }, 158 | popEnterTransition = { 159 | slideIntoContainer( 160 | AnimatedContentTransitionScope.SlideDirection.Start, 161 | animationSpec = tween(ANIMATION_DURATION) 162 | ) 163 | }, 164 | popExitTransition = { 165 | slideOutOfContainer( 166 | AnimatedContentTransitionScope.SlideDirection.End, 167 | animationSpec = tween(ANIMATION_DURATION) 168 | ) 169 | } 170 | ) { TransactionHistoryScreen(navController, activeWallet) } 171 | 172 | composable( 173 | enterTransition = { 174 | slideIntoContainer( 175 | AnimatedContentTransitionScope.SlideDirection.Start, 176 | animationSpec = tween(ANIMATION_DURATION) 177 | ) 178 | }, 179 | exitTransition = { 180 | slideOutOfContainer( 181 | AnimatedContentTransitionScope.SlideDirection.End, 182 | animationSpec = tween(ANIMATION_DURATION) 183 | ) 184 | }, 185 | popEnterTransition = { 186 | slideIntoContainer( 187 | AnimatedContentTransitionScope.SlideDirection.Start, 188 | animationSpec = tween(ANIMATION_DURATION) 189 | ) 190 | }, 191 | popExitTransition = { 192 | slideOutOfContainer( 193 | AnimatedContentTransitionScope.SlideDirection.End, 194 | animationSpec = tween(ANIMATION_DURATION) 195 | ) 196 | } 197 | ) { 198 | val args = it.toRoute() 199 | TransactionScreen(args.txid, navController) 200 | } 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /app/src/main/java/org/bitcoindevkit/devkitwallet/presentation/theme/DevkitWalletColors.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 thunderbiscuit and contributors. 3 | * Use of this source code is governed by the Apache 2.0 license that can be found in the ./LICENSE file. 4 | */ 5 | 6 | package org.bitcoindevkit.devkitwallet.presentation.theme 7 | 8 | import androidx.compose.ui.graphics.Color 9 | 10 | @Suppress("ktlint:standard:comment-spacing") 11 | object DevkitWalletColors { 12 | val primaryDark: Color = Color(0xFF203B46) // App bar 13 | val primary: Color = Color(0xFF264653) // Background 14 | val primaryLight: Color = Color(0xFF335F70) // Behind balance primary light 15 | val white: Color = Color(0xffffffff) // Most text 16 | val secondary: Color = Color(0xFF2A9D8F) // Buttons 17 | val accent1: Color = Color(0xFFE9C46A) // Receive button 18 | val accent2: Color = Color(0xFFE76F51) // Send button 19 | } 20 | 21 | internal val TestPink = Color(0xffff1493) 22 | -------------------------------------------------------------------------------- /app/src/main/java/org/bitcoindevkit/devkitwallet/presentation/theme/Fonts.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 thunderbiscuit and contributors. 3 | * Use of this source code is governed by the Apache 2.0 license that can be found in the ./LICENSE file. 4 | */ 5 | 6 | package org.bitcoindevkit.devkitwallet.presentation.theme 7 | 8 | import androidx.compose.ui.text.font.Font 9 | import androidx.compose.ui.text.font.FontFamily 10 | import androidx.compose.ui.text.font.FontStyle 11 | import androidx.compose.ui.text.font.FontWeight 12 | import org.bitcoindevkit.devkitwallet.R 13 | 14 | val quattroRegular = FontFamily( 15 | Font( 16 | resId = R.font.ia_writer_quattro_regular, 17 | weight = FontWeight.Normal, 18 | style = FontStyle.Normal 19 | ) 20 | ) 21 | 22 | val quattroBold = FontFamily( 23 | Font( 24 | resId = R.font.ia_writer_quattro_bold, 25 | weight = FontWeight.Bold, 26 | style = FontStyle.Normal 27 | ) 28 | ) 29 | 30 | val monoRegular = FontFamily( 31 | Font( 32 | resId = R.font.ia_writer_mono_regular, 33 | weight = FontWeight.Normal, 34 | style = FontStyle.Normal 35 | ) 36 | ) 37 | 38 | val monoBold = FontFamily( 39 | Font( 40 | resId = R.font.ia_writer_mono_bold, 41 | weight = FontWeight.Bold, 42 | style = FontStyle.Normal 43 | ) 44 | ) 45 | -------------------------------------------------------------------------------- /app/src/main/java/org/bitcoindevkit/devkitwallet/presentation/theme/Theme.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 thunderbiscuit and contributors. 3 | * Use of this source code is governed by the Apache 2.0 license that can be found in the ./LICENSE file. 4 | */ 5 | 6 | package org.bitcoindevkit.devkitwallet.presentation.theme 7 | 8 | import androidx.compose.material3.MaterialTheme 9 | import androidx.compose.runtime.Composable 10 | 11 | @Composable 12 | fun DevkitTheme(content: @Composable () -> Unit) { 13 | MaterialTheme( 14 | // colorScheme = devkitColors, 15 | // shapes = devkitShapes, 16 | typography = devkitTypography, 17 | content = content 18 | ) 19 | } 20 | 21 | // NOTES ON THE UI 22 | // - The standard padding is 32dp for start/end, 16dp for top/bottom 23 | -------------------------------------------------------------------------------- /app/src/main/java/org/bitcoindevkit/devkitwallet/presentation/theme/Type.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 thunderbiscuit and contributors. 3 | * Use of this source code is governed by the Apache 2.0 license that can be found in the ./LICENSE file. 4 | */ 5 | 6 | package org.bitcoindevkit.devkitwallet.presentation.theme 7 | 8 | import androidx.compose.material3.Typography 9 | import androidx.compose.ui.text.TextStyle 10 | import androidx.compose.ui.text.font.FontWeight 11 | import androidx.compose.ui.unit.sp 12 | 13 | internal val devkitTypography = 14 | Typography( 15 | labelLarge = 16 | TextStyle( 17 | fontFamily = quattroRegular, 18 | fontWeight = FontWeight.Normal, 19 | fontSize = 16.sp, 20 | lineHeight = 28.sp 21 | ), 22 | ) 23 | 24 | // These are the default text styles used by Material3 components: 25 | // Buttons: labelLarge 26 | 27 | internal val standardText = TextStyle( 28 | color = DevkitWalletColors.white, 29 | fontFamily = quattroRegular, 30 | fontSize = 14.sp, 31 | fontWeight = FontWeight.Normal, 32 | ) 33 | -------------------------------------------------------------------------------- /app/src/main/java/org/bitcoindevkit/devkitwallet/presentation/ui/components/CustomSnackbar.kt: -------------------------------------------------------------------------------- 1 | package org.bitcoindevkit.devkitwallet.presentation.ui.components 2 | 3 | import androidx.compose.foundation.layout.padding 4 | import androidx.compose.material3.Icon 5 | import androidx.compose.material3.IconButton 6 | import androidx.compose.material3.Snackbar 7 | import androidx.compose.material3.SnackbarData 8 | import androidx.compose.material3.Text 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.ui.Modifier 11 | import androidx.compose.ui.unit.dp 12 | import androidx.compose.ui.unit.sp 13 | import com.composables.icons.lucide.Lucide 14 | import com.composables.icons.lucide.X 15 | import org.bitcoindevkit.devkitwallet.presentation.theme.DevkitWalletColors 16 | import org.bitcoindevkit.devkitwallet.presentation.theme.quattroRegular 17 | 18 | @Composable 19 | fun CustomSnackbar(data: SnackbarData) { 20 | Snackbar( 21 | modifier = Modifier.padding(12.dp), 22 | action = { 23 | IconButton( 24 | onClick = { data.performAction() } 25 | ) { 26 | Icon( 27 | imageVector = Lucide.X, 28 | contentDescription = "Ok", 29 | tint = DevkitWalletColors.white 30 | ) 31 | } 32 | }, 33 | containerColor = DevkitWalletColors.primaryLight, 34 | ) { 35 | Text( 36 | text = data.visuals.message, 37 | fontFamily = quattroRegular, 38 | fontSize = 14.sp 39 | ) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/org/bitcoindevkit/devkitwallet/presentation/ui/components/LoadingAnimation.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 thunderbiscuit and contributors. 3 | * Use of this source code is governed by the Apache 2.0 license that can be found in the ./LICENSE file. 4 | */ 5 | 6 | package org.bitcoindevkit.devkitwallet.presentation.ui.components 7 | 8 | import androidx.compose.animation.core.Animatable 9 | import androidx.compose.animation.core.RepeatMode 10 | import androidx.compose.animation.core.infiniteRepeatable 11 | import androidx.compose.animation.core.tween 12 | import androidx.compose.foundation.background 13 | import androidx.compose.foundation.layout.Box 14 | import androidx.compose.foundation.layout.Row 15 | import androidx.compose.foundation.layout.Spacer 16 | import androidx.compose.foundation.layout.size 17 | import androidx.compose.foundation.layout.width 18 | import androidx.compose.foundation.shape.CircleShape 19 | import androidx.compose.runtime.Composable 20 | import androidx.compose.runtime.LaunchedEffect 21 | import androidx.compose.runtime.remember 22 | import androidx.compose.ui.Modifier 23 | import androidx.compose.ui.draw.clip 24 | import androidx.compose.ui.graphics.Color 25 | import androidx.compose.ui.unit.Dp 26 | import androidx.compose.ui.unit.dp 27 | import kotlinx.coroutines.delay 28 | 29 | @Composable 30 | fun LoadingAnimation( 31 | circleColor: Color = Color(0xffE9C46A), 32 | circleSize: Dp = 21.dp, 33 | animationDelay: Int = 800, 34 | initialAlpha: Float = 0.3f, 35 | ) { 36 | val circles = listOf( 37 | remember { Animatable(initialValue = initialAlpha) }, 38 | remember { Animatable(initialValue = initialAlpha) }, 39 | remember { Animatable(initialValue = initialAlpha) } 40 | ) 41 | 42 | circles.forEachIndexed { index, animatable -> 43 | LaunchedEffect(Unit) { 44 | // Use coroutine delay to sync animations 45 | delay(timeMillis = (animationDelay / circles.size).toLong() * index) 46 | 47 | animatable.animateTo( 48 | targetValue = 1f, 49 | animationSpec = infiniteRepeatable( 50 | animation = tween( 51 | durationMillis = animationDelay 52 | ), 53 | repeatMode = RepeatMode.Reverse 54 | ) 55 | ) 56 | } 57 | } 58 | 59 | // container for circles 60 | Row { 61 | circles.forEachIndexed { index, animatable -> 62 | // gap between the circles 63 | if (index != 0) Spacer(modifier = Modifier.width(width = 6.dp)) 64 | 65 | Box( 66 | modifier = Modifier 67 | .size(size = circleSize) 68 | .clip(shape = CircleShape) 69 | .background(circleColor.copy(alpha = animatable.value)) 70 | ) 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /app/src/main/java/org/bitcoindevkit/devkitwallet/presentation/ui/components/NeutralButton.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 thunderbiscuit and contributors. 3 | * Use of this source code is governed by the Apache 2.0 license that can be found in the ./LICENSE file. 4 | */ 5 | 6 | package org.bitcoindevkit.devkitwallet.presentation.ui.components 7 | 8 | import androidx.compose.foundation.layout.fillMaxWidth 9 | import androidx.compose.foundation.layout.height 10 | import androidx.compose.foundation.layout.padding 11 | import androidx.compose.foundation.shape.RoundedCornerShape 12 | import androidx.compose.material3.Button 13 | import androidx.compose.material3.ButtonDefaults 14 | import androidx.compose.material3.Text 15 | import androidx.compose.runtime.Composable 16 | import androidx.compose.ui.Modifier 17 | import androidx.compose.ui.draw.shadow 18 | import androidx.compose.ui.unit.dp 19 | import org.bitcoindevkit.devkitwallet.presentation.theme.DevkitWalletColors 20 | 21 | @Composable 22 | fun NeutralButton(text: String, enabled: Boolean = true, modifier: Modifier? = null, onClick: () -> Unit) { 23 | Button( 24 | onClick = onClick, 25 | colors = 26 | ButtonDefaults.buttonColors( 27 | containerColor = DevkitWalletColors.secondary, 28 | disabledContainerColor = DevkitWalletColors.secondary, 29 | ), 30 | shape = RoundedCornerShape(16.dp), 31 | enabled = enabled, 32 | modifier = modifier ?: Modifier 33 | .height(80.dp) 34 | .fillMaxWidth(0.9f) 35 | .padding(vertical = 8.dp, horizontal = 8.dp) 36 | .shadow(elevation = 4.dp, shape = RoundedCornerShape(16.dp)) 37 | ) { 38 | Text( 39 | text = text, 40 | ) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/org/bitcoindevkit/devkitwallet/presentation/ui/components/RadioButtonWithLabel.kt: -------------------------------------------------------------------------------- 1 | package org.bitcoindevkit.devkitwallet.presentation.ui.components 2 | 3 | import androidx.compose.foundation.clickable 4 | import androidx.compose.foundation.layout.Arrangement 5 | import androidx.compose.foundation.layout.Row 6 | import androidx.compose.foundation.layout.padding 7 | import androidx.compose.foundation.layout.size 8 | import androidx.compose.foundation.selection.selectable 9 | import androidx.compose.material3.RadioButton 10 | import androidx.compose.material3.RadioButtonDefaults 11 | import androidx.compose.material3.Text 12 | import androidx.compose.runtime.Composable 13 | import androidx.compose.ui.Alignment 14 | import androidx.compose.ui.Modifier 15 | import androidx.compose.ui.unit.dp 16 | import androidx.compose.ui.unit.sp 17 | import org.bitcoindevkit.devkitwallet.presentation.theme.DevkitWalletColors 18 | import org.bitcoindevkit.devkitwallet.presentation.theme.monoRegular 19 | 20 | @Composable 21 | fun RadioButtonWithLabel(label: String, isSelected: Boolean, onSelect: () -> Unit) { 22 | Row( 23 | verticalAlignment = Alignment.CenterVertically, 24 | horizontalArrangement = Arrangement.spacedBy(4.dp), 25 | modifier = Modifier 26 | .padding(0.dp) 27 | .selectable( 28 | selected = isSelected, 29 | onClick = onSelect 30 | ) 31 | ) { 32 | RadioButton( 33 | selected = isSelected, 34 | onClick = onSelect, 35 | colors = RadioButtonDefaults.colors( 36 | selectedColor = DevkitWalletColors.accent1, 37 | unselectedColor = DevkitWalletColors.accent2 38 | ), 39 | modifier = Modifier 40 | .padding(start = 8.dp) 41 | .size(40.dp) 42 | ) 43 | Text( 44 | text = label, 45 | color = DevkitWalletColors.white, 46 | fontFamily = monoRegular, 47 | fontSize = 12.sp, 48 | modifier = Modifier 49 | .clickable(onClick = onSelect) 50 | .padding(0.dp) 51 | ) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /app/src/main/java/org/bitcoindevkit/devkitwallet/presentation/ui/components/SecondaryScreensAppBar.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 thunderbiscuit and contributors. 3 | * Use of this source code is governed by the Apache 2.0 license that can be found in the ./LICENSE file. 4 | */ 5 | 6 | package org.bitcoindevkit.devkitwallet.presentation.ui.components 7 | 8 | import androidx.compose.material.icons.Icons 9 | import androidx.compose.material.icons.automirrored.rounded.ArrowBack 10 | import androidx.compose.material3.ExperimentalMaterial3Api 11 | import androidx.compose.material3.Icon 12 | import androidx.compose.material3.IconButton 13 | import androidx.compose.material3.Text 14 | import androidx.compose.material3.TopAppBar 15 | import androidx.compose.material3.TopAppBarDefaults 16 | import androidx.compose.runtime.Composable 17 | import androidx.compose.ui.unit.sp 18 | import org.bitcoindevkit.devkitwallet.presentation.theme.DevkitWalletColors 19 | import org.bitcoindevkit.devkitwallet.presentation.theme.quattroRegular 20 | 21 | @OptIn(ExperimentalMaterial3Api::class) 22 | @Composable 23 | internal fun SecondaryScreensAppBar(title: String, navigation: () -> Unit) { 24 | TopAppBar( 25 | title = { 26 | Text( 27 | text = title, 28 | color = DevkitWalletColors.white, 29 | fontSize = 18.sp, 30 | fontFamily = quattroRegular 31 | ) 32 | }, 33 | navigationIcon = { 34 | IconButton(onClick = navigation) { 35 | Icon( 36 | imageVector = Icons.AutoMirrored.Rounded.ArrowBack, 37 | contentDescription = "Back", 38 | tint = DevkitWalletColors.white 39 | ) 40 | } 41 | }, 42 | colors = 43 | TopAppBarDefaults.topAppBarColors( 44 | containerColor = DevkitWalletColors.primaryDark, 45 | ) 46 | ) 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/java/org/bitcoindevkit/devkitwallet/presentation/ui/components/TransactionCards.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 thunderbiscuit and contributors. 3 | * Use of this source code is governed by the Apache 2.0 license that can be found in the ./LICENSE file. 4 | */ 5 | 6 | package org.bitcoindevkit.devkitwallet.presentation.ui.components 7 | 8 | import android.util.Log 9 | import androidx.compose.foundation.background 10 | import androidx.compose.foundation.border 11 | import androidx.compose.foundation.clickable 12 | import androidx.compose.foundation.layout.Arrangement 13 | import androidx.compose.foundation.layout.Box 14 | import androidx.compose.foundation.layout.Row 15 | import androidx.compose.foundation.layout.fillMaxWidth 16 | import androidx.compose.foundation.layout.padding 17 | import androidx.compose.foundation.layout.size 18 | import androidx.compose.foundation.shape.CircleShape 19 | import androidx.compose.foundation.shape.RoundedCornerShape 20 | import androidx.compose.material3.Text 21 | import androidx.compose.runtime.Composable 22 | import androidx.compose.ui.Alignment 23 | import androidx.compose.ui.Modifier 24 | import androidx.compose.ui.draw.clip 25 | import androidx.compose.ui.graphics.Color 26 | import androidx.compose.ui.unit.dp 27 | import androidx.compose.ui.unit.sp 28 | import androidx.navigation.NavController 29 | import org.bitcoindevkit.devkitwallet.data.TxDetails 30 | import org.bitcoindevkit.devkitwallet.domain.utils.timestampToString 31 | import org.bitcoindevkit.devkitwallet.presentation.theme.DevkitWalletColors 32 | import org.bitcoindevkit.devkitwallet.presentation.theme.monoRegular 33 | import org.bitcoindevkit.devkitwallet.presentation.ui.screens.wallet.viewTransaction 34 | 35 | private const val TAG = "TransactionCards" 36 | 37 | @Composable 38 | fun ConfirmedTransactionCard(details: TxDetails, navController: NavController) { 39 | Row( 40 | Modifier 41 | .padding(horizontal = 8.dp, vertical = 6.dp) 42 | .fillMaxWidth() 43 | .background( 44 | color = DevkitWalletColors.primaryLight, 45 | shape = RoundedCornerShape(16.dp) 46 | ).clickable { viewTransaction(navController = navController, txid = details.txid) }, 47 | verticalAlignment = Alignment.CenterVertically, 48 | horizontalArrangement = Arrangement.Absolute.SpaceBetween 49 | ) { 50 | Text( 51 | confirmedTransactionsItem(details), 52 | fontFamily = monoRegular, 53 | fontSize = 12.sp, 54 | lineHeight = 20.sp, 55 | color = DevkitWalletColors.white, 56 | modifier = Modifier.padding(16.dp) 57 | ) 58 | Box( 59 | modifier = Modifier 60 | .padding(top = 16.dp, end = 16.dp) 61 | .size(size = 24.dp) 62 | .clip(shape = CircleShape) 63 | .background(DevkitWalletColors.secondary) 64 | .align(Alignment.Top) 65 | ) 66 | } 67 | } 68 | 69 | @Composable 70 | fun PendingTransactionCard(details: TxDetails, navController: NavController) { 71 | Row( 72 | Modifier 73 | .padding(horizontal = 8.dp, vertical = 6.dp) 74 | .fillMaxWidth() 75 | .background( 76 | color = DevkitWalletColors.primaryLight, 77 | shape = RoundedCornerShape(16.dp) 78 | ).border( 79 | width = 2.dp, 80 | color = DevkitWalletColors.accent1, 81 | shape = RoundedCornerShape(16.dp) 82 | ).clickable { 83 | // viewTransaction( 84 | // navController = navController, 85 | // txid = details.txid 86 | // ) 87 | }, 88 | verticalAlignment = Alignment.CenterVertically, 89 | horizontalArrangement = Arrangement.Absolute.SpaceBetween 90 | ) { 91 | Text( 92 | pendingTransactionsItem(details), 93 | fontFamily = monoRegular, 94 | fontSize = 12.sp, 95 | color = DevkitWalletColors.white, 96 | modifier = Modifier.padding(16.dp) 97 | ) 98 | Box( 99 | modifier = Modifier 100 | .padding(top = 16.dp, end = 16.dp) 101 | .size(size = 24.dp) 102 | .clip(shape = CircleShape) 103 | .background(Color(0xffE9C46A)) 104 | .align(Alignment.Top) 105 | ) 106 | } 107 | } 108 | 109 | fun pendingTransactionsItem(txDetails: TxDetails): String { 110 | return buildString { 111 | Log.i(TAG, "Pending transaction list item: $txDetails") 112 | 113 | appendLine("Confirmation time: Pending") 114 | appendLine("Received: ${txDetails.received}") 115 | appendLine("Sent: ${txDetails.sent}") 116 | appendLine("Total fee: ${txDetails.fee} sat") 117 | appendLine("Fee rate: ${txDetails.feeRate?.toSatPerVbCeil() ?: 0} sat/vbyte") 118 | append("Txid: ${txDetails.txid.take(n = 8)}...${txDetails.txid.takeLast(n = 8)}") 119 | } 120 | } 121 | 122 | fun confirmedTransactionsItem(txDetails: TxDetails): String { 123 | return buildString { 124 | Log.i(TAG, "Transaction list item: $txDetails") 125 | 126 | appendLine("Confirmation time: ${txDetails.confirmationTimestamp?.timestamp?.timestampToString()}") 127 | appendLine("Received: ${txDetails.received} sat") 128 | appendLine("Sent: ${txDetails.sent} sat") 129 | appendLine("Total fee: ${txDetails.fee} sat") 130 | appendLine("Fee rate: ${txDetails.feeRate?.toSatPerVbCeil() ?: 0} sat/vbyte") 131 | appendLine("Block: ${txDetails.confirmationBlock?.height}") 132 | append("Txid: ${txDetails.txid.take(n = 8)}...${txDetails.txid.takeLast(n = 8)}") 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /app/src/main/java/org/bitcoindevkit/devkitwallet/presentation/ui/components/WalletOptionsCard.kt: -------------------------------------------------------------------------------- 1 | package org.bitcoindevkit.devkitwallet.presentation.ui.components 2 | 3 | import androidx.compose.foundation.background 4 | import androidx.compose.foundation.layout.Arrangement 5 | import androidx.compose.foundation.layout.Column 6 | import androidx.compose.foundation.layout.Spacer 7 | import androidx.compose.foundation.layout.fillMaxWidth 8 | import androidx.compose.foundation.layout.padding 9 | import androidx.compose.foundation.shape.RoundedCornerShape 10 | import androidx.compose.material3.HorizontalDivider 11 | import androidx.compose.material3.Text 12 | import androidx.compose.runtime.Composable 13 | import androidx.compose.runtime.MutableState 14 | import androidx.compose.ui.Alignment 15 | import androidx.compose.ui.Modifier 16 | import androidx.compose.ui.unit.dp 17 | import androidx.compose.ui.unit.sp 18 | import org.bitcoindevkit.Network 19 | import org.bitcoindevkit.devkitwallet.data.ActiveWalletScriptType 20 | import org.bitcoindevkit.devkitwallet.domain.supportedNetworks 21 | import org.bitcoindevkit.devkitwallet.presentation.theme.DevkitWalletColors 22 | import org.bitcoindevkit.devkitwallet.presentation.theme.monoRegular 23 | import org.bitcoindevkit.devkitwallet.presentation.ui.screens.intro.displayString 24 | 25 | @Composable 26 | fun WalletOptionsCard( 27 | scriptTypes: List, 28 | selectedNetwork: MutableState, 29 | selectedScriptType: MutableState, 30 | ) { 31 | Column( 32 | Modifier 33 | .fillMaxWidth() 34 | .background( 35 | color = DevkitWalletColors.primaryLight, 36 | shape = RoundedCornerShape(16.dp) 37 | ), 38 | verticalArrangement = Arrangement.Center, 39 | horizontalAlignment = Alignment.Start 40 | ) { 41 | Text( 42 | text = "Network", 43 | fontFamily = monoRegular, 44 | fontSize = 14.sp, 45 | color = DevkitWalletColors.white, 46 | modifier = Modifier 47 | .align(Alignment.CenterHorizontally) 48 | .padding(top = 8.dp, bottom = 8.dp) 49 | ) 50 | 51 | HorizontalDivider( 52 | color = DevkitWalletColors.primaryDark, 53 | thickness = 4.dp, 54 | modifier = Modifier.padding(bottom = 8.dp) 55 | ) 56 | 57 | supportedNetworks.forEachIndexed { index, it -> 58 | RadioButtonWithLabel( 59 | label = it.displayString(), 60 | isSelected = selectedNetwork.value == it, 61 | onSelect = { selectedNetwork.value = it } 62 | ) 63 | if (index == 2) Spacer(modifier = Modifier.padding(bottom = 8.dp)) 64 | } 65 | 66 | Text( 67 | text = "Script Type", 68 | fontFamily = monoRegular, 69 | fontSize = 14.sp, 70 | color = DevkitWalletColors.white, 71 | modifier = Modifier 72 | .align(Alignment.CenterHorizontally) 73 | .padding(top = 16.dp, bottom = 8.dp) 74 | ) 75 | 76 | HorizontalDivider( 77 | color = DevkitWalletColors.primaryDark, 78 | thickness = 4.dp, 79 | modifier = Modifier.padding(bottom = 8.dp) 80 | ) 81 | 82 | scriptTypes.forEachIndexed { index, it -> 83 | RadioButtonWithLabel( 84 | label = it.displayString(), 85 | isSelected = selectedScriptType.value == it, 86 | onSelect = { selectedScriptType.value = it } 87 | ) 88 | if (index == 1) Spacer(modifier = Modifier.padding(bottom = 8.dp)) 89 | } 90 | } 91 | } 92 | 93 | @Composable 94 | fun NetworkOptionsCard(selectedNetwork: MutableState) { 95 | Column( 96 | Modifier 97 | .fillMaxWidth() 98 | .background( 99 | color = DevkitWalletColors.primaryLight, 100 | shape = RoundedCornerShape(16.dp) 101 | ), 102 | verticalArrangement = Arrangement.Center, 103 | horizontalAlignment = Alignment.Start 104 | ) { 105 | Text( 106 | text = "Network", 107 | fontFamily = monoRegular, 108 | fontSize = 14.sp, 109 | color = DevkitWalletColors.white, 110 | modifier = Modifier 111 | .align(Alignment.CenterHorizontally) 112 | .padding(top = 8.dp, bottom = 8.dp) 113 | ) 114 | 115 | HorizontalDivider( 116 | color = DevkitWalletColors.primaryDark, 117 | thickness = 4.dp, 118 | modifier = Modifier.padding(bottom = 8.dp) 119 | ) 120 | 121 | supportedNetworks.forEachIndexed { index, it -> 122 | RadioButtonWithLabel( 123 | label = it.displayString(), 124 | isSelected = selectedNetwork.value == it, 125 | onSelect = { selectedNetwork.value = it } 126 | ) 127 | if (index == 2) Spacer(modifier = Modifier.padding(bottom = 8.dp)) 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /app/src/main/java/org/bitcoindevkit/devkitwallet/presentation/ui/screens/WalletRoot.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 thunderbiscuit and contributors. 3 | * Use of this source code is governed by the Apache 2.0 license that can be found in the ./LICENSE file. 4 | */ 5 | 6 | package org.bitcoindevkit.devkitwallet.presentation.ui.screens 7 | 8 | import androidx.compose.foundation.Image 9 | import androidx.compose.foundation.background 10 | import androidx.compose.foundation.layout.Arrangement 11 | import androidx.compose.foundation.layout.Column 12 | import androidx.compose.foundation.layout.Spacer 13 | import androidx.compose.foundation.layout.fillMaxHeight 14 | import androidx.compose.foundation.layout.fillMaxWidth 15 | import androidx.compose.foundation.layout.height 16 | import androidx.compose.foundation.layout.padding 17 | import androidx.compose.foundation.layout.size 18 | import androidx.compose.material.icons.Icons 19 | import androidx.compose.material.icons.filled.Email 20 | import androidx.compose.material.icons.filled.Face 21 | import androidx.compose.material.icons.filled.Favorite 22 | import androidx.compose.material3.DrawerValue 23 | import androidx.compose.material3.Icon 24 | import androidx.compose.material3.ModalDrawerSheet 25 | import androidx.compose.material3.ModalNavigationDrawer 26 | import androidx.compose.material3.NavigationDrawerItem 27 | import androidx.compose.material3.NavigationDrawerItemDefaults 28 | import androidx.compose.material3.NavigationDrawerItemDefaults.colors 29 | import androidx.compose.material3.Text 30 | import androidx.compose.material3.rememberDrawerState 31 | import androidx.compose.runtime.Composable 32 | import androidx.compose.runtime.mutableStateOf 33 | import androidx.compose.runtime.remember 34 | import androidx.compose.ui.Alignment 35 | import androidx.compose.ui.Modifier 36 | import androidx.compose.ui.res.painterResource 37 | import androidx.compose.ui.unit.dp 38 | import androidx.compose.ui.unit.sp 39 | import androidx.navigation.NavController 40 | import com.composables.icons.lucide.History 41 | import com.composables.icons.lucide.Info 42 | import com.composables.icons.lucide.Lucide 43 | import com.composables.icons.lucide.SatelliteDish 44 | import com.composables.icons.lucide.ScrollText 45 | import org.bitcoindevkit.devkitwallet.BuildConfig 46 | import org.bitcoindevkit.devkitwallet.R 47 | import org.bitcoindevkit.devkitwallet.domain.Wallet 48 | import org.bitcoindevkit.devkitwallet.presentation.navigation.AboutScreen 49 | import org.bitcoindevkit.devkitwallet.presentation.navigation.BlockchainClientScreen 50 | import org.bitcoindevkit.devkitwallet.presentation.navigation.LogsScreen 51 | import org.bitcoindevkit.devkitwallet.presentation.navigation.RecoveryPhraseScreen 52 | import org.bitcoindevkit.devkitwallet.presentation.navigation.WalletNavigation 53 | import org.bitcoindevkit.devkitwallet.presentation.theme.DevkitWalletColors 54 | import org.bitcoindevkit.devkitwallet.presentation.theme.quattroRegular 55 | import org.bitcoindevkit.devkitwallet.presentation.theme.standardText 56 | import org.bitcoindevkit.devkitwallet.presentation.viewmodels.WalletViewModel 57 | 58 | @OptIn(androidx.compose.animation.ExperimentalAnimationApi::class) 59 | @Composable 60 | internal fun WalletRoot(navController: NavController, activeWallet: Wallet, walletViewModel: WalletViewModel) { 61 | val drawerState = rememberDrawerState(DrawerValue.Closed) 62 | 63 | val items = listOf(Icons.Default.Favorite, Icons.Default.Face, Icons.Default.Email, Icons.Default.Face) 64 | val selectedItem = remember { mutableStateOf(items[0]) } 65 | 66 | val navigationItemColors = 67 | colors( 68 | selectedContainerColor = DevkitWalletColors.primary, 69 | unselectedContainerColor = DevkitWalletColors.primary, 70 | selectedTextColor = DevkitWalletColors.white, 71 | unselectedTextColor = DevkitWalletColors.white 72 | ) 73 | 74 | ModalNavigationDrawer( 75 | drawerState = drawerState, 76 | drawerContent = { 77 | ModalDrawerSheet( 78 | drawerContainerColor = DevkitWalletColors.primary 79 | ) { 80 | Column( 81 | Modifier 82 | .background(color = DevkitWalletColors.secondary) 83 | .height(300.dp) 84 | .fillMaxHeight() 85 | .fillMaxWidth(), 86 | horizontalAlignment = Alignment.CenterHorizontally, 87 | verticalArrangement = Arrangement.Center 88 | ) { 89 | Image( 90 | painter = painterResource(id = R.drawable.ic_testnet_logo), 91 | contentDescription = "Bitcoin testnet logo", 92 | modifier = Modifier 93 | .size(90.dp) 94 | .padding(bottom = 16.dp) 95 | ) 96 | Text( 97 | text = "Devkit Wallet", 98 | color = DevkitWalletColors.white, 99 | fontFamily = quattroRegular, 100 | ) 101 | Spacer(modifier = Modifier.padding(4.dp)) 102 | Text( 103 | text = "The sample wallet on Android for BDK.", 104 | color = DevkitWalletColors.white, 105 | fontFamily = quattroRegular, 106 | fontSize = 12.sp, 107 | fontStyle = androidx.compose.ui.text.font.FontStyle.Italic 108 | ) 109 | Spacer(modifier = Modifier.padding(16.dp)) 110 | Text( 111 | text = BuildConfig.VARIANT_NAME, 112 | style = standardText 113 | ) 114 | } 115 | Column( 116 | Modifier 117 | .fillMaxHeight() 118 | .background(color = DevkitWalletColors.primary) 119 | ) { 120 | Spacer(modifier = Modifier.height(16.dp)) 121 | NavigationDrawerItem( 122 | icon = { Icon(Lucide.Info, contentDescription = "About", tint = DevkitWalletColors.white) }, 123 | label = { DrawerItemLabel("About") }, 124 | selected = items[0] == selectedItem.value, 125 | onClick = { navController.navigate(AboutScreen) }, 126 | colors = navigationItemColors, 127 | modifier = Modifier.padding(NavigationDrawerItemDefaults.ItemPadding), 128 | ) 129 | NavigationDrawerItem( 130 | icon = { 131 | Icon( 132 | Lucide.History, 133 | contentDescription = "Wallet Recovery Data", 134 | tint = DevkitWalletColors.white 135 | ) 136 | }, 137 | label = { DrawerItemLabel("Wallet Recovery Data") }, 138 | selected = items[1] == selectedItem.value, 139 | onClick = { navController.navigate(RecoveryPhraseScreen) }, 140 | colors = navigationItemColors, 141 | modifier = Modifier.padding(NavigationDrawerItemDefaults.ItemPadding), 142 | ) 143 | NavigationDrawerItem( 144 | icon = { 145 | Icon( 146 | Lucide.SatelliteDish, 147 | contentDescription = "Esplora Client", 148 | tint = DevkitWalletColors.white 149 | ) 150 | }, 151 | label = { DrawerItemLabel("Esplora Client") }, 152 | selected = items[2] == selectedItem.value, 153 | onClick = { navController.navigate(BlockchainClientScreen) }, 154 | colors = navigationItemColors, 155 | modifier = Modifier.padding(NavigationDrawerItemDefaults.ItemPadding), 156 | ) 157 | NavigationDrawerItem( 158 | icon = { 159 | Icon( 160 | Lucide.ScrollText, 161 | contentDescription = "Logs", 162 | tint = DevkitWalletColors.white 163 | ) 164 | }, 165 | label = { DrawerItemLabel("Logs") }, 166 | selected = items[3] == selectedItem.value, 167 | onClick = { navController.navigate(LogsScreen) }, 168 | colors = navigationItemColors, 169 | modifier = Modifier.padding(NavigationDrawerItemDefaults.ItemPadding), 170 | ) 171 | } 172 | } 173 | }, 174 | content = { 175 | WalletNavigation( 176 | drawerState = drawerState, 177 | activeWallet = activeWallet, 178 | walletViewModel = walletViewModel 179 | ) 180 | } 181 | ) 182 | } 183 | 184 | @Composable 185 | fun DrawerItemLabel(text: String) { 186 | Text( 187 | text = text, 188 | fontFamily = quattroRegular, 189 | ) 190 | } 191 | -------------------------------------------------------------------------------- /app/src/main/java/org/bitcoindevkit/devkitwallet/presentation/ui/screens/drawer/AboutScreen.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 thunderbiscuit and contributors. 3 | * Use of this source code is governed by the Apache 2.0 license that can be found in the ./LICENSE file. 4 | */ 5 | 6 | package org.bitcoindevkit.devkitwallet.presentation.ui.screens.drawer 7 | 8 | import androidx.compose.foundation.Image 9 | import androidx.compose.foundation.layout.Arrangement 10 | import androidx.compose.foundation.layout.Column 11 | import androidx.compose.foundation.layout.Spacer 12 | import androidx.compose.foundation.layout.fillMaxSize 13 | import androidx.compose.foundation.layout.padding 14 | import androidx.compose.foundation.layout.size 15 | import androidx.compose.material3.Scaffold 16 | import androidx.compose.material3.Text 17 | import androidx.compose.runtime.Composable 18 | import androidx.compose.ui.Alignment 19 | import androidx.compose.ui.Modifier 20 | import androidx.compose.ui.res.painterResource 21 | import androidx.compose.ui.text.style.TextAlign 22 | import androidx.compose.ui.tooling.preview.Devices 23 | import androidx.compose.ui.tooling.preview.Preview 24 | import androidx.compose.ui.unit.dp 25 | import androidx.navigation.NavController 26 | import androidx.navigation.compose.rememberNavController 27 | import org.bitcoindevkit.devkitwallet.R 28 | import org.bitcoindevkit.devkitwallet.presentation.navigation.WalletScreen 29 | import org.bitcoindevkit.devkitwallet.presentation.theme.DevkitWalletColors 30 | import org.bitcoindevkit.devkitwallet.presentation.theme.quattroRegular 31 | import org.bitcoindevkit.devkitwallet.presentation.ui.components.SecondaryScreensAppBar 32 | 33 | private val message: String = """ 34 | This wallet is build for: 35 | 36 | 1. Developers interested in learning how to leverage the Bitcoin Development Kit on Android. 37 | 38 | 2. Any bitcoiner looking for a Signet/Testnet/Regtest wallet! 39 | """.trimIndent() 40 | 41 | @Composable 42 | internal fun AboutScreen(navController: NavController) { 43 | Scaffold( 44 | topBar = { 45 | SecondaryScreensAppBar( 46 | title = "About", 47 | navigation = { navController.navigate(WalletScreen) } 48 | ) 49 | }, 50 | containerColor = DevkitWalletColors.primary 51 | ) { paddingValues -> 52 | Column( 53 | modifier = Modifier 54 | .fillMaxSize() 55 | .padding(paddingValues) 56 | .padding(16.dp), 57 | verticalArrangement = Arrangement.Top, 58 | horizontalAlignment = Alignment.CenterHorizontally, 59 | ) { 60 | Spacer(modifier = Modifier.padding(24.dp)) 61 | Image( 62 | painter = painterResource(id = R.drawable.bdk_logo), 63 | contentDescription = "BDK logo", 64 | Modifier.size(180.dp) 65 | ) 66 | Spacer(modifier = Modifier.padding(24.dp)) 67 | Text( 68 | text = message, 69 | color = DevkitWalletColors.white, 70 | textAlign = TextAlign.Start, 71 | fontFamily = quattroRegular, 72 | modifier = Modifier.padding(all = 8.dp) 73 | ) 74 | } 75 | } 76 | } 77 | 78 | @Preview(device = Devices.PIXEL_4, showBackground = true) 79 | @Composable 80 | internal fun PreviewAboutScreen() { 81 | AboutScreen(rememberNavController()) 82 | } 83 | -------------------------------------------------------------------------------- /app/src/main/java/org/bitcoindevkit/devkitwallet/presentation/ui/screens/drawer/BlockchainClientScreen.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 thunderbiscuit and contributors. 3 | * Use of this source code is governed by the Apache 2.0 license that can be found in the ./LICENSE file. 4 | */ 5 | 6 | package org.bitcoindevkit.devkitwallet.presentation.ui.screens.drawer 7 | 8 | import androidx.compose.foundation.layout.Column 9 | import androidx.compose.foundation.layout.fillMaxSize 10 | import androidx.compose.foundation.layout.padding 11 | import androidx.compose.material3.Scaffold 12 | import androidx.compose.material3.Text 13 | import androidx.compose.runtime.Composable 14 | import androidx.compose.runtime.MutableState 15 | import androidx.compose.runtime.mutableStateOf 16 | import androidx.compose.runtime.remember 17 | import androidx.compose.ui.Modifier 18 | import androidx.compose.ui.platform.LocalFocusManager 19 | import androidx.compose.ui.unit.dp 20 | import androidx.compose.ui.unit.sp 21 | import androidx.navigation.NavController 22 | import org.bitcoindevkit.devkitwallet.presentation.navigation.WalletScreen 23 | import org.bitcoindevkit.devkitwallet.presentation.theme.DevkitWalletColors 24 | import org.bitcoindevkit.devkitwallet.presentation.theme.quattroBold 25 | import org.bitcoindevkit.devkitwallet.presentation.theme.standardText 26 | import org.bitcoindevkit.devkitwallet.presentation.ui.components.SecondaryScreensAppBar 27 | import org.bitcoindevkit.devkitwallet.presentation.viewmodels.mvi.WalletScreenState 28 | 29 | @Composable 30 | internal fun BlockchainClientScreen(state: WalletScreenState, navController: NavController) { 31 | val focusManager = LocalFocusManager.current 32 | // val isBlockChainCreated = Wallet.isBlockChainCreated() 33 | val serverEndpoint: MutableState = remember { mutableStateOf("") } 34 | val isChecked: MutableState = remember { mutableStateOf(false) } 35 | // if (isBlockChainCreated) { 36 | // electrumServer.value = Wallet.getElectrumURL() 37 | // isChecked.value = Wallet.isElectrumServerDefault() 38 | // } 39 | 40 | Scaffold( 41 | topBar = { 42 | SecondaryScreensAppBar( 43 | title = "Esplora Client", 44 | navigation = { navController.navigate(WalletScreen) } 45 | ) 46 | }, 47 | containerColor = DevkitWalletColors.primary 48 | ) { paddingValues -> 49 | Column( 50 | modifier = Modifier 51 | .fillMaxSize() 52 | .padding(paddingValues) 53 | .padding(all = 16.dp), 54 | ) { 55 | Text( 56 | text = "Current Esplora client endpoint", 57 | color = DevkitWalletColors.white, 58 | fontSize = 18.sp, 59 | fontFamily = quattroBold, 60 | ) 61 | Text( 62 | text = state.esploraEndpoint, 63 | style = standardText, 64 | ) 65 | // Row(verticalAlignment = Alignment.CenterVertically) { 66 | // Text( 67 | // text = "Use default electrum URL", 68 | // color = DevkitWalletColors.white, 69 | // fontSize = 14.sp, 70 | // textAlign = TextAlign.Center, 71 | // ) 72 | // Switch( 73 | // checked = isChecked.value, 74 | // onCheckedChange = { 75 | // isChecked.value = it 76 | // if (it) { 77 | // Wallet.setElectrumSettings(ElectrumSettings.DEFAULT) 78 | // } else { 79 | // Wallet.setElectrumSettings(ElectrumSettings.CUSTOM) 80 | // } 81 | // }, 82 | // enabled = isBlockChainCreated 83 | // ) 84 | // } 85 | 86 | // OutlinedTextField( 87 | // value = electrumServer.value, 88 | // onValueChange = { electrumServer.value = it }, 89 | // label = { 90 | // Text( 91 | // text = "Electrum Server", 92 | // color = DevkitWalletColors.white, 93 | // ) 94 | // }, 95 | // singleLine = true, 96 | // textStyle = TextStyle(color = DevkitWalletColors.white), 97 | // colors = TextFieldDefaults.outlinedTextFieldColors( 98 | // focusedBorderColor = DevkitWalletColors.accent1, 99 | // unfocusedBorderColor = DevkitWalletColors.white, 100 | // cursorColor = DevkitWalletColors.accent1, 101 | // ), 102 | // keyboardActions = KeyboardActions(onDone = { 103 | // focusManager.clearFocus() 104 | // }), 105 | // modifier = Modifier.fillMaxWidth(), 106 | // enabled = isBlockChainCreated && !isChecked.value 107 | // ) 108 | 109 | // Button( 110 | // onClick = { 111 | // Wallet.changeElectrumServer(electrumServer.value) 112 | // focusManager.clearFocus() 113 | // }, 114 | // modifier = Modifier 115 | // .align(alignment = Alignment.End) 116 | // .padding(all = 8.dp), 117 | // colors = ButtonDefaults.buttonColors(DevkitWalletColors.secondary), 118 | // enabled = isBlockChainCreated && !isChecked.value 119 | // ) { 120 | // Text( 121 | // text = "Save", 122 | // color = DevkitWalletColors.white, 123 | // fontSize = 12.sp, 124 | // textAlign = TextAlign.Center, 125 | // ) 126 | // } 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /app/src/main/java/org/bitcoindevkit/devkitwallet/presentation/ui/screens/drawer/LogsScreen.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 thunderbiscuit and contributors. 3 | * Use of this source code is governed by the Apache 2.0 license that can be found in the ./LICENSE file. 4 | */ 5 | 6 | package org.bitcoindevkit.devkitwallet.presentation.ui.screens.drawer 7 | 8 | import androidx.compose.foundation.horizontalScroll 9 | import androidx.compose.foundation.layout.fillMaxSize 10 | import androidx.compose.foundation.layout.fillMaxWidth 11 | import androidx.compose.foundation.layout.padding 12 | import androidx.compose.foundation.lazy.LazyColumn 13 | import androidx.compose.foundation.lazy.items 14 | import androidx.compose.foundation.rememberScrollState 15 | import androidx.compose.material3.Scaffold 16 | import androidx.compose.material3.Text 17 | import androidx.compose.runtime.Composable 18 | import androidx.compose.runtime.remember 19 | import androidx.compose.ui.Modifier 20 | import androidx.compose.ui.text.style.TextOverflow 21 | import androidx.compose.ui.unit.dp 22 | import androidx.navigation.NavController 23 | import org.bitcoindevkit.devkitwallet.domain.DwLogger 24 | import org.bitcoindevkit.devkitwallet.presentation.navigation.WalletScreen 25 | import org.bitcoindevkit.devkitwallet.presentation.theme.DevkitWalletColors 26 | import org.bitcoindevkit.devkitwallet.presentation.theme.standardText 27 | import org.bitcoindevkit.devkitwallet.presentation.ui.components.SecondaryScreensAppBar 28 | 29 | @Composable 30 | fun LogsScreen(navController: NavController) { 31 | val logs: List = remember { DwLogger.getLogs() } 32 | 33 | Scaffold( 34 | topBar = { 35 | SecondaryScreensAppBar( 36 | title = "Logs", 37 | navigation = { navController.navigate(WalletScreen) } 38 | ) 39 | }, 40 | containerColor = DevkitWalletColors.primary 41 | ) { paddingValues -> 42 | LazyColumn( 43 | modifier = Modifier 44 | .fillMaxSize() 45 | .padding(paddingValues) 46 | .padding(16.dp) 47 | ) { 48 | items(logs) { logLine -> 49 | Text( 50 | text = logLine, 51 | style = standardText, 52 | maxLines = 1, 53 | overflow = TextOverflow.Visible, 54 | modifier = Modifier 55 | .fillMaxWidth() 56 | .horizontalScroll(rememberScrollState()) 57 | ) 58 | } 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /app/src/main/java/org/bitcoindevkit/devkitwallet/presentation/ui/screens/drawer/RecoveryDataScreen.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 thunderbiscuit and contributors. 3 | * Use of this source code is governed by the Apache 2.0 license that can be found in the ./LICENSE file. 4 | */ 5 | 6 | package org.bitcoindevkit.devkitwallet.presentation.ui.screens.drawer 7 | 8 | import android.content.ClipData 9 | import android.content.ClipboardManager 10 | import android.content.Context 11 | import androidx.compose.animation.Crossfade 12 | import androidx.compose.animation.core.tween 13 | import androidx.compose.foundation.background 14 | import androidx.compose.foundation.clickable 15 | import androidx.compose.foundation.layout.Arrangement 16 | import androidx.compose.foundation.layout.Box 17 | import androidx.compose.foundation.layout.Column 18 | import androidx.compose.foundation.layout.Spacer 19 | import androidx.compose.foundation.layout.fillMaxSize 20 | import androidx.compose.foundation.layout.padding 21 | import androidx.compose.foundation.layout.size 22 | import androidx.compose.foundation.shape.RoundedCornerShape 23 | import androidx.compose.foundation.text.selection.SelectionContainer 24 | import androidx.compose.material3.Icon 25 | import androidx.compose.material3.Scaffold 26 | import androidx.compose.material3.Text 27 | import androidx.compose.runtime.Composable 28 | import androidx.compose.runtime.mutableIntStateOf 29 | import androidx.compose.runtime.remember 30 | import androidx.compose.ui.Alignment 31 | import androidx.compose.ui.Modifier 32 | import androidx.compose.ui.graphics.Color 33 | import androidx.compose.ui.platform.LocalContext 34 | import androidx.compose.ui.unit.dp 35 | import androidx.navigation.NavController 36 | import com.composables.icons.lucide.ClipboardCopy 37 | import com.composables.icons.lucide.Lucide 38 | import org.bitcoindevkit.devkitwallet.domain.WalletSecrets 39 | import org.bitcoindevkit.devkitwallet.presentation.navigation.WalletScreen 40 | import org.bitcoindevkit.devkitwallet.presentation.theme.DevkitWalletColors 41 | import org.bitcoindevkit.devkitwallet.presentation.theme.monoRegular 42 | import org.bitcoindevkit.devkitwallet.presentation.theme.quattroRegular 43 | import org.bitcoindevkit.devkitwallet.presentation.ui.components.NeutralButton 44 | import org.bitcoindevkit.devkitwallet.presentation.ui.components.SecondaryScreensAppBar 45 | 46 | private val MESSAGE: String = """ 47 | The next screen will show your recovery phrase and descriptors. Make sure no one else is looking at your screen. 48 | """.trimIndent() 49 | 50 | @Composable 51 | internal fun RecoveryDataScreen(walletSecrets: WalletSecrets, navController: NavController) { 52 | val (currentIndex, setCurrentIndex) = remember { mutableIntStateOf(0) } 53 | 54 | Scaffold( 55 | topBar = { 56 | SecondaryScreensAppBar( 57 | title = "Your Wallet Recovery Data", 58 | navigation = { navController.navigate(WalletScreen) } 59 | ) 60 | }, 61 | containerColor = DevkitWalletColors.primary 62 | ) { paddingValues -> 63 | Crossfade( 64 | modifier = Modifier.padding(paddingValues), 65 | targetState = currentIndex, 66 | label = "", 67 | animationSpec = tween( 68 | durationMillis = 1000, 69 | delayMillis = 200, 70 | ) 71 | ) { screen -> 72 | when (screen) { 73 | 0 -> WarningText(setCurrentIndex = setCurrentIndex) 74 | 1 -> RecoveryPhrase(walletSecrets = walletSecrets) 75 | } 76 | } 77 | } 78 | } 79 | 80 | @Composable 81 | fun WarningText(setCurrentIndex: (Int) -> Unit) { 82 | Column( 83 | modifier = Modifier 84 | .fillMaxSize() 85 | .padding(horizontal = 32.dp, vertical = 16.dp), 86 | horizontalAlignment = Alignment.CenterHorizontally, 87 | verticalArrangement = Arrangement.Center 88 | ) { 89 | Text( 90 | text = MESSAGE, 91 | color = DevkitWalletColors.white, 92 | fontFamily = quattroRegular 93 | ) 94 | Spacer(modifier = Modifier.padding(16.dp)) 95 | NeutralButton( 96 | text = "See my recovery data", 97 | enabled = true 98 | ) { setCurrentIndex(1) } 99 | } 100 | } 101 | 102 | @Composable 103 | fun RecoveryPhrase(walletSecrets: WalletSecrets) { 104 | val context = LocalContext.current 105 | Column( 106 | modifier = Modifier 107 | .fillMaxSize() 108 | .padding(all = 32.dp) 109 | ) { 110 | Text( 111 | text = "Write down your recovery phrase and keep it in a safe place.", 112 | color = DevkitWalletColors.white, 113 | fontFamily = quattroRegular 114 | ) 115 | Spacer(modifier = Modifier.padding(8.dp)) 116 | Box { 117 | SelectionContainer { 118 | Text( 119 | modifier = Modifier 120 | .clickable { 121 | simpleCopyClipboard( 122 | walletSecrets.recoveryPhrase, 123 | context 124 | ) 125 | }.background( 126 | color = DevkitWalletColors.primaryLight, 127 | shape = RoundedCornerShape(16.dp) 128 | ).padding(12.dp), 129 | text = walletSecrets.recoveryPhrase, 130 | fontFamily = monoRegular, 131 | color = DevkitWalletColors.white 132 | ) 133 | } 134 | Icon( 135 | Lucide.ClipboardCopy, 136 | tint = Color.White, 137 | contentDescription = "Copy to clipboard", 138 | modifier = Modifier 139 | .padding(8.dp) 140 | .size(20.dp) 141 | .align(Alignment.BottomEnd) 142 | ) 143 | } 144 | Spacer(modifier = Modifier.padding(16.dp)) 145 | Text( 146 | text = "These are your descriptors.", 147 | color = DevkitWalletColors.white, 148 | fontFamily = quattroRegular 149 | ) 150 | Spacer(modifier = Modifier.padding(8.dp)) 151 | Box { 152 | SelectionContainer { 153 | Text( 154 | modifier = Modifier 155 | .clickable { 156 | simpleCopyClipboard( 157 | walletSecrets.descriptor.toStringWithSecret(), 158 | context 159 | ) 160 | }.background( 161 | color = DevkitWalletColors.primaryLight, 162 | shape = RoundedCornerShape(16.dp) 163 | ).padding(12.dp), 164 | text = walletSecrets.descriptor.toStringWithSecret(), 165 | fontFamily = monoRegular, 166 | color = DevkitWalletColors.white 167 | ) 168 | } 169 | Icon( 170 | Lucide.ClipboardCopy, 171 | tint = Color.White, 172 | contentDescription = "Copy to clipboard", 173 | modifier = Modifier 174 | .padding(8.dp) 175 | .size(20.dp) 176 | .align(Alignment.BottomEnd) 177 | ) 178 | } 179 | Spacer(modifier = Modifier.padding(4.dp)) 180 | Box { 181 | SelectionContainer { 182 | Text( 183 | modifier = Modifier 184 | .clickable { 185 | simpleCopyClipboard( 186 | walletSecrets.changeDescriptor.toStringWithSecret(), 187 | context 188 | ) 189 | }.background( 190 | color = DevkitWalletColors.primaryLight, 191 | shape = RoundedCornerShape(16.dp) 192 | ).padding(12.dp), 193 | text = walletSecrets.changeDescriptor.toStringWithSecret(), 194 | fontFamily = monoRegular, 195 | color = DevkitWalletColors.white 196 | ) 197 | } 198 | Icon( 199 | Lucide.ClipboardCopy, 200 | tint = Color.White, 201 | contentDescription = "Copy to clipboard", 202 | modifier = Modifier 203 | .padding(8.dp) 204 | .size(20.dp) 205 | .align(Alignment.BottomEnd) 206 | ) 207 | } 208 | } 209 | } 210 | 211 | fun simpleCopyClipboard(content: String, context: Context) { 212 | val clipboard: ClipboardManager = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager 213 | val clip: ClipData = ClipData.newPlainText("", content) 214 | clipboard.setPrimaryClip(clip) 215 | } 216 | 217 | // @Preview(device = Devices.PIXEL_4, showBackground = true) 218 | // @Composable 219 | // internal fun PreviewRecoveryPhraseScreen() { 220 | // RecoveryPhraseScreen() 221 | // } 222 | -------------------------------------------------------------------------------- /app/src/main/java/org/bitcoindevkit/devkitwallet/presentation/ui/screens/intro/ActiveWalletsScreen.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 thunderbiscuit and contributors. 3 | * Use of this source code is governed by the Apache 2.0 license that can be found in the ./LICENSE file. 4 | */ 5 | 6 | package org.bitcoindevkit.devkitwallet.presentation.ui.screens.intro 7 | 8 | import androidx.compose.foundation.background 9 | import androidx.compose.foundation.clickable 10 | import androidx.compose.foundation.layout.Arrangement 11 | import androidx.compose.foundation.layout.Column 12 | import androidx.compose.foundation.layout.Row 13 | import androidx.compose.foundation.layout.Spacer 14 | import androidx.compose.foundation.layout.fillMaxSize 15 | import androidx.compose.foundation.layout.fillMaxWidth 16 | import androidx.compose.foundation.layout.height 17 | import androidx.compose.foundation.layout.padding 18 | import androidx.compose.foundation.shape.RoundedCornerShape 19 | import androidx.compose.material3.Scaffold 20 | import androidx.compose.material3.Text 21 | import androidx.compose.runtime.Composable 22 | import androidx.compose.ui.Alignment 23 | import androidx.compose.ui.Modifier 24 | import androidx.compose.ui.unit.dp 25 | import androidx.compose.ui.unit.sp 26 | import androidx.navigation.NavController 27 | import org.bitcoindevkit.devkitwallet.data.SingleWallet 28 | import org.bitcoindevkit.devkitwallet.domain.DwLogger 29 | import org.bitcoindevkit.devkitwallet.domain.DwLogger.LogLevel.INFO 30 | import org.bitcoindevkit.devkitwallet.presentation.WalletCreateType 31 | import org.bitcoindevkit.devkitwallet.presentation.theme.DevkitWalletColors 32 | import org.bitcoindevkit.devkitwallet.presentation.theme.quattroRegular 33 | import org.bitcoindevkit.devkitwallet.presentation.theme.standardText 34 | import org.bitcoindevkit.devkitwallet.presentation.ui.components.SecondaryScreensAppBar 35 | 36 | private const val TAG = "ActiveWalletsScreen" 37 | 38 | @Composable 39 | internal fun ActiveWalletsScreen( 40 | activeWallets: List, 41 | navController: NavController, 42 | onBuildWalletButtonClicked: (WalletCreateType) -> Unit, 43 | ) { 44 | Scaffold( 45 | topBar = { 46 | SecondaryScreensAppBar(title = "Choose a Wallet", navigation = { navController.navigateUp() }) 47 | }, 48 | containerColor = DevkitWalletColors.primary 49 | ) { paddingValues -> 50 | Column( 51 | modifier = Modifier 52 | .fillMaxSize() 53 | .padding(paddingValues) 54 | ) { 55 | Spacer(modifier = Modifier.height(12.dp)) 56 | activeWallets.forEach { 57 | ActiveWalletCard(wallet = it, onBuildWalletButtonClicked) 58 | } 59 | if (activeWallets.isEmpty()) { 60 | Text( 61 | text = "No active wallets.", 62 | fontSize = 16.sp, 63 | fontFamily = quattroRegular, 64 | color = DevkitWalletColors.white, 65 | modifier = Modifier.padding(16.dp).align(Alignment.CenterHorizontally) 66 | ) 67 | } 68 | Spacer(modifier = Modifier.height(12.dp)) 69 | } 70 | } 71 | } 72 | 73 | @Composable 74 | fun ActiveWalletCard(wallet: SingleWallet, onBuildWalletButtonClicked: (WalletCreateType) -> Unit) { 75 | Column( 76 | Modifier 77 | .fillMaxWidth() 78 | // Padding outside the card 79 | .padding(horizontal = 16.dp, vertical = 8.dp) 80 | .background( 81 | color = DevkitWalletColors.primaryLight, 82 | shape = RoundedCornerShape(16.dp) 83 | ) 84 | // Padding inside the card 85 | .padding(horizontal = 4.dp, vertical = 8.dp) 86 | .clickable { 87 | DwLogger.log(INFO, "Activating existing wallet: ${wallet.name}") 88 | onBuildWalletButtonClicked(WalletCreateType.LOADEXISTING(wallet)) 89 | }, 90 | verticalArrangement = Arrangement.SpaceBetween, 91 | horizontalAlignment = Alignment.Start 92 | ) { 93 | DataField("Name", wallet.name) 94 | DataField("Network", wallet.network.toString()) 95 | DataField("Script Type", wallet.scriptType.toString()) 96 | } 97 | } 98 | 99 | @Composable 100 | fun DataField(name: String, value: String) { 101 | Row( 102 | verticalAlignment = Alignment.CenterVertically, 103 | horizontalArrangement = Arrangement.Absolute.SpaceBetween, 104 | modifier = Modifier 105 | .fillMaxWidth() 106 | .padding(horizontal = 16.dp, vertical = 4.dp) 107 | ) { 108 | Text( 109 | text = name, 110 | style = standardText, 111 | lineHeight = 18.sp, 112 | ) 113 | Text( 114 | text = value, 115 | style = standardText, 116 | lineHeight = 18.sp, 117 | ) 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /app/src/main/java/org/bitcoindevkit/devkitwallet/presentation/ui/screens/intro/CreateNewWallet.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 thunderbiscuit and contributors. 3 | * Use of this source code is governed by the Apache 2.0 license that can be found in the ./LICENSE file. 4 | */ 5 | 6 | package org.bitcoindevkit.devkitwallet.presentation.ui.screens.intro 7 | 8 | import androidx.compose.foundation.background 9 | import androidx.compose.foundation.layout.Column 10 | import androidx.compose.foundation.layout.Spacer 11 | import androidx.compose.foundation.layout.fillMaxSize 12 | import androidx.compose.foundation.layout.fillMaxWidth 13 | import androidx.compose.foundation.layout.height 14 | import androidx.compose.foundation.layout.padding 15 | import androidx.compose.foundation.shape.RoundedCornerShape 16 | import androidx.compose.material3.OutlinedTextField 17 | import androidx.compose.material3.OutlinedTextFieldDefaults 18 | import androidx.compose.material3.Scaffold 19 | import androidx.compose.material3.Text 20 | import androidx.compose.runtime.Composable 21 | import androidx.compose.runtime.MutableState 22 | import androidx.compose.runtime.mutableStateOf 23 | import androidx.compose.runtime.remember 24 | import androidx.compose.ui.Alignment 25 | import androidx.compose.ui.Modifier 26 | import androidx.compose.ui.draw.shadow 27 | import androidx.compose.ui.text.TextStyle 28 | import androidx.compose.ui.unit.dp 29 | import androidx.constraintlayout.compose.ConstraintLayout 30 | import androidx.navigation.NavController 31 | import org.bitcoindevkit.Network 32 | import org.bitcoindevkit.devkitwallet.data.ActiveWalletScriptType 33 | import org.bitcoindevkit.devkitwallet.data.NewWalletConfig 34 | import org.bitcoindevkit.devkitwallet.domain.DwLogger 35 | import org.bitcoindevkit.devkitwallet.domain.DwLogger.LogLevel.INFO 36 | import org.bitcoindevkit.devkitwallet.presentation.WalletCreateType 37 | import org.bitcoindevkit.devkitwallet.presentation.theme.DevkitWalletColors 38 | import org.bitcoindevkit.devkitwallet.presentation.theme.monoRegular 39 | import org.bitcoindevkit.devkitwallet.presentation.theme.standardText 40 | import org.bitcoindevkit.devkitwallet.presentation.ui.components.NeutralButton 41 | import org.bitcoindevkit.devkitwallet.presentation.ui.components.SecondaryScreensAppBar 42 | import org.bitcoindevkit.devkitwallet.presentation.ui.components.WalletOptionsCard 43 | 44 | @Composable 45 | internal fun CreateNewWalletScreen( 46 | navController: NavController, 47 | onBuildWalletButtonClicked: (WalletCreateType) -> Unit, 48 | ) { 49 | Scaffold( 50 | topBar = { 51 | SecondaryScreensAppBar(title = "Create a New Wallet", navigation = { navController.navigateUp() }) 52 | }, 53 | containerColor = DevkitWalletColors.primary 54 | ) { paddingValues -> 55 | 56 | ConstraintLayout( 57 | modifier = Modifier 58 | .padding(paddingValues) 59 | .fillMaxSize() 60 | .padding(vertical = 16.dp) 61 | ) { 62 | val (choices, button) = createRefs() 63 | 64 | val walletName: MutableState = remember { mutableStateOf("") } 65 | val selectedNetwork: MutableState = remember { mutableStateOf(Network.SIGNET) } 66 | val selectedScriptType: MutableState = 67 | remember { mutableStateOf(ActiveWalletScriptType.P2TR) } 68 | val scriptTypes = listOf(ActiveWalletScriptType.P2TR, ActiveWalletScriptType.P2WPKH) 69 | 70 | Column( 71 | modifier = Modifier 72 | .constrainAs(choices) { 73 | top.linkTo(parent.top) 74 | start.linkTo(parent.start) 75 | end.linkTo(parent.end) 76 | }.fillMaxSize() 77 | .background(color = DevkitWalletColors.primary) 78 | .padding(horizontal = 32.dp) 79 | ) { 80 | OutlinedTextField( 81 | modifier = Modifier 82 | .padding(bottom = 8.dp) 83 | .fillMaxWidth() 84 | .align(Alignment.CenterHorizontally), 85 | value = walletName.value, 86 | onValueChange = { walletName.value = it }, 87 | label = { 88 | Text( 89 | text = "Give your wallet a name", 90 | style = standardText, 91 | color = DevkitWalletColors.white, 92 | ) 93 | }, 94 | singleLine = true, 95 | textStyle = TextStyle(fontFamily = monoRegular, color = DevkitWalletColors.white), 96 | colors = OutlinedTextFieldDefaults.colors( 97 | cursorColor = DevkitWalletColors.accent1, 98 | focusedBorderColor = DevkitWalletColors.accent1, 99 | unfocusedBorderColor = DevkitWalletColors.white, 100 | ), 101 | ) 102 | 103 | Spacer(modifier = Modifier.padding(12.dp)) 104 | WalletOptionsCard(scriptTypes, selectedNetwork, selectedScriptType) 105 | Spacer(modifier = Modifier.padding(16.dp)) 106 | } 107 | 108 | Column( 109 | modifier = Modifier 110 | .constrainAs(button) { 111 | bottom.linkTo(parent.bottom) 112 | start.linkTo(parent.start) 113 | end.linkTo(parent.end) 114 | }.fillMaxWidth() 115 | .padding(horizontal = 32.dp) 116 | ) { 117 | NeutralButton( 118 | text = "Create Wallet", 119 | enabled = true, 120 | modifier = Modifier 121 | .height(80.dp) 122 | .fillMaxWidth() 123 | .padding(vertical = 8.dp) 124 | .shadow(elevation = 4.dp, shape = RoundedCornerShape(16.dp)), 125 | onClick = { 126 | val newWalletConfig = NewWalletConfig( 127 | name = walletName.value, 128 | network = selectedNetwork.value, 129 | scriptType = selectedScriptType.value 130 | ) 131 | DwLogger.log(INFO, "Creating new wallet named ${newWalletConfig.name}") 132 | onBuildWalletButtonClicked(WalletCreateType.FROMSCRATCH(newWalletConfig)) 133 | } 134 | ) 135 | } 136 | } 137 | } 138 | } 139 | 140 | fun ActiveWalletScriptType.displayString(): String { 141 | return when (this) { 142 | ActiveWalletScriptType.P2TR -> "P2TR (Taproot, BIP-86)" 143 | ActiveWalletScriptType.P2WPKH -> "P2WPKH (Native Segwit, BIP-84)" 144 | ActiveWalletScriptType.UNKNOWN -> TODO() 145 | ActiveWalletScriptType.UNRECOGNIZED -> TODO() 146 | } 147 | } 148 | 149 | fun Network.displayString(): String { 150 | return when (this) { 151 | Network.REGTEST -> "Regtest" 152 | Network.TESTNET -> "Testnet 3" 153 | Network.TESTNET4 -> "Testnet 4" 154 | Network.SIGNET -> "Signet" 155 | Network.BITCOIN -> TODO() 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /app/src/main/java/org/bitcoindevkit/devkitwallet/presentation/ui/screens/intro/OnboardingScreen.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 thunderbiscuit and contributors. 3 | * Use of this source code is governed by the Apache 2.0 license that can be found in the ./LICENSE file. 4 | */ 5 | 6 | package org.bitcoindevkit.devkitwallet.presentation.ui.screens.intro 7 | 8 | import androidx.compose.animation.Crossfade 9 | import androidx.compose.animation.core.tween 10 | import androidx.compose.foundation.Image 11 | import androidx.compose.foundation.background 12 | import androidx.compose.foundation.clickable 13 | import androidx.compose.foundation.interaction.MutableInteractionSource 14 | import androidx.compose.foundation.layout.Arrangement 15 | import androidx.compose.foundation.layout.Box 16 | import androidx.compose.foundation.layout.Row 17 | import androidx.compose.foundation.layout.fillMaxSize 18 | import androidx.compose.foundation.layout.fillMaxWidth 19 | import androidx.compose.foundation.layout.padding 20 | import androidx.compose.foundation.layout.size 21 | import androidx.compose.foundation.shape.CircleShape 22 | import androidx.compose.material3.Text 23 | import androidx.compose.runtime.Composable 24 | import androidx.compose.runtime.mutableIntStateOf 25 | import androidx.compose.runtime.remember 26 | import androidx.compose.ui.Modifier 27 | import androidx.compose.ui.draw.clip 28 | import androidx.compose.ui.graphics.Color 29 | import androidx.compose.ui.res.painterResource 30 | import androidx.compose.ui.unit.dp 31 | import androidx.constraintlayout.compose.ConstraintLayout 32 | import org.bitcoindevkit.devkitwallet.R 33 | import org.bitcoindevkit.devkitwallet.presentation.theme.DevkitWalletColors 34 | import org.bitcoindevkit.devkitwallet.presentation.theme.devkitTypography 35 | 36 | @Composable 37 | fun OnboardingScreen(onFinishOnboarding: () -> Unit) { 38 | val (currentIndex, setCurrentIndex) = remember { mutableIntStateOf(1) } 39 | 40 | @Suppress("ktlint:standard:max-line-length") 41 | val messages = listOf( 42 | "Easter egg #1: \uD83E\uDD5A", 43 | "Welcome to the Devkit Wallet! This app is a playground for developers and bitcoin enthusiasts to experiment with bitcoin's test networks.", 44 | "It is developed with the Bitcoin Dev Kit, a powerful set of libraries produced and maintained by the Bitcoin Dev Kit Foundation.\n\nThe variant of the app you have installed in the Esplora variant, which uses Esplora clients to fetch blockchain data for the wallet.", 45 | "The Foundation maintains this app as a way to showcase the capabilities of the Bitcoin Dev Kit and to provide a starting point for developers to build their own apps.\n\nIt is not a production application, and only works for testnet, signet, and regtest. Have fun!" 46 | ) 47 | 48 | ConstraintLayout( 49 | modifier = Modifier 50 | .fillMaxSize() 51 | .background(DevkitWalletColors.primary) 52 | ) { 53 | val (logo, intro, progress, buttons) = createRefs() 54 | 55 | Image( 56 | painter = painterResource(id = R.drawable.bdk_logo), 57 | contentDescription = "Bitcoin Dev Kit logo", 58 | Modifier 59 | .size(180.dp) 60 | .constrainAs(logo) { 61 | top.linkTo(parent.top, margin = 90.dp) 62 | start.linkTo(parent.start) 63 | end.linkTo(parent.end) 64 | } 65 | ) 66 | 67 | Crossfade( 68 | modifier = Modifier.constrainAs(intro) { 69 | top.linkTo(logo.bottom, margin = 90.dp) 70 | start.linkTo(parent.start) 71 | end.linkTo(parent.end) 72 | }, 73 | targetState = currentIndex, 74 | label = "", 75 | animationSpec = tween( 76 | durationMillis = 1000, 77 | delayMillis = 200, 78 | ) 79 | ) { screen -> 80 | when (screen) { 81 | 0 -> IntroTextPart(messages[0]) 82 | 1 -> IntroTextPart(messages[1]) 83 | 2 -> IntroTextPart(messages[2]) 84 | 3 -> IntroTextPart(messages[3]) 85 | } 86 | } 87 | 88 | Row( 89 | modifier = Modifier.constrainAs(progress) { 90 | bottom.linkTo(buttons.top, margin = 32.dp) 91 | start.linkTo(parent.start) 92 | end.linkTo(parent.end) 93 | }, 94 | horizontalArrangement = Arrangement.Center 95 | ) { 96 | Box( 97 | modifier = Modifier 98 | .padding(horizontal = 8.dp) 99 | .size(size = 16.dp) 100 | .clip(shape = CircleShape) 101 | .background( 102 | if (currentIndex == 1) Color(0xffE9C46A) else Color(0xffE9C46A).copy(alpha = 0.3f) 103 | ) 104 | ) 105 | Box( 106 | modifier = Modifier 107 | .padding(horizontal = 8.dp) 108 | .size(size = 16.dp) 109 | .clip(shape = CircleShape) 110 | .background( 111 | if (currentIndex == 2) Color(0xffE9C46A) else Color(0xffE9C46A).copy(alpha = 0.3f) 112 | ) 113 | ) 114 | Box( 115 | modifier = Modifier 116 | .padding(horizontal = 8.dp) 117 | .size(size = 16.dp) 118 | .clip(shape = CircleShape) 119 | .background( 120 | if (currentIndex == 3) Color(0xffE9C46A) else Color(0xffE9C46A).copy(alpha = 0.3f) 121 | ) 122 | ) 123 | } 124 | 125 | Row( 126 | modifier = Modifier 127 | .fillMaxWidth() 128 | .padding(horizontal = 32.dp) 129 | .constrainAs(buttons) { 130 | bottom.linkTo(parent.bottom, margin = 32.dp) 131 | start.linkTo(parent.start) 132 | end.linkTo(parent.end) 133 | }, 134 | horizontalArrangement = Arrangement.SpaceBetween, 135 | ) { 136 | Text( 137 | text = "Previous", 138 | modifier = Modifier 139 | .clickable( 140 | indication = null, 141 | interactionSource = remember { MutableInteractionSource() } 142 | ) { setCurrentIndex((currentIndex - 1).coerceIn(0, 3)) }, 143 | color = DevkitWalletColors.white, 144 | style = devkitTypography.labelLarge 145 | ) 146 | Text( 147 | text = if (currentIndex < 3) "Next" else "Awesome!", 148 | modifier = Modifier 149 | .clickable( 150 | indication = null, 151 | interactionSource = remember { MutableInteractionSource() } 152 | ) { 153 | if (currentIndex < 3) { 154 | setCurrentIndex( 155 | (currentIndex + 1).coerceIn(0, 3) 156 | ) 157 | } else { 158 | onFinishOnboarding() 159 | } 160 | }, 161 | color = DevkitWalletColors.white, 162 | style = devkitTypography.labelLarge 163 | ) 164 | } 165 | } 166 | } 167 | 168 | @Composable 169 | fun IntroTextPart(message: String) { 170 | Text( 171 | text = message, 172 | modifier = Modifier.padding(horizontal = 32.dp), 173 | color = DevkitWalletColors.white, 174 | style = devkitTypography.labelLarge 175 | ) 176 | } 177 | -------------------------------------------------------------------------------- /app/src/main/java/org/bitcoindevkit/devkitwallet/presentation/ui/screens/intro/WalletChoiceScreen.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 thunderbiscuit and contributors. 3 | * Use of this source code is governed by the Apache 2.0 license that can be found in the ./LICENSE file. 4 | */ 5 | 6 | package org.bitcoindevkit.devkitwallet.presentation.ui.screens.intro 7 | 8 | import androidx.compose.foundation.Image 9 | import androidx.compose.foundation.layout.Arrangement 10 | import androidx.compose.foundation.layout.Row 11 | import androidx.compose.foundation.layout.Spacer 12 | import androidx.compose.foundation.layout.fillMaxSize 13 | import androidx.compose.foundation.layout.fillMaxWidth 14 | import androidx.compose.foundation.layout.padding 15 | import androidx.compose.foundation.layout.size 16 | import androidx.compose.foundation.shape.RoundedCornerShape 17 | import androidx.compose.material3.Button 18 | import androidx.compose.material3.ButtonDefaults 19 | import androidx.compose.material3.Scaffold 20 | import androidx.compose.material3.Text 21 | import androidx.compose.runtime.Composable 22 | import androidx.compose.ui.Alignment 23 | import androidx.compose.ui.Modifier 24 | import androidx.compose.ui.draw.shadow 25 | import androidx.compose.ui.res.painterResource 26 | import androidx.compose.ui.text.style.TextAlign 27 | import androidx.compose.ui.unit.dp 28 | import androidx.compose.ui.unit.sp 29 | import androidx.constraintlayout.compose.ConstraintLayout 30 | import androidx.navigation.NavController 31 | import org.bitcoindevkit.devkitwallet.R 32 | import org.bitcoindevkit.devkitwallet.presentation.navigation.ActiveWalletsScreen 33 | import org.bitcoindevkit.devkitwallet.presentation.navigation.CreateNewWalletScreen 34 | import org.bitcoindevkit.devkitwallet.presentation.navigation.WalletRecoveryScreen 35 | import org.bitcoindevkit.devkitwallet.presentation.theme.DevkitWalletColors 36 | import org.bitcoindevkit.devkitwallet.presentation.theme.monoBold 37 | 38 | @Composable 39 | internal fun WalletChoiceScreen(navController: NavController) { 40 | Scaffold( 41 | containerColor = DevkitWalletColors.primary 42 | ) { paddingValues -> 43 | ConstraintLayout( 44 | modifier = Modifier 45 | .fillMaxSize() 46 | .padding(paddingValues) 47 | ) { 48 | val (logo, active, create, recover) = createRefs() 49 | 50 | Row( 51 | modifier = Modifier 52 | .fillMaxWidth() 53 | .padding(top = 90.dp) 54 | .constrainAs(logo) { 55 | top.linkTo(parent.top) 56 | }, 57 | horizontalArrangement = Arrangement.Center, 58 | verticalAlignment = Alignment.CenterVertically, 59 | ) { 60 | Image( 61 | painter = painterResource(id = R.drawable.ic_testnet_logo), 62 | contentDescription = "Bitcoin testnet logo", 63 | Modifier.size(90.dp) 64 | ) 65 | Spacer(modifier = Modifier.padding(8.dp)) 66 | Text( 67 | text = "Devkit\nWallet", 68 | color = DevkitWalletColors.white, 69 | fontSize = 28.sp, 70 | lineHeight = 38.sp, 71 | fontFamily = monoBold, 72 | ) 73 | } 74 | 75 | Button( 76 | onClick = { navController.navigate(ActiveWalletsScreen) }, 77 | colors = ButtonDefaults.buttonColors(DevkitWalletColors.secondary), 78 | shape = RoundedCornerShape(16.dp), 79 | enabled = true, 80 | modifier = Modifier 81 | .size(width = 300.dp, height = 150.dp) 82 | .padding(vertical = 8.dp, horizontal = 8.dp) 83 | .shadow(elevation = 8.dp, shape = RoundedCornerShape(16.dp)) 84 | .constrainAs(active) { 85 | bottom.linkTo(create.top) 86 | start.linkTo(parent.start) 87 | end.linkTo(parent.end) 88 | } 89 | ) { 90 | Text( 91 | text = "Use an\nActive Wallet", 92 | // fontSize = 18.sp, 93 | textAlign = TextAlign.Center, 94 | // lineHeight = 28.sp, 95 | ) 96 | } 97 | 98 | Button( 99 | onClick = { navController.navigate(CreateNewWalletScreen) }, 100 | colors = ButtonDefaults.buttonColors(DevkitWalletColors.secondary), 101 | shape = RoundedCornerShape(16.dp), 102 | modifier = Modifier 103 | .size(width = 300.dp, height = 150.dp) 104 | .padding(vertical = 8.dp, horizontal = 8.dp) 105 | .shadow(elevation = 8.dp, shape = RoundedCornerShape(16.dp)) 106 | .constrainAs(create) { 107 | bottom.linkTo(recover.top) 108 | start.linkTo(parent.start) 109 | end.linkTo(parent.end) 110 | } 111 | ) { 112 | Text( 113 | text = "Create a\nNew Wallet", 114 | // fontSize = 18.sp, 115 | textAlign = TextAlign.Center, 116 | // lineHeight = 28.sp, 117 | ) 118 | } 119 | 120 | Button( 121 | onClick = { navController.navigate(WalletRecoveryScreen) }, 122 | colors = ButtonDefaults.buttonColors(DevkitWalletColors.secondary), 123 | shape = RoundedCornerShape(16.dp), 124 | modifier = Modifier 125 | .size(width = 300.dp, height = 150.dp) 126 | .padding(vertical = 8.dp, horizontal = 8.dp) 127 | .shadow(elevation = 8.dp, shape = RoundedCornerShape(16.dp)) 128 | .constrainAs(recover) { 129 | bottom.linkTo(parent.bottom, margin = 70.dp) 130 | start.linkTo(parent.start) 131 | end.linkTo(parent.end) 132 | } 133 | ) { 134 | Text( 135 | text = "Recover an\nExisting Wallet", 136 | // fontSize = 18.sp, 137 | textAlign = TextAlign.Center, 138 | // lineHeight = 28.sp, 139 | ) 140 | } 141 | } 142 | } 143 | } 144 | 145 | // @Preview(device = Devices.PIXEL_4, showBackground = true) 146 | // @Composable 147 | // internal fun PreviewWalletChoiceScreen() { 148 | // WalletChoiceScreen(rememberNavController()) 149 | // } 150 | -------------------------------------------------------------------------------- /app/src/main/java/org/bitcoindevkit/devkitwallet/presentation/ui/screens/wallet/ReceiveScreen.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 thunderbiscuit and contributors. 3 | * Use of this source code is governed by the Apache 2.0 license that can be found in the ./LICENSE file. 4 | */ 5 | 6 | package org.bitcoindevkit.devkitwallet.presentation.ui.screens.wallet 7 | 8 | import android.content.ClipData 9 | import android.content.ClipboardManager 10 | import android.content.Context 11 | import android.util.Log 12 | import androidx.compose.foundation.Image 13 | import androidx.compose.foundation.background 14 | import androidx.compose.foundation.clickable 15 | import androidx.compose.foundation.layout.Arrangement 16 | import androidx.compose.foundation.layout.Box 17 | import androidx.compose.foundation.layout.Column 18 | import androidx.compose.foundation.layout.Spacer 19 | import androidx.compose.foundation.layout.fillMaxSize 20 | import androidx.compose.foundation.layout.fillMaxWidth 21 | import androidx.compose.foundation.layout.height 22 | import androidx.compose.foundation.layout.padding 23 | import androidx.compose.foundation.layout.size 24 | import androidx.compose.foundation.shape.RoundedCornerShape 25 | import androidx.compose.foundation.text.selection.SelectionContainer 26 | import androidx.compose.material3.Button 27 | import androidx.compose.material3.ButtonDefaults 28 | import androidx.compose.material3.Icon 29 | import androidx.compose.material3.Scaffold 30 | import androidx.compose.material3.SnackbarHost 31 | import androidx.compose.material3.SnackbarHostState 32 | import androidx.compose.material3.Text 33 | import androidx.compose.runtime.Composable 34 | import androidx.compose.runtime.remember 35 | import androidx.compose.runtime.rememberCoroutineScope 36 | import androidx.compose.ui.Alignment 37 | import androidx.compose.ui.Modifier 38 | import androidx.compose.ui.draw.clip 39 | import androidx.compose.ui.draw.shadow 40 | import androidx.compose.ui.graphics.Color 41 | import androidx.compose.ui.graphics.ImageBitmap 42 | import androidx.compose.ui.graphics.asImageBitmap 43 | import androidx.compose.ui.platform.LocalContext 44 | import androidx.compose.ui.text.style.TextAlign 45 | import androidx.compose.ui.unit.dp 46 | import androidx.compose.ui.unit.sp 47 | import androidx.constraintlayout.compose.ConstraintLayout 48 | import androidx.constraintlayout.compose.Dimension 49 | import androidx.core.graphics.createBitmap 50 | import androidx.navigation.NavController 51 | import com.composables.icons.lucide.ClipboardCopy 52 | import com.composables.icons.lucide.Lucide 53 | import com.google.zxing.BarcodeFormat 54 | import com.google.zxing.common.BitMatrix 55 | import com.google.zxing.qrcode.QRCodeWriter 56 | import kotlinx.coroutines.CoroutineScope 57 | import kotlinx.coroutines.delay 58 | import kotlinx.coroutines.launch 59 | import org.bitcoindevkit.devkitwallet.presentation.navigation.HomeScreen 60 | import org.bitcoindevkit.devkitwallet.presentation.theme.DevkitWalletColors 61 | import org.bitcoindevkit.devkitwallet.presentation.theme.monoRegular 62 | import org.bitcoindevkit.devkitwallet.presentation.theme.standardText 63 | import org.bitcoindevkit.devkitwallet.presentation.ui.components.SecondaryScreensAppBar 64 | import org.bitcoindevkit.devkitwallet.presentation.viewmodels.mvi.ReceiveScreenAction 65 | import org.bitcoindevkit.devkitwallet.presentation.viewmodels.mvi.ReceiveScreenState 66 | 67 | private const val TAG = "ReceiveScreen" 68 | 69 | @Composable 70 | internal fun ReceiveScreen( 71 | state: ReceiveScreenState, 72 | onAction: (ReceiveScreenAction) -> Unit, 73 | navController: NavController, 74 | ) { 75 | Log.i(TAG, "We are recomposing the ReceiveScreen") 76 | val snackbarHostState = remember { SnackbarHostState() } 77 | Scaffold( 78 | snackbarHost = { SnackbarHost(snackbarHostState) }, 79 | topBar = { 80 | SecondaryScreensAppBar( 81 | title = "Receive Address", 82 | navigation = { navController.navigate(HomeScreen) } 83 | ) 84 | }, 85 | containerColor = DevkitWalletColors.primary 86 | ) { paddingValues -> 87 | ConstraintLayout( 88 | modifier = Modifier 89 | .padding(paddingValues) 90 | .fillMaxSize() 91 | ) { 92 | val (QRCode, bottomButtons) = createRefs() 93 | val context = LocalContext.current 94 | val scope = rememberCoroutineScope() 95 | Column( 96 | horizontalAlignment = Alignment.CenterHorizontally, 97 | verticalArrangement = Arrangement.Center, 98 | modifier = Modifier 99 | .constrainAs(QRCode) { 100 | top.linkTo(parent.top) 101 | bottom.linkTo(bottomButtons.top) 102 | start.linkTo(parent.start) 103 | end.linkTo(parent.end) 104 | height = Dimension.fillToConstraints 105 | }.padding(horizontal = 32.dp) 106 | ) { 107 | val qr: ImageBitmap? = state.address?.let { addressToQR(it) } 108 | Log.i("ReceiveScreen", "New receive address is ${state.address}") 109 | if (qr != null) { 110 | Image( 111 | bitmap = qr, 112 | contentDescription = "Bitcoindevkit website QR code", 113 | Modifier.size(250.dp).clip(RoundedCornerShape(16.dp)) 114 | ) 115 | Spacer(modifier = Modifier.padding(vertical = 16.dp)) 116 | Box { 117 | SelectionContainer { 118 | Text( 119 | modifier = Modifier 120 | .clickable { 121 | copyToClipboard( 122 | state.address, 123 | context, 124 | scope, 125 | snackbarHostState, 126 | null, 127 | ) 128 | }.background( 129 | color = DevkitWalletColors.primaryLight, 130 | shape = RoundedCornerShape(16.dp) 131 | ).padding(12.dp), 132 | text = state.address.chunked(4).joinToString(" "), 133 | fontFamily = monoRegular, 134 | color = DevkitWalletColors.white 135 | ) 136 | } 137 | Icon( 138 | Lucide.ClipboardCopy, 139 | tint = Color.White, 140 | contentDescription = "Copy to clipboard", 141 | modifier = Modifier 142 | .padding(8.dp) 143 | .size(20.dp) 144 | .align(Alignment.BottomEnd) 145 | ) 146 | } 147 | Spacer(modifier = Modifier.padding(vertical = 16.dp)) 148 | Text( 149 | text = "Wallet address index: ${state.addressIndex}", 150 | fontFamily = monoRegular, 151 | color = DevkitWalletColors.white, 152 | modifier = Modifier.align(Alignment.Start) 153 | ) 154 | } 155 | } 156 | 157 | Column( 158 | Modifier 159 | .constrainAs(bottomButtons) { 160 | bottom.linkTo(parent.bottom) 161 | start.linkTo(parent.start) 162 | end.linkTo(parent.end) 163 | }.padding(bottom = 24.dp) 164 | ) { 165 | Button( 166 | onClick = { onAction(ReceiveScreenAction.UpdateAddress) }, 167 | colors = ButtonDefaults.buttonColors(DevkitWalletColors.secondary), 168 | shape = RoundedCornerShape(16.dp), 169 | modifier = Modifier 170 | .height(80.dp) 171 | .fillMaxWidth(0.9f) 172 | .padding(vertical = 8.dp, horizontal = 8.dp) 173 | .shadow(elevation = 4.dp, shape = RoundedCornerShape(16.dp)) 174 | ) { 175 | Text( 176 | text = "Generate address", 177 | style = standardText, 178 | textAlign = TextAlign.Center, 179 | lineHeight = 28.sp, 180 | ) 181 | } 182 | } 183 | } 184 | } 185 | } 186 | 187 | private fun addressToQR(address: String): ImageBitmap? { 188 | Log.i(TAG, "We are generating the QR code for address $address") 189 | try { 190 | val qrCodeWriter: QRCodeWriter = QRCodeWriter() 191 | val bitMatrix: BitMatrix = qrCodeWriter.encode(address, BarcodeFormat.QR_CODE, 1000, 1000) 192 | val bitMap = createBitmap(1000, 1000) 193 | for (x in 0 until 1000) { 194 | for (y in 0 until 1000) { 195 | // DevkitWalletColors.primaryDark for dark and DevkitWalletColors.white for light 196 | bitMap.setPixel(x, y, if (bitMatrix[x, y]) 0xff203b46.toInt() else 0xffffffff.toInt()) 197 | } 198 | } 199 | return bitMap.asImageBitmap() 200 | } catch (e: Throwable) { 201 | Log.i("ReceiveScreen", "Error with QRCode generation, $e") 202 | } 203 | return null 204 | } 205 | 206 | fun copyToClipboard( 207 | content: String, 208 | context: Context, 209 | scope: CoroutineScope, 210 | snackbarHostState: SnackbarHostState, 211 | setCopyClicked: ( 212 | (Boolean) -> Unit 213 | )?, 214 | ) { 215 | val clipboard: ClipboardManager = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager 216 | val clip: ClipData = ClipData.newPlainText("", content) 217 | clipboard.setPrimaryClip(clip) 218 | scope.launch { 219 | snackbarHostState.showSnackbar("Copied address to clipboard!") 220 | delay(1000) 221 | if (setCopyClicked != null) { 222 | setCopyClicked(false) 223 | } 224 | } 225 | } 226 | 227 | // @Preview(device = Devices.PIXEL_4, showBackground = true) 228 | // @Composable 229 | // internal fun PreviewReceiveScreen() { 230 | // ReceiveScreen(rememberNavController()) 231 | // } 232 | -------------------------------------------------------------------------------- /app/src/main/java/org/bitcoindevkit/devkitwallet/presentation/ui/screens/wallet/TransactionHistoryScreen.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 thunderbiscuit and contributors. 3 | * Use of this source code is governed by the Apache 2.0 license that can be found in the ./LICENSE file. 4 | */ 5 | 6 | package org.bitcoindevkit.devkitwallet.presentation.ui.screens.wallet 7 | 8 | import androidx.compose.foundation.layout.Column 9 | import androidx.compose.foundation.layout.fillMaxSize 10 | import androidx.compose.foundation.layout.padding 11 | import androidx.compose.foundation.rememberScrollState 12 | import androidx.compose.foundation.verticalScroll 13 | import androidx.compose.material3.Scaffold 14 | import androidx.compose.runtime.Composable 15 | import androidx.compose.ui.Modifier 16 | import androidx.compose.ui.unit.dp 17 | import androidx.navigation.NavController 18 | import org.bitcoindevkit.devkitwallet.domain.Wallet 19 | import org.bitcoindevkit.devkitwallet.presentation.navigation.HomeScreen 20 | import org.bitcoindevkit.devkitwallet.presentation.navigation.TransactionScreen 21 | import org.bitcoindevkit.devkitwallet.presentation.theme.DevkitWalletColors 22 | import org.bitcoindevkit.devkitwallet.presentation.ui.components.ConfirmedTransactionCard 23 | import org.bitcoindevkit.devkitwallet.presentation.ui.components.PendingTransactionCard 24 | import org.bitcoindevkit.devkitwallet.presentation.ui.components.SecondaryScreensAppBar 25 | 26 | private const val TAG = "TransactionHistoryScreen" 27 | 28 | @Composable 29 | internal fun TransactionHistoryScreen(navController: NavController, activeWallet: Wallet) { 30 | val (pendingTransactions, confirmedTransactions) = activeWallet.getAllTxDetails().partition { it.pending } 31 | 32 | Scaffold( 33 | topBar = { 34 | SecondaryScreensAppBar( 35 | title = "Transaction History", 36 | navigation = { navController.navigate(HomeScreen) } 37 | ) 38 | }, 39 | containerColor = DevkitWalletColors.primary 40 | ) { paddingValues -> 41 | val scrollState = rememberScrollState() 42 | Column( 43 | modifier = Modifier 44 | .padding(paddingValues) 45 | .fillMaxSize() 46 | .padding(top = 6.dp) 47 | .verticalScroll(state = scrollState) 48 | ) { 49 | if (pendingTransactions.isNotEmpty()) { 50 | pendingTransactions.forEach { 51 | PendingTransactionCard(details = it, navController = navController) 52 | } 53 | } 54 | if (confirmedTransactions.isNotEmpty()) { 55 | confirmedTransactions.sortedBy { it.confirmationBlock?.height }.forEach { 56 | ConfirmedTransactionCard(it, navController) 57 | } 58 | } 59 | } 60 | } 61 | } 62 | 63 | fun viewTransaction(navController: NavController, txid: String) { 64 | navController.navigate(TransactionScreen(txid)) 65 | } 66 | 67 | // @Preview(device = Devices.PIXEL_4, showBackground = true) 68 | // @Composable 69 | // internal fun PreviewTransactionsScreen() { 70 | // TransactionsScreen(rememberNavController()) 71 | // } 72 | -------------------------------------------------------------------------------- /app/src/main/java/org/bitcoindevkit/devkitwallet/presentation/ui/screens/wallet/TransactionScreen.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 thunderbiscuit and contributors. 3 | * Use of this source code is governed by the Apache 2.0 license that can be found in the ./LICENSE file. 4 | */ 5 | 6 | package org.bitcoindevkit.devkitwallet.presentation.ui.screens.wallet 7 | 8 | import androidx.compose.foundation.background 9 | import androidx.compose.foundation.layout.Arrangement 10 | import androidx.compose.foundation.layout.Column 11 | import androidx.compose.foundation.layout.fillMaxSize 12 | import androidx.compose.foundation.layout.fillMaxWidth 13 | import androidx.compose.foundation.layout.height 14 | import androidx.compose.foundation.layout.padding 15 | import androidx.compose.foundation.lazy.LazyColumn 16 | import androidx.compose.foundation.shape.RoundedCornerShape 17 | import androidx.compose.material3.Button 18 | import androidx.compose.material3.ButtonDefaults 19 | import androidx.compose.material3.Scaffold 20 | import androidx.compose.material3.Text 21 | import androidx.compose.runtime.Composable 22 | import androidx.compose.ui.Alignment 23 | import androidx.compose.ui.Modifier 24 | import androidx.compose.ui.draw.shadow 25 | import androidx.compose.ui.text.style.TextAlign 26 | import androidx.compose.ui.unit.dp 27 | import androidx.compose.ui.unit.sp 28 | import androidx.constraintlayout.compose.ConstraintLayout 29 | import androidx.constraintlayout.compose.Dimension 30 | import androidx.navigation.NavController 31 | import org.bitcoindevkit.devkitwallet.presentation.navigation.RbfScreen 32 | import org.bitcoindevkit.devkitwallet.presentation.theme.DevkitWalletColors 33 | import org.bitcoindevkit.devkitwallet.presentation.theme.monoRegular 34 | import org.bitcoindevkit.devkitwallet.presentation.ui.components.SecondaryScreensAppBar 35 | 36 | @Composable 37 | internal fun TransactionScreen(txid: String?, navController: NavController) { 38 | // val transaction = getTransaction(txid = txid) 39 | // if (transaction == null) { 40 | // navController.popBackStack() 41 | // } 42 | // val transactionDetail = getTransactionDetails(transaction = transaction!!) 43 | 44 | Scaffold( 45 | topBar = { 46 | SecondaryScreensAppBar( 47 | title = "Transaction Details", 48 | navigation = { navController.navigateUp() } 49 | ) 50 | }, 51 | containerColor = DevkitWalletColors.primary 52 | ) { paddingValues -> 53 | ConstraintLayout( 54 | modifier = Modifier 55 | .fillMaxSize() 56 | .background(DevkitWalletColors.primary) 57 | .padding(paddingValues) 58 | ) { 59 | val (screenTitle, transactions, bottomButton) = createRefs() 60 | 61 | Column( 62 | modifier = Modifier 63 | .constrainAs(screenTitle) { 64 | top.linkTo(parent.top) 65 | start.linkTo(parent.start) 66 | end.linkTo(parent.end) 67 | }.padding(top = 70.dp) 68 | ) { 69 | Text( 70 | text = "Transaction", 71 | color = DevkitWalletColors.white, 72 | fontSize = 28.sp, 73 | fontFamily = monoRegular, 74 | textAlign = TextAlign.Center, 75 | modifier = Modifier.fillMaxWidth() 76 | ) 77 | // Text( 78 | // text = transactionTitle(transaction = transaction), 79 | // color = DevkitWalletColors.white, 80 | // fontSize = 14.sp, 81 | // textAlign = TextAlign.Center, 82 | // modifier = Modifier.padding(horizontal = 16.dp) 83 | // ) 84 | } 85 | 86 | LazyColumn( 87 | horizontalAlignment = Alignment.CenterHorizontally, 88 | verticalArrangement = Arrangement.Center, 89 | modifier = Modifier 90 | .constrainAs(transactions) { 91 | top.linkTo(screenTitle.bottom) 92 | bottom.linkTo(bottomButton.top) 93 | start.linkTo(parent.start) 94 | end.linkTo(parent.end) 95 | height = Dimension.fillToConstraints 96 | } 97 | ) { 98 | // items(transactionDetail) { 99 | // Row( 100 | // modifier = Modifier 101 | // .fillMaxWidth() 102 | // .padding(all = 16.dp) 103 | // ) { 104 | // Text( 105 | // text = "${it.first} :", 106 | // fontSize = 16.sp, 107 | // color = DevkitWalletColors.white, 108 | // ) 109 | // Text( 110 | // text = it.second, 111 | // fontSize = 16.sp, 112 | // textAlign = TextAlign.End, 113 | // color = DevkitWalletColors.white, 114 | // modifier = Modifier.fillMaxWidth() 115 | // ) 116 | // } 117 | // } 118 | } 119 | 120 | Column( 121 | modifier = Modifier 122 | .fillMaxWidth(0.9f) 123 | .padding(vertical = 8.dp, horizontal = 8.dp) 124 | .shadow(elevation = 4.dp, shape = RoundedCornerShape(16.dp)) 125 | .constrainAs(bottomButton) { 126 | bottom.linkTo(parent.bottom) 127 | start.linkTo(parent.start) 128 | end.linkTo(parent.end) 129 | } 130 | ) { 131 | TransactionDetailButton( 132 | content = "increase fees", 133 | navController = navController, 134 | txid = txid 135 | ) 136 | } 137 | } 138 | } 139 | } 140 | 141 | @Composable 142 | fun TransactionDetailButton(content: String, navController: NavController, txid: String?) { 143 | Button( 144 | onClick = { 145 | when (content) { 146 | "increase fees" -> { 147 | navController.navigate(RbfScreen(txid!!)) 148 | } 149 | "back to transaction list" -> { 150 | navController.navigateUp() 151 | } 152 | } 153 | }, 154 | colors = ButtonDefaults.buttonColors(DevkitWalletColors.secondary), 155 | shape = RoundedCornerShape(16.dp), 156 | modifier = Modifier 157 | .height(60.dp) 158 | .fillMaxWidth() 159 | ) { 160 | Text( 161 | text = content, 162 | fontSize = 14.sp, 163 | fontFamily = monoRegular, 164 | textAlign = TextAlign.Center, 165 | lineHeight = 28.sp, 166 | ) 167 | } 168 | } 169 | 170 | // fun getTransactionDetails(transaction: TransactionDetails): List> { 171 | // val transactionDetails = mutableListOf>() 172 | // 173 | // if (transaction.confirmationTime != null) { 174 | // transactionDetails.add(Pair("Status", "Confirmed")) 175 | // transactionDetails.add(Pair("Timestamp", transaction.confirmationTime!!.timestamp.timestampToString())) 176 | // transactionDetails.add(Pair("Received", (if (transaction.received < transaction.sent) 0 else transaction.received).toString())) 177 | // transactionDetails.add(Pair("Sent", (if (transaction.sent < transaction.received) 0 else transaction.sent - transaction.received - transaction.fee!!).toString())) 178 | // transactionDetails.add(Pair("Fees", transaction.fee.toString())) 179 | // transactionDetails.add(Pair("Block", transaction.confirmationTime!!.height.toString())) 180 | // } else { 181 | // transactionDetails.add(Pair("Status", "Pending")) 182 | // transactionDetails.add(Pair("Timestamp", "Pending")) 183 | // transactionDetails.add(Pair("Received", (if (transaction.received < transaction.sent) 0 else transaction.received).toString())) 184 | // transactionDetails.add(Pair("Sent", (if (transaction.sent < transaction.received) 0 else transaction.sent - transaction.received - transaction.fee!!).toString())) 185 | // transactionDetails.add(Pair("Fees", transaction.fee.toString())) 186 | // } 187 | // return transactionDetails 188 | // } 189 | // 190 | // fun transactionTitle(transaction: TransactionDetails): String { 191 | // return transaction.txid 192 | // } 193 | // 194 | // fun getTransaction(txid: String?): TransactionDetails? { 195 | // if (txid.isNullOrEmpty()) { 196 | // return null 197 | // } 198 | // return Wallet.getTransaction(txid = txid) 199 | // } 200 | -------------------------------------------------------------------------------- /app/src/main/java/org/bitcoindevkit/devkitwallet/presentation/viewmodels/AddressViewModel.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 thunderbiscuit and contributors. 3 | * Use of this source code is governed by the Apache 2.0 license that can be found in the ./LICENSE file. 4 | */ 5 | 6 | package org.bitcoindevkit.devkitwallet.presentation.viewmodels 7 | 8 | import androidx.compose.runtime.getValue 9 | import androidx.compose.runtime.mutableStateOf 10 | import androidx.compose.runtime.setValue 11 | import androidx.lifecycle.ViewModel 12 | import org.bitcoindevkit.AddressInfo 13 | import org.bitcoindevkit.devkitwallet.domain.DwLogger 14 | import org.bitcoindevkit.devkitwallet.domain.DwLogger.LogLevel.INFO 15 | import org.bitcoindevkit.devkitwallet.domain.Wallet 16 | import org.bitcoindevkit.devkitwallet.presentation.viewmodels.mvi.ReceiveScreenAction 17 | import org.bitcoindevkit.devkitwallet.presentation.viewmodels.mvi.ReceiveScreenState 18 | 19 | internal class AddressViewModel(private val wallet: Wallet) : ViewModel() { 20 | var state: ReceiveScreenState by mutableStateOf(ReceiveScreenState()) 21 | private set 22 | 23 | fun onAction(action: ReceiveScreenAction) { 24 | when (action) { 25 | is ReceiveScreenAction.UpdateAddress -> updateAddress() 26 | } 27 | } 28 | 29 | private fun updateAddress() { 30 | val newAddress: AddressInfo = wallet.getNewAddress() 31 | DwLogger.log(INFO, "Revealing new address at index ${newAddress.index}") 32 | 33 | state = ReceiveScreenState( 34 | address = newAddress.address.toString(), 35 | addressIndex = newAddress.index 36 | ) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/org/bitcoindevkit/devkitwallet/presentation/viewmodels/SendViewModel.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 thunderbiscuit and contributors. 3 | * Use of this source code is governed by the Apache 2.0 license that can be found in the ./LICENSE file. 4 | */ 5 | 6 | package org.bitcoindevkit.devkitwallet.presentation.viewmodels 7 | 8 | import android.util.Log 9 | import androidx.lifecycle.ViewModel 10 | import org.bitcoindevkit.FeeRate 11 | import org.bitcoindevkit.Psbt 12 | import org.bitcoindevkit.devkitwallet.domain.Wallet 13 | import org.bitcoindevkit.devkitwallet.presentation.viewmodels.mvi.SendScreenAction 14 | import org.bitcoindevkit.devkitwallet.presentation.viewmodels.mvi.TransactionType 15 | import org.bitcoindevkit.devkitwallet.presentation.viewmodels.mvi.TxDataBundle 16 | 17 | private const val TAG = "SendViewModel" 18 | 19 | internal class SendViewModel(private val wallet: Wallet) : ViewModel() { 20 | fun onAction(action: SendScreenAction) { 21 | when (action) { 22 | is SendScreenAction.Broadcast -> broadcast(action.txDataBundle) 23 | } 24 | } 25 | 26 | private fun broadcast(txInfo: TxDataBundle) { 27 | try { 28 | // Create, sign, and broadcast 29 | val psbt: Psbt = 30 | when (txInfo.transactionType) { 31 | TransactionType.STANDARD -> 32 | wallet.createTransaction( 33 | recipientList = txInfo.recipients, 34 | feeRate = FeeRate.fromSatPerVb(txInfo.feeRate), 35 | ) 36 | // TransactionType.SEND_ALL -> Wallet.createSendAllTransaction(recipientList[0].address, FeeRate.fromSatPerVb(feeRate), rbfEnabled, opReturnMsg) 37 | TransactionType.SEND_ALL -> throw NotImplementedError("Send all not implemented") 38 | } 39 | val isSigned = wallet.sign(psbt) 40 | if (isSigned) { 41 | val txid: String = wallet.broadcast(psbt) 42 | Log.i(TAG, "Transaction was broadcast! txid: $txid") 43 | } else { 44 | Log.i(TAG, "Transaction not signed.") 45 | } 46 | } catch (e: Throwable) { 47 | Log.i(TAG, "Broadcast error: ${e.message}") 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/src/main/java/org/bitcoindevkit/devkitwallet/presentation/viewmodels/WalletViewModel.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 thunderbiscuit and contributors. 3 | * Use of this source code is governed by the Apache 2.0 license that can be found in the ./LICENSE file. 4 | */ 5 | 6 | package org.bitcoindevkit.devkitwallet.presentation.viewmodels 7 | 8 | import android.util.Log 9 | import androidx.compose.runtime.getValue 10 | import androidx.compose.runtime.mutableStateOf 11 | import androidx.compose.runtime.setValue 12 | import androidx.lifecycle.ViewModel 13 | import androidx.lifecycle.viewModelScope 14 | import kotlinx.coroutines.Dispatchers 15 | import kotlinx.coroutines.launch 16 | import kotlinx.coroutines.withContext 17 | import org.bitcoindevkit.devkitwallet.domain.CurrencyUnit 18 | import org.bitcoindevkit.devkitwallet.domain.Wallet 19 | import org.bitcoindevkit.devkitwallet.presentation.viewmodels.mvi.WalletScreenAction 20 | import org.bitcoindevkit.devkitwallet.presentation.viewmodels.mvi.WalletScreenState 21 | 22 | private const val TAG = "WalletViewModel" 23 | 24 | class WalletViewModel( 25 | private val wallet: Wallet, 26 | ) : ViewModel() { 27 | var state: WalletScreenState by mutableStateOf(WalletScreenState()) 28 | private set 29 | 30 | init { 31 | updateClientEndpoint() 32 | } 33 | 34 | fun onAction(action: WalletScreenAction) { 35 | when (action) { 36 | WalletScreenAction.UpdateBalance -> updateBalance() 37 | WalletScreenAction.SwitchUnit -> switchUnit() 38 | } 39 | } 40 | 41 | private fun switchUnit() { 42 | state = when (state.unit) { 43 | CurrencyUnit.Bitcoin -> state.copy(unit = CurrencyUnit.Satoshi) 44 | CurrencyUnit.Satoshi -> state.copy(unit = CurrencyUnit.Bitcoin) 45 | } 46 | } 47 | 48 | private fun updateBalance() { 49 | state = state.copy(syncing = true) 50 | viewModelScope.launch(Dispatchers.IO) { 51 | wallet.sync() 52 | withContext(Dispatchers.Main) { 53 | val newBalance = wallet.getBalance() 54 | Log.i(TAG, "New balance: $newBalance") 55 | state = state.copy(balance = newBalance, syncing = false) 56 | } 57 | } 58 | } 59 | 60 | private fun updateClientEndpoint() { 61 | viewModelScope.launch(Dispatchers.IO) { 62 | val endpoint = wallet.getClientEndpoint() 63 | withContext(Dispatchers.Main) { 64 | state = state.copy(esploraEndpoint = endpoint) 65 | } 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /app/src/main/java/org/bitcoindevkit/devkitwallet/presentation/viewmodels/mvi/MviReceiveScreen.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 thunderbiscuit and contributors. 3 | * Use of this source code is governed by the Apache 2.0 license that can be found in the ./LICENSE file. 4 | */ 5 | 6 | package org.bitcoindevkit.devkitwallet.presentation.viewmodels.mvi 7 | 8 | data class ReceiveScreenState( 9 | val address: String? = null, 10 | val addressIndex: UInt? = null, 11 | ) 12 | 13 | sealed interface ReceiveScreenAction { 14 | data object UpdateAddress : ReceiveScreenAction 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/org/bitcoindevkit/devkitwallet/presentation/viewmodels/mvi/MviSendScreen.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 thunderbiscuit and contributors. 3 | * Use of this source code is governed by the Apache 2.0 license that can be found in the ./LICENSE file. 4 | */ 5 | 6 | package org.bitcoindevkit.devkitwallet.presentation.viewmodels.mvi 7 | 8 | // data class SendScreenState( 9 | // val address: String? = null, 10 | // ) 11 | 12 | sealed class SendScreenAction { 13 | data class Broadcast(val txDataBundle: TxDataBundle) : SendScreenAction() 14 | } 15 | 16 | data class TxDataBundle( 17 | val recipients: List, 18 | val feeRate: ULong, 19 | val transactionType: TransactionType, 20 | ) 21 | 22 | data class Recipient(var address: String, var amount: ULong) 23 | 24 | enum class TransactionType { 25 | STANDARD, 26 | SEND_ALL, 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/org/bitcoindevkit/devkitwallet/presentation/viewmodels/mvi/MviWalletScreen.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 thunderbiscuit and contributors. 3 | * Use of this source code is governed by the Apache 2.0 license that can be found in the ./LICENSE file. 4 | */ 5 | 6 | package org.bitcoindevkit.devkitwallet.presentation.viewmodels.mvi 7 | 8 | import org.bitcoindevkit.devkitwallet.domain.CurrencyUnit 9 | 10 | data class WalletScreenState( 11 | val balance: ULong = 0u, 12 | val syncing: Boolean = false, 13 | val unit: CurrencyUnit = CurrencyUnit.Bitcoin, 14 | val esploraEndpoint: String = "", 15 | ) 16 | 17 | sealed interface WalletScreenAction { 18 | data object UpdateBalance : WalletScreenAction 19 | 20 | data object SwitchUnit : WalletScreenAction 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/proto/wallets.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option java_package = "org.bitcoindevkit.devkitwallet.data"; 4 | option java_multiple_files = true; 5 | 6 | message UserPreferences { 7 | bool introDone = 1; 8 | repeated SingleWallet wallets = 2; 9 | } 10 | 11 | message SingleWallet { 12 | string id = 1; 13 | string name = 2; 14 | ActiveWalletNetwork network = 3; 15 | ActiveWalletScriptType scriptType = 4; 16 | string descriptor = 5; 17 | string changeDescriptor = 6; 18 | string recoveryPhrase = 7; 19 | bool fullScanCompleted = 8; 20 | } 21 | 22 | enum ActiveWalletNetwork { 23 | TESTNET = 0; 24 | SIGNET = 1; 25 | REGTEST = 2; 26 | } 27 | 28 | enum ActiveWalletScriptType { 29 | P2WPKH = 0; 30 | P2TR = 1; 31 | UNKNOWN = 2; 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bdk_logo.xml: -------------------------------------------------------------------------------- 1 | 6 | 12 | 18 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_bitcoin_logo.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_bdk_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_bdk_foreground.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 16 | 22 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_testnet_logo.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/launch_screen.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/font/ia_writer_mono_bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcoindevkit/devkit-wallet/8dcdea6c64754d94773334ec5980bb64f789f079/app/src/main/res/font/ia_writer_mono_bold.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/ia_writer_mono_bold_italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcoindevkit/devkit-wallet/8dcdea6c64754d94773334ec5980bb64f789f079/app/src/main/res/font/ia_writer_mono_bold_italic.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/ia_writer_mono_regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcoindevkit/devkit-wallet/8dcdea6c64754d94773334ec5980bb64f789f079/app/src/main/res/font/ia_writer_mono_regular.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/ia_writer_mono_regular_italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcoindevkit/devkit-wallet/8dcdea6c64754d94773334ec5980bb64f789f079/app/src/main/res/font/ia_writer_mono_regular_italic.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/ia_writer_quattro_bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcoindevkit/devkit-wallet/8dcdea6c64754d94773334ec5980bb64f789f079/app/src/main/res/font/ia_writer_quattro_bold.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/ia_writer_quattro_bold_italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcoindevkit/devkit-wallet/8dcdea6c64754d94773334ec5980bb64f789f079/app/src/main/res/font/ia_writer_quattro_bold_italic.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/ia_writer_quattro_regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcoindevkit/devkit-wallet/8dcdea6c64754d94773334ec5980bb64f789f079/app/src/main/res/font/ia_writer_quattro_regular.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/ia_writer_quattro_regular_italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcoindevkit/devkit-wallet/8dcdea6c64754d94773334ec5980bb64f789f079/app/src/main/res/font/ia_writer_quattro_regular_italic.ttf -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_bdk.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_bdk_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcoindevkit/devkit-wallet/8dcdea6c64754d94773334ec5980bb64f789f079/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_bdk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcoindevkit/devkit-wallet/8dcdea6c64754d94773334ec5980bb64f789f079/app/src/main/res/mipmap-hdpi/ic_launcher_bdk.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_bdk_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcoindevkit/devkit-wallet/8dcdea6c64754d94773334ec5980bb64f789f079/app/src/main/res/mipmap-hdpi/ic_launcher_bdk_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcoindevkit/devkit-wallet/8dcdea6c64754d94773334ec5980bb64f789f079/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcoindevkit/devkit-wallet/8dcdea6c64754d94773334ec5980bb64f789f079/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_bdk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcoindevkit/devkit-wallet/8dcdea6c64754d94773334ec5980bb64f789f079/app/src/main/res/mipmap-mdpi/ic_launcher_bdk.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_bdk_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcoindevkit/devkit-wallet/8dcdea6c64754d94773334ec5980bb64f789f079/app/src/main/res/mipmap-mdpi/ic_launcher_bdk_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcoindevkit/devkit-wallet/8dcdea6c64754d94773334ec5980bb64f789f079/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcoindevkit/devkit-wallet/8dcdea6c64754d94773334ec5980bb64f789f079/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_bdk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcoindevkit/devkit-wallet/8dcdea6c64754d94773334ec5980bb64f789f079/app/src/main/res/mipmap-xhdpi/ic_launcher_bdk.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_bdk_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcoindevkit/devkit-wallet/8dcdea6c64754d94773334ec5980bb64f789f079/app/src/main/res/mipmap-xhdpi/ic_launcher_bdk_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcoindevkit/devkit-wallet/8dcdea6c64754d94773334ec5980bb64f789f079/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcoindevkit/devkit-wallet/8dcdea6c64754d94773334ec5980bb64f789f079/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_bdk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcoindevkit/devkit-wallet/8dcdea6c64754d94773334ec5980bb64f789f079/app/src/main/res/mipmap-xxhdpi/ic_launcher_bdk.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_bdk_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcoindevkit/devkit-wallet/8dcdea6c64754d94773334ec5980bb64f789f079/app/src/main/res/mipmap-xxhdpi/ic_launcher_bdk_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcoindevkit/devkit-wallet/8dcdea6c64754d94773334ec5980bb64f789f079/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcoindevkit/devkit-wallet/8dcdea6c64754d94773334ec5980bb64f789f079/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_bdk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcoindevkit/devkit-wallet/8dcdea6c64754d94773334ec5980bb64f789f079/app/src/main/res/mipmap-xxxhdpi/ic_launcher_bdk.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_bdk_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcoindevkit/devkit-wallet/8dcdea6c64754d94773334ec5980bb64f789f079/app/src/main/res/mipmap-xxxhdpi/ic_launcher_bdk_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcoindevkit/devkit-wallet/8dcdea6c64754d94773334ec5980bb64f789f079/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #203b46 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/values/splash.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Devkit Wallet 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/test/java/org/bitcoindevkit/devkitwallet/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package org.bitcoindevkit.devkitwallet 2 | 3 | import org.junit.Assert.assertEquals 4 | import org.junit.Test 5 | 6 | class ExampleUnitTest { 7 | @Test 8 | fun addition_isCorrect() { 9 | assertEquals(4, 2 + 2) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | android.nonTransitiveRClass=false 5 | android.nonFinalResIds=false 6 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcoindevkit/devkit-wallet/8dcdea6c64754d94773334ec5980bb64f789f079/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MSYS* | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /images/screenshots.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcoindevkit/devkit-wallet/8dcdea6c64754d94773334ec5980bb64f789f079/images/screenshots.png -------------------------------------------------------------------------------- /justfile: -------------------------------------------------------------------------------- 1 | @list: 2 | just --list 3 | 4 | check: 5 | ./gradlew ktlintCheck 6 | 7 | format: 8 | ./gradlew ktlintFormat 9 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "Devkit Wallet" 2 | include("app") 3 | 4 | pluginManagement { 5 | repositories { 6 | google() 7 | gradlePluginPortal() 8 | mavenCentral() 9 | } 10 | } 11 | 12 | dependencyResolutionManagement { 13 | repositories { 14 | google() 15 | mavenCentral() 16 | 17 | // Snapshots repository 18 | maven("https://s01.oss.sonatype.org/content/repositories/snapshots/") 19 | 20 | // Local Maven (~/.m2/repository/) 21 | // mavenLocal() 22 | } 23 | } 24 | --------------------------------------------------------------------------------