Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
7cba039
refactor: Cleanup debug Manifest
ovitrif Aug 22, 2024
62f36cc
wip: Scaffold UI state
ovitrif Sep 2, 2024
6794a27
refactor: Rename to WalletViewModel
ovitrif Sep 2, 2024
2f3f3b2
feat: Add LCE (Loading Content Error) UiState flows and UI
ovitrif Sep 2, 2024
83205a6
chore: Update dependencies
ovitrif Sep 11, 2024
daf6d61
chore: Migrate to Kotlin 2 and Use Compose Compiler
ovitrif Sep 11, 2024
8f0c65b
fix: ellipsisVisualTransformation on Kotlin 2 Compose
ovitrif Sep 11, 2024
fdbc206
chore: Remove NDK version spec
ovitrif Sep 11, 2024
a0e5b1c
feat: Declare error types
ovitrif Sep 11, 2024
4453d8c
refactor: Move Constants to env package
ovitrif Sep 11, 2024
e27610d
chore: Print encrypted keychain value in ui test
ovitrif Sep 12, 2024
6ed233c
feat: Use Keychain typed errors
ovitrif Sep 12, 2024
9fc95f9
feat: Use typed errors in Lightning service
ovitrif Sep 15, 2024
46c0474
feat: Use typed errors in Migration service
ovitrif Sep 15, 2024
0cfed89
feat: Use typed errors in OnChain service
ovitrif Sep 15, 2024
5f05445
feat: Use LdkError
ovitrif Sep 15, 2024
1d9e9f7
feat: Generate keypair for BT push notifications
ovitrif Sep 16, 2024
ee323a9
feat: Use typed errors in Blocktank service
ovitrif Sep 16, 2024
599d681
refactor: Env & const's reorg, cleanup, alignment with iOS
ovitrif Sep 16, 2024
a17842b
feat: Unit testing dependencies
ovitrif Sep 16, 2024
9cf90ac
feat: Viewmodel unit test samples
ovitrif Sep 16, 2024
1bda1da
refactor: Remove EsploraApi
ovitrif Sep 16, 2024
1781c08
feat: Keychain en/decrypt to bytes
ovitrif Sep 16, 2024
15b7767
refactor: Cleanup test
ovitrif Sep 16, 2024
63b4247
refactor: Extract Tags
ovitrif Sep 17, 2024
2ab7c8a
refactor: Rename BlocktankApi to BlocktankClient
ovitrif Sep 18, 2024
47425d8
refactor: Cleanup LnPeer
ovitrif Sep 18, 2024
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
52 changes: 30 additions & 22 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import org.jetbrains.kotlin.compose.compiler.gradle.ComposeFeatureFlag

plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.compose.compiler)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.serialization)
alias(libs.plugins.ksp)
Expand All @@ -10,7 +13,6 @@ plugins {
android {
namespace = "to.bitkit"
compileSdk = 34
ndkVersion = "26.1.10909125" // probably required by LDK bindings? - safer to keep it for now.
defaultConfig {
applicationId = "to.bitkit"
minSdk = 28
Expand Down Expand Up @@ -54,10 +56,6 @@ android {
buildConfig = true
compose = true
}
composeOptions {
// https://developer.android.com/jetpack/androidx/releases/compose-kotlin#pre-release_kotlin_compatibility
kotlinCompilerExtensionVersion = "1.5.14"
}
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
Expand All @@ -66,11 +64,18 @@ android {
@Suppress("UnstableApiUsage")
testOptions {
unitTests {
// isReturnDefaultValues = true // mockito
// isIncludeAndroidResources = true // robolectric
isReturnDefaultValues = true // mockito
isIncludeAndroidResources = true // robolectric
}
}
}
composeCompiler {
featureFlags = setOf(
ComposeFeatureFlag.StrongSkipping.disabled(),
ComposeFeatureFlag.OptimizeNonSkippingGroups,
)
reportsDestination = layout.buildDirectory.dir("compose_compiler")
}
dependencies {
implementation(fileTree("libs") { include("*.aar") })
implementation(platform(libs.kotlin.bom))
Expand All @@ -81,6 +86,7 @@ dependencies {
implementation(libs.datastore.preferences)
// BDK + LDK
implementation(libs.bdk.android)
implementation(libs.bitcoinj.core)
implementation(libs.ldk.node.android)
// Firebase
implementation(platform(libs.firebase.bom))
Expand All @@ -96,12 +102,12 @@ dependencies {
// Compose
implementation(platform(libs.compose.bom))
androidTestImplementation(platform(libs.compose.bom))
implementation(libs.material3)
implementation(libs.material.icons.extended)
implementation(libs.ui.tooling.preview)
debugImplementation(libs.ui.tooling)
debugImplementation(libs.ui.test.manifest)
androidTestImplementation(libs.ui.test.junit4)
implementation(libs.compose.material3)
implementation(libs.compose.material.icons.extended)
implementation(libs.compose.ui.tooling.preview)
debugImplementation(libs.compose.ui.tooling)
debugImplementation(libs.compose.ui.test.manifest)
androidTestImplementation(libs.compose.ui.test.junit4)
// Compose Navigation
implementation(libs.navigation.compose)
androidTestImplementation(libs.navigation.testing)
Expand All @@ -127,17 +133,19 @@ dependencies {
ksp(libs.room.compiler)
testImplementation(libs.room.testing)
// Test + Debug
androidTestImplementation(libs.espresso.core)
androidTestImplementation(libs.junit.ext)
androidTestImplementation(kotlin("test"))
androidTestImplementation(libs.test.core)
androidTestImplementation(libs.test.coroutines)
androidTestImplementation(libs.test.espresso.core)
androidTestImplementation(libs.test.junit.ext)
testImplementation(kotlin("test"))
testImplementation(libs.junit.junit)
// testImplementation("androidx.test:core:1.6.1")
// testImplementation("org.mockito:mockito-core:5.12.0")
// testImplementation("org.mockito.kotlin:mockito-kotlin:5.4.0")
// testImplementation("org.robolectric:robolectric:4.13")
// Other
implementation(libs.guava) // for ByteArray.toHex()+
testImplementation(libs.test.core)
testImplementation(libs.test.coroutines)
testImplementation(libs.test.junit)
testImplementation(libs.test.junit.ext)
testImplementation(libs.test.mockito.kotlin)
testImplementation(libs.test.robolectric)
testImplementation(libs.test.turbine)
}
ksp {
// cool but strict: https://developer.android.com/jetpack/androidx/releases/room#2.6.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import android.content.Context
import androidx.room.Room
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
Expand All @@ -14,20 +13,20 @@ import org.junit.Test
import org.junit.runner.RunWith
import to.bitkit.data.AppDb
import to.bitkit.data.entities.ConfigEntity
import to.bitkit.test.BaseTest
import to.bitkit.shared.KeychainError
import to.bitkit.test.BaseAndroidTest
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertNull
import kotlin.test.assertTrue

@RunWith(AndroidJUnit4::class)
@OptIn(ExperimentalCoroutinesApi::class)
class KeychainStoreTest : BaseTest() {
class KeychainTest : BaseAndroidTest() {

private val appContext by lazy { ApplicationProvider.getApplicationContext<Context>() }
private lateinit var db: AppDb

private lateinit var sut: KeychainStore
private lateinit var sut: Keychain

@Before
fun setUp() {
Expand All @@ -42,7 +41,7 @@ class KeychainStoreTest : BaseTest() {
}
}

sut = KeychainStore(
sut = Keychain(
db,
appContext,
testDispatcher,
Expand Down Expand Up @@ -70,7 +69,7 @@ class KeychainStoreTest : BaseTest() {
val key = "key"
sut.saveString(key, "value1")

assertFailsWith<IllegalArgumentException> { sut.saveString(key, "value2") }
assertFailsWith<KeychainError.FailedToSaveAlreadyExists> { sut.saveString(key, "value2") }
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class LdkMigrationTest {
runBlocking { start() }

assertTrue { nodeId == "02cd08b7b375e4263849121f9f0ffb2732a0b88d0fb74487575ac539b374f45a55" }
assertTrue { channels.isNotEmpty() }
assertTrue { channels?.isNotEmpty() == true }

runBlocking { stop() }
}
Expand Down
20 changes: 20 additions & 0 deletions app/src/androidTest/java/to/bitkit/test/BaseAndroidTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package to.bitkit.test

import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Rule

@OptIn(ExperimentalCoroutinesApi::class)
abstract class BaseAndroidTest(
testDispatcher: TestDispatcher = UnconfinedTestDispatcher()
) {
@get:Rule
val coroutinesTestRule = MainDispatcherRule(testDispatcher)

protected val testDispatcher get() = coroutinesTestRule.testDispatcher

protected fun test(block: suspend TestScope.() -> Unit) = runTest(testDispatcher) { block() }
}
12 changes: 4 additions & 8 deletions app/src/debug/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<manifest xmlns:tools="http://schemas.android.com/tools"
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>
tools:ignore="MissingApplicationIcon" />
</manifest>
3 changes: 2 additions & 1 deletion app/src/main/java/to/bitkit/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ import android.os.Bundle
import androidx.hilt.work.HiltWorkerFactory
import androidx.work.Configuration
import dagger.hilt.android.HiltAndroidApp
import to.bitkit.env.Env
import javax.inject.Inject
import kotlin.reflect.typeOf

@HiltAndroidApp
internal class App : Application(), Configuration.Provider {
internal open class App : Application(), Configuration.Provider {
@Inject
lateinit var workerFactory: HiltWorkerFactory

Expand Down
113 changes: 0 additions & 113 deletions app/src/main/java/to/bitkit/Constants.kt

This file was deleted.

2 changes: 1 addition & 1 deletion app/src/main/java/to/bitkit/async/ServiceQueue.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import kotlinx.coroutines.ExecutorCoroutineDispatcher
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.withContext
import to.bitkit.Tag.APP
import to.bitkit.env.Tag.APP
import to.bitkit.ext.callerName
import to.bitkit.shared.measured
import java.util.concurrent.Executors
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/java/to/bitkit/data/AppDb.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ import androidx.work.WorkerParameters
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import to.bitkit.BuildConfig
import to.bitkit.Env
import to.bitkit.data.entities.ConfigEntity
import to.bitkit.env.Env

@Database(
entities = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,32 @@ import io.ktor.client.request.post
import io.ktor.client.request.setBody
import io.ktor.client.statement.HttpResponse
import kotlinx.serialization.Serializable
import to.bitkit.shared.BlocktankError
import javax.inject.Inject
import javax.inject.Singleton

interface LspApi {
suspend fun registerDeviceForNotifications(payload: RegisterDeviceRequest)
suspend fun testNotification(deviceToken: String, payload: TestNotificationRequest)
}

class BlocktankApi @Inject constructor(
@Singleton
class BlocktankClient @Inject constructor(
private val client: HttpClient,
) : LspApi {
) {
private val baseUrl = "https://api.stag.blocktank.to"
private val notificationsApi = "$baseUrl/notifications/api/device"

override suspend fun registerDeviceForNotifications(payload: RegisterDeviceRequest) {
suspend fun registerDeviceForNotifications(payload: RegisterDeviceRequest) {
post(notificationsApi, payload)
}

override suspend fun testNotification(deviceToken: String, payload: TestNotificationRequest) {
suspend fun testNotification(deviceToken: String, payload: TestNotificationRequest) {
post("$notificationsApi/$deviceToken/test-notification", payload)
}

private suspend inline fun <reified T> post(url: String, payload: T) = client.post(url) { setBody(payload) }
private suspend inline fun <reified T> post(url: String, payload: T): HttpResponse {
val response = client.post(url) { setBody(payload) }
return when (val statusCode = response.status.value) {
!in 200..299 -> throw BlocktankError.InvalidResponse(statusCode)
else -> response
}
}
}

@Serializable
Expand Down
Loading