From 4f9465a2041471ca6ce1853d3ec5bda20ca4646b Mon Sep 17 00:00:00 2001 From: Kgothatso Ngako Date: Thu, 26 Mar 2026 02:52:08 +0200 Subject: [PATCH] Introduce navigationViewModel --- .../1.json | 26 +++- .../kotlin/ac/aux/compose/MainActivity.kt | 8 +- .../kotlin/ac/aux/compose/AuxApp.kt | 7 +- .../ac/aux/compose/database/AuxDatabase.kt | 10 ++ .../ac/aux/compose/database/dao/NostrDao.kt | 1 + .../ac/aux/compose/database/dao/ProfileDao.kt | 10 +- .../aux/compose/database/model/NostrEvent.kt | 8 +- .../model/intermdiate/LocalProfile.kt | 16 +++ .../repository/DatabaseNostrRepository.kt | 12 +- .../aux/compose/managers/DatabaseManager.kt | 18 +++ .../aux/compose/repository/NostrRepository.kt | 33 +++++ .../ui/composable/CreateProfileScreen.kt | 25 +--- .../compose/ui/composable/LoadingScreen.kt | 48 +++++++ .../ui/composable/navigation/AuxNavHost.kt | 125 ++++++++++++++---- .../navigation/routes/CreateProfileRoute.kt | 19 ++- .../navigation/routes/LoadingRoute.kt | 6 + .../ui/view/model/CreateProfileViewModel.kt | 11 +- .../ui/view/model/NavigationViewModel.kt | 89 +++++++++++++ .../ui/view/state/CreateProfileUIState.kt | 2 - .../ui/view/state/NavigationUIState.kt | 25 ++++ .../ac/aux/compose/MainViewController.kt | 7 +- .../src/jvmMain/kotlin/ac/aux/compose/main.kt | 7 +- 22 files changed, 447 insertions(+), 66 deletions(-) create mode 100644 composeApp/src/commonMain/kotlin/ac/aux/compose/database/model/intermdiate/LocalProfile.kt create mode 100644 composeApp/src/commonMain/kotlin/ac/aux/compose/managers/DatabaseManager.kt create mode 100644 composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/LoadingScreen.kt create mode 100755 composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/navigation/routes/LoadingRoute.kt create mode 100644 composeApp/src/commonMain/kotlin/ac/aux/compose/ui/view/model/NavigationViewModel.kt create mode 100644 composeApp/src/commonMain/kotlin/ac/aux/compose/ui/view/state/NavigationUIState.kt diff --git a/composeApp/schemas/ac.aux.compose.database.AuxDatabase/1.json b/composeApp/schemas/ac.aux.compose.database.AuxDatabase/1.json index 36debdc9..819ac83e 100644 --- a/composeApp/schemas/ac.aux.compose.database.AuxDatabase/1.json +++ b/composeApp/schemas/ac.aux.compose.database.AuxDatabase/1.json @@ -2,7 +2,7 @@ "formatVersion": 1, "database": { "version": 1, - "identityHash": "61eb893d34ad04f95f8b007f8448ad7f", + "identityHash": "80c7032502d17a1893ea8dc67debd365", "entities": [ { "tableName": "BroadcastNostrEventReceipt", @@ -221,7 +221,27 @@ "columnNames": [ "id" ] - } + }, + "indices": [ + { + "name": "index_NostrEvent_kind", + "unique": false, + "columnNames": [ + "kind" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_NostrEvent_kind` ON `${TABLE_NAME}` (`kind`)" + }, + { + "name": "index_NostrEvent_pubKey", + "unique": false, + "columnNames": [ + "pubKey" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_NostrEvent_pubKey` ON `${TABLE_NAME}` (`pubKey`)" + } + ] }, { "tableName": "Post", @@ -1075,7 +1095,7 @@ ], "setupQueries": [ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", - "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '61eb893d34ad04f95f8b007f8448ad7f')" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '80c7032502d17a1893ea8dc67debd365')" ] } } \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/ac/aux/compose/MainActivity.kt b/composeApp/src/androidMain/kotlin/ac/aux/compose/MainActivity.kt index 290e28e2..68ae41a9 100644 --- a/composeApp/src/androidMain/kotlin/ac/aux/compose/MainActivity.kt +++ b/composeApp/src/androidMain/kotlin/ac/aux/compose/MainActivity.kt @@ -7,6 +7,7 @@ import androidx.activity.enableEdgeToEdge import androidx.compose.runtime.Composable import androidx.compose.ui.tooling.preview.Preview import androidx.navigation.compose.rememberNavController +import com.vitorpamplona.quartz.utils.platform class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { @@ -17,7 +18,12 @@ class MainActivity : ComponentActivity() { val navController = rememberNavController() AuxApp( - navController + navController = navController, + auxGlobal = AuxGlobal( + platformContext = PlatformContext( + applicationContext = applicationContext + ) + ) ) } } diff --git a/composeApp/src/commonMain/kotlin/ac/aux/compose/AuxApp.kt b/composeApp/src/commonMain/kotlin/ac/aux/compose/AuxApp.kt index dc1d53f5..0ef844f1 100644 --- a/composeApp/src/commonMain/kotlin/ac/aux/compose/AuxApp.kt +++ b/composeApp/src/commonMain/kotlin/ac/aux/compose/AuxApp.kt @@ -12,17 +12,16 @@ import androidx.navigation.NavHostController @Composable fun AuxApp( navController: NavHostController, + auxGlobal: AuxGlobal ) { AuxTheme { Surface( modifier = Modifier.fillMaxSize() ) { AuxNavHost( - - navController = navController, - startDestination = LandingRoute + auxGlobal = auxGlobal, + navController = navController ) - } } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/ac/aux/compose/database/AuxDatabase.kt b/composeApp/src/commonMain/kotlin/ac/aux/compose/database/AuxDatabase.kt index 58ecc002..681728db 100644 --- a/composeApp/src/commonMain/kotlin/ac/aux/compose/database/AuxDatabase.kt +++ b/composeApp/src/commonMain/kotlin/ac/aux/compose/database/AuxDatabase.kt @@ -23,6 +23,8 @@ import ac.aux.compose.database.model.Zap import androidx.room.Database import androidx.room.RoomDatabase import androidx.room.TypeConverters +import androidx.room.immediateTransaction +import androidx.room.useWriterConnection @Database( entities = [ @@ -52,4 +54,12 @@ abstract class AuxDatabase: RoomDatabase() { abstract fun unsignedNostrEventDao(): UnsignedNostrEventDao abstract fun zapDao(): ZapDao + + suspend fun wipeData() { + useWriterConnection { transactor -> + transactor.immediateTransaction { + // TODO: Actually delete the data... + } + } + } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/ac/aux/compose/database/dao/NostrDao.kt b/composeApp/src/commonMain/kotlin/ac/aux/compose/database/dao/NostrDao.kt index b7d93aef..fb4e46b2 100644 --- a/composeApp/src/commonMain/kotlin/ac/aux/compose/database/dao/NostrDao.kt +++ b/composeApp/src/commonMain/kotlin/ac/aux/compose/database/dao/NostrDao.kt @@ -14,6 +14,7 @@ import androidx.room.Transaction abstract class NostrDao( private val database: AuxDatabase ) { + @Transaction open suspend fun insertNostrEvent( nostrEvent: NostrEvent, diff --git a/composeApp/src/commonMain/kotlin/ac/aux/compose/database/dao/ProfileDao.kt b/composeApp/src/commonMain/kotlin/ac/aux/compose/database/dao/ProfileDao.kt index 0071ff1d..cff595b2 100644 --- a/composeApp/src/commonMain/kotlin/ac/aux/compose/database/dao/ProfileDao.kt +++ b/composeApp/src/commonMain/kotlin/ac/aux/compose/database/dao/ProfileDao.kt @@ -3,10 +3,14 @@ package ac.aux.compose.database.dao import ac.aux.compose.database.model.NostrEvent import ac.aux.compose.database.model.Post import ac.aux.compose.database.model.Profile +import ac.aux.compose.database.model.UnsignedNostrEvent +import ac.aux.compose.database.model.intermdiate.LocalProfile import androidx.room.Dao +import androidx.room.Embedded import androidx.room.Insert import androidx.room.OnConflictStrategy import androidx.room.Query +import androidx.room.Relation import androidx.room.Upsert import kotlinx.coroutines.flow.Flow @@ -16,7 +20,7 @@ interface ProfileDao { fun getAllPosts(): List @Query("SELECT * FROM Profile WHERE publicKey = :publicKey") - fun getProfileByPublicKey(publicKey: String): Flow + fun getProfileByPublicKey(publicKey: String): Profile? @Insert( onConflict = OnConflictStrategy.IGNORE @@ -25,4 +29,8 @@ interface ProfileDao { @Upsert suspend fun upsert(profile: Profile) + + @Query("SELECT * FROM NostrEvent WHERE kind = 0 AND pubKey = :publicKey") + fun observeProfile(publicKey: String): Flow + } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/ac/aux/compose/database/model/NostrEvent.kt b/composeApp/src/commonMain/kotlin/ac/aux/compose/database/model/NostrEvent.kt index 83600731..6e7584cb 100644 --- a/composeApp/src/commonMain/kotlin/ac/aux/compose/database/model/NostrEvent.kt +++ b/composeApp/src/commonMain/kotlin/ac/aux/compose/database/model/NostrEvent.kt @@ -6,6 +6,7 @@ import ac.aux.compose.database.model.traits.SoftDeletableEntity import ac.aux.compose.database.model.traits.TimestampedEntity import androidx.room.Entity import androidx.room.Ignore +import androidx.room.Index import androidx.room.PrimaryKey import co.touchlab.kermit.Logger import com.vitorpamplona.quartz.nip01Core.core.HexKey @@ -21,7 +22,12 @@ import kotlin.String import kotlin.time.Clock import kotlin.time.Instant -@Entity +@Entity( + indices = [ + Index("kind"), + Index("pubKey"), + ] +) data class NostrEvent( @PrimaryKey val id: String, diff --git a/composeApp/src/commonMain/kotlin/ac/aux/compose/database/model/intermdiate/LocalProfile.kt b/composeApp/src/commonMain/kotlin/ac/aux/compose/database/model/intermdiate/LocalProfile.kt new file mode 100644 index 00000000..46415207 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/ac/aux/compose/database/model/intermdiate/LocalProfile.kt @@ -0,0 +1,16 @@ +package ac.aux.compose.database.model.intermdiate + +import ac.aux.compose.database.model.NostrEvent +import ac.aux.compose.database.model.Profile +import androidx.room.Embedded +import androidx.room.Relation + +data class LocalProfile( + @Embedded val nostrEvent: NostrEvent, + + @Relation( + parentColumn = "pubKey", + entityColumn = "publicKey" + ) + val profile: Profile? +) diff --git a/composeApp/src/commonMain/kotlin/ac/aux/compose/database/repository/DatabaseNostrRepository.kt b/composeApp/src/commonMain/kotlin/ac/aux/compose/database/repository/DatabaseNostrRepository.kt index 643ce54b..7f3cf1e7 100644 --- a/composeApp/src/commonMain/kotlin/ac/aux/compose/database/repository/DatabaseNostrRepository.kt +++ b/composeApp/src/commonMain/kotlin/ac/aux/compose/database/repository/DatabaseNostrRepository.kt @@ -4,15 +4,21 @@ import ac.aux.compose.database.AuxDatabase import ac.aux.compose.database.model.NostrEvent import ac.aux.compose.database.model.Profile import ac.aux.compose.database.model.UnsignedNostrEvent +import ac.aux.compose.database.model.intermdiate.LocalProfile import ac.aux.compose.repository.NostrRepository -import ac.aux.compose.ui.view.state.CreateProfileUIState +import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.Flow import kotlin.time.Instant class DatabaseNostrRepository( private val database: AuxDatabase ): NostrRepository { + override suspend fun observeProfile(publicKey: HexKey): Flow { + return database.profileDao().observeProfile(publicKey) + } + override suspend fun createNewProfile( name: String, biography: String, @@ -76,4 +82,8 @@ class DatabaseNostrRepository( null } + override suspend fun wipeDatabase() { + database.wipeData() + } + } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/ac/aux/compose/managers/DatabaseManager.kt b/composeApp/src/commonMain/kotlin/ac/aux/compose/managers/DatabaseManager.kt new file mode 100644 index 00000000..5fe082f6 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/ac/aux/compose/managers/DatabaseManager.kt @@ -0,0 +1,18 @@ +package ac.aux.compose.managers + +import ac.aux.compose.AuxGlobal +import ac.aux.compose.database.AuxDatabaseConstructor +import ac.aux.compose.database.builder.PlatformDatabaseBuilder +import ac.aux.compose.database.builder.getRoomDatabase + +class DatabaseManager( + auxGlobal: AuxGlobal +) { + val auxDatabase by lazy { + getRoomDatabase( + PlatformDatabaseBuilder.getDatabaseBuilder( + auxGlobal.platformContext + ) + ) + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/ac/aux/compose/repository/NostrRepository.kt b/composeApp/src/commonMain/kotlin/ac/aux/compose/repository/NostrRepository.kt index 828398c0..619451e6 100644 --- a/composeApp/src/commonMain/kotlin/ac/aux/compose/repository/NostrRepository.kt +++ b/composeApp/src/commonMain/kotlin/ac/aux/compose/repository/NostrRepository.kt @@ -1,11 +1,17 @@ package ac.aux.compose.repository +import ac.aux.compose.database.dao.ProfileDao import ac.aux.compose.database.model.NostrEvent import ac.aux.compose.database.model.Profile import ac.aux.compose.database.model.UnsignedNostrEvent +import ac.aux.compose.database.model.intermdiate.LocalProfile import ac.aux.compose.ui.view.state.CreateProfileUIState +import com.vitorpamplona.quartz.nip01Core.core.HexKey +import kotlinx.coroutines.flow.Flow interface NostrRepository { + suspend fun observeProfile(publicKey: HexKey): Flow + suspend fun createNewProfile( name: String, biography: String, @@ -16,4 +22,31 @@ interface NostrRepository { ) suspend fun saveUnsignedNostrEvent(unsignedNostrEvent: UnsignedNostrEvent): UnsignedNostrEvent? + + suspend fun wipeDatabase() + + companion object { + val NO_OP_NOSTR_REPOSITORY = object : NostrRepository { + override suspend fun observeProfile(publicKey: HexKey): Flow { + TODO("") + } + + override suspend fun createNewProfile( + name: String, + biography: String, + onError: () -> Unit, + onUnsignProfile: (UnsignedNostrEvent) -> Unit, + onUnbroadcastedProfile: (NostrEvent) -> Unit, + onProfileReady: (Profile) -> Unit + ) { + } + + override suspend fun saveUnsignedNostrEvent(unsignedNostrEvent: UnsignedNostrEvent): UnsignedNostrEvent? { + return null + } + + override suspend fun wipeDatabase() { + } + } + } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/CreateProfileScreen.kt b/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/CreateProfileScreen.kt index 36992baa..7d07b335 100644 --- a/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/CreateProfileScreen.kt +++ b/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/CreateProfileScreen.kt @@ -46,7 +46,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel fun CreateProfileScreen( initialCreateProfileUIState: CreateProfileUIState = CreateProfileUIState.Declaration, onNavigateToSocialPreconditionRoute: () -> Unit, - onEndThis: () -> Unit, + onNavigateToEndThis: () -> Unit, nostrRepository: NostrRepository ) { val createProfileViewModel: CreateProfileViewModel = viewModel ( @@ -386,7 +386,9 @@ fun CreateProfileScreen( ), onClick = { // TODO: Delete everything and close the app - onEndThis.invoke() + createProfileViewModel.wipeDatabase { + onNavigateToEndThis.invoke() + } } ) { Text( @@ -450,23 +452,8 @@ fun CreateAccountScreenPreview() { ) ), onNavigateToSocialPreconditionRoute = {}, - onEndThis = {}, - nostrRepository = object : NostrRepository { - override suspend fun createNewProfile( - name: String, - biography: String, - onError: () -> Unit, - onUnsignProfile: (UnsignedNostrEvent) -> Unit, - onUnbroadcastedProfile: (NostrEvent) -> Unit, - onProfileReady: (Profile) -> Unit - ) { - TODO("Not yet implemented") - } - - override suspend fun saveUnsignedNostrEvent(unsignedNostrEvent: UnsignedNostrEvent): UnsignedNostrEvent? { - TODO("Not yet implemented") - } - }) + onNavigateToEndThis = {}, + nostrRepository = NostrRepository.NO_OP_NOSTR_REPOSITORY) } } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/LoadingScreen.kt b/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/LoadingScreen.kt new file mode 100644 index 00000000..bcaceadf --- /dev/null +++ b/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/LoadingScreen.kt @@ -0,0 +1,48 @@ +package ac.aux.compose.ui.composable + +import ac.aux.compose.ui.composable.widgets.LoadingDataIndicator +import ac.aux.compose.ui.theme.AuxTheme +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Button +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp + +@Composable +fun LoadingScreen( +) { + Scaffold { innerPadding -> + Column( + modifier = Modifier.padding(innerPadding) + ) { + LoadingDataIndicator() + } + } +} + +@Preview +@Composable +private fun LoadingScreenPreview() { + AuxTheme { + Surface( + modifier = Modifier.fillMaxSize() + ) { + LoadingScreen( + ) + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/navigation/AuxNavHost.kt b/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/navigation/AuxNavHost.kt index 96a3547d..44e93368 100644 --- a/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/navigation/AuxNavHost.kt +++ b/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/navigation/AuxNavHost.kt @@ -1,19 +1,21 @@ package ac.aux.compose.ui.composable.navigation +import ac.aux.compose.AuxGlobal import ac.aux.compose.database.model.NostrEvent -import ac.aux.compose.database.model.Profile -import ac.aux.compose.database.model.UnsignedNostrEvent -import ac.aux.compose.repository.NostrRepository +import ac.aux.compose.database.repository.DatabaseNostrRepository +import ac.aux.compose.managers.DatabaseManager import ac.aux.compose.ui.composable.CreateProfileScreen import ac.aux.compose.ui.composable.FeedScreen import ac.aux.compose.ui.composable.ImplementationPendingScreen import ac.aux.compose.ui.composable.LandingScreen +import ac.aux.compose.ui.composable.LoadingScreen import ac.aux.compose.ui.composable.SocialPreconditionScreen import ac.aux.compose.ui.composable.navigation.routes.BlankRoute import ac.aux.compose.ui.composable.navigation.routes.CreateProfileRoute import ac.aux.compose.ui.composable.navigation.routes.FeedRoute import ac.aux.compose.ui.composable.navigation.routes.ImplementationPendingRoute import ac.aux.compose.ui.composable.navigation.routes.LandingRoute +import ac.aux.compose.ui.composable.navigation.routes.LoadingRoute import androidx.compose.runtime.Composable import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost @@ -22,24 +24,98 @@ import androidx.navigation.toRoute import ac.aux.compose.ui.composable.navigation.routes.Route import ac.aux.compose.ui.composable.navigation.routes.SocialPreconditionRoute import ac.aux.compose.ui.composable.widgets.feed.DUMMY_EVENTS +import ac.aux.compose.ui.view.model.NavigationViewModel +import ac.aux.compose.ui.view.state.CreateProfileUIState import ac.aux.compose.ui.view.state.FeedListUIState -import androidx.compose.foundation.layout.Column +import ac.aux.compose.ui.view.state.NavigationUIState import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.Surface +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import kotlinx.coroutines.delay +import androidx.lifecycle.compose.LocalLifecycleOwner +import androidx.lifecycle.viewmodel.compose.viewModel @Composable fun AuxNavHost( - navController: NavHostController, - startDestination: Route + auxGlobal: AuxGlobal, + navController: NavHostController ) { + val lifecycleOwner = LocalLifecycleOwner.current + val scope = rememberCoroutineScope() + + val databaseManager = DatabaseManager(auxGlobal) + + val nostrRepository = DatabaseNostrRepository( + database = databaseManager.auxDatabase + ) + + val navigationViewModel: NavigationViewModel = viewModel ( + factory = NavigationViewModel.factory( + NavigationUIState.Landing, + nostrRepository + ) + ) + + LaunchedEffect(lifecycleOwner) { + navigationViewModel.navigationUIState.collect { state -> + when (state) { + is NavigationUIState.Error -> { + navController.navigate( + route = ImplementationPendingRoute("Error screen") + ) { + popUpTo(0) + } + } + is NavigationUIState.Loading -> { + navController.navigate( + route = LoadingRoute + ) { + popUpTo(0) + } + } + is NavigationUIState.Landing -> { + navController.navigate( + route = LandingRoute + ) { + popUpTo(0) + } + } + is NavigationUIState.UnbroadcastedProfile -> { + navController.navigate( + route = CreateProfileRoute( + nostrEventId = state.nostrEvent.id + ) + ) { + popUpTo(0) + } + } + is NavigationUIState.ProfileLoaded -> { + navController.navigate( + route = FeedRoute + ) { + popUpTo(0) + } + } + else -> { + navController.navigate( + route = ImplementationPendingRoute("Unsupported $state") + ) { + popUpTo(0) + } + } + } + } + } NavHost( navController = navController, - startDestination = startDestination + startDestination = LoadingRoute ) { + composable { + LoadingScreen() + } composable { LandingScreen( onNavigateToLearnMore = { @@ -54,43 +130,34 @@ fun AuxNavHost( }, onNavigateToCreateProfile = { navController.navigate( - route = CreateProfileRoute + route = CreateProfileRoute() ) } ) } - composable { + composable { backStackEntry -> + val route = backStackEntry.toRoute() + CreateProfileScreen( + initialCreateProfileUIState = route.toPlaceholderNostrEvent()?.let { placeholderNostrEvent -> + CreateProfileUIState.UnbroadcastedProfile( + placeholderNostrEvent + ) + } ?: CreateProfileUIState.Declaration, onNavigateToSocialPreconditionRoute = { navController.navigate( route = SocialPreconditionRoute ) }, - onEndThis = { - // TODO: Delete everything and close the app + onNavigateToEndThis = { navController.navigate( route = BlankRoute ) { popUpTo(0) } }, - nostrRepository = object : NostrRepository { - override suspend fun createNewProfile( - name: String, - biography: String, - onError: () -> Unit, - onUnsignProfile: (UnsignedNostrEvent) -> Unit, - onUnbroadcastedProfile: (NostrEvent) -> Unit, - onProfileReady: (Profile) -> Unit - ) { - - } - - override suspend fun saveUnsignedNostrEvent(unsignedNostrEvent: UnsignedNostrEvent): UnsignedNostrEvent? { - delay(2_100) - return unsignedNostrEvent - } - }) + nostrRepository = nostrRepository + ) } composable { SocialPreconditionScreen( diff --git a/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/navigation/routes/CreateProfileRoute.kt b/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/navigation/routes/CreateProfileRoute.kt index 1bc6cf47..4c08a996 100755 --- a/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/navigation/routes/CreateProfileRoute.kt +++ b/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/navigation/routes/CreateProfileRoute.kt @@ -1,7 +1,24 @@ package ac.aux.compose.ui.composable.navigation.routes +import ac.aux.compose.database.model.NostrEvent +import ac.aux.compose.database.model.Profile import kotlinx.serialization.Serializable +import kotlin.time.Clock @Serializable -object CreateProfileRoute: Route() { +data class CreateProfileRoute( + val nostrEventId: String? = null, +): Route() { + + fun toPlaceholderNostrEvent(): NostrEvent? = nostrEventId?.let { + NostrEvent( + id = nostrEventId, + pubKey = "", + createdAt = Clock.System.now(), + tags = emptyArray(), + kind = 0, + sig = "", + content = "" + ) + } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/navigation/routes/LoadingRoute.kt b/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/navigation/routes/LoadingRoute.kt new file mode 100755 index 00000000..59003b2a --- /dev/null +++ b/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/navigation/routes/LoadingRoute.kt @@ -0,0 +1,6 @@ +package ac.aux.compose.ui.composable.navigation.routes + +import kotlinx.serialization.Serializable + +@Serializable +object LoadingRoute: Route() \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/view/model/CreateProfileViewModel.kt b/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/view/model/CreateProfileViewModel.kt index 92395698..4f3300b8 100644 --- a/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/view/model/CreateProfileViewModel.kt +++ b/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/view/model/CreateProfileViewModel.kt @@ -90,8 +90,6 @@ class CreateProfileViewModel( onUnbroadcastedProfile = { unbroadcastedNostrEvent -> createProfileUIState.value = CreateProfileUIState.UnbroadcastedProfile( nostrEvent = unbroadcastedNostrEvent, - name = createProfileFormState.nameField.text.value, - bio = createProfileFormState.biographyField.text.value ) }, onProfileReady = { profile -> @@ -102,4 +100,13 @@ class CreateProfileViewModel( ) } } + + fun wipeDatabase( + onNavigateToEndThis: () -> Unit + ) { + viewModelScope.launch { + nostrRepository.wipeDatabase() + onNavigateToEndThis.invoke() + } + } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/view/model/NavigationViewModel.kt b/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/view/model/NavigationViewModel.kt new file mode 100644 index 00000000..70261a23 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/view/model/NavigationViewModel.kt @@ -0,0 +1,89 @@ +package ac.aux.compose.ui.view.model + +import ac.aux.compose.repository.NostrRepository +import ac.aux.compose.ui.view.state.NavigationUIState +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewmodel.initializer +import androidx.lifecycle.viewmodel.viewModelFactory +import androidx.lifecycle.viewModelScope +import co.touchlab.kermit.Logger +import com.vitorpamplona.quartz.nip01Core.core.toHexKey +import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.getAndUpdate +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch +import kotlin.random.Random + +class NavigationViewModel( + initialNavigationUIState: NavigationUIState, + val nostrRepository: NostrRepository +): ViewModel() { + companion object { + private const val TAG = "NavigationViewModel" + + fun factory( + initialNavigationUIState: NavigationUIState, + nostrRepository: NostrRepository + ): ViewModelProvider.Factory = viewModelFactory { + initializer { + NavigationViewModel( + initialNavigationUIState = initialNavigationUIState, + nostrRepository = nostrRepository + ) + } + } + } + + val tempDevelopmentKeyPair = KeyPair( + privKey = Random(21_000_000L).nextBytes(32) + ) + private val _navigationUIState = MutableStateFlow( + initialNavigationUIState + ) + val navigationUIState = _navigationUIState.asStateFlow() + + init { + observeProfile() + } + + private val logger = Logger.withTag(TAG) + + + + private fun observeProfile() { + viewModelScope.launch { + nostrRepository.observeProfile(tempDevelopmentKeyPair.pubKey.toHexKey()).map { localProfile -> + + _navigationUIState.getAndUpdate { + if (localProfile == null) { + NavigationUIState.Landing + } else if (localProfile.profile != null) { + NavigationUIState.ProfileLoaded( + profile = localProfile.profile + ) + } else if (localProfile.nostrEvent != null) { + NavigationUIState.UnbroadcastedProfile( + nostrEvent = localProfile.nostrEvent + ) + // TODO: Support unsignedNostrEvent with pubKey so we can have multi account support... +// } else if (localProfile.nostrEvent != null) { +// NavigationUIState.UnbroadcastedProfile( +// nostrEvent = localProfile.nostrEvent +// ) + } else { + NavigationUIState.Error + } + } + }.catch { + NavigationUIState.Error + }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), NavigationUIState.Landing) + } + } + +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/view/state/CreateProfileUIState.kt b/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/view/state/CreateProfileUIState.kt index 592a598b..1a296ed4 100644 --- a/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/view/state/CreateProfileUIState.kt +++ b/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/view/state/CreateProfileUIState.kt @@ -24,8 +24,6 @@ abstract class CreateProfileUIState { data class UnbroadcastedProfile( val nostrEvent: NostrEvent, - val name: String, - val bio: String ) : CreateProfileUIState() data class ProfileReady( diff --git a/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/view/state/NavigationUIState.kt b/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/view/state/NavigationUIState.kt new file mode 100644 index 00000000..5a4f744c --- /dev/null +++ b/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/view/state/NavigationUIState.kt @@ -0,0 +1,25 @@ +package ac.aux.compose.ui.view.state + +import ac.aux.compose.database.model.NostrEvent +import ac.aux.compose.database.model.Profile +import ac.aux.compose.database.model.UnsignedNostrEvent + +abstract class NavigationUIState { + data object Error: NavigationUIState() + + data object Loading: NavigationUIState() + + data object Landing: NavigationUIState() + + data class UnsignedProfile( + val unsignedNostrEvent: UnsignedNostrEvent, + ) : NavigationUIState() + + data class UnbroadcastedProfile( + val nostrEvent: NostrEvent, + ) : NavigationUIState() + + data class ProfileLoaded( + val profile: Profile + ) : NavigationUIState() +} \ No newline at end of file diff --git a/composeApp/src/iosMain/kotlin/ac/aux/compose/MainViewController.kt b/composeApp/src/iosMain/kotlin/ac/aux/compose/MainViewController.kt index 0ed35bfe..a18735c6 100644 --- a/composeApp/src/iosMain/kotlin/ac/aux/compose/MainViewController.kt +++ b/composeApp/src/iosMain/kotlin/ac/aux/compose/MainViewController.kt @@ -6,5 +6,10 @@ import androidx.navigation.compose.rememberNavController fun MainViewController() = ComposeUIViewController { val navController = rememberNavController() - AuxApp(navController) + AuxApp( + navController = navController, + auxGlobal = AuxGlobal( + platformContext = PlatformContext() + ) + ) } \ No newline at end of file diff --git a/composeApp/src/jvmMain/kotlin/ac/aux/compose/main.kt b/composeApp/src/jvmMain/kotlin/ac/aux/compose/main.kt index ff6e33f3..caef58c7 100644 --- a/composeApp/src/jvmMain/kotlin/ac/aux/compose/main.kt +++ b/composeApp/src/jvmMain/kotlin/ac/aux/compose/main.kt @@ -10,6 +10,11 @@ fun main() = application { title = "Aux", ) { val navController = rememberNavController() - AuxApp(navController) + AuxApp( + navController = navController, + auxGlobal = AuxGlobal( + platformContext = PlatformContext() + ) + ) } } \ No newline at end of file