Basic Navigation
This commit is contained in:
1
app/proguard-rules.pro
vendored
1
app/proguard-rules.pro
vendored
@@ -32,3 +32,4 @@
|
|||||||
kotlinx.serialization.KSerializer serializer(...);
|
kotlinx.serialization.KSerializer serializer(...);
|
||||||
}
|
}
|
||||||
-keep class xyz.quaver.pupil.** { *; }
|
-keep class xyz.quaver.pupil.** { *; }
|
||||||
|
-dontwarn org.slf4j.impl.StaticLoggerBinder
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
"filters": [],
|
"filters": [],
|
||||||
"attributes": [],
|
"attributes": [],
|
||||||
"versionCode": 69,
|
"versionCode": 69,
|
||||||
"versionName": "5.3.10",
|
"versionName": "6.0.0",
|
||||||
"outputFile": "app-release.apk"
|
"outputFile": "app-release.apk"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -27,10 +27,10 @@ import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass
|
|||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.core.view.WindowCompat
|
import androidx.core.view.WindowCompat
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import androidx.navigation.compose.rememberNavController
|
||||||
import com.google.accompanist.adaptive.calculateDisplayFeatures
|
import com.google.accompanist.adaptive.calculateDisplayFeatures
|
||||||
import xyz.quaver.pupil.ui.composable.MainApp
|
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.MainUIState
|
|
||||||
import xyz.quaver.pupil.ui.viewmodel.MainViewModel
|
import xyz.quaver.pupil.ui.viewmodel.MainViewModel
|
||||||
|
|
||||||
class MainActivity : BaseActivity() {
|
class MainActivity : BaseActivity() {
|
||||||
@@ -48,13 +48,15 @@ class MainActivity : BaseActivity() {
|
|||||||
val windowSize = calculateWindowSizeClass(this)
|
val windowSize = calculateWindowSizeClass(this)
|
||||||
val displayFeatures = calculateDisplayFeatures(this)
|
val displayFeatures = calculateDisplayFeatures(this)
|
||||||
|
|
||||||
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
|
val uiState by viewModel.searchState.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
|
val navController = rememberNavController()
|
||||||
|
|
||||||
MainApp(
|
MainApp(
|
||||||
windowSize = windowSize,
|
windowSize = windowSize,
|
||||||
displayFeatures = displayFeatures,
|
displayFeatures = displayFeatures,
|
||||||
uiState = uiState,
|
uiState = uiState,
|
||||||
navigateToDestination = viewModel::navigateToDestination,
|
navController = navController,
|
||||||
closeDetailScreen = viewModel::closeDetailScreen,
|
closeDetailScreen = viewModel::closeDetailScreen,
|
||||||
onQueryChange = viewModel::onQueryChange,
|
onQueryChange = viewModel::onQueryChange,
|
||||||
loadSearchResult = viewModel::loadSearchResult
|
loadSearchResult = viewModel::loadSearchResult
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
package xyz.quaver.pupil.ui.composable
|
package xyz.quaver.pupil.ui.composable
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.animation.fadeIn
|
||||||
|
import androidx.compose.animation.fadeOut
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
@@ -14,25 +18,39 @@ import androidx.compose.material3.DrawerValue
|
|||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.ModalNavigationDrawer
|
import androidx.compose.material3.ModalNavigationDrawer
|
||||||
import androidx.compose.material3.PermanentNavigationDrawer
|
import androidx.compose.material3.PermanentNavigationDrawer
|
||||||
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.rememberDrawerState
|
import androidx.compose.material3.rememberDrawerState
|
||||||
import androidx.compose.material3.windowsizeclass.WindowHeightSizeClass
|
import androidx.compose.material3.windowsizeclass.WindowHeightSizeClass
|
||||||
import androidx.compose.material3.windowsizeclass.WindowSizeClass
|
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.getValue
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.navigation.NavHostController
|
||||||
|
import androidx.navigation.activity
|
||||||
|
import androidx.navigation.compose.NavHost
|
||||||
|
import androidx.navigation.compose.composable
|
||||||
|
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||||
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 kotlinx.coroutines.launch
|
||||||
|
import xyz.quaver.pupil.R
|
||||||
import xyz.quaver.pupil.networking.SearchQuery
|
import xyz.quaver.pupil.networking.SearchQuery
|
||||||
import xyz.quaver.pupil.ui.viewmodel.MainUIState
|
import xyz.quaver.pupil.ui.ReaderActivity
|
||||||
|
import xyz.quaver.pupil.ui.SettingsActivity
|
||||||
|
import xyz.quaver.pupil.ui.viewmodel.SearchState
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MainApp(
|
fun MainApp(
|
||||||
windowSize: WindowSizeClass,
|
windowSize: WindowSizeClass,
|
||||||
displayFeatures: List<DisplayFeature>,
|
displayFeatures: List<DisplayFeature>,
|
||||||
uiState: MainUIState,
|
uiState: SearchState,
|
||||||
navigateToDestination: (MainDestination) -> Unit,
|
navController: NavHostController,
|
||||||
closeDetailScreen: () -> Unit,
|
closeDetailScreen: () -> Unit,
|
||||||
onQueryChange: (SearchQuery?) -> Unit,
|
onQueryChange: (SearchQuery?) -> Unit,
|
||||||
loadSearchResult: (IntRange) -> Unit
|
loadSearchResult: (IntRange) -> Unit
|
||||||
@@ -87,7 +105,7 @@ fun MainApp(
|
|||||||
displayFeatures,
|
displayFeatures,
|
||||||
navigationContentPosition,
|
navigationContentPosition,
|
||||||
uiState,
|
uiState,
|
||||||
navigateToDestination,
|
navController,
|
||||||
closeDetailScreen = closeDetailScreen,
|
closeDetailScreen = closeDetailScreen,
|
||||||
onQueryChange = onQueryChange,
|
onQueryChange = onQueryChange,
|
||||||
loadSearchResult = loadSearchResult
|
loadSearchResult = loadSearchResult
|
||||||
@@ -100,8 +118,8 @@ private fun MainNavigationWrapper(
|
|||||||
contentType: ContentType,
|
contentType: ContentType,
|
||||||
displayFeatures: List<DisplayFeature>,
|
displayFeatures: List<DisplayFeature>,
|
||||||
navigationContentPosition: NavigationContentPosition,
|
navigationContentPosition: NavigationContentPosition,
|
||||||
uiState: MainUIState,
|
uiState: SearchState,
|
||||||
navigateToDestination: (MainDestination) -> Unit,
|
navController: NavHostController,
|
||||||
closeDetailScreen: () -> Unit,
|
closeDetailScreen: () -> Unit,
|
||||||
onQueryChange: (SearchQuery?) -> Unit,
|
onQueryChange: (SearchQuery?) -> Unit,
|
||||||
loadSearchResult: (IntRange) -> Unit
|
loadSearchResult: (IntRange) -> Unit
|
||||||
@@ -109,6 +127,9 @@ private fun MainNavigationWrapper(
|
|||||||
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
|
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
val navBackStackEntry by navController.currentBackStackEntryAsState()
|
||||||
|
val currentRoute = navBackStackEntry?.destination?.route
|
||||||
|
|
||||||
val openDrawer: () -> Unit = {
|
val openDrawer: () -> Unit = {
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
drawerState.open()
|
drawerState.open()
|
||||||
@@ -119,9 +140,12 @@ private fun MainNavigationWrapper(
|
|||||||
PermanentNavigationDrawer(
|
PermanentNavigationDrawer(
|
||||||
drawerContent = {
|
drawerContent = {
|
||||||
PermanentNavigationDrawerContent(
|
PermanentNavigationDrawerContent(
|
||||||
selectedDestination = uiState.currentDestination,
|
selectedDestination = currentRoute,
|
||||||
|
navigateToDestination = { navController.navigate(it.route) {
|
||||||
|
popUpTo(MainDestination.Search.route)
|
||||||
|
launchSingleTop = true
|
||||||
|
} },
|
||||||
navigationContentPosition = navigationContentPosition,
|
navigationContentPosition = navigationContentPosition,
|
||||||
navigateToDestination = navigateToDestination
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
@@ -129,9 +153,8 @@ private fun MainNavigationWrapper(
|
|||||||
navigationType = navigationType,
|
navigationType = navigationType,
|
||||||
contentType = contentType,
|
contentType = contentType,
|
||||||
displayFeatures = displayFeatures,
|
displayFeatures = displayFeatures,
|
||||||
navigationContentPosition = navigationContentPosition,
|
|
||||||
uiState = uiState,
|
uiState = uiState,
|
||||||
navigateToDestination = navigateToDestination,
|
navController = navController,
|
||||||
onDrawerClicked = openDrawer,
|
onDrawerClicked = openDrawer,
|
||||||
closeDetailScreen = closeDetailScreen,
|
closeDetailScreen = closeDetailScreen,
|
||||||
onQueryChange = onQueryChange,
|
onQueryChange = onQueryChange,
|
||||||
@@ -142,9 +165,12 @@ private fun MainNavigationWrapper(
|
|||||||
ModalNavigationDrawer(
|
ModalNavigationDrawer(
|
||||||
drawerContent = {
|
drawerContent = {
|
||||||
ModalNavigationDrawerContent(
|
ModalNavigationDrawerContent(
|
||||||
selectedDestination = uiState.currentDestination,
|
selectedDestination = currentRoute,
|
||||||
|
navigateToDestination = { navController.navigate(it.route) {
|
||||||
|
popUpTo(MainDestination.Search.route)
|
||||||
|
launchSingleTop = true
|
||||||
|
} },
|
||||||
navigationContentPosition = navigationContentPosition,
|
navigationContentPosition = navigationContentPosition,
|
||||||
navigateToDestination = navigateToDestination,
|
|
||||||
onDrawerClicked = {
|
onDrawerClicked = {
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
drawerState.close()
|
drawerState.close()
|
||||||
@@ -158,9 +184,8 @@ private fun MainNavigationWrapper(
|
|||||||
navigationType = navigationType,
|
navigationType = navigationType,
|
||||||
contentType = contentType,
|
contentType = contentType,
|
||||||
displayFeatures = displayFeatures,
|
displayFeatures = displayFeatures,
|
||||||
navigationContentPosition = navigationContentPosition,
|
|
||||||
uiState = uiState,
|
uiState = uiState,
|
||||||
navigateToDestination = navigateToDestination,
|
navController = navController,
|
||||||
onDrawerClicked = openDrawer,
|
onDrawerClicked = openDrawer,
|
||||||
closeDetailScreen = closeDetailScreen,
|
closeDetailScreen = closeDetailScreen,
|
||||||
onQueryChange = onQueryChange,
|
onQueryChange = onQueryChange,
|
||||||
@@ -170,25 +195,43 @@ private fun MainNavigationWrapper(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun NotImplemented() {
|
||||||
|
Box(Modifier.fillMaxSize()) {
|
||||||
|
Column(
|
||||||
|
Modifier.align(Alignment.Center),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
|
) {
|
||||||
|
Text("(⁄ ⁄•⁄ω⁄•⁄ ⁄)", style = MaterialTheme.typography.headlineMedium, color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f))
|
||||||
|
Text(stringResource(R.string.not_implemented), textAlign = TextAlign.Center)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MainContent(
|
fun MainContent(
|
||||||
navigationType: NavigationType,
|
navigationType: NavigationType,
|
||||||
contentType: ContentType,
|
contentType: ContentType,
|
||||||
displayFeatures: List<DisplayFeature>,
|
displayFeatures: List<DisplayFeature>,
|
||||||
navigationContentPosition: NavigationContentPosition,
|
uiState: SearchState,
|
||||||
uiState: MainUIState,
|
navController: NavHostController,
|
||||||
navigateToDestination: (MainDestination) -> Unit,
|
|
||||||
onDrawerClicked: () -> Unit,
|
onDrawerClicked: () -> Unit,
|
||||||
closeDetailScreen: () -> Unit,
|
closeDetailScreen: () -> Unit,
|
||||||
onQueryChange: (SearchQuery?) -> Unit,
|
onQueryChange: (SearchQuery?) -> Unit,
|
||||||
loadSearchResult: (IntRange) -> Unit
|
loadSearchResult: (IntRange) -> Unit
|
||||||
) {
|
) {
|
||||||
|
val navBackStackEntry by navController.currentBackStackEntryAsState()
|
||||||
|
val currentRoute = navBackStackEntry?.destination?.route
|
||||||
|
|
||||||
Row(modifier = Modifier.fillMaxSize()) {
|
Row(modifier = Modifier.fillMaxSize()) {
|
||||||
AnimatedVisibility(visible = navigationType == NavigationType.NAVIGATION_RAIL) {
|
AnimatedVisibility(visible = navigationType == NavigationType.NAVIGATION_RAIL) {
|
||||||
MainNavigationRail(
|
MainNavigationRail(
|
||||||
selectedDestination = uiState.currentDestination,
|
selectedDestination = currentRoute,
|
||||||
navigationContentPosition = navigationContentPosition,
|
navigateToDestination = { navController.navigate(it.route) {
|
||||||
navigateToDestination = navigateToDestination,
|
popUpTo(MainDestination.Search.route)
|
||||||
|
launchSingleTop = true
|
||||||
|
} },
|
||||||
onDrawerClicked = onDrawerClicked
|
onDrawerClicked = onDrawerClicked
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -198,27 +241,55 @@ fun MainContent(
|
|||||||
.background(MaterialTheme.colorScheme.inverseOnSurface)
|
.background(MaterialTheme.colorScheme.inverseOnSurface)
|
||||||
) {
|
) {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier.weight(1f)
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
.run {
|
.run {
|
||||||
if (navigationType == NavigationType.BOTTOM_NAVIGATION) {
|
if (navigationType == NavigationType.BOTTOM_NAVIGATION) {
|
||||||
this.consumeWindowInsets(WindowInsets.ime)
|
this
|
||||||
|
.consumeWindowInsets(WindowInsets.ime)
|
||||||
.consumeWindowInsets(WindowInsets.navigationBars)
|
.consumeWindowInsets(WindowInsets.navigationBars)
|
||||||
} else this
|
} else this
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
MainScreen(
|
NavHost(
|
||||||
contentType = contentType,
|
modifier = Modifier.fillMaxSize(),
|
||||||
displayFeatures = displayFeatures,
|
navController = navController,
|
||||||
uiState = uiState,
|
startDestination = MainDestination.Search.route
|
||||||
closeDetailScreen = closeDetailScreen,
|
) {
|
||||||
onQueryChange = onQueryChange,
|
composable(MainDestination.Search.route) {
|
||||||
loadSearchResult = loadSearchResult
|
SearchScreen(
|
||||||
)
|
contentType = contentType,
|
||||||
|
displayFeatures = displayFeatures,
|
||||||
|
uiState = uiState,
|
||||||
|
closeDetailScreen = closeDetailScreen,
|
||||||
|
onQueryChange = onQueryChange,
|
||||||
|
loadSearchResult = loadSearchResult
|
||||||
|
)
|
||||||
|
}
|
||||||
|
composable(MainDestination.History.route) {
|
||||||
|
NotImplemented()
|
||||||
|
}
|
||||||
|
composable(MainDestination.Downloads.route) {
|
||||||
|
NotImplemented()
|
||||||
|
}
|
||||||
|
composable(MainDestination.Favorites.route) {
|
||||||
|
NotImplemented()
|
||||||
|
}
|
||||||
|
activity(MainDestination.Settings.route) {
|
||||||
|
activityClass = SettingsActivity::class
|
||||||
|
}
|
||||||
|
activity(MainDestination.ImageViewer.route) {
|
||||||
|
activityClass = ReaderActivity::class
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
AnimatedVisibility(visible = navigationType == NavigationType.BOTTOM_NAVIGATION) {
|
AnimatedVisibility(visible = navigationType == NavigationType.BOTTOM_NAVIGATION) {
|
||||||
BottomNavigationBar(
|
BottomNavigationBar(
|
||||||
selectedDestination = uiState.currentDestination,
|
selectedDestination = currentRoute,
|
||||||
navigateToDestination = navigateToDestination
|
navigateToDestination = { navController.navigate(it.route) {
|
||||||
|
popUpTo(MainDestination.Search.route)
|
||||||
|
launchSingleTop = true
|
||||||
|
} }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
package xyz.quaver.pupil.ui.composable
|
package xyz.quaver.pupil.ui.composable
|
||||||
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.automirrored.filled.MenuBook
|
||||||
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.Favorite
|
||||||
import androidx.compose.material.icons.filled.History
|
import androidx.compose.material.icons.filled.History
|
||||||
|
import androidx.compose.material.icons.filled.MenuBook
|
||||||
import androidx.compose.material.icons.filled.Search
|
import androidx.compose.material.icons.filled.Search
|
||||||
|
import androidx.compose.material.icons.filled.Settings
|
||||||
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
|
||||||
@@ -14,34 +17,48 @@ sealed interface MainDestination {
|
|||||||
val icon: ImageVector
|
val icon: ImageVector
|
||||||
val textId: Int
|
val textId: Int
|
||||||
|
|
||||||
object Search: MainDestination {
|
data object Search: MainDestination {
|
||||||
override val route = "search"
|
override val route = "search"
|
||||||
override val icon = Icons.Default.Search
|
override val icon = Icons.Default.Search
|
||||||
override val textId = R.string.main_destination_search
|
override val textId = R.string.main_destination_search
|
||||||
}
|
}
|
||||||
|
|
||||||
object History: MainDestination {
|
data object History: MainDestination {
|
||||||
override val route = "history"
|
override val route = "history"
|
||||||
override val icon = Icons.Default.History
|
override val icon = Icons.Default.History
|
||||||
override val textId = R.string.main_destination_history
|
override val textId = R.string.main_destination_history
|
||||||
}
|
}
|
||||||
|
|
||||||
object Downloads: MainDestination {
|
data object Downloads: MainDestination {
|
||||||
override val route = "downloads"
|
override val route = "downloads"
|
||||||
override val icon = Icons.Default.Download
|
override val icon = Icons.Default.Download
|
||||||
override val textId = R.string.main_destination_downloads
|
override val textId = R.string.main_destination_downloads
|
||||||
}
|
}
|
||||||
|
|
||||||
object Favorites: MainDestination {
|
data object Favorites: MainDestination {
|
||||||
override val route = "favorites"
|
override val route = "favorites"
|
||||||
override val icon = Icons.Default.Favorite
|
override val icon = Icons.Default.Favorite
|
||||||
override val textId = R.string.main_destination_favorites
|
override val textId = R.string.main_destination_favorites
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data object Settings: MainDestination {
|
||||||
|
override val route = "settings"
|
||||||
|
override val icon = Icons.Default.Settings
|
||||||
|
override val textId = R.string.main_destination_settings
|
||||||
|
}
|
||||||
|
|
||||||
|
data object ImageViewer: MainDestination {
|
||||||
|
override val route = "image_viewer"
|
||||||
|
override val icon = Icons.AutoMirrored.Filled.MenuBook
|
||||||
|
override val textId = R.string.main_destination_image_viewer
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val mainDestinations = listOf(
|
val mainDestinations = listOf(
|
||||||
MainDestination.Search,
|
MainDestination.Search,
|
||||||
MainDestination.History,
|
MainDestination.History,
|
||||||
MainDestination.Downloads,
|
MainDestination.Downloads,
|
||||||
MainDestination.Favorites
|
MainDestination.Favorites,
|
||||||
|
MainDestination.Settings
|
||||||
)
|
)
|
||||||
@@ -43,13 +43,14 @@ 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 androidx.compose.ui.unit.offset
|
||||||
|
import androidx.navigation.NavDestination
|
||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun PermanentNavigationDrawerContent(
|
fun PermanentNavigationDrawerContent(
|
||||||
selectedDestination: MainDestination,
|
selectedDestination: String?,
|
||||||
|
navigateToDestination: (MainDestination) -> Unit,
|
||||||
navigationContentPosition: NavigationContentPosition,
|
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),
|
||||||
@@ -97,7 +98,7 @@ fun PermanentNavigationDrawerContent(
|
|||||||
contentDescription = stringResource(destination.textId)
|
contentDescription = stringResource(destination.textId)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
selected = selectedDestination.route == destination.route,
|
selected = selectedDestination == destination.route,
|
||||||
colors = NavigationDrawerItemDefaults.colors(
|
colors = NavigationDrawerItemDefaults.colors(
|
||||||
unselectedContainerColor = Color.Transparent
|
unselectedContainerColor = Color.Transparent
|
||||||
),
|
),
|
||||||
@@ -113,7 +114,7 @@ fun PermanentNavigationDrawerContent(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ModalNavigationDrawerContent(
|
fun ModalNavigationDrawerContent(
|
||||||
selectedDestination: MainDestination,
|
selectedDestination: String?,
|
||||||
navigationContentPosition: NavigationContentPosition,
|
navigationContentPosition: NavigationContentPosition,
|
||||||
navigateToDestination: (MainDestination) -> Unit,
|
navigateToDestination: (MainDestination) -> Unit,
|
||||||
onDrawerClicked: () -> Unit
|
onDrawerClicked: () -> Unit
|
||||||
@@ -177,7 +178,7 @@ fun ModalNavigationDrawerContent(
|
|||||||
contentDescription = stringResource(destination.textId)
|
contentDescription = stringResource(destination.textId)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
selected = selectedDestination.route == destination.route,
|
selected = selectedDestination == destination.route,
|
||||||
colors = NavigationDrawerItemDefaults.colors(
|
colors = NavigationDrawerItemDefaults.colors(
|
||||||
unselectedContainerColor = Color.Transparent
|
unselectedContainerColor = Color.Transparent
|
||||||
),
|
),
|
||||||
@@ -193,8 +194,7 @@ fun ModalNavigationDrawerContent(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MainNavigationRail(
|
fun MainNavigationRail(
|
||||||
selectedDestination: MainDestination,
|
selectedDestination: String?,
|
||||||
navigationContentPosition: NavigationContentPosition,
|
|
||||||
navigateToDestination: (MainDestination) -> Unit,
|
navigateToDestination: (MainDestination) -> Unit,
|
||||||
onDrawerClicked: () -> Unit
|
onDrawerClicked: () -> Unit
|
||||||
) {
|
) {
|
||||||
@@ -220,7 +220,7 @@ fun MainNavigationRail(
|
|||||||
) {
|
) {
|
||||||
mainDestinations.forEach { destination ->
|
mainDestinations.forEach { destination ->
|
||||||
NavigationRailItem(
|
NavigationRailItem(
|
||||||
selected = selectedDestination.route == destination.route,
|
selected = selectedDestination == destination.route,
|
||||||
onClick = { navigateToDestination(destination) },
|
onClick = { navigateToDestination(destination) },
|
||||||
icon = {
|
icon = {
|
||||||
Icon(
|
Icon(
|
||||||
@@ -236,13 +236,13 @@ fun MainNavigationRail(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BottomNavigationBar(
|
fun BottomNavigationBar(
|
||||||
selectedDestination: MainDestination,
|
selectedDestination: String?,
|
||||||
navigateToDestination: (MainDestination) -> Unit
|
navigateToDestination: (MainDestination) -> Unit
|
||||||
) {
|
) {
|
||||||
NavigationBar(modifier = Modifier.fillMaxWidth(), windowInsets = WindowInsets.ime.union(WindowInsets.navigationBars)) {
|
NavigationBar(modifier = Modifier.fillMaxWidth(), windowInsets = WindowInsets.ime.union(WindowInsets.navigationBars)) {
|
||||||
mainDestinations.forEach { destination ->
|
mainDestinations.forEach { destination ->
|
||||||
NavigationBarItem(
|
NavigationBarItem(
|
||||||
selected = selectedDestination.route == destination.route,
|
selected = selectedDestination == destination.route,
|
||||||
onClick = { navigateToDestination(destination) },
|
onClick = { navigateToDestination(destination) },
|
||||||
icon = {
|
icon = {
|
||||||
Icon(
|
Icon(
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ import xyz.quaver.pupil.networking.SearchQuery
|
|||||||
import xyz.quaver.pupil.ui.theme.Blue600
|
import xyz.quaver.pupil.ui.theme.Blue600
|
||||||
import xyz.quaver.pupil.ui.theme.Pink600
|
import xyz.quaver.pupil.ui.theme.Pink600
|
||||||
import xyz.quaver.pupil.ui.theme.Yellow400
|
import xyz.quaver.pupil.ui.theme.Yellow400
|
||||||
import xyz.quaver.pupil.ui.viewmodel.MainUIState
|
import xyz.quaver.pupil.ui.viewmodel.SearchState
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
private val iconMap = mapOf(
|
private val iconMap = mapOf(
|
||||||
@@ -304,7 +304,6 @@ fun SearchBar(
|
|||||||
query: SearchQuery?,
|
query: SearchQuery?,
|
||||||
onQueryChange: (SearchQuery?) -> Unit,
|
onQueryChange: (SearchQuery?) -> Unit,
|
||||||
onSearchBarPositioned: (Int) -> Unit,
|
onSearchBarPositioned: (Int) -> Unit,
|
||||||
onSearch: () -> Unit,
|
|
||||||
topOffset: Int,
|
topOffset: Int,
|
||||||
onTopOffsetChange: (Int) -> Unit,
|
onTopOffsetChange: (Int) -> Unit,
|
||||||
content: @Composable () -> Unit
|
content: @Composable () -> Unit
|
||||||
@@ -404,85 +403,6 @@ fun SearchBar(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun MainScreen(
|
|
||||||
contentType: ContentType,
|
|
||||||
displayFeatures: List<DisplayFeature>,
|
|
||||||
uiState: MainUIState,
|
|
||||||
closeDetailScreen: () -> Unit,
|
|
||||||
onQueryChange: (SearchQuery?) -> Unit,
|
|
||||||
loadSearchResult: (IntRange) -> Unit
|
|
||||||
) {
|
|
||||||
LaunchedEffect(contentType) {
|
|
||||||
if (contentType == ContentType.SINGLE_PANE && !uiState.isDetailOnlyOpen) {
|
|
||||||
closeDetailScreen()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val galleryLazyListState = rememberLazyListState()
|
|
||||||
|
|
||||||
val itemsPerPage by remember { mutableIntStateOf(20) }
|
|
||||||
|
|
||||||
val pageToRange: (Int) -> IntRange = remember(itemsPerPage) {{ page ->
|
|
||||||
page * itemsPerPage ..< (page+1) * itemsPerPage
|
|
||||||
}}
|
|
||||||
|
|
||||||
val currentPage = remember(uiState) {
|
|
||||||
if (uiState.currentRange != IntRange.EMPTY) {
|
|
||||||
uiState.currentRange.first / itemsPerPage
|
|
||||||
} else 0
|
|
||||||
}
|
|
||||||
|
|
||||||
val maxPage = remember(itemsPerPage, uiState) {
|
|
||||||
if (uiState.galleryCount != null) {
|
|
||||||
uiState.galleryCount / itemsPerPage + if (uiState.galleryCount % itemsPerPage != 0) 1 else 0
|
|
||||||
} else 0
|
|
||||||
}
|
|
||||||
|
|
||||||
val loadResult: (Int) -> Unit = remember(loadSearchResult) {{ page ->
|
|
||||||
loadSearchResult(pageToRange(page))
|
|
||||||
}}
|
|
||||||
|
|
||||||
LaunchedEffect(uiState.query, uiState.currentDestination) { loadSearchResult(pageToRange(0)) }
|
|
||||||
|
|
||||||
if (contentType == ContentType.DUAL_PANE) {
|
|
||||||
TwoPane(
|
|
||||||
first = {
|
|
||||||
GalleryList(
|
|
||||||
contentType = contentType,
|
|
||||||
galleries = uiState.galleries,
|
|
||||||
query = uiState.query,
|
|
||||||
currentPage = currentPage,
|
|
||||||
maxPage = maxPage,
|
|
||||||
loading = uiState.loading,
|
|
||||||
error = uiState.error,
|
|
||||||
galleryLazyListState = galleryLazyListState,
|
|
||||||
onQueryChange = onQueryChange,
|
|
||||||
onPageChange = loadResult
|
|
||||||
)
|
|
||||||
},
|
|
||||||
second = {
|
|
||||||
|
|
||||||
},
|
|
||||||
strategy = HorizontalTwoPaneStrategy(splitFraction = 0.5f, gapWidth = 16.dp),
|
|
||||||
displayFeatures = displayFeatures
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
GalleryList(
|
|
||||||
contentType = contentType,
|
|
||||||
galleries = uiState.galleries,
|
|
||||||
query = uiState.query,
|
|
||||||
currentPage = currentPage,
|
|
||||||
maxPage = maxPage,
|
|
||||||
loading = uiState.loading,
|
|
||||||
error = uiState.error,
|
|
||||||
galleryLazyListState = galleryLazyListState,
|
|
||||||
onQueryChange = onQueryChange,
|
|
||||||
onPageChange = loadResult
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun GalleryList(
|
fun GalleryList(
|
||||||
contentType: ContentType,
|
contentType: ContentType,
|
||||||
@@ -492,14 +412,8 @@ fun GalleryList(
|
|||||||
maxPage: Int,
|
maxPage: Int,
|
||||||
loading: Boolean = false,
|
loading: Boolean = false,
|
||||||
error: Boolean = false,
|
error: Boolean = false,
|
||||||
openedGallery: GalleryInfo? = null,
|
|
||||||
onPageChange: (Int) -> Unit,
|
onPageChange: (Int) -> Unit,
|
||||||
onQueryChange: (SearchQuery?) -> Unit = {},
|
onQueryChange: (SearchQuery?) -> Unit = {},
|
||||||
search: () -> Unit = {},
|
|
||||||
selectedGalleryIds: Set<Int> = emptySet(),
|
|
||||||
toggleGallerySelection: (Int) -> Unit = {},
|
|
||||||
galleryLazyListState: LazyListState,
|
|
||||||
navigateToDetails: (GalleryInfo, ContentType) -> Unit = { gi, ct -> }
|
|
||||||
) {
|
) {
|
||||||
val listState = rememberLazyListState()
|
val listState = rememberLazyListState()
|
||||||
var topOffset by remember { mutableIntStateOf(0) }
|
var topOffset by remember { mutableIntStateOf(0) }
|
||||||
@@ -522,7 +436,6 @@ fun GalleryList(
|
|||||||
query = query,
|
query = query,
|
||||||
onQueryChange = onQueryChange,
|
onQueryChange = onQueryChange,
|
||||||
onSearchBarPositioned = { searchBarPosition = it },
|
onSearchBarPositioned = { searchBarPosition = it },
|
||||||
onSearch = search,
|
|
||||||
topOffset = topOffset,
|
topOffset = topOffset,
|
||||||
onTopOffsetChange = { topOffset = it },
|
onTopOffsetChange = { topOffset = it },
|
||||||
) {
|
) {
|
||||||
@@ -563,7 +476,7 @@ fun GalleryList(
|
|||||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
state = listState
|
state = listState
|
||||||
) {
|
) {
|
||||||
items(galleries) {galleryInfo ->
|
items(galleries, key = { it.id }) {galleryInfo ->
|
||||||
DetailedGalleryInfo(
|
DetailedGalleryInfo(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
@@ -576,3 +489,79 @@ fun GalleryList(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SearchScreen(
|
||||||
|
contentType: ContentType,
|
||||||
|
displayFeatures: List<DisplayFeature>,
|
||||||
|
uiState: SearchState,
|
||||||
|
closeDetailScreen: () -> Unit,
|
||||||
|
onQueryChange: (SearchQuery?) -> Unit,
|
||||||
|
loadSearchResult: (IntRange) -> Unit
|
||||||
|
) {
|
||||||
|
LaunchedEffect(contentType) {
|
||||||
|
if (contentType == ContentType.SINGLE_PANE && !uiState.isDetailOnlyOpen) {
|
||||||
|
closeDetailScreen()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val itemsPerPage by remember { mutableIntStateOf(20) }
|
||||||
|
|
||||||
|
val pageToRange: (Int) -> IntRange = remember(itemsPerPage) {{ page ->
|
||||||
|
page * itemsPerPage ..< (page+1) * itemsPerPage
|
||||||
|
}}
|
||||||
|
|
||||||
|
val currentPage = remember(uiState) {
|
||||||
|
if (uiState.currentRange != IntRange.EMPTY) {
|
||||||
|
uiState.currentRange.first / itemsPerPage
|
||||||
|
} else 0
|
||||||
|
}
|
||||||
|
|
||||||
|
val maxPage = remember(itemsPerPage, uiState) {
|
||||||
|
if (uiState.galleryCount != null) {
|
||||||
|
uiState.galleryCount / itemsPerPage + if (uiState.galleryCount % itemsPerPage != 0) 1 else 0
|
||||||
|
} else 0
|
||||||
|
}
|
||||||
|
|
||||||
|
val loadResult: (Int) -> Unit = remember(loadSearchResult) {{ page ->
|
||||||
|
loadSearchResult(pageToRange(page))
|
||||||
|
}}
|
||||||
|
|
||||||
|
LaunchedEffect(uiState.query, currentPage) { loadSearchResult(pageToRange(currentPage)) }
|
||||||
|
|
||||||
|
if (contentType == ContentType.DUAL_PANE) {
|
||||||
|
TwoPane(
|
||||||
|
first = {
|
||||||
|
GalleryList(
|
||||||
|
contentType = contentType,
|
||||||
|
galleries = uiState.galleries,
|
||||||
|
query = uiState.query,
|
||||||
|
currentPage = currentPage,
|
||||||
|
maxPage = maxPage,
|
||||||
|
loading = uiState.loading,
|
||||||
|
error = uiState.error,
|
||||||
|
onQueryChange = onQueryChange,
|
||||||
|
onPageChange = loadResult
|
||||||
|
)
|
||||||
|
},
|
||||||
|
second = {
|
||||||
|
|
||||||
|
},
|
||||||
|
strategy = HorizontalTwoPaneStrategy(splitFraction = 0.5f, gapWidth = 16.dp),
|
||||||
|
displayFeatures = displayFeatures
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
GalleryList(
|
||||||
|
contentType = contentType,
|
||||||
|
galleries = uiState.galleries,
|
||||||
|
query = uiState.query,
|
||||||
|
currentPage = currentPage,
|
||||||
|
maxPage = maxPage,
|
||||||
|
loading = uiState.loading,
|
||||||
|
error = uiState.error,
|
||||||
|
onQueryChange = onQueryChange,
|
||||||
|
onPageChange = loadResult
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -9,23 +9,15 @@ import kotlinx.coroutines.launch
|
|||||||
import xyz.quaver.pupil.networking.GalleryInfo
|
import xyz.quaver.pupil.networking.GalleryInfo
|
||||||
import xyz.quaver.pupil.networking.GallerySearchSource
|
import xyz.quaver.pupil.networking.GallerySearchSource
|
||||||
import xyz.quaver.pupil.networking.SearchQuery
|
import xyz.quaver.pupil.networking.SearchQuery
|
||||||
import xyz.quaver.pupil.ui.composable.MainDestination
|
|
||||||
import xyz.quaver.pupil.ui.composable.mainDestinations
|
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
class MainViewModel : ViewModel() {
|
class MainViewModel : ViewModel() {
|
||||||
private val _uiState = MutableStateFlow(MainUIState())
|
private val _uiState = MutableStateFlow(SearchState())
|
||||||
val uiState: StateFlow<MainUIState> = _uiState
|
val searchState: StateFlow<SearchState> = _uiState
|
||||||
private var searchSource: GallerySearchSource = GallerySearchSource(null)
|
private var searchSource: GallerySearchSource = GallerySearchSource(null)
|
||||||
private var job: Job? = null
|
private var job: Job? = null
|
||||||
|
|
||||||
fun navigateToDestination(destination: MainDestination) {
|
|
||||||
_uiState.value = MainUIState(
|
|
||||||
currentDestination = destination
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun closeDetailScreen() {
|
fun closeDetailScreen() {
|
||||||
_uiState.value = _uiState.value.copy(
|
_uiState.value = _uiState.value.copy(
|
||||||
isDetailOnlyOpen = false
|
isDetailOnlyOpen = false
|
||||||
@@ -45,7 +37,7 @@ class MainViewModel : ViewModel() {
|
|||||||
fun loadSearchResult(range: IntRange) {
|
fun loadSearchResult(range: IntRange) {
|
||||||
job?.cancel()
|
job?.cancel()
|
||||||
job = viewModelScope.launch {
|
job = viewModelScope.launch {
|
||||||
val sanitizedRange = max(range.first, 0) .. min(range.last, uiState.value.galleryCount ?: Int.MAX_VALUE)
|
val sanitizedRange = max(range.first, 0) .. min(range.last, searchState.value.galleryCount ?: Int.MAX_VALUE)
|
||||||
_uiState.value = _uiState.value.copy(
|
_uiState.value = _uiState.value.copy(
|
||||||
loading = true,
|
loading = true,
|
||||||
currentRange = sanitizedRange
|
currentRange = sanitizedRange
|
||||||
@@ -72,8 +64,7 @@ class MainViewModel : ViewModel() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class MainUIState(
|
data class SearchState(
|
||||||
val currentDestination: MainDestination = mainDestinations.first(),
|
|
||||||
val query: SearchQuery? = null,
|
val query: SearchQuery? = null,
|
||||||
val galleries: List<GalleryInfo> = emptyList(),
|
val galleries: List<GalleryInfo> = emptyList(),
|
||||||
val loading: Boolean = false,
|
val loading: Boolean = false,
|
||||||
|
|||||||
@@ -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_destination_settings">設定</string>
|
||||||
<string name="main_open_navigation_drawer">メニューを開く</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>
|
||||||
@@ -166,4 +167,6 @@
|
|||||||
<string name="search_add_query_item_tag">タグ</string>
|
<string name="search_add_query_item_tag">タグ</string>
|
||||||
<string name="move_to_page">%1$d ページへ移動</string>
|
<string name="move_to_page">%1$d ページへ移動</string>
|
||||||
<string name="search_bar_edit_tag">タッチして編集</string>
|
<string name="search_bar_edit_tag">タッチして編集</string>
|
||||||
|
<string name="main_destination_image_viewer">イメージビューア</string>
|
||||||
|
<string name="not_implemented">この機能はまだ実装されていません</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -36,6 +36,8 @@
|
|||||||
<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_destination_settings">설정</string>
|
||||||
|
<string name="main_destination_image_viewer">뷰어</string>
|
||||||
<string name="main_open_navigation_drawer">메뉴 열기</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>
|
||||||
@@ -166,4 +168,5 @@
|
|||||||
<string name="search_add_query_item_tag">태그</string>
|
<string name="search_add_query_item_tag">태그</string>
|
||||||
<string name="move_to_page">%1$d 페이지로 이동</string>
|
<string name="move_to_page">%1$d 페이지로 이동</string>
|
||||||
<string name="search_bar_edit_tag">터치하여 수정</string>
|
<string name="search_bar_edit_tag">터치하여 수정</string>
|
||||||
|
<string name="not_implemented">이 기능은 아직 개발 중입니다</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -32,6 +32,8 @@
|
|||||||
|
|
||||||
<string name="unlimited">Unlimited</string>
|
<string name="unlimited">Unlimited</string>
|
||||||
|
|
||||||
|
<string name="not_implemented">This feature is not implemented yet</string>
|
||||||
|
|
||||||
<string name="copied_to_clipboard">Copied to clipboard</string>
|
<string name="copied_to_clipboard">Copied to clipboard</string>
|
||||||
|
|
||||||
<string name="channel_download">Download</string>
|
<string name="channel_download">Download</string>
|
||||||
@@ -57,6 +59,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_destination_settings">Settings</string>
|
||||||
|
<string name="main_destination_image_viewer">Reader</string>
|
||||||
<string name="main_open_navigation_drawer">Open Navigation Drawer</string>
|
<string name="main_open_navigation_drawer">Open Navigation Drawer</string>
|
||||||
<string name="main_close_navigation_drawer">Close 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>
|
||||||
|
|||||||
Reference in New Issue
Block a user