diff --git a/composeApp/src/androidMain/kotlin/press/mantra/android/MainActivity.kt b/composeApp/src/androidMain/kotlin/press/mantra/android/MainActivity.kt index 43f8a75f..9ee6c8d5 100644 --- a/composeApp/src/androidMain/kotlin/press/mantra/android/MainActivity.kt +++ b/composeApp/src/androidMain/kotlin/press/mantra/android/MainActivity.kt @@ -7,13 +7,14 @@ import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.navigation.compose.rememberNavController +import fr.acinq.phoenix.NFCActivity import press.mantra.compose.PlatformContext import press.mantra.compose.MantraApp import press.mantra.compose.MantraGlobal import fr.acinq.phoenix.PhoenixGlobal import fr.acinq.phoenix.android.services.HceService -class MainActivity : ComponentActivity() { +class MainActivity : ComponentActivity(), NFCActivity { private var nfcAdapter: NfcAdapter? = null @@ -41,11 +42,11 @@ class MainActivity : ComponentActivity() { } } - fun stopHceService() { + override fun stopHceService() { stopService(Intent(this@MainActivity, HceService::class.java)) } - fun stopNfcReader() { + override fun stopNfcReader() { if (com.machankura.compose.ui.composable.widgets.nfc.NfcStateRepository.isReading()) { com.machankura.compose.ui.composable.widgets.nfc.NfcStateRepository.updateState( com.machankura.compose.ui.composable.widgets.nfc.NfcState.Inactive) diff --git a/composeApp/src/androidMain/kotlin/press/mantra/android/MantraApplication.kt b/composeApp/src/androidMain/kotlin/press/mantra/android/MantraApplication.kt index 53eec311..3fde574e 100644 --- a/composeApp/src/androidMain/kotlin/press/mantra/android/MantraApplication.kt +++ b/composeApp/src/androidMain/kotlin/press/mantra/android/MantraApplication.kt @@ -7,22 +7,31 @@ import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.preferencesDataStore import fr.acinq.phoenix.PhoenixGlobal import fr.acinq.phoenix.android.BusinessManager +import fr.acinq.phoenix.android.LightningApplication import fr.acinq.phoenix.utils.PlatformContext import fr.acinq.phoenix.utils.preferences.GlobalPrefs /** This datastore persists preferences across node ids. */ val Context.globalPrefs: DataStore by preferencesDataStore(name = "globalprefs") -class MantraApplication: Application() { - lateinit var globalPrefs: GlobalPrefs - lateinit var phoenixGlobal: PhoenixGlobal +class MantraApplication: Application(), LightningApplication { + private lateinit var globalPrefs: GlobalPrefs + private lateinit var phoenixGlobal: PhoenixGlobal override fun onCreate() { super.onCreate() phoenixGlobal = PhoenixGlobal(PlatformContext(applicationContext)) globalPrefs = GlobalPrefs(applicationContext.globalPrefs) - BusinessManager.initialize(applicationContext) + BusinessManager.initialize(this) } + + override fun getGlobalPrefs(): GlobalPrefs { + return globalPrefs + } + + override fun getPhoenixGlobal(): PhoenixGlobal { + return phoenixGlobal + } } \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/press/mantra/compose/extensions/Phoenix.android.kt b/composeApp/src/androidMain/kotlin/press/mantra/compose/extensions/Phoenix.android.kt index 06c074e2..5b321ea9 100644 --- a/composeApp/src/androidMain/kotlin/press/mantra/compose/extensions/Phoenix.android.kt +++ b/composeApp/src/androidMain/kotlin/press/mantra/compose/extensions/Phoenix.android.kt @@ -24,10 +24,10 @@ actual fun schedulePlatformLogic(phoenixGlobal: PhoenixGlobal) { } actual fun getShowIntroFlow(phoenixGlobal: PhoenixGlobal): Flow { val machankuraApplication = phoenixGlobal.ctx.applicationContext as MantraApplication - return machankuraApplication.globalPrefs.getShowIntro + return machankuraApplication.getGlobalPrefs().getShowIntro } actual fun getGlobalPrefs(phoenixGlobal: PhoenixGlobal): GlobalPrefs { val machankuraApplication = phoenixGlobal.ctx.applicationContext as MantraApplication - return machankuraApplication.globalPrefs + return machankuraApplication.getGlobalPrefs() } \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/press/mantra/compose/ui/view/model/NavigationViewModel.android.kt b/composeApp/src/androidMain/kotlin/press/mantra/compose/ui/view/model/NavigationViewModel.android.kt index f1220cf8..eaf692c1 100644 --- a/composeApp/src/androidMain/kotlin/press/mantra/compose/ui/view/model/NavigationViewModel.android.kt +++ b/composeApp/src/androidMain/kotlin/press/mantra/compose/ui/view/model/NavigationViewModel.android.kt @@ -36,12 +36,12 @@ actual fun loadAndDecryptSeed(phoenixGlobal: PhoenixGlobal): DecryptSeedResult { actual fun getAvailableWalletsMeta(phoenixGlobal: PhoenixGlobal): Flow> { val machankuraApplication = phoenixGlobal.ctx.applicationContext as MantraApplication - return machankuraApplication.globalPrefs.getAvailableWalletsMeta + return machankuraApplication.getGlobalPrefs().getAvailableWalletsMeta } actual suspend fun saveAvailableWalletMeta(phoenixGlobal: PhoenixGlobal, metadata: UserWalletMetadata) { val machankuraApplication = phoenixGlobal.ctx.applicationContext as MantraApplication - machankuraApplication.globalPrefs.saveAvailableWalletMeta(metadata) + machankuraApplication.getGlobalPrefs().saveAvailableWalletMeta(metadata) } actual suspend fun saveAvailableWalletMeta( @@ -52,7 +52,7 @@ actual suspend fun saveAvailableWalletMeta( isHidden: Boolean ) { val machankuraApplication = phoenixGlobal.ctx.applicationContext as MantraApplication - machankuraApplication.globalPrefs.saveAvailableWalletMeta( + machankuraApplication.getGlobalPrefs().saveAvailableWalletMeta( walletId = walletId, name = name, avatar = avatar, diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/database/dao/NostrDao.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/database/dao/NostrDao.kt index 9a5081c3..fd8cc712 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/database/dao/NostrDao.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/database/dao/NostrDao.kt @@ -361,7 +361,7 @@ abstract class NostrDao( // MLSGroupEvent handling... nostrEvent.toKeyPackageEvent()?.let { keyPackageEvent -> - logger.d("KeyPackageEvent: $keyPackageEvent") + logger.d("KeyPackageEvent: ${keyPackageEvent.toJson()}") database.marmotKeyPackageDao().upsert( MarmotKeyPackage( id = keyPackageEvent.id, diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/database/dao/UnsignedNostrEventDao.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/database/dao/UnsignedNostrEventDao.kt index 4a84964d..926231cd 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/database/dao/UnsignedNostrEventDao.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/database/dao/UnsignedNostrEventDao.kt @@ -7,23 +7,24 @@ import androidx.room3.Transaction import androidx.room3.Upsert import press.mantra.compose.database.model.intermdiate.LocalAccount import kotlinx.coroutines.flow.Flow +import press.mantra.compose.database.model.UnsignedNostrEvent @Dao interface UnsignedNostrEventDao { @Query("SELECT * FROM UnsignedNostrEvent") - fun getAUnsignedNostrEvents(): List + fun getAUnsignedNostrEvents(): List @Query("SELECT * FROM UnsignedNostrEvent") // TODO: where nostrEvent.unsignedNostrEventId is 0 - fun getAllUnprocessedUnsignedNostrEvents(): List + fun getAllUnprocessedUnsignedNostrEvents(): List @Query("SELECT * FROM UnsignedNostrEvent WHERE id = :id") - fun getUnsignedNostrEventById(id: Long): Flow + fun getUnsignedNostrEventById(id: Long): Flow @Upsert - suspend fun upsert(unsignedNostrEvent: press.mantra.compose.database.model.UnsignedNostrEvent): Long + suspend fun upsert(unsignedNostrEvent: UnsignedNostrEvent): Long @Insert - suspend fun insert(unsignedNostrEvents: List) + suspend fun insert(unsignedNostrEvents: List) @Transaction @Query("SELECT * FROM UnsignedNostrEvent WHERE kind = 0 AND pubKey = :publicKey") @@ -33,6 +34,6 @@ interface UnsignedNostrEventDao { @Query("SELECT * FROM UnsignedNostrEvent WHERE kind = 0") fun getLocalAccounts(): List - @Query("SELECT * FROM UnsignedNostrEvent WHERE pubKey = :publicKey AND signedAt IS NULL") - fun observeUnsignedNostrEvents(publicKey: String): Flow + @Query("SELECT * FROM UnsignedNostrEvent WHERE pubKey = :publicKey AND signedAt IS NULL ORDER BY kind ASC") + fun observeUnsignedNostrEvents(publicKey: String): Flow } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/extensions/WalletManagerExtension.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/extensions/WalletManagerExtension.kt new file mode 100644 index 00000000..c6ab1442 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/extensions/WalletManagerExtension.kt @@ -0,0 +1,12 @@ +package press.mantra.compose.extensions + +import com.vitorpamplona.quartz.nip01Core.core.toHexKey +import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair +import fr.acinq.lightning.crypto.LocalKeyManager +import fr.acinq.phoenix.managers.nostrPrivateKey + +fun LocalKeyManager.nostrPublicKey(): String { + return KeyPair( + privKey = nostrPrivateKey().value.toByteArray() + ).pubKey.toHexKey() +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/network/relays/RelaysSocketManager.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/network/relays/RelaysSocketManager.kt index 7adca36e..7c81a050 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/network/relays/RelaysSocketManager.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/network/relays/RelaysSocketManager.kt @@ -8,7 +8,6 @@ import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.ReqCmd import com.vitorpamplona.quartz.nip77Negentropy.NegCloseCmd import com.vitorpamplona.quartz.nip77Negentropy.NegOpenCmd import fr.acinq.phoenix.data.ActiveWallet -import fr.acinq.phoenix.managers.nostrPublicKey import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -20,6 +19,7 @@ import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock +import press.mantra.compose.extensions.nostrPublicKey /** diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/view/model/CreateProfileViewModel.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/view/model/CreateProfileViewModel.kt index 96125e8e..e1211ede 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/view/model/CreateProfileViewModel.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/view/model/CreateProfileViewModel.kt @@ -17,7 +17,6 @@ import fr.acinq.bitcoin.byteVector import fr.acinq.lightning.Lightning import fr.acinq.lightning.crypto.LocalKeyManager import fr.acinq.phoenix.managers.NodeParamsManager -import fr.acinq.phoenix.managers.nostrPublicKey import fr.acinq.phoenix.managers.nsecPassword import fr.acinq.phoenix.utils.MnemonicLanguage import kotlinx.coroutines.CoroutineExceptionHandler @@ -25,6 +24,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.IO import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import press.mantra.compose.extensions.nostrPublicKey import kotlin.time.Duration.Companion.seconds class CreateProfileViewModel( @@ -129,11 +129,6 @@ class CreateProfileViewModel( biography = createProfileFormState.biographyField.textFieldState.text.toString(), onCompletion = { logger.d("Created: $pubkey" ) - - marmotRepository.publishMarmotKeyPackageBundle( - publicKey = pubkey, - nsecPassword = localKeyManager.nsecPassword() - ) } ) } diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/view/model/NavigationViewModel.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/view/model/NavigationViewModel.kt index 86f3c053..f4bcfd12 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/view/model/NavigationViewModel.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/view/model/NavigationViewModel.kt @@ -9,7 +9,6 @@ import press.mantra.compose.ui.composable.navigation.routes.SovereignWalletStart import press.mantra.compose.ui.view.state.NavigationUIState import co.touchlab.kermit.Logger import fr.acinq.phoenix.data.ActiveWallet -import fr.acinq.phoenix.managers.nostrPublicKey import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.IO @@ -21,6 +20,7 @@ import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.getAndUpdate import kotlinx.coroutines.launch +import press.mantra.compose.extensions.nostrPublicKey import kotlin.time.Duration.Companion.milliseconds class NavigationViewModel( @@ -181,24 +181,26 @@ class NavigationViewModel( NavigationUIState.StartupPhoenix } } else { - if (activeWallet.business == null) { - _navigationUIState.getAndUpdate { - NavigationUIState.StartupPhoenix - } - } else { - val activeUserPublicKey = activeWallet.business.walletManager.keyManager.value?.nostrPublicKey() - - if (activeUserPublicKey == null) { + activeWallet.business.let { business -> + if (business == null) { _navigationUIState.getAndUpdate { - NavigationUIState.Loading(text = "Couldn't get the nostrKey") + NavigationUIState.StartupPhoenix } } else { - logger.i("Observing: $activeUserPublicKey") + val activeUserPublicKey = business.walletManager.keyManager.value?.nostrPublicKey() - loadNostrProfile(activeUserPublicKey) + if (activeUserPublicKey == null) { + _navigationUIState.getAndUpdate { + NavigationUIState.Loading(text = "Couldn't get the nostrKey") + } + } else { + logger.i("Observing: $activeUserPublicKey") + + loadNostrProfile(activeUserPublicKey) + } } + logger.i("Navigation UI State is Landing") } - logger.i("Navigation UI State is Landing") } } } diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/view/model/NotaryViewModel.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/view/model/NotaryViewModel.kt index 24cea448..d87b080d 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/view/model/NotaryViewModel.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/view/model/NotaryViewModel.kt @@ -18,6 +18,7 @@ import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.distinctUntilChanged @@ -26,6 +27,7 @@ import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import press.mantra.compose.database.model.NostrEvent +import kotlin.time.Duration.Companion.seconds import kotlin.time.Instant class NotaryViewModel( @@ -204,8 +206,12 @@ class NotaryViewModel( marmotRepository.observeActiveMarmotKeyPackageBundle( publicKey = keyPair.pubKey.toHexKey() ).distinctUntilChanged().collect { marmotKeyPackageBundleOrNull -> + if (marmotKeyPackageBundleOrNull == null) { guardNotarization("key package bundle for ${keyPair.pubKey.toHexKey()}") { + delay(3.seconds) + // TODO: We shouldn't try to create a key package without profile + // we should be check profile in the query to get updates logger.d("Need to create new key package bundle") marmotRepository.publishMarmotKeyPackageBundle( publicKey = keyPair.pubKey.toHexKey(),