Navigation bars
This commit is contained in:
@@ -92,9 +92,12 @@ dependencies {
|
|||||||
implementation 'androidx.compose.material:material-icons-extended'
|
implementation 'androidx.compose.material:material-icons-extended'
|
||||||
implementation 'androidx.activity:activity-compose:1.8.2'
|
implementation 'androidx.activity:activity-compose:1.8.2'
|
||||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0'
|
implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0'
|
||||||
|
implementation 'androidx.lifecycle:lifecycle-runtime-compose:2.7.0'
|
||||||
implementation "com.google.accompanist:accompanist-adaptive:0.34.0"
|
implementation "com.google.accompanist:accompanist-adaptive:0.34.0"
|
||||||
implementation "androidx.navigation:navigation-compose:2.7.7"
|
implementation "androidx.navigation:navigation-compose:2.7.7"
|
||||||
|
|
||||||
|
kapt 'androidx.lifecycle:lifecycle-compiler:2.7.0'
|
||||||
|
|
||||||
implementation "androidx.paging:paging-compose:3.2.1"
|
implementation "androidx.paging:paging-compose:3.2.1"
|
||||||
|
|
||||||
implementation "io.ktor:ktor-client-core:2.3.8"
|
implementation "io.ktor:ktor-client-core:2.3.8"
|
||||||
|
|||||||
@@ -24,8 +24,10 @@ import androidx.activity.enableEdgeToEdge
|
|||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi
|
import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi
|
||||||
import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass
|
import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import com.google.accompanist.adaptive.calculateDisplayFeatures
|
import com.google.accompanist.adaptive.calculateDisplayFeatures
|
||||||
import xyz.quaver.pupil.ui.composable.PupilApp
|
import xyz.quaver.pupil.ui.composable.MainApp
|
||||||
import xyz.quaver.pupil.ui.theme.AppTheme
|
import xyz.quaver.pupil.ui.theme.AppTheme
|
||||||
import xyz.quaver.pupil.ui.viewmodel.MainViewModel
|
import xyz.quaver.pupil.ui.viewmodel.MainViewModel
|
||||||
|
|
||||||
@@ -42,10 +44,13 @@ class MainActivity : BaseActivity() {
|
|||||||
val windowSize = calculateWindowSizeClass(this)
|
val windowSize = calculateWindowSizeClass(this)
|
||||||
val displayFeatures = calculateDisplayFeatures(this)
|
val displayFeatures = calculateDisplayFeatures(this)
|
||||||
|
|
||||||
PupilApp(
|
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
|
MainApp(
|
||||||
windowSize = windowSize,
|
windowSize = windowSize,
|
||||||
displayFeatures = displayFeatures,
|
displayFeatures = displayFeatures,
|
||||||
uiState = viewModel.uiState
|
uiState = uiState,
|
||||||
|
navigateToDestination = viewModel::navigateToDestination
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package xyz.quaver.pupil.ui.composable
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun GalleryList(
|
||||||
|
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,6 +1,14 @@
|
|||||||
package xyz.quaver.pupil.ui.composable
|
package xyz.quaver.pupil.ui.composable
|
||||||
|
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.material3.DrawerValue
|
import androidx.compose.material3.DrawerValue
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.ModalNavigationDrawer
|
||||||
import androidx.compose.material3.PermanentNavigationDrawer
|
import androidx.compose.material3.PermanentNavigationDrawer
|
||||||
import androidx.compose.material3.rememberDrawerState
|
import androidx.compose.material3.rememberDrawerState
|
||||||
import androidx.compose.material3.windowsizeclass.WindowHeightSizeClass
|
import androidx.compose.material3.windowsizeclass.WindowHeightSizeClass
|
||||||
@@ -8,16 +16,18 @@ import androidx.compose.material3.windowsizeclass.WindowSizeClass
|
|||||||
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
|
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.window.layout.DisplayFeature
|
import androidx.window.layout.DisplayFeature
|
||||||
import androidx.window.layout.FoldingFeature
|
import androidx.window.layout.FoldingFeature
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import xyz.quaver.pupil.ui.viewmodel.MainUIState
|
import xyz.quaver.pupil.ui.viewmodel.MainUIState
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun PupilApp(
|
fun MainApp(
|
||||||
windowSize: WindowSizeClass,
|
windowSize: WindowSizeClass,
|
||||||
displayFeatures: List<DisplayFeature>,
|
displayFeatures: List<DisplayFeature>,
|
||||||
uiState: MainUIState
|
uiState: MainUIState,
|
||||||
|
navigateToDestination: (MainDestination) -> Unit
|
||||||
) {
|
) {
|
||||||
val navigationType: NavigationType
|
val navigationType: NavigationType
|
||||||
val contentType: ContentType
|
val contentType: ContentType
|
||||||
@@ -31,7 +41,7 @@ fun PupilApp(
|
|||||||
|
|
||||||
when (windowSize.widthSizeClass) {
|
when (windowSize.widthSizeClass) {
|
||||||
WindowWidthSizeClass.Compact -> {
|
WindowWidthSizeClass.Compact -> {
|
||||||
navigationType = NavigationType.NAVIGATION_RAIL
|
navigationType = NavigationType.BOTTOM_NAVIGATION
|
||||||
contentType = ContentType.SINGLE_PANE
|
contentType = ContentType.SINGLE_PANE
|
||||||
}
|
}
|
||||||
WindowWidthSizeClass.Medium -> {
|
WindowWidthSizeClass.Medium -> {
|
||||||
@@ -51,7 +61,7 @@ fun PupilApp(
|
|||||||
contentType = ContentType.DUAL_PANE
|
contentType = ContentType.DUAL_PANE
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
navigationType = NavigationType.NAVIGATION_RAIL
|
navigationType = NavigationType.BOTTOM_NAVIGATION
|
||||||
contentType = ContentType.SINGLE_PANE
|
contentType = ContentType.SINGLE_PANE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -63,30 +73,113 @@ fun PupilApp(
|
|||||||
else -> NavigationContentPosition.TOP
|
else -> NavigationContentPosition.TOP
|
||||||
}
|
}
|
||||||
|
|
||||||
PupilNavigationWrapper(
|
MainNavigationWrapper(
|
||||||
navigationType,
|
navigationType,
|
||||||
contentType,
|
contentType,
|
||||||
navigationContentPosition
|
displayFeatures,
|
||||||
|
navigationContentPosition,
|
||||||
|
uiState,
|
||||||
|
navigateToDestination
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun PupilNavigationWrapper(
|
private fun MainNavigationWrapper(
|
||||||
navigationType: NavigationType,
|
navigationType: NavigationType,
|
||||||
contentType: ContentType,
|
contentType: ContentType,
|
||||||
navigationContentPosition: NavigationContentPosition
|
displayFeatures: List<DisplayFeature>,
|
||||||
|
navigationContentPosition: NavigationContentPosition,
|
||||||
|
uiState: MainUIState,
|
||||||
|
navigateToDestination: (MainDestination) -> Unit
|
||||||
) {
|
) {
|
||||||
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
|
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
val openDrawer: () -> Unit = {
|
||||||
|
coroutineScope.launch {
|
||||||
|
drawerState.open()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (navigationType == NavigationType.PERMANENT_NAVIGATION_DRAWER) {
|
if (navigationType == NavigationType.PERMANENT_NAVIGATION_DRAWER) {
|
||||||
PermanentNavigationDrawer(drawerContent = {
|
PermanentNavigationDrawer(drawerContent = {
|
||||||
PermanentNavigationDrawerContent(
|
PermanentNavigationDrawerContent(
|
||||||
navigationContentPosition = navigationContentPosition
|
selectedDestination = uiState.currentDestination,
|
||||||
|
navigationContentPosition = navigationContentPosition,
|
||||||
|
navigateToDestination = navigateToDestination
|
||||||
)
|
)
|
||||||
}) {
|
}) {
|
||||||
// PupilMain()
|
MainContent(
|
||||||
|
navigationType = navigationType,
|
||||||
|
contentType = contentType,
|
||||||
|
displayFeatures = displayFeatures,
|
||||||
|
navigationContentPosition = navigationContentPosition,
|
||||||
|
uiState = uiState,
|
||||||
|
navigateToDestination = navigateToDestination,
|
||||||
|
onDrawerClicked = openDrawer
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ModalNavigationDrawer(
|
||||||
|
drawerContent = {
|
||||||
|
ModalNavigationDrawerContent(
|
||||||
|
selectedDestination = uiState.currentDestination,
|
||||||
|
navigationContentPosition = navigationContentPosition,
|
||||||
|
navigateToDestination = navigateToDestination,
|
||||||
|
onDrawerClicked = {
|
||||||
|
coroutineScope.launch {
|
||||||
|
drawerState.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
drawerState = drawerState
|
||||||
|
) {
|
||||||
|
MainContent(
|
||||||
|
navigationType = navigationType,
|
||||||
|
contentType = contentType,
|
||||||
|
displayFeatures = displayFeatures,
|
||||||
|
navigationContentPosition = navigationContentPosition,
|
||||||
|
uiState = uiState,
|
||||||
|
navigateToDestination = navigateToDestination,
|
||||||
|
onDrawerClicked = openDrawer
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MainContent(
|
||||||
|
navigationType: NavigationType,
|
||||||
|
contentType: ContentType,
|
||||||
|
displayFeatures: List<DisplayFeature>,
|
||||||
|
navigationContentPosition: NavigationContentPosition,
|
||||||
|
uiState: MainUIState,
|
||||||
|
navigateToDestination: (MainDestination) -> Unit,
|
||||||
|
onDrawerClicked: () -> Unit
|
||||||
|
) {
|
||||||
|
Row(modifier = Modifier.fillMaxSize()) {
|
||||||
|
AnimatedVisibility(visible = navigationType == NavigationType.NAVIGATION_RAIL) {
|
||||||
|
MainNavigationRail(
|
||||||
|
selectedDestination = uiState.currentDestination,
|
||||||
|
navigationContentPosition = navigationContentPosition,
|
||||||
|
navigateToDestination = navigateToDestination,
|
||||||
|
onDrawerClicked = onDrawerClicked
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.background(MaterialTheme.colorScheme.inverseOnSurface)
|
||||||
|
) {
|
||||||
|
Box(modifier = Modifier.weight(1f))
|
||||||
|
AnimatedVisibility(visible = navigationType == NavigationType.BOTTOM_NAVIGATION) {
|
||||||
|
BottomNavigationBar(
|
||||||
|
selectedDestination = uiState.currentDestination,
|
||||||
|
navigateToDestination = navigateToDestination
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,37 +2,46 @@ package xyz.quaver.pupil.ui.composable
|
|||||||
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Download
|
import androidx.compose.material.icons.filled.Download
|
||||||
|
import androidx.compose.material.icons.filled.Favorite
|
||||||
import androidx.compose.material.icons.filled.History
|
import androidx.compose.material.icons.filled.History
|
||||||
import androidx.compose.material.icons.filled.Search
|
import androidx.compose.material.icons.filled.Search
|
||||||
import androidx.compose.material.icons.filled.Star
|
import androidx.compose.material.icons.filled.Star
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
|
|
||||||
data class MainDestination(
|
sealed interface MainDestination {
|
||||||
val route: String,
|
val route: String
|
||||||
val icon: ImageVector,
|
val icon: ImageVector
|
||||||
val textId: Int
|
val textId: Int
|
||||||
)
|
|
||||||
|
object Search: MainDestination {
|
||||||
|
override val route = "search"
|
||||||
|
override val icon = Icons.Default.Search
|
||||||
|
override val textId = R.string.main_destination_search
|
||||||
|
}
|
||||||
|
|
||||||
|
object History: MainDestination {
|
||||||
|
override val route = "history"
|
||||||
|
override val icon = Icons.Default.History
|
||||||
|
override val textId = R.string.main_destination_history
|
||||||
|
}
|
||||||
|
|
||||||
|
object Downloads: MainDestination {
|
||||||
|
override val route = "downloads"
|
||||||
|
override val icon = Icons.Default.Download
|
||||||
|
override val textId = R.string.main_destination_downloads
|
||||||
|
}
|
||||||
|
|
||||||
|
object Favorites: MainDestination {
|
||||||
|
override val route = "favorites"
|
||||||
|
override val icon = Icons.Default.Favorite
|
||||||
|
override val textId = R.string.main_destination_favorites
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val mainDestinations = listOf(
|
val mainDestinations = listOf(
|
||||||
MainDestination(
|
MainDestination.Search,
|
||||||
"search",
|
MainDestination.History,
|
||||||
Icons.Default.Search,
|
MainDestination.Downloads,
|
||||||
R.string.main_destination_search
|
MainDestination.Favorites
|
||||||
),
|
|
||||||
MainDestination(
|
|
||||||
"history",
|
|
||||||
Icons.Default.History,
|
|
||||||
R.string.main_destination_history
|
|
||||||
),
|
|
||||||
MainDestination(
|
|
||||||
"downloads",
|
|
||||||
Icons.Default.Download,
|
|
||||||
R.string.main_destination_downloads
|
|
||||||
),
|
|
||||||
MainDestination(
|
|
||||||
"favorites",
|
|
||||||
Icons.Default.Star,
|
|
||||||
R.string.main_destination_favorites
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
@@ -4,56 +4,289 @@ import androidx.compose.foundation.background
|
|||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.sizeIn
|
import androidx.compose.foundation.layout.sizeIn
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.automirrored.filled.MenuOpen
|
||||||
|
import androidx.compose.material.icons.filled.Menu
|
||||||
|
import androidx.compose.material.icons.filled.MenuOpen
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.ModalDrawerSheet
|
||||||
|
import androidx.compose.material3.NavigationBar
|
||||||
|
import androidx.compose.material3.NavigationBarItem
|
||||||
|
import androidx.compose.material3.NavigationDrawerItem
|
||||||
|
import androidx.compose.material3.NavigationDrawerItemDefaults
|
||||||
|
import androidx.compose.material3.NavigationRail
|
||||||
|
import androidx.compose.material3.NavigationRailItem
|
||||||
import androidx.compose.material3.PermanentDrawerSheet
|
import androidx.compose.material3.PermanentDrawerSheet
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.layout.Layout
|
||||||
|
import androidx.compose.ui.layout.Measurable
|
||||||
|
import androidx.compose.ui.layout.MeasurePolicy
|
||||||
|
import androidx.compose.ui.layout.layout
|
||||||
|
import androidx.compose.ui.layout.layoutId
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.offset
|
||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun PermanentNavigationDrawerContent(
|
fun PermanentNavigationDrawerContent(
|
||||||
navigationContentPosition: NavigationContentPosition
|
selectedDestination: MainDestination,
|
||||||
|
navigationContentPosition: NavigationContentPosition,
|
||||||
|
navigateToDestination: (MainDestination) -> Unit
|
||||||
) {
|
) {
|
||||||
PermanentDrawerSheet(
|
PermanentDrawerSheet(
|
||||||
modifier = Modifier.sizeIn(minWidth = 200.dp, maxWidth = 300.dp),
|
modifier = Modifier.sizeIn(minWidth = 200.dp, maxWidth = 300.dp),
|
||||||
drawerContainerColor = MaterialTheme.colorScheme.inverseOnSurface
|
drawerContainerColor = MaterialTheme.colorScheme.inverseOnSurface
|
||||||
) {
|
) {
|
||||||
Column(
|
Layout(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.background(MaterialTheme.colorScheme.inverseOnSurface)
|
.background(MaterialTheme.colorScheme.inverseOnSurface)
|
||||||
.padding(16.dp)
|
.padding(16.dp),
|
||||||
) {
|
content = {
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically
|
modifier = Modifier.layoutId(LayoutType.HEADER),
|
||||||
) {
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
modifier = Modifier.size(32.dp),
|
||||||
|
painter = painterResource(R.drawable.app_icon),
|
||||||
|
tint = Color.Unspecified,
|
||||||
|
contentDescription = "app icon"
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.padding(16.dp),
|
||||||
|
text = "Pupil",
|
||||||
|
style = MaterialTheme.typography.titleLarge.copy(fontWeight = FontWeight.Bold),
|
||||||
|
color = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.layoutId(LayoutType.CONTENT)
|
||||||
|
.verticalScroll(rememberScrollState()),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
mainDestinations.forEach { destination ->
|
||||||
|
NavigationDrawerItem(
|
||||||
|
label = {
|
||||||
|
Text(
|
||||||
|
text = stringResource(destination.textId),
|
||||||
|
modifier = Modifier.padding(16.dp)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
icon = {
|
||||||
|
Icon(
|
||||||
|
imageVector = destination.icon,
|
||||||
|
contentDescription = stringResource(destination.textId)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
selected = selectedDestination.route == destination.route,
|
||||||
|
colors = NavigationDrawerItemDefaults.colors(
|
||||||
|
unselectedContainerColor = Color.Transparent
|
||||||
|
),
|
||||||
|
onClick = { navigateToDestination(destination) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
measurePolicy = navigationMeasurePolicy(navigationContentPosition)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ModalNavigationDrawerContent(
|
||||||
|
selectedDestination: MainDestination,
|
||||||
|
navigationContentPosition: NavigationContentPosition,
|
||||||
|
navigateToDestination: (MainDestination) -> Unit,
|
||||||
|
onDrawerClicked: () -> Unit
|
||||||
|
) {
|
||||||
|
ModalDrawerSheet {
|
||||||
|
Layout(
|
||||||
|
modifier = Modifier
|
||||||
|
.background(MaterialTheme.colorScheme.inverseOnSurface)
|
||||||
|
.padding(16.dp),
|
||||||
|
content = {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.layoutId(LayoutType.HEADER)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(16.dp),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
modifier = Modifier.size(32.dp),
|
||||||
|
painter = painterResource(R.drawable.app_icon),
|
||||||
|
tint = Color.Unspecified,
|
||||||
|
contentDescription = "app icon"
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.padding(16.dp),
|
||||||
|
text = "Pupil",
|
||||||
|
style = MaterialTheme.typography.titleLarge.copy(fontWeight = FontWeight.Bold),
|
||||||
|
color = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
IconButton(onClick = onDrawerClicked) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.AutoMirrored.Default.MenuOpen,
|
||||||
|
contentDescription = stringResource(R.string.main_open_navigation_drawer)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column (
|
||||||
|
modifier = Modifier
|
||||||
|
.layoutId(LayoutType.CONTENT)
|
||||||
|
.verticalScroll(rememberScrollState()),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
mainDestinations.forEach { destination ->
|
||||||
|
NavigationDrawerItem(
|
||||||
|
label = {
|
||||||
|
Text(
|
||||||
|
text = stringResource(destination.textId),
|
||||||
|
modifier = Modifier.padding(16.dp)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
icon = {
|
||||||
|
Icon(
|
||||||
|
imageVector = destination.icon,
|
||||||
|
contentDescription = stringResource(destination.textId)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
selected = selectedDestination.route == destination.route,
|
||||||
|
colors = NavigationDrawerItemDefaults.colors(
|
||||||
|
unselectedContainerColor = Color.Transparent
|
||||||
|
),
|
||||||
|
onClick = { navigateToDestination(destination) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
measurePolicy = navigationMeasurePolicy(navigationContentPosition)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MainNavigationRail(
|
||||||
|
selectedDestination: MainDestination,
|
||||||
|
navigationContentPosition: NavigationContentPosition,
|
||||||
|
navigateToDestination: (MainDestination) -> Unit,
|
||||||
|
onDrawerClicked: () -> Unit
|
||||||
|
) {
|
||||||
|
NavigationRail (
|
||||||
|
modifier = Modifier.fillMaxHeight(),
|
||||||
|
containerColor = MaterialTheme.colorScheme.inverseOnSurface
|
||||||
|
) {
|
||||||
|
NavigationRailItem(
|
||||||
|
selected = false,
|
||||||
|
onClick = onDrawerClicked,
|
||||||
|
icon = {
|
||||||
Icon(
|
Icon(
|
||||||
modifier = Modifier.size(32.dp),
|
imageVector = Icons.Default.Menu,
|
||||||
painter = painterResource(R.drawable.app_icon),
|
contentDescription = stringResource(R.string.main_open_navigation_drawer)
|
||||||
tint = Color.Unspecified,
|
|
||||||
contentDescription = "app icon"
|
|
||||||
)
|
)
|
||||||
Text(
|
}
|
||||||
modifier = Modifier.padding(16.dp),
|
)
|
||||||
text = "Pupil",
|
|
||||||
style = MaterialTheme.typography.titleLarge.copy(fontWeight = FontWeight.Bold),
|
Column(
|
||||||
color = MaterialTheme.colorScheme.primary
|
modifier = Modifier.verticalScroll(rememberScrollState()),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||||
|
) {
|
||||||
|
mainDestinations.forEach { destination ->
|
||||||
|
NavigationRailItem(
|
||||||
|
selected = selectedDestination.route == destination.route,
|
||||||
|
onClick = { navigateToDestination(destination) },
|
||||||
|
icon = {
|
||||||
|
Icon(
|
||||||
|
imageVector = destination.icon,
|
||||||
|
contentDescription = stringResource(destination.textId)
|
||||||
|
)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Column(
|
}
|
||||||
|
}
|
||||||
|
|
||||||
) {
|
@Composable
|
||||||
Text("Help")
|
fun BottomNavigationBar(
|
||||||
|
selectedDestination: MainDestination,
|
||||||
|
navigateToDestination: (MainDestination) -> Unit
|
||||||
|
) {
|
||||||
|
NavigationBar(modifier = Modifier.fillMaxWidth()) {
|
||||||
|
mainDestinations.forEach { destination ->
|
||||||
|
NavigationBarItem(
|
||||||
|
selected = selectedDestination.route == destination.route,
|
||||||
|
onClick = { navigateToDestination(destination) },
|
||||||
|
icon = {
|
||||||
|
Icon(
|
||||||
|
imageVector = destination.icon,
|
||||||
|
contentDescription = stringResource(destination.textId)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun navigationMeasurePolicy(
|
||||||
|
navigationContentPosition: NavigationContentPosition,
|
||||||
|
): MeasurePolicy {
|
||||||
|
return MeasurePolicy { measurables, constraints ->
|
||||||
|
lateinit var headerMeasurable: Measurable
|
||||||
|
lateinit var contentMeasurable: Measurable
|
||||||
|
measurables.forEach {
|
||||||
|
when (it.layoutId) {
|
||||||
|
LayoutType.HEADER -> headerMeasurable = it
|
||||||
|
LayoutType.CONTENT -> contentMeasurable = it
|
||||||
|
else -> error("Unknown layoutId encountered!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val headerPlaceable = headerMeasurable.measure(constraints)
|
||||||
|
val contentPlaceable = contentMeasurable.measure(
|
||||||
|
constraints.offset(vertical = -headerPlaceable.height)
|
||||||
|
)
|
||||||
|
layout(constraints.maxWidth, constraints.maxHeight) {
|
||||||
|
headerPlaceable.placeRelative(0, 0)
|
||||||
|
|
||||||
|
val nonContentVerticalSpace = constraints.maxHeight - contentPlaceable.height
|
||||||
|
|
||||||
|
val contentPlaceableY = when (navigationContentPosition) {
|
||||||
|
NavigationContentPosition.TOP -> 0
|
||||||
|
NavigationContentPosition.CENTER -> nonContentVerticalSpace / 2
|
||||||
|
}.coerceAtLeast(headerPlaceable.height)
|
||||||
|
|
||||||
|
contentPlaceable.placeRelative(0, contentPlaceableY)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class LayoutType {
|
||||||
|
HEADER, CONTENT
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
package xyz.quaver.pupil.ui.composable
|
package xyz.quaver.pupil.ui.composable
|
||||||
|
|
||||||
enum class NavigationType {
|
enum class NavigationType {
|
||||||
NAVIGATION_RAIL, PERMANENT_NAVIGATION_DRAWER
|
NAVIGATION_RAIL, PERMANENT_NAVIGATION_DRAWER, BOTTOM_NAVIGATION
|
||||||
}
|
}
|
||||||
@@ -1,14 +1,25 @@
|
|||||||
package xyz.quaver.pupil.ui.viewmodel
|
package xyz.quaver.pupil.ui.viewmodel
|
||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import xyz.quaver.pupil.networking.SearchQuery
|
import xyz.quaver.pupil.networking.SearchQuery
|
||||||
import xyz.quaver.pupil.ui.composable.MainRoutes
|
import xyz.quaver.pupil.ui.composable.MainDestination
|
||||||
|
import xyz.quaver.pupil.ui.composable.mainDestinations
|
||||||
|
|
||||||
class MainViewModel : ViewModel() {
|
class MainViewModel : ViewModel() {
|
||||||
val uiState: MainUIState = MainUIState()
|
private val _uiState = MutableStateFlow(MainUIState())
|
||||||
|
val uiState: StateFlow<MainUIState> = _uiState
|
||||||
|
|
||||||
|
fun navigateToDestination(destination: MainDestination) {
|
||||||
|
_uiState.value = MainUIState(
|
||||||
|
currentDestination = destination
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class MainUIState(
|
data class MainUIState(
|
||||||
val route: MainRoutes = MainRoutes.SEARCH,
|
val currentDestination: MainDestination = mainDestinations.first(),
|
||||||
val query: SearchQuery? = null
|
val query: SearchQuery? = null,
|
||||||
|
val loading: Boolean = true
|
||||||
)
|
)
|
||||||
@@ -33,6 +33,7 @@
|
|||||||
<string name="default_query_dialog_filter_guro">グロフィルター</string>
|
<string name="default_query_dialog_filter_guro">グロフィルター</string>
|
||||||
<string name="default_query_dialog_language">"言語: "</string>
|
<string name="default_query_dialog_language">"言語: "</string>
|
||||||
<string name="default_query_dialog_title">デフォルトキーワード設定</string>
|
<string name="default_query_dialog_title">デフォルトキーワード設定</string>
|
||||||
|
<string name="main_open_navigation_drawer">メニューを開く</string>
|
||||||
<string name="main_drawer_group_contact_title">お問い合わせ先</string>
|
<string name="main_drawer_group_contact_title">お問い合わせ先</string>
|
||||||
<string name="main_drawer_group_contact_homepage">ホームページ</string>
|
<string name="main_drawer_group_contact_homepage">ホームページ</string>
|
||||||
<string name="main_drawer_group_contact_help">ヘルプ</string>
|
<string name="main_drawer_group_contact_help">ヘルプ</string>
|
||||||
@@ -159,4 +160,5 @@
|
|||||||
<string name="unaccessible_download_folder">アンドロイド11以上では外部からのアプリ内部空間接近が不可能です。ダウンロードフォルダを変更しますか?</string>
|
<string name="unaccessible_download_folder">アンドロイド11以上では外部からのアプリ内部空間接近が不可能です。ダウンロードフォルダを変更しますか?</string>
|
||||||
<string name="settings_networking">ネットワーク</string>
|
<string name="settings_networking">ネットワーク</string>
|
||||||
<string name="settings_recover_downloads">ダウンロードデータベースを再構築</string>
|
<string name="settings_recover_downloads">ダウンロードデータベースを再構築</string>
|
||||||
|
<string name="main_close_navigation_drawer">メニューを閉じる</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -36,6 +36,7 @@
|
|||||||
<string name="main_drawer_group_contact_github">Github</string>
|
<string name="main_drawer_group_contact_github">Github</string>
|
||||||
<string name="main_drawer_group_contact_help">도움말</string>
|
<string name="main_drawer_group_contact_help">도움말</string>
|
||||||
<string name="main_drawer_group_contact_homepage">홈페이지</string>
|
<string name="main_drawer_group_contact_homepage">홈페이지</string>
|
||||||
|
<string name="main_open_navigation_drawer">메뉴 열기</string>
|
||||||
<string name="main_drawer_group_contact_title">문의</string>
|
<string name="main_drawer_group_contact_title">문의</string>
|
||||||
<string name="reader_fab_fullscreen">전체 화면</string>
|
<string name="reader_fab_fullscreen">전체 화면</string>
|
||||||
<string name="channel_download">다운로드</string>
|
<string name="channel_download">다운로드</string>
|
||||||
@@ -159,4 +160,5 @@
|
|||||||
<string name="unaccessible_download_folder">안드로이드 11 이상에서는 외부에서 현재 다운로드 폴더에 접근할 수 없습니다. 변경하시겠습니까?</string>
|
<string name="unaccessible_download_folder">안드로이드 11 이상에서는 외부에서 현재 다운로드 폴더에 접근할 수 없습니다. 변경하시겠습니까?</string>
|
||||||
<string name="settings_networking">네트워크</string>
|
<string name="settings_networking">네트워크</string>
|
||||||
<string name="settings_recover_downloads">다운로드 데이터베이스 복구</string>
|
<string name="settings_recover_downloads">다운로드 데이터베이스 복구</string>
|
||||||
|
<string name="main_close_navigation_drawer">메뉴 닫기</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -57,6 +57,8 @@
|
|||||||
<string name="main_destination_history">History</string>
|
<string name="main_destination_history">History</string>
|
||||||
<string name="main_destination_downloads">Downloads</string>
|
<string name="main_destination_downloads">Downloads</string>
|
||||||
<string name="main_destination_favorites">Favorites</string>
|
<string name="main_destination_favorites">Favorites</string>
|
||||||
|
<string name="main_open_navigation_drawer">Open Navigation Drawer</string>
|
||||||
|
<string name="main_close_navigation_drawer">Close Navigation Drawer</string>
|
||||||
<string name="main_drawer_group_contact_title">Contact</string>
|
<string name="main_drawer_group_contact_title">Contact</string>
|
||||||
<string name="main_drawer_group_contact_help">Help</string>
|
<string name="main_drawer_group_contact_help">Help</string>
|
||||||
<string name="main_drawer_group_contact_homepage">Visit homepage</string>
|
<string name="main_drawer_group_contact_homepage">Visit homepage</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user