Minor tweaks to download custom dialog

- Allow large decrements (just goes to 0)
- Use Material3 text field for proper theming
- Move dialog composable to presentation package
This commit is contained in:
arkon 2022-08-26 09:16:26 -04:00
parent 2453d1a886
commit 03b9950fa1
19 changed files with 189 additions and 180 deletions

View file

@ -9,7 +9,7 @@ import eu.kanade.tachiyomi.R
val Category.visualName: String
@Composable
get() = when {
isSystemCategory -> stringResource(id = R.string.label_default)
isSystemCategory -> stringResource(R.string.label_default)
else -> name
}

View file

@ -110,7 +110,7 @@ fun AppBar(
IconButton(onClick = onCancelActionMode) {
Icon(
imageVector = Icons.Default.Close,
contentDescription = stringResource(id = R.string.action_cancel),
contentDescription = stringResource(R.string.action_cancel),
)
}
} else {

View file

@ -39,14 +39,14 @@ fun ChangeCategoryDialog(
onEditCategories()
},
) {
Text(text = stringResource(id = R.string.action_edit_categories))
Text(text = stringResource(R.string.action_edit_categories))
}
},
title = {
Text(text = stringResource(id = R.string.action_move_category))
Text(text = stringResource(R.string.action_move_category))
},
text = {
Text(text = stringResource(id = R.string.information_empty_category_dialog))
Text(text = stringResource(R.string.information_empty_category_dialog))
},
)
return
@ -60,11 +60,11 @@ fun ChangeCategoryDialog(
onDismissRequest()
onEditCategories()
},) {
Text(text = stringResource(id = R.string.action_edit))
Text(text = stringResource(R.string.action_edit))
}
Spacer(modifier = Modifier.weight(1f))
TextButton(onClick = onDismissRequest) {
Text(text = stringResource(id = android.R.string.cancel))
Text(text = stringResource(android.R.string.cancel))
}
TextButton(
onClick = {
@ -75,12 +75,12 @@ fun ChangeCategoryDialog(
)
},
) {
Text(text = stringResource(id = R.string.action_add))
Text(text = stringResource(R.string.action_add))
}
}
},
title = {
Text(text = stringResource(id = R.string.action_move_category))
Text(text = stringResource(R.string.action_move_category))
},
text = {
Column {

View file

@ -36,7 +36,7 @@ fun DeleteLibraryMangaDialog(
onDismissRequest = onDismissRequest,
dismissButton = {
TextButton(onClick = onDismissRequest) {
Text(text = stringResource(id = android.R.string.cancel))
Text(text = stringResource(android.R.string.cancel))
}
},
confirmButton = {
@ -49,11 +49,11 @@ fun DeleteLibraryMangaDialog(
)
},
) {
Text(text = stringResource(id = android.R.string.ok))
Text(text = stringResource(android.R.string.ok))
}
},
title = {
Text(text = stringResource(id = R.string.action_remove))
Text(text = stringResource(R.string.action_remove))
},
text = {
Column {
@ -69,7 +69,7 @@ fun DeleteLibraryMangaDialog(
list = mutableList.toList()
},
)
Text(text = stringResource(id = state.value))
Text(text = stringResource(state.value))
}
}
}

View file

@ -62,7 +62,7 @@ fun HistoryRegularToolbar(
scrollBehavior: TopAppBarScrollBehavior,
) {
AppBar(
title = stringResource(id = R.string.history),
title = stringResource(R.string.history),
actions = {
IconButton(onClick = onClickSearch) {
Icon(Icons.Outlined.Search, contentDescription = stringResource(R.string.action_search))
@ -105,7 +105,7 @@ fun HistorySearchToolbar(
actions = {
AnimatedVisibility(visible = searchQuery.isNotEmpty()) {
IconButton(onClick = onClickResetSearch) {
Icon(Icons.Outlined.Close, contentDescription = stringResource(id = R.string.action_reset))
Icon(Icons.Outlined.Close, contentDescription = stringResource(R.string.action_reset))
}
}
},

View file

@ -193,7 +193,7 @@ fun LibrarySearchToolbar(
actions = {
AnimatedVisibility(visible = searchQuery.isNotEmpty()) {
IconButton(onClick = onClickResetSearch) {
Icon(Icons.Outlined.Close, contentDescription = stringResource(id = R.string.action_reset))
Icon(Icons.Outlined.Close, contentDescription = stringResource(R.string.action_reset))
}
}
},

View file

@ -1,7 +1,6 @@
package eu.kanade.tachiyomi.ui.manga.chapter
package eu.kanade.presentation.manga.components
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.ChevronLeft
@ -11,6 +10,8 @@ import androidx.compose.material.icons.outlined.KeyboardDoubleArrowRight
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
@ -19,8 +20,10 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.style.TextAlign
import eu.kanade.tachiyomi.R
@Composable
@ -34,7 +37,7 @@ fun DownloadCustomAmountDialog(
onDismissRequest = onDismissRequest,
dismissButton = {
TextButton(onClick = onDismissRequest) {
Text(text = stringResource(id = android.R.string.cancel))
Text(text = stringResource(android.R.string.cancel))
}
},
confirmButton = {
@ -44,11 +47,11 @@ fun DownloadCustomAmountDialog(
onConfirm(amount.coerceIn(0, maxAmount))
},
) {
Text(text = stringResource(id = android.R.string.ok))
Text(text = stringResource(R.string.action_download))
}
},
title = {
Text(text = stringResource(id = R.string.custom_download))
Text(text = stringResource(R.string.custom_download))
},
text = {
val onChangeAmount: (Int) -> Unit = { amount = (amount + it).coerceIn(0, maxAmount) }
@ -57,7 +60,7 @@ fun DownloadCustomAmountDialog(
) {
IconButton(
onClick = { onChangeAmount(-10) },
enabled = amount > 10,
enabled = amount > 0,
) {
Icon(imageVector = Icons.Outlined.KeyboardDoubleArrowLeft, contentDescription = "")
}
@ -67,10 +70,12 @@ fun DownloadCustomAmountDialog(
) {
Icon(imageVector = Icons.Outlined.ChevronLeft, contentDescription = "")
}
BasicTextField(
OutlinedTextField(
modifier = Modifier.weight(1f),
value = amount.toString(),
onValueChange = { onChangeAmount(it.toIntOrNull() ?: 0) },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
textStyle = LocalTextStyle.current.copy(textAlign = TextAlign.Center),
)
IconButton(
onClick = { onChangeAmount(1) },

View file

@ -0,0 +1,57 @@
package eu.kanade.presentation.manga.components
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.Source
@Composable
fun DuplicateMangaDialog(
onDismissRequest: () -> Unit,
onConfirm: () -> Unit,
onOpenManga: () -> Unit,
duplicateFrom: Source,
) {
AlertDialog(
onDismissRequest = onDismissRequest,
confirmButton = {
Row {
TextButton(onClick = {
onDismissRequest()
onOpenManga()
},) {
Text(text = stringResource(R.string.action_show_manga))
}
Spacer(modifier = Modifier.weight(1f))
TextButton(onClick = onDismissRequest) {
Text(text = stringResource(android.R.string.cancel))
}
TextButton(
onClick = {
onDismissRequest()
onConfirm()
},
) {
Text(text = stringResource(R.string.action_add))
}
}
},
title = {
Text(text = stringResource(R.string.are_you_sure))
},
text = {
Text(
text = stringResource(
id = R.string.confirm_manga_add_duplicate,
duplicateFrom.name,
),
)
},
)
}

View file

@ -16,7 +16,7 @@ fun DeleteChaptersDialog(
onDismissRequest = onDismissRequest,
dismissButton = {
TextButton(onClick = onDismissRequest) {
Text(text = stringResource(id = android.R.string.cancel))
Text(text = stringResource(android.R.string.cancel))
}
},
confirmButton = {
@ -26,14 +26,14 @@ fun DeleteChaptersDialog(
onConfirm()
},
) {
Text(text = stringResource(id = android.R.string.ok))
Text(text = stringResource(android.R.string.ok))
}
},
title = {
Text(text = stringResource(id = R.string.are_you_sure))
Text(text = stringResource(R.string.are_you_sure))
},
text = {
Text(text = stringResource(id = R.string.confirm_delete_chapters))
Text(text = stringResource(R.string.confirm_delete_chapters))
},
)
}

View file

@ -35,7 +35,7 @@ fun ClearDatabaseContent(
)
}
}
false -> EmptyScreen(message = stringResource(id = R.string.database_clean))
false -> EmptyScreen(message = stringResource(R.string.database_clean))
}
}
}

View file

@ -16,16 +16,16 @@ fun ClearDatabaseDeleteDialog(
onDismissRequest = onDismissRequest,
confirmButton = {
TextButton(onClick = onDelete) {
Text(text = stringResource(id = android.R.string.ok))
Text(text = stringResource(android.R.string.ok))
}
},
dismissButton = {
TextButton(onClick = onDismissRequest) {
Text(text = stringResource(id = android.R.string.cancel))
Text(text = stringResource(android.R.string.cancel))
}
},
text = {
Text(text = stringResource(id = R.string.clear_database_confirmation))
Text(text = stringResource(R.string.clear_database_confirmation))
},
)
}

View file

@ -27,7 +27,7 @@ fun ClearDatabaseFloatingActionButton(
ExtendedFloatingActionButton(
modifier = Modifier.navigationBarsPadding(),
text = {
Text(text = stringResource(id = R.string.action_delete))
Text(text = stringResource(R.string.action_delete))
},
icon = {
Icon(Icons.Outlined.Delete, contentDescription = "")

View file

@ -43,7 +43,7 @@ fun ClearDatabaseItem(
text = source.visualName,
style = MaterialTheme.typography.bodyMedium,
)
Text(text = stringResource(id = R.string.clear_database_source_item_count, count))
Text(text = stringResource(R.string.clear_database_source_item_count, count))
}
Checkbox(
checked = isSelected,

View file

@ -18,19 +18,19 @@ fun ClearDatabaseToolbar(
onClickInvertSelection: () -> Unit,
) {
AppBar(
title = stringResource(id = R.string.pref_clear_database),
title = stringResource(R.string.pref_clear_database),
navigateUp = navigateUp,
actions = {
if (state.isEmpty.not()) {
AppBarActions(
actions = listOf(
AppBar.Action(
title = stringResource(id = R.string.action_select_all),
title = stringResource(R.string.action_select_all),
icon = Icons.Outlined.SelectAll,
onClick = onClickSelectAll,
),
AppBar.Action(
title = stringResource(id = R.string.action_select_all),
title = stringResource(R.string.action_select_all),
icon = Icons.Outlined.FlipToBack,
onClick = onClickInvertSelection,
),

View file

@ -92,7 +92,7 @@ class UpdatesGridGlanceWidget : GlanceAppWidget() {
contentAlignment = Alignment.Center,
) {
Text(
text = stringResource(id = R.string.appwidget_unavailable_locked),
text = stringResource(R.string.appwidget_unavailable_locked),
style = TextStyle(
color = ColorProvider(R.color.appwidget_on_secondary_container),
fontSize = 12.sp,
@ -114,7 +114,7 @@ class UpdatesGridGlanceWidget : GlanceAppWidget() {
if (inData == null) {
CircularProgressIndicator()
} else if (inData.isEmpty()) {
Text(text = stringResource(id = R.string.information_no_recent))
Text(text = stringResource(R.string.information_no_recent))
} else {
(0 until rowCount).forEach { i ->
val coverRow = (0 until columnCount).mapNotNull { j ->

View file

@ -641,7 +641,7 @@ class LibraryPresenter(
fun getToolbarTitle(): androidx.compose.runtime.State<LibraryToolbarTitle> {
val category = categories.getOrNull(activeCategory)
val defaultTitle = stringResource(id = R.string.label_library)
val defaultTitle = stringResource(R.string.label_library)
val categoryName = category?.visualName ?: defaultTitle
val default = remember { LibraryToolbarTitle(defaultTitle) }

View file

@ -2,19 +2,10 @@ package eu.kanade.tachiyomi.ui.manga
import android.app.Dialog
import android.os.Bundle
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import com.bluelinelabs.conductor.Controller
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import eu.kanade.domain.manga.model.Manga
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.ui.base.controller.pushController
@ -55,48 +46,3 @@ class AddDuplicateMangaDialog(bundle: Bundle? = null) : DialogController(bundle)
.create()
}
}
@Composable
fun DuplicateDialog(
onDismissRequest: () -> Unit,
onConfirm: () -> Unit,
onOpenManga: () -> Unit,
duplicateFrom: Source,
) {
AlertDialog(
onDismissRequest = onDismissRequest,
confirmButton = {
Row {
TextButton(onClick = {
onDismissRequest()
onOpenManga()
},) {
Text(text = stringResource(id = R.string.action_show_manga))
}
Spacer(modifier = Modifier.weight(1f))
TextButton(onClick = onDismissRequest) {
Text(text = stringResource(id = android.R.string.cancel))
}
TextButton(
onClick = {
onDismissRequest()
onConfirm()
},
) {
Text(text = stringResource(id = R.string.action_add))
}
}
},
title = {
Text(text = stringResource(id = R.string.are_you_sure))
},
text = {
Text(
text = stringResource(
id = R.string.confirm_manga_add_duplicate,
duplicateFrom.name,
),
)
},
)
}

View file

@ -26,6 +26,8 @@ import eu.kanade.presentation.components.LoadingScreen
import eu.kanade.presentation.manga.DownloadAction
import eu.kanade.presentation.manga.MangaScreen
import eu.kanade.presentation.manga.components.DeleteChaptersDialog
import eu.kanade.presentation.manga.components.DownloadCustomAmountDialog
import eu.kanade.presentation.manga.components.DuplicateMangaDialog
import eu.kanade.presentation.util.calculateWindowWidthSizeClass
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.download.DownloadService
@ -46,7 +48,6 @@ import eu.kanade.tachiyomi.ui.library.LibraryController
import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.ui.manga.MangaPresenter.Dialog
import eu.kanade.tachiyomi.ui.manga.chapter.ChaptersSettingsSheet
import eu.kanade.tachiyomi.ui.manga.chapter.DownloadCustomAmountDialog
import eu.kanade.tachiyomi.ui.manga.info.MangaFullCoverDialog
import eu.kanade.tachiyomi.ui.manga.track.TrackItem
import eu.kanade.tachiyomi.ui.manga.track.TrackSearchDialog
@ -105,6 +106,12 @@ class MangaController : FullComposeController<MangaPresenter> {
@Composable
override fun ComposeContent() {
val state by presenter.state.collectAsState()
if (state is MangaScreenState.Loading) {
LoadingScreen()
return
}
val dialog by derivedStateOf {
when (val state = state) {
MangaScreenState.Loading -> null
@ -112,93 +119,88 @@ class MangaController : FullComposeController<MangaPresenter> {
}
}
if (state is MangaScreenState.Success) {
val successState = state as MangaScreenState.Success
val isHttpSource = remember { successState.source is HttpSource }
val successState = state as MangaScreenState.Success
val isHttpSource = remember { successState.source is HttpSource }
val scope = rememberCoroutineScope()
val scope = rememberCoroutineScope()
MangaScreen(
state = successState,
snackbarHostState = snackbarHostState,
windowWidthSizeClass = calculateWindowWidthSizeClass(),
onBackClicked = router::popCurrentController,
onChapterClicked = this::openChapter,
onDownloadChapter = this::onDownloadChapters.takeIf { !successState.source.isLocalOrStub() },
onAddToLibraryClicked = this::onFavoriteClick,
onWebViewClicked = this::openMangaInWebView.takeIf { isHttpSource },
onTrackingClicked = trackSheet::show.takeIf { successState.trackingAvailable },
onTagClicked = this::performGenreSearch,
onFilterButtonClicked = settingsSheet::show,
onRefresh = presenter::fetchAllFromSource,
onContinueReading = this::continueReading,
onSearch = this::performSearch,
onCoverClicked = this::openCoverDialog,
onShareClicked = this::shareManga.takeIf { isHttpSource },
onDownloadActionClicked = this::runDownloadChapterAction.takeIf { !successState.source.isLocalOrStub() },
onEditCategoryClicked = presenter::promptChangeCategories.takeIf { successState.manga.favorite },
onMigrateClicked = this::migrateManga.takeIf { successState.manga.favorite },
onMultiBookmarkClicked = presenter::bookmarkChapters,
onMultiMarkAsReadClicked = presenter::markChaptersRead,
onMarkPreviousAsReadClicked = presenter::markPreviousChapterRead,
onMultiDeleteClicked = presenter::showDeleteChapterDialog,
onChapterSelected = presenter::toggleSelection,
onAllChapterSelected = presenter::toggleAllSelection,
onInvertSelection = presenter::invertSelection,
)
MangaScreen(
state = successState,
snackbarHostState = snackbarHostState,
windowWidthSizeClass = calculateWindowWidthSizeClass(),
onBackClicked = router::popCurrentController,
onChapterClicked = this::openChapter,
onDownloadChapter = this::onDownloadChapters.takeIf { !successState.source.isLocalOrStub() },
onAddToLibraryClicked = this::onFavoriteClick,
onWebViewClicked = this::openMangaInWebView.takeIf { isHttpSource },
onTrackingClicked = trackSheet::show.takeIf { successState.trackingAvailable },
onTagClicked = this::performGenreSearch,
onFilterButtonClicked = settingsSheet::show,
onRefresh = presenter::fetchAllFromSource,
onContinueReading = this::continueReading,
onSearch = this::performSearch,
onCoverClicked = this::openCoverDialog,
onShareClicked = this::shareManga.takeIf { isHttpSource },
onDownloadActionClicked = this::runDownloadChapterAction.takeIf { !successState.source.isLocalOrStub() },
onEditCategoryClicked = presenter::promptChangeCategories.takeIf { successState.manga.favorite },
onMigrateClicked = this::migrateManga.takeIf { successState.manga.favorite },
onMultiBookmarkClicked = presenter::bookmarkChapters,
onMultiMarkAsReadClicked = presenter::markChaptersRead,
onMarkPreviousAsReadClicked = presenter::markPreviousChapterRead,
onMultiDeleteClicked = presenter::showDeleteChapterDialog,
onChapterSelected = presenter::toggleSelection,
onAllChapterSelected = presenter::toggleAllSelection,
onInvertSelection = presenter::invertSelection,
)
val onDismissRequest = { presenter.dismissDialog() }
when (val dialog = dialog) {
is Dialog.ChangeCategory -> {
ChangeCategoryDialog(
initialSelection = dialog.initialSelection,
onDismissRequest = onDismissRequest,
onEditCategories = {
router.pushController(CategoryController())
},
onConfirm = { include, _ ->
presenter.moveMangaToCategoriesAndAddToLibrary(dialog.manga, include)
},
)
}
is Dialog.DeleteChapters -> {
DeleteChaptersDialog(
onDismissRequest = onDismissRequest,
onConfirm = {
deleteChapters(dialog.chapters)
},
)
}
is Dialog.DownloadCustomAmount -> {
DownloadCustomAmountDialog(
maxAmount = dialog.max,
onDismissRequest = onDismissRequest,
onConfirm = { amount ->
val chaptersToDownload = presenter.getUnreadChaptersSorted().take(amount)
if (chaptersToDownload.isNotEmpty()) {
scope.launch { downloadChapters(chaptersToDownload) }
}
},
)
}
is Dialog.DuplicateManga -> {
DuplicateDialog(
onDismissRequest = onDismissRequest,
onConfirm = {
presenter.toggleFavorite(
onRemoved = {},
onAdded = {},
checkDuplicate = false,
)
},
onOpenManga = { router.pushController(MangaController(dialog.duplicate.id)) },
duplicateFrom = presenter.getSourceOrStub(dialog.duplicate),
)
}
null -> {}
val onDismissRequest = { presenter.dismissDialog() }
when (val dialog = dialog) {
is Dialog.ChangeCategory -> {
ChangeCategoryDialog(
initialSelection = dialog.initialSelection,
onDismissRequest = onDismissRequest,
onEditCategories = {
router.pushController(CategoryController())
},
onConfirm = { include, _ ->
presenter.moveMangaToCategoriesAndAddToLibrary(dialog.manga, include)
},
)
}
} else {
LoadingScreen()
is Dialog.DeleteChapters -> {
DeleteChaptersDialog(
onDismissRequest = onDismissRequest,
onConfirm = {
deleteChapters(dialog.chapters)
},
)
}
is Dialog.DownloadCustomAmount -> {
DownloadCustomAmountDialog(
maxAmount = dialog.max,
onDismissRequest = onDismissRequest,
onConfirm = { amount ->
val chaptersToDownload = presenter.getUnreadChaptersSorted().take(amount)
if (chaptersToDownload.isNotEmpty()) {
scope.launch { downloadChapters(chaptersToDownload) }
}
},
)
}
is Dialog.DuplicateManga -> {
DuplicateMangaDialog(
onDismissRequest = onDismissRequest,
onConfirm = {
presenter.toggleFavorite(
onRemoved = {},
onAdded = {},
checkDuplicate = false,
)
},
onOpenManga = { router.pushController(MangaController(dialog.duplicate.id)) },
duplicateFrom = presenter.getSourceOrStub(dialog.duplicate),
)
}
null -> {}
}
}
@ -427,7 +429,7 @@ class MangaController : FullComposeController<MangaPresenter> {
}
}
fun deleteChapters(chapters: List<DomainChapter>) {
private fun deleteChapters(chapters: List<DomainChapter>) {
if (chapters.isEmpty()) return
presenter.deleteChapters(chapters)
}

View file

@ -26,7 +26,6 @@ subprojects {
kotlinter {
experimentalRules = true
disabledRules = arrayOf(
"experimental:argument-list-wrapping", // Doesn't play well with Android Studio
"filename", // Often broken to give a more general name