Skip to content

Instantly share code, notes, and snippets.

@bmc08gt
Last active August 25, 2023 13:10
Show Gist options
  • Save bmc08gt/933158353f2c77d530ee37428b822dd6 to your computer and use it in GitHub Desktop.
Save bmc08gt/933158353f2c77d530ee37428b822dd6 to your computer and use it in GitHub Desktop.
TipKit
@Composable
fun Content() {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally
) {
+ val tips = LocalTipsEngine.current!!.tips as SampleTips
var checked by remember {
mutableStateOf(false)
}
val onCheckChanged = { didCheck: Boolean ->
checked = didCheck
+ tips.anchor.toggle.record(didCheck)
}
var clicks by remember {
mutableStateOf(0)
}
val onClick = {
clicks++
+ tips.anchor.clicks.record()
}
Anchor(
modifier = Modifier
.padding(top = 32.dp)
.border(1.dp, Color.Red)
+ .popoverTip(tips.anchor),
)
Surface(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.weight(1f),
tonalElevation = 8.dp,
shape = MaterialTheme.shapes.extraLarge
) {
LazyColumn(modifier = Modifier.weight(1f), contentPadding = PaddingValues(16.dp)) {
+ item { InlineTip(tip = tips.anchor3) }
items(100) { num ->
Text(text = "item $num")
}
}
}
Text(text = "Click count:$clicks")
Switch(
checked = checked,
onCheckedChange = onCheckChanged
)
Button(
modifier = Modifier
.padding(bottom = 64.dp)
+ .popoverTip(tips.anchor2, alignment = Alignment.CenterEnd),
onClick = onClick
) {
Text(text = "Click me")
}
}
}
}
@Singleton
class AnchorTip @Inject constructor(
eventEngine: EventEngine,
tipEngine: TipsEngine
) : Tip(eventEngine, tipEngine) {
init {
flowPosition = 0
flowId = "onboarding-flow"
}
val clicks = Trigger(
id = "clicks",
engine = eventEngine
).also { await(it) }
val toggle = Trigger(
id = "toggle",
engine = eventEngine
).also { await(it) }
override fun title(): @Composable () -> Unit {
return {
Text(
text = "Remember",
color = MaterialTheme.colorScheme.primary,
style = MaterialTheme.typography.titleMedium
)
}
}
override fun message(): @Composable () -> Unit {
return {
Text(
text = "With great power, comes great responsibility",
style = MaterialTheme.typography.bodySmall
)
}
}
override fun asset(): @Composable () -> Unit {
return {
Image(
imageVector = Icons.Rounded.Info,
colorFilter = ColorFilter.tint(LocalContentColor.current),
contentDescription = null
)
}
}
override fun actions(): List<TipAction> {
return listOf(
TipAction(name,"learn-more", "Learn More")
)
}
override suspend fun criteria(): List<EligibilityCriteria> {
val clicks = clicks.events.firstOrNull().orEmpty()
val toggledOn = toggle.events.firstOrNull()?.lastOrNull()?.value as? Boolean ?: false
return listOf(
{ clicks.count() >= 5 },
{ toggledOn }
)
}
}
@Singleton
class Anchor2Tip @Inject constructor(
eventEngine: EventEngine,
tipEngine: TipsEngine
) : Tip(eventEngine, tipEngine) {
init {
flowPosition = 1
flowId = "onboarding-flow"
}
override fun title(): @Composable () -> Unit {
return {
Text(
text = "Flows",
color = MaterialTheme.colorScheme.primary,
style = MaterialTheme.typography.titleMedium
)
}
}
override fun message(): @Composable () -> Unit {
return {
Text(
text = "Chain tips together in a flow",
style = MaterialTheme.typography.bodySmall
)
}
}
override fun asset(): @Composable () -> Unit {
return {
Image(
imageVector = Icons.Rounded.ArrowForward,
colorFilter = ColorFilter.tint(LocalContentColor.current),
contentDescription = null
)
}
}
override suspend fun criteria(): List<EligibilityCriteria> {
val priorTipSeen = flow.find { it.name == "anchortip" }?.hasBeenSeen() ?: false
return listOf( { priorTipSeen } )
}
}
@Singleton
class Anchor3Tip @Inject constructor(
eventEngine: EventEngine,
tipEngine: TipsEngine
) : Tip(eventEngine, tipEngine) {
init {
flowPosition = 2
flowId = "onboarding-flow"
}
override fun title(): @Composable () -> Unit {
return {
Text(
text = "Inline",
color = MaterialTheme.colorScheme.primary,
style = MaterialTheme.typography.titleMedium
)
}
}
override fun message(): @Composable () -> Unit {
return {
Text(
text = "We can even render them inline on screen content.",
style = MaterialTheme.typography.bodySmall
)
}
}
override fun asset(): @Composable () -> Unit {
return {
Image(
imageVector = Icons.Rounded.Favorite,
colorFilter = ColorFilter.tint(LocalContentColor.current),
contentDescription = null
)
}
}
override suspend fun criteria(): List<EligibilityCriteria> {
val priorTipSeen = flow.find { it.name == "anchor2tip" }?.hasBeenSeen() ?: false
return listOf( { priorTipSeen } )
}
}
@bmc08gt
Copy link
Author

bmc08gt commented Jun 28, 2023

  • All tip logic, and UI contents (as well as actions) are part of the Tip .
  • can be attached to any composable within the TipScaffold at any level of the node tree
  • Asset support
  • Multiple criteria
  • actions and navigation
  • Styled more uniformly
  • supports tip "flows" to allow chaining tips together (e.g onboarding guided tour)
  • inline tips
Screen.Recording.2023-06-28.at.3.44.21.PM.mov

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment