Skip to content

Commit

Permalink
build: refactor scaffold layout to support rtl.
Browse files Browse the repository at this point in the history
  • Loading branch information
oxyroid committed May 9, 2024
1 parent 141b4f2 commit 34bbe78
Show file tree
Hide file tree
Showing 14 changed files with 219 additions and 125 deletions.
4 changes: 2 additions & 2 deletions androidApp/src/main/java/com/m3u/androidApp/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import com.m3u.androidApp.ui.App
import com.m3u.androidApp.ui.AppViewModel
import com.m3u.ui.Events.connectDPadIntent
import com.m3u.ui.Events.enableDPadReaction
import com.m3u.ui.Toolkit
import com.m3u.ui.helper.Helper
import dagger.hilt.android.AndroidEntryPoint
Expand All @@ -33,7 +33,7 @@ class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
installSplashScreen()
enableEdgeToEdge()
connectDPadIntent()
enableDPadReaction()
super.onCreate(savedInstanceState)
setContent {
Toolkit(helper) {
Expand Down
2 changes: 1 addition & 1 deletion androidApp/src/main/java/com/m3u/androidApp/ui/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ private fun AppImpl(

val tv = isTelevision()

AppScaffold(
Scaffold(
rootDestination = rootDestination,
onBackPressed = onBackPressed,
navigateToRoot = navigateToRoot,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
Expand All @@ -32,14 +31,17 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.layout.SubcomposeLayout
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEach
import com.m3u.androidApp.ui.internal.AppScaffoldImpl
import com.m3u.androidApp.ui.internal.AppScaffoldRailImpl
import com.m3u.androidApp.ui.internal.AppScaffoldTvImpl
import androidx.compose.ui.util.fastMap
import androidx.compose.ui.util.fastMaxOfOrNull
import com.m3u.androidApp.ui.internal.SmartphoneScaffoldImpl
import com.m3u.androidApp.ui.internal.TabletScaffoldImpl
import com.m3u.androidApp.ui.internal.TelevisionScaffoldImpl
import com.m3u.material.components.Background
import com.m3u.material.components.Icon
import com.m3u.material.components.IconButton
Expand All @@ -59,7 +61,7 @@ import dev.chrisbanes.haze.hazeChild

@Composable
@OptIn(InternalComposeApi::class)
internal fun AppScaffold(
internal fun Scaffold(
rootDestination: Destination.Root?,
navigateToRoot: (Destination.Root) -> Unit,
modifier: Modifier = Modifier,
Expand All @@ -80,7 +82,7 @@ internal fun AppScaffold(
Background {
when {
tv -> {
AppScaffoldTvImpl(
TelevisionScaffoldImpl(
rootDestination = rootDestination,
fob = fob,
title = title,
Expand All @@ -93,7 +95,7 @@ internal fun AppScaffold(
}

!useRailNav -> {
AppScaffoldImpl(
SmartphoneScaffoldImpl(
rootDestination = rootDestination,
alwaysShowLabel = alwaysShowLabel,
fob = fob,
Expand All @@ -107,7 +109,7 @@ internal fun AppScaffold(
}

else -> {
AppScaffoldRailImpl(
TabletScaffoldImpl(
rootDestination = rootDestination,
alwaysShowLabel = alwaysShowLabel,
fob = fob,
Expand Down Expand Up @@ -135,7 +137,7 @@ internal fun Items(
}

@Composable
internal fun TopBarWithContent(
internal fun MainContent(
windowInsets: WindowInsets,
title: String,
onBackPressed: (() -> Unit)?,
Expand All @@ -151,6 +153,7 @@ internal fun TopBarWithContent(
if (!tv) {
TopAppBar(
colors = TopAppBarDefaults.topAppBarColors(Color.Transparent),
windowInsets = windowInsets,
title = {
Row(
horizontalArrangement = Arrangement.SpaceBetween,
Expand Down Expand Up @@ -209,9 +212,7 @@ internal fun TopBarWithContent(
}
},
contentWindowInsets = windowInsets,
containerColor = MaterialTheme.colorScheme.background,
contentColor = MaterialTheme.colorScheme.onBackground,
modifier = Modifier.fillMaxSize()
containerColor = MaterialTheme.colorScheme.background
) { padding ->
Background {
Box {
Expand Down Expand Up @@ -289,3 +290,68 @@ internal fun NavigationItemLayout(
}
block(selected, actualOnClick, icon, label)
}

internal enum class ScaffoldContent { Navigation, MainContent }
internal enum class ScaffoldRole { SmartPhone, Tablet, Television }

@Composable
internal fun ScaffoldLayout(
role: ScaffoldRole,
navigation: @Composable () -> Unit,
mainContent: @Composable (PaddingValues) -> Unit
) {
SubcomposeLayout { constraints ->
val layoutWidth = constraints.maxWidth
val layoutHeight = constraints.maxHeight
val looseConstraints = constraints.copy(minWidth = 0, minHeight = 0)
val navigationPlaceables = subcompose(
slotId = ScaffoldContent.Navigation,
content = navigation
)
.fastMap { it.measure(looseConstraints) }
val navigationWidth = navigationPlaceables.fastMaxOfOrNull { it.width } ?: 0
val navigationHeight = navigationPlaceables.fastMaxOfOrNull { it.height } ?: 0
val mainContentPadding = when (role) {
ScaffoldRole.SmartPhone -> PaddingValues(
bottom = navigationHeight.toDp()
)

else -> PaddingValues()
}
val mainContentPlaceables = subcompose(ScaffoldContent.MainContent) {
mainContent(mainContentPadding)
}
.fastMap {
when (role) {
ScaffoldRole.SmartPhone -> it.measure(looseConstraints)
else -> it.measure(
constraints.copy(
maxWidth = layoutWidth - navigationWidth
)
)
}
}
layout(layoutWidth, layoutHeight) {
when (role) {
ScaffoldRole.SmartPhone -> {
mainContentPlaceables.fastForEach {
it.place(0, 0)
}
navigationPlaceables.fastForEach {
it.place(0, layoutHeight - navigationHeight)
}
}

else -> {
// rtl
navigationPlaceables.fastForEach {
it.placeRelative(0, 0)
}
mainContentPlaceables.fastForEach {
it.placeRelative(navigationWidth, 0)
}
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.m3u.androidApp.ui.internal

import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.WindowInsets
Expand All @@ -13,19 +12,15 @@ import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
import androidx.compose.runtime.Composable
import androidx.compose.runtime.InternalComposeApi
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.graphics.Color
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
import com.m3u.androidApp.ui.Items
import com.m3u.androidApp.ui.MainContent
import com.m3u.androidApp.ui.NavigationItemLayout
import com.m3u.androidApp.ui.TopBarWithContent
import com.m3u.androidApp.ui.ScaffoldLayout
import com.m3u.androidApp.ui.ScaffoldRole
import com.m3u.material.components.Background
import com.m3u.material.ktx.plus
import com.m3u.material.model.LocalHazeState
import com.m3u.ui.Destination
Expand All @@ -36,7 +31,7 @@ import dev.chrisbanes.haze.hazeChild

@Composable
@InternalComposeApi
fun AppScaffoldImpl(
fun SmartphoneScaffoldImpl(
rootDestination: Destination.Root?,
alwaysShowLabel: Boolean,
fob: Fob?,
Expand All @@ -48,31 +43,14 @@ fun AppScaffoldImpl(
modifier: Modifier = Modifier
) {
val hazeState = LocalHazeState.current
val density = LocalDensity.current
var navigationHeight by remember { mutableStateOf(0.dp) }
Box(modifier) {
TopBarWithContent(
windowInsets = WindowInsets.systemBars.exclude(WindowInsets.navigationBars),
title = title,
onBackPressed = onBackPressed,
actions = actions,
content = {
content(it + PaddingValues(bottom = navigationHeight))
}
)

val navigation = @Composable {
NavigationBar(
containerColor = Color.Transparent,
contentColor = MaterialTheme.colorScheme.onBackground,
modifier = Modifier
.hazeChild(hazeState, style = HazeStyle(blurRadius = 6.dp, noiseFactor = 0.4f))
.fillMaxWidth()
.align(Alignment.BottomCenter)
.onGloballyPositioned {
navigationHeight = with(density) {
it.size.height.toDp()
}
}
) {
Items { currentRootDestination ->
NavigationItemLayout(
Expand All @@ -95,4 +73,21 @@ fun AppScaffoldImpl(
}
}
}
}
val mainContent = @Composable { contentPadding: PaddingValues ->
MainContent(
windowInsets = WindowInsets.systemBars.exclude(WindowInsets.navigationBars),
title = title,
onBackPressed = onBackPressed,
actions = actions,
content = { content(it + contentPadding) }
)
}

Background(modifier) {
ScaffoldLayout(
role = ScaffoldRole.SmartPhone,
mainContent = mainContent,
navigation = navigation
)
}
}
Original file line number Diff line number Diff line change
@@ -1,28 +1,34 @@
package com.m3u.androidApp.ui.internal

import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.exclude
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.systemBars
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.NavigationRail
import androidx.compose.material3.NavigationRailDefaults
import androidx.compose.material3.NavigationRailItem
import androidx.compose.runtime.Composable
import androidx.compose.runtime.InternalComposeApi
import androidx.compose.ui.Modifier
import com.m3u.androidApp.ui.Items
import com.m3u.androidApp.ui.MainContent
import com.m3u.androidApp.ui.NavigationItemLayout
import com.m3u.androidApp.ui.TopBarWithContent
import com.m3u.androidApp.ui.ScaffoldLayout
import com.m3u.androidApp.ui.ScaffoldRole
import com.m3u.material.components.Background
import com.m3u.material.ktx.plus
import com.m3u.ui.Destination
import com.m3u.ui.helper.Action
import com.m3u.ui.helper.Fob

@Composable
@InternalComposeApi
internal fun AppScaffoldRailImpl(
internal fun TabletScaffoldImpl(
rootDestination: Destination.Root?,
alwaysShowLabel: Boolean,
fob: Fob?,
Expand All @@ -33,11 +39,14 @@ internal fun AppScaffoldRailImpl(
content: @Composable BoxScope.(PaddingValues) -> Unit,
modifier: Modifier = Modifier
) {
Row(modifier) {
val navigationWindowInsets = NavigationRailDefaults.windowInsets

val navigation = @Composable {
NavigationRail(
modifier = Modifier.fillMaxHeight(),
containerColor = MaterialTheme.colorScheme.background,
contentColor = MaterialTheme.colorScheme.onBackground,
windowInsets = navigationWindowInsets,
// keep header not null
header = {}
) {
Expand All @@ -61,18 +70,24 @@ internal fun AppScaffoldRailImpl(
}
}
}
Box(
Modifier
.fillMaxHeight()
.weight(1f)
) {
TopBarWithContent(
windowInsets = WindowInsets.systemBars,
title = title,
onBackPressed = onBackPressed,
actions = actions,
content = content
)
}
}
val mainContent = @Composable { contentPadding: PaddingValues ->
MainContent(
windowInsets = WindowInsets.systemBars.exclude(
navigationWindowInsets.only(WindowInsetsSides.Start)
),
title = title,
onBackPressed = onBackPressed,
actions = actions,
content = { content(it + contentPadding) }
)
}

Background(modifier) {
ScaffoldLayout(
role = ScaffoldRole.Tablet,
navigation = navigation,
mainContent = mainContent
)
}
}
Loading

0 comments on commit 34bbe78

Please sign in to comment.