From 3dea688eea68d994f56170c3a92c6402eb6f5a94 Mon Sep 17 00:00:00 2001 From: Kgothatso Ngako Date: Thu, 26 Mar 2026 00:20:20 +0200 Subject: [PATCH] Room Database setup --- composeApp/build.gradle.kts | 4 + .../ac/aux/compose/PlatformContext.android.kt | 7 ++ .../PlatformDatabaseBuilder.android.kt | 18 +++++ .../kotlin/ac/aux/compose/AuxGlobal.kt | 7 ++ .../kotlin/ac/aux/compose/PlatformContext.kt | 3 + .../database/AuxDatabaseConstructor.kt | 9 +++ .../builder/PlatformDatabaseBuilder.kt | 21 ++++++ .../repository/DatabaseNostrRepository.kt | 58 +++++++++++++++ .../aux/compose/repository/NostrRepository.kt | 12 +++ .../ui/composable/CreateProfileScreen.kt | 12 +++ .../ui/composable/navigation/AuxNavHost.kt | 13 ++++ .../ui/view/model/CreateProfileViewModel.kt | 74 ++++++------------- .../ac/aux/compose/PlatformContext.ios.kt | 3 + .../builder/PlatformDatabaseBuilder.ios.kt | 32 ++++++++ .../ac/aux/compose/PlatformContext.jvm.kt | 3 + .../builder/PlatformDatabaseBuilder.jvm.kt | 16 ++++ 16 files changed, 242 insertions(+), 50 deletions(-) create mode 100644 composeApp/src/androidMain/kotlin/ac/aux/compose/PlatformContext.android.kt create mode 100644 composeApp/src/androidMain/kotlin/ac/aux/compose/database/builder/PlatformDatabaseBuilder.android.kt create mode 100644 composeApp/src/commonMain/kotlin/ac/aux/compose/AuxGlobal.kt create mode 100644 composeApp/src/commonMain/kotlin/ac/aux/compose/PlatformContext.kt create mode 100644 composeApp/src/commonMain/kotlin/ac/aux/compose/database/AuxDatabaseConstructor.kt create mode 100644 composeApp/src/commonMain/kotlin/ac/aux/compose/database/builder/PlatformDatabaseBuilder.kt create mode 100644 composeApp/src/iosMain/kotlin/ac/aux/compose/PlatformContext.ios.kt create mode 100644 composeApp/src/iosMain/kotlin/ac/aux/compose/database/builder/PlatformDatabaseBuilder.ios.kt create mode 100644 composeApp/src/jvmMain/kotlin/ac/aux/compose/PlatformContext.jvm.kt create mode 100644 composeApp/src/jvmMain/kotlin/ac/aux/compose/database/builder/PlatformDatabaseBuilder.jvm.kt diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index 0e635bd6..1a980bc4 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -13,6 +13,10 @@ plugins { } kotlin { + compilerOptions { + freeCompilerArgs.add("-Xexpect-actual-classes") + } + androidTarget { compilerOptions { jvmTarget.set(JvmTarget.JVM_11) diff --git a/composeApp/src/androidMain/kotlin/ac/aux/compose/PlatformContext.android.kt b/composeApp/src/androidMain/kotlin/ac/aux/compose/PlatformContext.android.kt new file mode 100644 index 00000000..cc089f74 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/ac/aux/compose/PlatformContext.android.kt @@ -0,0 +1,7 @@ +package ac.aux.compose + +import android.content.Context + +actual class PlatformContext( + val applicationContext: Context +) \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/ac/aux/compose/database/builder/PlatformDatabaseBuilder.android.kt b/composeApp/src/androidMain/kotlin/ac/aux/compose/database/builder/PlatformDatabaseBuilder.android.kt new file mode 100644 index 00000000..c2bfa3af --- /dev/null +++ b/composeApp/src/androidMain/kotlin/ac/aux/compose/database/builder/PlatformDatabaseBuilder.android.kt @@ -0,0 +1,18 @@ +package ac.aux.compose.database.builder + +import ac.aux.compose.PlatformContext +import ac.aux.compose.database.AuxDatabase +import androidx.room.Room +import androidx.room.RoomDatabase + +actual object PlatformDatabaseBuilder { + actual fun getDatabaseBuilder(platformContext: PlatformContext): RoomDatabase.Builder { + val appContext = platformContext.applicationContext + + val dbFile = appContext.getDatabasePath("aux.db") + return Room.databaseBuilder( + context = appContext, + name = dbFile.absolutePath + ) + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/ac/aux/compose/AuxGlobal.kt b/composeApp/src/commonMain/kotlin/ac/aux/compose/AuxGlobal.kt new file mode 100644 index 00000000..eded1563 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/ac/aux/compose/AuxGlobal.kt @@ -0,0 +1,7 @@ +package ac.aux.compose + +class AuxGlobal( + val platformContext: PlatformContext +) { + +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/ac/aux/compose/PlatformContext.kt b/composeApp/src/commonMain/kotlin/ac/aux/compose/PlatformContext.kt new file mode 100644 index 00000000..459b47a2 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/ac/aux/compose/PlatformContext.kt @@ -0,0 +1,3 @@ +package ac.aux.compose + +expect class PlatformContext \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/ac/aux/compose/database/AuxDatabaseConstructor.kt b/composeApp/src/commonMain/kotlin/ac/aux/compose/database/AuxDatabaseConstructor.kt new file mode 100644 index 00000000..9d37d991 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/ac/aux/compose/database/AuxDatabaseConstructor.kt @@ -0,0 +1,9 @@ +package ac.aux.compose.database + +import androidx.room.RoomDatabaseConstructor + +@Suppress("KotlinNoActualForExpect", "NO_ACTUAL_FOR_EXPECT") +expect object AuxDatabaseConstructor: RoomDatabaseConstructor { + + override fun initialize(): AuxDatabase +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/ac/aux/compose/database/builder/PlatformDatabaseBuilder.kt b/composeApp/src/commonMain/kotlin/ac/aux/compose/database/builder/PlatformDatabaseBuilder.kt new file mode 100644 index 00000000..5b0bc1c5 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/ac/aux/compose/database/builder/PlatformDatabaseBuilder.kt @@ -0,0 +1,21 @@ +package ac.aux.compose.database.builder + +import ac.aux.compose.PlatformContext +import ac.aux.compose.database.AuxDatabase +import androidx.room.RoomDatabase +import androidx.sqlite.driver.bundled.BundledSQLiteDriver +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO + +expect object PlatformDatabaseBuilder { + fun getDatabaseBuilder(platformContext: PlatformContext): RoomDatabase.Builder +} + +fun getRoomDatabase( + builder: RoomDatabase.Builder +): AuxDatabase { + return builder + .setDriver(BundledSQLiteDriver()) + .setQueryCoroutineContext(Dispatchers.IO) + .build() +} \ No newline at end of file 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 47a3a79a..643ce54b 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 @@ -1,13 +1,71 @@ package ac.aux.compose.database.repository 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.repository.NostrRepository +import ac.aux.compose.ui.view.state.CreateProfileUIState +import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent import kotlinx.coroutines.delay +import kotlin.time.Instant class DatabaseNostrRepository( private val database: AuxDatabase ): NostrRepository { + override suspend fun createNewProfile( + name: String, + biography: String, + onError: () -> Unit, + onUnsignProfile: (UnsignedNostrEvent) -> Unit, + onUnbroadcastedProfile: (NostrEvent) -> Unit, + onProfileReady: (Profile) -> Unit + ) { + val profileEventTemplate = MetadataEvent.createNew( + name = name, + about = biography, + ) + + val unsignedNostrEvent = saveUnsignedNostrEvent( + UnsignedNostrEvent( + kind = profileEventTemplate.kind, + createdAt = Instant.fromEpochMilliseconds(profileEventTemplate.createdAt), + tags = profileEventTemplate.tags, + content = profileEventTemplate.content + ) + ) + + if (unsignedNostrEvent != null) { + onUnsignProfile.invoke(unsignedNostrEvent) + + delay(7_500) // Seeming busy is very important... + + // TODO: Call and collect kind 0 nostrEvent with pubKey + onUnbroadcastedProfile.invoke( + NostrEvent( + id = "", + pubKey = "", + kind = 0, + content = "Something here", + tags = emptyArray(), + sig = "" + ) + ) + + delay(4_500) // Seeming busy is very important... + + onProfileReady.invoke( + Profile( + publicKey = "", + nostrEventId = "", + displayName = name, + about = biography, + ) + ) + } else { + onError.invoke() + } + } override suspend fun saveUnsignedNostrEvent(unsignedNostrEvent: UnsignedNostrEvent): UnsignedNostrEvent? = try { delay(2_100) // Just to make it look like we are doing serious work... 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 cdad6677..828398c0 100644 --- a/composeApp/src/commonMain/kotlin/ac/aux/compose/repository/NostrRepository.kt +++ b/composeApp/src/commonMain/kotlin/ac/aux/compose/repository/NostrRepository.kt @@ -1,7 +1,19 @@ package ac.aux.compose.repository +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.ui.view.state.CreateProfileUIState interface NostrRepository { + suspend fun createNewProfile( + name: String, + biography: String, + onError: () -> Unit, + onUnsignProfile: (UnsignedNostrEvent) -> Unit, + onUnbroadcastedProfile: (NostrEvent) -> Unit, + onProfileReady: (Profile) -> Unit + ) + suspend fun saveUnsignedNostrEvent(unsignedNostrEvent: UnsignedNostrEvent): UnsignedNostrEvent? } \ 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 723f91bc..36992baa 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 @@ -1,5 +1,6 @@ package ac.aux.compose.ui.composable +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 @@ -451,6 +452,17 @@ 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") } 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 a7382f98..96a3547d 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,5 +1,7 @@ package ac.aux.compose.ui.composable.navigation +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.ui.composable.CreateProfileScreen @@ -73,6 +75,17 @@ fun AuxNavHost( } }, 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 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 b3ae0d28..92395698 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 @@ -74,58 +74,32 @@ class CreateProfileViewModel( viewModelScope.launch(Dispatchers.IO) { - val profileEventTemplate = MetadataEvent.createNew( + nostrRepository.createNewProfile( name = createProfileFormState.nameField.text.value, - about = createProfileFormState.biographyField.text.value - ) - - val unsignedNostrEvent = nostrRepository.saveUnsignedNostrEvent( - UnsignedNostrEvent( - kind = profileEventTemplate.kind, - createdAt = Instant.fromEpochMilliseconds(profileEventTemplate.createdAt), - tags = profileEventTemplate.tags, - content = profileEventTemplate.content - ) - ) - - isActionPending.value = false - - if (unsignedNostrEvent != null) { - createProfileUIState.value = CreateProfileUIState.UnsignedProfile( - unsignedNostrEvent = unsignedNostrEvent, - name = createProfileFormState.nameField.text.value, - bio = createProfileFormState.biographyField.text.value - ) - - delay(7_500) // Seeming busy is very important... - - createProfileUIState.value = CreateProfileUIState.UnbroadcastedProfile( - nostrEvent = NostrEvent( - id = "", - pubKey = "", - kind = 0, - content = "Something here", - tags = emptyArray(), - sig = "" - ), - name = createProfileFormState.nameField.text.value, - bio = createProfileFormState.biographyField.text.value - ) - - delay(4_500) // Seeming busy is very important... - - createProfileUIState.value = CreateProfileUIState.ProfileReady( - profile = Profile( - publicKey = "", - nostrEventId = "", - displayName = createProfileFormState.nameField.text.value, - about = createProfileFormState.biographyField.text.value, + biography = createProfileFormState.biographyField.text.value, + onError = { + createProfileUIState.value = CreateProfileUIState.Error + }, + onUnsignProfile = { unsignedNostrEvent -> + createProfileUIState.value = CreateProfileUIState.UnsignedProfile( + unsignedNostrEvent = unsignedNostrEvent, + name = createProfileFormState.nameField.text.value, + bio = createProfileFormState.biographyField.text.value ) - ) - } else { - createProfileUIState.value = CreateProfileUIState.Error - } - + }, + onUnbroadcastedProfile = { unbroadcastedNostrEvent -> + createProfileUIState.value = CreateProfileUIState.UnbroadcastedProfile( + nostrEvent = unbroadcastedNostrEvent, + name = createProfileFormState.nameField.text.value, + bio = createProfileFormState.biographyField.text.value + ) + }, + onProfileReady = { profile -> + createProfileUIState.value = CreateProfileUIState.ProfileReady( + profile = profile + ) + } + ) } } } \ No newline at end of file diff --git a/composeApp/src/iosMain/kotlin/ac/aux/compose/PlatformContext.ios.kt b/composeApp/src/iosMain/kotlin/ac/aux/compose/PlatformContext.ios.kt new file mode 100644 index 00000000..e4be7c73 --- /dev/null +++ b/composeApp/src/iosMain/kotlin/ac/aux/compose/PlatformContext.ios.kt @@ -0,0 +1,3 @@ +package ac.aux.compose + +actual class PlatformContext \ No newline at end of file diff --git a/composeApp/src/iosMain/kotlin/ac/aux/compose/database/builder/PlatformDatabaseBuilder.ios.kt b/composeApp/src/iosMain/kotlin/ac/aux/compose/database/builder/PlatformDatabaseBuilder.ios.kt new file mode 100644 index 00000000..6f38b667 --- /dev/null +++ b/composeApp/src/iosMain/kotlin/ac/aux/compose/database/builder/PlatformDatabaseBuilder.ios.kt @@ -0,0 +1,32 @@ +package ac.aux.compose.database.builder + +import ac.aux.compose.PlatformContext +import ac.aux.compose.database.AuxDatabase +import androidx.room.Room +import androidx.room.RoomDatabase +import kotlinx.cinterop.ExperimentalForeignApi +import platform.Foundation.NSDocumentDirectory +import platform.Foundation.NSFileManager +import platform.Foundation.NSUserDomainMask + +actual object PlatformDatabaseBuilder { + actual fun getDatabaseBuilder(platformContext: PlatformContext): RoomDatabase.Builder { + val dbFilePath = documentDirectory() + "/aux.db" + return Room.databaseBuilder( + name = dbFilePath, + ) + } + + @OptIn(ExperimentalForeignApi::class) + private fun documentDirectory(): String { + val documentDirectory = NSFileManager.defaultManager.URLForDirectory( + directory = NSDocumentDirectory, + inDomain = NSUserDomainMask, + appropriateForURL = null, + create = false, + error = null, + ) + return requireNotNull(documentDirectory?.path) + } + +} \ No newline at end of file diff --git a/composeApp/src/jvmMain/kotlin/ac/aux/compose/PlatformContext.jvm.kt b/composeApp/src/jvmMain/kotlin/ac/aux/compose/PlatformContext.jvm.kt new file mode 100644 index 00000000..e4be7c73 --- /dev/null +++ b/composeApp/src/jvmMain/kotlin/ac/aux/compose/PlatformContext.jvm.kt @@ -0,0 +1,3 @@ +package ac.aux.compose + +actual class PlatformContext \ No newline at end of file diff --git a/composeApp/src/jvmMain/kotlin/ac/aux/compose/database/builder/PlatformDatabaseBuilder.jvm.kt b/composeApp/src/jvmMain/kotlin/ac/aux/compose/database/builder/PlatformDatabaseBuilder.jvm.kt new file mode 100644 index 00000000..683d4ec3 --- /dev/null +++ b/composeApp/src/jvmMain/kotlin/ac/aux/compose/database/builder/PlatformDatabaseBuilder.jvm.kt @@ -0,0 +1,16 @@ +package ac.aux.compose.database.builder + +import ac.aux.compose.PlatformContext +import ac.aux.compose.database.AuxDatabase +import androidx.room.Room +import androidx.room.RoomDatabase +import java.io.File + +actual object PlatformDatabaseBuilder { + actual fun getDatabaseBuilder(platformContext: PlatformContext): RoomDatabase.Builder { + val dbFile = File(System.getProperty("java.io.tmpdir"), "aux.db") // TODO: Stop using tmpDir on desktop... + return Room.databaseBuilder( + name = dbFile.absolutePath, + ) + } +} \ No newline at end of file