Skip to content

Commit

Permalink
🎨 Decoupled SnackbarProvider, NavControllerProvider and NestedScrollC…
Browse files Browse the repository at this point in the history
…onnectionProvider from AppViewModel
  • Loading branch information
zhufucdev committed Feb 3, 2024
1 parent e364e4d commit 93beb80
Show file tree
Hide file tree
Showing 8 changed files with 70 additions and 57 deletions.
29 changes: 14 additions & 15 deletions app/src/main/java/com/zhufucdev/motion_emulator/ui/AppHome.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.material3.windowsizeclass.WindowSizeClass
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
import androidx.compose.runtime.Composable
Expand All @@ -37,35 +38,36 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavDestination
import androidx.navigation.NavDestination.Companion.hierarchy
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import com.zhufucdev.motion_emulator.ui.model.AppViewModel
import com.zhufucdev.motion_emulator.R
import com.zhufucdev.motion_emulator.ui.component.TooltipHost
import com.zhufucdev.motion_emulator.ui.composition.DefaultFloatingActionButtonManipulator
import com.zhufucdev.motion_emulator.ui.composition.LocalNavControllerProvider
import com.zhufucdev.motion_emulator.ui.composition.LocalNestedScrollConnectionProvider
import com.zhufucdev.motion_emulator.ui.composition.LocalSnackbarProvider
import com.zhufucdev.motion_emulator.ui.composition.SnackbarProvider

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AppHome(windowSize: WindowSizeClass) {
val model = viewModel<AppViewModel>()
val navController = rememberNavController()
model.navController = navController
val backStackEntry by navController.currentBackStackEntryAsState()
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
val snackbars = remember { SnackbarHostState() }

CompositionLocalProvider(
LocalSnackbarProvider provides SnackbarProvider(snackbars)
LocalSnackbarProvider provides snackbars,
LocalNestedScrollConnectionProvider provides scrollBehavior.nestedScrollConnection,
LocalNavControllerProvider provides navController
) {
when (windowSize.widthSizeClass) {
WindowWidthSizeClass.Compact -> {
Scaffold(
topBar = { TopBar() },
topBar = { TopBar(scrollBehavior) },
floatingActionButton = {
DefaultFloatingActionButtonManipulator.CurrentFloatingActionButton()
},
Expand Down Expand Up @@ -100,7 +102,7 @@ fun AppHome(windowSize: WindowSizeClass) {
}
}
Scaffold(
topBar = { TopBar() },
topBar = { TopBar(scrollBehavior) },
floatingActionButton = { DefaultFloatingActionButtonManipulator.CurrentFloatingActionButton() },
snackbarHost = { SnackbarHost(snackbars) }
) {
Expand All @@ -125,7 +127,7 @@ fun AppHome(windowSize: WindowSizeClass) {
}
) {
Scaffold(
topBar = { TopBar() },
topBar = { TopBar(scrollBehavior) },
floatingActionButton = { DefaultFloatingActionButtonManipulator.CurrentFloatingActionButton() },
snackbarHost = { SnackbarHost(snackbars) }
) {
Expand All @@ -139,10 +141,9 @@ fun AppHome(windowSize: WindowSizeClass) {

@Composable
private fun NavContent(paddingValues: PaddingValues) {
val model = viewModel<AppViewModel>()
val provider = LocalViewModelStoreOwner.current!!
NavHost(
navController = model.navController,
navController = LocalNavControllerProvider.current!!,
startDestination = NavigationDestinations.EMULATE.name
) {
composable(NavigationDestinations.PLUGINS.name) {
Expand Down Expand Up @@ -171,9 +172,7 @@ private fun NavContent(paddingValues: PaddingValues) {

@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun TopBar() {
val model = viewModel<AppViewModel>()
model.scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
private fun TopBar(scrollBehavior: TopAppBarScrollBehavior) {
TooltipHost {
CenterAlignedTopAppBar(
title = {
Expand All @@ -182,7 +181,7 @@ private fun TopBar() {
fontFamily = FontFamily.Serif
)
},
scrollBehavior = model.scrollBehavior,
scrollBehavior = scrollBehavior,
navigationIcon = {
Icon(
painter = painterResource(id = R.drawable.ic_launcher_monochrome),
Expand Down
22 changes: 17 additions & 5 deletions app/src/main/java/com/zhufucdev/motion_emulator/ui/ManagerApp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,16 @@ import androidx.compose.ui.window.Popup
import androidx.compose.ui.window.PopupProperties
import androidx.constraintlayout.compose.ConstraintLayout
import androidx.lifecycle.viewmodel.compose.viewModel
import com.aventrix.jnanoid.jnanoid.NanoIdUtils
import com.zhufucdev.me.stub.CellTimeline
import com.zhufucdev.me.stub.Data
import com.zhufucdev.me.stub.Motion
import com.zhufucdev.me.stub.Trace
import com.zhufucdev.motion_emulator.R
import com.zhufucdev.motion_emulator.data.Cells
import com.zhufucdev.motion_emulator.data.Motions
import com.zhufucdev.motion_emulator.data.Traces
import com.zhufucdev.motion_emulator.extension.AppUpdater
import com.zhufucdev.motion_emulator.extension.dateString
import com.zhufucdev.motion_emulator.extension.effectiveTimeFormat
import com.zhufucdev.motion_emulator.ui.component.CaptionText
Expand All @@ -91,6 +94,7 @@ import com.zhufucdev.motion_emulator.ui.component.HorizontalSpacer
import com.zhufucdev.motion_emulator.ui.component.Swipeable
import com.zhufucdev.motion_emulator.ui.component.TooltipHost
import com.zhufucdev.motion_emulator.ui.component.VerticalSpacer
import com.zhufucdev.motion_emulator.ui.composition.LocalNestedScrollConnectionProvider
import com.zhufucdev.motion_emulator.ui.composition.LocalSnackbarProvider
import com.zhufucdev.motion_emulator.ui.composition.ScaffoldElements
import com.zhufucdev.motion_emulator.ui.model.AppViewModel
Expand Down Expand Up @@ -200,9 +204,13 @@ fun ManagerApp(
Box(
modifier = Modifier
.fillMaxSize()
.nestedScroll(appModel.scrollBehavior.nestedScrollConnection)
.alpha(1 - actionTransition * 0.7f)
.padding(paddingValues),
.padding(paddingValues)
.then(
LocalNestedScrollConnectionProvider.current
?.let { Modifier.nestedScroll(it) }
?: Modifier
),
) {
OverviewScreen(managerModel)
}
Expand All @@ -212,11 +220,15 @@ fun ManagerApp(
@Composable
@Preview
fun ActivityPreview() {
val appModel = AppViewModel(AppUpdater(LocalContext.current))
MotionEmulatorTheme {
ManagerApp(
paddingValues = PaddingValues(0.dp),

appModel = appModel,
managerModel = ManagerViewModel(
emptyList(), LocalContext.current, listOf()
)
)
}
}

Expand Down Expand Up @@ -249,7 +261,7 @@ fun OverviewScreen(viewModel: ManagerViewModel = viewModel()) {
if (it.resultCode == Activity.RESULT_OK && uri != null) {
coroutine.launch {
val count = viewModel.import(uri)
snackbars.controller?.showSnackbar(
snackbars?.showSnackbar(
message = context.getString(R.string.text_imported, count)
)
}
Expand Down Expand Up @@ -392,7 +404,7 @@ fun OverviewScreen(viewModel: ManagerViewModel = viewModel()) {
viewModel.remove(item)

coroutine.launch {
val res = snackbars.controller?.showSnackbar(
val res = snackbars?.showSnackbar(
message = context.getString(R.string.text_deleted, displayName),
actionLabel = context.getString(R.string.action_undo)
)
Expand Down
47 changes: 23 additions & 24 deletions app/src/main/java/com/zhufucdev/motion_emulator/ui/PluginsApp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,6 @@ import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarScrollBehavior
Expand Down Expand Up @@ -86,9 +84,9 @@ import com.zhufucdev.motion_emulator.plugin.PluginUpdater
import com.zhufucdev.motion_emulator.ui.component.CaptionText
import com.zhufucdev.motion_emulator.ui.component.TooltipHost
import com.zhufucdev.motion_emulator.ui.component.TooltipScope
import com.zhufucdev.motion_emulator.ui.composition.LocalNestedScrollConnectionProvider
import com.zhufucdev.motion_emulator.ui.composition.LocalSnackbarProvider
import com.zhufucdev.motion_emulator.ui.composition.ScaffoldElements
import com.zhufucdev.motion_emulator.ui.model.AppViewModel
import com.zhufucdev.motion_emulator.ui.model.PluginItem
import com.zhufucdev.motion_emulator.ui.model.PluginItemState
import com.zhufucdev.motion_emulator.ui.model.PluginViewModel
Expand All @@ -100,33 +98,32 @@ import com.zhufucdev.update.UpdaterStatus
import com.zhufucdev.update.canInstallUpdate
import com.zhufucdev.update.installUpdate
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.launch
import java.io.File
import kotlin.math.max

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun PluginsApp(paddingValues: PaddingValues) {
fun PluginsApp(paddingValues: PaddingValues, viewModel: PluginViewModel = viewModel()) {
ScaffoldElements {
noFloatingButton()
}

val model = viewModel<PluginViewModel>()
val snackbars = LocalSnackbarProvider.current
val listState = rememberLazyListState()
var listBounds by remember {
mutableStateOf<Rect?>(null)
}
val enabled = remember(model.plugins) {
model.plugins.filter { it.enabled }.toMutableStateList()
val enabled = remember(viewModel.plugins) {
viewModel.plugins.filter { it.enabled }.toMutableStateList()
}
val disabled = remember(model.plugins) {
model.plugins.filter { !it.enabled }.toMutableStateList()
val disabled = remember(viewModel.plugins) {
viewModel.plugins.filter { !it.enabled }.toMutableStateList()
}
val downloadable by model.downloadable.collectAsState(initial = emptyList())
val downloadable by viewModel.downloadable.collectAsState(initial = emptyList())
val disabledList by remember(disabled) {
derivedStateOf {
disabled + downloadable.filter { edge -> !model.plugins.any { it.id == edge.id } }
disabled + downloadable.filter { edge -> !viewModel.plugins.any { it.id == edge.id } }
}
}

Expand All @@ -141,7 +138,7 @@ fun PluginsApp(paddingValues: PaddingValues) {
}
}
}
val onDrop: (PluginItem) -> Unit = remember(model.plugins) {
val onDrop: (PluginItem) -> Unit = remember(viewModel.plugins) {
{
val disabledRelatedIndex = hoveringItemIndex - max(1, enabled.size) - 2
if (disabledRelatedIndex >= 0) {
Expand All @@ -167,16 +164,17 @@ fun PluginsApp(paddingValues: PaddingValues) {
}
}
}
model.save(enabled)
viewModel.save(enabled)
floating = null
}
}

TooltipHost {
val appModel = viewModel<AppViewModel>()
Scaffold(
modifier = Modifier.nestedScroll(appModel.scrollBehavior.nestedScrollConnection)
) { _ ->
Box(
modifier = LocalNestedScrollConnectionProvider.current
?.let { Modifier.nestedScroll(it) }
?: Modifier
) {
LazyColumn(
state = listState,
modifier = Modifier
Expand All @@ -202,7 +200,6 @@ fun PluginsApp(paddingValues: PaddingValues) {
}
},
plugins = enabled,
snackbarHostState = snackbars.controller!!,
isHovered = hoveringItemIndex == 1,
onPrepareDrag = { plugin, offset, pos ->
floating = FloatingItem(plugin, offset, pos)
Expand All @@ -229,7 +226,6 @@ fun PluginsApp(paddingValues: PaddingValues) {
}
},
plugins = disabledList,
snackbarHostState = snackbars.controller,
isHovered = hoveringItemIndex == enabled.size + 3,
onPrepareDrag = { plugin, offset, pos ->
floating = FloatingItem(plugin, offset, pos)
Expand Down Expand Up @@ -339,7 +335,6 @@ private fun TooltipScope.PluginsAppTopBar(
private fun LazyListScope.operativeArea(
label: @Composable () -> Unit,
plugins: List<PluginItem>,
snackbarHostState: SnackbarHostState,
isHovered: Boolean,
onPrepareDrag: (PluginItem, Offset, Offset) -> Unit,
onDrag: (Offset) -> Unit,
Expand Down Expand Up @@ -427,6 +422,7 @@ private fun LazyListScope.operativeArea(
}
}

val snackbars = LocalSnackbarProvider.current
Surface(
onClick = {
val status = updater.status
Expand All @@ -443,13 +439,13 @@ private fun LazyListScope.operativeArea(
}

if (updater.update == null) {
snackbarHostState.showSnackbar(context.getString(R.string.text_asset_fetch_failed))
snackbars?.showSnackbar(context.getString(R.string.text_asset_fetch_failed))
return@launch
}
val file = try {
updater.download()
} catch (e: Exception) {
snackbarHostState.showSnackbar(context.getString(R.string.text_asset_fetch_failed))
snackbars?.showSnackbar(context.getString(R.string.text_asset_fetch_failed))
return@launch
}
installRequest(file)
Expand Down Expand Up @@ -676,6 +672,9 @@ private fun DropArea(
@Composable
fun PluginsAppPreview() {
MotionEmulatorTheme {
PluginsApp(PaddingValues(0.dp))
PluginsApp(
paddingValues = PaddingValues(0.dp),
viewModel = PluginViewModel(emptyList(), flow { })
)
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
@file:OptIn(ExperimentalMaterial3Api::class)

package com.zhufucdev.motion_emulator.ui

import androidx.compose.animation.*
Expand Down Expand Up @@ -56,7 +54,7 @@ import kotlinx.coroutines.*
import kotlin.math.*
import kotlin.time.Duration.Companion.seconds

@OptIn(ExperimentalFoundationApi::class)
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class)
@Composable
fun TraceEditor(target: Trace, viewModel: ManagerViewModel = viewModel()) {
var rename by remember { mutableStateOf(target.name) }
Expand Down Expand Up @@ -205,7 +203,7 @@ fun TraceEditor(target: Trace, viewModel: ManagerViewModel = viewModel()) {

lifecycleCoroutine.launch {
val result =
snackbars.controller?.showSnackbar(
snackbars?.showSnackbar(
message = context.getString(R.string.text_deleted, it.name),
actionLabel = context.getString(R.string.action_undo),
withDismissAction = true
Expand Down Expand Up @@ -241,7 +239,7 @@ fun TraceEditor(target: Trace, viewModel: ManagerViewModel = viewModel()) {

lifecycleCoroutine.launch {
val result =
snackbars.controller?.showSnackbar(
snackbars?.showSnackbar(
message = context.getString(
R.string.text_deleted,
context.getString(saltTypeNames[it.type]!!)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.zhufucdev.motion_emulator.ui.composition

import androidx.compose.runtime.compositionLocalOf
import androidx.navigation.NavHostController

val LocalNavControllerProvider = compositionLocalOf<NavHostController?> { null }
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.zhufucdev.motion_emulator.ui.composition

import androidx.compose.runtime.compositionLocalOf
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection

val LocalNestedScrollConnectionProvider = compositionLocalOf<NestedScrollConnection?> { null }
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,5 @@ package com.zhufucdev.motion_emulator.ui.composition
import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.compositionLocalOf

data class SnackbarProvider(val controller: SnackbarHostState?)

val LocalSnackbarProvider = compositionLocalOf { SnackbarProvider(null) }
val LocalSnackbarProvider = compositionLocalOf<SnackbarHostState?> { null }
Loading

0 comments on commit 93beb80

Please sign in to comment.