From 2af7e2f6815313ce749cfdeb8fd1b065dc7f88c0 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Wed, 17 Nov 2021 14:01:44 +0600 Subject: [PATCH 1/6] start 0.8.3 --- CHANGELOG.md | 2 ++ gradle.properties | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 92b7a675688..95b5f8b908c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Changelog +## 0.8.3 + ## 0.8.2 * `Versions`: diff --git a/gradle.properties b/gradle.properties index b4e09144229..da742209f01 100644 --- a/gradle.properties +++ b/gradle.properties @@ -45,5 +45,5 @@ dokka_version=1.5.31 # Project data group=dev.inmo -version=0.8.2 -android_code_version=82 +version=0.8.3 +android_code_version=83 From aa45a4ab13ccc2ed3048cfb9cc46c33e37d96da2 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Wed, 17 Nov 2021 14:07:11 +0600 Subject: [PATCH 2/6] now Pagination is an ClosedRange --- CHANGELOG.md | 3 +++ .../dev/inmo/micro_utils/pagination/Pagination.kt | 13 +++++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95b5f8b908c..5b016dd9dab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## 0.8.3 +* `Pagination`: + * `Pagination` now extends `ClosedRange` + ## 0.8.2 * `Versions`: diff --git a/pagination/common/src/commonMain/kotlin/dev/inmo/micro_utils/pagination/Pagination.kt b/pagination/common/src/commonMain/kotlin/dev/inmo/micro_utils/pagination/Pagination.kt index 69bb2138c64..5afd2b1461e 100644 --- a/pagination/common/src/commonMain/kotlin/dev/inmo/micro_utils/pagination/Pagination.kt +++ b/pagination/common/src/commonMain/kotlin/dev/inmo/micro_utils/pagination/Pagination.kt @@ -9,7 +9,7 @@ import kotlin.math.floor * If you want to request something, you should use [SimplePagination]. If you need to return some result including * pagination - [PaginationResult] */ -interface Pagination { +interface Pagination : ClosedRange { /** * Started with 0. * Number of page inside of pagination. Offset can be calculated as [page] * [size] @@ -20,6 +20,11 @@ interface Pagination { * Size of current page. Offset can be calculated as [page] * [size] */ val size: Int + + override val start: Int + get() = page * size + override val endInclusive: Int + get() = lastIndex } /** @@ -32,7 +37,7 @@ inline val Pagination.isFirstPage * First number in index of objects. It can be used as offset for databases or other data sources */ val Pagination.firstIndex: Int - get() = page * size + get() = start /** * Last number in index of objects. In fact, one [Pagination] object represent data in next range: @@ -41,7 +46,7 @@ val Pagination.firstIndex: Int * you will retrieve [Pagination.firstIndex] == 10 and [Pagination.lastIndex] == 19. Here [Pagination.lastIndexExclusive] == 20 */ val Pagination.lastIndexExclusive: Int - get() = firstIndex + size + get() = endInclusive + 1 /** * Last number in index of objects. In fact, one [Pagination] object represent data in next range: @@ -50,7 +55,7 @@ val Pagination.lastIndexExclusive: Int * you will retrieve [Pagination.firstIndex] == 10 and [Pagination.lastIndex] == 19. */ val Pagination.lastIndex: Int - get() = lastIndexExclusive - 1 + get() = endInclusive /** * Calculates pages count for given [datasetSize] From fb63de756870a3dd875a6ae57ab8c018b4500c23 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Wed, 17 Nov 2021 14:22:45 +0600 Subject: [PATCH 3/6] intersect --- CHANGELOG.md | 3 +++ .../micro_utils/common/RangeIntersection.kt | 19 +++++++++++++++++++ pagination/common/build.gradle | 10 ++++++++++ .../inmo/micro_utils/pagination/Pagination.kt | 7 +++++++ 4 files changed, 39 insertions(+) create mode 100644 common/src/commonMain/kotlin/dev/inmo/micro_utils/common/RangeIntersection.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b016dd9dab..410ddfff15d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,11 @@ ## 0.8.3 +* `Common`: + * Ranges intersection functionality * `Pagination`: * `Pagination` now extends `ClosedRange` + * `Pagination` intersection functionality ## 0.8.2 diff --git a/common/src/commonMain/kotlin/dev/inmo/micro_utils/common/RangeIntersection.kt b/common/src/commonMain/kotlin/dev/inmo/micro_utils/common/RangeIntersection.kt new file mode 100644 index 00000000000..e8bcf5a5750 --- /dev/null +++ b/common/src/commonMain/kotlin/dev/inmo/micro_utils/common/RangeIntersection.kt @@ -0,0 +1,19 @@ +package dev.inmo.micro_utils.common + +fun > ClosedRange.intersect(other: ClosedRange): Pair? = when { + start == other.start && endInclusive == other.endInclusive -> start to endInclusive + start > other.endInclusive || other.start > endInclusive -> null + else -> maxOf(start, other.start) to minOf(endInclusive, other.endInclusive) +} + +fun IntRange.intersect( + other: IntRange +): IntRange? = (this as ClosedRange).intersect(other as ClosedRange) ?.let { + it.first .. it.second +} + +fun LongRange.intersect( + other: LongRange +): LongRange? = (this as ClosedRange).intersect(other as ClosedRange) ?.let { + it.first .. it.second +} diff --git a/pagination/common/build.gradle b/pagination/common/build.gradle index 7c54502f100..082ce99346a 100644 --- a/pagination/common/build.gradle +++ b/pagination/common/build.gradle @@ -5,3 +5,13 @@ plugins { } apply from: "$mppProjectWithSerializationPresetPath" + +kotlin { + sourceSets { + commonMain { + dependencies { + api project(":micro_utils.common") + } + } + } +} diff --git a/pagination/common/src/commonMain/kotlin/dev/inmo/micro_utils/pagination/Pagination.kt b/pagination/common/src/commonMain/kotlin/dev/inmo/micro_utils/pagination/Pagination.kt index 5afd2b1461e..b20a778af2f 100644 --- a/pagination/common/src/commonMain/kotlin/dev/inmo/micro_utils/pagination/Pagination.kt +++ b/pagination/common/src/commonMain/kotlin/dev/inmo/micro_utils/pagination/Pagination.kt @@ -1,5 +1,6 @@ package dev.inmo.micro_utils.pagination +import dev.inmo.micro_utils.common.intersect import kotlin.math.ceil import kotlin.math.floor @@ -27,6 +28,12 @@ interface Pagination : ClosedRange { get() = lastIndex } +fun Pagination.intersect( + other: Pagination +): Pagination? = (this as ClosedRange).intersect(other as ClosedRange) ?.let { + PaginationByIndexes(it.first, it.second) +} + /** * Logical shortcut for comparison that page is 0 */ From bcbab3b38059c567551f245ce1baa4527865bc12 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Wed, 17 Nov 2021 17:38:41 +0600 Subject: [PATCH 4/6] add Optional type --- .../dev/inmo/micro_utils/common/Optional.kt | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 common/src/commonMain/kotlin/dev/inmo/micro_utils/common/Optional.kt diff --git a/common/src/commonMain/kotlin/dev/inmo/micro_utils/common/Optional.kt b/common/src/commonMain/kotlin/dev/inmo/micro_utils/common/Optional.kt new file mode 100644 index 00000000000..82919cfc60c --- /dev/null +++ b/common/src/commonMain/kotlin/dev/inmo/micro_utils/common/Optional.kt @@ -0,0 +1,52 @@ +package dev.inmo.micro_utils.common + +import kotlinx.serialization.Serializable + +/** + * This type represents [T] as not only potentially nullable data, but also as a data which can not be presented. This + * type will be useful in cases when [T] is nullable and null as valuable data too in time of data absence should be + * presented by some third type. + * + * Let's imagine, you have nullable name in some database. In case when name is not nullable everything is clear - null + * will represent absence of row in the database. In case when name is nullable null will be a little bit dual-meaning, + * cause this null will say nothing about availability of the row (of course, it is exaggerated example) + * + * @see Optional.presented + * @see Optional.absent + * @see Optional.optional + * @see Optional.onPresented + * @see Optional.onAbsent + */ +@Serializable +data class Optional internal constructor( + internal val data: T?, + internal val dataPresented: Boolean +) { + companion object { + /** + * Will create [Optional] with presented data + */ + fun presented(data: T) = Optional(data, true) + /** + * Will create [Optional] without data + */ + fun absent() = Optional(null, false) + } +} + +inline val T.optional + get() = Optional.presented(this) + +/** + * Will call [block] when data presented ([Optional.dataPresented] == true) + */ +fun Optional.onPresented(block: (T) -> Unit): Optional = apply { + if (dataPresented) { block(data as T) } +} + +/** + * Will call [block] when data absent ([Optional.dataPresented] == false) + */ +fun Optional.onAbsent(block: () -> Unit): Optional = apply { + if (!dataPresented) { block() } +} From e337cd98c8594eb92e3a5c434743b21fa3097ecc Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Wed, 17 Nov 2021 21:31:35 +0600 Subject: [PATCH 5/6] optional workaround --- CHANGELOG.md | 1 + .../dev/inmo/micro_utils/common/Optional.kt | 21 +++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 410ddfff15d..19db51c1b84 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * `Common`: * Ranges intersection functionality + * New type `Optional` * `Pagination`: * `Pagination` now extends `ClosedRange` * `Pagination` intersection functionality diff --git a/common/src/commonMain/kotlin/dev/inmo/micro_utils/common/Optional.kt b/common/src/commonMain/kotlin/dev/inmo/micro_utils/common/Optional.kt index 82919cfc60c..a166434beb7 100644 --- a/common/src/commonMain/kotlin/dev/inmo/micro_utils/common/Optional.kt +++ b/common/src/commonMain/kotlin/dev/inmo/micro_utils/common/Optional.kt @@ -50,3 +50,24 @@ fun Optional.onPresented(block: (T) -> Unit): Optional = apply { fun Optional.onAbsent(block: () -> Unit): Optional = apply { if (!dataPresented) { block() } } + +/** + * Returns [Optional.data] if [Optional.dataPresented] of [this] is true, or null otherwise + */ +fun Optional.dataOrNull() = if (dataPresented) data as T else null + +/** + * Returns [Optional.data] if [Optional.dataPresented] of [this] is true, or throw [throwable] otherwise + */ +fun Optional.dataOrThrow(throwable: Throwable) = if (dataPresented) data as T else throw throwable + + +/** + * Returns [Optional.data] if [Optional.dataPresented] of [this] is true, or call [block] and returns the result of it + */ +fun Optional.dataOrElse(block: () -> T) = if (dataPresented) data as T else block() + +/** + * Returns [Optional.data] if [Optional.dataPresented] of [this] is true, or call [block] and returns the result of it + */ +suspend fun Optional.dataOrElseSuspendable(block: suspend () -> T) = if (dataPresented) data as T else block() From 6bbbea0bc322f76a6f52d39352ff35bcc6106d0c Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Thu, 18 Nov 2021 23:03:24 +0600 Subject: [PATCH 6/6] suppressions in Optional.kt --- .../kotlin/dev/inmo/micro_utils/common/Optional.kt | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/common/src/commonMain/kotlin/dev/inmo/micro_utils/common/Optional.kt b/common/src/commonMain/kotlin/dev/inmo/micro_utils/common/Optional.kt index a166434beb7..3c48c1b4da9 100644 --- a/common/src/commonMain/kotlin/dev/inmo/micro_utils/common/Optional.kt +++ b/common/src/commonMain/kotlin/dev/inmo/micro_utils/common/Optional.kt @@ -1,3 +1,5 @@ +@file:Suppress("unused") + package dev.inmo.micro_utils.common import kotlinx.serialization.Serializable @@ -41,7 +43,7 @@ inline val T.optional * Will call [block] when data presented ([Optional.dataPresented] == true) */ fun Optional.onPresented(block: (T) -> Unit): Optional = apply { - if (dataPresented) { block(data as T) } + if (dataPresented) { @Suppress("UNCHECKED_CAST") block(data as T) } } /** @@ -54,20 +56,20 @@ fun Optional.onAbsent(block: () -> Unit): Optional = apply { /** * Returns [Optional.data] if [Optional.dataPresented] of [this] is true, or null otherwise */ -fun Optional.dataOrNull() = if (dataPresented) data as T else null +fun Optional.dataOrNull() = if (dataPresented) @Suppress("UNCHECKED_CAST") (data as T) else null /** * Returns [Optional.data] if [Optional.dataPresented] of [this] is true, or throw [throwable] otherwise */ -fun Optional.dataOrThrow(throwable: Throwable) = if (dataPresented) data as T else throw throwable +fun Optional.dataOrThrow(throwable: Throwable) = if (dataPresented) @Suppress("UNCHECKED_CAST") (data as T) else throw throwable /** * Returns [Optional.data] if [Optional.dataPresented] of [this] is true, or call [block] and returns the result of it */ -fun Optional.dataOrElse(block: () -> T) = if (dataPresented) data as T else block() +fun Optional.dataOrElse(block: () -> T) = if (dataPresented) @Suppress("UNCHECKED_CAST") (data as T) else block() /** * Returns [Optional.data] if [Optional.dataPresented] of [this] is true, or call [block] and returns the result of it */ -suspend fun Optional.dataOrElseSuspendable(block: suspend () -> T) = if (dataPresented) data as T else block() +suspend fun Optional.dataOrElseSuspendable(block: suspend () -> T) = if (dataPresented) @Suppress("UNCHECKED_CAST") (data as T) else block()