From 25e9345d02fb34a40c253de609b210a9172f13dc Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Mon, 14 Dec 2020 18:44:24 +0600 Subject: [PATCH 1/7] start 0.4.12 --- CHANGELOG.md | 2 ++ gradle.properties | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d00f69cbe2d..8840ee571d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Changelog +## 0.4.12 + ## 0.4.11 * `Common` diff --git a/gradle.properties b/gradle.properties index f4fb5d1fe8b..78fccfdb2b9 100644 --- a/gradle.properties +++ b/gradle.properties @@ -35,10 +35,10 @@ espresso_core=3.3.0 # Dokka -dokka_version=1.4.20 +dokka_version=1.4.21 # Project data group=dev.inmo -version=0.4.11 -android_code_version=15 +version=0.4.12 +android_code_version=16 From 12e37184e1d24769bf807ff60f9a71151664dbdb Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Mon, 14 Dec 2020 19:17:16 +0600 Subject: [PATCH 2/7] add selector --- CHANGELOG.md | 3 + README.md | 1 + gradle.properties | 2 +- selector/common/build.gradle | 17 ++++ .../dev/inmo/micro_utils/selector/Selector.kt | 85 +++++++++++++++++++ selector/common/src/main/AndroidManifest.xml | 1 + settings.gradle | 1 + 7 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 selector/common/build.gradle create mode 100644 selector/common/src/commonMain/kotlin/dev/inmo/micro_utils/selector/Selector.kt create mode 100644 selector/common/src/main/AndroidManifest.xml diff --git a/CHANGELOG.md b/CHANGELOG.md index 8840ee571d7..f356ff1c470 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## 0.4.12 +* `Selector` + * Project created + ## 0.4.11 * `Common` diff --git a/README.md b/README.md index 1c11fb08176..0686112a8cf 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ You always can look at the { + val selectedItems: List + val itemSelected: SharedFlow + val itemUnselected: SharedFlow + + suspend fun toggleSelection(element: T) +} + +@Suppress("NOTHING_TO_INLINE") +inline operator fun Selector.contains(element: T) = selectedItems.contains(element) +@Suppress("NOTHING_TO_INLINE") +inline fun Selector.nothingSelected(): Boolean = selectedItems.isEmpty() + +class SingleSelector( + selectedItem: T? = null, + useMutex: Boolean = true +) : Selector { + var selectedItem: T? = selectedItem + private set + override val selectedItems: List + get() = selectedItem ?.let { listOf(it) } ?: emptyList() + + private val _itemSelected = MutableSharedFlow() + override val itemSelected: SharedFlow = _itemSelected.asSharedFlow() + private val _itemUnselected = MutableSharedFlow() + override val itemUnselected: SharedFlow = _itemUnselected.asSharedFlow() + + private val mutex = if (useMutex) { + Mutex() + } else { + null + } + + override suspend fun toggleSelection(element: T) { + mutex ?.lock() + if (selectedItem == element) { + selectedItem = null + _itemUnselected.emit(element) + } else { + val previouslySelected = selectedItem + selectedItem = null + if (previouslySelected != null) { + _itemUnselected.emit(previouslySelected) + } + selectedItem = element + _itemSelected.emit(element) + } + mutex ?.unlock() + } +} + +class MultipleSelector( + selectedItems: List, + useMutex: Boolean = true +) : Selector { + private val _selectedItems: MutableList = selectedItems.toMutableList() + override val selectedItems: List = _selectedItems + + private val _itemSelected = MutableSharedFlow() + override val itemSelected: SharedFlow = _itemSelected.asSharedFlow() + private val _itemUnselected = MutableSharedFlow() + override val itemUnselected: SharedFlow = _itemUnselected.asSharedFlow() + + private val mutex = if (useMutex) { + Mutex() + } else { + null + } + + override suspend fun toggleSelection(element: T) { + mutex ?.lock() + if (_selectedItems.remove(element)) { + _itemUnselected.emit(element) + } else { + _selectedItems.add(element) + _itemSelected.emit(element) + } + mutex ?.unlock() + } +} diff --git a/selector/common/src/main/AndroidManifest.xml b/selector/common/src/main/AndroidManifest.xml new file mode 100644 index 00000000000..af2679c3813 --- /dev/null +++ b/selector/common/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 320b82da2da..c438c68de35 100644 --- a/settings.gradle +++ b/settings.gradle @@ -2,6 +2,7 @@ rootProject.name='micro_utils' String[] includes = [ ":common", + ":selector:common", ":pagination:common", ":pagination:exposed", ":pagination:ktor:common", From 80bc226ee1f12fe28489039be94dc2e3ac5c3272 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Mon, 14 Dec 2020 19:36:07 +0600 Subject: [PATCH 3/7] selector docs and several refactorings --- .../dev/inmo/micro_utils/selector/Selector.kt | 38 ++++++++++++++++--- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/selector/common/src/commonMain/kotlin/dev/inmo/micro_utils/selector/Selector.kt b/selector/common/src/commonMain/kotlin/dev/inmo/micro_utils/selector/Selector.kt index 465b11d3167..ed0366096e5 100644 --- a/selector/common/src/commonMain/kotlin/dev/inmo/micro_utils/selector/Selector.kt +++ b/selector/common/src/commonMain/kotlin/dev/inmo/micro_utils/selector/Selector.kt @@ -3,6 +3,9 @@ package dev.inmo.micro_utils.selector import kotlinx.coroutines.flow.* import kotlinx.coroutines.sync.Mutex +/** + * Unified interface which can be used in any system which require some selection functionality + */ interface Selector { val selectedItems: List val itemSelected: SharedFlow @@ -16,9 +19,18 @@ inline operator fun Selector.contains(element: T) = selectedItems.contain @Suppress("NOTHING_TO_INLINE") inline fun Selector.nothingSelected(): Boolean = selectedItems.isEmpty() +/** + * Realization of [Selector] with one or without selected element. This realization will always have empty + * [selectedItems] when nothing selected and one element in [selectedItems] when something selected. Contains + * [selectedItem] value for simple access to currently selected item. + * + * On calling of [toggleSelection] previous selection will be erased and [itemUnselected] will emit this element. + * + * @param safeChanges Set to false to disable using of [mutex] for synchronizing changes on [toggleSelection] + */ class SingleSelector( selectedItem: T? = null, - useMutex: Boolean = true + safeChanges: Boolean = true ) : Selector { var selectedItem: T? = selectedItem private set @@ -30,7 +42,7 @@ class SingleSelector( private val _itemUnselected = MutableSharedFlow() override val itemUnselected: SharedFlow = _itemUnselected.asSharedFlow() - private val mutex = if (useMutex) { + private val mutex = if (safeChanges) { Mutex() } else { null @@ -54,9 +66,15 @@ class SingleSelector( } } +/** + * Realization of [Selector] with multiple selected elements. On calling of [toggleSelection] this realization will select passed element OR deselect it if it is already in + * [selectedItems] + * + * @param safeChanges Set to false to disable using of [mutex] for synchronizing changes on [toggleSelection] + */ class MultipleSelector( - selectedItems: List, - useMutex: Boolean = true + selectedItems: List = emptyList(), + safeChanges: Boolean = true ) : Selector { private val _selectedItems: MutableList = selectedItems.toMutableList() override val selectedItems: List = _selectedItems @@ -66,7 +84,7 @@ class MultipleSelector( private val _itemUnselected = MutableSharedFlow() override val itemUnselected: SharedFlow = _itemUnselected.asSharedFlow() - private val mutex = if (useMutex) { + private val mutex = if (safeChanges) { Mutex() } else { null @@ -83,3 +101,13 @@ class MultipleSelector( mutex ?.unlock() } } + +@Suppress("FunctionName", "NOTHING_TO_INLINE") +inline fun Selector( + multiple: Boolean, + safeChanges: Boolean = true +): Selector = if (multiple) { + MultipleSelector(safeChanges = safeChanges) +} else { + SingleSelector(safeChanges = safeChanges) +} From 6f9d5e2d5f406a7c9541fbf59705227a364f347e Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Mon, 14 Dec 2020 19:45:14 +0600 Subject: [PATCH 4/7] add itemSelectionFlow --- .../micro_utils/selector/SelectorItemsFlow.kt | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 selector/common/src/commonMain/kotlin/dev/inmo/micro_utils/selector/SelectorItemsFlow.kt diff --git a/selector/common/src/commonMain/kotlin/dev/inmo/micro_utils/selector/SelectorItemsFlow.kt b/selector/common/src/commonMain/kotlin/dev/inmo/micro_utils/selector/SelectorItemsFlow.kt new file mode 100644 index 00000000000..29fd3917cb1 --- /dev/null +++ b/selector/common/src/commonMain/kotlin/dev/inmo/micro_utils/selector/SelectorItemsFlow.kt @@ -0,0 +1,17 @@ +package dev.inmo.micro_utils.selector + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.* + +/** + * @return Returned [SharedFlow] will emit true when [element] has been selected in [this] [Selector] and will emit + * false when this [element] was deselected + * + * @see [Selector] + * @see [Selector.itemSelected] + * @see [Selector.itemUnselected] + */ +fun Selector.itemSelectionFlow(element: T, scope: CoroutineScope): SharedFlow = MutableSharedFlow().apply { + itemSelected.onEach { if (it == element) emit(true) }.launchIn(scope) + itemUnselected.onEach { if (it == element) emit(false) }.launchIn(scope) +}.asSharedFlow() From a60cb596d1c89484b45834dd76273abfcd0d8a3e Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Mon, 14 Dec 2020 20:01:37 +0600 Subject: [PATCH 5/7] add functionality to selector --- .../dev/inmo/micro_utils/selector/Selector.kt | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/selector/common/src/commonMain/kotlin/dev/inmo/micro_utils/selector/Selector.kt b/selector/common/src/commonMain/kotlin/dev/inmo/micro_utils/selector/Selector.kt index ed0366096e5..ae55738447e 100644 --- a/selector/common/src/commonMain/kotlin/dev/inmo/micro_utils/selector/Selector.kt +++ b/selector/common/src/commonMain/kotlin/dev/inmo/micro_utils/selector/Selector.kt @@ -12,12 +12,21 @@ interface Selector { val itemUnselected: SharedFlow suspend fun toggleSelection(element: T) + suspend fun forceSelect(element: T) + suspend fun forceDeselect(element: T) + suspend fun clearSelection() } @Suppress("NOTHING_TO_INLINE") inline operator fun Selector.contains(element: T) = selectedItems.contains(element) @Suppress("NOTHING_TO_INLINE") inline fun Selector.nothingSelected(): Boolean = selectedItems.isEmpty() +suspend inline fun Selector.toggleSelection(elements: List) = elements.forEach { toggleSelection(it) } +suspend inline fun Selector.forceSelect(elements: List) = elements.forEach { forceSelect(it) } +suspend inline fun Selector.forceDeselect(elements: List) = elements.forEach { forceDeselect(it) } +suspend inline fun Selector.toggleSelection(firstElement: T, vararg elements: T) = toggleSelection(listOf(firstElement) + elements.toList()) +suspend inline fun Selector.forceSelect(firstElement: T, vararg elements: T) = forceSelect(listOf(firstElement) + elements.toList()) +suspend inline fun Selector.forceDeselect(firstElement: T, vararg elements: T) = forceDeselect(listOf(firstElement) + elements.toList()) /** * Realization of [Selector] with one or without selected element. This realization will always have empty @@ -48,6 +57,24 @@ class SingleSelector( null } + override suspend fun forceDeselect(element: T) { + mutex ?.lock() + if (selectedItem == element) { + selectedItem = null + _itemUnselected.emit(element) + } + mutex ?.unlock() + } + + override suspend fun forceSelect(element: T) { + mutex ?.lock() + if (selectedItem != element) { + selectedItem = element + _itemSelected.emit(element) + } + mutex ?.unlock() + } + override suspend fun toggleSelection(element: T) { mutex ?.lock() if (selectedItem == element) { @@ -64,6 +91,10 @@ class SingleSelector( } mutex ?.unlock() } + + override suspend fun clearSelection() { + selectedItem ?.let { forceDeselect(it) } + } } /** @@ -90,6 +121,22 @@ class MultipleSelector( null } + override suspend fun forceDeselect(element: T) { + mutex ?.lock() + if (_selectedItems.remove(element)) { + _itemUnselected.emit(element) + } + mutex ?.unlock() + } + + override suspend fun forceSelect(element: T) { + mutex ?.lock() + if (element !in _selectedItems && _selectedItems.add(element)) { + _itemSelected.emit(element) + } + mutex ?.unlock() + } + override suspend fun toggleSelection(element: T) { mutex ?.lock() if (_selectedItems.remove(element)) { @@ -100,6 +147,14 @@ class MultipleSelector( } mutex ?.unlock() } + + override suspend fun clearSelection() { + mutex ?.lock() + val preSelectedItems = _selectedItems.toList() + _selectedItems.clear() + preSelectedItems.forEach { _itemUnselected.emit(it) } + mutex ?.unlock() + } } @Suppress("FunctionName", "NOTHING_TO_INLINE") From 46bfb0941517546b28a24e41c29d7343126b02f1 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Mon, 14 Dec 2020 20:06:46 +0600 Subject: [PATCH 6/7] launchSynchronously signature update --- CHANGELOG.md | 2 ++ .../dev/inmo/micro_utils/coroutines/LaunchSynchronously.kt | 6 ++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f356ff1c470..a8be02b7448 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## 0.4.12 +* `Coroutines` + * Update `launchSynchronously` signature * `Selector` * Project created diff --git a/coroutines/src/jvmMain/kotlin/dev/inmo/micro_utils/coroutines/LaunchSynchronously.kt b/coroutines/src/jvmMain/kotlin/dev/inmo/micro_utils/coroutines/LaunchSynchronously.kt index c2a226e18be..bfd044534b5 100644 --- a/coroutines/src/jvmMain/kotlin/dev/inmo/micro_utils/coroutines/LaunchSynchronously.kt +++ b/coroutines/src/jvmMain/kotlin/dev/inmo/micro_utils/coroutines/LaunchSynchronously.kt @@ -2,12 +2,12 @@ package dev.inmo.micro_utils.coroutines import kotlinx.coroutines.* -fun launchSynchronously(scope: CoroutineScope = CoroutineScope(Dispatchers.Default), block: suspend CoroutineScope.() -> T): T { +fun CoroutineScope.launchSynchronously(block: suspend CoroutineScope.() -> T): T { var throwable: Throwable? = null var result: T? = null val objectToSynchronize = java.lang.Object() val launchCallback = { - scope.launch { + launch { safely( { throwable = it @@ -26,3 +26,5 @@ fun launchSynchronously(scope: CoroutineScope = CoroutineScope(Dispatchers.D } throw throwable ?: return result!! } + +fun launchSynchronously(block: suspend CoroutineScope.() -> T): T = CoroutineScope(Dispatchers.Default).launchSynchronously(block) From d5fe19f0a5c589a98593d4718cc03738952551fe Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Mon, 14 Dec 2020 20:20:30 +0600 Subject: [PATCH 7/7] update changelog --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a8be02b7448..93a27ec0c30 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,8 @@ ## 0.4.12 * `Coroutines` - * Update `launchSynchronously` signature + * `JVM` + * Update `launchSynchronously` signature * `Selector` * Project created