diff --git a/app/src/main/java/eu/kanade/presentation/manga/MangaNotesScreen.kt b/app/src/main/java/eu/kanade/presentation/manga/MangaNotesScreen.kt index 84dd8bbbd..b2968d8a4 100644 --- a/app/src/main/java/eu/kanade/presentation/manga/MangaNotesScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/manga/MangaNotesScreen.kt @@ -3,9 +3,14 @@ package eu.kanade.presentation.manga import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.imePadding +import androidx.compose.foundation.layout.navigationBars +import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons @@ -39,12 +44,13 @@ fun MangaNotesScreen( beginEditing: () -> Unit, endEditing: () -> Unit, onSave: (String) -> Unit, + modifier: Modifier = Modifier, ) { Scaffold( topBar = { scrollBehavior -> AppBar( titleContent = { - AppBarTitle(title = stringResource(MR.strings.action_notes), subtitle = state.title) + AppBarTitle(title = stringResource(MR.strings.action_notes), subtitle = state.manga.title) }, navigateUp = navigateUp, scrollBehavior = scrollBehavior, @@ -60,7 +66,11 @@ fun MangaNotesScreen( ) { ExtendedFloatingActionButton( text = { - Text(text = stringResource(if (state.editing) MR.strings.action_apply else MR.strings.action_edit)) + Text( + text = stringResource( + if (state.editing) MR.strings.action_apply else MR.strings.action_edit + ), + ) }, icon = { Icon( @@ -73,22 +83,28 @@ fun MangaNotesScreen( ) } }, + modifier = modifier, ) { paddingValues -> if (state.editing) { MangaNotesTextArea( + state = state, + onSave = onSave, modifier = Modifier .padding( top = paddingValues.calculateTopPadding() + MaterialTheme.padding.small, - bottom = MaterialTheme.padding.large, + bottom = MaterialTheme.padding.small, ) - .padding(horizontal = MaterialTheme.padding.small), - state = state, - onSave = onSave, + .padding(horizontal = MaterialTheme.padding.small) + .windowInsetsPadding( + WindowInsets.navigationBars + .only(WindowInsetsSides.Bottom), + ), ) return@Scaffold } - if (state.content == null) { + + if (state.notes.isNullOrBlank()) { EmptyScreen( stringRes = MR.strings.information_no_notes, modifier = Modifier.padding(paddingValues), @@ -101,8 +117,8 @@ fun MangaNotesScreen( .verticalScroll(rememberScrollState()) .fillMaxWidth() .padding( - horizontal = MaterialTheme.padding.small, - vertical = paddingValues.calculateTopPadding() + MaterialTheme.padding.small, + horizontal = MaterialTheme.padding.medium, + vertical = paddingValues.calculateTopPadding() + MaterialTheme.padding.medium, ), style = RichTextStyle( stringStyle = RichTextStringStyle( @@ -110,7 +126,7 @@ fun MangaNotesScreen( ), ), ) { - Markdown(content = state.content) + Markdown(content = state.notes) } return@Scaffold diff --git a/app/src/main/java/eu/kanade/presentation/manga/MangaScreen.kt b/app/src/main/java/eu/kanade/presentation/manga/MangaScreen.kt index 99d086526..d80146081 100644 --- a/app/src/main/java/eu/kanade/presentation/manga/MangaScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/manga/MangaScreen.kt @@ -55,6 +55,7 @@ import eu.kanade.presentation.manga.components.MangaActionRow import eu.kanade.presentation.manga.components.MangaBottomActionMenu import eu.kanade.presentation.manga.components.MangaChapterListItem import eu.kanade.presentation.manga.components.MangaInfoBox +import eu.kanade.presentation.manga.components.MangaNotesSection import eu.kanade.presentation.manga.components.MangaToolbar import eu.kanade.presentation.manga.components.MissingChapterCountListItem import eu.kanade.presentation.util.formatChapterNumber @@ -424,6 +425,16 @@ private fun MangaScreenSmallImpl( ) } + item( + key = MangaScreenItem.NOTES_SECTION, + contentType = MangaScreenItem.NOTES_SECTION, + ) { + MangaNotesSection( + onClickNotes = onClickNotes, + content = state.manga.notes, + ) + } + item( key = MangaScreenItem.CHAPTER_HEADER, contentType = MangaScreenItem.CHAPTER_HEADER, diff --git a/app/src/main/java/eu/kanade/presentation/manga/MangaScreenConstants.kt b/app/src/main/java/eu/kanade/presentation/manga/MangaScreenConstants.kt index 22a6664d0..785038c78 100644 --- a/app/src/main/java/eu/kanade/presentation/manga/MangaScreenConstants.kt +++ b/app/src/main/java/eu/kanade/presentation/manga/MangaScreenConstants.kt @@ -17,6 +17,7 @@ enum class MangaScreenItem { INFO_BOX, ACTION_ROW, DESCRIPTION_WITH_TAG, + NOTES_SECTION, CHAPTER_HEADER, CHAPTER, } diff --git a/app/src/main/java/eu/kanade/presentation/manga/components/MangaNotesSection.kt b/app/src/main/java/eu/kanade/presentation/manga/components/MangaNotesSection.kt new file mode 100644 index 000000000..a20931187 --- /dev/null +++ b/app/src/main/java/eu/kanade/presentation/manga/components/MangaNotesSection.kt @@ -0,0 +1,101 @@ +package eu.kanade.presentation.manga.components + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Edit +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +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.text.SpanStyle +import androidx.compose.ui.tooling.preview.PreviewLightDark +import androidx.compose.ui.unit.dp +import com.halilibo.richtext.markdown.Markdown +import com.halilibo.richtext.ui.RichTextStyle +import com.halilibo.richtext.ui.material3.RichText +import com.halilibo.richtext.ui.string.RichTextStringStyle +import tachiyomi.i18n.MR +import tachiyomi.presentation.core.components.material.Button +import tachiyomi.presentation.core.components.material.ButtonDefaults +import tachiyomi.presentation.core.components.material.padding +import tachiyomi.presentation.core.i18n.stringResource + +@Composable +fun MangaNotesSection( + onClickNotes: () -> Unit, + content: String?, + modifier: Modifier = Modifier, +) { + Column(modifier.fillMaxWidth()) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier + .fillMaxWidth(), + ) { + if (!content.isNullOrBlank()) { + RichText( + modifier = Modifier + .fillMaxWidth() + .padding(MaterialTheme.padding.medium), + style = RichTextStyle( + stringStyle = RichTextStringStyle( + linkStyle = SpanStyle(color = MaterialTheme.colorScheme.primary), + ), + ), + ) { + Markdown(content = content) + } + } + + Button( + onClick = onClickNotes, + colors = ButtonDefaults.buttonColors( + containerColor = Color.Transparent, + contentColor = MaterialTheme.colorScheme.primary, + ), + shape = RoundedCornerShape(8.dp), + modifier = Modifier + .padding(horizontal = 16.dp, vertical = 4.dp), + ) { + Row( + horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.extraSmall), + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + imageVector = Icons.Outlined.Edit, + contentDescription = null, + modifier = Modifier + .size(16.dp), + ) + Text( + stringResource( + if (content.isNullOrBlank()) { + MR.strings.action_add_notes + } else { + MR.strings.action_edit_notes + }, + ), + ) + } + } + } + } +} + +@PreviewLightDark +@Composable +private fun MangaNotesSectionPreview() { + MangaNotesSection( + onClickNotes = {}, + content = "# Hello world\ntest1234 hi there!", + ) +} diff --git a/app/src/main/java/eu/kanade/presentation/manga/components/MangaNotesTextArea.kt b/app/src/main/java/eu/kanade/presentation/manga/components/MangaNotesTextArea.kt index c7f71144c..03eb3732f 100644 --- a/app/src/main/java/eu/kanade/presentation/manga/components/MangaNotesTextArea.kt +++ b/app/src/main/java/eu/kanade/presentation/manga/components/MangaNotesTextArea.kt @@ -19,11 +19,11 @@ import eu.kanade.tachiyomi.ui.manga.notes.MangaNotesScreenState @Composable fun MangaNotesTextArea( - modifier: Modifier = Modifier, state: MangaNotesScreenState.Success, onSave: (String) -> Unit, + modifier: Modifier = Modifier, ) { - var text by remember { mutableStateOf(state.content.orEmpty()) } + var text by remember { mutableStateOf(state.notes.orEmpty()) } val focusRequester = remember { FocusRequester() } Column( diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/MangaBackupCreator.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/MangaBackupCreator.kt index 040de931b..c615c18af 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/MangaBackupCreator.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/MangaBackupCreator.kt @@ -99,4 +99,5 @@ private fun Manga.toBackupManga() = lastModifiedAt = this.lastModifiedAt, favoriteModifiedAt = this.favoriteModifiedAt, version = this.version, + notes = notes, ) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupManga.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupManga.kt index 3bfc1ffcc..0adc1ed4f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupManga.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupManga.kt @@ -38,8 +38,10 @@ data class BackupManga( @ProtoNumber(105) var updateStrategy: UpdateStrategy = UpdateStrategy.ALWAYS_UPDATE, @ProtoNumber(106) var lastModifiedAt: Long = 0, @ProtoNumber(107) var favoriteModifiedAt: Long? = null, + // Mihon values start here @ProtoNumber(108) var excludedScanlators: List = emptyList(), @ProtoNumber(109) var version: Long = 0, + @ProtoNumber(110) var notes: String? = null, ) { fun getMangaImpl(): Manga { return Manga.create().copy( @@ -60,6 +62,7 @@ data class BackupManga( lastModifiedAt = this@BackupManga.lastModifiedAt, favoriteModifiedAt = this@BackupManga.favoriteModifiedAt, version = this@BackupManga.version, + notes = this@BackupManga.notes, ) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/MangaRestorer.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/MangaRestorer.kt index 213fc4bf7..9f6d6bcb3 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/MangaRestorer.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/MangaRestorer.kt @@ -129,6 +129,7 @@ class MangaRestorer( updateStrategy = manga.updateStrategy.let(UpdateStrategyColumnAdapter::encode), version = manga.version, isSyncing = 1, + notes = manga.notes, ) } return manga diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreen.kt index 6a6081967..84e573666 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreen.kt @@ -165,7 +165,7 @@ class MangaScreen( onMigrateClicked = { navigator.push(MigrateSearchScreen(successState.manga.id)) }.takeIf { successState.manga.favorite }, - onNotesClicked = { navigator.push(MangaNotesScreen(successState.manga.id)) }, + onNotesClicked = { navigator.push(MangaNotesScreen(successState.manga)) }, onMultiBookmarkClicked = screenModel::bookmarkChapters, onMultiMarkAsReadClicked = screenModel::markChaptersRead, onMarkPreviousAsReadClicked = screenModel::markPreviousChapterRead, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/notes/MangaNotesScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/notes/MangaNotesScreen.kt index ddcb273f3..51a48a77e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/notes/MangaNotesScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/notes/MangaNotesScreen.kt @@ -2,7 +2,6 @@ package eu.kanade.tachiyomi.ui.manga.notes import androidx.activity.compose.BackHandler import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import cafe.adriel.voyager.core.model.rememberScreenModel @@ -10,22 +9,17 @@ import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.currentOrThrow import eu.kanade.presentation.manga.MangaNotesScreen import eu.kanade.presentation.util.Screen +import tachiyomi.domain.manga.model.Manga import tachiyomi.presentation.core.screens.LoadingScreen -class MangaNotesScreen(private val mangaId: Long) : Screen() { +class MangaNotesScreen(private val manga: Manga) : Screen() { @Composable override fun Content() { val navigator = LocalNavigator.currentOrThrow - val screenModel = rememberScreenModel { MangaNotesScreenModel(mangaId = mangaId) } + val screenModel = rememberScreenModel { MangaNotesScreenModel(manga = manga) } val state by screenModel.state.collectAsState() - DisposableEffect(Unit) { - onDispose { - - } - } - if (state is MangaNotesScreenState.Loading) { LoadingScreen() return diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/notes/MangaNotesScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/notes/MangaNotesScreenModel.kt index 5e19c6665..2f5b6f6bc 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/notes/MangaNotesScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/notes/MangaNotesScreenModel.kt @@ -3,36 +3,27 @@ package eu.kanade.tachiyomi.ui.manga.notes import androidx.compose.runtime.Immutable import cafe.adriel.voyager.core.model.StateScreenModel import cafe.adriel.voyager.core.model.screenModelScope -import eu.kanade.tachiyomi.ui.category.CategoryEvent -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.update -import kotlinx.coroutines.launch -import tachiyomi.domain.manga.interactor.GetManga +import tachiyomi.core.common.util.lang.launchNonCancellable import tachiyomi.domain.manga.interactor.SetMangaNotes +import tachiyomi.domain.manga.model.Manga import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get class MangaNotesScreenModel( - val mangaId: Long, - getManga: GetManga = Injekt.get(), - setMangaNotes: SetMangaNotes = Injekt.get(), + val manga: Manga, + private val setMangaNotes: SetMangaNotes = Injekt.get(), ) : StateScreenModel(MangaNotesScreenState.Loading) { - private val _events: Channel = Channel() - val events = _events.receiveAsFlow() + private val successState: MangaNotesScreenState.Success? + get() = state.value as? MangaNotesScreenState.Success init { - screenModelScope.launch { - getManga.subscribe(mangaId).collectLatest { manga -> - mutableState.update { - MangaNotesScreenState.Success( - content = manga.notes, - title = manga.title, - ) - } - } + mutableState.update { + MangaNotesScreenState.Success( + manga = manga, + notes = manga.notes, + ) } } @@ -55,14 +46,21 @@ class MangaNotesScreenModel( } fun saveText(content: String) { + // don't save what isn't modified + if (content == successState?.notes) return + mutableState.update { when (it) { MangaNotesScreenState.Loading -> it - is MangaNotesScreenState.Success -> it.copy(content = content) + is MangaNotesScreenState.Success -> { + it.copy(notes = content) + } } } - // do the magic to set it backend + screenModelScope.launchNonCancellable { + setMangaNotes.awaitSetNotes(manga, content) + } } } @@ -73,8 +71,8 @@ sealed interface MangaNotesScreenState { @Immutable data class Success( - val content: String?, - val title: String, + val manga: Manga, + val notes: String?, val editing: Boolean = false, ) : MangaNotesScreenState diff --git a/data/src/main/java/tachiyomi/data/manga/MangaRepositoryImpl.kt b/data/src/main/java/tachiyomi/data/manga/MangaRepositoryImpl.kt index f7aaf4c99..98cd877ae 100644 --- a/data/src/main/java/tachiyomi/data/manga/MangaRepositoryImpl.kt +++ b/data/src/main/java/tachiyomi/data/manga/MangaRepositoryImpl.kt @@ -167,6 +167,7 @@ class MangaRepositoryImpl( updateStrategy = value.updateStrategy?.let(UpdateStrategyColumnAdapter::encode), version = value.version, isSyncing = 0, + notes = value.notes ) } } diff --git a/data/src/main/sqldelight/tachiyomi/data/mangas.sq b/data/src/main/sqldelight/tachiyomi/data/mangas.sq index 525bd71c2..c37e9bce8 100644 --- a/data/src/main/sqldelight/tachiyomi/data/mangas.sq +++ b/data/src/main/sqldelight/tachiyomi/data/mangas.sq @@ -167,7 +167,8 @@ UPDATE mangas SET update_strategy = coalesce(:updateStrategy, update_strategy), calculate_interval = coalesce(:calculateInterval, calculate_interval), version = coalesce(:version, version), - is_syncing = coalesce(:isSyncing, is_syncing) + is_syncing = coalesce(:isSyncing, is_syncing), + notes = coalesce(:notes, notes) WHERE _id = :mangaId; selectLastInsertedRowId: diff --git a/i18n/src/commonMain/moko-resources/base/strings.xml b/i18n/src/commonMain/moko-resources/base/strings.xml index a0b80a5fd..65bd59ffc 100644 --- a/i18n/src/commonMain/moko-resources/base/strings.xml +++ b/i18n/src/commonMain/moko-resources/base/strings.xml @@ -147,6 +147,8 @@ Move to bottom Move series to bottom Notes + Add Notes + Edit Notes Install Share Save