Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
*.iml
.gradle
.idea
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties

# Secrets
google-services.json
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Bitkit Android
Bitkit Android Native app.

## Prerequisites
1. Download `google-services.json` to `./app` from FCM Console
2. Run Polar with the default initial network

## Dev Tips
- To communicate from host to emulator use:
`127.0.0.1` and setup port forwarding via ADB, eg. `adb forward tcp:9777 tcp:9777`
- To communicate from emulator to host use:
`10.0.2.2` instead of `localhost`
1 change: 1 addition & 0 deletions app/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
139 changes: 139 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
plugins {
id("com.android.application")
id("kotlin-android")
id("org.jetbrains.kotlin.plugin.serialization")
id("com.google.devtools.ksp")
id("com.google.dagger.hilt.android")
id("com.google.gms.google-services")
}

android {
namespace = "to.bitkit"
compileSdk = 34
ndkVersion = "26.1.10909125"

defaultConfig {
applicationId = "to.bitkit"
minSdk = 28
targetSdk = 34
versionCode = 1
versionName = "1.0"

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"

vectorDrawables {
useSupportLibrary = true
}
}
signingConfigs {
getByName("debug") {
storeFile = file("debug.keystore")
storePassword = "android"
keyAlias = "androiddebugkey"
keyPassword = "android"
}
}
buildTypes {
debug {
signingConfig = signingConfigs.getByName("debug")
// applicationIdSuffix = ".dev"
}
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
buildFeatures {
buildConfig = true
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.5.14"
}
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
}

dependencies {
// BDK & LDK
implementation("org.bitcoindevkit:bdk-android:0.30.0")
implementation(fileTree("libs") { include("*.aar") })
// implementation("org.lightningdevkit:ldk-node-jvm:0.3.0")

implementation("androidx.core:core-ktx:1.13.1")
implementation("androidx.appcompat:appcompat:1.7.0")
implementation("androidx.activity:activity-compose:1.9.0")

// Firebase
implementation(platform("com.google.firebase:firebase-bom:33.1.2"))
implementation("com.google.firebase:firebase-messaging")
implementation("com.google.firebase:firebase-analytics")

// Lifecycle
val lifecycleVersion = "2.8.3"
// ViewModel
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion")
// ViewModel utilities for Compose
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:$lifecycleVersion")
// LiveData
implementation("androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion")
// Lifecycles only (without ViewModel or LiveData)
implementation("androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion")
// Lifecycle utilities for Compose
implementation("androidx.lifecycle:lifecycle-runtime-compose:$lifecycleVersion")
// Saved state module for ViewModel
implementation("androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycleVersion")

val composeNavigationVersion = "2.7.7"
implementation("androidx.navigation:navigation-compose:$composeNavigationVersion")
androidTestImplementation("androidx.navigation:navigation-testing:$composeNavigationVersion")

// Compose Tooling for Android Studio Preview
val composeToolingVersion = "1.6.8"
implementation("androidx.compose.ui:ui-tooling-preview:$composeToolingVersion")
debugImplementation("androidx.compose.ui:ui-tooling:$composeToolingVersion")

// Dagger-Hilt
val hiltVersion = "2.51.1"
implementation("com.google.dagger:hilt-android:$hiltVersion")
ksp("com.google.dagger:hilt-android-compiler:$hiltVersion")
implementation("androidx.hilt:hilt-navigation-compose:1.2.0")

// Material Design
implementation("com.google.android.material:material:1.12.0")
implementation("androidx.compose.material3:material3:1.2.1")
implementation("androidx.compose.material:material-icons-extended:1.7.0-beta05")

// Ktor
val ktorVersion = "2.3.8"
implementation("io.ktor:ktor-client-core:$ktorVersion")
implementation("io.ktor:ktor-client-okhttp:$ktorVersion")
implementation("io.ktor:ktor-client-cio:$ktorVersion")
implementation("io.ktor:ktor-client-logging:$ktorVersion")
implementation("io.ktor:ktor-client-content-negotiation:$ktorVersion")
implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion")
implementation("ch.qos.logback:logback-classic:1.2.11")

// Testing
debugImplementation("androidx.compose.ui:ui-test-manifest:1.6.8")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.2.1")
androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1")
androidTestImplementation("androidx.compose.ui:ui-test-junit4:1.6.8")

// Other
implementation("com.google.guava:guava:31.1-android") // for ByteArray.toHex()+
}
Binary file added app/debug.keystore
Binary file not shown.
Binary file added app/libs/LDK-release.aar
Binary file not shown.
21 changes: 21 additions & 0 deletions app/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.kts.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
22 changes: 22 additions & 0 deletions app/src/androidTest/java/to/bitkit/ExampleInstrumentedTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package to.bitkit

import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith

/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals(BuildConfig.APPLICATION_ID, appContext.packageName)
}
}
12 changes: 12 additions & 0 deletions app/src/debug/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<application
android:usesCleartextTraffic="true"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.App"></application>
</manifest>
59 changes: 59 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.INTERNET" />

<application
android:name=".App"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.App">
<activity
android:name=".LauncherActivity"
android:exported="true"
android:theme="@style/Theme.App.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".ui.MainActivity"
android:exported="true"
android:theme="@style/Theme.App.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>

<service
android:name=".fcm.MessagingService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>

<!-- Set custom default icon.
This is used when no icon is set for incoming notification messages.
See https://goo.gl/l4GJaQ -->
<meta-data
android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="@drawable/ic_notification" />
<!-- Set color used with incoming notification messages.
This is used when no color is set for the incoming notification message.
See https://goo.gl/6BKBk7 -->
<meta-data
android:name="com.google.firebase.messaging.default_notification_color"
android:resource="@color/teal_200" />
<!-- Set default notifications channel for Firebase. -->
<meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id"
android:value="@string/app_notifications_channel_id" />
</application>
</manifest>
62 changes: 62 additions & 0 deletions app/src/main/java/to/bitkit/App.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package to.bitkit

import android.annotation.SuppressLint
import android.app.Activity
import android.app.Application
import android.os.Bundle
import dagger.hilt.android.HiltAndroidApp
import kotlin.reflect.typeOf

@HiltAndroidApp
internal class App : Application() {
override fun onCreate() {
super.onCreate()
currentActivity = CurrentActivity().also { registerActivityLifecycleCallbacks(it) }
}

override fun onTerminate() {
super.onTerminate()
unregisterActivityLifecycleCallbacks(currentActivity).also { currentActivity = null }
}

companion object {
@SuppressLint("StaticFieldLeak") // Should be safe given its manual memory management
internal var currentActivity: CurrentActivity? = null
}

inner class CurrentActivity : ActivityLifecycleCallbacks {
var value: Activity? = null
private set

override fun onActivityCreated(activity: Activity, bundle: Bundle?) {
this.value = activity
}

override fun onActivityStarted(activity: Activity) {
this.value = activity
}

override fun onActivityResumed(activity: Activity) {
this.value = activity
}

override fun onActivityPaused(activity: Activity) = Unit
override fun onActivityStopped(activity: Activity) = Unit
override fun onActivitySaveInstanceState(activity: Activity, bundle: Bundle) = Unit
override fun onActivityDestroyed(activity: Activity) = Unit
}
}

/**
* Returns the current activity of the application.
*
* **NEVER** store the result to a variable, further calls to such reference can lead to memory leaks.
*
* **ALWAYS** retrieve the current activity functionally, processing on the result of this function.
* */
internal inline fun <reified T> currentActivity(): T {
when (val activity = App.currentActivity?.value) {
is T -> return activity
else -> throw IllegalArgumentException("Current Activity is not '${typeOf<T>()}'")
}
}
10 changes: 10 additions & 0 deletions app/src/main/java/to/bitkit/Constants.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package to.bitkit

import org.bitcoindevkit.Network

internal const val _DEV = "_DEV"
internal const val _FCM = "_FCM"
internal const val _LDK = "_LDK"
internal const val _BDK = "_BDK"

internal val NETWORK = Network.REGTEST
Loading