Initial markdown render test

This commit is contained in:
imkunet 2024-02-18 06:19:25 -05:00 committed by imkunet
parent be671b42ce
commit 28c23d184a
No known key found for this signature in database
GPG Key ID: 32E0ECFB90A68C42
13 changed files with 256 additions and 1 deletions

View File

@ -77,6 +77,7 @@ import tachiyomi.domain.manga.interactor.GetMangaWithChapters
import tachiyomi.domain.manga.interactor.NetworkToLocalManga
import tachiyomi.domain.manga.interactor.ResetViewerFlags
import tachiyomi.domain.manga.interactor.SetMangaChapterFlags
import tachiyomi.domain.manga.interactor.SetMangaNotes
import tachiyomi.domain.manga.repository.MangaRepository
import tachiyomi.domain.release.interactor.GetApplicationRelease
import tachiyomi.domain.release.service.ReleaseService
@ -122,6 +123,7 @@ class DomainModule : InjektModule {
addFactory { GetUpcomingManga(get()) }
addFactory { ResetViewerFlags(get()) }
addFactory { SetMangaChapterFlags(get()) }
addFactory { SetMangaNotes(get()) }
addFactory { FetchInterval(get()) }
addFactory { SetMangaDefaultChapterFlags(get(), get(), get()) }
addFactory { SetMangaViewerFlags(get()) }

View File

@ -0,0 +1,89 @@
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.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.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.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.AppBarTitle
import eu.kanade.tachiyomi.ui.manga.notes.MangaNotesScreenState
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.material.ExtendedFloatingActionButton
import tachiyomi.presentation.core.components.material.Scaffold
import tachiyomi.presentation.core.components.material.padding
import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.screens.EmptyScreen
@Composable
fun MangaNotesScreen(
state: MangaNotesScreenState.Success,
navigateUp: () -> Unit,
beginEditing: () -> Unit,
endEditing: () -> Unit,
) {
Scaffold(
topBar = { scrollBehavior ->
AppBar(
titleContent = {
AppBarTitle(title = stringResource(MR.strings.action_notes), subtitle = state.title)
},
navigateUp = navigateUp,
scrollBehavior = scrollBehavior,
)
},
floatingActionButton = {
AnimatedVisibility(
true,
enter = fadeIn(),
exit = fadeOut(),
) {
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,
)
}
},
) { paddingValues ->
if (state.isEmpty || state.content == null) {
EmptyScreen(
stringRes = MR.strings.information_no_notes,
modifier = Modifier.padding(paddingValues),
)
return@Scaffold
}
RichText(
modifier = Modifier
.fillMaxWidth()
.padding(
horizontal = MaterialTheme.padding.small,
vertical = paddingValues.calculateTopPadding() + MaterialTheme.padding.small,
),
style = RichTextStyle(
stringStyle = RichTextStringStyle(
linkStyle = SpanStyle(color = MaterialTheme.colorScheme.primary),
),
),
) {
Markdown(content = state.content)
}
}
}

View File

@ -112,6 +112,7 @@ fun MangaScreen(
onEditCategoryClicked: (() -> Unit)?,
onEditFetchIntervalClicked: (() -> Unit)?,
onMigrateClicked: (() -> Unit)?,
onNotesClicked: () -> Unit,
// For bottom action menu
onMultiBookmarkClicked: (List<Chapter>, bookmarked: Boolean) -> Unit,
@ -160,6 +161,7 @@ fun MangaScreen(
onEditCategoryClicked = onEditCategoryClicked,
onEditIntervalClicked = onEditFetchIntervalClicked,
onMigrateClicked = onMigrateClicked,
onClickNotes = onNotesClicked,
onMultiBookmarkClicked = onMultiBookmarkClicked,
onMultiMarkAsReadClicked = onMultiMarkAsReadClicked,
onMarkPreviousAsReadClicked = onMarkPreviousAsReadClicked,
@ -195,6 +197,7 @@ fun MangaScreen(
onEditCategoryClicked = onEditCategoryClicked,
onEditIntervalClicked = onEditFetchIntervalClicked,
onMigrateClicked = onMigrateClicked,
onClickNotes = onNotesClicked,
onMultiBookmarkClicked = onMultiBookmarkClicked,
onMultiMarkAsReadClicked = onMultiMarkAsReadClicked,
onMarkPreviousAsReadClicked = onMarkPreviousAsReadClicked,
@ -240,6 +243,7 @@ private fun MangaScreenSmallImpl(
onEditCategoryClicked: (() -> Unit)?,
onEditIntervalClicked: (() -> Unit)?,
onMigrateClicked: (() -> Unit)?,
onClickNotes: () -> Unit,
// For bottom action menu
onMultiBookmarkClicked: (List<Chapter>, bookmarked: Boolean) -> Unit,
@ -305,6 +309,7 @@ private fun MangaScreenSmallImpl(
onClickEditCategory = onEditCategoryClicked,
onClickRefresh = onRefresh,
onClickMigrate = onMigrateClicked,
onClickNotes = onClickNotes,
actionModeCounter = selectedChapterCount,
onSelectAll = { onAllChapterSelected(true) },
onInvertSelection = { onInvertSelection() },
@ -484,6 +489,7 @@ fun MangaScreenLargeImpl(
onEditCategoryClicked: (() -> Unit)?,
onEditIntervalClicked: (() -> Unit)?,
onMigrateClicked: (() -> Unit)?,
onClickNotes: () -> Unit,
// For bottom action menu
onMultiBookmarkClicked: (List<Chapter>, bookmarked: Boolean) -> Unit,
@ -542,6 +548,7 @@ fun MangaScreenLargeImpl(
onClickEditCategory = onEditCategoryClicked,
onClickRefresh = onRefresh,
onClickMigrate = onMigrateClicked,
onClickNotes = onClickNotes,
actionModeCounter = selectedChapterCount,
onSelectAll = { onAllChapterSelected(true) },
onInvertSelection = { onInvertSelection() },

View File

@ -44,6 +44,7 @@ fun MangaToolbar(
onClickEditCategory: (() -> Unit)?,
onClickRefresh: () -> Unit,
onClickMigrate: (() -> Unit)?,
onClickNotes: () -> Unit,
// For action mode
actionModeCounter: Int,
@ -149,6 +150,13 @@ fun MangaToolbar(
),
)
}
add(
AppBar.OverflowAction(
title = stringResource(MR.strings.action_notes),
onClick = onClickNotes,
)
)
}
.build(),
)

View File

@ -50,6 +50,7 @@ import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreen
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchScreen
import eu.kanade.tachiyomi.ui.category.CategoryScreen
import eu.kanade.tachiyomi.ui.home.HomeScreen
import eu.kanade.tachiyomi.ui.manga.notes.MangaNotesScreen
import eu.kanade.tachiyomi.ui.manga.track.TrackInfoDialogHomeScreen
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import eu.kanade.tachiyomi.ui.setting.SettingsScreen
@ -164,6 +165,7 @@ class MangaScreen(
onMigrateClicked = {
navigator.push(MigrateSearchScreen(successState.manga.id))
}.takeIf { successState.manga.favorite },
onNotesClicked = { navigator.push(MangaNotesScreen(successState.manga.id)) },
onMultiBookmarkClicked = screenModel::bookmarkChapters,
onMultiMarkAsReadClicked = screenModel::markChaptersRead,
onMarkPreviousAsReadClicked = screenModel::markPreviousChapterRead,

View File

@ -0,0 +1,42 @@
package eu.kanade.tachiyomi.ui.manga.notes
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
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.presentation.core.screens.LoadingScreen
class MangaNotesScreen(private val mangaId: Long) : Screen() {
@Composable
override fun Content() {
val navigator = LocalNavigator.currentOrThrow
val screenModel = rememberScreenModel { MangaNotesScreenModel(mangaId = mangaId) }
val state by screenModel.state.collectAsState()
DisposableEffect(Unit) {
onDispose {
}
}
if (state is MangaNotesScreenState.Loading) {
LoadingScreen()
return
}
val successState = state as MangaNotesScreenState.Success
MangaNotesScreen(
state = successState,
navigateUp = navigator::pop,
beginEditing = { screenModel.beginEditing() },
endEditing = { screenModel.endEditing() },
)
}
}

View File

@ -0,0 +1,75 @@
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.domain.manga.interactor.SetMangaNotes
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class MangaNotesScreenModel(
val mangaId: Long,
getManga: GetManga = Injekt.get(),
setMangaNotes: SetMangaNotes = Injekt.get(),
) : StateScreenModel<MangaNotesScreenState>(MangaNotesScreenState.Loading) {
private val _events: Channel<CategoryEvent> = Channel()
val events = _events.receiveAsFlow()
init {
screenModelScope.launch {
getManga.subscribe(mangaId).collectLatest { manga ->
mutableState.update {
MangaNotesScreenState.Success(
content = manga.notes,
title = manga.title,
)
}
}
}
}
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)
}
}
}
}
sealed interface MangaNotesScreenState {
@Immutable
data object Loading : MangaNotesScreenState
@Immutable
data class Success(
val content: String?,
val title: String,
val editing: Boolean = false,
) : MangaNotesScreenState {
val isEmpty: Boolean
get() = content.isNullOrEmpty()
}
}

View File

@ -31,6 +31,7 @@ object MangaMapper {
version: Long,
@Suppress("UNUSED_PARAMETER")
isSyncing: Long,
notes: String?,
): Manga = Manga(
id = id,
source = source,
@ -55,6 +56,7 @@ object MangaMapper {
lastModifiedAt = lastModifiedAt,
favoriteModifiedAt = favoriteModifiedAt,
version = version,
notes = notes,
)
fun mapLibraryManga(
@ -82,6 +84,7 @@ object MangaMapper {
favoriteModifiedAt: Long?,
version: Long,
isSyncing: Long,
notes: String?,
totalCount: Long,
readCount: Double,
latestUpload: Long,
@ -115,6 +118,7 @@ object MangaMapper {
favoriteModifiedAt,
version,
isSyncing,
notes,
),
category = category,
totalChapters = totalCount,

View File

@ -27,7 +27,8 @@ CREATE TABLE mangas(
last_modified_at INTEGER NOT NULL DEFAULT 0,
favorite_modified_at INTEGER,
version INTEGER NOT NULL DEFAULT 0,
is_syncing INTEGER NOT NULL DEFAULT 0
is_syncing INTEGER NOT NULL DEFAULT 0,
notes TEXT
);
CREATE INDEX library_favorite_index ON mangas(favorite) WHERE favorite = 1;

View File

@ -0,0 +1,19 @@
package tachiyomi.domain.manga.interactor
import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.manga.model.MangaUpdate
import tachiyomi.domain.manga.repository.MangaRepository
class SetMangaNotes(
private val mangaRepository: MangaRepository,
) {
suspend fun awaitSetNotes(manga: Manga, notes: String): Boolean {
return mangaRepository.update(
MangaUpdate(
id = manga.id,
notes = notes,
),
)
}
}

View File

@ -32,6 +32,7 @@ data class Manga(
val lastModifiedAt: Long,
val favoriteModifiedAt: Long?,
val version: Long,
val notes: String?,
) : Serializable {
val expectedNextUpdate: Instant?
@ -126,6 +127,7 @@ data class Manga(
lastModifiedAt = 0L,
favoriteModifiedAt = null,
version = 0L,
notes = null,
)
}
}

View File

@ -24,6 +24,7 @@ data class MangaUpdate(
val updateStrategy: UpdateStrategy? = null,
val initialized: Boolean? = null,
val version: Long? = null,
val notes: String? = null,
)
fun Manga.toMangaUpdate(): MangaUpdate {
@ -49,5 +50,6 @@ fun Manga.toMangaUpdate(): MangaUpdate {
updateStrategy = updateStrategy,
initialized = initialized,
version = version,
notes = notes,
)
}

View File

@ -146,6 +146,7 @@
<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_all_for_series">Move series to bottom</string>
<string name="action_notes">Notes</string>
<string name="action_install">Install</string>
<string name="action_share">Share</string>
<string name="action_save">Save</string>
@ -912,6 +913,7 @@
<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_required_plain">*required</string>
<string name="information_no_notes">There are no notes here yet!</string>
<!-- Do not translate "WebView" -->
<string name="information_webview_required">WebView is required for the app to function</string>
<!-- Do not translate "WebView" -->