Room Database setup

This commit is contained in:
Kgothatso Ngako
2026-03-26 00:20:20 +02:00
parent f8e7a20b98
commit 3dea688eea
16 changed files with 242 additions and 50 deletions

View File

@@ -0,0 +1,7 @@
package ac.aux.compose
import android.content.Context
actual class PlatformContext(
val applicationContext: Context
)

View File

@@ -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<AuxDatabase> {
val appContext = platformContext.applicationContext
val dbFile = appContext.getDatabasePath("aux.db")
return Room.databaseBuilder<AuxDatabase>(
context = appContext,
name = dbFile.absolutePath
)
}
}

View File

@@ -0,0 +1,7 @@
package ac.aux.compose
class AuxGlobal(
val platformContext: PlatformContext
) {
}

View File

@@ -0,0 +1,3 @@
package ac.aux.compose
expect class PlatformContext

View File

@@ -0,0 +1,9 @@
package ac.aux.compose.database
import androidx.room.RoomDatabaseConstructor
@Suppress("KotlinNoActualForExpect", "NO_ACTUAL_FOR_EXPECT")
expect object AuxDatabaseConstructor: RoomDatabaseConstructor<AuxDatabase> {
override fun initialize(): AuxDatabase
}

View File

@@ -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<AuxDatabase>
}
fun getRoomDatabase(
builder: RoomDatabase.Builder<AuxDatabase>
): AuxDatabase {
return builder
.setDriver(BundledSQLiteDriver())
.setQueryCoroutineContext(Dispatchers.IO)
.build()
}

View File

@@ -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...

View File

@@ -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?
}

View File

@@ -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")
}

View File

@@ -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

View File

@@ -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
)
}
)
}
}
}

View File

@@ -0,0 +1,3 @@
package ac.aux.compose
actual class PlatformContext

View File

@@ -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<AuxDatabase> {
val dbFilePath = documentDirectory() + "/aux.db"
return Room.databaseBuilder<AuxDatabase>(
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)
}
}

View File

@@ -0,0 +1,3 @@
package ac.aux.compose
actual class PlatformContext

View File

@@ -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<AuxDatabase> {
val dbFile = File(System.getProperty("java.io.tmpdir"), "aux.db") // TODO: Stop using tmpDir on desktop...
return Room.databaseBuilder<AuxDatabase>(
name = dbFile.absolutePath,
)
}
}