Skip to content

Commit

Permalink
Merge pull request #603 from igorescodro/kmp/pre-populate
Browse files Browse the repository at this point in the history
✨ Introduce category pre-population on iOS
  • Loading branch information
igorescodro authored Oct 5, 2023
2 parents c35492f + e2eacba commit b964a82
Show file tree
Hide file tree
Showing 10 changed files with 140 additions and 66 deletions.
3 changes: 3 additions & 0 deletions data/local/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,12 @@ kotlin {
commonDependencies {
implementation(projects.libraries.coroutines)
implementation(projects.data.repository)
implementation(projects.resources)

implementation(libs.koin.core)
implementation(libs.kotlinx.datetime)
implementation(libs.sqldelight.coroutines)
implementation(libs.moko.resources.core)
}
androidDependencies {
implementation(libs.sqldelight.driver)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,63 +1,41 @@
package com.escodro.local.provider

import android.annotation.SuppressLint
import android.content.Context
import app.cash.sqldelight.db.SqlDriver
import app.cash.sqldelight.driver.android.AndroidSqliteDriver
import com.escodro.coroutines.AppCoroutineScope
import com.escodro.local.AlkaaDatabase
import com.escodro.local.Category
import com.escodro.local.R
import com.escodro.resources.MR
import dev.icerock.moko.resources.desc.desc

/**
* Provides the platform-specific [SqlDriver] to be used in the database.
*/
internal class AndroidDriverFactory(
private val context: Context,
private val appCoroutineScope: AppCoroutineScope,
) : DriverFactory {

/**
* Creates the platform-specific [SqlDriver] to be used in the database.
*
* @param databaseName the database name
*
* @return the [SqlDriver] to be used in the database
*/
override fun createDriver(databaseName: String): SqlDriver =
AndroidSqliteDriver(AlkaaDatabase.Schema, context, databaseName)

override fun prepopulateDatabase(database: AlkaaDatabase, databaseName: String) {
val databaseFile = context.getDatabasePath(databaseName)
if (!databaseFile.exists()) {
appCoroutineScope.launch {
for (category in getDefaultCategoryList()) {
database.categoryQueries.insert(
category_name = category.category_name,
category_color = category.category_color,
)
}
}
}
}
override fun shouldPrepopulateDatabase(databaseName: String): Boolean =
!context.getDatabasePath(databaseName).exists()

@SuppressLint("ResourceType")
private fun getDefaultCategoryList() =
listOf(
Category(
category_id = 0,
category_name = context.getString(R.string.category_default_personal),
category_color = context.resources.getString(R.color.blue),
),
Category(
category_id = 0,
category_name = context.getString(R.string.category_default_work),
category_color = context.getString(R.color.green),
),
Category(
category_id = 0,
category_name = context.getString(R.string.category_default_shopping),
category_color = context.getString(R.color.orange),
),
)
override fun getPrepopulateData(): List<Category> = listOf(
Category(
category_id = 0,
category_name = MR.strings.category_default_personal.desc().toString(context),
category_color = context.getString(MR.colors.blue.resourceId),
),
Category(
category_id = 0,
category_name = MR.strings.category_default_work.desc().toString(context),
category_color = context.getString(MR.colors.green.resourceId),
),
Category(
category_id = 0,
category_name = MR.strings.category_default_shopping.desc().toString(context),
category_color = context.getString(MR.colors.orange.resourceId),
),
)
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.escodro.local.provider

import com.escodro.coroutines.AppCoroutineScope
import com.escodro.local.AlkaaDatabase
import com.escodro.local.Task
import com.escodro.local.converter.alarmIntervalAdapter
Expand All @@ -8,7 +9,10 @@ import com.escodro.local.converter.dateTimeAdapter
/**
* Repository with the local database.
*/
class DatabaseProvider(private val driverFactory: DriverFactory) {
internal class DatabaseProvider(
private val driverFactory: DriverFactory,
private val appCoroutineScope: AppCoroutineScope,
) {

private var database: AlkaaDatabase? = null

Expand All @@ -32,10 +36,23 @@ class DatabaseProvider(private val driverFactory: DriverFactory) {
),
)

driverFactory.prepopulateDatabase(database = database, databaseName = DATABASE_NAME)
prepopulateDatabase(database)
return database
}

private fun prepopulateDatabase(database: AlkaaDatabase) {
if (driverFactory.shouldPrepopulateDatabase(DATABASE_NAME)) {
appCoroutineScope.launch {
for (category in driverFactory.getPrepopulateData()) {
database.categoryQueries.insert(
category_name = category.category_name,
category_color = category.category_color,
)
}
}
}
}

private companion object {
private const val DATABASE_NAME = "todo-db"
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,35 @@
package com.escodro.local.provider

import app.cash.sqldelight.db.SqlDriver
import com.escodro.local.AlkaaDatabase
import com.escodro.local.Category

interface DriverFactory {
/**
* Provides the platform-specific [SqlDriver] to be used in the database.
*/
internal interface DriverFactory {

/**
* Creates the platform-specific [SqlDriver] to be used in the database.
*
* @param databaseName the database name
*
* @return the [SqlDriver] to be used in the database
*/
fun createDriver(databaseName: String): SqlDriver

// TODO better document here
fun prepopulateDatabase(database: AlkaaDatabase, databaseName: String)
/**
* Checks if the database is opening for the first time and should be prepopulated.
*
* @param databaseName the database name
*
* @return true if the database should be prepopulated, false otherwise
*/
fun shouldPrepopulateDatabase(databaseName: String): Boolean

/**
* Gets the prepopulate data to be inserted in the database.
*
* @return the prepopulate data
*/
fun getPrepopulateData(): List<Category>
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,69 @@ package com.escodro.local.provider
import app.cash.sqldelight.db.SqlDriver
import app.cash.sqldelight.driver.native.NativeSqliteDriver
import com.escodro.local.AlkaaDatabase
import com.escodro.local.Category
import com.escodro.resources.MR
import dev.icerock.moko.resources.desc.desc
import dev.icerock.moko.resources.getUIColor
import kotlinx.cinterop.CPointer
import kotlinx.cinterop.DoubleVarOf
import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.cinterop.get
import platform.CoreGraphics.CGColorGetComponents
import platform.CoreGraphics.CGFloat
import platform.Foundation.NSFileManager
import platform.Foundation.NSLibraryDirectory
import platform.Foundation.NSURL
import platform.Foundation.NSUserDomainMask
import platform.Foundation.URLByAppendingPathComponent
import platform.UIKit.UIColor

internal class IosDriverFactory : DriverFactory {
override fun createDriver(databaseName: String): SqlDriver =
NativeSqliteDriver(AlkaaDatabase.Schema, databaseName)

override fun prepopulateDatabase(
database: AlkaaDatabase,
databaseName: String,
) {
// TODO: Implement prepopulateDatabase
override fun shouldPrepopulateDatabase(databaseName: String): Boolean =
!databaseExists(databaseName)

override fun getPrepopulateData(): List<Category> = listOf(
Category(
category_id = 0,
category_name = MR.strings.category_default_personal.desc().localized(),
category_color = MR.colors.blue.getUIColor().toHex(),
),
Category(
category_id = 0,
category_name = MR.strings.category_default_work.desc().localized(),
category_color = MR.colors.green.getUIColor().toHex(),
),
Category(
category_id = 0,
category_name = MR.strings.category_default_shopping.desc().localized(),
category_color = MR.colors.orange.getUIColor().toHex(),
),
)

private fun databaseExists(databaseName: String): Boolean {
val fileManager = NSFileManager.defaultManager
val documentDirectory = NSFileManager.defaultManager.URLsForDirectory(
NSLibraryDirectory,
NSUserDomainMask,
).last() as NSURL
val file = documentDirectory
.URLByAppendingPathComponent("$DATABASE_PATH$databaseName")?.path
return fileManager.fileExistsAtPath(file ?: "")
}

@OptIn(ExperimentalForeignApi::class)
private fun UIColor.toHex(): String {
val components: CPointer<DoubleVarOf<CGFloat>>? = CGColorGetComponents(CGColor)
val r = components?.get(0)?.times(255)?.toInt()?.toString(16)?.padStart(2, '0') ?: "00"
val g = components?.get(1)?.times(255)?.toInt()?.toString(16)?.padStart(2, '0') ?: "00"
val b = components?.get(2)?.times(255)?.toInt()?.toString(16)?.padStart(2, '0') ?: "00"
return "#$r$g$b"
}

private companion object {
private const val DATABASE_PATH = "Application Support/databases/"
}
}
6 changes: 0 additions & 6 deletions data/local/src/main/res/values-pt-rBR/strings.xml

This file was deleted.

6 changes: 0 additions & 6 deletions data/local/src/main/res/values/strings.xml

This file was deleted.

5 changes: 5 additions & 0 deletions resources/src/commonMain/resources/MR/base/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -90,4 +90,9 @@
<string name="preference_title_settings">Settings</string>
<string name="preference_title_open_source">Open source licenses</string>

<!-- Database -->
<string name="category_default_personal">Personal</string>
<string name="category_default_work">Work</string>
<string name="category_default_shopping">Shopping List</string>

</resources>
File renamed without changes.
5 changes: 5 additions & 0 deletions resources/src/commonMain/resources/MR/pt-br/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -86,4 +86,9 @@
<string name="preference_title_features">Funcionalidades</string>
<string name="preference_title_settings">Preferências</string>
<string name="preference_title_open_source">Licenças de código aberto</string>

<!-- Database -->
<string name="category_default_personal">Pessoal</string>
<string name="category_default_work">Trabalho</string>
<string name="category_default_shopping">Lista de Compras</string>
</resources>

0 comments on commit b964a82

Please sign in to comment.