mirror of
https://github.com/InsanusMokrassar/MicroUtils.git
synced 2025-09-17 14:29:24 +00:00
Compare commits
30 Commits
Author | SHA1 | Date | |
---|---|---|---|
b872babe45 | |||
5a9cabc4bd | |||
3ba630684a | |||
498cd12f94 | |||
062848f2e4 | |||
d4b4547718 | |||
22cd440dd7 | |||
6fc64526d4 | |||
08075dfafe | |||
efcb25622e | |||
5ebf29d1fb | |||
b7d0ce3c97 | |||
e20929aec4 | |||
f74167cc65 | |||
c55cd5342e | |||
237d89d611 | |||
aff9b52f1c | |||
d8cc6ed06e | |||
dffafa54c3 | |||
776a3af497 | |||
5b3d9e8d64 | |||
90b7d74a0c | |||
5b596c76e0 | |||
c59e601e2e | |||
5ce71ee6f6 | |||
97dadf517a | |||
a739e874bf | |||
c73cb14615 | |||
d9464f7b90 | |||
aeee41680e |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -10,5 +10,6 @@ build/
|
||||
out/
|
||||
|
||||
secret.gradle
|
||||
local.properties
|
||||
|
||||
publishing.sh
|
||||
|
15
.travis.yml
15
.travis.yml
@@ -1,10 +1,23 @@
|
||||
language: java
|
||||
language: android
|
||||
install: true
|
||||
|
||||
os: linux
|
||||
dist: trusty
|
||||
jdk: oraclejdk8
|
||||
|
||||
android:
|
||||
components:
|
||||
- tools
|
||||
- platform-tools
|
||||
- build-tools-30.0.2
|
||||
- android-30
|
||||
- add-on
|
||||
- extra
|
||||
|
||||
before_script:
|
||||
- yes | /usr/local/android-sdk/tools/bin/sdkmanager "build-tools;30.0.2"
|
||||
- yes | /usr/local/android-sdk/tools/bin/sdkmanager "platforms;android-30"
|
||||
|
||||
jobs:
|
||||
include:
|
||||
- stage: build
|
||||
|
59
CHANGELOG.md
59
CHANGELOG.md
@@ -1,5 +1,64 @@
|
||||
# Changelog
|
||||
|
||||
## 0.4.0
|
||||
|
||||
* `Repos`:
|
||||
* `ReadOneToManyKeyValueRepo` got `keys` method with value parameter
|
||||
* All implementations inside of this library has been updated
|
||||
* `ReadStandardKeyValueRepo` got `keys` method with value parameter
|
||||
* All implementations inside of this library has been updated
|
||||
* New extensions `withMapper`
|
||||
|
||||
## 0.3.3
|
||||
|
||||
* `Coroutines`:
|
||||
* New extension `Flow<T>#plus`
|
||||
|
||||
## 0.3.2
|
||||
|
||||
* `Versions`:
|
||||
* `Coroutines`: `1.4.1` -> `1.4.2`
|
||||
* `Repos`:
|
||||
* `Common`:
|
||||
* New inline function `mapper` for simplier creating of `MapperRepo` objects
|
||||
* Extensions `withMapper` for keyvalue repos and onetomany repos
|
||||
|
||||
## 0.3.1
|
||||
|
||||
**ANDROID PACKAGES**
|
||||
|
||||
* `Android`:
|
||||
* `RecyclerView`:
|
||||
* Library has been created
|
||||
* `Common`
|
||||
* Now available package `dev.inmo:micro_utils.common-android`
|
||||
* `Coroutines`
|
||||
* Now available package `dev.inmo:micro_utils.coroutines-android`
|
||||
* `Ktor`
|
||||
* `Common`
|
||||
* Now available package `dev.inmo:micro_utils.ktor.common-android`
|
||||
* `Client`
|
||||
* Now available package `dev.inmo:micro_utils.ktor.client-android`
|
||||
* `MimeTypes`
|
||||
* Now available package `dev.inmo:micro_utils.mime_types-android`
|
||||
* `Pagination`
|
||||
* `Common`
|
||||
* Now available package `dev.inmo:micro_utils.pagination.common-android`
|
||||
* `Ktor`
|
||||
* `Common`
|
||||
* Now available package `dev.inmo:micro_utils.pagination.ktor.common-android`
|
||||
* `Repos`
|
||||
* `Common`
|
||||
* Now available package `dev.inmo:micro_utils.repos.common-android`
|
||||
* Now it is possible to use default realizations of repos abstractions natively on android
|
||||
* `Inmemory`
|
||||
* Now available package `dev.inmo:micro_utils.repos.inmemory-android`
|
||||
* `Ktor`
|
||||
* `Common`
|
||||
* Now available package `dev.inmo:micro_utils.repos.ktor.common-android`
|
||||
* `Common`
|
||||
* Now available package `dev.inmo:micro_utils.repos.ktor.client-android`
|
||||
|
||||
## 0.3.0
|
||||
|
||||
All deprecations has been removed
|
||||
|
23
android/recyclerview/build.gradle
Normal file
23
android/recyclerview/build.gradle
Normal file
@@ -0,0 +1,23 @@
|
||||
plugins {
|
||||
id "org.jetbrains.kotlin.multiplatform"
|
||||
id "org.jetbrains.kotlin.plugin.serialization"
|
||||
id "com.android.library"
|
||||
id "kotlin-android-extensions"
|
||||
}
|
||||
|
||||
apply from: "$mppAndroidProjectPresetPath"
|
||||
|
||||
kotlin {
|
||||
sourceSets {
|
||||
commonMain {
|
||||
dependencies {
|
||||
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version"
|
||||
}
|
||||
}
|
||||
androidMain {
|
||||
dependencies {
|
||||
api "androidx.recyclerview:recyclerview:$androidx_recycler_version"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
1
android/recyclerview/src/main/AndroidManifest.xml
Normal file
1
android/recyclerview/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
||||
<manifest package="dev.inmo.micro_utils.android.recyclerview"/>
|
@@ -0,0 +1,22 @@
|
||||
package dev.inmo.micro_utils.android.recyclerview
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
|
||||
abstract class AbstractStandardViewHolder<T>(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
viewId: Int,
|
||||
onViewInflated: ((View) -> Unit)? = null
|
||||
) : AbstractViewHolder<T>(
|
||||
inflater.inflate(viewId, container, false).also {
|
||||
onViewInflated ?.invoke(it)
|
||||
}
|
||||
) {
|
||||
constructor(
|
||||
container: ViewGroup,
|
||||
viewId: Int,
|
||||
onViewInflated: ((View) -> Unit)? = null
|
||||
) : this(LayoutInflater.from(container.context), container, viewId, onViewInflated)
|
||||
}
|
@@ -0,0 +1,10 @@
|
||||
package dev.inmo.micro_utils.android.recyclerview
|
||||
|
||||
import android.view.View
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
||||
abstract class AbstractViewHolder<in T>(
|
||||
view: View
|
||||
) : RecyclerView.ViewHolder(view) {
|
||||
abstract fun onBind(item: T)
|
||||
}
|
@@ -0,0 +1,9 @@
|
||||
package dev.inmo.micro_utils.android.recyclerview
|
||||
|
||||
import android.content.Context
|
||||
import android.widget.LinearLayout
|
||||
import androidx.recyclerview.widget.DividerItemDecoration
|
||||
|
||||
val Context.recyclerViewItemsDecoration
|
||||
get() = DividerItemDecoration(this, LinearLayout.VERTICAL)
|
||||
|
@@ -0,0 +1,47 @@
|
||||
package dev.inmo.micro_utils.android.recyclerview
|
||||
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
||||
fun RecyclerView.lastVisibleItemFlow(
|
||||
completingScope: CoroutineScope
|
||||
): Flow<Int> {
|
||||
val lastVisibleElementFun: () -> Int = (layoutManager as? LinearLayoutManager) ?.let { it::findLastVisibleItemPosition } ?: error("Currently supported only linear layout manager")
|
||||
val lastVisibleFlow = MutableStateFlow(lastVisibleElementFun())
|
||||
addOnScrollListener(
|
||||
object : RecyclerView.OnScrollListener() {
|
||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||
super.onScrolled(recyclerView, dx, dy)
|
||||
lastVisibleFlow.value = lastVisibleElementFun()
|
||||
}
|
||||
}.also { scrollListener ->
|
||||
lastVisibleFlow.onCompletion {
|
||||
removeOnScrollListener(scrollListener)
|
||||
}.launchIn(completingScope)
|
||||
}
|
||||
)
|
||||
return lastVisibleFlow.asStateFlow()
|
||||
}
|
||||
|
||||
inline fun Flow<Int>.mapLeftItems(
|
||||
crossinline countGetter: () -> Int
|
||||
): Flow<Int> = map { countGetter() - it }
|
||||
|
||||
inline fun Flow<Int>.mapRequireFilling(
|
||||
minimalLeftItems: Int,
|
||||
crossinline countGetter: () -> Int
|
||||
): Flow<Int> = mapLeftItems(countGetter).mapNotNull {
|
||||
if (it < minimalLeftItems) {
|
||||
it
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
inline fun RecyclerView.mapRequireFilling(
|
||||
minimalLeftItems: Int,
|
||||
completingScope: CoroutineScope,
|
||||
crossinline countGetter: () -> Int
|
||||
): Flow<Int> = lastVisibleItemFlow(completingScope).mapRequireFilling(minimalLeftItems, countGetter)
|
@@ -0,0 +1,68 @@
|
||||
package dev.inmo.micro_utils.android.recyclerview
|
||||
|
||||
import android.view.View
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
||||
|
||||
abstract class RecyclerViewAdapter<T>(
|
||||
val data: List<T>
|
||||
): RecyclerView.Adapter<AbstractViewHolder<T>>() {
|
||||
var emptyView: View? = null
|
||||
set(value) {
|
||||
field = value
|
||||
checkEmpty()
|
||||
}
|
||||
|
||||
init {
|
||||
registerAdapterDataObserver(
|
||||
object : RecyclerView.AdapterDataObserver() {
|
||||
override fun onItemRangeChanged(positionStart: Int, itemCount: Int) {
|
||||
super.onItemRangeChanged(positionStart, itemCount)
|
||||
checkEmpty()
|
||||
}
|
||||
|
||||
override fun onItemRangeChanged(positionStart: Int, itemCount: Int, payload: Any?) {
|
||||
super.onItemRangeChanged(positionStart, itemCount, payload)
|
||||
checkEmpty()
|
||||
}
|
||||
|
||||
override fun onChanged() {
|
||||
super.onChanged()
|
||||
checkEmpty()
|
||||
}
|
||||
|
||||
override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {
|
||||
super.onItemRangeRemoved(positionStart, itemCount)
|
||||
checkEmpty()
|
||||
}
|
||||
|
||||
override fun onItemRangeMoved(fromPosition: Int, toPosition: Int, itemCount: Int) {
|
||||
super.onItemRangeMoved(fromPosition, toPosition, itemCount)
|
||||
checkEmpty()
|
||||
}
|
||||
|
||||
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
|
||||
super.onItemRangeInserted(positionStart, itemCount)
|
||||
checkEmpty()
|
||||
}
|
||||
}
|
||||
)
|
||||
checkEmpty()
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = data.size
|
||||
|
||||
override fun onBindViewHolder(holder: AbstractViewHolder<T>, position: Int) {
|
||||
holder.onBind(data[position])
|
||||
}
|
||||
|
||||
private fun checkEmpty() {
|
||||
emptyView ?. let {
|
||||
if (data.isEmpty()) {
|
||||
it.visibility = View.VISIBLE
|
||||
} else {
|
||||
it.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,16 +1,20 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenLocal()
|
||||
jcenter()
|
||||
google()
|
||||
mavenCentral()
|
||||
mavenLocal()
|
||||
maven { url "https://plugins.gradle.org/m2/" }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:4.0.0'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
|
||||
classpath "com.jfrog.bintray.gradle:gradle-bintray-plugin:$gradle_bintray_plugin_version"
|
||||
classpath "com.getkeepsafe.dexcount:dexcount-gradle-plugin:$dexcount_version"
|
||||
classpath "com.github.breadmoirai:github-release:$github_release_plugin_version"
|
||||
classpath "org.jetbrains.dokka:dokka-gradle-plugin:$dokka_version"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +23,7 @@ allprojects {
|
||||
mavenLocal()
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
google()
|
||||
maven { url "https://kotlin.bintray.com/kotlinx" }
|
||||
}
|
||||
}
|
||||
|
@@ -12,4 +12,7 @@ function assert_success() {
|
||||
export RELEASE_MODE=true
|
||||
project="$1"
|
||||
|
||||
assert_success ./gradlew clean "$project:clean" "$project:build" "$project:publishToMavenLocal" "$project:bintrayUpload"
|
||||
assert_success ./gradlew clean
|
||||
assert_success ./gradlew "$project:build"
|
||||
assert_success ./gradlew "$project:publishToMavenLocal"
|
||||
assert_success ./gradlew "$project:bintrayUpload"
|
||||
|
@@ -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"
|
||||
|
1
common/src/main/AndroidManifest.xml
Normal file
1
common/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
||||
<manifest package="dev.inmo.micro_utils.common"/>
|
@@ -0,0 +1,7 @@
|
||||
package dev.inmo.micro_utils.common
|
||||
|
||||
@Suppress("UNCHECKED_CAST", "SimplifiableCall")
|
||||
inline fun <T, R> Iterable<T>.mapNotNullA(transform: (T) -> R?): List<R> = map(transform).filter { it != null } as List<R>
|
||||
|
||||
@Suppress("UNCHECKED_CAST", "SimplifiableCall")
|
||||
inline fun <T, R> Array<T>.mapNotNullA(mapper: (T) -> R?): List<R> = map(mapper).filter { it != null } as List<R>
|
@@ -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"
|
||||
@@ -12,5 +14,10 @@ kotlin {
|
||||
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version"
|
||||
}
|
||||
}
|
||||
androidMain {
|
||||
dependencies {
|
||||
api "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
@file:Suppress("NOTHING_TO_INLINE")
|
||||
|
||||
package dev.inmo.micro_utils.coroutines
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.merge
|
||||
|
||||
inline operator fun <T> Flow<T>.plus(other: Flow<T>) = merge(this, other)
|
1
coroutines/src/main/AndroidManifest.xml
Normal file
1
coroutines/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
||||
<manifest package="dev.inmo.micro_utils.coroutines"/>
|
@@ -0,0 +1,10 @@
|
||||
package dev.inmo.micro_utils.coroutines
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
suspend inline fun <T> doInUI(noinline block: suspend CoroutineScope.() -> T) = withContext(
|
||||
Dispatchers.Main,
|
||||
block
|
||||
)
|
38
defaultAndroidSettings
Normal file
38
defaultAndroidSettings
Normal file
@@ -0,0 +1,38 @@
|
||||
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
|
||||
}
|
||||
debug {
|
||||
debuggable true
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main.java.srcDirs += 'src/main/kotlin'
|
||||
}
|
||||
}
|
@@ -1,26 +1,15 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenLocal()
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
|
||||
classpath "org.jetbrains.dokka:dokka-gradle-plugin:$dokka_version"
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id "org.jetbrains.kotlin.multiplatform"
|
||||
id "org.jetbrains.kotlin.plugin.serialization"
|
||||
id "org.jetbrains.dokka" version "$dokka_version"
|
||||
id "com.android.library"
|
||||
id "kotlin-android-extensions"
|
||||
id "org.jetbrains.dokka"
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
jcenter()
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
@@ -30,6 +19,7 @@ kotlin {
|
||||
browser()
|
||||
nodejs()
|
||||
}
|
||||
android {}
|
||||
|
||||
sourceSets {
|
||||
commonMain {
|
||||
@@ -41,6 +31,57 @@ kotlin {
|
||||
it != project
|
||||
&& it.hasProperty("kotlin")
|
||||
&& it.kotlin.sourceSets.any { it.name.contains("commonMain") }
|
||||
&& it.kotlin.sourceSets.any { it.name.contains("jsMain") }
|
||||
&& it.kotlin.sourceSets.any { it.name.contains("jvmMain") }
|
||||
&& it.kotlin.sourceSets.any { it.name.contains("androidMain") }
|
||||
) {
|
||||
api it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
jsMain {
|
||||
dependencies {
|
||||
implementation kotlin('stdlib')
|
||||
|
||||
project.parent.subprojects.forEach {
|
||||
if (
|
||||
it != project
|
||||
&& it.hasProperty("kotlin")
|
||||
&& it.kotlin.sourceSets.any { it.name.contains("commonMain") }
|
||||
&& it.kotlin.sourceSets.any { it.name.contains("jsMain") }
|
||||
) {
|
||||
api it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
jvmMain {
|
||||
dependencies {
|
||||
implementation kotlin('stdlib')
|
||||
|
||||
project.parent.subprojects.forEach {
|
||||
if (
|
||||
it != project
|
||||
&& it.hasProperty("kotlin")
|
||||
&& it.kotlin.sourceSets.any { it.name.contains("commonMain") }
|
||||
&& it.kotlin.sourceSets.any { it.name.contains("jvmMain") }
|
||||
) {
|
||||
api it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
androidMain {
|
||||
dependencies {
|
||||
implementation kotlin('stdlib')
|
||||
|
||||
project.parent.subprojects.forEach {
|
||||
if (
|
||||
it != project
|
||||
&& it.hasProperty("kotlin")
|
||||
&& it.kotlin.sourceSets.any { it.name.contains("commonMain") }
|
||||
&& it.kotlin.sourceSets.any { it.name.contains("androidMain") }
|
||||
) {
|
||||
api it
|
||||
}
|
||||
@@ -84,5 +125,11 @@ tasks.dokkaHtml {
|
||||
named("jvmMain") {
|
||||
sourceRoots.setFrom(findSourcesWithName("jvmMain", "commonMain"))
|
||||
}
|
||||
|
||||
named("androidMain") {
|
||||
sourceRoots.setFrom(findSourcesWithName("androidMain", "commonMain"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
apply from: "$defaultAndroidSettingsPresetPath"
|
||||
|
@@ -1,3 +0,0 @@
|
||||
dokka_version=1.4.0
|
||||
|
||||
org.gradle.jvmargs=-Xmx1024m
|
1
dokka/src/main/AndroidManifest.xml
Normal file
1
dokka/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
||||
<manifest package="dev.inmo.dokka"/>
|
@@ -10,17 +10,21 @@ allprojects {
|
||||
}
|
||||
|
||||
internalProject = { name ->
|
||||
if (releaseMode) {
|
||||
"$group:$name:$version"
|
||||
} else {
|
||||
projectByName("$name")
|
||||
}
|
||||
// if (releaseMode) {
|
||||
// "$group:$name:$version"
|
||||
// } else {
|
||||
// projectByName("$name")
|
||||
// }
|
||||
projectByName("$name")
|
||||
}
|
||||
|
||||
releaseMode = (project.hasProperty('RELEASE_MODE') && project.property('RELEASE_MODE') == "true") || System.getenv('RELEASE_MODE') == "true"
|
||||
|
||||
mppProjectWithSerializationPresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerialization"
|
||||
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"
|
||||
|
@@ -3,13 +3,16 @@ org.gradle.parallel=true
|
||||
kotlin.js.generate.externals=true
|
||||
kotlin.incremental=true
|
||||
kotlin.incremental.js=true
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
org.gradle.jvmargs=-Xmx3072m
|
||||
|
||||
kotlin_version=1.4.10
|
||||
kotlin_coroutines_version=1.4.1
|
||||
kotlin_serialisation_core_version=1.0.1
|
||||
kotlin_exposed_version=0.28.1
|
||||
|
||||
ktor_version=1.4.1
|
||||
ktor_version=1.4.2
|
||||
|
||||
klockVersion=1.12.1
|
||||
|
||||
@@ -18,5 +21,25 @@ github_release_plugin_version=2.2.12
|
||||
|
||||
uuidVersion=0.2.2
|
||||
|
||||
# ANDROID
|
||||
|
||||
core_ktx_version=1.3.2
|
||||
androidx_recycler_version=1.1.0
|
||||
|
||||
android_minSdkVersion=19
|
||||
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
|
||||
|
||||
# Dokka
|
||||
|
||||
dokka_version=1.4.0
|
||||
|
||||
# Project data
|
||||
|
||||
group=dev.inmo
|
||||
version=0.3.0
|
||||
version=0.4.0
|
||||
android_code_version=4
|
||||
|
@@ -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"
|
||||
|
1
ktor/client/src/main/AndroidManifest.xml
Normal file
1
ktor/client/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
||||
<manifest package="dev.inmo.micro_utils.ktor.client"/>
|
@@ -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"
|
||||
|
1
ktor/common/src/main/AndroidManifest.xml
Normal file
1
ktor/common/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
||||
<manifest package="dev.inmo.micro_utils.ktor.common"/>
|
@@ -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"
|
||||
|
1
mime_types/src/main/AndroidManifest.xml
Normal file
1
mime_types/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
||||
<manifest package="dev.inmo.micro_utils.mime_types"/>
|
26
mppAndroidProject
Normal file
26
mppAndroidProject
Normal file
@@ -0,0 +1,26 @@
|
||||
project.version = "$version"
|
||||
project.group = "$group"
|
||||
|
||||
apply from: "$publishGradlePath"
|
||||
|
||||
kotlin {
|
||||
android {
|
||||
publishAllLibraryVariants()
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
commonMain {
|
||||
dependencies {
|
||||
implementation kotlin('stdlib')
|
||||
}
|
||||
}
|
||||
commonTest {
|
||||
dependencies {
|
||||
implementation kotlin('test-common')
|
||||
implementation kotlin('test-annotations-common')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
apply from: "$defaultAndroidSettingsPresetPath"
|
@@ -9,6 +9,9 @@ kotlin {
|
||||
browser()
|
||||
nodejs()
|
||||
}
|
||||
android {
|
||||
publishAllLibraryVariants()
|
||||
}
|
||||
|
||||
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"
|
||||
|
@@ -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"
|
||||
|
1
pagination/common/src/main/AndroidManifest.xml
Normal file
1
pagination/common/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
||||
<manifest package="dev.inmo.micro_utils.pagination.common"/>
|
@@ -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"
|
||||
|
1
pagination/ktor/common/src/main/AndroidManifest.xml
Normal file
1
pagination/ktor/common/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
||||
<manifest package="dev.inmo.micro_utils.pagination.ktor.common"/>
|
@@ -13,7 +13,17 @@ bintray {
|
||||
if (directorySubname == "kotlinMultiplatform") {
|
||||
it.setPath("${project.name}/${project.version}/${project.name}-${project.version}.module")
|
||||
} else {
|
||||
it.setPath("${project.name}-${directorySubname}/${project.version}/${project.name}-${directorySubname}-${project.version}.module")
|
||||
switch (directorySubname) {
|
||||
case "androidRelease":
|
||||
it.setPath("${project.name}-android/${project.version}/${project.name}-android-${project.version}.module")
|
||||
break
|
||||
case "androidDebug":
|
||||
it.setPath("${project.name}-android-debug/${project.version}/${project.name}-android-debug-${project.version}.module")
|
||||
break
|
||||
default:
|
||||
it.setPath("${project.name}-${directorySubname}/${project.version}/${project.name}-${directorySubname}-${project.version}.module")
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (directorySubname == "kotlinMultiplatform" && it.getName() == "pom-default.xml") {
|
||||
|
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -6,4 +6,16 @@ interface MapperRepo<FromKey, FromValue, ToKey, ToValue> {
|
||||
|
||||
suspend fun ToKey.toInnerKey() = this as FromKey
|
||||
suspend fun ToValue.toInnerValue() = this as FromValue
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> mapper(
|
||||
crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
|
||||
crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
|
||||
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
|
||||
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
|
||||
) = object : MapperRepo<FromKey, FromValue, ToKey, ToValue> {
|
||||
override suspend fun FromKey.toOutKey(): ToKey = keyFromToTo()
|
||||
override suspend fun FromValue.toOutValue(): ToValue = valueFromToTo()
|
||||
override suspend fun ToKey.toInnerKey(): FromKey = keyToToFrom()
|
||||
override suspend fun ToValue.toInnerValue(): FromValue = valueToToFrom()
|
||||
}
|
||||
|
@@ -6,6 +6,7 @@ import kotlinx.coroutines.flow.Flow
|
||||
interface ReadOneToManyKeyValueRepo<Key, Value> : Repo {
|
||||
suspend fun get(k: Key, pagination: Pagination, reversed: Boolean = false): PaginationResult<Value>
|
||||
suspend fun keys(pagination: Pagination, reversed: Boolean = false): PaginationResult<Key>
|
||||
suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean = false): PaginationResult<Key>
|
||||
suspend fun contains(k: Key): Boolean
|
||||
suspend fun contains(k: Key, v: Value): Boolean
|
||||
suspend fun count(k: Key): Long
|
||||
|
@@ -8,6 +8,7 @@ interface ReadStandardKeyValueRepo<Key, Value> : Repo {
|
||||
suspend fun get(k: Key): Value?
|
||||
suspend fun values(pagination: Pagination, reversed: Boolean = false): PaginationResult<Value>
|
||||
suspend fun keys(pagination: Pagination, reversed: Boolean = false): PaginationResult<Key>
|
||||
suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean = false): PaginationResult<Key>
|
||||
suspend fun contains(key: Key): Boolean
|
||||
suspend fun count(): Long
|
||||
}
|
||||
|
@@ -44,6 +44,23 @@ open class MapperReadStandardKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun keys(
|
||||
v: FromValue,
|
||||
pagination: Pagination,
|
||||
reversed: Boolean
|
||||
): PaginationResult<FromKey> = to.keys(
|
||||
v.toOutValue(),
|
||||
pagination,
|
||||
reversed
|
||||
).let {
|
||||
PaginationResult(
|
||||
it.page,
|
||||
it.pagesNumber,
|
||||
it.results.map { it.toInnerKey() },
|
||||
it.size
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun contains(key: FromKey): Boolean = to.contains(
|
||||
key.toOutKey()
|
||||
)
|
||||
@@ -51,6 +68,21 @@ open class MapperReadStandardKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||
override suspend fun count(): Long = to.count()
|
||||
}
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun <FromKey, FromValue, ToKey, ToValue> ReadStandardKeyValueRepo<ToKey, ToValue>.withMapper(
|
||||
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
||||
): ReadStandardKeyValueRepo<FromKey, FromValue> = MapperReadStandardKeyValueRepo(this, mapper)
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> ReadStandardKeyValueRepo<ToKey, ToValue>.withMapper(
|
||||
crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
|
||||
crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
|
||||
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
|
||||
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
|
||||
): ReadStandardKeyValueRepo<FromKey, FromValue> = withMapper(
|
||||
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
|
||||
)
|
||||
|
||||
open class MapperWriteStandardKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||
private val to: WriteStandardKeyValueRepo<ToKey, ToValue>,
|
||||
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
||||
@@ -75,6 +107,21 @@ open class MapperWriteStandardKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun <FromKey, FromValue, ToKey, ToValue> WriteStandardKeyValueRepo<ToKey, ToValue>.withMapper(
|
||||
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
||||
): WriteStandardKeyValueRepo<FromKey, FromValue> = MapperWriteStandardKeyValueRepo(this, mapper)
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> WriteStandardKeyValueRepo<ToKey, ToValue>.withMapper(
|
||||
crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
|
||||
crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
|
||||
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
|
||||
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
|
||||
): WriteStandardKeyValueRepo<FromKey, FromValue> = withMapper(
|
||||
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
|
||||
)
|
||||
|
||||
open class MapperStandardKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||
private val to: StandardKeyValueRepo<ToKey, ToValue>,
|
||||
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
||||
@@ -82,3 +129,18 @@ open class MapperStandardKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||
MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper,
|
||||
ReadStandardKeyValueRepo<FromKey, FromValue> by MapperReadStandardKeyValueRepo(to, mapper),
|
||||
WriteStandardKeyValueRepo<FromKey, FromValue> by MapperWriteStandardKeyValueRepo(to, mapper)
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun <FromKey, FromValue, ToKey, ToValue> StandardKeyValueRepo<ToKey, ToValue>.withMapper(
|
||||
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
||||
): StandardKeyValueRepo<FromKey, FromValue> = MapperStandardKeyValueRepo(this, mapper)
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> StandardKeyValueRepo<ToKey, ToValue>.withMapper(
|
||||
crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
|
||||
crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
|
||||
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
|
||||
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
|
||||
): StandardKeyValueRepo<FromKey, FromValue> = withMapper(
|
||||
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
|
||||
)
|
||||
|
@@ -42,6 +42,23 @@ open class MapperReadOneToManyKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun keys(
|
||||
v: FromValue,
|
||||
pagination: Pagination,
|
||||
reversed: Boolean
|
||||
): PaginationResult<FromKey> = to.keys(
|
||||
v.toOutValue(),
|
||||
pagination,
|
||||
reversed
|
||||
).let {
|
||||
PaginationResult(
|
||||
it.page,
|
||||
it.pagesNumber,
|
||||
it.results.map { it.toInnerKey() },
|
||||
it.size
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun contains(k: FromKey): Boolean = to.contains(k.toOutKey())
|
||||
override suspend fun contains(k: FromKey, v: FromValue): Boolean = to.contains(k.toOutKey(), v.toOutValue())
|
||||
|
||||
@@ -49,6 +66,21 @@ open class MapperReadOneToManyKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||
override suspend fun count(k: FromKey): Long = to.count(k.toOutKey())
|
||||
}
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun <FromKey, FromValue, ToKey, ToValue> ReadOneToManyKeyValueRepo<ToKey, ToValue>.withMapper(
|
||||
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
||||
): ReadOneToManyKeyValueRepo<FromKey, FromValue> = MapperReadOneToManyKeyValueRepo(this, mapper)
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> ReadOneToManyKeyValueRepo<ToKey, ToValue>.withMapper(
|
||||
crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
|
||||
crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
|
||||
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
|
||||
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
|
||||
): ReadOneToManyKeyValueRepo<FromKey, FromValue> = withMapper(
|
||||
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
|
||||
)
|
||||
|
||||
open class MapperWriteOneToManyKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||
private val to: WriteOneToManyKeyValueRepo<ToKey, ToValue>,
|
||||
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
||||
@@ -78,6 +110,21 @@ open class MapperWriteOneToManyKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||
override suspend fun clear(k: FromKey) = to.clear(k.toOutKey())
|
||||
}
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun <FromKey, FromValue, ToKey, ToValue> WriteOneToManyKeyValueRepo<ToKey, ToValue>.withMapper(
|
||||
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
||||
): WriteOneToManyKeyValueRepo<FromKey, FromValue> = MapperWriteOneToManyKeyValueRepo(this, mapper)
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> WriteOneToManyKeyValueRepo<ToKey, ToValue>.withMapper(
|
||||
crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
|
||||
crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
|
||||
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
|
||||
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
|
||||
): WriteOneToManyKeyValueRepo<FromKey, FromValue> = withMapper(
|
||||
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
|
||||
)
|
||||
|
||||
open class MapperOneToManyKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||
private val to: OneToManyKeyValueRepo<ToKey, ToValue>,
|
||||
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
||||
@@ -85,3 +132,18 @@ open class MapperOneToManyKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||
MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper,
|
||||
ReadOneToManyKeyValueRepo<FromKey, FromValue> by MapperReadOneToManyKeyValueRepo(to, mapper),
|
||||
WriteOneToManyKeyValueRepo<FromKey, FromValue> by MapperWriteOneToManyKeyValueRepo(to, mapper)
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun <FromKey, FromValue, ToKey, ToValue> OneToManyKeyValueRepo<ToKey, ToValue>.withMapper(
|
||||
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
||||
): OneToManyKeyValueRepo<FromKey, FromValue> = MapperOneToManyKeyValueRepo(this, mapper)
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> OneToManyKeyValueRepo<ToKey, ToValue>.withMapper(
|
||||
crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
|
||||
crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
|
||||
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
|
||||
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
|
||||
): OneToManyKeyValueRepo<FromKey, FromValue> = withMapper(
|
||||
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
|
||||
)
|
||||
|
1
repos/common/src/main/AndroidManifest.xml
Normal file
1
repos/common/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
||||
<manifest package="dev.inmo.micro_utils.repos.common"/>
|
@@ -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
|
||||
}
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
package dev.inmo.micro_utils.repos
|
||||
|
||||
import androidx.core.content.contentValuesOf
|
||||
|
||||
@Suppress("UNCHECKED_CAST", "SimplifiableCall")
|
||||
fun contentValuesOfNotNull(vararg pairs: Pair<String, Any?>?) = contentValuesOf(
|
||||
*(pairs.filter { it != null } as List<Pair<String, Any?>>).toTypedArray()
|
||||
)
|
@@ -0,0 +1,21 @@
|
||||
package dev.inmo.micro_utils.repos
|
||||
|
||||
import android.database.Cursor
|
||||
|
||||
inline fun <T> Cursor.map(
|
||||
block: (Cursor) -> T
|
||||
): List<T> {
|
||||
val result = mutableListOf<T>()
|
||||
if (moveToFirst()) {
|
||||
do {
|
||||
result.add(block(this))
|
||||
} while (moveToNext())
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
fun Cursor.firstOrNull(): Cursor? = if (moveToFirst()) {
|
||||
this
|
||||
} else {
|
||||
null
|
||||
}
|
@@ -0,0 +1,6 @@
|
||||
package dev.inmo.micro_utils.repos
|
||||
|
||||
import kotlinx.coroutines.newSingleThreadContext
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
val DatabaseCoroutineContext: CoroutineContext = newSingleThreadContext("db-context")
|
@@ -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<String, ColumnType>
|
||||
) = "create table $tableName (${columnsToTypes.joinToString(", ") { "${it.first} ${it.second}" }});"
|
||||
|
||||
fun SQLiteDatabase.createTable(
|
||||
tableName: String,
|
||||
vararg columnsToTypes: Pair<String, ColumnType>,
|
||||
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<String>? = null,
|
||||
selection: String? = null,
|
||||
selectionArgs: Array<String>? = 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 { "\"?\"" }
|
||||
}
|
@@ -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)
|
@@ -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 <T> 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 <T> SQLiteDatabase.inlineTransaction(block: SQLiteDatabase.() -> T): T {
|
||||
return when {
|
||||
inTransaction() -> block()
|
||||
else -> {
|
||||
beginTransaction()
|
||||
try {
|
||||
block().also {
|
||||
setTransactionSuccessful()
|
||||
}
|
||||
} finally {
|
||||
endTransaction()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,7 @@
|
||||
package dev.inmo.micro_utils.repos
|
||||
|
||||
val internalId = "_id"
|
||||
val internalIdType = ColumnType.Numeric.INTEGER(
|
||||
autoincrement = true,
|
||||
primaryKey = true
|
||||
)
|
@@ -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<T> (
|
||||
val callback: suspend SQLiteDatabase.() -> T,
|
||||
val continuation: Continuation<T>
|
||||
) {
|
||||
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 <T> writableTransaction(block: suspend SQLiteDatabase.() -> T): T = sqlOpenHelper.writableTransaction(block)
|
||||
|
||||
suspend fun <T> readableTransaction(block: suspend SQLiteDatabase.() -> T): T = sqlOpenHelper.readableTransaction(block)
|
||||
}
|
||||
|
||||
suspend fun <T> SQLiteOpenHelper.writableTransaction(block: suspend SQLiteDatabase.() -> T): T {
|
||||
return writableDatabase.transaction(block)
|
||||
}
|
||||
|
||||
suspend fun <T> SQLiteOpenHelper.readableTransaction(block: suspend SQLiteDatabase.() -> T): T {
|
||||
return readableDatabase.transaction(block)
|
||||
}
|
@@ -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> T.asId: String
|
||||
get() = (this as? String) ?: this!!.toString()
|
||||
|
||||
abstract class AbstractAndroidCRUDRepo<ObjectType, IdType>(
|
||||
protected val helper: StandardSQLHelper
|
||||
) : ReadStandardCRUDRepo<ObjectType, IdType> {
|
||||
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<ObjectType> {
|
||||
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<ObjectType>().createPaginationResult(pagination, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -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<ObjectType, IdType, InputValueType>(
|
||||
helper: StandardSQLHelper
|
||||
) : WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>,
|
||||
AbstractAndroidCRUDRepo<ObjectType, IdType>(helper) {
|
||||
protected val newObjectsChannel = BroadcastChannel<ObjectType>(64)
|
||||
protected val updateObjectsChannel = BroadcastChannel<ObjectType>(64)
|
||||
protected val deleteObjectsIdsChannel = BroadcastChannel<IdType>(64)
|
||||
override val newObjectsFlow: Flow<ObjectType> = newObjectsChannel.asFlow()
|
||||
override val updatedObjectsFlow: Flow<ObjectType> = updateObjectsChannel.asFlow()
|
||||
override val deletedObjectsIdsFlow: Flow<IdType> = deleteObjectsIdsChannel.asFlow()
|
||||
|
||||
protected abstract suspend fun InputValueType.asContentValues(id: IdType? = null): ContentValues
|
||||
|
||||
override suspend fun create(values: List<InputValueType>): List<ObjectType> {
|
||||
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<IdType>) {
|
||||
val deleted = mutableListOf<IdType>()
|
||||
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<UpdatedValuePair<IdType, InputValueType>>): List<ObjectType> {
|
||||
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() } }
|
||||
}
|
@@ -0,0 +1,144 @@
|
||||
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<String, KeyValueStore<*>>()
|
||||
|
||||
fun <T : Any> Context.keyValueStore(
|
||||
name: String = "default",
|
||||
cacheValues: Boolean = false
|
||||
): StandardKeyValueRepo<String, T> {
|
||||
return cache.getOrPut(name) {
|
||||
KeyValueStore<T>(this, name, cacheValues)
|
||||
} as KeyValueStore<T>
|
||||
}
|
||||
|
||||
class KeyValueStore<T : Any> internal constructor (
|
||||
c: Context,
|
||||
preferencesName: String,
|
||||
useCache: Boolean = false
|
||||
) : SharedPreferences.OnSharedPreferenceChangeListener, StandardKeyValueRepo<String, T> {
|
||||
private val sharedPreferences = c.getSharedPreferences(preferencesName, Context.MODE_PRIVATE)
|
||||
|
||||
private val cachedData = if (useCache) {
|
||||
mutableMapOf<String, Any>()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
private val onNewValueChannel = BroadcastChannel<Pair<String, T>>(Channel.BUFFERED)
|
||||
private val onValueRemovedChannel = BroadcastChannel<String>(Channel.BUFFERED)
|
||||
|
||||
override val onNewValue: Flow<Pair<String, T>> = onNewValueChannel.asFlow()
|
||||
override val onValueRemoved: Flow<String> = 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<T> {
|
||||
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<String> {
|
||||
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 keys(v: T, pagination: Pagination, reversed: Boolean): PaginationResult<String> {
|
||||
val resultPagination = if (reversed) pagination.reverse(count()) else pagination
|
||||
return sharedPreferences.all.mapNotNull { (k, value) -> if (value == v) k else null }.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<String, T>) {
|
||||
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<String>) {
|
||||
sharedPreferences.edit {
|
||||
toUnset.forEach { remove(it) }
|
||||
}
|
||||
toUnset.forEach { onValueRemovedChannel.send(it) }
|
||||
}
|
||||
}
|
@@ -0,0 +1,214 @@
|
||||
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<Key, Value>(
|
||||
private val tableName: String,
|
||||
private val keySerializer: KSerializer<Key>,
|
||||
private val valueSerializer: KSerializer<Value>,
|
||||
private val helper: SQLiteOpenHelper
|
||||
) : OneToManyKeyValueRepo<Key, Value> {
|
||||
private val _onNewValue: MutableSharedFlow<Pair<Key, Value>> = MutableSharedFlow()
|
||||
override val onNewValue: Flow<Pair<Key, Value>> = _onNewValue.asSharedFlow()
|
||||
private val _onValueRemoved: MutableSharedFlow<Pair<Key, Value>> = MutableSharedFlow()
|
||||
override val onValueRemoved: Flow<Pair<Key, Value>> = _onValueRemoved.asSharedFlow()
|
||||
private val _onDataCleared = MutableSharedFlow<Key>()
|
||||
override val onDataCleared: Flow<Key> = _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<Key, List<Value>>) {
|
||||
val added = mutableListOf<Pair<Key, Value>>()
|
||||
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<Value> = 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<Value>().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<Key> = count().let { count ->
|
||||
val resultPagination = pagination.let { if (reversed) pagination.reverse(count) else pagination }
|
||||
helper.readableTransaction {
|
||||
select(
|
||||
tableName,
|
||||
limit = resultPagination.limitClause()
|
||||
).use { c ->
|
||||
mutableListOf<Key>().also {
|
||||
if (c.moveToFirst()) {
|
||||
do {
|
||||
it.add(c.getString(idColumnName).asKey())
|
||||
} while (c.moveToNext())
|
||||
}
|
||||
}
|
||||
}
|
||||
}.createPaginationResult(
|
||||
pagination,
|
||||
count
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun keys(
|
||||
v: Value,
|
||||
pagination: Pagination,
|
||||
reversed: Boolean
|
||||
): PaginationResult<Key> = count().let { count ->
|
||||
val resultPagination = pagination.let { if (reversed) pagination.reverse(count) else pagination }
|
||||
helper.readableTransaction {
|
||||
select(
|
||||
tableName,
|
||||
selection = "$valueColumnName=?",
|
||||
selectionArgs = arrayOf(v.asValue()),
|
||||
limit = resultPagination.limitClause()
|
||||
).use { c ->
|
||||
mutableListOf<Key>().also {
|
||||
if (c.moveToFirst()) {
|
||||
do {
|
||||
it.add(c.getString(idColumnName).asKey())
|
||||
} while (c.moveToNext())
|
||||
}
|
||||
}
|
||||
}
|
||||
}.createPaginationResult(
|
||||
pagination,
|
||||
count
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun remove(toRemove: Map<Key, List<Value>>) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
@@ -33,6 +33,16 @@ open class ExposedReadKeyValueRepo<Key, Value>(
|
||||
}
|
||||
}.createPaginationResult(pagination, count())
|
||||
|
||||
override suspend fun keys(value: Value, pagination: Pagination, reversed: Boolean): PaginationResult<Key> = transaction(database) {
|
||||
select { valueColumn.eq(value) }.let {
|
||||
it.count() to it.paginate(pagination, keyColumn to if (reversed) SortOrder.DESC else SortOrder.ASC).map {
|
||||
it[keyColumn]
|
||||
}
|
||||
}
|
||||
}.let { (count, list) ->
|
||||
list.createPaginationResult(pagination, count)
|
||||
}
|
||||
|
||||
override suspend fun values(pagination: Pagination, reversed: Boolean): PaginationResult<Value> = transaction(database) {
|
||||
selectAll().paginate(pagination, keyColumn to if (reversed) SortOrder.DESC else SortOrder.ASC).map {
|
||||
it[valueColumn]
|
||||
|
@@ -39,6 +39,21 @@ open class ExposedReadOneToManyKeyValueRepo<Key, Value>(
|
||||
count()
|
||||
)
|
||||
|
||||
override suspend fun keys(
|
||||
v: Value,
|
||||
pagination: Pagination,
|
||||
reversed: Boolean
|
||||
): PaginationResult<Key> = transaction(database) {
|
||||
select { valueColumn.eq(v) }.let {
|
||||
it.count() to it.paginate(pagination, keyColumn, reversed).map { it[keyColumn] }
|
||||
}
|
||||
}.let { (count, list) ->
|
||||
list.createPaginationResult(
|
||||
pagination,
|
||||
count
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun contains(k: Key): Boolean = transaction(database) {
|
||||
select { keyColumn.eq(k) }.limit(1).any()
|
||||
}
|
||||
|
@@ -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"
|
||||
|
@@ -39,6 +39,18 @@ class ReadMapKeyValueRepo<Key, Value>(
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean): PaginationResult<Key> {
|
||||
val keys: List<Key> = map.mapNotNull { (k, value) -> if (v == value) k else null }
|
||||
val actualPagination = if (reversed) pagination.reverse(keys.size) else pagination
|
||||
return keys.paginate(actualPagination).let {
|
||||
if (reversed) {
|
||||
it.copy(results = it.results.reversed())
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun contains(key: Key): Boolean = map.containsKey(key)
|
||||
|
||||
override suspend fun count(): Long = map.size.toLong()
|
||||
|
@@ -32,6 +32,18 @@ class MapReadOneToManyKeyValueRepo<Key, Value>(
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean): PaginationResult<Key> {
|
||||
val keys = map.keys.filter { map[it] ?.contains(v) == true }
|
||||
val actualPagination = if (reversed) pagination.reverse(keys.size) else pagination
|
||||
return keys.paginate(actualPagination).let {
|
||||
if (reversed) {
|
||||
it.copy(results = it.results.reversed())
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun contains(k: Key): Boolean = map.containsKey(k)
|
||||
|
||||
override suspend fun contains(k: Key, v: Value): Boolean = map[k] ?.contains(v) == true
|
||||
|
1
repos/inmemory/src/main/AndroidManifest.xml
Normal file
1
repos/inmemory/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
||||
<manifest package="dev.inmo.micro_utils.repos.inmemory"/>
|
@@ -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"
|
||||
|
@@ -6,6 +6,7 @@ import dev.inmo.micro_utils.ktor.common.buildStandardUrl
|
||||
import dev.inmo.micro_utils.pagination.*
|
||||
import dev.inmo.micro_utils.repos.ReadStandardKeyValueRepo
|
||||
import dev.inmo.micro_utils.repos.ktor.common.key_value.*
|
||||
import dev.inmo.micro_utils.repos.ktor.common.valueParameterName
|
||||
import io.ktor.client.HttpClient
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.builtins.serializer
|
||||
@@ -50,6 +51,18 @@ class KtorReadStandardKeyValueRepo<Key, Value> (
|
||||
PaginationResult.serializer(keySerializer)
|
||||
)
|
||||
|
||||
override suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean): PaginationResult<Key> = client.uniget(
|
||||
buildStandardUrl(
|
||||
baseUrl,
|
||||
keysRoute,
|
||||
mapOf(
|
||||
valueParameterName to valueSerializer.encodeUrlQueryValue(v),
|
||||
reversedParameterName to Boolean.serializer().encodeUrlQueryValue(reversed)
|
||||
) + pagination.asUrlQueryParts
|
||||
),
|
||||
PaginationResult.serializer(keySerializer)
|
||||
)
|
||||
|
||||
override suspend fun contains(key: Key): Boolean = client.uniget(
|
||||
buildStandardUrl(
|
||||
baseUrl,
|
||||
|
@@ -5,7 +5,10 @@ import dev.inmo.micro_utils.ktor.client.uniget
|
||||
import dev.inmo.micro_utils.ktor.common.buildStandardUrl
|
||||
import dev.inmo.micro_utils.pagination.*
|
||||
import dev.inmo.micro_utils.repos.ReadOneToManyKeyValueRepo
|
||||
import dev.inmo.micro_utils.repos.ktor.common.keyParameterName
|
||||
import dev.inmo.micro_utils.repos.ktor.common.one_to_many.*
|
||||
import dev.inmo.micro_utils.repos.ktor.common.reversedParameterName
|
||||
import dev.inmo.micro_utils.repos.ktor.common.valueParameterName
|
||||
import io.ktor.client.HttpClient
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.builtins.serializer
|
||||
@@ -42,6 +45,18 @@ class KtorReadOneToManyKeyValueRepo<Key, Value> (
|
||||
paginationKeyResultSerializer
|
||||
)
|
||||
|
||||
override suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean): PaginationResult<Key> = client.uniget(
|
||||
buildStandardUrl(
|
||||
baseUrl,
|
||||
keysRoute,
|
||||
mapOf(
|
||||
valueParameterName to valueSerializer.encodeUrlQueryValue(v),
|
||||
reversedParameterName to Boolean.serializer().encodeUrlQueryValue(reversed)
|
||||
) + pagination.asUrlQueryParts
|
||||
),
|
||||
paginationKeyResultSerializer
|
||||
)
|
||||
|
||||
override suspend fun contains(k: Key): Boolean = client.uniget(
|
||||
buildStandardUrl(
|
||||
baseUrl,
|
||||
|
1
repos/ktor/client/src/main/AndroidManifest.xml
Normal file
1
repos/ktor/client/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
||||
<manifest package="dev.inmo.micro_utils.repos.ktor.client"/>
|
@@ -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"
|
||||
|
@@ -0,0 +1,5 @@
|
||||
package dev.inmo.micro_utils.repos.ktor.common
|
||||
|
||||
const val keyParameterName = "key"
|
||||
const val valueParameterName = "value"
|
||||
const val reversedParameterName = "reversed"
|
@@ -1,5 +1,8 @@
|
||||
package dev.inmo.micro_utils.repos.ktor.common.one_to_many
|
||||
|
||||
const val keyParameterName = "key"
|
||||
const val valueParameterName = "value"
|
||||
const val reversedParameterName = "reversed"
|
||||
@Deprecated("Replaced", ReplaceWith("keyParameterName", "dev.inmo.micro_utils.repos.ktor.common.keyParameterName"))
|
||||
const val keyParameterName = dev.inmo.micro_utils.repos.ktor.common.keyParameterName
|
||||
@Deprecated("Replaced", ReplaceWith("valueParameterName", "dev.inmo.micro_utils.repos.ktor.common.valueParameterName"))
|
||||
const val valueParameterName = dev.inmo.micro_utils.repos.ktor.common.valueParameterName
|
||||
@Deprecated("Replaced", ReplaceWith("reversedParameterName", "dev.inmo.micro_utils.repos.ktor.common.reversedParameterName"))
|
||||
const val reversedParameterName = dev.inmo.micro_utils.repos.ktor.common.reversedParameterName
|
1
repos/ktor/common/src/main/AndroidManifest.xml
Normal file
1
repos/ktor/common/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
||||
<manifest package="dev.inmo.micro_utils.repos.ktor.common"/>
|
@@ -1,11 +1,11 @@
|
||||
package dev.inmo.micro_utils.repos.ktor.server.key_value
|
||||
|
||||
import dev.inmo.micro_utils.ktor.server.decodeUrlQueryValueOrSendError
|
||||
import dev.inmo.micro_utils.ktor.server.unianswer
|
||||
import dev.inmo.micro_utils.ktor.server.*
|
||||
import dev.inmo.micro_utils.pagination.PaginationResult
|
||||
import dev.inmo.micro_utils.pagination.extractPagination
|
||||
import dev.inmo.micro_utils.repos.ReadStandardKeyValueRepo
|
||||
import dev.inmo.micro_utils.repos.ktor.common.key_value.*
|
||||
import dev.inmo.micro_utils.repos.ktor.common.valueParameterName
|
||||
import io.ktor.application.call
|
||||
import io.ktor.routing.Route
|
||||
import io.ktor.routing.get
|
||||
@@ -49,10 +49,11 @@ fun <K, V> Route.configureReadStandartKeyValueRepoRoutes (
|
||||
reversedParameterName,
|
||||
Boolean.serializer()
|
||||
) ?: return@get
|
||||
val value = call.decodeUrlQueryValue(valueParameterName, valueSerializer)
|
||||
|
||||
call.unianswer(
|
||||
PaginationResult.serializer(keySerializer),
|
||||
originalRepo.keys(parination, reversed)
|
||||
value ?.let { originalRepo.keys(value, parination, reversed) } ?: originalRepo.keys(parination, reversed)
|
||||
)
|
||||
}
|
||||
|
||||
|
@@ -4,7 +4,11 @@ import dev.inmo.micro_utils.ktor.server.*
|
||||
import dev.inmo.micro_utils.pagination.PaginationResult
|
||||
import dev.inmo.micro_utils.pagination.extractPagination
|
||||
import dev.inmo.micro_utils.repos.ReadOneToManyKeyValueRepo
|
||||
import dev.inmo.micro_utils.repos.ktor.common.*
|
||||
import dev.inmo.micro_utils.repos.ktor.common.keyParameterName
|
||||
import dev.inmo.micro_utils.repos.ktor.common.one_to_many.*
|
||||
import dev.inmo.micro_utils.repos.ktor.common.valueParameterName
|
||||
import dev.inmo.micro_utils.repos.ktor.common.reversedParameterName
|
||||
import io.ktor.application.call
|
||||
import io.ktor.routing.Route
|
||||
import io.ktor.routing.get
|
||||
@@ -42,10 +46,14 @@ fun <Key, Value> Route.configureOneToManyReadKeyValueRepoRoutes(
|
||||
reversedParameterName,
|
||||
Boolean.serializer()
|
||||
) ?: false
|
||||
val value: Value? = call.decodeUrlQueryValue(
|
||||
valueParameterName,
|
||||
valueSealizer
|
||||
)
|
||||
|
||||
call.unianswer(
|
||||
paginationKeyResult,
|
||||
originalRepo.keys(pagination, reversed)
|
||||
value ?.let { originalRepo.keys(value, pagination, reversed) } ?: originalRepo.keys(pagination, reversed)
|
||||
)
|
||||
}
|
||||
|
||||
|
@@ -17,13 +17,18 @@ String[] includes = [
|
||||
":ktor:common",
|
||||
":ktor:client",
|
||||
":coroutines",
|
||||
":android:recyclerview",
|
||||
|
||||
":dokka"
|
||||
]
|
||||
|
||||
|
||||
includes.each {
|
||||
include it
|
||||
ProjectDescriptor project = project(it)
|
||||
project.name = rootProject.name + project.projectDir.absolutePath.replace("${rootDir.absolutePath}", "").replace(File.separator, ".")
|
||||
includes.each { originalName ->
|
||||
String projectDirectory = "${rootProject.projectDir.getAbsolutePath()}${originalName.replaceAll(":", File.separator)}"
|
||||
String projectName = "${rootProject.name}${originalName.replaceAll(":", ".")}"
|
||||
String projectIdentifier = ":${projectName}"
|
||||
include projectIdentifier
|
||||
ProjectDescriptor project = project(projectIdentifier)
|
||||
project.name = projectName
|
||||
project.projectDir = new File(projectDirectory)
|
||||
}
|
||||
|
Reference in New Issue
Block a user