Abandon markdown for rich text

Thank you Syer10 for recommending this library
- Already built component for elegant rich text editing
- Adds a dependency, could possibly remove another (NewUpdateScreen.kt's
markdown viewer)
This commit is contained in:
imkunet 2024-09-20 04:31:06 -04:00
parent 7f7177f597
commit 6dbc9efa17
No known key found for this signature in database
GPG Key ID: 32E0ECFB90A68C42
11 changed files with 206 additions and 219 deletions

View File

@ -233,6 +233,7 @@ dependencies {
} }
implementation(libs.insetter) implementation(libs.insetter)
implementation(libs.bundles.richtext) implementation(libs.bundles.richtext)
implementation(libs.richeditor.compose)
implementation(libs.aboutLibraries.compose) implementation(libs.aboutLibraries.compose)
implementation(libs.bundles.voyager) implementation(libs.bundles.voyager)
implementation(libs.compose.materialmotion) implementation(libs.compose.materialmotion)

View File

@ -1,52 +1,28 @@
package eu.kanade.presentation.manga 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.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.filled.EditNote
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.text.SpanStyle
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 eu.kanade.presentation.components.AppBar import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.components.AppBarTitle import eu.kanade.presentation.components.AppBarTitle
import eu.kanade.presentation.manga.components.MangaNotesTextArea import eu.kanade.presentation.manga.components.MangaNotesTextArea
import eu.kanade.tachiyomi.ui.manga.notes.MangaNotesScreenState import eu.kanade.tachiyomi.ui.manga.notes.MangaNotesScreenState
import kotlinx.collections.immutable.persistentListOf
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.material.ExtendedFloatingActionButton
import tachiyomi.presentation.core.components.material.Scaffold import tachiyomi.presentation.core.components.material.Scaffold
import tachiyomi.presentation.core.components.material.padding import tachiyomi.presentation.core.components.material.padding
import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.screens.EmptyScreen
import tachiyomi.presentation.core.screens.EmptyScreenAction
@Composable @Composable
fun MangaNotesScreen( fun MangaNotesScreen(
state: MangaNotesScreenState.Success, state: MangaNotesScreenState.Success,
navigateUp: () -> Unit, navigateUp: () -> Unit,
beginEditing: () -> Unit,
endEditing: () -> Unit,
onSave: (String) -> Unit, onSave: (String) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
@ -60,98 +36,22 @@ fun MangaNotesScreen(
scrollBehavior = scrollBehavior, scrollBehavior = scrollBehavior,
) )
}, },
floatingActionButton = {
AnimatedVisibility(
true,
enter = fadeIn(),
exit = fadeOut(),
modifier = Modifier
.imePadding(),
) {
ExtendedFloatingActionButton(
text = {
Text(
text = stringResource(
if (state.editing) MR.strings.action_apply else MR.strings.action_edit,
),
)
},
icon = {
Icon(
imageVector = if (state.editing) Icons.Filled.Check else Icons.Filled.Edit,
contentDescription = null,
)
},
onClick = { if (state.editing) endEditing() else beginEditing() },
expanded = true,
)
}
},
modifier = modifier modifier = modifier
.imePadding(), .imePadding(),
) { paddingValues -> ) { paddingValues ->
AnimatedVisibility( MangaNotesTextArea(
state.editing, state = state,
enter = fadeIn(), onSave = onSave,
exit = fadeOut(), modifier = Modifier
) { .padding(
MangaNotesTextArea( top = paddingValues.calculateTopPadding() + MaterialTheme.padding.small,
state = state, bottom = MaterialTheme.padding.small,
onSave = onSave, )
modifier = Modifier .padding(horizontal = MaterialTheme.padding.small)
.padding( .windowInsetsPadding(
top = paddingValues.calculateTopPadding() + MaterialTheme.padding.small, WindowInsets.navigationBars
bottom = MaterialTheme.padding.small, .only(WindowInsetsSides.Bottom),
)
.padding(horizontal = MaterialTheme.padding.small)
.windowInsetsPadding(
WindowInsets.navigationBars
.only(WindowInsetsSides.Bottom),
),
)
}
AnimatedVisibility(
!state.editing && state.notes.isNullOrBlank(),
enter = fadeIn(),
exit = fadeOut(),
) {
EmptyScreen(
stringRes = MR.strings.information_no_notes,
modifier = Modifier.padding(paddingValues),
actions = persistentListOf(
EmptyScreenAction(
MR.strings.action_add_notes,
Icons.Filled.EditNote,
beginEditing,
),
), ),
) )
}
AnimatedVisibility(
!state.editing && !state.notes.isNullOrBlank(),
enter = fadeIn(),
exit = fadeOut(),
) {
SelectionContainer {
RichText(
modifier = Modifier
.verticalScroll(rememberScrollState())
.fillMaxWidth()
.padding(
horizontal = MaterialTheme.padding.medium,
vertical = paddingValues.calculateTopPadding() + MaterialTheme.padding.medium,
),
style = RichTextStyle(
stringStyle = RichTextStringStyle(
linkStyle = SpanStyle(color = MaterialTheme.colorScheme.primary),
),
),
) {
Markdown(content = state.notes.orEmpty())
}
}
}
} }
} }

View File

@ -113,7 +113,6 @@ fun MangaScreen(
onEditFetchIntervalClicked: (() -> Unit)?, onEditFetchIntervalClicked: (() -> Unit)?,
onMigrateClicked: (() -> Unit)?, onMigrateClicked: (() -> Unit)?,
onNotesClicked: () -> Unit, onNotesClicked: () -> Unit,
onNotesEditClicked: () -> Unit,
// For bottom action menu // For bottom action menu
onMultiBookmarkClicked: (List<Chapter>, bookmarked: Boolean) -> Unit, onMultiBookmarkClicked: (List<Chapter>, bookmarked: Boolean) -> Unit,
@ -163,7 +162,6 @@ fun MangaScreen(
onEditIntervalClicked = onEditFetchIntervalClicked, onEditIntervalClicked = onEditFetchIntervalClicked,
onMigrateClicked = onMigrateClicked, onMigrateClicked = onMigrateClicked,
onNotesClicked = onNotesClicked, onNotesClicked = onNotesClicked,
onNotesEditClicked = onNotesEditClicked,
onMultiBookmarkClicked = onMultiBookmarkClicked, onMultiBookmarkClicked = onMultiBookmarkClicked,
onMultiMarkAsReadClicked = onMultiMarkAsReadClicked, onMultiMarkAsReadClicked = onMultiMarkAsReadClicked,
onMarkPreviousAsReadClicked = onMarkPreviousAsReadClicked, onMarkPreviousAsReadClicked = onMarkPreviousAsReadClicked,
@ -200,7 +198,6 @@ fun MangaScreen(
onEditIntervalClicked = onEditFetchIntervalClicked, onEditIntervalClicked = onEditFetchIntervalClicked,
onMigrateClicked = onMigrateClicked, onMigrateClicked = onMigrateClicked,
onNotesClicked = onNotesClicked, onNotesClicked = onNotesClicked,
onNotesEditClicked = onNotesEditClicked,
onMultiBookmarkClicked = onMultiBookmarkClicked, onMultiBookmarkClicked = onMultiBookmarkClicked,
onMultiMarkAsReadClicked = onMultiMarkAsReadClicked, onMultiMarkAsReadClicked = onMultiMarkAsReadClicked,
onMarkPreviousAsReadClicked = onMarkPreviousAsReadClicked, onMarkPreviousAsReadClicked = onMarkPreviousAsReadClicked,
@ -247,7 +244,6 @@ private fun MangaScreenSmallImpl(
onEditIntervalClicked: (() -> Unit)?, onEditIntervalClicked: (() -> Unit)?,
onMigrateClicked: (() -> Unit)?, onMigrateClicked: (() -> Unit)?,
onNotesClicked: () -> Unit, onNotesClicked: () -> Unit,
onNotesEditClicked: () -> Unit,
// For bottom action menu // For bottom action menu
onMultiBookmarkClicked: (List<Chapter>, bookmarked: Boolean) -> Unit, onMultiBookmarkClicked: (List<Chapter>, bookmarked: Boolean) -> Unit,
@ -428,7 +424,7 @@ private fun MangaScreenSmallImpl(
noteContent = state.manga.notes, noteContent = state.manga.notes,
onTagSearch = onTagSearch, onTagSearch = onTagSearch,
onCopyTagToClipboard = onCopyTagToClipboard, onCopyTagToClipboard = onCopyTagToClipboard,
onClickNotes = onNotesEditClicked, onClickNotes = onNotesClicked,
) )
} }
@ -498,7 +494,6 @@ fun MangaScreenLargeImpl(
onEditIntervalClicked: (() -> Unit)?, onEditIntervalClicked: (() -> Unit)?,
onMigrateClicked: (() -> Unit)?, onMigrateClicked: (() -> Unit)?,
onNotesClicked: () -> Unit, onNotesClicked: () -> Unit,
onNotesEditClicked: () -> Unit,
// For bottom action menu // For bottom action menu
onMultiBookmarkClicked: (List<Chapter>, bookmarked: Boolean) -> Unit, onMultiBookmarkClicked: (List<Chapter>, bookmarked: Boolean) -> Unit,
@ -659,7 +654,7 @@ fun MangaScreenLargeImpl(
noteContent = state.manga.notes, noteContent = state.manga.notes,
onTagSearch = onTagSearch, onTagSearch = onTagSearch,
onCopyTagToClipboard = onCopyTagToClipboard, onCopyTagToClipboard = onCopyTagToClipboard,
onClickNotes = onNotesEditClicked, onClickNotes = onNotesClicked,
) )
} }
}, },

View File

@ -0,0 +1,28 @@
package eu.kanade.presentation.manga.components
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.mohamedrejeb.richeditor.annotation.ExperimentalRichTextApi
import com.mohamedrejeb.richeditor.model.rememberRichTextState
import com.mohamedrejeb.richeditor.ui.material3.RichText
@OptIn(ExperimentalRichTextApi::class)
@Composable
fun MangaNotesDisplay(
content: String,
modifier: Modifier,
) {
val richTextState = rememberRichTextState().setHtml(html = content)
richTextState.config.linkColor = MaterialTheme.colorScheme.primary
richTextState.config.listIndent = 15
SelectionContainer {
RichText(
modifier = modifier,
style = MaterialTheme.typography.bodyMedium,
state = richTextState,
)
}
}

View File

@ -14,7 +14,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.EditNote import androidx.compose.material.icons.filled.EditNote
import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.HorizontalDivider
@ -25,13 +24,8 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.tooling.preview.PreviewLightDark
import androidx.compose.ui.unit.dp 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.i18n.MR
import tachiyomi.presentation.core.components.material.Button import tachiyomi.presentation.core.components.material.Button
import tachiyomi.presentation.core.components.material.ButtonDefaults import tachiyomi.presentation.core.components.material.ButtonDefaults
@ -50,19 +44,10 @@ fun MangaNotesSection(
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
) { ) {
if (!content.isNullOrBlank()) { if (!content.isNullOrBlank()) {
SelectionContainer { MangaNotesDisplay(
RichText( content = content,
modifier = Modifier modifier = modifier.fillMaxWidth(),
.fillMaxWidth(), )
style = RichTextStyle(
stringStyle = RichTextStringStyle(
linkStyle = SpanStyle(color = MaterialTheme.colorScheme.primary),
),
),
) {
Markdown(content = content)
}
}
AnimatedVisibility( AnimatedVisibility(
visible = expanded, visible = expanded,

View File

@ -1,26 +1,52 @@
package eu.kanade.presentation.manga.components package eu.kanade.presentation.manga.components
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn import androidx.compose.foundation.background
import androidx.compose.animation.fadeOut import androidx.compose.foundation.clickable
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.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.OutlinedTextField import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.isImeVisible
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.FormatListBulleted
import androidx.compose.material.icons.outlined.FormatBold
import androidx.compose.material.icons.outlined.FormatItalic
import androidx.compose.material.icons.outlined.FormatListNumbered
import androidx.compose.material.icons.outlined.FormatSize
import androidx.compose.material.icons.outlined.FormatUnderlined
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.VerticalDivider
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.clip
import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.text.TextRange import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.dp
import com.mohamedrejeb.richeditor.model.rememberRichTextState
import com.mohamedrejeb.richeditor.ui.material3.RichTextEditor
import eu.kanade.tachiyomi.ui.manga.notes.MangaNotesScreenState import eu.kanade.tachiyomi.ui.manga.notes.MangaNotesScreenState
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.i18n.stringResource
private const val MAX_LENGTH = 10_000 private const val MAX_LENGTH = 10_000
@ -30,49 +56,135 @@ fun MangaNotesTextArea(
onSave: (String) -> Unit, onSave: (String) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
var text by remember { val richTextState = rememberRichTextState()
mutableStateOf(TextFieldValue(state.notes.orEmpty(), TextRange(Int.MAX_VALUE))) richTextState.config.linkColor = MaterialTheme.colorScheme.primary
} richTextState.config.listIndent = 15
val focusRequester = remember { FocusRequester() } val focusRequester = remember { FocusRequester() }
Box( val largeFontSize = MaterialTheme.typography.headlineMedium.fontSize
Column(
modifier = modifier modifier = modifier
.fillMaxSize(), .fillMaxSize(),
) { ) {
OutlinedTextField( RichTextEditor(
value = text, state = richTextState,
onValueChange = { if (it.text.length <= MAX_LENGTH) text = it }, textStyle = MaterialTheme.typography.bodyMedium,
modifier = Modifier maxLength = MAX_LENGTH,
.fillMaxSize() placeholder = {
.focusRequester(focusRequester), Text(text = stringResource(MR.strings.notes_placeholder))
supportingText = {
val displayWarning = text.text.length > MAX_LENGTH / 10 * 9
if (!displayWarning) {
Text(
text = "0",
modifier = Modifier.alpha(0f),
)
}
AnimatedVisibility(
displayWarning,
enter = fadeIn(),
exit = fadeOut(),
) {
Text(
text = "${text.text.length} / $MAX_LENGTH",
)
}
}, },
modifier = Modifier
.weight(1f)
.fillMaxWidth()
.focusRequester(focusRequester),
) )
AnimatedVisibility(
visible = WindowInsets.isImeVisible,
) {
LazyRow(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(2.dp),
modifier = Modifier
.padding(top = 4.dp),
) {
item {
SlackDemoPanelButton(
onClick = { richTextState.toggleSpanStyle(SpanStyle(fontWeight = FontWeight.Bold)) },
isSelected = richTextState.currentSpanStyle.fontWeight == FontWeight.Bold,
icon = Icons.Outlined.FormatBold,
)
}
item {
SlackDemoPanelButton(
onClick = { richTextState.toggleSpanStyle(SpanStyle(fontStyle = FontStyle.Italic)) },
isSelected = richTextState.currentSpanStyle.fontStyle == FontStyle.Italic,
icon = Icons.Outlined.FormatItalic,
)
}
item {
SlackDemoPanelButton(
onClick = {
richTextState.toggleSpanStyle(SpanStyle(textDecoration = TextDecoration.Underline))
},
isSelected =
richTextState.currentSpanStyle.textDecoration?.contains(TextDecoration.Underline) == true,
icon = Icons.Outlined.FormatUnderlined,
)
}
item {
VerticalDivider(
modifier = Modifier
.height(24.dp),
)
}
item {
SlackDemoPanelButton(
onClick = { richTextState.toggleUnorderedList() },
isSelected = richTextState.isUnorderedList,
icon = Icons.AutoMirrored.Outlined.FormatListBulleted,
)
}
item {
SlackDemoPanelButton(
onClick = { richTextState.toggleOrderedList() },
isSelected = richTextState.isOrderedList,
icon = Icons.Outlined.FormatListNumbered,
)
}
item {
VerticalDivider(
modifier = Modifier
.height(24.dp),
)
}
item {
SlackDemoPanelButton(
onClick = { richTextState.toggleSpanStyle(SpanStyle(fontSize = largeFontSize)) },
isSelected = richTextState.currentSpanStyle.fontSize == largeFontSize,
icon = Icons.Outlined.FormatSize,
)
}
}
}
} }
LaunchedEffect(focusRequester) { LaunchedEffect(focusRequester) {
state.notes?.let { richTextState.setHtml(it) }
focusRequester.requestFocus() focusRequester.requestFocus()
} }
DisposableEffect(Unit) { DisposableEffect(Unit) {
onDispose { onDispose {
onSave(text.text) onSave(richTextState.toHtml())
} }
} }
} }
@Composable
fun SlackDemoPanelButton(
onClick: () -> Unit,
icon: ImageVector,
isSelected: Boolean,
modifier: Modifier = Modifier,
) {
Box(
modifier = modifier
.clip(RoundedCornerShape(10.dp))
.clickable(
onClick = onClick,
enabled = true,
role = Role.Button,
),
contentAlignment = Alignment.Center,
) {
Icon(
icon,
contentDescription = icon.name,
tint = if (isSelected) MaterialTheme.colorScheme.onPrimary else MaterialTheme.colorScheme.primary,
modifier = Modifier
.background(color = if (isSelected) MaterialTheme.colorScheme.onBackground else Color.Transparent)
.padding(6.dp),
)
}
}

View File

@ -166,7 +166,6 @@ class MangaScreen(
navigator.push(MigrateSearchScreen(successState.manga.id)) navigator.push(MigrateSearchScreen(successState.manga.id))
}.takeIf { successState.manga.favorite }, }.takeIf { successState.manga.favorite },
onNotesClicked = { navigator.push(MangaNotesScreen(manga = successState.manga)) }, onNotesClicked = { navigator.push(MangaNotesScreen(manga = successState.manga)) },
onNotesEditClicked = { navigator.push(MangaNotesScreen(manga = successState.manga, editing = true)) },
onMultiBookmarkClicked = screenModel::bookmarkChapters, onMultiBookmarkClicked = screenModel::bookmarkChapters,
onMultiMarkAsReadClicked = screenModel::markChaptersRead, onMultiMarkAsReadClicked = screenModel::markChaptersRead,
onMarkPreviousAsReadClicked = screenModel::markPreviousChapterRead, onMarkPreviousAsReadClicked = screenModel::markPreviousChapterRead,

View File

@ -1,6 +1,5 @@
package eu.kanade.tachiyomi.ui.manga.notes package eu.kanade.tachiyomi.ui.manga.notes
import androidx.activity.compose.BackHandler
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@ -14,7 +13,6 @@ import tachiyomi.presentation.core.screens.LoadingScreen
class MangaNotesScreen( class MangaNotesScreen(
private val manga: Manga, private val manga: Manga,
private val editing: Boolean = false,
) : Screen() { ) : Screen() {
@Composable @Composable
override fun Content() { override fun Content() {
@ -23,7 +21,6 @@ class MangaNotesScreen(
val screenModel = rememberScreenModel { val screenModel = rememberScreenModel {
MangaNotesScreenModel( MangaNotesScreenModel(
manga = manga, manga = manga,
editing = editing,
) )
} }
val state by screenModel.state.collectAsState() val state by screenModel.state.collectAsState()
@ -35,22 +32,9 @@ class MangaNotesScreen(
val successState = state as MangaNotesScreenState.Success val successState = state as MangaNotesScreenState.Success
BackHandler(
onBack = {
if (!successState.editing) {
navigator.pop()
return@BackHandler
}
screenModel.endEditing()
},
)
MangaNotesScreen( MangaNotesScreen(
state = successState, state = successState,
navigateUp = navigator::pop, navigateUp = navigator::pop,
beginEditing = { screenModel.beginEditing() },
endEditing = { screenModel.endEditing() },
onSave = { screenModel.saveText(it) }, onSave = { screenModel.saveText(it) },
) )
} }

View File

@ -12,7 +12,6 @@ import uy.kohesive.injekt.api.get
class MangaNotesScreenModel( class MangaNotesScreenModel(
val manga: Manga, val manga: Manga,
editing: Boolean,
private val setMangaNotes: SetMangaNotes = Injekt.get(), private val setMangaNotes: SetMangaNotes = Injekt.get(),
) : StateScreenModel<MangaNotesScreenState>(MangaNotesScreenState.Loading) { ) : StateScreenModel<MangaNotesScreenState>(MangaNotesScreenState.Loading) {
@ -24,29 +23,10 @@ class MangaNotesScreenModel(
MangaNotesScreenState.Success( MangaNotesScreenState.Success(
manga = manga, manga = manga,
notes = manga.notes, notes = manga.notes,
editing = editing,
) )
} }
} }
fun beginEditing() {
mutableState.update {
when (it) {
MangaNotesScreenState.Loading -> it
is MangaNotesScreenState.Success -> it.copy(editing = true)
}
}
}
fun endEditing() {
mutableState.update {
when (it) {
MangaNotesScreenState.Loading -> it
is MangaNotesScreenState.Success -> it.copy(editing = false)
}
}
}
fun saveText(content: String) { fun saveText(content: String) {
// don't save what isn't modified // don't save what isn't modified
if (content == successState?.notes) return if (content == successState?.notes) return
@ -75,7 +55,5 @@ sealed interface MangaNotesScreenState {
data class Success( data class Success(
val manga: Manga, val manga: Manga,
val notes: String?, val notes: String?,
val editing: Boolean = false,
) : MangaNotesScreenState ) : MangaNotesScreenState
} }

View File

@ -56,6 +56,8 @@ natural-comparator = "com.github.gpanther:java-nat-sort:natural-comparator-1.1"
richtext-commonmark = { module = "com.halilibo.compose-richtext:richtext-commonmark", version.ref = "richtext" } richtext-commonmark = { module = "com.halilibo.compose-richtext:richtext-commonmark", version.ref = "richtext" }
richtext-m3 = { module = "com.halilibo.compose-richtext:richtext-ui-material3", version.ref = "richtext" } richtext-m3 = { module = "com.halilibo.compose-richtext:richtext-ui-material3", version.ref = "richtext" }
richeditor-compose = "com.mohamedrejeb.richeditor:richeditor-compose:1.0.0-rc06"
material = "com.google.android.material:material:1.12.0" material = "com.google.android.material:material:1.12.0"
flexible-adapter-core = "com.github.arkon.FlexibleAdapter:flexible-adapter:c8013533" flexible-adapter-core = "com.github.arkon.FlexibleAdapter:flexible-adapter:c8013533"
photoview = "com.github.chrisbanes:PhotoView:2.3.0" photoview = "com.github.chrisbanes:PhotoView:2.3.0"

View File

@ -146,9 +146,9 @@
<string name="action_move_to_top_all_for_series">Move series to top</string> <string name="action_move_to_top_all_for_series">Move series to top</string>
<string name="action_move_to_bottom">Move to bottom</string> <string name="action_move_to_bottom">Move to bottom</string>
<string name="action_move_to_bottom_all_for_series">Move series to bottom</string> <string name="action_move_to_bottom_all_for_series">Move series to bottom</string>
<string name="action_notes">Notes</string> <string name="action_notes">Note</string>
<string name="action_add_notes">Add Notes</string> <string name="action_add_notes">Add Note</string>
<string name="action_edit_notes">Edit Notes</string> <string name="action_edit_notes">Edit Note</string>
<string name="action_install">Install</string> <string name="action_install">Install</string>
<string name="action_share">Share</string> <string name="action_share">Share</string>
<string name="action_save">Save</string> <string name="action_save">Save</string>
@ -915,7 +915,7 @@
<string name="information_cloudflare_bypass_failure">Failed to bypass Cloudflare</string> <string name="information_cloudflare_bypass_failure">Failed to bypass Cloudflare</string>
<string name="information_cloudflare_help">Tap here for help with Cloudflare</string> <string name="information_cloudflare_help">Tap here for help with Cloudflare</string>
<string name="information_required_plain">*required</string> <string name="information_required_plain">*required</string>
<string name="information_no_notes">There are no notes here yet!</string> <string name="information_no_notes">No notes</string>
<!-- Do not translate "WebView" --> <!-- Do not translate "WebView" -->
<string name="information_webview_required">WebView is required for the app to function</string> <string name="information_webview_required">WebView is required for the app to function</string>
<!-- Do not translate "WebView" --> <!-- Do not translate "WebView" -->
@ -956,4 +956,7 @@
<string name="exception_http">HTTP %d, check website in WebView</string> <string name="exception_http">HTTP %d, check website in WebView</string>
<string name="exception_offline">No Internet connection</string> <string name="exception_offline">No Internet connection</string>
<string name="exception_unknown_host">Couldn\'t reach %s</string> <string name="exception_unknown_host">Couldn\'t reach %s</string>
<!-- Notes screen -->
<string name="notes_placeholder">My analysis of the story begins with ontological antirealism, that is to say...</string>
</resources> </resources>