diff --git a/CHANGELOG.md b/CHANGELOG.md index 92b7a675688..19db51c1b84 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## 0.8.3 + +* `Common`: + * Ranges intersection functionality + * New type `Optional` +* `Pagination`: + * `Pagination` now extends `ClosedRange` + * `Pagination` intersection functionality + ## 0.8.2 * `Versions`: 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..3c48c1b4da9 --- /dev/null +++ b/common/src/commonMain/kotlin/dev/inmo/micro_utils/common/Optional.kt @@ -0,0 +1,75 @@ +@file:Suppress("unused") + +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) { @Suppress("UNCHECKED_CAST") block(data as T) } +} + +/** + * Will call [block] when data absent ([Optional.dataPresented] == false) + */ +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) @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) @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) @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) @Suppress("UNCHECKED_CAST") (data as T) else block() 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/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 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 69bb2138c64..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 @@ -9,7 +10,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 +21,17 @@ 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 +} + +fun Pagination.intersect( + other: Pagination +): Pagination? = (this as ClosedRange).intersect(other as ClosedRange) ?.let { + PaginationByIndexes(it.first, it.second) } /** @@ -32,7 +44,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 +53,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 +62,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]