Skip to content

Commit f25bfaf

Browse files
imkunetAntsyLich
authored andcommitted
Add user manga notes (mihonapp#428)
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com> (cherry picked from commit 8fbe630)
1 parent 762f76f commit f25bfaf

27 files changed

Lines changed: 597 additions & 23 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ The format is a modified version of [Keep a Changelog](https://keepachangelog.co
1313
## [Unreleased]
1414
### Added
1515
- Add more Kaomoji for empty/error screens ([@ianfhunter](https://github.com/ianfhunter/)) ([#1909](https://github.com/mihonapp/mihon/pull/1909))
16+
- Add user manga notes ([@imkunet](https://github.com/imkunet), [@AntsyLich](https://github.com/AntsyLich)) ([#428](https://github.com/mihonapp/mihon/pull/428))
1617

1718
### Fixes
1819
- Fix Bangumi search results including novels ([@MajorTanya](https://github.com/MajorTanya)) ([#1885](https://github.com/mihonapp/mihon/pull/1885))

app/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,7 @@ dependencies {
281281
}
282282
implementation(libs.insetter)
283283
implementation(libs.bundles.richtext)
284+
implementation(libs.richeditor.compose)
284285
implementation(libs.aboutLibraries.compose)
285286
implementation(libs.bundles.voyager)
286287
implementation(libs.compose.materialmotion)

app/src/main/java/eu/kanade/domain/DomainModule.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ import tachiyomi.domain.manga.interactor.GetMangaWithChapters
8080
import tachiyomi.domain.manga.interactor.NetworkToLocalManga
8181
import tachiyomi.domain.manga.interactor.ResetViewerFlags
8282
import tachiyomi.domain.manga.interactor.SetMangaChapterFlags
83+
import tachiyomi.domain.manga.interactor.UpdateMangaNotes
8384
import tachiyomi.domain.manga.repository.MangaRepository
8485
import tachiyomi.domain.release.interactor.GetApplicationRelease
8586
import tachiyomi.domain.release.service.ReleaseService
@@ -133,6 +134,7 @@ class DomainModule : InjektModule {
133134
addFactory { SetMangaViewerFlags(get()) }
134135
addFactory { NetworkToLocalManga(get()) }
135136
addFactory { UpdateManga(get(), get()) }
137+
addFactory { UpdateMangaNotes(get()) }
136138
addFactory { SetMangaCategories(get()) }
137139
addFactory { GetExcludedScanlators(get()) }
138140
addFactory { SetExcludedScanlators(get()) }
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package eu.kanade.presentation.manga
2+
3+
import androidx.compose.foundation.layout.consumeWindowInsets
4+
import androidx.compose.foundation.layout.imePadding
5+
import androidx.compose.foundation.layout.padding
6+
import androidx.compose.runtime.Composable
7+
import androidx.compose.ui.Modifier
8+
import eu.kanade.presentation.components.AppBar
9+
import eu.kanade.presentation.components.AppBarTitle
10+
import eu.kanade.presentation.manga.components.MangaNotesTextArea
11+
import eu.kanade.tachiyomi.ui.manga.notes.MangaNotesScreen
12+
import tachiyomi.i18n.MR
13+
import tachiyomi.presentation.core.components.material.Scaffold
14+
import tachiyomi.presentation.core.i18n.stringResource
15+
16+
@Composable
17+
fun MangaNotesScreen(
18+
state: MangaNotesScreen.State,
19+
navigateUp: () -> Unit,
20+
onUpdate: (String) -> Unit,
21+
) {
22+
Scaffold(
23+
topBar = { topBarScrollBehavior ->
24+
AppBar(
25+
titleContent = {
26+
AppBarTitle(
27+
title = stringResource(MR.strings.action_edit_notes),
28+
subtitle = state.manga.title,
29+
)
30+
},
31+
navigateUp = navigateUp,
32+
scrollBehavior = topBarScrollBehavior,
33+
)
34+
},
35+
) { contentPadding ->
36+
MangaNotesTextArea(
37+
state = state,
38+
onUpdate = onUpdate,
39+
modifier = Modifier
40+
.padding(contentPadding)
41+
.consumeWindowInsets(contentPadding)
42+
.imePadding(),
43+
)
44+
}
45+
}

app/src/main/java/eu/kanade/presentation/manga/MangaScreen.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ fun MangaScreen(
170170
onEditCategoryClicked: (() -> Unit)?,
171171
onEditFetchIntervalClicked: (() -> Unit)?,
172172
onMigrateClicked: (() -> Unit)?,
173+
onEditNotesClicked: () -> Unit,
173174
// SY -->
174175
onMetadataViewerClicked: () -> Unit,
175176
onEditInfoClicked: () -> Unit,
@@ -243,6 +244,7 @@ fun MangaScreen(
243244
onEditCategoryClicked = onEditCategoryClicked,
244245
onEditIntervalClicked = onEditFetchIntervalClicked,
245246
onMigrateClicked = onMigrateClicked,
247+
onEditNotesClicked = onEditNotesClicked,
246248
// SY -->
247249
onMetadataViewerClicked = onMetadataViewerClicked,
248250
onEditInfoClicked = onEditInfoClicked,
@@ -302,6 +304,7 @@ fun MangaScreen(
302304
onEditCategoryClicked = onEditCategoryClicked,
303305
onEditIntervalClicked = onEditFetchIntervalClicked,
304306
onMigrateClicked = onMigrateClicked,
307+
onEditNotesClicked = onEditNotesClicked,
305308
// SY -->
306309
onMetadataViewerClicked = onMetadataViewerClicked,
307310
onEditInfoClicked = onEditInfoClicked,
@@ -371,6 +374,7 @@ private fun MangaScreenSmallImpl(
371374
onEditCategoryClicked: (() -> Unit)?,
372375
onEditIntervalClicked: (() -> Unit)?,
373376
onMigrateClicked: (() -> Unit)?,
377+
onEditNotesClicked: () -> Unit,
374378
// SY -->
375379
onMetadataViewerClicked: () -> Unit,
376380
onEditInfoClicked: () -> Unit,
@@ -477,6 +481,7 @@ private fun MangaScreenSmallImpl(
477481
onClickEditCategory = onEditCategoryClicked,
478482
onClickRefresh = onRefresh,
479483
onClickMigrate = onMigrateClicked,
484+
onClickEditNotes = onEditNotesClicked,
480485
// SY -->
481486
onClickEditInfo = onEditInfoClicked.takeIf { state.manga.favorite },
482487
// KMK -->
@@ -673,8 +678,10 @@ private fun MangaScreenSmallImpl(
673678
defaultExpandState = state.isFromSource && !state.manga.favorite,
674679
description = state.manga.description,
675680
tagsProvider = { state.manga.genre },
681+
notes = state.manga.notes,
676682
onTagSearch = onTagSearch,
677683
onCopyTagToClipboard = onCopyTagToClipboard,
684+
onEditNotes = onEditNotesClicked,
678685
// SY -->
679686
doSearch = onSearch,
680687
searchMetadataChips = remember(state.meta, state.source.id, state.manga.genre) {
@@ -826,6 +833,7 @@ private fun MangaScreenLargeImpl(
826833
onEditCategoryClicked: (() -> Unit)?,
827834
onEditIntervalClicked: (() -> Unit)?,
828835
onMigrateClicked: (() -> Unit)?,
836+
onEditNotesClicked: () -> Unit,
829837
// SY -->
830838
onMetadataViewerClicked: () -> Unit,
831839
onEditInfoClicked: () -> Unit,
@@ -923,6 +931,7 @@ private fun MangaScreenLargeImpl(
923931
onClickEditCategory = onEditCategoryClicked,
924932
onClickRefresh = onRefresh,
925933
onClickMigrate = onMigrateClicked,
934+
onClickEditNotes = onEditNotesClicked,
926935
onCancelActionMode = { onAllChapterSelected(false) },
927936
// SY -->
928937
onClickEditInfo = onEditInfoClicked.takeIf { state.manga.favorite },
@@ -1100,8 +1109,10 @@ private fun MangaScreenLargeImpl(
11001109
defaultExpandState = true,
11011110
description = state.manga.description,
11021111
tagsProvider = { state.manga.genre },
1112+
notes = state.manga.notes,
11031113
onTagSearch = onTagSearch,
11041114
onCopyTagToClipboard = onCopyTagToClipboard,
1115+
onEditNotes = onEditNotesClicked,
11051116
// SY -->
11061117
doSearch = onSearch,
11071118
searchMetadataChips = remember(state.meta, state.source.id, state.manga.genre) {

app/src/main/java/eu/kanade/presentation/manga/components/MangaInfoHeader.kt

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -334,8 +334,10 @@ fun ExpandableMangaDescription(
334334
defaultExpandState: Boolean,
335335
description: String?,
336336
tagsProvider: () -> List<String>?,
337+
notes: String,
337338
onTagSearch: (String) -> Unit,
338339
onCopyTagToClipboard: (tag: String) -> Unit,
340+
onEditNotes: () -> Unit,
339341
// SY -->
340342
searchMetadataChips: SearchMetadataChips?,
341343
doSearch: (query: String, global: Boolean) -> Unit,
@@ -361,6 +363,8 @@ fun ExpandableMangaDescription(
361363
expandedDescription = desc,
362364
shrunkDescription = trimmedDescription,
363365
expanded = expanded,
366+
notes = notes,
367+
onEditNotesClicked = onEditNotes,
364368
modifier = Modifier
365369
.padding(top = 8.dp)
366370
.padding(horizontal = 16.dp)
@@ -828,7 +832,9 @@ private fun ColumnScope.MangaContentInfo(
828832
private fun MangaSummary(
829833
expandedDescription: String,
830834
shrunkDescription: String,
835+
notes: String,
831836
expanded: Boolean,
837+
onEditNotesClicked: () -> Unit,
832838
modifier: Modifier = Modifier,
833839
) {
834840
val animProgress by animateFloatAsState(
@@ -840,27 +846,43 @@ private fun MangaSummary(
840846
contents = listOf(
841847
{
842848
Text(
843-
text = "\n\n", // Shows at least 3 lines
849+
// Shows at least 3 lines if no notes
850+
// when there are notes show 6
851+
text = if (notes.isBlank()) "\n\n" else "\n\n\n\n\n",
844852
style = MaterialTheme.typography.bodyMedium,
845853
)
846854
},
847855
{
848-
Text(
849-
text = expandedDescription,
850-
style = MaterialTheme.typography.bodyMedium,
851-
)
852-
},
853-
{
854-
SelectionContainer {
856+
Column {
857+
MangaNotesSection(
858+
content = notes,
859+
expanded = true,
860+
onEditNotes = onEditNotesClicked,
861+
)
855862
Text(
856-
text = if (expanded) expandedDescription else shrunkDescription,
857-
maxLines = Int.MAX_VALUE,
863+
text = expandedDescription,
858864
style = MaterialTheme.typography.bodyMedium,
859-
color = MaterialTheme.colorScheme.onBackground,
860-
modifier = Modifier.secondaryItemAlpha(),
861865
)
862866
}
863867
},
868+
{
869+
Column {
870+
MangaNotesSection(
871+
content = notes,
872+
expanded = expanded,
873+
onEditNotes = onEditNotesClicked,
874+
)
875+
SelectionContainer {
876+
Text(
877+
text = if (expanded) expandedDescription else shrunkDescription,
878+
maxLines = Int.MAX_VALUE,
879+
style = MaterialTheme.typography.bodyMedium,
880+
color = MaterialTheme.colorScheme.onBackground,
881+
modifier = Modifier.secondaryItemAlpha(),
882+
)
883+
}
884+
}
885+
},
864886
{
865887
val colors = listOf(Color.Transparent, MaterialTheme.colorScheme.background)
866888
Box(
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package eu.kanade.presentation.manga.components
2+
3+
import androidx.compose.animation.animateContentSize
4+
import androidx.compose.animation.core.Animatable
5+
import androidx.compose.animation.core.tween
6+
import androidx.compose.foundation.text.selection.SelectionContainer
7+
import androidx.compose.material3.MaterialTheme
8+
import androidx.compose.runtime.Composable
9+
import androidx.compose.runtime.LaunchedEffect
10+
import androidx.compose.runtime.getValue
11+
import androidx.compose.runtime.mutableStateOf
12+
import androidx.compose.runtime.remember
13+
import androidx.compose.runtime.setValue
14+
import androidx.compose.ui.Modifier
15+
import androidx.compose.ui.draw.alpha
16+
import com.mohamedrejeb.richeditor.model.rememberRichTextState
17+
import com.mohamedrejeb.richeditor.ui.material3.RichText
18+
19+
private val FADE_TIME = tween<Float>(500)
20+
21+
@Composable
22+
fun MangaNotesDisplay(
23+
content: String,
24+
modifier: Modifier,
25+
) {
26+
val alpha = remember { Animatable(1f) }
27+
var contentUpdatedOnce by remember { mutableStateOf(false) }
28+
29+
val richTextState = rememberRichTextState()
30+
val primaryColor = MaterialTheme.colorScheme.primary
31+
LaunchedEffect(content) {
32+
richTextState.setMarkdown(content)
33+
34+
if (!contentUpdatedOnce) {
35+
contentUpdatedOnce = true
36+
return@LaunchedEffect
37+
}
38+
39+
alpha.snapTo(targetValue = 0f)
40+
alpha.animateTo(targetValue = 1f, animationSpec = FADE_TIME)
41+
}
42+
LaunchedEffect(Unit) {
43+
richTextState.config.unorderedListIndent = 4
44+
richTextState.config.orderedListIndent = 20
45+
}
46+
LaunchedEffect(primaryColor) {
47+
richTextState.config.linkColor = primaryColor
48+
}
49+
50+
SelectionContainer {
51+
RichText(
52+
modifier = modifier
53+
// Only animate size if the notes changes
54+
.then(if (contentUpdatedOnce) Modifier.animateContentSize() else Modifier)
55+
.alpha(alpha.value),
56+
style = MaterialTheme.typography.bodyMedium,
57+
state = richTextState,
58+
)
59+
}
60+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package eu.kanade.presentation.manga.components
2+
3+
import androidx.compose.foundation.layout.Arrangement
4+
import androidx.compose.foundation.layout.Column
5+
import androidx.compose.foundation.layout.Row
6+
import androidx.compose.foundation.layout.fillMaxWidth
7+
import androidx.compose.foundation.layout.padding
8+
import androidx.compose.foundation.layout.size
9+
import androidx.compose.foundation.shape.RoundedCornerShape
10+
import androidx.compose.material.icons.Icons
11+
import androidx.compose.material.icons.filled.EditNote
12+
import androidx.compose.material3.HorizontalDivider
13+
import androidx.compose.material3.Icon
14+
import androidx.compose.material3.MaterialTheme
15+
import androidx.compose.material3.Text
16+
import androidx.compose.runtime.Composable
17+
import androidx.compose.ui.Alignment
18+
import androidx.compose.ui.Modifier
19+
import androidx.compose.ui.graphics.Color
20+
import androidx.compose.ui.tooling.preview.PreviewLightDark
21+
import androidx.compose.ui.unit.dp
22+
import tachiyomi.i18n.MR
23+
import tachiyomi.presentation.core.components.material.Button
24+
import tachiyomi.presentation.core.components.material.ButtonDefaults
25+
import tachiyomi.presentation.core.components.material.padding
26+
import tachiyomi.presentation.core.i18n.stringResource
27+
28+
@Composable
29+
fun MangaNotesSection(
30+
content: String,
31+
expanded: Boolean,
32+
onEditNotes: () -> Unit,
33+
modifier: Modifier = Modifier,
34+
) {
35+
if (content.isBlank()) return
36+
Column(
37+
modifier = modifier.fillMaxWidth(),
38+
horizontalAlignment = Alignment.CenterHorizontally,
39+
) {
40+
MangaNotesDisplay(
41+
content = content,
42+
modifier = Modifier.fillMaxWidth(),
43+
)
44+
if (expanded) {
45+
Button(
46+
onClick = onEditNotes,
47+
colors = ButtonDefaults.buttonColors(
48+
containerColor = Color.Transparent,
49+
contentColor = MaterialTheme.colorScheme.primary,
50+
),
51+
shape = RoundedCornerShape(8.dp),
52+
modifier = Modifier
53+
.padding(horizontal = 16.dp, vertical = 4.dp),
54+
) {
55+
Row(
56+
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.extraSmall),
57+
verticalAlignment = Alignment.CenterVertically,
58+
) {
59+
Icon(
60+
imageVector = Icons.Filled.EditNote,
61+
contentDescription = null,
62+
modifier = Modifier
63+
.size(16.dp),
64+
)
65+
Text(
66+
stringResource(MR.strings.action_edit_notes),
67+
)
68+
}
69+
}
70+
}
71+
72+
HorizontalDivider(
73+
modifier = Modifier
74+
.padding(
75+
top = if (expanded) 0.dp else 12.dp,
76+
bottom = if (expanded) 16.dp else 12.dp,
77+
),
78+
)
79+
}
80+
}
81+
82+
@PreviewLightDark
83+
@Composable
84+
private fun MangaNotesSectionPreview() {
85+
MangaNotesSection(
86+
onEditNotes = {},
87+
expanded = true,
88+
content = "# Hello world\ntest1234 hi there!",
89+
)
90+
}

0 commit comments

Comments
 (0)