diff --git a/common/build.gradle b/common/build.gradle
index 9597bd30a20..2ab8e52cd06 100644
--- a/common/build.gradle
+++ b/common/build.gradle
@@ -1,6 +1,8 @@
plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
+ id "com.android.library"
+ id "kotlin-android-extensions"
}
apply from: "$mppProjectWithSerializationPresetPath"
diff --git a/common/src/main/AndroidManifest.xml b/common/src/main/AndroidManifest.xml
new file mode 100644
index 00000000000..e62d6f86575
--- /dev/null
+++ b/common/src/main/AndroidManifest.xml
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/common/src/main/kotlin/dev/inmo/micro_utils/common/Mapping.kt b/common/src/main/kotlin/dev/inmo/micro_utils/common/Mapping.kt
new file mode 100644
index 00000000000..c9483694bbf
--- /dev/null
+++ b/common/src/main/kotlin/dev/inmo/micro_utils/common/Mapping.kt
@@ -0,0 +1,7 @@
+package dev.inmo.micro_utils.common
+
+@Suppress("UNCHECKED_CAST", "SimplifiableCall")
+inline fun Iterable.mapNotNullA(transform: (T) -> R?): List = map(transform).filter { it != null } as List
+
+@Suppress("UNCHECKED_CAST", "SimplifiableCall")
+inline fun Array.mapNotNullA(mapper: (T) -> R?): List = map(mapper).filter { it != null } as List
diff --git a/coroutines/build.gradle b/coroutines/build.gradle
index 7616133d4f6..6cfeab046d3 100644
--- a/coroutines/build.gradle
+++ b/coroutines/build.gradle
@@ -1,6 +1,8 @@
plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
+ id "com.android.library"
+ id "kotlin-android-extensions"
}
apply from: "$mppProjectWithSerializationPresetPath"
diff --git a/coroutines/src/main/AndroidManifest.xml b/coroutines/src/main/AndroidManifest.xml
new file mode 100644
index 00000000000..4536ab400ac
--- /dev/null
+++ b/coroutines/src/main/AndroidManifest.xml
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/defaultAndroidSettings b/defaultAndroidSettings
new file mode 100644
index 00000000000..8e8909ff220
--- /dev/null
+++ b/defaultAndroidSettings
@@ -0,0 +1,31 @@
+android {
+ compileSdkVersion "$android_compileSdkVersion".toInteger()
+ buildToolsVersion "$android_buildToolsVersion"
+
+ defaultConfig {
+ minSdkVersion "$android_minSdkVersion".toInteger()
+ targetSdkVersion "$android_compileSdkVersion".toInteger()
+ versionCode "${android_code_version}".toInteger()
+ versionName "$version"
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ }
+ }
+
+ packagingOptions {
+ exclude 'META-INF/kotlinx-serialization-runtime.kotlin_module'
+ exclude 'META-INF/kotlinx-serialization-cbor.kotlin_module'
+ exclude 'META-INF/kotlinx-serialization-properties.kotlin_module'
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+
+ kotlinOptions {
+ jvmTarget = JavaVersion.VERSION_1_8.toString()
+ }
+}
diff --git a/extensions.gradle b/extensions.gradle
index 8a736a982cb..dd5e93145fa 100644
--- a/extensions.gradle
+++ b/extensions.gradle
@@ -23,6 +23,8 @@ allprojects {
mppJavaProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJavaProject"
mppAndroidProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppAndroidProject"
+ defaultAndroidSettingsPresetPath = "${rootProject.projectDir.absolutePath}/defaultAndroidSettings"
+
publishGradlePath = "${rootProject.projectDir.absolutePath}/publish.gradle"
publishMavenPath = "${rootProject.projectDir.absolutePath}/maven.publish.gradle"
}
diff --git a/gradle.properties b/gradle.properties
index d1fd270523a..250c56a1160 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -3,6 +3,8 @@ org.gradle.parallel=true
kotlin.js.generate.externals=true
kotlin.incremental=true
kotlin.incremental.js=true
+android.useAndroidX=true
+android.enableJetifier=true
kotlin_version=1.4.10
kotlin_coroutines_version=1.4.1
@@ -20,10 +22,15 @@ uuidVersion=0.2.2
# ANDROID
+core_ktx_version=1.3.2
+
android_minSdkVersion=24
android_compileSdkVersion=30
android_buildToolsVersion=30.0.2
dexcount_version=2.0.0-RC1
+junit_version=4.12
+test_ext_junit_version=1.1.2
+espresso_core=3.3.0
group=dev.inmo
version=0.3.1
diff --git a/ktor/client/build.gradle b/ktor/client/build.gradle
index 280a7647124..6983e6f63b4 100644
--- a/ktor/client/build.gradle
+++ b/ktor/client/build.gradle
@@ -1,6 +1,8 @@
plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
+ id "com.android.library"
+ id "kotlin-android-extensions"
}
apply from: "$mppProjectWithSerializationPresetPath"
diff --git a/ktor/client/src/main/AndroidManifest.xml b/ktor/client/src/main/AndroidManifest.xml
new file mode 100644
index 00000000000..034ad33094d
--- /dev/null
+++ b/ktor/client/src/main/AndroidManifest.xml
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/ktor/common/build.gradle b/ktor/common/build.gradle
index 0d6bb922b88..3413a1a4877 100644
--- a/ktor/common/build.gradle
+++ b/ktor/common/build.gradle
@@ -1,6 +1,8 @@
plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
+ id "com.android.library"
+ id "kotlin-android-extensions"
}
apply from: "$mppProjectWithSerializationPresetPath"
diff --git a/ktor/common/src/main/AndroidManifest.xml b/ktor/common/src/main/AndroidManifest.xml
new file mode 100644
index 00000000000..9a1797bdc71
--- /dev/null
+++ b/ktor/common/src/main/AndroidManifest.xml
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/mime_types/build.gradle b/mime_types/build.gradle
index 9597bd30a20..2ab8e52cd06 100644
--- a/mime_types/build.gradle
+++ b/mime_types/build.gradle
@@ -1,6 +1,8 @@
plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
+ id "com.android.library"
+ id "kotlin-android-extensions"
}
apply from: "$mppProjectWithSerializationPresetPath"
diff --git a/mime_types/src/main/AndroidManifest.xml b/mime_types/src/main/AndroidManifest.xml
new file mode 100644
index 00000000000..be8221df297
--- /dev/null
+++ b/mime_types/src/main/AndroidManifest.xml
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/mppAndroidProject b/mppAndroidProject
index e4cb95af6f9..c9bee90b00f 100644
--- a/mppAndroidProject
+++ b/mppAndroidProject
@@ -21,32 +21,4 @@ kotlin {
}
}
-android {
- compileSdkVersion "$android_compileSdkVersion".toInteger()
- defaultConfig {
- minSdkVersion "$android_minSdkVersion".toInteger()
- targetSdkVersion "$android_compileSdkVersion".toInteger()
- versionCode "${android_code_version}".toInteger()
- versionName "$version"
- }
- buildTypes {
- release {
- minifyEnabled false
- }
- }
-
- packagingOptions {
- exclude 'META-INF/kotlinx-serialization-runtime.kotlin_module'
- exclude 'META-INF/kotlinx-serialization-cbor.kotlin_module'
- exclude 'META-INF/kotlinx-serialization-properties.kotlin_module'
- }
-
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_8
- targetCompatibility JavaVersion.VERSION_1_8
- }
-
- kotlinOptions {
- jvmTarget = JavaVersion.VERSION_1_8.toString()
- }
-}
+apply from: "$defaultAndroidSettingsPresetPath"
diff --git a/mppProjectWithSerialization b/mppProjectWithSerialization
index 7ee94f9fec3..08ec899fe5d 100644
--- a/mppProjectWithSerialization
+++ b/mppProjectWithSerialization
@@ -9,6 +9,9 @@ kotlin {
browser()
nodejs()
}
+ android {
+ publishLibraryVariants()
+ }
sourceSets {
commonMain {
@@ -34,5 +37,14 @@ kotlin {
implementation kotlin('test-junit')
}
}
+ androidTest {
+ dependencies {
+ implementation kotlin('test-junit')
+ implementation "androidx.test.ext:junit:$test_ext_junit_version"
+ implementation "androidx.test.espresso:espresso-core:$espresso_core"
+ }
+ }
}
}
+
+apply from: "$defaultAndroidSettingsPresetPath"
diff --git a/pagination/common/build.gradle b/pagination/common/build.gradle
index 9597bd30a20..2ab8e52cd06 100644
--- a/pagination/common/build.gradle
+++ b/pagination/common/build.gradle
@@ -1,6 +1,8 @@
plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
+ id "com.android.library"
+ id "kotlin-android-extensions"
}
apply from: "$mppProjectWithSerializationPresetPath"
diff --git a/pagination/common/src/main/AndroidManifest.xml b/pagination/common/src/main/AndroidManifest.xml
new file mode 100644
index 00000000000..3d678ce1cad
--- /dev/null
+++ b/pagination/common/src/main/AndroidManifest.xml
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/pagination/ktor/common/build.gradle b/pagination/ktor/common/build.gradle
index dfbe880de3d..18c9967a1ab 100644
--- a/pagination/ktor/common/build.gradle
+++ b/pagination/ktor/common/build.gradle
@@ -1,6 +1,8 @@
plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
+ id "com.android.library"
+ id "kotlin-android-extensions"
}
apply from: "$mppProjectWithSerializationPresetPath"
diff --git a/pagination/ktor/common/src/main/AndroidManifest.xml b/pagination/ktor/common/src/main/AndroidManifest.xml
new file mode 100644
index 00000000000..b2896442be7
--- /dev/null
+++ b/pagination/ktor/common/src/main/AndroidManifest.xml
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/repos/android/build.gradle b/repos/android/build.gradle
index d35a26fc165..aa5218eaf68 100644
--- a/repos/android/build.gradle
+++ b/repos/android/build.gradle
@@ -6,3 +6,19 @@ plugins {
}
apply from: "$mppAndroidProjectPresetPath"
+
+kotlin {
+ sourceSets {
+ commonMain {
+ dependencies {
+ api internalProject("micro_utils.repos.common")
+ api internalProject("micro_utils.coroutines")
+ }
+ }
+ androidMain {
+ dependencies {
+ implementation "androidx.core:core-ktx:$core_ktx_version"
+ }
+ }
+ }
+}
diff --git a/repos/android/src/main/AndroidManifest.xml b/repos/android/src/main/AndroidManifest.xml
index 49151ff7948..cbd8db25bb7 100644
--- a/repos/android/src/main/AndroidManifest.xml
+++ b/repos/android/src/main/AndroidManifest.xml
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/repos/common/build.gradle b/repos/common/build.gradle
index a36a04e5b95..e78548f9238 100644
--- a/repos/common/build.gradle
+++ b/repos/common/build.gradle
@@ -1,6 +1,8 @@
plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
+ id "com.android.library"
+ id "kotlin-android-extensions"
}
apply from: "$mppProjectWithSerializationPresetPath"
@@ -15,5 +17,13 @@ kotlin {
api "com.benasher44:uuid:$uuidVersion"
}
}
+
+ androidMain {
+ dependencies {
+ api "androidx.core:core-ktx:$core_ktx_version"
+ api internalProject("micro_utils.common")
+ api internalProject("micro_utils.coroutines")
+ }
+ }
}
}
\ No newline at end of file
diff --git a/repos/common/src/main/AndroidManifest.xml b/repos/common/src/main/AndroidManifest.xml
new file mode 100644
index 00000000000..66982e66339
--- /dev/null
+++ b/repos/common/src/main/AndroidManifest.xml
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/repos/common/src/main/kotlin/dev/inmo/micro_utils/repos/ColumnType.kt b/repos/common/src/main/kotlin/dev/inmo/micro_utils/repos/ColumnType.kt
new file mode 100644
index 00000000000..b2026a216f5
--- /dev/null
+++ b/repos/common/src/main/kotlin/dev/inmo/micro_utils/repos/ColumnType.kt
@@ -0,0 +1,46 @@
+package dev.inmo.micro_utils.repos
+
+sealed class ColumnType(
+ typeName: String,
+ nullable: Boolean
+) {
+ open val asType: String = "$typeName${if (!nullable) " not null" else ""}"
+ sealed class Text(
+ nullable: Boolean
+ ) : ColumnType("text", nullable) {
+ object NULLABLE : Text(true)
+ object NOT_NULLABLE : Text(false)
+ }
+ sealed class Numeric(
+ typeName: String,
+ autoincrement: Boolean = false,
+ primaryKey: Boolean = false,
+ nullable: Boolean = false
+ ) : ColumnType(
+ typeName,
+ nullable
+ ) {
+ override val asType: String = "${super.asType}${if (primaryKey) " primary key" else ""}${if (autoincrement) " autoincrement" else ""}"
+
+ class INTEGER(
+ autoincrement: Boolean = false,
+ primaryKey: Boolean = false,
+ nullable: Boolean = false
+ ) : Numeric(
+ "integer",
+ autoincrement,
+ primaryKey,
+ nullable
+ )
+ class DOUBLE(autoincrement: Boolean = false, primaryKey: Boolean = false, nullable: Boolean = false) : Numeric(
+ "double",
+ autoincrement,
+ primaryKey,
+ nullable
+ )
+ }
+
+ override fun toString(): String {
+ return asType
+ }
+}
\ No newline at end of file
diff --git a/repos/common/src/main/kotlin/dev/inmo/micro_utils/repos/ContentValuesOf.kt b/repos/common/src/main/kotlin/dev/inmo/micro_utils/repos/ContentValuesOf.kt
new file mode 100644
index 00000000000..44b80bd7dc1
--- /dev/null
+++ b/repos/common/src/main/kotlin/dev/inmo/micro_utils/repos/ContentValuesOf.kt
@@ -0,0 +1,8 @@
+package dev.inmo.micro_utils.repos
+
+import androidx.core.content.contentValuesOf
+
+@Suppress("UNCHECKED_CAST", "SimplifiableCall")
+fun contentValuesOfNotNull(vararg pairs: Pair?) = contentValuesOf(
+ *(pairs.filter { it != null } as List>).toTypedArray()
+)
diff --git a/repos/common/src/main/kotlin/dev/inmo/micro_utils/repos/CursorMapping.kt b/repos/common/src/main/kotlin/dev/inmo/micro_utils/repos/CursorMapping.kt
new file mode 100644
index 00000000000..e74374ac802
--- /dev/null
+++ b/repos/common/src/main/kotlin/dev/inmo/micro_utils/repos/CursorMapping.kt
@@ -0,0 +1,21 @@
+package dev.inmo.micro_utils.repos
+
+import android.database.Cursor
+
+inline fun Cursor.map(
+ block: (Cursor) -> T
+): List {
+ val result = mutableListOf()
+ if (moveToFirst()) {
+ do {
+ result.add(block(this))
+ } while (moveToNext())
+ }
+ return result
+}
+
+fun Cursor.firstOrNull(): Cursor? = if (moveToFirst()) {
+ this
+} else {
+ null
+}
diff --git a/repos/common/src/main/kotlin/dev/inmo/micro_utils/repos/DatabaseCoroutineContext.kt b/repos/common/src/main/kotlin/dev/inmo/micro_utils/repos/DatabaseCoroutineContext.kt
new file mode 100644
index 00000000000..8a2932eb0fe
--- /dev/null
+++ b/repos/common/src/main/kotlin/dev/inmo/micro_utils/repos/DatabaseCoroutineContext.kt
@@ -0,0 +1,6 @@
+package dev.inmo.micro_utils.repos
+
+import kotlinx.coroutines.newSingleThreadContext
+import kotlin.coroutines.CoroutineContext
+
+val DatabaseCoroutineContext: CoroutineContext = newSingleThreadContext("db-context")
diff --git a/repos/common/src/main/kotlin/dev/inmo/micro_utils/repos/DatabaseOperations.kt b/repos/common/src/main/kotlin/dev/inmo/micro_utils/repos/DatabaseOperations.kt
new file mode 100644
index 00000000000..b1cfdb28804
--- /dev/null
+++ b/repos/common/src/main/kotlin/dev/inmo/micro_utils/repos/DatabaseOperations.kt
@@ -0,0 +1,64 @@
+package dev.inmo.micro_utils.repos
+
+import android.database.Cursor
+import android.database.sqlite.SQLiteDatabase
+
+fun createTableQuery(
+ tableName: String,
+ vararg columnsToTypes: Pair
+) = "create table $tableName (${columnsToTypes.joinToString(", ") { "${it.first} ${it.second}" }});"
+
+fun SQLiteDatabase.createTable(
+ tableName: String,
+ vararg columnsToTypes: Pair,
+ onInit: (SQLiteDatabase.() -> Unit)? = null
+): Boolean {
+ val existing = rawQuery("SELECT name FROM sqlite_master WHERE type='table' AND name='$tableName'", null).use {
+ it.count > 0
+ }
+ return if (existing) {
+ false
+ // TODO:: add upgrade opportunity
+ } else {
+ execSQL(createTableQuery(tableName, *columnsToTypes))
+ onInit ?.invoke(this)
+ true
+ }
+}
+
+fun Cursor.getString(columnName: String) = getString(
+ getColumnIndex(columnName)
+)
+
+fun Cursor.getLong(columnName: String) = getLong(
+ getColumnIndex(columnName)
+)
+
+fun Cursor.getInt(columnName: String) = getInt(
+ getColumnIndex(columnName)
+)
+
+fun Cursor.getDouble(columnName: String) = getDouble(
+ getColumnIndex(columnName)
+)
+
+fun SQLiteDatabase.select(
+ table: String,
+ columns: Array? = null,
+ selection: String? = null,
+ selectionArgs: Array? = null,
+ groupBy: String? = null,
+ having: String? = null,
+ orderBy: String? = null,
+ limit: String? = null
+) = query(
+ table, columns, selection, selectionArgs, groupBy, having, orderBy, limit
+)
+
+fun makePlaceholders(count: Int): String {
+ return (0 until count).joinToString { "?" }
+}
+
+fun makeStringPlaceholders(count: Int): String {
+ return (0 until count).joinToString { "\"?\"" }
+}
diff --git a/repos/common/src/main/kotlin/dev/inmo/micro_utils/repos/DatabasePaginationExtensions.kt b/repos/common/src/main/kotlin/dev/inmo/micro_utils/repos/DatabasePaginationExtensions.kt
new file mode 100644
index 00000000000..df72f0b4b61
--- /dev/null
+++ b/repos/common/src/main/kotlin/dev/inmo/micro_utils/repos/DatabasePaginationExtensions.kt
@@ -0,0 +1,8 @@
+package dev.inmo.micro_utils.repos
+
+import dev.inmo.micro_utils.pagination.Pagination
+import dev.inmo.micro_utils.pagination.firstIndex
+
+fun limitClause(size: Long, since: Long? = null) = "${since ?.let { "$it, " } ?: ""}$size"
+fun limitClause(size: Int, since: Int? = null) = limitClause(size.toLong(), since ?.toLong())
+fun Pagination.limitClause() = limitClause(size, firstIndex)
diff --git a/repos/common/src/main/kotlin/dev/inmo/micro_utils/repos/DatabaseTransactions.kt b/repos/common/src/main/kotlin/dev/inmo/micro_utils/repos/DatabaseTransactions.kt
new file mode 100644
index 00000000000..1ac58f237a5
--- /dev/null
+++ b/repos/common/src/main/kotlin/dev/inmo/micro_utils/repos/DatabaseTransactions.kt
@@ -0,0 +1,45 @@
+package dev.inmo.micro_utils.repos
+
+import android.database.sqlite.SQLiteDatabase
+import dev.inmo.micro_utils.coroutines.safely
+import kotlinx.coroutines.withContext
+
+suspend fun SQLiteDatabase.transaction(block: suspend SQLiteDatabase.() -> T): T {
+ return withContext(DatabaseCoroutineContext) {
+ when {
+ inTransaction() -> {
+ block()
+ }
+ else -> {
+ beginTransaction()
+ safely(
+ {
+ endTransaction()
+ throw it
+ }
+ ) {
+ block().also {
+ setTransactionSuccessful()
+ endTransaction()
+ }
+ }
+ }
+ }
+ }
+}
+
+inline fun SQLiteDatabase.inlineTransaction(block: SQLiteDatabase.() -> T): T {
+ return when {
+ inTransaction() -> block()
+ else -> {
+ beginTransaction()
+ try {
+ block().also {
+ setTransactionSuccessful()
+ }
+ } finally {
+ endTransaction()
+ }
+ }
+ }
+}
diff --git a/repos/common/src/main/kotlin/dev/inmo/micro_utils/repos/InternalId.kt b/repos/common/src/main/kotlin/dev/inmo/micro_utils/repos/InternalId.kt
new file mode 100644
index 00000000000..c95db5f7ab9
--- /dev/null
+++ b/repos/common/src/main/kotlin/dev/inmo/micro_utils/repos/InternalId.kt
@@ -0,0 +1,7 @@
+package dev.inmo.micro_utils.repos
+
+val internalId = "_id"
+val internalIdType = ColumnType.Numeric.INTEGER(
+ autoincrement = true,
+ primaryKey = true
+)
diff --git a/repos/common/src/main/kotlin/dev/inmo/micro_utils/repos/StandardSQLHelper.kt b/repos/common/src/main/kotlin/dev/inmo/micro_utils/repos/StandardSQLHelper.kt
new file mode 100644
index 00000000000..8de4676138a
--- /dev/null
+++ b/repos/common/src/main/kotlin/dev/inmo/micro_utils/repos/StandardSQLHelper.kt
@@ -0,0 +1,51 @@
+package dev.inmo.micro_utils.repos
+
+import android.content.Context
+import android.database.DatabaseErrorHandler
+import android.database.sqlite.SQLiteDatabase
+import android.database.sqlite.SQLiteOpenHelper
+import dev.inmo.micro_utils.coroutines.safely
+import kotlin.coroutines.Continuation
+import kotlin.coroutines.resume
+import kotlin.coroutines.resumeWithException
+
+private data class CallbackContinuationPair (
+ val callback: suspend SQLiteDatabase.() -> T,
+ val continuation: Continuation
+) {
+ suspend fun SQLiteDatabase.execute() {
+ safely(
+ {
+ continuation.resumeWithException(it)
+ }
+ ) {
+ continuation.resume(callback())
+ }
+ }
+}
+
+class StandardSQLHelper(
+ context: Context,
+ name: String,
+ factory: SQLiteDatabase.CursorFactory? = null,
+ version: Int = 1,
+ errorHandler: DatabaseErrorHandler? = null
+) {
+ val sqlOpenHelper = object : SQLiteOpenHelper(context, name, factory, version, errorHandler) {
+ override fun onCreate(db: SQLiteDatabase?) {}
+
+ override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {}
+ }
+
+ suspend fun writableTransaction(block: suspend SQLiteDatabase.() -> T): T = sqlOpenHelper.writableTransaction(block)
+
+ suspend fun readableTransaction(block: suspend SQLiteDatabase.() -> T): T = sqlOpenHelper.readableTransaction(block)
+}
+
+suspend fun SQLiteOpenHelper.writableTransaction(block: suspend SQLiteDatabase.() -> T): T {
+ return writableDatabase.transaction(block)
+}
+
+suspend fun SQLiteOpenHelper.readableTransaction(block: suspend SQLiteDatabase.() -> T): T {
+ return readableDatabase.transaction(block)
+}
diff --git a/repos/common/src/main/kotlin/dev/inmo/micro_utils/repos/crud/AbstractAndroidCRUDRepo.kt b/repos/common/src/main/kotlin/dev/inmo/micro_utils/repos/crud/AbstractAndroidCRUDRepo.kt
new file mode 100644
index 00000000000..bd1109b71ea
--- /dev/null
+++ b/repos/common/src/main/kotlin/dev/inmo/micro_utils/repos/crud/AbstractAndroidCRUDRepo.kt
@@ -0,0 +1,67 @@
+package dev.inmo.micro_utils.repos.crud
+
+import android.database.Cursor
+import android.database.sqlite.SQLiteDatabase
+import dev.inmo.micro_utils.pagination.Pagination
+import dev.inmo.micro_utils.pagination.PaginationResult
+import dev.inmo.micro_utils.pagination.createPaginationResult
+import dev.inmo.micro_utils.repos.*
+
+val T.asId: String
+ get() = (this as? String) ?: this!!.toString()
+
+abstract class AbstractAndroidCRUDRepo(
+ protected val helper: StandardSQLHelper
+) : ReadStandardCRUDRepo {
+ protected abstract val tableName: String
+ protected abstract val idColumnName: String
+ protected abstract suspend fun Cursor.toObject(): ObjectType
+ protected fun SQLiteDatabase.count(): Long = select(tableName).use {
+ it.count
+ }.toLong()
+
+ override suspend fun contains(id: IdType): Boolean = helper.readableTransaction {
+ select(
+ tableName,
+ null,
+ "$idColumnName=?",
+ arrayOf(id.asId)
+ ).use {
+ it.count > 0
+ }
+ }
+
+ override suspend fun getById(id: IdType): ObjectType? = helper.readableTransaction {
+ select(
+ tableName,
+ selection = "$idColumnName=?",
+ selectionArgs = arrayOf(id.asId),
+ limit = limitClause(1)
+ ).use { c ->
+ if (c.moveToFirst()) {
+ c.toObject()
+ } else {
+ null
+ }
+ }
+ }
+
+ override suspend fun getByPagination(pagination: Pagination): PaginationResult {
+ return helper.readableTransaction {
+ select(
+ tableName,
+ limit = pagination.limitClause()
+ ).use {
+ if (it.moveToFirst()) {
+ val resultList = mutableListOf(it.toObject())
+ while (it.moveToNext()) {
+ resultList.add(it.toObject())
+ }
+ resultList.createPaginationResult(pagination, count())
+ } else {
+ emptyList().createPaginationResult(pagination, 0)
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/repos/common/src/main/kotlin/dev/inmo/micro_utils/repos/crud/AbstractMutableAndroidCRUDRepo.kt b/repos/common/src/main/kotlin/dev/inmo/micro_utils/repos/crud/AbstractMutableAndroidCRUDRepo.kt
new file mode 100644
index 00000000000..83bab7ad2b9
--- /dev/null
+++ b/repos/common/src/main/kotlin/dev/inmo/micro_utils/repos/crud/AbstractMutableAndroidCRUDRepo.kt
@@ -0,0 +1,104 @@
+package dev.inmo.micro_utils.repos.crud
+
+import android.content.ContentValues
+import dev.inmo.micro_utils.common.mapNotNullA
+import dev.inmo.micro_utils.repos.*
+import kotlinx.coroutines.channels.BroadcastChannel
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.asFlow
+
+abstract class AbstractMutableAndroidCRUDRepo(
+ helper: StandardSQLHelper
+) : WriteStandardCRUDRepo,
+ AbstractAndroidCRUDRepo(helper) {
+ protected val newObjectsChannel = BroadcastChannel(64)
+ protected val updateObjectsChannel = BroadcastChannel(64)
+ protected val deleteObjectsIdsChannel = BroadcastChannel(64)
+ override val newObjectsFlow: Flow = newObjectsChannel.asFlow()
+ override val updatedObjectsFlow: Flow = updateObjectsChannel.asFlow()
+ override val deletedObjectsIdsFlow: Flow = deleteObjectsIdsChannel.asFlow()
+
+ protected abstract suspend fun InputValueType.asContentValues(id: IdType? = null): ContentValues
+
+ override suspend fun create(values: List): List {
+ val indexes = helper.writableTransaction {
+ values.map {
+ insert(tableName, null, it.asContentValues())
+ }
+ }
+ return helper.readableTransaction {
+ indexes.mapNotNullA { i ->
+ select(
+ tableName,
+ selection = "$internalId=?",
+ selectionArgs = arrayOf(i.toString())
+ ).use { c ->
+ if (c.moveToFirst()) {
+ c.toObject()
+ } else {
+ null
+ }
+ }
+ }
+ }.also {
+ it.forEach {
+ newObjectsChannel.send(it)
+ }
+ }
+ }
+
+ override suspend fun deleteById(ids: List) {
+ val deleted = mutableListOf()
+ helper.writableTransaction {
+ ids.forEach { id ->
+ delete(tableName, "$idColumnName=?", arrayOf(id.asId)).also {
+ if (it > 0) {
+ deleted.add(id)
+ }
+ }
+ }
+ }
+ deleted.forEach {
+ deleteObjectsIdsChannel.send(it)
+ }
+ }
+
+ override suspend fun update(id: IdType, value: InputValueType): ObjectType? {
+ val asContentValues = value.asContentValues(id)
+ if (asContentValues.keySet().isNotEmpty()) {
+ helper.writableTransaction {
+ update(
+ tableName,
+ asContentValues,
+ "$idColumnName=?",
+ arrayOf(id.asId)
+ )
+ }
+ }
+ return getById(id) ?.also {
+ updateObjectsChannel.send(it)
+ }
+ }
+
+ override suspend fun update(values: List>): List {
+ helper.writableTransaction {
+ values.forEach { (id, value) ->
+ update(
+ tableName,
+ value.asContentValues(id),
+ "$idColumnName=?",
+ arrayOf(id.asId)
+ )
+ }
+ }
+ return values.mapNotNullA {
+ getById(it.first)
+ }.also {
+ it.forEach {
+ updateObjectsChannel.send(it)
+ }
+ }
+ }
+
+ override suspend fun count(): Long = helper.readableTransaction { select(tableName).use { it.count.toLong() } }
+}
\ No newline at end of file
diff --git a/repos/common/src/main/kotlin/dev/inmo/micro_utils/repos/keyvalue/KeyValueStore.kt b/repos/common/src/main/kotlin/dev/inmo/micro_utils/repos/keyvalue/KeyValueStore.kt
new file mode 100644
index 00000000000..bcf16a4f7a6
--- /dev/null
+++ b/repos/common/src/main/kotlin/dev/inmo/micro_utils/repos/keyvalue/KeyValueStore.kt
@@ -0,0 +1,130 @@
+package dev.inmo.micro_utils.repos.keyvalue
+
+import android.content.Context
+import android.content.SharedPreferences
+import androidx.core.content.edit
+import dev.inmo.micro_utils.pagination.Pagination
+import dev.inmo.micro_utils.pagination.PaginationResult
+import dev.inmo.micro_utils.pagination.utils.paginate
+import dev.inmo.micro_utils.pagination.utils.reverse
+import dev.inmo.micro_utils.repos.StandardKeyValueRepo
+import kotlinx.coroutines.channels.BroadcastChannel
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.asFlow
+
+private val cache = HashMap>()
+
+fun Context.keyValueStore(
+ name: String = "default",
+ cacheValues: Boolean = false
+): StandardKeyValueRepo {
+ return cache.getOrPut(name) {
+ KeyValueStore(this, name, cacheValues)
+ } as KeyValueStore
+}
+
+class KeyValueStore internal constructor (
+ c: Context,
+ preferencesName: String,
+ useCache: Boolean = false
+) : SharedPreferences.OnSharedPreferenceChangeListener, StandardKeyValueRepo {
+ private val sharedPreferences = c.getSharedPreferences(preferencesName, Context.MODE_PRIVATE)
+
+ private val cachedData = if (useCache) {
+ mutableMapOf()
+ } else {
+ null
+ }
+
+ private val onNewValueChannel = BroadcastChannel>(Channel.BUFFERED)
+ private val onValueRemovedChannel = BroadcastChannel(Channel.BUFFERED)
+
+ override val onNewValue: Flow> = onNewValueChannel.asFlow()
+ override val onValueRemoved: Flow = onValueRemovedChannel.asFlow()
+
+ init {
+ cachedData ?.let {
+ sharedPreferences.all.forEach {
+ if (it.value != null) {
+ cachedData[it.key] = it.value as Any
+ }
+ }
+ sharedPreferences.registerOnSharedPreferenceChangeListener(this)
+ }
+ }
+
+ override fun onSharedPreferenceChanged(sp: SharedPreferences, key: String) {
+ val value = sp.all[key]
+ cachedData ?: return
+ if (value != null) {
+ cachedData[key] = value
+ } else {
+ cachedData.remove(key)
+ }
+ }
+
+ override suspend fun get(k: String): T? {
+ return (cachedData ?. get(k) ?: sharedPreferences.all[k]) as? T
+ }
+
+ override suspend fun values(pagination: Pagination, reversed: Boolean): PaginationResult {
+ val resultPagination = if (reversed) pagination.reverse(count()) else pagination
+ return sharedPreferences.all.values.paginate(
+ resultPagination
+ ).let {
+ PaginationResult(
+ it.page,
+ it.pagesNumber,
+ it.results.map { it as T }.let { if (reversed) it.reversed() else it },
+ it.size
+ )
+ }
+ }
+
+ override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult {
+ val resultPagination = if (reversed) pagination.reverse(count()) else pagination
+ return sharedPreferences.all.keys.paginate(
+ resultPagination
+ ).let {
+ PaginationResult(
+ it.page,
+ it.pagesNumber,
+ it.results.let { if (reversed) it.reversed() else it },
+ it.size
+ )
+ }
+ }
+
+ override suspend fun contains(key: String): Boolean = sharedPreferences.contains(key)
+
+ override suspend fun count(): Long = sharedPreferences.all.size.toLong()
+
+ override suspend fun set(toSet: Map) {
+ sharedPreferences.edit {
+ toSet.forEach { (k, v) ->
+ when(v) {
+ is Int -> putInt(k, v)
+ is Long -> putLong(k, v)
+ is Float -> putFloat(k, v)
+ is String -> putString(k, v)
+ is Boolean -> putBoolean(k, v)
+ is Set<*> -> putStringSet(k, v.map { (it as? String) ?: it.toString() }.toSet())
+ else -> error(
+ "Currently supported only primitive types and set for SharedPreferences KeyValue repos"
+ )
+ }
+ }
+ }
+ toSet.forEach { (k, v) ->
+ onNewValueChannel.send(k to v)
+ }
+ }
+
+ override suspend fun unset(toUnset: List) {
+ sharedPreferences.edit {
+ toUnset.forEach { remove(it) }
+ }
+ toUnset.forEach { onValueRemovedChannel.send(it) }
+ }
+}
diff --git a/repos/common/src/main/kotlin/dev/inmo/micro_utils/repos/onetomany/OneToManyAndroidRepo.kt b/repos/common/src/main/kotlin/dev/inmo/micro_utils/repos/onetomany/OneToManyAndroidRepo.kt
new file mode 100644
index 00000000000..de4b6f22078
--- /dev/null
+++ b/repos/common/src/main/kotlin/dev/inmo/micro_utils/repos/onetomany/OneToManyAndroidRepo.kt
@@ -0,0 +1,187 @@
+package dev.inmo.micro_utils.repos.onetomany
+
+import android.database.sqlite.SQLiteOpenHelper
+import androidx.core.content.contentValuesOf
+import dev.inmo.micro_utils.common.mapNotNullA
+import dev.inmo.micro_utils.pagination.FirstPagePagination
+import dev.inmo.micro_utils.pagination.Pagination
+import dev.inmo.micro_utils.pagination.PaginationResult
+import dev.inmo.micro_utils.pagination.createPaginationResult
+import dev.inmo.micro_utils.pagination.utils.reverse
+import dev.inmo.micro_utils.repos.*
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.asSharedFlow
+import kotlinx.coroutines.runBlocking
+import kotlinx.serialization.KSerializer
+import kotlinx.serialization.json.Json
+
+private val internalSerialFormat = Json {
+ ignoreUnknownKeys = true
+}
+
+class OneToManyAndroidRepo(
+ private val tableName: String,
+ private val keySerializer: KSerializer,
+ private val valueSerializer: KSerializer,
+ private val helper: SQLiteOpenHelper
+) : OneToManyKeyValueRepo {
+ private val _onNewValue: MutableSharedFlow> = MutableSharedFlow()
+ override val onNewValue: Flow> = _onNewValue.asSharedFlow()
+ private val _onValueRemoved: MutableSharedFlow> = MutableSharedFlow()
+ override val onValueRemoved: Flow> = _onValueRemoved.asSharedFlow()
+ private val _onDataCleared = MutableSharedFlow()
+ override val onDataCleared: Flow = _onDataCleared.asSharedFlow()
+
+ private val idColumnName = "id"
+ private val valueColumnName = "value"
+
+ private fun Key.asId() = internalSerialFormat.encodeToString(keySerializer, this)
+ private fun Value.asValue() = internalSerialFormat.encodeToString(valueSerializer, this)
+ private fun String.asValue(): Value = internalSerialFormat.decodeFromString(valueSerializer, this)
+ private fun String.asKey(): Key = internalSerialFormat.decodeFromString(keySerializer, this)
+
+ init {
+ runBlocking(DatabaseCoroutineContext) {
+ helper.writableTransaction {
+ createTable(
+ tableName,
+ internalId to internalIdType,
+ idColumnName to ColumnType.Text.NOT_NULLABLE,
+ valueColumnName to ColumnType.Text.NULLABLE
+ )
+ }
+ }
+ }
+
+ override suspend fun add(toAdd: Map>) {
+ val added = mutableListOf>()
+ helper.writableTransaction {
+ toAdd.forEach { (k, values) ->
+ values.forEach { v ->
+ insert(
+ tableName,
+ null,
+ contentValuesOf(
+ idColumnName to k.asId(),
+ valueColumnName to v.asValue()
+ )
+ ).also {
+ if (it != -1L) {
+ added.add(k to v)
+ }
+ }
+ }
+ }
+ }
+ added.forEach { _onNewValue.emit(it) }
+ }
+
+ override suspend fun clear(k: Key) {
+ helper.writableTransaction {
+ delete(tableName, "$idColumnName=?", arrayOf(k.asId()))
+ }.also {
+ if (it > 0) {
+ _onDataCleared.emit(k)
+ }
+ }
+ }
+
+ override suspend fun contains(k: Key): Boolean = helper.readableTransaction {
+ select(tableName, selection = "$idColumnName=?", selectionArgs = arrayOf(k.asId()), limit = FirstPagePagination(1).limitClause()).use {
+ it.count > 0
+ }
+ }
+
+ override suspend fun contains(k: Key, v: Value): Boolean = helper.readableTransaction {
+ select(
+ tableName,
+ selection = "$idColumnName=? AND $valueColumnName=?",
+ selectionArgs = arrayOf(k.asId(), v.asValue()),
+ limit = FirstPagePagination(1).limitClause()
+ ).use {
+ it.count > 0
+ }
+ }
+
+ override suspend fun count(): Long =helper.readableTransaction {
+ select(
+ tableName
+ ).use {
+ it.count
+ }
+ }.toLong()
+
+ override suspend fun count(k: Key): Long = helper.readableTransaction {
+ select(tableName, selection = "$idColumnName=?", selectionArgs = arrayOf(k.asId()), limit = FirstPagePagination(1).limitClause()).use {
+ it.count
+ }
+ }.toLong()
+
+ override suspend fun get(
+ k: Key,
+ pagination: Pagination,
+ reversed: Boolean
+ ): PaginationResult = count(k).let { count ->
+ val resultPagination = pagination.let { if (reversed) pagination.reverse(count) else pagination }
+ helper.readableTransaction {
+ select(
+ tableName,
+ selection = "$idColumnName=?",
+ selectionArgs = arrayOf(k.asId()),
+ limit = resultPagination.limitClause()
+ ).use { c ->
+ mutableListOf().also {
+ if (c.moveToFirst()) {
+ do {
+ it.add(c.getString(valueColumnName).asValue())
+ } while (c.moveToNext())
+ }
+ }
+ }
+ }.createPaginationResult(
+ pagination,
+ count
+ )
+ }
+
+ override suspend fun keys(
+ pagination: Pagination,
+ reversed: Boolean
+ ): PaginationResult = count().let { count ->
+ val resultPagination = pagination.let { if (reversed) pagination.reverse(count) else pagination }
+ helper.readableTransaction {
+ select(
+ tableName,
+ limit = resultPagination.limitClause()
+ ).use { c ->
+ mutableListOf().also {
+ if (c.moveToFirst()) {
+ do {
+ it.add(c.getString(idColumnName).asKey())
+ } while (c.moveToNext())
+ }
+ }
+ }
+ }.createPaginationResult(
+ pagination,
+ count
+ )
+ }
+
+ override suspend fun remove(toRemove: Map>) {
+ helper.writableTransaction {
+ toRemove.flatMap { (k, vs) ->
+ vs.mapNotNullA { v ->
+ if (delete(tableName, "$idColumnName=? AND $valueColumnName=?", arrayOf(k.asId(), v.asValue())) > 0) {
+ k to v
+ } else {
+ null
+ }
+ }
+ }
+ }.forEach { (k, v) ->
+ _onValueRemoved.emit(k to v)
+ }
+ }
+}
diff --git a/repos/inmemory/build.gradle b/repos/inmemory/build.gradle
index 162e4af700e..2e766c6966f 100644
--- a/repos/inmemory/build.gradle
+++ b/repos/inmemory/build.gradle
@@ -1,6 +1,8 @@
plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
+ id "com.android.library"
+ id "kotlin-android-extensions"
}
apply from: "$mppProjectWithSerializationPresetPath"
diff --git a/repos/inmemory/src/main/AndroidManifest.xml b/repos/inmemory/src/main/AndroidManifest.xml
new file mode 100644
index 00000000000..63948d05fbf
--- /dev/null
+++ b/repos/inmemory/src/main/AndroidManifest.xml
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/repos/ktor/client/build.gradle b/repos/ktor/client/build.gradle
index 2076deebca3..30fea42d54c 100644
--- a/repos/ktor/client/build.gradle
+++ b/repos/ktor/client/build.gradle
@@ -1,6 +1,8 @@
plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
+ id "com.android.library"
+ id "kotlin-android-extensions"
}
apply from: "$mppProjectWithSerializationPresetPath"
diff --git a/repos/ktor/client/src/main/AndroidManifest.xml b/repos/ktor/client/src/main/AndroidManifest.xml
new file mode 100644
index 00000000000..a6de7e9b159
--- /dev/null
+++ b/repos/ktor/client/src/main/AndroidManifest.xml
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/repos/ktor/common/build.gradle b/repos/ktor/common/build.gradle
index 506faf0b021..7449b67d2fb 100644
--- a/repos/ktor/common/build.gradle
+++ b/repos/ktor/common/build.gradle
@@ -1,6 +1,8 @@
plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
+ id "com.android.library"
+ id "kotlin-android-extensions"
}
apply from: "$mppProjectWithSerializationPresetPath"
diff --git a/repos/ktor/common/src/main/AndroidManifest.xml b/repos/ktor/common/src/main/AndroidManifest.xml
new file mode 100644
index 00000000000..df34e12f6a0
--- /dev/null
+++ b/repos/ktor/common/src/main/AndroidManifest.xml
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
index 3a4bff38743..4bfb57df815 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -13,7 +13,6 @@ String[] includes = [
":repos:ktor:client",
":repos:ktor:common",
":repos:ktor:server",
- ":repos:android",
":ktor:server",
":ktor:common",
":ktor:client",