Basic Navigation

This commit is contained in:
tom5079
2024-03-27 00:21:16 -07:00
parent e8ba5c4881
commit 62948abf75
11 changed files with 236 additions and 155 deletions

View File

@@ -31,4 +31,5 @@
-keepclasseswithmembers class xyz.quaver.pupil.** { # <-- change package name to your app's
kotlinx.serialization.KSerializer serializer(...);
}
-keep class xyz.quaver.pupil.** { *; }
-keep class xyz.quaver.pupil.** { *; }
-dontwarn org.slf4j.impl.StaticLoggerBinder

View File

@@ -12,7 +12,7 @@
"filters": [],
"attributes": [],
"versionCode": 69,
"versionName": "5.3.10",
"versionName": "6.0.0",
"outputFile": "app-release.apk"
}
],

View File

@@ -27,10 +27,10 @@ import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass
import androidx.compose.runtime.getValue
import androidx.core.view.WindowCompat
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.compose.rememberNavController
import com.google.accompanist.adaptive.calculateDisplayFeatures
import xyz.quaver.pupil.ui.composable.MainApp
import xyz.quaver.pupil.ui.theme.AppTheme
import xyz.quaver.pupil.ui.viewmodel.MainUIState
import xyz.quaver.pupil.ui.viewmodel.MainViewModel
class MainActivity : BaseActivity() {
@@ -48,13 +48,15 @@ class MainActivity : BaseActivity() {
val windowSize = calculateWindowSizeClass(this)
val displayFeatures = calculateDisplayFeatures(this)
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
val uiState by viewModel.searchState.collectAsStateWithLifecycle()
val navController = rememberNavController()
MainApp(
windowSize = windowSize,
displayFeatures = displayFeatures,
uiState = uiState,
navigateToDestination = viewModel::navigateToDestination,
navController = navController,
closeDetailScreen = viewModel::closeDetailScreen,
onQueryChange = viewModel::onQueryChange,
loadSearchResult = viewModel::loadSearchResult

View File

@@ -1,7 +1,11 @@
package xyz.quaver.pupil.ui.composable
import android.util.Log
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
@@ -14,25 +18,39 @@ import androidx.compose.material3.DrawerValue
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalNavigationDrawer
import androidx.compose.material3.PermanentNavigationDrawer
import androidx.compose.material3.Text
import androidx.compose.material3.rememberDrawerState
import androidx.compose.material3.windowsizeclass.WindowHeightSizeClass
import androidx.compose.material3.windowsizeclass.WindowSizeClass
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
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.FoldingFeature
import kotlinx.coroutines.launch
import xyz.quaver.pupil.R
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
fun MainApp(
windowSize: WindowSizeClass,
displayFeatures: List<DisplayFeature>,
uiState: MainUIState,
navigateToDestination: (MainDestination) -> Unit,
uiState: SearchState,
navController: NavHostController,
closeDetailScreen: () -> Unit,
onQueryChange: (SearchQuery?) -> Unit,
loadSearchResult: (IntRange) -> Unit
@@ -87,7 +105,7 @@ fun MainApp(
displayFeatures,
navigationContentPosition,
uiState,
navigateToDestination,
navController,
closeDetailScreen = closeDetailScreen,
onQueryChange = onQueryChange,
loadSearchResult = loadSearchResult
@@ -100,8 +118,8 @@ private fun MainNavigationWrapper(
contentType: ContentType,
displayFeatures: List<DisplayFeature>,
navigationContentPosition: NavigationContentPosition,
uiState: MainUIState,
navigateToDestination: (MainDestination) -> Unit,
uiState: SearchState,
navController: NavHostController,
closeDetailScreen: () -> Unit,
onQueryChange: (SearchQuery?) -> Unit,
loadSearchResult: (IntRange) -> Unit
@@ -109,6 +127,9 @@ private fun MainNavigationWrapper(
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
val coroutineScope = rememberCoroutineScope()
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route
val openDrawer: () -> Unit = {
coroutineScope.launch {
drawerState.open()
@@ -119,9 +140,12 @@ private fun MainNavigationWrapper(
PermanentNavigationDrawer(
drawerContent = {
PermanentNavigationDrawerContent(
selectedDestination = uiState.currentDestination,
selectedDestination = currentRoute,
navigateToDestination = { navController.navigate(it.route) {
popUpTo(MainDestination.Search.route)
launchSingleTop = true
} },
navigationContentPosition = navigationContentPosition,
navigateToDestination = navigateToDestination
)
}
) {
@@ -129,9 +153,8 @@ private fun MainNavigationWrapper(
navigationType = navigationType,
contentType = contentType,
displayFeatures = displayFeatures,
navigationContentPosition = navigationContentPosition,
uiState = uiState,
navigateToDestination = navigateToDestination,
navController = navController,
onDrawerClicked = openDrawer,
closeDetailScreen = closeDetailScreen,
onQueryChange = onQueryChange,
@@ -142,9 +165,12 @@ private fun MainNavigationWrapper(
ModalNavigationDrawer(
drawerContent = {
ModalNavigationDrawerContent(
selectedDestination = uiState.currentDestination,
selectedDestination = currentRoute,
navigateToDestination = { navController.navigate(it.route) {
popUpTo(MainDestination.Search.route)
launchSingleTop = true
} },
navigationContentPosition = navigationContentPosition,
navigateToDestination = navigateToDestination,
onDrawerClicked = {
coroutineScope.launch {
drawerState.close()
@@ -158,9 +184,8 @@ private fun MainNavigationWrapper(
navigationType = navigationType,
contentType = contentType,
displayFeatures = displayFeatures,
navigationContentPosition = navigationContentPosition,
uiState = uiState,
navigateToDestination = navigateToDestination,
navController = navController,
onDrawerClicked = openDrawer,
closeDetailScreen = closeDetailScreen,
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
fun MainContent(
navigationType: NavigationType,
contentType: ContentType,
displayFeatures: List<DisplayFeature>,
navigationContentPosition: NavigationContentPosition,
uiState: MainUIState,
navigateToDestination: (MainDestination) -> Unit,
uiState: SearchState,
navController: NavHostController,
onDrawerClicked: () -> Unit,
closeDetailScreen: () -> Unit,
onQueryChange: (SearchQuery?) -> Unit,
loadSearchResult: (IntRange) -> Unit
) {
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route
Row(modifier = Modifier.fillMaxSize()) {
AnimatedVisibility(visible = navigationType == NavigationType.NAVIGATION_RAIL) {
MainNavigationRail(
selectedDestination = uiState.currentDestination,
navigationContentPosition = navigationContentPosition,
navigateToDestination = navigateToDestination,
selectedDestination = currentRoute,
navigateToDestination = { navController.navigate(it.route) {
popUpTo(MainDestination.Search.route)
launchSingleTop = true
} },
onDrawerClicked = onDrawerClicked
)
}
@@ -198,27 +241,55 @@ fun MainContent(
.background(MaterialTheme.colorScheme.inverseOnSurface)
) {
Box(
modifier = Modifier.weight(1f)
modifier = Modifier
.weight(1f)
.run {
if (navigationType == NavigationType.BOTTOM_NAVIGATION) {
this.consumeWindowInsets(WindowInsets.ime)
this
.consumeWindowInsets(WindowInsets.ime)
.consumeWindowInsets(WindowInsets.navigationBars)
} else this
}
) {
MainScreen(
contentType = contentType,
displayFeatures = displayFeatures,
uiState = uiState,
closeDetailScreen = closeDetailScreen,
onQueryChange = onQueryChange,
loadSearchResult = loadSearchResult
)
NavHost(
modifier = Modifier.fillMaxSize(),
navController = navController,
startDestination = MainDestination.Search.route
) {
composable(MainDestination.Search.route) {
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) {
BottomNavigationBar(
selectedDestination = uiState.currentDestination,
navigateToDestination = navigateToDestination
selectedDestination = currentRoute,
navigateToDestination = { navController.navigate(it.route) {
popUpTo(MainDestination.Search.route)
launchSingleTop = true
} }
)
}
}

View File

@@ -1,10 +1,13 @@
package xyz.quaver.pupil.ui.composable
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.Favorite
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.Settings
import androidx.compose.material.icons.filled.Star
import androidx.compose.ui.graphics.vector.ImageVector
import xyz.quaver.pupil.R
@@ -14,34 +17,48 @@ sealed interface MainDestination {
val icon: ImageVector
val textId: Int
object Search: MainDestination {
data object Search: MainDestination {
override val route = "search"
override val icon = Icons.Default.Search
override val textId = R.string.main_destination_search
}
object History: MainDestination {
data object History: MainDestination {
override val route = "history"
override val icon = Icons.Default.History
override val textId = R.string.main_destination_history
}
object Downloads: MainDestination {
data object Downloads: MainDestination {
override val route = "downloads"
override val icon = Icons.Default.Download
override val textId = R.string.main_destination_downloads
}
object Favorites: MainDestination {
data object Favorites: MainDestination {
override val route = "favorites"
override val icon = Icons.Default.Favorite
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(
MainDestination.Search,
MainDestination.History,
MainDestination.Downloads,
MainDestination.Favorites
MainDestination.Favorites,
MainDestination.Settings
)

View File

@@ -43,13 +43,14 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.offset
import androidx.navigation.NavDestination
import xyz.quaver.pupil.R
@Composable
fun PermanentNavigationDrawerContent(
selectedDestination: MainDestination,
selectedDestination: String?,
navigateToDestination: (MainDestination) -> Unit,
navigationContentPosition: NavigationContentPosition,
navigateToDestination: (MainDestination) -> Unit
) {
PermanentDrawerSheet(
modifier = Modifier.sizeIn(minWidth = 200.dp, maxWidth = 300.dp),
@@ -97,7 +98,7 @@ fun PermanentNavigationDrawerContent(
contentDescription = stringResource(destination.textId)
)
},
selected = selectedDestination.route == destination.route,
selected = selectedDestination == destination.route,
colors = NavigationDrawerItemDefaults.colors(
unselectedContainerColor = Color.Transparent
),
@@ -113,7 +114,7 @@ fun PermanentNavigationDrawerContent(
@Composable
fun ModalNavigationDrawerContent(
selectedDestination: MainDestination,
selectedDestination: String?,
navigationContentPosition: NavigationContentPosition,
navigateToDestination: (MainDestination) -> Unit,
onDrawerClicked: () -> Unit
@@ -177,7 +178,7 @@ fun ModalNavigationDrawerContent(
contentDescription = stringResource(destination.textId)
)
},
selected = selectedDestination.route == destination.route,
selected = selectedDestination == destination.route,
colors = NavigationDrawerItemDefaults.colors(
unselectedContainerColor = Color.Transparent
),
@@ -193,8 +194,7 @@ fun ModalNavigationDrawerContent(
@Composable
fun MainNavigationRail(
selectedDestination: MainDestination,
navigationContentPosition: NavigationContentPosition,
selectedDestination: String?,
navigateToDestination: (MainDestination) -> Unit,
onDrawerClicked: () -> Unit
) {
@@ -220,7 +220,7 @@ fun MainNavigationRail(
) {
mainDestinations.forEach { destination ->
NavigationRailItem(
selected = selectedDestination.route == destination.route,
selected = selectedDestination == destination.route,
onClick = { navigateToDestination(destination) },
icon = {
Icon(
@@ -236,13 +236,13 @@ fun MainNavigationRail(
@Composable
fun BottomNavigationBar(
selectedDestination: MainDestination,
selectedDestination: String?,
navigateToDestination: (MainDestination) -> Unit
) {
NavigationBar(modifier = Modifier.fillMaxWidth(), windowInsets = WindowInsets.ime.union(WindowInsets.navigationBars)) {
mainDestinations.forEach { destination ->
NavigationBarItem(
selected = selectedDestination.route == destination.route,
selected = selectedDestination == destination.route,
onClick = { navigateToDestination(destination) },
icon = {
Icon(

View File

@@ -96,7 +96,7 @@ import xyz.quaver.pupil.networking.SearchQuery
import xyz.quaver.pupil.ui.theme.Blue600
import xyz.quaver.pupil.ui.theme.Pink600
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
private val iconMap = mapOf(
@@ -304,7 +304,6 @@ fun SearchBar(
query: SearchQuery?,
onQueryChange: (SearchQuery?) -> Unit,
onSearchBarPositioned: (Int) -> Unit,
onSearch: () -> Unit,
topOffset: Int,
onTopOffsetChange: (Int) -> 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
fun GalleryList(
contentType: ContentType,
@@ -492,14 +412,8 @@ fun GalleryList(
maxPage: Int,
loading: Boolean = false,
error: Boolean = false,
openedGallery: GalleryInfo? = null,
onPageChange: (Int) -> Unit,
onQueryChange: (SearchQuery?) -> Unit = {},
search: () -> Unit = {},
selectedGalleryIds: Set<Int> = emptySet(),
toggleGallerySelection: (Int) -> Unit = {},
galleryLazyListState: LazyListState,
navigateToDetails: (GalleryInfo, ContentType) -> Unit = { gi, ct -> }
) {
val listState = rememberLazyListState()
var topOffset by remember { mutableIntStateOf(0) }
@@ -522,7 +436,6 @@ fun GalleryList(
query = query,
onQueryChange = onQueryChange,
onSearchBarPositioned = { searchBarPosition = it },
onSearch = search,
topOffset = topOffset,
onTopOffsetChange = { topOffset = it },
) {
@@ -563,7 +476,7 @@ fun GalleryList(
verticalArrangement = Arrangement.spacedBy(8.dp),
state = listState
) {
items(galleries) {galleryInfo ->
items(galleries, key = { it.id }) {galleryInfo ->
DetailedGalleryInfo(
modifier = Modifier
.fillMaxWidth()
@@ -575,4 +488,80 @@ 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
)
}
}

View File

@@ -9,23 +9,15 @@ import kotlinx.coroutines.launch
import xyz.quaver.pupil.networking.GalleryInfo
import xyz.quaver.pupil.networking.GallerySearchSource
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.min
class MainViewModel : ViewModel() {
private val _uiState = MutableStateFlow(MainUIState())
val uiState: StateFlow<MainUIState> = _uiState
private val _uiState = MutableStateFlow(SearchState())
val searchState: StateFlow<SearchState> = _uiState
private var searchSource: GallerySearchSource = GallerySearchSource(null)
private var job: Job? = null
fun navigateToDestination(destination: MainDestination) {
_uiState.value = MainUIState(
currentDestination = destination
)
}
fun closeDetailScreen() {
_uiState.value = _uiState.value.copy(
isDetailOnlyOpen = false
@@ -45,7 +37,7 @@ class MainViewModel : ViewModel() {
fun loadSearchResult(range: IntRange) {
job?.cancel()
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(
loading = true,
currentRange = sanitizedRange
@@ -72,8 +64,7 @@ class MainViewModel : ViewModel() {
}
}
data class MainUIState(
val currentDestination: MainDestination = mainDestinations.first(),
data class SearchState(
val query: SearchQuery? = null,
val galleries: List<GalleryInfo> = emptyList(),
val loading: Boolean = false,

View File

@@ -33,6 +33,7 @@
<string name="default_query_dialog_filter_guro">グロフィルター</string>
<string name="default_query_dialog_language">"言語: "</string>
<string name="default_query_dialog_title">デフォルトキーワード設定</string>
<string name="main_destination_settings">設定</string>
<string name="main_open_navigation_drawer">メニューを開く</string>
<string name="main_drawer_group_contact_title">お問い合わせ先</string>
<string name="main_drawer_group_contact_homepage">ホームページ</string>
@@ -166,4 +167,6 @@
<string name="search_add_query_item_tag">タグ</string>
<string name="move_to_page">%1$d ページへ移動</string>
<string name="search_bar_edit_tag">タッチして編集</string>
<string name="main_destination_image_viewer">イメージビューア</string>
<string name="not_implemented">この機能はまだ実装されていません</string>
</resources>

View File

@@ -36,6 +36,8 @@
<string name="main_drawer_group_contact_github">Github</string>
<string name="main_drawer_group_contact_help">도움말</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_drawer_group_contact_title">문의</string>
<string name="reader_fab_fullscreen">전체 화면</string>
@@ -166,4 +168,5 @@
<string name="search_add_query_item_tag">태그</string>
<string name="move_to_page">%1$d 페이지로 이동</string>
<string name="search_bar_edit_tag">터치하여 수정</string>
<string name="not_implemented">이 기능은 아직 개발 중입니다</string>
</resources>

View File

@@ -32,6 +32,8 @@
<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="channel_download">Download</string>
@@ -57,6 +59,8 @@
<string name="main_destination_history">History</string>
<string name="main_destination_downloads">Downloads</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_close_navigation_drawer">Close Navigation Drawer</string>
<string name="main_drawer_group_contact_title">Contact</string>