Compare commits

..

60 Commits
0.3.1 ... 0.4.6

Author SHA1 Message Date
091bb1394f Warning annotation 2020-11-26 18:01:12 +06:00
b82c3864a0 typealiases for repos 2020-11-26 15:32:00 +06:00
49ee38a936 FileStandardKeyValueRepo 2020-11-26 15:03:40 +06:00
c201866c51 FlowCollector#invoke 2020-11-26 15:02:30 +06:00
023f841fb5 Pagination#isFirstPage 2020-11-26 15:01:45 +06:00
76102e9ab3 start 0.4.6 2020-11-26 15:00:32 +06:00
b2fc5e2a4d add query params docs 2020-11-25 11:09:54 +06:00
55aacb8753 update publish script 2020-11-23 22:46:40 +06:00
8702846216 Merge pull request #22 from InsanusMokrassar/0.4.5
0.4.5
2020-11-23 18:48:57 +06:00
3347a6d189 Update build.gradle 2020-11-23 18:46:52 +06:00
47b3b42949 replaces 2020-11-23 18:32:14 +06:00
e985631621 add recyclerview.alerts project 2020-11-23 18:10:53 +06:00
e15034bfa4 add tools to android 2020-11-23 17:39:17 +06:00
e5dbcd25dd remove kotlin android extensions 2020-11-23 17:27:52 +06:00
3714c02c12 add Android.Alerts project 2020-11-23 17:24:02 +06:00
fa636b4146 start 0.4.5 2020-11-23 17:15:21 +06:00
3d825aebc3 Update LeftItems.kt 2020-11-23 01:18:30 +06:00
629884a396 update left items 2020-11-23 00:36:37 +06:00
e7a0fa4e8f Merge pull request #21 from InsanusMokrassar/0.4.4
0.4.4
2020-11-22 23:47:04 +06:00
1a41f37a9d update changelog 2020-11-22 23:42:01 +06:00
07a65e0bb5 fixes 2020-11-22 23:19:16 +06:00
615f7f99c3 one more time update mechanism of sql transactions in android 2020-11-22 22:00:50 +06:00
3de5558ed4 new mechanism of transactions 2020-11-22 21:54:51 +06:00
6bbe3a271f update defaults in VersionsRepo 2020-11-22 19:27:53 +06:00
8bee29f683 update version of klock and remove gradle bintray plugin 2020-11-22 19:12:10 +06:00
c40f0fdcb9 VersionsRepo 2020-11-22 19:07:37 +06:00
30d1453a12 start 0.4.4 2020-11-22 17:49:39 +06:00
75fa88b00d Merge pull request #20 from InsanusMokrassar/0.4.3
0.4.3
2020-11-20 13:28:44 +06:00
0f817ad212 update kotlin 2020-11-20 13:10:40 +06:00
d7b46ae0d4 getSp and getDp 2020-11-20 10:23:40 +06:00
b6be14ecca start 0.4.3 2020-11-20 10:20:35 +06:00
33dbfc6f69 Merge pull request #19 from InsanusMokrassar/0.4.2
0.4.2
2020-11-18 20:32:42 +06:00
fcacdcd544 set method in realizations 2020-11-18 20:27:34 +06:00
dd2fc5a86f start 0.4.2 2020-11-18 20:26:08 +06:00
0f8a6f6bde update publishing files 2020-11-18 18:22:19 +06:00
1efd94181d Merge pull request #18 from InsanusMokrassar/0.4.1
0.4.1
2020-11-17 00:51:25 +06:00
71ff0232aa fix keyvalue exposed repo initiation 2020-11-16 20:05:22 +06:00
63921cd984 fix of shared flow size in ExposedKeyValueRepo 2020-11-16 19:48:28 +06:00
051e03bed3 start 0.4.1 2020-11-16 19:46:50 +06:00
a051394f4f Merge pull request #17 from InsanusMokrassar/0.4.0
0.4.0
2020-11-14 18:04:59 +06:00
b872babe45 new extensions withMapper 2020-11-14 17:35:11 +06:00
5a9cabc4bd add ReadStandardKeyValueRepo#keys with value parameter 2020-11-14 17:27:26 +06:00
3ba630684a new ReadOneToManyKeyValueRepo#keys 2020-11-14 16:44:28 +06:00
498cd12f94 start 0.4.0 2020-11-14 16:18:42 +06:00
062848f2e4 Merge pull request #16 from InsanusMokrassar/0.3.3
0.3.3
2020-11-12 22:20:11 +06:00
d4b4547718 plus for flows 2020-11-12 22:16:21 +06:00
22cd440dd7 start 0.3.3 2020-11-12 22:10:41 +06:00
6fc64526d4 update renaming of android parts 2020-11-11 16:29:43 +06:00
08075dfafe update min sdk for kotlin parts 2020-11-11 16:17:19 +06:00
efcb25622e fixes in gradle files 2020-11-11 16:06:44 +06:00
5ebf29d1fb Merge pull request #15 from InsanusMokrassar/0.3.2
0.3.2
2020-11-11 13:14:46 +06:00
b7d0ce3c97 formatting fix in keyvalue 2020-11-11 12:56:31 +06:00
e20929aec4 mappers workaround 2020-11-11 12:56:31 +06:00
f74167cc65 start 0.3.2 2020-11-11 12:56:31 +06:00
c55cd5342e update travis
trying to manually install packages in travis
2020-11-11 12:53:34 +06:00
237d89d611 experimentally update extension internalProject 2020-11-10 23:55:11 +06:00
aff9b52f1c update max heap size of jvm for gradle 2020-11-10 21:46:57 +06:00
d8cc6ed06e update max heap size of jvm for gradle 2020-11-10 21:46:19 +06:00
dffafa54c3 update publishing scripts 2020-11-10 20:53:08 +06:00
776a3af497 Merge pull request #14 from InsanusMokrassar/0.3.1
0.3.1
2020-11-10 19:50:57 +06:00
77 changed files with 1578 additions and 187 deletions

View File

@@ -1,13 +1,27 @@
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
script: ./gradlew build -s -x jvmTest -x jsIrTest -x jsIrBrowserTest -x jsIrNodeTest -x jsLegacyTest -x jsLegacyBrowserTest -x jsLegacyNodeTest
- state: test
script: ./gradlew allTests
# Tests are temporarily disabled on public travis due to the problems of launching
# - state: test
# script: ./gradlew allTests

View File

@@ -1,5 +1,94 @@
# Changelog
## 0.4.6
* `Common`
* New annotation `Warning` has been added
* `Pagination`
* `Common`
* `Pagination` got new extension: `Pagination#isFirstPage`
* `Coroutines`:
* New extension `FlowCollector#invoke` has been added
* `Repos`
* `Common`
* `JVM` (and `Android` since `Android API 26`):
* `FileStandardKeyValueRepo` has been added
* Add several `typealias`es for each type of repos
## 0.4.5
* `Android`
* `Alerts`
* `Common`
* Project has been created
* `RecyclerView`
* Project has been created
* `Common`
* Annotation `PreviewFeature` has been added
* `Android`
* Added tools to work with visibility in more comfortable way
* Added tools to work with disabled/enabled state in more comfortable way
* Added tools to work with expanded/collapsed state in more comfortable way (in preview mode)
## 0.4.4
* `Versions`:
* `Klock`: `1.12.1` -> `2.0.0`
* `Commons`:
* Update left items functionality to include work with `GridLayoutManager`
* `Repos`:
* Add interface `VersionsRepo`
* Add default realization of `VersionsRepo` named `StandardVersionsRepo` which use `StandardVersionsRepoProxy`
to get access to some end-store
* Add default realization of `StandardVersionsRepoProxy` based on `KeyValue` repos
* Add realizations of `StandardVersionsRepoProxy` for exposed and android (`SQL` and `SharedPreferences`)
* `Commons`:
* In Android fully reworked transactions functions
* Now `DatabaseCoroutineContext` is a shortcut for `Dispatchers.IO`
## 0.4.3
* `Versions`:
* `Kotlin`: `1.4.10` -> `1.4.20`
* `Common`:
* Two new extensions for Android:
* `Resources#getSp`
* `Resources#getDp`
## 0.4.2
* `Repos`:
* Add `WriteOneToManyKeyValueRepo#set` function and extensions
## 0.4.1
* `Repos`:
* Fixed error in `ExposedKeyValueRepo` related to negative size of shared flow
* Fixed error in `ExposedKeyValueRepo` related to avoiding of table initiation
## 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**
@@ -201,4 +290,4 @@ All deprecations has been removed
## 0.1.0
Inited :)
Inited :)

View File

@@ -0,0 +1,17 @@
plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
}
apply from: "$mppAndroidProjectPresetPath"
kotlin {
sourceSets {
androidMain {
dependencies {
api "androidx.appcompat:appcompat-resources:$appcompat_version"
}
}
}
}

View File

@@ -0,0 +1 @@
<manifest package="dev.inmo.micro_utils.android.alerts.common"/>

View File

@@ -0,0 +1,55 @@
@file:Suppress("NOTHING_TO_INLINE", "unused")
package dev.inmo.micro_utils.android.alerts.common
import android.app.AlertDialog
import android.content.Context
import android.content.DialogInterface
typealias AlertDialogCallback = (DialogInterface) -> Unit
inline fun Context.createAlertDialogTemplate(
title: String? = null,
positivePair: Pair<String, AlertDialogCallback?>? = null,
neutralPair: Pair<String, AlertDialogCallback?>? = null,
negativePair: Pair<String, AlertDialogCallback?>? = null
): AlertDialog.Builder {
val builder = AlertDialog.Builder(this)
title ?.let {
builder.setTitle(title)
}
positivePair ?. let {
builder.setPositiveButton(it.first) { di, _ -> it.second ?. invoke(di) }
}
negativePair ?. let {
builder.setNegativeButton(it.first) { di, _ -> it.second ?. invoke(di) }
}
neutralPair ?. let {
builder.setNeutralButton(it.first) { di, _ -> it.second ?. invoke(di) }
}
return builder
}
inline fun Context.createAlertDialogTemplateWithResources(
title: Int? = null,
positivePair: Pair<Int, AlertDialogCallback?>? = null,
neutralPair: Pair<Int, AlertDialogCallback?>? = null,
negativePair: Pair<Int, AlertDialogCallback?>? = null
): AlertDialog.Builder = createAlertDialogTemplate(
title ?.let { getString(it) },
positivePair ?.let { getString(it.first) to it.second },
neutralPair ?.let { getString(it.first) to it.second },
negativePair ?.let { getString(it.first) to it.second }
)
inline fun AlertDialog.setDismissChecker(noinline checker: () -> Boolean) : AlertDialog {
setOnDismissListener {
if (!checker()) {
show()
}
}
return this
}

View File

@@ -0,0 +1,38 @@
@file:Suppress("NOTHING_TO_INLINE", "unused")
package dev.inmo.micro_utils.android.alerts.common
import android.app.AlertDialog
import android.content.Context
import android.view.View
inline fun <T: View> Context.createCustomViewAlertDialog(
title: String? = null,
positivePair: Pair<String, AlertDialogCallback?>? = null,
neutralPair: Pair<String, AlertDialogCallback?>? = null,
negativePair: Pair<String, AlertDialogCallback?>? = null,
show: Boolean = true,
viewCreator: (Context) -> T
): AlertDialog = createAlertDialogTemplate(
title, positivePair, neutralPair, negativePair
).apply {
setView(viewCreator(this@createCustomViewAlertDialog))
}.create().apply {
if (show) show()
}
inline fun <T: View> Context.createCustomViewAlertDialogWithResources(
title: Int? = null,
positivePair: Pair<Int, AlertDialogCallback?>? = null,
neutralPair: Pair<Int, AlertDialogCallback?>? = null,
negativePair: Pair<Int, AlertDialogCallback?>? = null,
show: Boolean = true,
viewCreator: (Context) -> T
): AlertDialog = createCustomViewAlertDialog(
title ?.let { getString(it) },
positivePair ?.let { getString(it.first) to it.second },
neutralPair ?.let { getString(it.first) to it.second },
negativePair ?.let { getString(it.first) to it.second },
show,
viewCreator
)

View File

@@ -0,0 +1,45 @@
@file:Suppress("NOTHING_TO_INLINE", "unused")
package dev.inmo.micro_utils.android.alerts.common
import android.app.AlertDialog
import android.content.Context
import androidx.annotation.StringRes
inline fun Context.createSimpleTextAlertDialog(
text: String,
title: String? = null,
positivePair: Pair<String, AlertDialogCallback?>? = null,
neutralPair: Pair<String, AlertDialogCallback?>? = null,
negativePair: Pair<String, AlertDialogCallback?>? = null,
show: Boolean = true
): AlertDialog = createAlertDialogTemplate(
title,
positivePair,
neutralPair,
negativePair
).apply {
setMessage(text)
}.create().apply {
if (show) {
show()
}
}
inline fun Context.createSimpleTextAlertDialog(
@StringRes
text: Int,
@StringRes
title: Int? = null,
positivePair: Pair<Int, AlertDialogCallback?>? = null,
neutralPair: Pair<Int, AlertDialogCallback?>? = null,
negativePair: Pair<Int, AlertDialogCallback?>? = null,
show: Boolean = true
): AlertDialog = createSimpleTextAlertDialog(
getString(text),
title ?.let { getString(it) },
positivePair ?.let { getString(it.first) to it.second },
neutralPair ?.let { getString(it.first) to it.second },
negativePair ?.let { getString(it.first) to it.second },
show
)

View File

@@ -0,0 +1,18 @@
plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
}
apply from: "$mppAndroidProjectPresetPath"
kotlin {
sourceSets {
commonMain {
dependencies {
api internalProject("micro_utils.android.alerts.common")
api internalProject("micro_utils.android.recyclerview")
}
}
}
}

View File

@@ -0,0 +1 @@
<manifest package="dev.inmo.micro_utils.android.alerts.recyclerview"/>

View File

@@ -0,0 +1,65 @@
@file:Suppress("unused")
package dev.inmo.micro_utils.android.alerts.recyclerview
import android.app.AlertDialog
import android.content.Context
import android.content.DialogInterface
import android.view.ViewGroup
import android.widget.TextView
import dev.inmo.micro_utils.android.alerts.common.AlertDialogCallback
import dev.inmo.micro_utils.android.recyclerview.*
data class AlertAction(
val title: String,
val callback: (DialogInterface) -> Unit
)
private class ActionViewHolder(
container: ViewGroup, dialogInterfaceGetter: () -> DialogInterface
) : AbstractStandardViewHolder<AlertAction>(container, android.R.layout.simple_list_item_1) {
private lateinit var action: AlertAction
private val textView: TextView
get() = itemView.findViewById(android.R.id.text1)
init {
itemView.setOnClickListener {
action.callback(dialogInterfaceGetter())
}
}
override fun onBind(item: AlertAction) {
action = item
textView.text = item.title
}
}
private class ActionsRecyclerViewAdapter(
data: List<AlertAction>,
private val dialogInterfaceGetter: () -> DialogInterface
) : RecyclerViewAdapter<AlertAction>(data) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AbstractViewHolder<AlertAction> = ActionViewHolder(
parent, dialogInterfaceGetter
)
}
fun Context.createActionsAlertDialog(
actions: List<AlertAction>,
title: Int? = null,
positivePair: Pair<Int, AlertDialogCallback?>? = null,
neutralPair: Pair<Int, AlertDialogCallback?>? = null,
negativePair: Pair<Int, AlertDialogCallback?>? = null,
show: Boolean = true
): AlertDialog {
lateinit var dialogInterface: DialogInterface
return createRecyclerViewDialog(
title, positivePair, neutralPair, negativePair, show
) {
ActionsRecyclerViewAdapter(
actions
) {
dialogInterface
}
}.also { dialogInterface = it }
}

View File

@@ -0,0 +1,43 @@
package dev.inmo.micro_utils.android.alerts.recyclerview
import android.app.AlertDialog
import android.content.Context
import android.widget.LinearLayout
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import dev.inmo.micro_utils.android.alerts.common.AlertDialogCallback
import dev.inmo.micro_utils.android.alerts.common.createCustomViewAlertDialogWithResources
fun Context.createRecyclerViewDialog(
title: Int? = null,
positivePair: Pair<Int, AlertDialogCallback?>? = null,
neutralPair: Pair<Int, AlertDialogCallback?>? = null,
negativePair: Pair<Int, AlertDialogCallback?>? = null,
show: Boolean = true,
layoutManager: RecyclerView.LayoutManager = LinearLayoutManager(this),
marginOfRecyclerView: Int = 8, // dp
recyclerViewSetUp: RecyclerView.() -> Unit = {},
adapterFactory: () -> RecyclerView.Adapter<*>
): AlertDialog {
val recyclerView = RecyclerView(this).apply {
layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
).apply {
setMargins(marginOfRecyclerView, marginOfRecyclerView, marginOfRecyclerView, marginOfRecyclerView)
}
this.layoutManager = layoutManager
adapter = adapterFactory()
recyclerViewSetUp()
}
return createCustomViewAlertDialogWithResources(
title,
positivePair,
neutralPair,
negativePair,
show
) {
recyclerView
}
}

View File

@@ -2,7 +2,6 @@ plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
id "kotlin-android-extensions"
}
apply from: "$mppAndroidProjectPresetPath"

View File

@@ -1,14 +1,20 @@
package dev.inmo.micro_utils.android.recyclerview
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.*
@Suppress("NOTHING_TO_INLINE")
private inline fun RecyclerView.LayoutManager.findLastVisibleItemPositionGetter(): (() -> Int)? = when (this) {
is LinearLayoutManager -> ::findLastVisibleItemPosition
is GridLayoutManager -> ::findLastVisibleItemPosition
else -> null
}
fun RecyclerView.lastVisibleItemFlow(
completingScope: CoroutineScope
): Flow<Int> {
val lastVisibleElementFun: () -> Int = (layoutManager as? LinearLayoutManager) ?.let { it::findLastVisibleItemPosition } ?: error("Currently supported only linear layout manager")
val lastVisibleElementFun: () -> Int = layoutManager ?.findLastVisibleItemPositionGetter() ?: error("Currently supported only linear and grid layout manager")
val lastVisibleFlow = MutableStateFlow(lastVisibleElementFun())
addOnScrollListener(
object : RecyclerView.OnScrollListener() {

View File

@@ -8,10 +8,9 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:4.0.0'
classpath 'com.android.tools.build:gradle:4.0.2'
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"

View File

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

View File

@@ -2,7 +2,6 @@ plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
id "kotlin-android-extensions"
}
apply from: "$mppProjectWithSerializationPresetPath"

View File

@@ -0,0 +1,37 @@
package dev.inmo.micro_utils.common
@RequiresOptIn(
"It is possible, that behaviour of this thing will be changed or removed in future releases",
RequiresOptIn.Level.WARNING
)
@Target(
AnnotationTarget.CLASS,
AnnotationTarget.CONSTRUCTOR,
AnnotationTarget.FIELD,
AnnotationTarget.PROPERTY,
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.PROPERTY_SETTER,
AnnotationTarget.FUNCTION,
AnnotationTarget.TYPE,
AnnotationTarget.TYPEALIAS,
AnnotationTarget.TYPE_PARAMETER
)
annotation class PreviewFeature
@RequiresOptIn(
"This thing is marked as warned. See message of warn to get more info",
RequiresOptIn.Level.WARNING
)
@Target(
AnnotationTarget.CLASS,
AnnotationTarget.CONSTRUCTOR,
AnnotationTarget.FIELD,
AnnotationTarget.PROPERTY,
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.PROPERTY_SETTER,
AnnotationTarget.FUNCTION,
AnnotationTarget.TYPE,
AnnotationTarget.TYPEALIAS,
AnnotationTarget.TYPE_PARAMETER
)
annotation class Warning(val message: String)

View File

@@ -0,0 +1,13 @@
@file:Suppress("NOTHING_TO_INLINE")
package dev.inmo.micro_utils.common
import android.content.res.Resources
inline fun Resources.getSp(
resId: Int
) = getDimension(resId) / displayMetrics.scaledDensity
inline fun Resources.getDp(
resId: Int
) = getDimension(resId) * displayMetrics.density

View File

@@ -0,0 +1,76 @@
package dev.inmo.micro_utils.common
import android.view.View
import android.view.ViewGroup
import android.view.animation.Animation
import android.view.animation.Transformation
@PreviewFeature
fun View.expand(
duration: Long = 500,
targetWidth: Int = ViewGroup.LayoutParams.MATCH_PARENT,
targetHeight: Int = ViewGroup.LayoutParams.WRAP_CONTENT
) {
measure(targetWidth, targetHeight)
val measuredHeight: Int = measuredHeight
layoutParams.height = 0
visibility = View.VISIBLE
val a: Animation = object : Animation() {
override fun applyTransformation(interpolatedTime: Float, t: Transformation?) {
super.applyTransformation(interpolatedTime, t)
layoutParams.height = if (interpolatedTime == 1f) targetHeight else (measuredHeight * interpolatedTime).toInt()
requestLayout()
}
override fun willChangeBounds(): Boolean {
return true
}
}
a.duration = duration
startAnimation(a)
}
@PreviewFeature
fun View.collapse(duration: Long = 500) {
val initialHeight: Int = measuredHeight
val a: Animation = object : Animation() {
override fun applyTransformation(interpolatedTime: Float, t: Transformation?) {
if (interpolatedTime == 1f) {
visibility = View.GONE
} else {
layoutParams.height = initialHeight - (initialHeight * interpolatedTime).toInt()
requestLayout()
}
}
override fun willChangeBounds(): Boolean {
return true
}
}
a.duration = duration
startAnimation(a)
}
@PreviewFeature
inline val View.isCollapsed
get() = visibility == View.GONE
@PreviewFeature
inline val View.isExpanded
get() = !isCollapsed
/**
* @return true in case of expanding
*/
@PreviewFeature
fun View.toggleExpandState(duration: Long = 500): Boolean = if (isCollapsed) {
expand(duration)
true
} else {
collapse(duration)
false
}

View File

@@ -0,0 +1,34 @@
@file:Suppress("NOTHING_TO_INLINE", "unused")
package dev.inmo.micro_utils.common
import android.view.View
import android.view.ViewGroup
inline val View.enabled
get() = isEnabled
inline val View.disabled
get() = !enabled
fun View.disable() {
if (this is ViewGroup) {
(0 until childCount).forEach { getChildAt(it).disable() }
}
isEnabled = false
}
fun View.enable() {
if (this is ViewGroup) {
(0 until childCount).forEach { getChildAt(it).enable() }
}
isEnabled = true
}
fun View.toggleEnabledState(enabled: Boolean) {
if (enabled) {
enable()
} else {
disable()
}
}

View File

@@ -0,0 +1,35 @@
@file:Suppress("NOTHING_TO_INLINE", "unused")
package dev.inmo.micro_utils.common
import android.view.View
inline val View.gone
get() = visibility == View.GONE
inline fun View.gone() {
visibility = View.GONE
}
inline val View.hidden
get() = visibility == View.INVISIBLE
inline fun View.hide() {
visibility = View.INVISIBLE
}
inline val View.shown
get() = visibility == View.VISIBLE
inline fun View.show() {
visibility = View.VISIBLE
}
fun View.toggleVisibility(goneOnHide: Boolean = true) {
if (isShown) {
if (goneOnHide) {
gone()
} else {
hide()
}
} else {
show()
}
}

View File

@@ -2,7 +2,6 @@ plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
id "kotlin-android-extensions"
}
apply from: "$mppProjectWithSerializationPresetPath"

View File

@@ -0,0 +1,5 @@
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.flow.FlowCollector
suspend inline operator fun <T> FlowCollector<T>.invoke(value: T) = emit(value)

View File

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

View File

@@ -12,6 +12,9 @@ android {
release {
minifyEnabled false
}
debug {
debuggable true
}
}
packagingOptions {
@@ -28,4 +31,8 @@ android {
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
}

View File

@@ -2,7 +2,6 @@ plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
id "kotlin-android-extensions"
id "org.jetbrains.dokka"
}

View File

@@ -10,11 +10,12 @@ 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"

View File

@@ -5,18 +5,17 @@ kotlin.incremental=true
kotlin.incremental.js=true
android.useAndroidX=true
android.enableJetifier=true
org.gradle.jvmargs=-Xmx4096m
org.gradle.jvmargs=-Xmx2048m
kotlin_version=1.4.10
kotlin_version=1.4.20
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
klockVersion=2.0.0
gradle_bintray_plugin_version=1.8.5
github_release_plugin_version=2.2.12
uuidVersion=0.2.2
@@ -25,8 +24,9 @@ uuidVersion=0.2.2
core_ktx_version=1.3.2
androidx_recycler_version=1.1.0
appcompat_version=1.2.0
android_minSdkVersion=24
android_minSdkVersion=19
android_compileSdkVersion=30
android_buildToolsVersion=30.0.2
dexcount_version=2.0.0-RC1
@@ -41,5 +41,5 @@ dokka_version=1.4.0
# Project data
group=dev.inmo
version=0.3.1
android_code_version=1
version=0.4.6
android_code_version=10

View File

@@ -2,7 +2,6 @@ plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
id "kotlin-android-extensions"
}
apply from: "$mppProjectWithSerializationPresetPath"

View File

@@ -2,7 +2,6 @@ plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
id "kotlin-android-extensions"
}
apply from: "$mppProjectWithSerializationPresetPath"

View File

@@ -3,16 +3,28 @@ package dev.inmo.micro_utils.ktor.common
typealias QueryParam = Pair<String, String?>
typealias QueryParams = Map<String, String?>
/**
* Create query part which includes key=value pairs separated with &
*/
val QueryParams.asUrlQuery: String
get() = keys.joinToString("&") { "${it}${get(it) ?.let { value -> "=$value" }}" }
/**
* Create query part which includes key=value pairs separated with &
*/
val List<QueryParam>.asUrlQuery: String
get() = joinToString("&") { (key, value) -> "${key}${value ?.let { _ -> "=$value" }}" }
/**
* Create query part which includes key=value pairs separated with & and attach to receiver
*/
fun String.includeQueryParams(
queryParams: QueryParams
): String = "$this${if(queryParams.isNotEmpty()) "${if (contains("?")) "&" else "?"}${queryParams.asUrlQuery}" else ""}"
/**
* Create query part which includes key=value pairs separated with & and attach to receiver
*/
fun String.includeQueryParams(
queryParams: List<QueryParam>
): String = "$this${if (contains("?")) "&" else "?"}${queryParams.asUrlQuery}"

View File

@@ -1,60 +0,0 @@
apply plugin: 'maven-publish'
task javadocsJar(type: Jar) {
classifier = 'javadoc'
}
afterEvaluate {
project.publishing.publications.all {
// rename artifacts
groupId "${project.group}"
if (it.name.contains('kotlinMultiplatform')) {
artifactId = "${project.name}"
} else {
artifactId = "${project.name}-$name"
}
}
}
publishing {
publications.all {
artifact javadocsJar
pom {
description = ""
name = "${project.name}"
url = "https://git.inmo.dev/InsanusMokrassar/MicroUtils_mirror"
scm {
developerConnection = "scm:git:[fetch=]ssh://git@git.inmo.dev:8322/InsanusMokrassar/MicroUtils_mirror.git[push=]ssh://git@git.inmo.dev:8322/InsanusMokrassar/MicroUtils_mirror.git"
url = "ssh://git@git.inmo.dev:8322/InsanusMokrassar/MicroUtils_mirror.git"
}
developers {
developer {
id = "InsanusMokrassar"
name = "Aleksei Ovsiannikov"
email = "ovsyannikov.alexey95@gmail.com"
}
developer {
id = "000Sanya"
name = "Syrov Aleksandr"
email = "000sanya.000sanya@gmail.com"
}
}
licenses {
license {
name = "Apache Software License 2.0"
url = "https://git.inmo.dev/InsanusMokrassar/MicroUtils_mirror/src/master/LICENSE"
}
}
}
}
}

View File

@@ -2,7 +2,6 @@ plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
id "kotlin-android-extensions"
}
apply from: "$mppProjectWithSerializationPresetPath"

View File

@@ -4,7 +4,9 @@ project.group = "$group"
apply from: "$publishGradlePath"
kotlin {
android {}
android {
publishAllLibraryVariants()
}
sourceSets {
commonMain {

View File

@@ -10,7 +10,7 @@ kotlin {
nodejs()
}
android {
publishLibraryVariants("release")
publishAllLibraryVariants()
}
sourceSets {

View File

@@ -2,7 +2,6 @@ plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
id "kotlin-android-extensions"
}
apply from: "$mppProjectWithSerializationPresetPath"

View File

@@ -22,6 +22,12 @@ interface Pagination {
val size: Int
}
/**
* Logical shortcut for comparison that page is 0
*/
inline val Pagination.isFirstPage
get() = page == 0
/**
* First number in index of objects. It can be used as offset for databases or other data sources
*/

View File

@@ -2,7 +2,6 @@ plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
id "kotlin-android-extensions"
}
apply from: "$mppProjectWithSerializationPresetPath"

View File

@@ -1 +1 @@
{"bintrayConfig":{"repo":"MicroUtils","packageName":"${project.name}","packageVcs":"https://github.com/InsanusMokrassar/MicroUtils","autoPublish":true,"overridePublish":true},"licenses":[{"id":"Apache-2.0","title":"Apache Software License 2.0","url":"https://git.inmo.dev/InsanusMokrassar/MicroUtils_mirror/src/master/LICENSE"}],"mavenConfig":{"name":"${project.name}","description":"","url":"https://git.inmo.dev/InsanusMokrassar/MicroUtils_mirror","vcsUrl":"ssh://git@git.inmo.dev:8322/InsanusMokrassar/MicroUtils_mirror.git","developers":[{"id":"InsanusMokrassar","name":"Aleksei Ovsiannikov","eMail":"ovsyannikov.alexey95@gmail.com"},{"id":"000Sanya","name":"Syrov Aleksandr","eMail":"000sanya.000sanya@gmail.com"}]},"type":"Multiplatform"}
{"bintrayConfig":{"repo":"MicroUtils","packageName":"${project.name}","packageVcs":"https://github.com/InsanusMokrassar/MicroUtils","autoPublish":true,"overridePublish":true},"licenses":[{"id":"Apache-2.0","title":"Apache Software License 2.0","url":"https://git.inmo.dev/InsanusMokrassar/MicroUtils_mirror/src/master/LICENSE"}],"mavenConfig":{"name":"${project.name}","description":"","url":"https://git.inmo.dev/InsanusMokrassar/MicroUtils_mirror","vcsUrl":"ssh://git@git.inmo.dev:8322/InsanusMokrassar/MicroUtils_mirror.git","developers":[{"id":"InsanusMokrassar","name":"Aleksei Ovsiannikov","eMail":"ovsyannikov.alexey95@gmail.com"},{"id":"000Sanya","name":"Syrov Aleksandr","eMail":"000sanya.000sanya@gmail.com"}]}}

View File

@@ -1,59 +1,76 @@
apply plugin: 'com.jfrog.bintray'
apply plugin: 'maven-publish'
apply from: "$publishMavenPath"
bintray {
user = project.hasProperty('BINTRAY_USER') ? project.property('BINTRAY_USER') : System.getenv('BINTRAY_USER')
key = project.hasProperty('BINTRAY_KEY') ? project.property('BINTRAY_KEY') : System.getenv('BINTRAY_KEY')
filesSpec {
from "${buildDir}/publications/"
eachFile {
String directorySubname = it.getFile().parentFile.name
if (it.getName() == "module.json") {
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")
}
} else {
if (directorySubname == "kotlinMultiplatform" && it.getName() == "pom-default.xml") {
it.setPath("${project.name}/${project.version}/${project.name}-${project.version}.pom")
} else {
it.exclude()
}
}
}
into "${project.group}".replace(".", "/")
}
publish = true
override = true
pkg {
repo = "MicroUtils"
name = "${project.name}"
vcsUrl = "https://github.com/InsanusMokrassar/MicroUtils"
licenses = ["Apache-2.0"]
version {
name = "${project.version}"
released = new Date()
vcsTag = "${project.version}"
gpg {
sign = true
passphrase = project.hasProperty('signing.gnupg.passphrase') ? project.property('signing.gnupg.passphrase') : System.getenv('signing.gnupg.passphrase')
}
}
}
task javadocsJar(type: Jar) {
classifier = 'javadoc'
}
task sourceJar (type : Jar) {
classifier = 'sources'
}
bintrayUpload.doFirst {
publications = publishing.publications.collect {
afterEvaluate {
project.publishing.publications.all {
// rename artifacts
groupId "${project.group}"
if (it.name.contains('kotlinMultiplatform')) {
null
artifactId = "${project.name}"
artifact sourceJar
} else {
it.name
artifactId = "${project.name}-$name"
}
} - null
}
}
bintrayUpload.dependsOn publishToMavenLocal
publishing {
publications.all {
artifact javadocsJar
pom {
description = ""
name = "${project.name}"
url = "https://git.inmo.dev/InsanusMokrassar/MicroUtils_mirror"
scm {
developerConnection = "scm:git:[fetch=]ssh://git@git.inmo.dev:8322/InsanusMokrassar/MicroUtils_mirror.git[push=]ssh://git@git.inmo.dev:8322/InsanusMokrassar/MicroUtils_mirror.git"
url = "ssh://git@git.inmo.dev:8322/InsanusMokrassar/MicroUtils_mirror.git"
}
developers {
developer {
id = "InsanusMokrassar"
name = "Aleksei Ovsiannikov"
email = "ovsyannikov.alexey95@gmail.com"
}
developer {
id = "000Sanya"
name = "Syrov Aleksandr"
email = "000sanya.000sanya@gmail.com"
}
}
licenses {
license {
name = "Apache Software License 2.0"
url = "https://git.inmo.dev/InsanusMokrassar/MicroUtils_mirror/src/master/LICENSE"
}
}
}
repositories {
maven {
name = "bintray"
url = uri("https://api.bintray.com/maven/${project.hasProperty('BINTRAY_USER') ? project.property('BINTRAY_USER') : System.getenv('BINTRAY_USER')}/MicroUtils/${project.name}/;publish=1;override=1")
credentials {
username = project.hasProperty('BINTRAY_USER') ? project.property('BINTRAY_USER') : System.getenv('BINTRAY_USER')
password = project.hasProperty('BINTRAY_KEY') ? project.property('BINTRAY_KEY') : System.getenv('BINTRAY_KEY')
}
}
}
}
}

View File

@@ -2,7 +2,6 @@ plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
id "kotlin-android-extensions"
}
apply from: "$mppProjectWithSerializationPresetPath"
@@ -18,6 +17,11 @@ kotlin {
}
}
jvmMain {
dependencies {
api internalProject("micro_utils.common")
}
}
androidMain {
dependencies {
api "androidx.core:core-ktx:$core_ktx_version"

View File

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

View File

@@ -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
@@ -35,6 +36,7 @@ interface ReadOneToManyKeyValueRepo<Key, Value> : Repo {
}
}
}
typealias ReadKeyValuesRepo<Key,Value> = ReadOneToManyKeyValueRepo<Key, Value>
interface WriteOneToManyKeyValueRepo<Key, Value> : Repo {
val onNewValue: Flow<Pair<Key, Value>>
@@ -46,7 +48,13 @@ interface WriteOneToManyKeyValueRepo<Key, Value> : Repo {
suspend fun remove(toRemove: Map<Key, List<Value>>)
suspend fun clear(k: Key)
suspend fun set(toSet: Map<Key, List<Value>>) {
toSet.keys.forEach { key -> clear(key) }
add(toSet)
}
}
typealias WriteKeyValuesRepo<Key,Value> = WriteOneToManyKeyValueRepo<Key, Value>
suspend inline fun <Key, Value, REPO : WriteOneToManyKeyValueRepo<Key, Value>> REPO.add(
keysAndValues: List<Pair<Key, List<Value>>>
@@ -64,7 +72,24 @@ suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.add(
k: Key, vararg v: Value
) = add(k, v.toList())
suspend inline fun <Key, Value, REPO : WriteOneToManyKeyValueRepo<Key, Value>> REPO.set(
keysAndValues: List<Pair<Key, List<Value>>>
) = set(keysAndValues.toMap())
suspend inline fun <Key, Value, REPO : WriteOneToManyKeyValueRepo<Key, Value>> REPO.set(
vararg keysAndValues: Pair<Key, List<Value>>
) = set(keysAndValues.toMap())
suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.set(
k: Key, v: List<Value>
) = set(mapOf(k to v))
suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.set(
k: Key, vararg v: Value
) = set(k, v.toList())
interface OneToManyKeyValueRepo<Key, Value> : ReadOneToManyKeyValueRepo<Key, Value>, WriteOneToManyKeyValueRepo<Key, Value>
typealias KeyValuesRepo<Key,Value> = OneToManyKeyValueRepo<Key, Value>
suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.remove(
keysAndValues: List<Pair<Key, List<Value>>>

View File

@@ -10,6 +10,7 @@ interface ReadStandardCRUDRepo<ObjectType, IdType> : Repo {
suspend fun contains(id: IdType): Boolean
suspend fun count(): Long
}
typealias ReadCRUDRepo<ObjectType, IdType> = ReadStandardCRUDRepo<ObjectType, IdType>
typealias UpdatedValuePair<IdType, ValueType> = Pair<IdType, ValueType>
val <IdType> UpdatedValuePair<IdType, *>.id
@@ -27,6 +28,7 @@ interface WriteStandardCRUDRepo<ObjectType, IdType, InputValueType> : Repo {
suspend fun update(values: List<UpdatedValuePair<IdType, InputValueType>>): List<ObjectType>
suspend fun deleteById(ids: List<IdType>)
}
typealias WriteCRUDRepo<ObjectType, IdType, InputValueType> = WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>
suspend fun <ObjectType, IdType, InputValueType> WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>.create(
vararg values: InputValueType
@@ -39,4 +41,5 @@ suspend fun <ObjectType, IdType, InputValueType> WriteStandardCRUDRepo<ObjectTyp
) = deleteById(ids.toList())
interface StandardCRUDRepo<ObjectType, IdType, InputValueType> : ReadStandardCRUDRepo<ObjectType, IdType>,
WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>
WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>
typealias CRUDRepo<ObjectType, IdType, InputValueType> = StandardCRUDRepo<ObjectType, IdType, InputValueType>

View File

@@ -8,9 +8,11 @@ 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
}
typealias ReadKeyValueRepo<Key,Value> = ReadStandardKeyValueRepo<Key, Value>
interface WriteStandardKeyValueRepo<Key, Value> : Repo {
val onNewValue: Flow<Pair<Key, Value>>
@@ -19,6 +21,7 @@ interface WriteStandardKeyValueRepo<Key, Value> : Repo {
suspend fun set(toSet: Map<Key, Value>)
suspend fun unset(toUnset: List<Key>)
}
typealias WriteKeyValueRepo<Key,Value> = WriteStandardKeyValueRepo<Key, Value>
suspend inline fun <Key, Value> WriteStandardKeyValueRepo<Key, Value>.set(
vararg toSet: Pair<Key, Value>
@@ -32,4 +35,5 @@ suspend inline fun <Key, Value> WriteStandardKeyValueRepo<Key, Value>.unset(
vararg k: Key
) = unset(k.toList())
interface StandardKeyValueRepo<Key, Value> : ReadStandardKeyValueRepo<Key, Value>, WriteStandardKeyValueRepo<Key, Value>
interface StandardKeyValueRepo<Key, Value> : ReadStandardKeyValueRepo<Key, Value>, WriteStandardKeyValueRepo<Key, Value>
typealias KeyValueRepo<Key,Value> = StandardKeyValueRepo<Key, Value>

View File

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

View File

@@ -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>
@@ -75,9 +107,30 @@ open class MapperWriteOneToManyKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
}.toMap()
)
override suspend fun set(toSet: Map<FromKey, List<FromValue>>) {
to.set(
toSet.map { (k, vs) -> k.toOutKey() to vs.map { v -> v.toOutValue() } }.toMap()
)
}
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 +138,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)
)

View File

@@ -0,0 +1,13 @@
package dev.inmo.micro_utils.repos.versions
import dev.inmo.micro_utils.repos.StandardKeyValueRepo
import dev.inmo.micro_utils.repos.set
class KeyValueBasedVersionsRepoProxy<T>(
private val keyValueStore: StandardKeyValueRepo<String, Int>,
override val database: T
) : StandardVersionsRepoProxy<T> {
override suspend fun getTableVersion(tableName: String): Int? = keyValueStore.get(tableName)
override suspend fun updateTableVersion(tableName: String, version: Int) { keyValueStore.set(tableName, version) }
}

View File

@@ -0,0 +1,36 @@
package dev.inmo.micro_utils.repos.versions
import dev.inmo.micro_utils.repos.Repo
interface StandardVersionsRepoProxy<T> : Repo {
val database: T
suspend fun getTableVersion(tableName: String): Int?
suspend fun updateTableVersion(tableName: String, version: Int)
}
class StandardVersionsRepo<T>(
private val proxy: StandardVersionsRepoProxy<T>
) : VersionsRepo<T> {
override suspend fun setTableVersion(
tableName: String,
version: Int,
onCreate: suspend T.() -> Unit,
onUpdate: suspend T.(from: Int, to: Int) -> Unit
) {
var savedVersion = proxy.getTableVersion(tableName)
if (savedVersion == null) {
proxy.database.onCreate()
proxy.updateTableVersion(tableName, version)
} else {
while (savedVersion != null && savedVersion < version) {
val newVersion = savedVersion + 1
proxy.database.onUpdate(savedVersion, newVersion)
proxy.updateTableVersion(tableName, newVersion)
savedVersion = newVersion
}
}
}
}

View File

@@ -0,0 +1,31 @@
package dev.inmo.micro_utils.repos.versions
import dev.inmo.micro_utils.repos.Repo
/**
* This interface has been created due to requirement to work with different versions of databases and make some
* migrations between versions
*
* @param T It is a type of database, which will be used by this repo to retrieve current table version and update it
*/
interface VersionsRepo<T> : Repo {
/**
* By default, instance of this interface will check that version of table with name [tableName] is less than
* [version] or is absent
*
* * In case if [tableName] didn't found, will be called [onCreate] and version of table will be set up to [version]
* * In case if [tableName] have version less than parameter [version], it will increase version one-by-one
* until database version will be equal to [version]
*
* @param version Current version of table
* @param onCreate This callback will be called in case when table have no information about table
* @param onUpdate This callback will be called after **iterative** changing of version. It is expected that parameter
* "to" will always be greater than "from"
*/
suspend fun setTableVersion(
tableName: String,
version: Int,
onCreate: suspend T.() -> Unit = {},
onUpdate: suspend T.(from: Int, to: Int) -> Unit = { _, _ ->}
)
}

View File

@@ -0,0 +1,171 @@
package dev.inmo.micro_utils.repos
import dev.inmo.micro_utils.common.Warning
import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.pagination.utils.reverse
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import java.io.File
import java.nio.file.FileSystems
import java.nio.file.Path
import java.nio.file.StandardWatchEventKinds.*
private inline val String.isAbsolute
get() = startsWith(File.separator)
class FileReadStandardKeyValueRepo(
private val folder: File
) : ReadStandardKeyValueRepo<String, File> {
init {
folder.mkdirs()
}
override suspend fun get(k: String): File? {
val file = File(folder, k)
if (file.exists()) {
return file
}
return null
}
override suspend fun values(pagination: Pagination, reversed: Boolean): PaginationResult<File> {
val count = count()
val resultPagination = if (reversed) pagination.reverse(count) else pagination
val filesPaths = folder.list() ?.copyOfRange(resultPagination.firstIndex, resultPagination.lastIndex) ?: return emptyPaginationResult()
if (reversed) {
filesPaths.reverse()
}
return filesPaths.map { File(folder, it) }.createPaginationResult(
resultPagination,
count
)
}
override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult<String> {
val count = count()
val resultPagination = if (reversed) pagination.reverse(count) else pagination
val filesPaths = folder.list() ?.copyOfRange(resultPagination.firstIndex, resultPagination.lastIndex) ?: return emptyPaginationResult()
if (reversed) {
filesPaths.reverse()
}
return filesPaths.toList().createPaginationResult(
resultPagination,
count
)
}
override suspend fun keys(
v: File,
pagination: Pagination,
reversed: Boolean
): PaginationResult<String> {
val resultPagination = if (reversed) pagination.reverse(1L) else pagination
return if (resultPagination.isFirstPage) {
val fileSubpath = v.absolutePath.removePrefix(folder.absolutePath)
if (fileSubpath == v.absolutePath) {
emptyList()
} else {
listOf(fileSubpath)
}
} else {
emptyList()
}.createPaginationResult(resultPagination, 1L)
}
override suspend fun contains(key: String): Boolean {
return File(folder, key).exists()
}
override suspend fun count(): Long = folder.list() ?.size ?.toLong() ?: 0L
}
/**
* Files watching will not correctly works on Android with version of API lower than API 26
*/
@Warning("Files watching will not correctly works on Android Platform with version of API lower than API 26")
class FileWriteStandardKeyValueRepo(
private val folder: File,
filesChangedProcessingScope: CoroutineScope? = null
) : WriteStandardKeyValueRepo<String, File> {
private val _onNewValue = MutableSharedFlow<Pair<String, File>>()
override val onNewValue: Flow<Pair<String, File>> = _onNewValue.asSharedFlow()
private val _onValueRemoved = MutableSharedFlow<String>()
override val onValueRemoved: Flow<String> = _onValueRemoved.asSharedFlow()
init {
folder.mkdirs()
filesChangedProcessingScope ?.let {
it.launch {
try {
val folderPath = folder.toPath()
while (isActive) {
val key = try {
folderPath.register(FileSystems.getDefault().newWatchService(), ENTRY_CREATE, ENTRY_MODIFY, ENTRY_DELETE)
} catch (e: Exception) {
// add verbose way to show that file watching is not working
return@launch
}
for (event in key.pollEvents()) {
val relativeFilePath = (event.context() as? Path) ?: continue
val file = relativeFilePath.toFile()
val relativePath = file.toRelativeString(folder)
when (event.kind()) {
ENTRY_CREATE, ENTRY_MODIFY -> {
launch {
_onNewValue.emit(relativePath to file)
}
}
ENTRY_DELETE -> {
launch {
_onValueRemoved.emit(relativePath)
}
}
}
}
if (key.isValid || folder.exists()) {
continue
}
break
}
} catch (e: Throwable) {
// add verbose way to notify that this functionality is disabled
}
}
}
}
override suspend fun set(toSet: Map<String, File>) {
supervisorScope {
toSet.map { (filename, fileSource) ->
launch {
val file = File(folder, filename)
file.delete()
fileSource.copyTo(file, overwrite = true)
_onNewValue.emit(filename to file)
}
}
}.joinAll()
}
override suspend fun unset(toUnset: List<String>) {
toUnset.forEach {
val file = File(folder, it)
if (file.exists()) {
file.delete()
_onValueRemoved.emit(it)
}
}
}
}
@Warning("Files watching will not correctly works on Android Platform with version of API lower than API 26")
class FileStandardKeyValueRepo(
folder: File,
filesChangedProcessingScope: CoroutineScope? = null
) : StandardKeyValueRepo<String, File>,
WriteStandardKeyValueRepo<String, File> by FileWriteStandardKeyValueRepo(folder, filesChangedProcessingScope),
ReadStandardKeyValueRepo<String, File> by FileReadStandardKeyValueRepo(folder)

View File

@@ -1,6 +1,6 @@
package dev.inmo.micro_utils.repos
import kotlinx.coroutines.newSingleThreadContext
import kotlinx.coroutines.Dispatchers
import kotlin.coroutines.CoroutineContext
val DatabaseCoroutineContext: CoroutineContext = newSingleThreadContext("db-context")
val DatabaseCoroutineContext: CoroutineContext = Dispatchers.IO

View File

@@ -2,26 +2,68 @@ package dev.inmo.micro_utils.repos
import android.database.sqlite.SQLiteDatabase
import dev.inmo.micro_utils.coroutines.safely
import kotlinx.coroutines.withContext
import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import java.util.concurrent.Executors
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.coroutineContext
private object ContextsPool {
private val contexts = mutableListOf<CoroutineContext>()
private val mutex = Mutex(locked = false)
private val freeContexts = mutableListOf<CoroutineContext>()
suspend fun acquireContext(): CoroutineContext {
return mutex.withLock {
freeContexts.removeFirstOrNull() ?: Executors.newSingleThreadExecutor().asCoroutineDispatcher().also {
contexts.add(it)
}
}
}
suspend fun freeContext(context: CoroutineContext) {
return mutex.withLock {
if (context in contexts && context !in freeContexts) {
freeContexts.add(context)
}
}
}
suspend fun <T> use(block: suspend (CoroutineContext) -> T): T = acquireContext().let {
try {
block(it)
} finally {
freeContext(it)
}
}
}
class TransactionContext(
val databaseContext: CoroutineContext
): CoroutineContext.Element {
override val key: CoroutineContext.Key<TransactionContext> = TransactionContext
companion object : CoroutineContext.Key<TransactionContext>
}
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()
}
return coroutineContext[TransactionContext] ?.let {
withContext(it.databaseContext) {
block()
}
} ?: ContextsPool.use { context ->
withContext(TransactionContext(context) + context) {
beginTransaction()
safely(
{
endTransaction()
throw it
}
) {
block().also {
setTransactionSuccessful()
endTransaction()
}
}
}

View File

@@ -5,6 +5,7 @@ import android.database.DatabaseErrorHandler
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
import dev.inmo.micro_utils.coroutines.safely
import dev.inmo.micro_utils.repos.versions.*
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
@@ -36,6 +37,9 @@ class StandardSQLHelper(
override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {}
}
val versionsRepo: VersionsRepo<SQLiteOpenHelper> by lazy {
StandardVersionsRepo(AndroidSQLStandardVersionsRepoProxy(sqlOpenHelper))
}
suspend fun <T> writableTransaction(block: suspend SQLiteDatabase.() -> T): T = sqlOpenHelper.writableTransaction(block)

View File

@@ -96,6 +96,20 @@ class KeyValueStore<T : Any> internal constructor (
}
}
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()

View File

@@ -87,6 +87,29 @@ class OneToManyAndroidRepo<Key, Value>(
}
}
override suspend fun set(toSet: Map<Key, List<Value>>) {
val (clearedKeys, inserted) = helper.writableTransaction {
toSet.mapNotNull { (k, _) ->
if (delete(tableName, "$idColumnName=?", arrayOf(k.asId())) > 0) {
k
} else {
null
}
} to toSet.flatMap { (k, values) ->
values.map { v ->
insert(
tableName,
null,
contentValuesOf(idColumnName to k.asId(), valueColumnName to v.asValue())
)
k to v
}
}
}
clearedKeys.forEach { _onDataCleared.emit(it) }
inserted.forEach { newPair -> _onNewValue.emit(newPair) }
}
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
@@ -169,6 +192,33 @@ class OneToManyAndroidRepo<Key, Value>(
)
}
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) ->

View File

@@ -0,0 +1,63 @@
package dev.inmo.micro_utils.repos.versions
import android.database.sqlite.SQLiteOpenHelper
import androidx.core.content.contentValuesOf
import dev.inmo.micro_utils.repos.*
import kotlinx.coroutines.runBlocking
/**
* Will create [VersionsRepo] based on [SQLiteOpenHelper] with table inside of [database]
*/
@Suppress("NOTHING_TO_INLINE")
inline fun versionsRepo(database: SQLiteOpenHelper): VersionsRepo<SQLiteOpenHelper> = StandardVersionsRepo(
AndroidSQLStandardVersionsRepoProxy(database)
)
class AndroidSQLStandardVersionsRepoProxy(
override val database: SQLiteOpenHelper
) : StandardVersionsRepoProxy<SQLiteOpenHelper> {
private val tableName: String = "AndroidSQLStandardVersionsRepo"
private val tableNameColumnName = "tableName"
private val tableVersionColumnName = "version"
init {
runBlocking(DatabaseCoroutineContext) {
database.writableTransaction {
createTable(
tableName,
tableNameColumnName to ColumnType.Text.NOT_NULLABLE,
tableVersionColumnName to ColumnType.Numeric.INTEGER()
)
}
}
}
override suspend fun getTableVersion(table: String): Int? = database.writableTransaction {
select(
tableName,
selection = "$tableNameColumnName=?",
selectionArgs = arrayOf(table),
limit = limitClause(1)
).use {
if (it.moveToFirst()) {
it.getInt(tableVersionColumnName)
} else {
null
}
}
}
override suspend fun updateTableVersion(table: String, version: Int) {
database.writableTransaction {
val updated = update(
tableName,
contentValuesOf(tableVersionColumnName to version),
"$tableNameColumnName=?",
arrayOf(table)
) > 0
if (!updated) {
insert(tableName, null, contentValuesOf(tableNameColumnName to table, tableVersionColumnName to version))
}
}
}
}

View File

@@ -0,0 +1,49 @@
@file:Suppress("NOTHING_TO_INLINE")
package dev.inmo.micro_utils.repos.versions
import android.content.Context
import android.database.sqlite.SQLiteOpenHelper
import androidx.core.content.contentValuesOf
import dev.inmo.micro_utils.repos.*
import dev.inmo.micro_utils.repos.keyvalue.keyValueStore
import kotlinx.coroutines.runBlocking
/**
* Will create [VersionsRepo] based on [T], but versions will be stored in [StandardKeyValueRepo]
*
* @receiver Will be used to create [KeyValueBasedVersionsRepoProxy] via [keyValueStore] and pass it to [StandardVersionsRepo]
*
* @see [KeyValueBasedVersionsRepoProxy]
* @see [keyValueStore]
*/
inline fun <T> Context.versionsKeyValueRepo(
database: T
): VersionsRepo<T> = StandardVersionsRepo(
KeyValueBasedVersionsRepoProxy(
keyValueStore("SPVersionsRepo"),
database
)
)
/**
* Will create [VersionsRepo] based on [SQLiteOpenHelper], but versions will be stored in [StandardKeyValueRepo]
*
* @receiver Will be used to create [StandardKeyValueRepo] via [keyValueStore] and pass it to [StandardVersionsRepo]
*
* @see [keyValueStore]
*/
inline fun Context.versionsKeyValueRepoForSQL(
database: SQLiteOpenHelper
) = versionsKeyValueRepo(database)
/**
* Will create [VersionsRepo] based on [SQLiteOpenHelper], but versions will be stored in [StandardKeyValueRepo]
*
* @param context Will be used to create [StandardKeyValueRepo] via [keyValueStore] and pass it to [StandardVersionsRepo]
*
* @see [keyValueStore]
*/
inline fun versionsRepo(
context: Context,
database: SQLiteOpenHelper
) = context.versionsKeyValueRepoForSQL(database)

View File

@@ -2,6 +2,7 @@ package dev.inmo.micro_utils.repos.exposed.keyvalue
import dev.inmo.micro_utils.repos.StandardKeyValueRepo
import dev.inmo.micro_utils.repos.exposed.ColumnAllocator
import dev.inmo.micro_utils.repos.exposed.initTable
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.*
import org.jetbrains.exposed.sql.*
@@ -18,8 +19,8 @@ open class ExposedKeyValueRepo<Key, Value>(
valueColumnAllocator,
tableName
) {
private val _onNewValue = MutableSharedFlow<Pair<Key, Value>>(Channel.BUFFERED)
private val _onValueRemoved = MutableSharedFlow<Key>(Channel.BUFFERED)
private val _onNewValue = MutableSharedFlow<Pair<Key, Value>>()
private val _onValueRemoved = MutableSharedFlow<Key>()
override val onNewValue: Flow<Pair<Key, Value>> = _onNewValue.asSharedFlow()
override val onValueRemoved: Flow<Key> = _onValueRemoved.asSharedFlow()

View File

@@ -2,8 +2,7 @@ package dev.inmo.micro_utils.repos.exposed.keyvalue
import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.repos.ReadStandardKeyValueRepo
import dev.inmo.micro_utils.repos.exposed.ColumnAllocator
import dev.inmo.micro_utils.repos.exposed.ExposedRepo
import dev.inmo.micro_utils.repos.exposed.*
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.transactions.transaction
@@ -17,6 +16,8 @@ open class ExposedReadKeyValueRepo<Key, Value>(
protected val valueColumn: Column<Value> = valueColumnAllocator()
override val primaryKey: PrimaryKey = PrimaryKey(keyColumn, valueColumn)
init { initTable() }
override suspend fun get(k: Key): Value? = transaction(database) {
select { keyColumn.eq(k) }.limit(1).firstOrNull() ?.getOrNull(valueColumn)
}
@@ -33,6 +34,16 @@ open class ExposedReadKeyValueRepo<Key, Value>(
}
}.createPaginationResult(pagination, 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 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]

View File

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

View File

@@ -0,0 +1,48 @@
package dev.inmo.micro_utils.repos.exposed.versions
import dev.inmo.micro_utils.repos.exposed.ExposedRepo
import dev.inmo.micro_utils.repos.exposed.initTable
import dev.inmo.micro_utils.repos.versions.*
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.transactions.transaction
/**
* Use this method to create [StandardVersionsRepo] based on [Database] with [ExposedStandardVersionsRepoProxy] as
* [StandardVersionsRepoProxy]
*/
@Suppress("NOTHING_TO_INLINE")
inline fun versionsRepo(database: Database): VersionsRepo<Database> = StandardVersionsRepo(
ExposedStandardVersionsRepoProxy(database)
)
class ExposedStandardVersionsRepoProxy(
override val database: Database
) : StandardVersionsRepoProxy<Database>, Table("ExposedVersionsProxy"), ExposedRepo {
private val tableNameColumn = text("tableName")
private val tableVersionColumn = integer("tableName")
init {
initTable()
}
override suspend fun getTableVersion(tableName: String): Int? = transaction(database) {
select { tableNameColumn.eq(tableName) }.limit(1).firstOrNull() ?.getOrNull(tableVersionColumn)
}
override suspend fun updateTableVersion(tableName: String, version: Int) {
transaction(database) {
val updated = update(
{ tableNameColumn.eq(tableName) }
) {
it[tableVersionColumn] = version
} > 0
if (!updated) {
insert {
it[tableNameColumn] = tableName
it[tableVersionColumn] = version
}
}
}
}
}

View File

@@ -2,7 +2,6 @@ plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
id "kotlin-android-extensions"
}
apply from: "$mppProjectWithSerializationPresetPath"

View File

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

View File

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

View File

@@ -2,7 +2,6 @@ plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
id "kotlin-android-extensions"
}
apply from: "$mppProjectWithSerializationPresetPath"

View File

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

View File

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

View File

@@ -42,12 +42,12 @@ class KtorWriteOneToManyKeyValueRepo<Key, Value> (
override suspend fun add(toAdd: Map<Key, List<Value>>) = client.unipost(
buildStandardUrl(
baseUrl,
clearRoute,
addRoute,
),
BodyPair(keyValueMapSerializer, toAdd),
Unit.serializer(),
)
override suspend fun clear(k: Key) = client.unipost(
override suspend fun clear(k: Key) = client.unipost(
buildStandardUrl(
baseUrl,
clearRoute,
@@ -55,4 +55,13 @@ class KtorWriteOneToManyKeyValueRepo<Key, Value> (
BodyPair(keySerializer, k),
Unit.serializer(),
)
override suspend fun set(toSet: Map<Key, List<Value>>) = client.unipost(
buildStandardUrl(
baseUrl,
setRoute,
),
BodyPair(keyValueMapSerializer, toSet),
Unit.serializer(),
)
}

View File

@@ -2,7 +2,6 @@ plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
id "kotlin-android-extensions"
}
apply from: "$mppProjectWithSerializationPresetPath"

View File

@@ -0,0 +1,5 @@
package dev.inmo.micro_utils.repos.ktor.common
const val keyParameterName = "key"
const val valueParameterName = "value"
const val reversedParameterName = "reversed"

View File

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

View File

@@ -13,4 +13,5 @@ const val onDataClearedRoute = "onDataCleared"
const val addRoute = "add"
const val removeRoute = "remove"
const val clearRoute = "clear"
const val clearRoute = "clear"
const val setRoute = "set"

View File

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

View File

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

View File

@@ -61,4 +61,13 @@ fun <Key, Value> Route.configureOneToManyWriteKeyValueRepoRoutes(
originalRepo.clear(key),
)
}
post(setRoute) {
val obj = call.uniload(keyValueMapSerializer)
call.unianswer(
Unit.serializer(),
originalRepo.set(obj)
)
}
}

View File

@@ -18,6 +18,8 @@ String[] includes = [
":ktor:client",
":coroutines",
":android:recyclerview",
":android:alerts:common",
":android:alerts:recyclerview",
":dokka"
]