Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tinker with a ScaffoldScreen #924

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package dev.zacsweers.catchup.circuit

import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.text.font.FontWeight
import com.slack.circuit.codegen.annotations.CircuitInject
import com.slack.circuit.foundation.CircuitContent
import com.slack.circuit.foundation.NavEvent
import com.slack.circuit.foundation.onNavEvent
import com.slack.circuit.runtime.CircuitUiEvent
import com.slack.circuit.runtime.CircuitUiState
import com.slack.circuit.runtime.Navigator
import com.slack.circuit.runtime.Screen
import com.slack.circuit.runtime.presenter.Presenter
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dev.zacsweers.catchup.di.AppScope
import kotlinx.parcelize.Parcelize

@Parcelize
data class ScaffoldScreen(val title: String, val screen: Screen) : Screen {
data class State(val title: String, val screen: Screen, val eventSink: (Event) -> Unit) :
CircuitUiState

data class Event(val navEvent: NavEvent) : CircuitUiEvent
}

class ScaffoldPresenter
@AssistedInject
constructor(
@Assisted val screen: ScaffoldScreen,
@Assisted val navigator: Navigator,
) : Presenter<ScaffoldScreen.State> {
@Composable
override fun present(): ScaffoldScreen.State {
return ScaffoldScreen.State(screen.title, screen.screen) { event ->
navigator.onNavEvent(event.navEvent)
}
}

@CircuitInject(ScaffoldScreen::class, AppScope::class)
@AssistedFactory
fun interface Factory {
fun create(screen: ScaffoldScreen, navigator: Navigator): ScaffoldPresenter
}
}

@CircuitInject(ScaffoldScreen::class, AppScope::class)
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ScaffoldUi(state: ScaffoldScreen.State, modifier: Modifier = Modifier) {
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
Scaffold(
modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
contentWindowInsets = WindowInsets(0, 0, 0, 0),
containerColor = Color.Transparent,
topBar = {
TopAppBar(
title = { Text(state.title, fontWeight = FontWeight.Black) },
scrollBehavior = scrollBehavior
)
},
) { innerPadding ->
CircuitContent(state.screen, modifier = Modifier.padding(innerPadding))
}
}
80 changes: 47 additions & 33 deletions app/src/main/kotlin/io/sweers/catchup/home/HomeScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
Expand All @@ -53,18 +54,21 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.window.layout.FoldingFeature
import com.google.accompanist.adaptive.HorizontalTwoPaneStrategy
import com.google.accompanist.adaptive.TwoPane
import com.google.accompanist.systemuicontroller.rememberSystemUiController
import com.slack.circuit.backstack.rememberSaveableBackStack
import com.slack.circuit.codegen.annotations.CircuitInject
import com.slack.circuit.foundation.CircuitContent
import com.slack.circuit.foundation.NavEvent
import com.slack.circuit.foundation.NavigableCircuitContent
import com.slack.circuit.foundation.onNavEvent
import com.slack.circuit.foundation.push
import com.slack.circuit.foundation.rememberCircuitNavigator
import com.slack.circuit.overlay.LocalOverlayHost
import com.slack.circuit.runtime.CircuitUiEvent
import com.slack.circuit.runtime.CircuitUiState
Expand All @@ -77,10 +81,12 @@ import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dagger.multibindings.StringKey
import dev.zacsweers.catchup.circuit.ScaffoldScreen
import dev.zacsweers.catchup.compose.LocalDisplayFeatures
import dev.zacsweers.catchup.compose.LocalDynamicTheme
import dev.zacsweers.catchup.compose.LocalScrollToTop
import dev.zacsweers.catchup.compose.MutableScrollToTop
import dev.zacsweers.catchup.compose.composableResources
import dev.zacsweers.catchup.compose.rememberStableCoroutineScope
import dev.zacsweers.catchup.deeplink.DeepLinkable
import dev.zacsweers.catchup.di.AppScope
Expand Down Expand Up @@ -215,9 +221,46 @@ fun Home(state: HomeScreen.State, modifier: Modifier = Modifier) {
val foldingFeature =
remember(displayFeatures) { displayFeatures.filterIsInstance<FoldingFeature>().firstOrNull() }

// TODO
// if foldable - twopane it
// if wide tablet - panel it. Maybe? or just
// if not - pager it
if (foldingFeature != null) {
// TODO
// try a PaneledCircuitContent where it's just a row of the backstack?

val resources = composableResources()
val currentRootScreen by remember { mutableStateOf<Screen>(SettingsScreen) }
var selectedIndex by remember { mutableIntStateOf(state.selectedIndex) }
selectedIndex = state.selectedIndex

// TODO hoist the backstack _up_ to the home screen? Only really matters for settings
val backstack = rememberSaveableBackStack { push(currentRootScreen) }
// TODO route nav events?
val navigator = rememberCircuitNavigator(backstack)

// TODO what about this
LaunchedEffect(Unit) {
snapshotFlow { selectedIndex }
.distinctUntilChanged()
.collect { index ->
val newScreen =
if (index == state.serviceMetas.size) {
// TODO should probably just synthesize putting the settings in the list
SettingsScreen
} else {
// Embed the content in a scaffold for padding and such
val meta = state.serviceMetas[index]
val title = resources.getString(meta.name)
ScaffoldScreen(title, ServiceScreen(meta.id))
}
println("Reset root to $newScreen")
navigator.goTo(newScreen)
}
}

val secondPaneContent: @Composable () -> Unit = {
NavigableCircuitContent(navigator, backstack)
}
val currentSecondPaneContent by rememberUpdatedState(secondPaneContent)

TwoPane(
first = {
Expand All @@ -226,38 +269,9 @@ fun Home(state: HomeScreen.State, modifier: Modifier = Modifier) {
VerticalDivider(Modifier.align(Alignment.CenterEnd), thickness = Dp.Hairline)
}
},
// TODO animate content changes, ideally same as nav decoration
second = {
// Box is to prevent it from flashing the background between changes
Box {
// TODO temporary until https://github.com/slackhq/circuit/pull/799
key(state.selectedIndex) {
// TODO
// should probably just synthesize putting the settings in the list
// crossfade?
if (state.selectedIndex == state.serviceMetas.size) {
// TODO this doesn't reaaaaaaally work because it wants to navigate on its own
CircuitContent(SettingsScreen)
} else {
// Embed the content in a scaffold for padding and such
val meta = state.serviceMetas[state.selectedIndex]
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
Scaffold(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
contentWindowInsets = WindowInsets(0, 0, 0, 0),
containerColor = Color.Transparent,
topBar = {
TopAppBar(
title = { Text(stringResource(meta.name), fontWeight = FontWeight.Black) },
scrollBehavior = scrollBehavior
)
},
) { innerPadding ->
CircuitContent(ServiceScreen(meta.id), modifier = Modifier.padding(innerPadding))
}
}
}
}
Box { currentSecondPaneContent() }
},
strategy = { density, layoutDirection, layoutCoordinates ->
// Split vertically if the height is larger than the width
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package dev.zacsweers.catchup.compose

import android.content.res.Configuration
import android.content.res.Resources
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext

/**
* A composable function that returns the [Resources]. It will be recomposed when [Configuration]
* gets updated.
*/
// Copied from Compose, idk why it's internal
@Composable
@ReadOnlyComposable
fun composableResources(): Resources {
LocalConfiguration.current
return LocalContext.current.resources
}