A collection of utils to facilitate development with Jetpack Compose.
Make sure you have jitpack added to your dependency resolution repositories by adding the following to your settings.gradle.kts
:
dependencyResolutionManagement {
repositories {
maven("https://jitpack.io")
}
}
Then add the dependencies you lust for to your build.gradle.kts
files:
dependencies {
// Core utils
implementation "com.github.w2sv.Composed:composed:<version>"
// Permission utils
implementation "com.github.w2sv.Composed:permissions:<version>"
}
- State Savers
- Styled Text
- Modifiers
- Layout
- Flow Collectors
- Lifecycle Observers
- Orientation
- Dimension Conversion
- Color Conversion
- Map Conversion
- Drawer State
- SnackbarHostState
- Easing
- Permission States
/**
* Returns a rememberSavable state saver for Color.
*/
fun colorSaver(): Saver<Color, Int>
/**
* Returns a rememberSavable state saver for an optional Color.
*/
fun nullableColorSaver(): Saver<Color?, Float>
/**
* listSaver for an optional object, enabling handling of non-null instances only.
*/
fun <Original, Saveable> nullableListSaver(
saveNonNull: SaverScope.(value: Original) -> List<Saveable>,
restoreNonNull: (list: List<Saveable>) -> Original?
): Saver<Original?, Any>
/**
* mapSaver for an optional object, enabling handling of non-null instances only.
*/
fun <T> nullableMapSaver(
saveNonNull: SaverScope.(value: T) -> Map<String, Any?>,
restoreNonNull: (Map<String, Any?>) -> T
): Saver<T, Any>
/**
* Converts a HTML-styled string resource text to a remembered AnnotatedString.
*/
@Composable
fun rememberStyledTextResource(@StringRes id: Int, vararg formatArgs: Any): AnnotatedString
/**
* Applies modifiers depending on a condition.
*/
inline fun Modifier.thenIf(
condition: Boolean,
onFalse: Modifier.() -> Modifier = { this },
onTrue: Modifier.() -> Modifier = { this },
): Modifier
/**
* [Column] whose [elements], rendered through [makeElement], will be divided by [makeDivider]. [makeDivider] will be invoked only in between elements, that is, neither before the first, nor after the last element.
*/
@Composable
fun <T> InterElementDividedColumn(
elements: List<T>,
makeElement: @Composable ColumnScope.(T) -> Unit,
modifier: Modifier = Modifier,
makeDivider: @Composable ColumnScope.() -> Unit = { HorizontalDivider() },
verticalArrangement: Arrangement.Vertical = Arrangement.Top,
horizontalAlignment: Alignment.Horizontal = Alignment.Start,
)
/**
* [Row] whose [elements], rendered through [makeElement], will be divided by [makeDivider]. [makeDivider] will be invoked only in between elements, that is, neither before the first, nor after the last element.
*/
@Composable
fun <T> InterElementDividedRow(
elements: List<T>,
makeElement: @Composable RowScope.(T) -> Unit,
modifier: Modifier = Modifier,
makeDivider: @Composable RowScope.() -> Unit = { VerticalDivider() },
horizontalArrangement: Arrangement.Horizontal = Arrangement.Start,
verticalAlignment: Alignment.Vertical = Alignment.Top,
)
/**
* Collects from a flow and emits values into a collector.
*/
@Composable
fun <T> CollectFromFlow(
flow: Flow<T>,
key1: Any? = null,
key2: Any? = null,
collector: FlowCollector<T>
)
/**
* Collects latest from a flow with given action.
*/
@Composable
fun <T> CollectLatestFromFlow(
flow: Flow<T>,
key1: Any? = null,
key2: Any? = null,
action: suspend (value: T) -> Unit
)
/**
* Runs a callback whenever the lifecycleOwner reaches the given lifecycleEvent.
*/
@Composable
fun OnLifecycleEvent(
lifecycleEvent: Lifecycle.Event,
lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
key1: Any? = null,
key2: Any? = null,
callback: () -> Unit
)
/**
* Runs a callback when removed from composition.
*/
@Composable
fun OnDispose(callback: () -> Unit)
/**
* Returns true if the landscape mode is active, false otherwise.
*/
val isLandscapeModeActive: Boolean
/**
* Returns true if the portrait mode is active, false otherwise.
*/
val isPortraitModeActive: Boolean
/**
* Converts Dp to pixels.
*/
@Composable
@ReadOnlyComposable
fun Dp.toPx(): Float
/**
* Converts pixels to Dp.
*/
@Composable
@ReadOnlyComposable
fun Int.toDp(): Dp
/**
* Converts pixels to Dp.
*/
@Composable
@ReadOnlyComposable
fun Float.toDp(): Dp
/**
* Converts a hex color string to Color.
*/
fun String.toComposeColor(): Color
/**
* Converts a regular Map to a SnapshotStateMap.
*/
fun <K, V> Map<K, V>.toMutableStateMap(): SnapshotStateMap<K, V>
/**
* Returns a State<Float> whose value ranges from 0.0 (drawer closed) to 1.0 (drawer fully open).
*/
fun DrawerState.visibilityPercentage(@FloatRange(from = 0.0) maxWidthPx: Float): State<Float>
/**
* Remembers a visibility percentage for the drawer.
*/
@Composable
fun DrawerState.rememberVisibilityPercentage(@FloatRange(from = 0.0) maxWidthPx: Float = DrawerDefaults.MaximumDrawerWidth.toPx()): State<Float>
/**
* Dismisses the currently showing snackbar if there is one and shows a new one with the given [snackbarVisuals].
*/
suspend fun SnackbarHostState.dismissCurrentSnackbarAndShow(snackbarVisuals: SnackbarVisuals)
/**
* Dismisses the currently showing snackbar if there is one and shows a new one with the given parameters.
*/
suspend fun SnackbarHostState.dismissCurrentSnackbarAndShow(
message: String,
actionLabel: String? = null,
withDismissAction: Boolean = false,
duration: SnackbarDuration = if (actionLabel == null) SnackbarDuration.Short else SnackbarDuration.Indefinite
)
fun TimeInterpolator.toEasing() = Easing
/**
* Permission state which, as opposed to the accompanist ones,
* - exposes a [grantedFromRequest] shared flow to allow for distributed subscription and callback invocation, instead of only being able to pass a onPermissionResult callback upon instantiation, which needs to cover all granting reactions, possibly impacting various components
* - allows for callbacks upon permission requesting being suppressed
*/
@Stable
interface ExtendedPermissionState {
val granted: Boolean
val grantedFromRequest: SharedFlow<Boolean>
fun launchRequest(onSuppressed: (() -> Unit)? = null)
}
// With the implementations:
@Stable
open class ExtendedSinglePermissionState(
private val requestLaunchedBefore: StateFlow<Boolean>,
permissionState: PermissionState,
override val grantedFromRequest: SharedFlow<Boolean>,
private val defaultOnLaunchingSuppressed: () -> Unit = {}
) : PermissionState by permissionState, ExtendedPermissionState
@Composable
fun rememberExtendedSinglePermissionState(
permission: String,
requestLaunchedBefore: StateFlow<Boolean>,
saveRequestLaunched: () -> Unit,
defaultOnPermissionResult: (Boolean) -> Unit = {},
defaultOnLaunchingSuppressed: () -> Unit = {},
scope: CoroutineScope = rememberCoroutineScope()
): ExtendedSinglePermissionState
// And
@Stable
open class ExtendedMultiplePermissionsState(
private val requestLaunchedBefore: StateFlow<Boolean>,
multiplePermissionsState: MultiplePermissionsState,
override val grantedFromRequest: SharedFlow<Boolean>,
private val defaultOnLaunchingSuppressed: () -> Unit = {}
) : MultiplePermissionsState by multiplePermissionsState, ExtendedPermissionState
@Composable
fun rememberExtendedMultiplePermissionsState(
permissions: List<String>,
requestLaunchedBefore: StateFlow<Boolean>,
saveRequestLaunched: () -> Unit,
defaultOnPermissionResult: (Map<String, Boolean>) -> Unit = {},
defaultOnLaunchingSuppressed: () -> Unit = {},
scope: CoroutineScope = rememberCoroutineScope()
): ExtendedMultiplePermissionsState
Designed and developed by 2024 w2sv (Janek Zangenberg)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.