Skip to content

Commit

Permalink
catalog: theme reveal effect
Browse files Browse the repository at this point in the history
  • Loading branch information
hrach committed Oct 23, 2023
1 parent eed3860 commit 05d4669
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 12 deletions.
62 changes: 62 additions & 0 deletions catalog/src/main/java/kiwi/orbit/compose/catalog/AppTheme.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,28 @@ package kiwi.orbit.compose.catalog

import android.annotation.SuppressLint
import android.content.Context
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import kiwi.orbit.compose.catalog.utils.CirclePath
import kiwi.orbit.compose.ui.OrbitTheme
import kiwi.orbit.compose.ui.foundation.Typography
import kiwi.orbit.compose.ui.foundation.createTypography
Expand All @@ -29,6 +45,52 @@ fun AppTheme(
)
}

// Inspired by https://github.com/sanathsajeevakumara/ThemePickerAnimation
@Composable
internal fun AnimatedAppThem(
isLightTheme: Boolean,
onThemeToggle: (Boolean) -> Unit,
content: @Composable (onToggleTheme: (offset: Offset) -> Unit) -> Unit,
) {
var animationOffset by remember { mutableStateOf(Offset(0f, 0f)) }
AnimatedContent(
targetState = isLightTheme,
modifier = Modifier.fillMaxSize(),
transitionSpec = {
fadeIn(
initialAlpha = 0f,
animationSpec = tween(100),
) togetherWith fadeOut(
targetAlpha = .9f,
animationSpec = tween(800),
)
},
label = "AppThemeChange",
) { currentTheme ->
val revealSize = remember { Animatable(1f) }
LaunchedEffect(Unit) {
if (animationOffset.x > 0f) {
revealSize.snapTo(0f)
revealSize.animateTo(1f, animationSpec = tween(800))
} else {
revealSize.snapTo(1f)
}
}
Box(
modifier = Modifier
.fillMaxSize()
.clip(CirclePath(revealSize.value, animationOffset)),
) {
AppTheme(isLightTheme = currentTheme) {
content { offset ->
animationOffset = offset
onThemeToggle(!currentTheme)
}
}
}
}
}

@SuppressLint("DiscouragedApi")
@Composable
private fun rememberTypography(context: Context): Typography = remember {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.semantics.semantics
Expand Down Expand Up @@ -79,8 +80,8 @@ import kotlinx.serialization.ExperimentalSerializationApi
fun CatalogApplication() {
val systemUiController = rememberSystemUiController()

var isLightTheme by rememberSaveable { mutableStateOf<Boolean?>(null) }
val isLightThemeFinal = isLightTheme ?: !isSystemInDarkTheme()
var isLightThemeUser by rememberSaveable { mutableStateOf<Boolean?>(null) }
val isLightThemeFinal = isLightThemeUser ?: !isSystemInDarkTheme()

SideEffect {
systemUiController.setSystemBarsColor(
Expand All @@ -89,19 +90,18 @@ fun CatalogApplication() {
)
}

AppTheme(isLightTheme = isLightThemeFinal) {
NavGraph(
onToggleTheme = {
isLightTheme = !isLightThemeFinal
},
)
AnimatedAppThem(
isLightTheme = isLightThemeFinal,
onThemeToggle = { isLightThemeUser = it },
) { onThemeToggle ->
NavGraph(onThemeToggle = onThemeToggle)
}
}

@OptIn(ExperimentalSerializationApi::class, ExperimentalComposeUiApi::class)
@Composable
private fun NavGraph(
onToggleTheme: () -> Unit,
onThemeToggle: (offset: Offset) -> Unit,
) {
val density = LocalDensity.current
val navController = rememberNavController()
Expand All @@ -115,7 +115,7 @@ private fun NavGraph(
popEnterTransition = { SharedXAxisPopEnterTransition(density) },
popExitTransition = { SharedXAxisPopExitTransition(density) },
) {
composable<Destinations.Main> { MainScreen(navController::navigate, onToggleTheme) }
composable<Destinations.Main> { MainScreen(navController::navigate, onThemeToggle) }

composable<Destinations.Colors> { ColorsScreen(navController::navigateUp) }
composable<Destinations.Icons> { IconsScreen(navController::navigateUp) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,22 @@ import androidx.compose.material.icons.rounded.Tab
import androidx.compose.material.icons.rounded.ToggleOn
import androidx.compose.material.icons.rounded.WebAsset
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.layout.positionInWindow
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.center
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.toOffset
import com.kiwi.navigationcompose.typed.Destination
import kiwi.orbit.compose.catalog.Destinations
import kiwi.orbit.compose.icons.Icons
Expand Down Expand Up @@ -72,7 +81,7 @@ private data class MenuItem(
@Composable
internal fun MainScreen(
onNavigate: (Destination) -> Unit,
onToggleTheme: () -> Unit,
onThemeToggle: (Offset) -> Unit,
) {
fun MenuItem(title: String, icon: Any, testTag: String, onNavigate: () -> Destination): MenuItem =
MenuItem(title, icon, testTag, onClick = { onNavigate(onNavigate()) })
Expand Down Expand Up @@ -133,7 +142,13 @@ internal fun MainScreen(
TopAppBarLarge(
title = { Text("Orbit Compose Catalog") },
actions = {
IconButton(onClick = onToggleTheme) {
var offset by remember { mutableStateOf(Offset(0f, 0f)) }
IconButton(
modifier = Modifier.onGloballyPositioned {
offset = it.positionInWindow() + it.size.center.toOffset()
},
onClick = { onThemeToggle(offset) },
) {
Icon(MIcons.BrightnessMedium, contentDescription = null)
}
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package kiwi.orbit.compose.catalog.utils

import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.geometry.center
import androidx.compose.ui.graphics.Outline
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.LayoutDirection
import kotlin.math.sqrt

class CirclePath(
private val progress: Float,
private val origin: Offset = Offset(0f, 0f),
) : Shape {
override fun createOutline(
size: Size,
layoutDirection: LayoutDirection,
density: Density,
): Outline {
val center = Offset(
x = size.center.x - ((size.center.x - origin.x) * (1f - progress)),
y = size.center.y - ((size.center.y - origin.y) * (1f - progress)),
)
val radius = (sqrt(
size.height * size.height + size.width * size.width,
) * .5f) * progress

return Outline.Generic(
Path().apply {
addOval(
Rect(
center = center,
radius = radius,
),
)
},
)
}
}

0 comments on commit 05d4669

Please sign in to comment.