Skip to content

Commit 1af0274

Browse files
authored
Merge pull request #239 from synonymdev/feat/dev-settings
Dev settings
2 parents dd64336 + 021f515 commit 1af0274

24 files changed

+697
-448
lines changed

app/src/main/java/to/bitkit/repositories/BlocktankRepo.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ class BlocktankRepo @Inject constructor(
4949
private val cacheStore: CacheStore,
5050
@Named("enablePolling") private val enablePolling: Boolean,
5151
) {
52-
private val repoScope = CoroutineScope(SupervisorJob() + bgDispatcher)
52+
private val repoScope = CoroutineScope(bgDispatcher + SupervisorJob())
5353

5454
private val _blocktankState = MutableStateFlow(BlocktankState())
5555
val blocktankState: StateFlow<BlocktankState> = _blocktankState.asStateFlow()
@@ -344,6 +344,10 @@ class BlocktankRepo @Inject constructor(
344344
return@withContext min(lspBalance, maxLspBalance)
345345
}
346346

347+
suspend fun resetState() = withContext(bgDispatcher) {
348+
_blocktankState.update { BlocktankState() }
349+
}
350+
347351
companion object {
348352
private const val TAG = "BlocktankRepo"
349353
private const val DEFAULT_CHANNEL_EXPIRY_WEEKS = 6u

app/src/main/java/to/bitkit/repositories/LightningRepo.kt

Lines changed: 4 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package to.bitkit.repositories
22

33
import android.net.Uri
44
import com.google.firebase.messaging.FirebaseMessaging
5-
import com.synonym.bitkitcore.IBtInfo
65
import com.synonym.bitkitcore.createWithdrawCallbackUrl
76
import com.synonym.bitkitcore.getLnurlInvoice
87
import kotlinx.coroutines.CoroutineDispatcher
@@ -11,7 +10,6 @@ import kotlinx.coroutines.flow.Flow
1110
import kotlinx.coroutines.flow.MutableStateFlow
1211
import kotlinx.coroutines.flow.asStateFlow
1312
import kotlinx.coroutines.flow.first
14-
import kotlinx.coroutines.flow.map
1513
import kotlinx.coroutines.flow.update
1614
import kotlinx.coroutines.tasks.await
1715
import kotlinx.coroutines.withContext
@@ -410,6 +408,7 @@ class LightningRepo @Inject constructor(
410408
val invoice = getLnurlInvoice(address, amountSatoshis)
411409
Result.success(invoice)
412410
}
411+
413412
suspend fun handleLnUrlWithdraw(
414413
k1: String,
415414
callback: String,
@@ -426,7 +425,7 @@ class LightningRepo @Inject constructor(
426425
* Extension function to remove duplicate query parameters from a URL string
427426
* Keeps the first occurrence of each parameter
428427
*/
429-
private fun String.removeDuplicateQueryParams(): String { //TODO REMOVE AFTER CORE FIX
428+
private fun String.removeDuplicateQueryParams(): String { // TODO REMOVE AFTER CORE FIX
430429
return try {
431430
val uri = Uri.parse(this)
432431
val builder = uri.buildUpon().clearQuery()
@@ -470,6 +469,7 @@ class LightningRepo @Inject constructor(
470469
* @return A `Result` with the `Txid` of sent transaction, or an error if the transaction fails
471470
* or the fee rate cannot be retrieved.
472471
*/
472+
473473
suspend fun sendOnChain(
474474
address: Address,
475475
sats: ULong,
@@ -478,8 +478,7 @@ class LightningRepo @Inject constructor(
478478
): Result<Txid> =
479479
executeWhenNodeRunning("Send on-chain") {
480480
val transactionSpeed = speed ?: settingsStore.data.first().defaultTransactionSpeed
481-
val fees = coreService.blocktank.getFees().getOrThrow()
482-
val satsPerVByte = fees.getSatsPerVByteFor(transactionSpeed)
481+
val satsPerVByte = getFeeRateForSpeed(transactionSpeed).getOrThrow().toUInt()
483482

484483
// if utxos are manually specified, use them, otherwise run auto coin select if enabled
485484
val finalUtxosToSpend = utxosToSpend ?: determineUtxosToSpend(
@@ -647,16 +646,6 @@ class LightningRepo @Inject constructor(
647646
_lightningState.value.nodeLifecycleState.isRunning() && lightningService.channels?.isNotEmpty() == true
648647

649648
// Notification handling
650-
suspend fun getFcmToken(): Result<String> = withContext(bgDispatcher) {
651-
try {
652-
val token = firebaseMessaging.token.await()
653-
Result.success(token)
654-
} catch (e: Throwable) {
655-
Logger.error("Get FCM token error", e)
656-
Result.failure(e)
657-
}
658-
}
659-
660649
suspend fun registerForNotifications(): Result<Unit> = executeWhenNodeRunning("Register for notifications") {
661650
return@executeWhenNodeRunning try {
662651
val token = firebaseMessaging.token.await()
@@ -675,28 +664,6 @@ class LightningRepo @Inject constructor(
675664
}
676665
}
677666

678-
suspend fun testNotification(): Result<Unit> = executeWhenNodeRunning("Test notification") {
679-
try {
680-
val token = firebaseMessaging.token.await()
681-
blocktankNotificationsService.testNotification(token)
682-
Result.success(Unit)
683-
} catch (e: Throwable) {
684-
Logger.error("Test notification error", e)
685-
Result.failure(e)
686-
}
687-
}
688-
689-
suspend fun getBlocktankInfo(): Result<IBtInfo> = withContext(bgDispatcher) {
690-
try {
691-
val info = coreService.blocktank.info(refresh = true)
692-
?: return@withContext Result.failure(Exception("Couldn't get info"))
693-
Result.success(info)
694-
} catch (e: Throwable) {
695-
Logger.error("Blocktank info error", e)
696-
Result.failure(e)
697-
}
698-
}
699-
700667
suspend fun bumpFeeByRbf(
701668
originalTxId: Txid,
702669
satsPerVByte: UInt,

app/src/main/java/to/bitkit/repositories/LogsRepo.kt

Lines changed: 46 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,36 @@
11
package to.bitkit.repositories
22

3+
import android.content.Context
4+
import android.net.Uri
5+
import androidx.core.content.FileProvider
6+
import dagger.hilt.android.qualifiers.ApplicationContext
37
import kotlinx.coroutines.CoroutineDispatcher
48
import kotlinx.coroutines.withContext
59
import to.bitkit.data.ChatwootHttpClient
610
import to.bitkit.di.BgDispatcher
11+
import to.bitkit.di.IoDispatcher
712
import to.bitkit.env.Env
13+
import to.bitkit.ext.fromBase64
814
import to.bitkit.ext.getEnumValueOf
15+
import to.bitkit.ext.toBase64
916
import to.bitkit.models.ChatwootMessage
1017
import to.bitkit.utils.Logger
1118
import java.io.BufferedReader
1219
import java.io.ByteArrayOutputStream
1320
import java.io.File
1421
import java.io.FileInputStream
1522
import java.io.FileReader
16-
import java.util.Base64
1723
import java.util.zip.ZipEntry
1824
import java.util.zip.ZipOutputStream
1925
import javax.inject.Inject
2026
import javax.inject.Singleton
2127

2228
@Singleton
2329
class LogsRepo @Inject constructor(
30+
@ApplicationContext private val context: Context,
2431
@BgDispatcher private val bgDispatcher: CoroutineDispatcher,
25-
private val chatwootHttpClient: ChatwootHttpClient
32+
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
33+
private val chatwootHttpClient: ChatwootHttpClient,
2634
) {
2735
suspend fun postQuestion(email: String, message: String): Result<Unit> = withContext(bgDispatcher) {
2836
return@withContext try {
@@ -46,7 +54,7 @@ class LogsRepo @Inject constructor(
4654
}
4755
}
4856

49-
/** * Lists log files sorted by newest first */
57+
/** Lists log files sorted by newest first */
5058
suspend fun getLogs(): Result<List<LogFile>> = withContext(bgDispatcher) {
5159
try {
5260
val logDir = File(Env.logDir)
@@ -102,20 +110,46 @@ class LogsRepo @Inject constructor(
102110
}
103111
}
104112

105-
/** Zips up the most recent logs and returns base64 of zip file */
113+
/** Zips and saves the most recent logs returning the content uri */
114+
suspend fun zipLogsForSharing(
115+
limit: Int = 20,
116+
source: LogSource? = null,
117+
): Result<Uri> = withContext(bgDispatcher) {
118+
zipLogs(limit, source).mapCatching { base64String ->
119+
val file = withContext(ioDispatcher) {
120+
val tempDir = context.cacheDir.resolve("logs").apply { mkdirs() }
121+
122+
val zipFileName = "bitkit_logs_${System.currentTimeMillis()}.zip"
123+
val tempFile = File(tempDir, zipFileName)
124+
125+
// Convert base64 back to bytes and write to file
126+
val zipBytes = base64String.fromBase64()
127+
tempFile.writeBytes(zipBytes)
128+
return@withContext tempFile
129+
}
130+
val contentUri = FileProvider.getUriForFile(context, Env.FILE_PROVIDER_AUTHORITY, file)
131+
if (contentUri == null) error("Failed to create content uri")
132+
133+
return@mapCatching contentUri
134+
}.onFailure {
135+
Logger.error("Error preparing logs for sharing", it)
136+
}
137+
}
138+
139+
140+
/** Zips the most recent logs and returns base64 of zip file */
106141
suspend fun zipLogs(
107142
limit: Int = 20,
108-
includeAllSources: Boolean = false
143+
source: LogSource? = null,
109144
): Result<String> = withContext(bgDispatcher) {
110145
return@withContext try {
111-
val logsResult = getLogs()
112-
if (logsResult.isFailure) {
113-
return@withContext Result.failure(logsResult.exceptionOrNull() ?: Exception("Failed to get logs"))
146+
val logsResult = getLogs().onFailure {
147+
return@withContext Result.failure(it)
114148
}
115149

116-
val allLogs = logsResult.getOrNull()?.filter { it.source != LogSource.Unknown } ?: emptyList()
117-
val logsToZip = if (includeAllSources) {
118-
allLogs.take(limit)
150+
val allLogs = logsResult.getOrDefault(emptyList()).filter { it.source != LogSource.Unknown }
151+
val logsToZip = if (source != null) {
152+
allLogs.filter { it.source == source }.take(limit)
119153
} else {
120154
// Group by source and take most recent from each
121155
allLogs.groupBy { it.source }
@@ -157,7 +191,7 @@ class LogsRepo @Inject constructor(
157191
byteArrayOut.toByteArray()
158192
}
159193

160-
return Base64.getEncoder().encodeToString(zipBytes)
194+
return zipBytes.toBase64()
161195
}
162196

163197
private companion object {

app/src/main/java/to/bitkit/services/BlocktankNotificationsService.kt

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ class BlocktankNotificationsService @Inject constructor(
2323
private val keychain: Keychain,
2424
private val crypto: Crypto,
2525
) {
26-
2726
suspend fun registerDevice(deviceToken: String) = withContext(bgDispatcher) {
2827
val nodeId = lightningService.nodeId ?: throw ServiceError.NodeNotStarted
2928

@@ -65,17 +64,4 @@ class BlocktankNotificationsService @Inject constructor(
6564

6665
Logger.info("Device registered for notifications")
6766
}
68-
69-
suspend fun testNotification(deviceToken: String) = withContext(bgDispatcher) {
70-
Logger.debug("Sending test notification to self…")
71-
72-
ServiceQueue.CORE.background {
73-
com.synonym.bitkitcore.testNotification(
74-
deviceToken = deviceToken,
75-
secretMessage = "hello",
76-
notificationType = "incomingHtlc",
77-
customUrl = Env.blocktankPushNotificationServer,
78-
)
79-
}
80-
}
8167
}

app/src/main/java/to/bitkit/services/LightningService.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import org.lightningdevkit.ldknode.BuildException
2020
import org.lightningdevkit.ldknode.Builder
2121
import org.lightningdevkit.ldknode.ChannelDetails
2222
import org.lightningdevkit.ldknode.CoinSelectionAlgorithm
23+
import org.lightningdevkit.ldknode.Config
2324
import org.lightningdevkit.ldknode.ElectrumSyncConfig
2425
import org.lightningdevkit.ldknode.Event
2526
import org.lightningdevkit.ldknode.FeeRate
@@ -652,6 +653,7 @@ class LightningService @Inject constructor(
652653
val nodeId: String? get() = node?.nodeId()
653654
val balances: BalanceDetails? get() = node?.listBalances()
654655
val status: NodeStatus? get() = node?.status()
656+
val config: Config? get() = node?.config()
655657
val peers: List<LnPeer>? get() = node?.listPeers()?.map { it.toLnPeer() }
656658
val channels: List<ChannelDetails>? get() = node?.listChannels()
657659
val payments: List<PaymentDetails>? get() = node?.listPayments()

app/src/main/java/to/bitkit/ui/ContentView.kt

Lines changed: 14 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ import to.bitkit.ui.components.SheetHost
3939
import to.bitkit.ui.onboarding.InitializingWalletView
4040
import to.bitkit.ui.onboarding.WalletInitResult
4141
import to.bitkit.ui.onboarding.WalletInitResultView
42-
import to.bitkit.ui.screens.DevSettingsScreen
42+
import to.bitkit.ui.screens.settings.DevSettingsScreen
43+
import to.bitkit.ui.screens.settings.FeeSettingsScreen
4344
import to.bitkit.ui.screens.profile.CreateProfileScreen
4445
import to.bitkit.ui.screens.profile.ProfileIntroScreen
4546
import to.bitkit.ui.screens.scanner.QrScanningScreen
@@ -424,8 +425,6 @@ private fun RootNavHost(
424425
orderDetailSettings(navController)
425426
cjitDetailSettings(navController)
426427
lightningConnections(navController)
427-
devSettings(walletViewModel, navController)
428-
regtestSettings(navController)
429428
activityItem(activityListViewModel, navController)
430429
qrScanner(appViewModel, navController)
431430
authCheck(navController)
@@ -673,6 +672,15 @@ private fun NavGraphBuilder.settings(
673672
onClose = { navController.navigateToHome() },
674673
)
675674
}
675+
composableWithDefaultTransitions<Routes.DevSettings> {
676+
DevSettingsScreen(navController)
677+
}
678+
composableWithDefaultTransitions<Routes.FeeSettings> {
679+
FeeSettingsScreen(navController)
680+
}
681+
composableWithDefaultTransitions<Routes.RegtestSettings> {
682+
BlocktankRegtestScreen(navController)
683+
}
676684
}
677685

678686
private fun NavGraphBuilder.profile(
@@ -911,24 +919,6 @@ private fun NavGraphBuilder.lightningConnections(
911919
}
912920
}
913921

914-
private fun NavGraphBuilder.devSettings(
915-
viewModel: WalletViewModel,
916-
navController: NavHostController,
917-
) {
918-
composableWithDefaultTransitions<Routes.DevSettings> {
919-
DevSettingsScreen(viewModel, navController)
920-
}
921-
}
922-
923-
private fun NavGraphBuilder.regtestSettings(
924-
navController: NavHostController,
925-
) {
926-
composableWithDefaultTransitions<Routes.RegtestSettings> {
927-
val viewModel = hiltViewModel<BlocktankRegtestViewModel>()
928-
BlocktankRegtestScreen(viewModel, navController)
929-
}
930-
}
931-
932922
private fun NavGraphBuilder.activityItem(
933923
activityListViewModel: ActivityListViewModel,
934924
navController: NavHostController,
@@ -1489,6 +1479,9 @@ sealed interface Routes {
14891479
@Serializable
14901480
data object DevSettings : Routes
14911481

1482+
@Serializable
1483+
data object FeeSettings : Routes
1484+
14921485
@Serializable
14931486
data object RegtestSettings : Routes
14941487

app/src/main/java/to/bitkit/ui/components/InfoScreenContent.kt

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
88
import androidx.compose.foundation.layout.height
99
import androidx.compose.foundation.layout.padding
1010
import androidx.compose.foundation.layout.size
11-
import androidx.compose.material3.CenterAlignedTopAppBar
12-
import androidx.compose.material3.ExperimentalMaterial3Api
1311
import androidx.compose.runtime.Composable
1412
import androidx.compose.ui.Alignment
1513
import androidx.compose.ui.Modifier
@@ -21,14 +19,14 @@ import androidx.compose.ui.text.AnnotatedString
2119
import androidx.compose.ui.tooling.preview.Preview
2220
import androidx.compose.ui.unit.dp
2321
import to.bitkit.R
22+
import to.bitkit.ui.scaffold.AppTopBar
2423
import to.bitkit.ui.scaffold.CloseNavIcon
2524
import to.bitkit.ui.scaffold.ScreenColumn
2625
import to.bitkit.ui.theme.AppThemeSurface
2726
import to.bitkit.ui.theme.Colors
2827
import to.bitkit.ui.utils.withAccent
2928
import to.bitkit.ui.utils.withAccentBoldBright
3029

31-
@OptIn(ExperimentalMaterial3Api::class)
3230
@Composable
3331
fun InfoScreenContent(
3432
navTitle: String,
@@ -41,8 +39,9 @@ fun InfoScreenContent(
4139
onCloseClick: () -> Unit,
4240
) {
4341
ScreenColumn {
44-
CenterAlignedTopAppBar(
45-
title = { Title(text = navTitle) },
42+
AppTopBar(
43+
titleText = navTitle,
44+
onBackClick = null,
4645
actions = {
4746
if (showCloseButton) {
4847
CloseNavIcon(onCloseClick)
@@ -86,7 +85,7 @@ fun InfoScreenContent(
8685
}
8786
}
8887

89-
@Preview(showSystemUi = true, showBackground = true)
88+
@Preview(showSystemUi = true)
9089
@Composable
9190
private fun Preview() {
9291
AppThemeSurface {

0 commit comments

Comments
 (0)