Skip to content

Instantly share code, notes, and snippets.

@adamp
Created May 21, 2020 15:14
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save adamp/47f2762d70bc33a5e2c75a31413ec202 to your computer and use it in GitHub Desktop.
Save adamp/47f2762d70bc33a5e2c75a31413ec202 to your computer and use it in GitHub Desktop.
SnackBar prototype
import androidx.compose.*
import androidx.ui.core.Modifier
import androidx.ui.foundation.Text
import androidx.ui.layout.Row
import androidx.ui.layout.Spacer
import androidx.ui.material.Button
import androidx.ui.material.Surface
import kotlinx.coroutines.CancellableContinuation
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlin.coroutines.resume
/**
* A plain SnackBar layout with an optional button.
*/
@Composable
fun SnackBar(
message: String,
modifier: Modifier = Modifier,
actionLabel: String? = null,
onAction: (() -> Unit)? = null
) {
Surface(modifier) {
Row {
Text(message)
Spacer(Modifier.weight(1f))
if (actionLabel != null && onAction != null) {
Button(onClick = onAction) {
Text(actionLabel)
}
}
}
}
}
@Stable
class SnackMenu {
/**
* Only one snack can show at a time.
* Since a suspending Mutex is a fair queue, this manages our message queue
* and we don't have to maintain one.
*/
private val mutex = Mutex()
enum class Result {
Dismissed,
ActionPerformed
}
interface Snack {
val message: String
val actionLabel: String?
fun dismiss()
fun performAction()
}
private class SnackImpl(
override val message: String,
override val actionLabel: String?,
private val continuation: CancellableContinuation<Result>
) : Snack {
override fun dismiss() {
continuation.resume(Result.Dismissed)
}
override fun performAction() {
continuation.resume(Result.ActionPerformed)
}
}
/**
* The current [Snack] being shown by this menu.
*/
var currentSnack by mutableStateOf<Snack?>(null)
private set
/**
* Serves a snack showing [message] with an optional action if [actionLabel] is present.
* [serve] suspends until the snack is shown and subsequently addressed. If the caller
* is cancelled the snack will be removed from display and/or the queue to be displayed.
* Returns [Result.Dismissed] if the snack was dismissed, or [Result.ActionPerformed]
* if the optional action was selected.
*/
suspend fun serve(
message: String,
actionLabel: String? = null
): Result = mutex.withLock {
try {
suspendCancellableCoroutine { co ->
currentSnack = SnackImpl(message, actionLabel, co)
}
} finally {
currentSnack = null
}
}
}
@Composable
fun SnackBarHost(
menu: SnackMenu,
modifier: Modifier = Modifier
) {
val snack = menu.currentSnack ?: return
SnackBar(
message = snack.message,
modifier = modifier,// TODO: append swipe to dismiss that calls snack.dismiss()
actionLabel = snack.actionLabel,
onAction = { snack.performAction() }
)
}
@Composable
fun Demo(events: Flow<String>) {
val menu = remember { SnackMenu() }
launchInComposition(events) {
events.collect {
menu.serve(it)
}
}
SnackBarHost(menu)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment