Skip to content

Commit 3f0d158

Browse files
committed
Merge branch 'master' into mainnet
2 parents 7e7c187 + e87a299 commit 3f0d158

File tree

57 files changed

+1313
-721
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+1313
-721
lines changed

phoenix-android/build.gradle.kts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ android {
2424
applicationId = "fr.acinq.phoenix.mainnet"
2525
minSdk = 26
2626
targetSdk = 33
27-
versionCode = 68
28-
versionName = "2.0.13"
27+
versionCode = 69
28+
versionName = gitCommitHash()
2929
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
3030
resourceConfigurations.addAll(listOf("en", "fr", "de", "es", "b+es+419", "cs", "pt-rBR"))
3131
}

phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/Ambients.kt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import androidx.compose.runtime.compositionLocalOf
2222
import androidx.compose.runtime.staticCompositionLocalOf
2323
import androidx.compose.ui.platform.LocalContext
2424
import androidx.navigation.NavController
25-
import fr.acinq.lightning.utils.ServerAddress
2625
import fr.acinq.phoenix.PhoenixBusiness
2726
import fr.acinq.phoenix.android.utils.UserTheme
2827
import fr.acinq.phoenix.android.utils.datastore.InternalDataRepository
@@ -40,7 +39,6 @@ val LocalBitcoinUnit = compositionLocalOf { BitcoinUnit.Sat }
4039
val LocalFiatCurrency = compositionLocalOf { FiatCurrency.USD }
4140
val LocalExchangeRates = compositionLocalOf<List<ExchangeRate>> { listOf() }
4241
val LocalShowInFiat = compositionLocalOf { false }
43-
val LocalElectrumServer = compositionLocalOf<ServerAddress?> { null }
4442
val isDarkTheme: Boolean
4543
@Composable
4644
get() = LocalTheme.current.let { it == UserTheme.DARK || (it == UserTheme.SYSTEM && isSystemInDarkTheme()) }

phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/AppView.kt

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package fr.acinq.phoenix.android
1818

1919
import android.Manifest
20+
import android.content.Intent
2021
import android.net.Uri
2122
import android.os.Build
2223
import android.text.format.DateUtils
@@ -25,6 +26,7 @@ import androidx.compose.foundation.layout.Arrangement
2526
import androidx.compose.foundation.layout.Column
2627
import androidx.compose.foundation.layout.Spacer
2728
import androidx.compose.foundation.layout.fillMaxHeight
29+
import androidx.compose.foundation.layout.fillMaxSize
2830
import androidx.compose.foundation.layout.fillMaxWidth
2931
import androidx.compose.foundation.layout.height
3032
import androidx.compose.foundation.layout.padding
@@ -36,6 +38,7 @@ import androidx.compose.runtime.LaunchedEffect
3638
import androidx.compose.runtime.collectAsState
3739
import androidx.compose.runtime.getValue
3840
import androidx.compose.runtime.livedata.observeAsState
41+
import androidx.compose.ui.Alignment
3942
import androidx.compose.ui.Modifier
4043
import androidx.compose.ui.platform.LocalContext
4144
import androidx.compose.ui.res.stringResource
@@ -53,6 +56,7 @@ import androidx.navigation.navOptions
5356
import com.google.accompanist.permissions.ExperimentalPermissionsApi
5457
import com.google.accompanist.permissions.isGranted
5558
import com.google.accompanist.permissions.rememberPermissionState
59+
import fr.acinq.phoenix.PhoenixBusiness
5660
import fr.acinq.phoenix.android.components.Button
5761
import fr.acinq.phoenix.android.components.Dialog
5862
import fr.acinq.phoenix.android.components.openLink
@@ -94,45 +98,54 @@ import kotlinx.coroutines.flow.first
9498
import org.kodein.memory.util.currentTimestampMillis
9599

96100

101+
@Composable
102+
fun LoadingAppView() {
103+
Column(
104+
modifier = Modifier.fillMaxSize(),
105+
verticalArrangement = Arrangement.Center,
106+
horizontalAlignment = Alignment.CenterHorizontally,
107+
) {
108+
Text(text = "Initializing...")
109+
}
110+
}
111+
97112
@Composable
98113
fun AppView(
114+
business: PhoenixBusiness,
99115
appVM: AppViewModel,
100116
navController: NavHostController,
101117
) {
102118
val log = logger("Navigation")
103119
log.debug { "init app view composition" }
104120

105-
val fiatRates = application.business.currencyManager.ratesFlow.collectAsState(listOf())
106121
val context = LocalContext.current
107122
val isAmountInFiat = UserPrefs.getIsAmountInFiat(context).collectAsState(false)
108123
val fiatCurrency = UserPrefs.getFiatCurrency(context).collectAsState(initial = FiatCurrency.USD)
109124
val bitcoinUnit = UserPrefs.getBitcoinUnit(context).collectAsState(initial = BitcoinUnit.Sat)
110-
val electrumServer = UserPrefs.getElectrumServer(context).collectAsState(initial = null)
111-
val business = application.business
125+
val fiatRates by business.currencyManager.ratesFlow.collectAsState(emptyList())
112126

113127
CompositionLocalProvider(
114128
LocalBusiness provides business,
115129
LocalControllerFactory provides business.controllers,
116130
LocalNavController provides navController,
117-
LocalExchangeRates provides fiatRates.value,
131+
LocalExchangeRates provides fiatRates,
118132
LocalBitcoinUnit provides bitcoinUnit.value,
119133
LocalFiatCurrency provides fiatCurrency.value,
120134
LocalShowInFiat provides isAmountInFiat.value,
121-
LocalElectrumServer provides electrumServer.value,
122135
) {
123-
124136
// we keep a view model storing payments so that we don't have to fetch them every time
125-
val paymentsViewModel: PaymentsViewModel = viewModel(
137+
val paymentsViewModel = viewModel<PaymentsViewModel>(
126138
factory = PaymentsViewModel.Factory(
127139
connectionsFlow = business.connectionsManager.connections,
128140
paymentsManager = business.paymentsManager,
129141
)
130142
)
131-
132-
val noticesViewModel = viewModel<NoticesViewModel>(factory = NoticesViewModel.Factory(
133-
appConfigurationManager = business.appConfigurationManager,
134-
peerManager = business.peerManager
135-
))
143+
val noticesViewModel = viewModel<NoticesViewModel>(
144+
factory = NoticesViewModel.Factory(
145+
appConfigurationManager = business.appConfigurationManager,
146+
peerManager = business.peerManager
147+
)
148+
)
136149
MonitorNotices(vm = noticesViewModel)
137150

138151
val legacyAppStatus = LegacyPrefsDatastore.getLegacyAppStatus(context).collectAsState(null)
@@ -148,6 +161,7 @@ fun AppView(
148161
.fillMaxWidth()
149162
.fillMaxHeight()
150163
) {
164+
151165
NavHost(navController = navController, startDestination = "${Screen.Startup.route}?next={next}") {
152166
composable(
153167
route = "${Screen.Startup.route}?next={next}",
@@ -224,10 +238,10 @@ fun AppView(
224238
navDeepLink { uriPattern = "scanview:{data}" },
225239
)
226240
) {
227-
val input = it.arguments?.getString("data")
228-
RequireStarted(walletState, nextUri = "scanview:$input") {
241+
val intent = it.arguments?.getParcelable<Intent>(NavController.KEY_DEEP_LINK_INTENT)
242+
RequireStarted(walletState, nextUri = "scanview:${intent?.data?.toString()}") {
229243
ScanDataView(
230-
input = input,
244+
input = intent?.data?.toString()?.substringAfter("scanview:"),
231245
onBackClick = { popToHome(navController) },
232246
onAuthSchemeInfoClick = { navController.navigate("${Screen.PaymentSettings.route}/true") }
233247
)
@@ -384,7 +398,16 @@ fun AppView(
384398
)
385399
}
386400
composable(Screen.ResetWallet.route) {
387-
ResetWallet(onBackClick = { navController.popBackStack() })
401+
appVM.service?.let { nodeService ->
402+
val application = application
403+
ResetWallet(
404+
onShutdownBusiness = application::shutdownBusiness,
405+
onShutdownService = nodeService::shutdown,
406+
onPrefsClear = application::clearPreferences,
407+
onBusinessReset = application::resetBusiness,
408+
onBackClick = { navController.popBackStack() }
409+
)
410+
}
388411
}
389412
}
390413
}

phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/MainActivity.kt

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -22,21 +22,24 @@ import android.os.Bundle
2222
import androidx.activity.compose.setContent
2323
import androidx.activity.viewModels
2424
import androidx.appcompat.app.AppCompatActivity
25+
import androidx.compose.runtime.collectAsState
2526
import androidx.core.net.toUri
2627
import androidx.lifecycle.lifecycleScope
2728
import androidx.navigation.*
2829
import androidx.navigation.compose.rememberNavController
2930
import fr.acinq.lightning.io.PhoenixAndroidLegacyInfoEvent
3031
import fr.acinq.lightning.utils.Connection
31-
import fr.acinq.phoenix.PhoenixBusiness
3232
import fr.acinq.phoenix.android.services.NodeService
3333
import fr.acinq.phoenix.android.utils.LegacyMigrationHelper
3434
import fr.acinq.phoenix.android.utils.PhoenixAndroidTheme
3535
import fr.acinq.phoenix.legacy.utils.*
3636
import fr.acinq.phoenix.managers.AppConnectionsDaemon
37+
import kotlinx.coroutines.ExperimentalCoroutinesApi
3738
import kotlinx.coroutines.delay
3839
import kotlinx.coroutines.flow.filterNotNull
3940
import kotlinx.coroutines.flow.first
41+
import kotlinx.coroutines.flow.flattenMerge
42+
import kotlinx.coroutines.flow.map
4043
import kotlinx.coroutines.launch
4144
import org.slf4j.Logger
4245
import org.slf4j.LoggerFactory
@@ -49,6 +52,7 @@ class MainActivity : AppCompatActivity() {
4952

5053
private var navController: NavHostController? = null
5154

55+
@OptIn(ExperimentalCoroutinesApi::class)
5256
override fun onCreate(savedInstanceState: Bundle?) {
5357
super.onCreate(savedInstanceState)
5458
appViewModel.serviceState.observe(this) {
@@ -78,7 +82,7 @@ class MainActivity : AppCompatActivity() {
7882
// listen to legacy channels events on the peer's event bus
7983
lifecycleScope.launch {
8084
val application = (application as PhoenixApplication)
81-
application.business.peerManager.getPeer().eventsFlow.collect {
85+
application.business.filterNotNull().map { it.peerManager.getPeer().eventsFlow }.flattenMerge().collect {
8286
if (it is PhoenixAndroidLegacyInfoEvent) {
8387
if (it.info.hasChannels) {
8488
log.info("legacy channels have been found")
@@ -95,9 +99,14 @@ class MainActivity : AppCompatActivity() {
9599

96100
setContent {
97101
navController = rememberNavController()
102+
val businessState = (application as PhoenixApplication).business.collectAsState()
103+
98104
navController?.let {
99105
PhoenixAndroidTheme(it) {
100-
AppView(appViewModel, it)
106+
when (val business = businessState.value) {
107+
null -> LoadingAppView()
108+
else -> AppView(business, appViewModel, it)
109+
}
101110
}
102111
}
103112
}
@@ -108,8 +117,9 @@ class MainActivity : AppCompatActivity() {
108117
// force the intent flag to single top, in order to avoid [handleDeepLink] finish the current activity.
109118
// this would otherwise clear the app view model, i.e. loose the state which virtually reboots the app
110119
// TODO: look into detaching the app state from the activity
111-
log.info("receive new_intent=$intent")
120+
log.info("receive new_intent with data=${intent?.data}")
112121
intent?.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
122+
113123
intent?.fixUri()
114124
this.navController?.handleDeepLink(intent)
115125
}
@@ -123,30 +133,29 @@ class MainActivity : AppCompatActivity() {
123133

124134
override fun onResume() {
125135
super.onResume()
126-
val business = (application as? PhoenixApplication)?.business
127-
val daemon = business?.appConnectionsDaemon
128-
if (daemon != null) {
129-
tryReconnect(business, daemon)
130-
}
136+
tryReconnect()
131137
}
132138

133-
private fun tryReconnect(business: PhoenixBusiness, daemon: AppConnectionsDaemon) {
139+
private fun tryReconnect() {
134140
lifecycleScope.launch {
135141
val isDataMigrationExpected = LegacyPrefsDatastore.getDataMigrationExpected(applicationContext).filterNotNull().first()
136142
if (isDataMigrationExpected) {
137143
log.debug("ignoring tryReconnect when data migration is in progress")
138144
} else {
139-
val connections = business.connectionsManager.connections.value
140-
if (connections.electrum !is Connection.ESTABLISHED) {
141-
lifecycleScope.launch {
142-
log.info("resuming app with electrum conn=${connections.electrum}, forcing reconnection...")
143-
daemon.forceReconnect(AppConnectionsDaemon.ControlTarget.Electrum)
145+
(application as? PhoenixApplication)?.business?.value?.let { business ->
146+
val daemon = business.appConnectionsDaemon ?: return@launch
147+
val connections = business.connectionsManager.connections.value
148+
if (connections.electrum !is Connection.ESTABLISHED) {
149+
lifecycleScope.launch {
150+
log.info("resuming app with electrum conn=${connections.electrum}, forcing reconnection...")
151+
daemon.forceReconnect(AppConnectionsDaemon.ControlTarget.Electrum)
152+
}
144153
}
145-
}
146-
if (connections.peer !is Connection.ESTABLISHED) {
147-
lifecycleScope.launch {
148-
log.info("resuming app with peer conn=${connections.peer}, forcing reconnection...")
149-
daemon.forceReconnect(AppConnectionsDaemon.ControlTarget.Peer)
154+
if (connections.peer !is Connection.ESTABLISHED) {
155+
lifecycleScope.launch {
156+
log.info("resuming app with peer conn=${connections.peer}, forcing reconnection...")
157+
daemon.forceReconnect(AppConnectionsDaemon.ControlTarget.Peer)
158+
}
150159
}
151160
}
152161
}

phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/PhoenixApplication.kt

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,25 @@ import fr.acinq.phoenix.PhoenixBusiness
2121
import fr.acinq.phoenix.android.utils.Logging
2222
import fr.acinq.phoenix.android.utils.SystemNotificationHelper
2323
import fr.acinq.phoenix.android.utils.datastore.InternalDataRepository
24+
import fr.acinq.phoenix.android.utils.datastore.UserPrefs
2425
import fr.acinq.phoenix.legacy.AppContext
2526
import fr.acinq.phoenix.legacy.internalData
27+
import fr.acinq.phoenix.legacy.utils.LegacyPrefsDatastore
28+
import fr.acinq.phoenix.managers.AppConnectionsDaemon
2629
import fr.acinq.phoenix.utils.PlatformContext
30+
import kotlinx.coroutines.flow.MutableStateFlow
31+
import kotlinx.coroutines.flow.asStateFlow
2732

2833
class PhoenixApplication : AppContext() {
29-
val business by lazy { PhoenixBusiness(PlatformContext(this)) }
34+
35+
private val _business = MutableStateFlow<PhoenixBusiness?>(null) // by lazy { MutableStateFlow(PhoenixBusiness(PlatformContext(applicationContext))) }
36+
val business = _business.asStateFlow()
37+
3038
lateinit var internalDataRepository: InternalDataRepository
3139

3240
override fun onCreate() {
3341
super.onCreate()
42+
_business.value = PhoenixBusiness(PlatformContext(applicationContext))
3443
Logging.setupLogger(applicationContext)
3544
SystemNotificationHelper.registerNotificationChannels(applicationContext)
3645
internalDataRepository = InternalDataRepository(applicationContext.internalData)
@@ -41,4 +50,20 @@ class PhoenixApplication : AppContext() {
4150
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP)
4251
})
4352
}
53+
54+
fun shutdownBusiness() {
55+
log.info("business=${business.value}")
56+
business.value?.appConnectionsDaemon?.incrementDisconnectCount(AppConnectionsDaemon.ControlTarget.All)
57+
business.value?.stop()
58+
}
59+
fun resetBusiness() {
60+
_business.value = PhoenixBusiness(PlatformContext(this))
61+
log.info("business=$business")
62+
}
63+
64+
suspend fun clearPreferences() {
65+
internalDataRepository.clear()
66+
UserPrefs.clear(applicationContext)
67+
LegacyPrefsDatastore.clear(applicationContext)
68+
}
4469
}

phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/home/ConnectionDialog.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ import fr.acinq.phoenix.managers.Connections
5353

5454
@Composable
5555
fun ConnectionDialog(
56-
connections: Connections?,
56+
connections: Connections,
5757
electrumBlockheight: Int,
5858
onClose: () -> Unit,
5959
onTorClick: () -> Unit,
@@ -63,7 +63,7 @@ fun ConnectionDialog(
6363

6464
Dialog(title = stringResource(id = R.string.conndialog_title), onDismiss = onClose) {
6565
Column {
66-
if (connections?.internet != Connection.ESTABLISHED) {
66+
if (connections.internet != Connection.ESTABLISHED) {
6767
Text(
6868
text = stringResource(id = R.string.conndialog_network),
6969
modifier = Modifier.padding(top = 16.dp, start = 24.dp, end = 24.dp)

0 commit comments

Comments
 (0)