🚀 Change Path option

* Changes path to the file of the book

Resolves: #152
This commit is contained in:
Acclorite 2025-01-28 10:42:36 +02:00
parent de3b45e6a1
commit 40f9122a65
28 changed files with 184 additions and 48 deletions

View file

@ -11,6 +11,7 @@ fun BookInfoBottomSheet(
bottomSheet: BottomSheet?,
book: Book,
canResetCover: Boolean,
showPathDialog: (BookInfoEvent.OnShowPathDialog) -> Unit,
changeCover: (BookInfoEvent.OnChangeCover) -> Unit,
resetCover: (BookInfoEvent.OnResetCover) -> Unit,
deleteCover: (BookInfoEvent.OnDeleteCover) -> Unit,
@ -33,6 +34,7 @@ fun BookInfoBottomSheet(
BookInfoScreen.DETAILS_BOTTOM_SHEET -> {
BookInfoDetailsBottomSheet(
book = book,
showPathDialog = showPathDialog,
dismissBottomSheet = dismissBottomSheet
)
}

View file

@ -24,6 +24,8 @@ fun BookInfoContent(
actionAuthorDialog: (BookInfoEvent.OnActionAuthorDialog) -> Unit,
showDescriptionDialog: (BookInfoEvent.OnShowDescriptionDialog) -> Unit,
actionDescriptionDialog: (BookInfoEvent.OnActionDescriptionDialog) -> Unit,
showPathDialog: (BookInfoEvent.OnShowPathDialog) -> Unit,
actionPathDialog: (BookInfoEvent.OnActionPathDialog) -> Unit,
showMoveDialog: (BookInfoEvent.OnShowMoveDialog) -> Unit,
showDeleteDialog: (BookInfoEvent.OnShowDeleteDialog) -> Unit,
actionDeleteDialog: (BookInfoEvent.OnActionDeleteDialog) -> Unit,
@ -44,6 +46,7 @@ fun BookInfoContent(
actionTitleDialog = actionTitleDialog,
actionAuthorDialog = actionAuthorDialog,
actionDescriptionDialog = actionDescriptionDialog,
actionPathDialog = actionPathDialog,
actionDeleteDialog = actionDeleteDialog,
actionMoveDialog = actionMoveDialog,
dismissDialog = dismissDialog,
@ -54,6 +57,7 @@ fun BookInfoContent(
BookInfoBottomSheet(
bottomSheet = bottomSheet,
book = book,
showPathDialog = showPathDialog,
canResetCover = canResetCover,
changeCover = changeCover,
resetCover = resetCover,

View file

@ -14,6 +14,8 @@ import ua.acclorite.book_story.R
import ua.acclorite.book_story.domain.library.book.Book
import ua.acclorite.book_story.presentation.core.components.common.LazyColumnWithScrollbar
import ua.acclorite.book_story.presentation.core.components.modal_bottom_sheet.ModalBottomSheet
import ua.acclorite.book_story.presentation.core.constants.Constants
import ua.acclorite.book_story.presentation.core.constants.provideExtensions
import ua.acclorite.book_story.presentation.settings.components.SettingsSubcategoryTitle
import ua.acclorite.book_story.ui.book_info.BookInfoEvent
import java.io.File
@ -24,6 +26,7 @@ import java.util.Locale
@Composable
fun BookInfoDetailsBottomSheet(
book: Book,
showPathDialog: (BookInfoEvent.OnShowPathDialog) -> Unit,
dismissBottomSheet: (BookInfoEvent.OnDismissBottomSheet) -> Unit
) {
val pattern = remember { SimpleDateFormat("HH:mm dd MMM yyyy", Locale.getDefault()) }
@ -46,7 +49,11 @@ fun BookInfoDetailsBottomSheet(
}
val fileExists = remember(book.filePath) {
File(book.filePath).exists()
File(book.filePath).let {
it.exists() && it.isFile && Constants.provideExtensions().any { ext ->
it.name.endsWith(ext, ignoreCase = true)
}
}
}
ModalBottomSheet(
@ -73,7 +80,10 @@ fun BookInfoDetailsBottomSheet(
BookInfoDetailsBottomSheetItem(
label = stringResource(id = R.string.file_path),
text = book.filePath,
editable = false,
editable = true,
onEdit = {
showPathDialog(BookInfoEvent.OnShowPathDialog)
},
showError = !fileExists,
errorMessage = stringResource(id = R.string.error_no_file)
)

View file

@ -13,6 +13,7 @@ fun BookInfoDialog(
actionTitleDialog: (BookInfoEvent.OnActionTitleDialog) -> Unit,
actionAuthorDialog: (BookInfoEvent.OnActionAuthorDialog) -> Unit,
actionDescriptionDialog: (BookInfoEvent.OnActionDescriptionDialog) -> Unit,
actionPathDialog: (BookInfoEvent.OnActionPathDialog) -> Unit,
actionDeleteDialog: (BookInfoEvent.OnActionDeleteDialog) -> Unit,
actionMoveDialog: (BookInfoEvent.OnActionMoveDialog) -> Unit,
dismissDialog: (BookInfoEvent.OnDismissDialog) -> Unit,
@ -60,5 +61,13 @@ fun BookInfoDialog(
dismissDialog = dismissDialog
)
}
BookInfoScreen.PATH_DIALOG -> {
BookInfoPathDialog(
book = book,
actionPathDialog = actionPathDialog,
dismissDialog = dismissDialog
)
}
}
}

View file

@ -0,0 +1,32 @@
package ua.acclorite.book_story.presentation.book_info
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import ua.acclorite.book_story.domain.library.book.Book
import ua.acclorite.book_story.presentation.core.components.dialog.DialogWithTextField
import ua.acclorite.book_story.ui.book_info.BookInfoEvent
@Composable
fun BookInfoPathDialog(
book: Book,
actionPathDialog: (BookInfoEvent.OnActionPathDialog) -> Unit,
dismissDialog: (BookInfoEvent.OnDismissDialog) -> Unit
) {
val context = LocalContext.current
DialogWithTextField(
initialValue = book.filePath.trim(),
lengthLimit = 10000,
onDismiss = {
dismissDialog(BookInfoEvent.OnDismissDialog)
},
onAction = {
if (it.isBlank()) return@DialogWithTextField
actionPathDialog(
BookInfoEvent.OnActionPathDialog(
path = it.trim().replace("\n", ""),
context = context
)
)
}
)
}

View file

@ -96,8 +96,8 @@ fun ReaderContent(
dismissBottomSheet: (ReaderEvent.OnDismissBottomSheet) -> Unit,
showChaptersDrawer: (ReaderEvent.OnShowChaptersDrawer) -> Unit,
dismissDrawer: (ReaderEvent.OnDismissDrawer) -> Unit,
navigateBack: () -> Unit,
navigateToBookInfo: () -> Unit
navigateToBookInfo: (changePath: Boolean) -> Unit,
navigateBack: () -> Unit
) {
ReaderBottomSheet(
bottomSheet = bottomSheet,
@ -176,6 +176,7 @@ fun ReaderContent(
ReaderErrorPlaceholder(
errorMessage = errorMessage,
leave = leave,
navigateToBookInfo = navigateToBookInfo,
navigateBack = navigateBack
)
}

View file

@ -1,47 +1,80 @@
package ua.acclorite.book_story.presentation.reader
import androidx.compose.foundation.background
import android.annotation.SuppressLint
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import ua.acclorite.book_story.R
import ua.acclorite.book_story.domain.ui.UIText
import ua.acclorite.book_story.presentation.core.components.placeholder.ErrorPlaceholder
import ua.acclorite.book_story.presentation.core.components.top_bar.TopAppBar
import ua.acclorite.book_story.presentation.core.components.top_bar.TopAppBarData
import ua.acclorite.book_story.presentation.core.util.LocalActivity
import ua.acclorite.book_story.presentation.navigator.NavigatorBackIconButton
import ua.acclorite.book_story.ui.reader.ReaderEvent
@OptIn(ExperimentalMaterial3Api::class)
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
@Composable
fun ReaderErrorPlaceholder(
errorMessage: UIText,
leave: (ReaderEvent.OnLeave) -> Unit,
navigateToBookInfo: (changePath: Boolean) -> Unit,
navigateBack: () -> Unit
) {
val activity = LocalActivity.current
Box(
modifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colorScheme.surface),
contentAlignment = Alignment.Center
) {
ErrorPlaceholder(
errorMessage = errorMessage.asString(),
icon = painterResource(id = R.drawable.error),
actionTitle = stringResource(id = R.string.go_back),
action = {
leave(
ReaderEvent.OnLeave(
activity = activity,
navigate = {
navigateBack()
}
Scaffold(
modifier = Modifier.fillMaxSize(),
containerColor = MaterialTheme.colorScheme.surface,
topBar = {
TopAppBar(
containerColor = Color.Transparent,
scrollBehavior = null,
isTopBarScrolled = null,
shownTopBar = 0,
topBars = listOf(
TopAppBarData(
contentID = 0,
contentNavigationIcon = {
NavigatorBackIconButton {
navigateBack()
}
},
contentTitle = {},
contentActions = {}
)
)
}
)
)
}
) {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
ErrorPlaceholder(
errorMessage = errorMessage.asString(),
icon = painterResource(id = R.drawable.error),
actionTitle = stringResource(id = R.string.change_path),
action = {
leave(
ReaderEvent.OnLeave(
activity = activity,
navigate = {
navigateToBookInfo(true)
}
)
)
}
)
}
}
}

View file

@ -97,8 +97,8 @@ fun ReaderScaffold(
openDictionary: (ReaderEvent.OnOpenDictionary) -> Unit,
showSettingsBottomSheet: (ReaderEvent.OnShowSettingsBottomSheet) -> Unit,
showChaptersDrawer: (ReaderEvent.OnShowChaptersDrawer) -> Unit,
navigateBack: () -> Unit,
navigateToBookInfo: () -> Unit
navigateToBookInfo: (changePath: Boolean) -> Unit,
navigateBack: () -> Unit
) {
Scaffold(
Modifier

View file

@ -47,8 +47,8 @@ fun ReaderTopBar(
selectNextPreset: (SettingsEvent.OnSelectNextPreset) -> Unit,
showSettingsBottomSheet: (ReaderEvent.OnShowSettingsBottomSheet) -> Unit,
showChaptersDrawer: (ReaderEvent.OnShowChaptersDrawer) -> Unit,
navigateBack: () -> Unit,
navigateToBookInfo: () -> Unit
navigateToBookInfo: (changePath: Boolean) -> Unit,
navigateBack: () -> Unit
) {
val activity = LocalActivity.current
val animatedChapterProgress = animateFloatAsState(
@ -100,7 +100,7 @@ fun ReaderTopBar(
ReaderEvent.OnLeave(
activity = activity,
navigate = {
navigateToBookInfo()
navigateToBookInfo(false)
}
)
)

View file

@ -51,6 +51,13 @@ sealed class BookInfoEvent {
val context: Context
) : BookInfoEvent()
data object OnShowPathDialog : BookInfoEvent()
data class OnActionPathDialog(
val path: String,
val context: Context
) : BookInfoEvent()
data object OnShowDeleteDialog : BookInfoEvent()
data class OnActionDeleteDialog(

View file

@ -280,6 +280,35 @@ class BookInfoModel @Inject constructor(
}
}
is BookInfoEvent.OnShowPathDialog -> {
_state.update {
it.copy(
dialog = BookInfoScreen.PATH_DIALOG
)
}
}
is BookInfoEvent.OnActionPathDialog -> {
launch {
_state.update {
it.copy(
book = it.book.copy(
filePath = event.path
)
)
}
updateBook.execute(_state.value.book)
LibraryScreen.refreshListChannel.trySend(0)
HistoryScreen.refreshListChannel.trySend(0)
withContext(Dispatchers.Main) {
event.context.getString(R.string.path_changed)
.showToast(context = event.context)
}
}
}
is BookInfoEvent.OnShowDeleteDialog -> {
_state.update {
it.copy(
@ -368,6 +397,7 @@ class BookInfoModel @Inject constructor(
fun init(
bookId: Int,
changePath: Boolean,
navigateBack: () -> Unit
) {
viewModelScope.launch(Dispatchers.IO) {
@ -390,6 +420,9 @@ class BookInfoModel @Inject constructor(
)
}
if (changePath) {
onEvent(BookInfoEvent.OnShowPathDialog)
}
onEvent(BookInfoEvent.OnCheckCoverReset)
}
}

View file

@ -11,6 +11,8 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.getOrElse
import kotlinx.parcelize.Parcelize
import ua.acclorite.book_story.domain.navigator.Screen
import ua.acclorite.book_story.presentation.book_info.BookInfoContent
@ -28,9 +30,12 @@ data class BookInfoScreen(val bookId: Int) : Screen, Parcelable {
const val TITLE_DIALOG = "title_dialog"
const val AUTHOR_DIALOG = "author_dialog"
const val DESCRIPTION_DIALOG = "description_dialog"
const val PATH_DIALOG = "path_dialog"
const val CHANGE_COVER_BOTTOM_SHEET = "change_cover_bottom_sheet"
const val DETAILS_BOTTOM_SHEET = "details_bottom_sheet"
val changePathChannel: Channel<Boolean> = Channel(Channel.CONFLATED)
}
@OptIn(ExperimentalMaterialApi::class)
@ -45,6 +50,7 @@ data class BookInfoScreen(val bookId: Int) : Screen, Parcelable {
LaunchedEffect(Unit) {
screenModel.init(
bookId = bookId,
changePath = changePathChannel.tryReceive().getOrElse { false },
navigateBack = {
navigator.pop()
}
@ -72,6 +78,8 @@ data class BookInfoScreen(val bookId: Int) : Screen, Parcelable {
actionAuthorDialog = screenModel::onEvent,
showDescriptionDialog = screenModel::onEvent,
actionDescriptionDialog = screenModel::onEvent,
showPathDialog = screenModel::onEvent,
actionPathDialog = screenModel::onEvent,
checkCoverReset = screenModel::onEvent,
changeCover = screenModel::onEvent,
resetCover = screenModel::onEvent,

View file

@ -242,7 +242,8 @@ class ReaderModel @Inject constructor(
if (
_state.value.isLoading ||
layoutInfo.totalItemsCount < 1 ||
_state.value.text.isEmpty()
_state.value.text.isEmpty() ||
_state.value.errorMessage != null
) return@apply
_state.update {
@ -589,7 +590,8 @@ class ReaderModel @Inject constructor(
if (
isLoading ||
listState.layoutInfo.totalItemsCount == 0 ||
text.isEmpty()
text.isEmpty() ||
errorMessage != null
) {
return book.progress
}

View file

@ -467,7 +467,8 @@ data class ReaderScreen(val bookId: Int) : Screen, Parcelable {
navigateBack = {
navigator.pop()
},
navigateToBookInfo = {
navigateToBookInfo = { changePath ->
if (changePath) BookInfoScreen.changePathChannel.trySend(true)
navigator.push(
BookInfoScreen(
bookId = bookId,

View file

@ -43,7 +43,6 @@
<string name="get_help">أحتاج المساعدة</string>
<string name="add_book">أضف كتابا˝</string>
<string name="never">أبدا˝</string>
<string name="go_back">عودة</string>
<string name="undo">تراجع</string>
<string name="reset_cover">إعادة ضبط صورة الغلاف</string>
<string name="appearance_settings">المظهر</string>

View file

@ -146,7 +146,6 @@
<string name="error_something_went_wrong">Něco se pokazilo.</string>
<string name="grant">Povolit</string>
<string name="add_book">Přidat knihu</string>
<string name="go_back">Zpět</string>
<string name="copy">Kopírovat</string>
<string name="translate">Přeložit</string>
<string name="next">Další</string>

View file

@ -36,7 +36,6 @@
<string name="add_book">Buch hinzufügen</string>
<string name="move">Verschieben</string>
<string name="never">Niemals</string>
<string name="go_back">Zurück gehen</string>
<string name="undo">Rückgängig machen</string>
<string name="copy">Kopieren</string>
<string name="translate">Übersetzen</string>

View file

@ -97,7 +97,6 @@
<string name="update_query">Actualizar %1$s</string>
<string name="grant_permission">Conceder permiso</string>
<string name="never">Nunca</string>
<string name="go_back">Regresar</string>
<string name="copy">Copiar</string>
<string name="done">Hecho</string>
<string name="undo">Deshacer</string>

View file

@ -35,7 +35,6 @@
<string name="move">Déplacer</string>
<string name="delete">Supprimer</string>
<string name="never">Jamais</string>
<string name="go_back">Retour</string>
<string name="undo">Annuler</string>
<string name="copy">Copier</string>
<string name="translate">Traduire</string>

View file

@ -31,7 +31,6 @@
<string name="get_help">मुझे सहायता की आवश्यकता है</string>
<string name="add_book">एक किताब जोड़ें</string>
<string name="never">कभी नहीं</string>
<string name="go_back">पीछे जाना</string>
<string name="undo">पूर्ववत् करें</string>
<string name="copy">कॉपी करें</string>
<string name="translate">अनुवाद करें</string>

View file

@ -43,7 +43,6 @@
<string name="red_theme">Marte</string>
<string name="next">Prossimo</string>
<string name="delete">Elimina</string>
<string name="go_back">Indietro</string>
<string name="web_search">Ricerca web</string>
<string name="dark_theme_on">Abilitato</string>
<string name="start_permissions_notifications">Permesso per le notifiche</string>

View file

@ -144,7 +144,6 @@
<string name="grant_permission">Udziel zgody</string>
<string name="get_help">Potrzebuję pomocy</string>
<string name="add_book">Dodaj książkę</string>
<string name="go_back">Powróć</string>
<string name="translate">Przetłumacz</string>
<string name="appearance_settings_desc">Motyw, kolory</string>
<string name="reader_settings_desc">Czcionka, tekst, tłumacz</string>

View file

@ -136,7 +136,6 @@
<string name="paragraph_indentation_option">Recuo de parágrafo</string>
<string name="letter_spacing_option">Espaçamento das letras</string>
<string name="color_preset_query">Padrão %1$s</string>
<string name="go_back">Voltar</string>
<string name="system_reader_settings">Sistema</string>
<string name="theme_appearance_settings">Preferência de temas</string>
<string name="absolute_dark_option">Escuro absoluto</string>

View file

@ -105,7 +105,6 @@
<string name="error_no_description">விளக்கம் இல்லை.</string>
<string name="selected_items_count_query">%1$s தேர்ந்தெடுக்கப்பட்டன</string>
<string name="update_query">புதுப்பிப்பு %1$s</string>
<string name="go_back">திரும்பிச் செல்லுங்கள்</string>
<string name="undo">செயல்தவிர்</string>
<string name="copy">நகலெடு</string>
<string name="translate">மொழிபெயர்த்திடு</string>

View file

@ -53,7 +53,6 @@
<string name="move">Taşı</string>
<string name="delete">Sil</string>
<string name="never">Asla</string>
<string name="go_back">Geri Dön</string>
<string name="undo">Geri Al</string>
<string name="copy">Kopyala</string>
<string name="translate">Çevir</string>

View file

@ -47,7 +47,10 @@
<string name="error_no_dictionary">Не знайдено жодного словника.</string>
<string name="error_no_browser">Не знайдено жодного браузера.</string>
<string name="error_no_share_app">Не знайдено застосунка щоб поділитися.</string>
<string name="error_could_not_get_text">Файл порожній, пошкоджений або його не існує. Будь ласка, перевірте і спробуйте знову.</string>
<string name="error_could_not_get_text">
Файл порожній, пошкоджений або відсутній.
Перевірте файл або змініть шлях і спробуйте ще раз.
</string>
<string name="error_check_internet">Щось пішло не так. Спробуйте пізніше.</string>
<string name="error_could_not_reset_cover">Не вдалося скинути обкладинку.</string>
<string name="error_closed_source">Закритий код</string>
@ -74,7 +77,6 @@
<string name="move">Перемістити</string>
<string name="delete">Видалити</string>
<string name="never">Ніколи</string>
<string name="go_back">Назад</string>
<string name="undo">Скасувати</string>
<string name="copy">Копіювати</string>
<string name="translate">Перекласти</string>
@ -86,6 +88,7 @@
<string name="web_search">Веб-пошук</string>
<string name="share">Поділитися</string>
<string name="ok">ОК</string>
<string name="change_path">Змінити шлях</string>
<!-- Book -->
<string name="unknown_author">Невідомий</string>
@ -318,6 +321,7 @@
<string name="title_changed">Назва була успішно змінена.</string>
<string name="author_changed">Автор був успішно змінений.</string>
<string name="description_changed">Опис був успішно змінений.</string>
<string name="path_changed">Шлях до файлу був успішно змінений.</string>
<!-- Snackbar messages -->
<string name="history_element_deleted">Елемент історії був успішно видалений.</string>

View file

@ -48,7 +48,6 @@
<string name="color_preset_selected_query">已选择《%1$s》</string>
<string name="grant">授予</string>
<string name="grant_permission">授予权限</string>
<string name="go_back">返回</string>
<string name="undo">撤销</string>
<string name="next">下一个</string>
<string name="unknown_author">未知</string>

View file

@ -90,7 +90,8 @@
<string name="error_no_browser">No browser app found.</string>
<string name="error_no_share_app">No app to share found.</string>
<string name="error_could_not_get_text">
File is empty, corrupted, or does not exist. Please verify and try again.
File is empty, corrupted, or missing.
Please verify the file or update the path and try again.
</string>
<string name="error_check_internet">
Something went wrong.
@ -129,7 +130,6 @@
<string name="move">Move</string>
<string name="delete">Delete</string>
<string name="never">Never</string>
<string name="go_back">Go back</string>
<string name="undo">Undo</string>
<string name="copy">Copy</string>
<string name="translate">Translate</string>
@ -141,6 +141,7 @@
<string name="web_search">Web search</string>
<string name="share">Share</string>
<string name="ok">OK</string>
<string name="change_path">Change path</string>
<!-- Book -->
<string name="unknown_author">Unknown</string>
@ -375,6 +376,7 @@
<string name="title_changed">Title was successfully changed.</string>
<string name="author_changed">Author was successfully changed.</string>
<string name="description_changed">Description was successfully changed.</string>
<string name="path_changed">File path was successfully changed.</string>
<!-- Snackbar messages -->
<string name="history_element_deleted">History element was successfully deleted.</string>