From 7df12c68fdf30a80970e34bcde29eb2d5917dd46 Mon Sep 17 00:00:00 2001 From: Jobobby04 Date: Sat, 26 Nov 2022 13:36:06 -0500 Subject: [PATCH] Use Voyager for a few screens --- .../tachiyomi/ui/manga/MangaController.kt | 6 +- .../kanade/tachiyomi/ui/manga/MangaScreen.kt | 8 +- .../exh/pagepreview/PagePreviewController.kt | 27 +--- .../java/exh/pagepreview/PagePreviewScreen.kt | 40 ++++++ ...Presenter.kt => PagePreviewScreenModel.kt} | 26 ++-- .../java/exh/ui/base/CoroutinePresenter.kt | 68 ---------- .../exh/ui/metadata/MetadataViewController.kt | 122 ++---------------- .../exh/ui/metadata/MetadataViewPresenter.kt | 48 ------- .../exh/ui/metadata/MetadataViewScreen.kt | 100 ++++++++++++++ .../ui/metadata/MetadataViewScreenModel.kt | 55 ++++++++ .../ui/smartsearch/SmartSearchController.kt | 89 +------------ .../exh/ui/smartsearch/SmartSearchScreen.kt | 88 +++++++++++++ ...Presenter.kt => SmartSearchScreenModel.kt} | 27 ++-- 13 files changed, 333 insertions(+), 371 deletions(-) create mode 100644 app/src/main/java/exh/pagepreview/PagePreviewScreen.kt rename app/src/main/java/exh/pagepreview/{PagePreviewPresenter.kt => PagePreviewScreenModel.kt} (84%) delete mode 100644 app/src/main/java/exh/ui/base/CoroutinePresenter.kt delete mode 100644 app/src/main/java/exh/ui/metadata/MetadataViewPresenter.kt create mode 100644 app/src/main/java/exh/ui/metadata/MetadataViewScreen.kt create mode 100644 app/src/main/java/exh/ui/metadata/MetadataViewScreenModel.kt create mode 100644 app/src/main/java/exh/ui/smartsearch/SmartSearchScreen.kt rename app/src/main/java/exh/ui/smartsearch/{SmartSearchPresenter.kt => SmartSearchScreenModel.kt} (70%) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt index 26c4de22e..007c64bb8 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt @@ -6,7 +6,7 @@ import androidx.core.os.bundleOf import cafe.adriel.voyager.navigator.Navigator import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController import eu.kanade.tachiyomi.ui.browse.source.SourcesController -import eu.kanade.tachiyomi.util.system.getParcelableCompat +import eu.kanade.tachiyomi.util.system.getSerializableCompat class MangaController : BasicFullComposeController { @@ -33,9 +33,7 @@ class MangaController : BasicFullComposeController { // SY --> val smartSearchConfig: SourcesController.SmartSearchConfig? - get() = args.getParcelableCompat( - SMART_SEARCH_CONFIG_EXTRA, - ) + get() = args.getSerializableCompat(SMART_SEARCH_CONFIG_EXTRA) // SY <-- @Composable 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 13aee8ae6..982a8ead4 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 @@ -86,7 +86,7 @@ import exh.recs.RecommendsController import exh.source.MERGED_SOURCE_ID import exh.source.getMainSource import exh.source.isMdBasedSource -import exh.ui.metadata.MetadataViewController +import exh.ui.metadata.MetadataViewScreen import kotlinx.coroutines.CancellationException import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -166,7 +166,7 @@ class MangaScreen( onEditCategoryClicked = screenModel::promptChangeCategories.takeIf { successState.manga.favorite }, onMigrateClicked = { migrateManga(router, screenModel.manga!!) }.takeIf { successState.manga.favorite }, // SY --> - onMetadataViewerClicked = { openMetadataViewer(router, successState.manga) }, + onMetadataViewerClicked = { openMetadataViewer(navigator, successState.manga) }, onEditInfoClicked = screenModel::showEditMangaInfoDialog, onRecommendClicked = { openRecommends(context, router, screenModel.source?.getMainSource(), successState.manga) }, onMergedSettingsClicked = screenModel::showEditMergedSettingsDialog, @@ -446,8 +446,8 @@ class MangaScreen( } // SY --> - private fun openMetadataViewer(router: Router, manga: Manga) { - router.pushController(MetadataViewController(manga)) + private fun openMetadataViewer(navigator: Navigator, manga: Manga) { + navigator.push(MetadataViewScreen(manga.id, manga.source)) } private fun openMergedMangaWebview(context: Context, mergedMangaData: MergedMangaData) { diff --git a/app/src/main/java/exh/pagepreview/PagePreviewController.kt b/app/src/main/java/exh/pagepreview/PagePreviewController.kt index 604aa1942..68a5f5ce6 100644 --- a/app/src/main/java/exh/pagepreview/PagePreviewController.kt +++ b/app/src/main/java/exh/pagepreview/PagePreviewController.kt @@ -2,13 +2,11 @@ package exh.pagepreview import android.os.Bundle import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState import androidx.core.os.bundleOf -import eu.kanade.tachiyomi.ui.base.controller.FullComposeController -import eu.kanade.tachiyomi.ui.reader.ReaderActivity -import exh.pagepreview.components.PagePreviewScreen +import cafe.adriel.voyager.navigator.Navigator +import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController -class PagePreviewController : FullComposeController { +class PagePreviewController : BasicFullComposeController { @Suppress("unused") constructor(bundle: Bundle? = null) : super(bundle) @@ -17,26 +15,9 @@ class PagePreviewController : FullComposeController { bundleOf(MANGA_ID to mangaId), ) - override fun createPresenter() = PagePreviewPresenter(args.getLong(MANGA_ID, -1)) - @Composable override fun ComposeContent() { - PagePreviewScreen( - state = presenter.state.collectAsState().value, - pageDialogOpen = presenter.pageDialogOpen, - onPageSelected = presenter::moveToPage, - onOpenPage = this::openPage, - onOpenPageDialog = { presenter.pageDialogOpen = true }, - onDismissPageDialog = { presenter.pageDialogOpen = false }, - navigateUp = router::popCurrentController, - ) - } - - fun openPage(page: Int) { - val state = presenter.state.value as? PagePreviewState.Success ?: return - activity?.run { - startActivity(ReaderActivity.newIntent(this, state.manga.id, state.chapter.id, page)) - } + Navigator(screen = PagePreviewScreen(args.getLong(MANGA_ID, -1))) } companion object { diff --git a/app/src/main/java/exh/pagepreview/PagePreviewScreen.kt b/app/src/main/java/exh/pagepreview/PagePreviewScreen.kt new file mode 100644 index 000000000..baf61ce4d --- /dev/null +++ b/app/src/main/java/exh/pagepreview/PagePreviewScreen.kt @@ -0,0 +1,40 @@ +package exh.pagepreview + +import android.content.Context +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.platform.LocalContext +import cafe.adriel.voyager.core.model.rememberScreenModel +import cafe.adriel.voyager.core.screen.Screen +import cafe.adriel.voyager.navigator.currentOrThrow +import eu.kanade.presentation.util.LocalRouter +import eu.kanade.tachiyomi.ui.reader.ReaderActivity +import exh.pagepreview.components.PagePreviewScreen + +class PagePreviewScreen(private val mangaId: Long) : Screen { + + @Composable + override fun Content() { + val screenModel = rememberScreenModel { PagePreviewScreenModel(mangaId) } + val context = LocalContext.current + val state by screenModel.state.collectAsState() + val router = LocalRouter.currentOrThrow + PagePreviewScreen( + state = state, + pageDialogOpen = screenModel.pageDialogOpen, + onPageSelected = screenModel::moveToPage, + onOpenPage = { openPage(context, state, it) }, + onOpenPageDialog = { screenModel.pageDialogOpen = true }, + onDismissPageDialog = { screenModel.pageDialogOpen = false }, + navigateUp = router::popCurrentController, + ) + } + + fun openPage(context: Context, state: PagePreviewState, page: Int) { + if (state !is PagePreviewState.Success) return + context.run { + startActivity(ReaderActivity.newIntent(this, state.manga.id, state.chapter.id, page)) + } + } +} diff --git a/app/src/main/java/exh/pagepreview/PagePreviewPresenter.kt b/app/src/main/java/exh/pagepreview/PagePreviewScreenModel.kt similarity index 84% rename from app/src/main/java/exh/pagepreview/PagePreviewPresenter.kt rename to app/src/main/java/exh/pagepreview/PagePreviewScreenModel.kt index 61ac9ab99..8fe01d327 100644 --- a/app/src/main/java/exh/pagepreview/PagePreviewPresenter.kt +++ b/app/src/main/java/exh/pagepreview/PagePreviewScreenModel.kt @@ -1,9 +1,10 @@ package exh.pagepreview -import android.os.Bundle import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue +import cafe.adriel.voyager.core.model.StateScreenModel +import cafe.adriel.voyager.core.model.coroutineScope import eu.kanade.domain.chapter.model.Chapter import eu.kanade.domain.history.interactor.GetNextChapters import eu.kanade.domain.manga.interactor.GetManga @@ -12,10 +13,8 @@ import eu.kanade.domain.manga.model.Manga import eu.kanade.domain.manga.model.PagePreview import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.SourceManager -import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter import eu.kanade.tachiyomi.util.lang.launchIO import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.onEach @@ -23,29 +22,24 @@ import kotlinx.coroutines.flow.update import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get -class PagePreviewPresenter( +class PagePreviewScreenModel( private val mangaId: Long, private val getPagePreviews: GetPagePreviews = Injekt.get(), private val getManga: GetManga = Injekt.get(), private val getNextChapters: GetNextChapters = Injekt.get(), private val sourceManager: SourceManager = Injekt.get(), -) : BasePresenter() { - - private val _state = MutableStateFlow(PagePreviewState.Loading) - val state = _state.asStateFlow() +) : StateScreenModel(PagePreviewState.Loading) { private val page = MutableStateFlow(1) var pageDialogOpen by mutableStateOf(false) - override fun onCreate(savedState: Bundle?) { - super.onCreate(savedState) - - presenterScope.launchIO { + init { + coroutineScope.launchIO { val manga = getManga.await(mangaId)!! val chapter = getNextChapters.await(mangaId, onlyUnread = false).lastOrNull() if (chapter == null) { - _state.update { + mutableState.update { PagePreviewState.Error(Exception("No chapters found")) } return@launchIO @@ -56,10 +50,10 @@ class PagePreviewPresenter( when ( val previews = getPagePreviews.await(manga, source, page) ) { - is GetPagePreviews.Result.Error -> _state.update { + is GetPagePreviews.Result.Error -> mutableState.update { PagePreviewState.Error(previews.error) } - is GetPagePreviews.Result.Success -> _state.update { + is GetPagePreviews.Result.Success -> mutableState.update { when (it) { PagePreviewState.Loading, is PagePreviewState.Error -> { PagePreviewState.Success( @@ -84,7 +78,7 @@ class PagePreviewPresenter( } } .catch { e -> - _state.update { + mutableState.update { PagePreviewState.Error(e) } } diff --git a/app/src/main/java/exh/ui/base/CoroutinePresenter.kt b/app/src/main/java/exh/ui/base/CoroutinePresenter.kt deleted file mode 100644 index b6ee26642..000000000 --- a/app/src/main/java/exh/ui/base/CoroutinePresenter.kt +++ /dev/null @@ -1,68 +0,0 @@ -package exh.ui.base - -import android.os.Bundle -import androidx.annotation.CallSuper -import eu.kanade.tachiyomi.util.lang.launchUI -import eu.kanade.tachiyomi.util.lang.withUIContext -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.MainScope -import kotlinx.coroutines.cancel -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flowOn -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.mapNotNull -import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.isActive -import nucleus.presenter.Presenter -import kotlin.coroutines.CoroutineContext -import kotlin.coroutines.EmptyCoroutineContext - -@Suppress("DEPRECATION", "unused") -open class CoroutinePresenter( - private val scope: () -> CoroutineScope = ::MainScope, -) : Presenter() { - var presenterScope: CoroutineScope = scope() - - @CallSuper - override fun onCreate(savedState: Bundle?) { - super.onCreate(savedState) - if (!presenterScope.isActive) { - presenterScope = scope() - } - } - - @Suppress("DeprecatedCallableAddReplaceWith") - @Deprecated("Use launchInView, Flow.inView, Flow.mapView") - override fun getView(): V? { - return super.getView() - } - - fun launchInView(block: (CoroutineScope, V) -> Unit) = presenterScope.launchUI { - view?.let { block.invoke(this, it) } - } - - inline fun Flow.inView(crossinline block: (V, F) -> Unit) = onEach { - withUIContext { - view?.let { view -> block(view, it) } - } - } - - inline fun Flow.mapView(crossinline block: (V, F) -> P): Flow

{ - return mapNotNull { - withUIContext { - view?.let { view -> block(view, it) } - } - } - } - - fun Flow<*>.launchUnderContext(context: CoroutineContext = EmptyCoroutineContext) = flowOn(context) - .launch() - - fun Flow<*>.launch() = launchIn(presenterScope) - - @CallSuper - override fun onDestroy() { - super.onDestroy() - presenterScope.cancel() - } -} diff --git a/app/src/main/java/exh/ui/metadata/MetadataViewController.kt b/app/src/main/java/exh/ui/metadata/MetadataViewController.kt index a4f2b1c9a..5b6b6fefb 100644 --- a/app/src/main/java/exh/ui/metadata/MetadataViewController.kt +++ b/app/src/main/java/exh/ui/metadata/MetadataViewController.kt @@ -1,128 +1,30 @@ package exh.ui.metadata import android.os.Bundle -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.asPaddingValues -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.navigationBars -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.lazy.items -import androidx.compose.material3.LocalContentColor -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.unit.dp import androidx.core.os.bundleOf -import eu.kanade.domain.manga.interactor.GetManga +import cafe.adriel.voyager.navigator.Navigator import eu.kanade.domain.manga.model.Manga -import eu.kanade.presentation.components.AppBar -import eu.kanade.presentation.components.EmptyScreen -import eu.kanade.presentation.components.LoadingScreen -import eu.kanade.presentation.components.Scaffold -import eu.kanade.presentation.components.ScrollbarLazyColumn -import eu.kanade.presentation.util.clickableNoIndication -import eu.kanade.presentation.util.plus -import eu.kanade.presentation.util.topSmallPaddingValues -import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.source.Source -import eu.kanade.tachiyomi.source.SourceManager -import eu.kanade.tachiyomi.ui.base.controller.FullComposeController -import eu.kanade.tachiyomi.ui.manga.MangaController -import eu.kanade.tachiyomi.util.system.copyToClipboard -import kotlinx.coroutines.runBlocking -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get +import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController -class MetadataViewController : FullComposeController { +class MetadataViewController : BasicFullComposeController { constructor(manga: Manga) : super( bundleOf( - MangaController.MANGA_EXTRA to manga.id, + MANGA_EXTRA to manga.id, + SOURCE_EXTRA to manga.source, ), - ) { - this.manga = manga - source = Injekt.get().getOrStub(manga.source) - } - - constructor(mangaId: Long) : this( - runBlocking { Injekt.get().await(mangaId)!! }, ) @Suppress("unused") - constructor(bundle: Bundle) : this(bundle.getLong(MangaController.MANGA_EXTRA)) - - var manga: Manga? = null - private set - var source: Source? = null - private set - - override fun createPresenter(): MetadataViewPresenter { - return MetadataViewPresenter(manga!!, source!!) - } + constructor(bundle: Bundle) : super(bundle) @Composable override fun ComposeContent() { - val state by presenter.state.collectAsState() - Scaffold( - topBar = { scrollBehavior -> - AppBar( - title = manga?.title, - navigateUp = router::popCurrentController, - scrollBehavior = scrollBehavior, - ) - }, - ) { paddingValues -> - when (val state = state) { - MetadataViewState.Loading -> LoadingScreen() - MetadataViewState.MetadataNotFound -> EmptyScreen(R.string.no_results_found) - MetadataViewState.SourceNotFound -> EmptyScreen(R.string.source_empty_screen) - is MetadataViewState.Success -> { - val context = LocalContext.current - val items = remember(state.meta) { state.meta.getExtraInfoPairs(context) } - ScrollbarLazyColumn( - contentPadding = paddingValues + WindowInsets.navigationBars.asPaddingValues() + topSmallPaddingValues, - ) { - items(items) { (title, text) -> - Row( - Modifier - .fillMaxWidth() - .clickableNoIndication( - onLongClick = { - context.copyToClipboard( - title, - text, - ) - }, - onClick = {}, - ) - .padding(vertical = 8.dp), - ) { - Text( - title, - modifier = Modifier - .width(140.dp) - .padding(start = 16.dp), - style = MaterialTheme.typography.bodyMedium, - ) - Text( - text, - modifier = Modifier - .fillMaxWidth() - .padding(start = 8.dp, end = 8.dp), - style = MaterialTheme.typography.bodyMedium, - color = LocalContentColor.current.copy(alpha = 0.7F), - ) - } - } - } - } - } - } + Navigator(screen = MetadataViewScreen(args.getLong(MANGA_EXTRA), args.getLong(SOURCE_EXTRA))) + } + + companion object { + const val MANGA_EXTRA = "manga" + const val SOURCE_EXTRA = "source" } } diff --git a/app/src/main/java/exh/ui/metadata/MetadataViewPresenter.kt b/app/src/main/java/exh/ui/metadata/MetadataViewPresenter.kt deleted file mode 100644 index a2f7bfaa7..000000000 --- a/app/src/main/java/exh/ui/metadata/MetadataViewPresenter.kt +++ /dev/null @@ -1,48 +0,0 @@ -package exh.ui.metadata - -import android.os.Bundle -import eu.kanade.domain.manga.interactor.GetFlatMetadataById -import eu.kanade.domain.manga.model.Manga -import eu.kanade.tachiyomi.source.Source -import eu.kanade.tachiyomi.source.online.MetadataSource -import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter -import eu.kanade.tachiyomi.util.lang.launchNonCancellable -import exh.metadata.metadata.base.RaisedSearchMetadata -import exh.source.getMainSource -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get - -class MetadataViewPresenter( - val manga: Manga, - val source: Source, - private val getFlatMetadataById: GetFlatMetadataById = Injekt.get(), -) : BasePresenter() { - private val _state = MutableStateFlow(MetadataViewState.Loading) - val state = _state.asStateFlow() - - override fun onCreate(savedState: Bundle?) { - super.onCreate(savedState) - - presenterScope.launchNonCancellable { - val metadataSource = source.getMainSource>() - if (metadataSource == null) { - _state.value = MetadataViewState.SourceNotFound - return@launchNonCancellable - } - - _state.value = when (val flatMetadata = getFlatMetadataById.await(manga.id)) { - null -> MetadataViewState.MetadataNotFound - else -> MetadataViewState.Success(flatMetadata.raise(metadataSource.metaClass)) - } - } - } -} - -sealed class MetadataViewState { - object Loading : MetadataViewState() - data class Success(val meta: RaisedSearchMetadata) : MetadataViewState() - object MetadataNotFound : MetadataViewState() - object SourceNotFound : MetadataViewState() -} diff --git a/app/src/main/java/exh/ui/metadata/MetadataViewScreen.kt b/app/src/main/java/exh/ui/metadata/MetadataViewScreen.kt new file mode 100644 index 000000000..6214a3a09 --- /dev/null +++ b/app/src/main/java/exh/ui/metadata/MetadataViewScreen.kt @@ -0,0 +1,100 @@ +package exh.ui.metadata + +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.asPaddingValues +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.navigationBars +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp +import cafe.adriel.voyager.core.model.rememberScreenModel +import cafe.adriel.voyager.core.screen.Screen +import cafe.adriel.voyager.navigator.LocalNavigator +import cafe.adriel.voyager.navigator.currentOrThrow +import eu.kanade.presentation.components.AppBar +import eu.kanade.presentation.components.EmptyScreen +import eu.kanade.presentation.components.LoadingScreen +import eu.kanade.presentation.components.Scaffold +import eu.kanade.presentation.components.ScrollbarLazyColumn +import eu.kanade.presentation.util.clickableNoIndication +import eu.kanade.presentation.util.plus +import eu.kanade.presentation.util.topSmallPaddingValues +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.util.system.copyToClipboard + +class MetadataViewScreen(private val mangaId: Long, private val sourceId: Long) : Screen { + + @Composable + override fun Content() { + val screenModel = rememberScreenModel { MetadataViewScreenModel(mangaId, sourceId) } + val navigator = LocalNavigator.currentOrThrow + + val state by screenModel.state.collectAsState() + Scaffold( + topBar = { scrollBehavior -> + AppBar( + title = screenModel.manga.collectAsState().value?.title, + navigateUp = navigator::pop, + scrollBehavior = scrollBehavior, + ) + }, + ) { paddingValues -> + when (val state = state) { + MetadataViewState.Loading -> LoadingScreen() + MetadataViewState.MetadataNotFound -> EmptyScreen(R.string.no_results_found) + MetadataViewState.SourceNotFound -> EmptyScreen(R.string.source_empty_screen) + is MetadataViewState.Success -> { + val context = LocalContext.current + val items = remember(state.meta) { state.meta.getExtraInfoPairs(context) } + ScrollbarLazyColumn( + contentPadding = paddingValues + WindowInsets.navigationBars.asPaddingValues() + topSmallPaddingValues, + ) { + items(items) { (title, text) -> + Row( + Modifier + .fillMaxWidth() + .clickableNoIndication( + onLongClick = { + context.copyToClipboard( + title, + text, + ) + }, + onClick = {}, + ) + .padding(vertical = 8.dp), + ) { + Text( + title, + modifier = Modifier + .width(140.dp) + .padding(start = 16.dp), + style = MaterialTheme.typography.bodyMedium, + ) + Text( + text, + modifier = Modifier + .fillMaxWidth() + .padding(start = 8.dp, end = 8.dp), + style = MaterialTheme.typography.bodyMedium, + color = LocalContentColor.current.copy(alpha = 0.7F), + ) + } + } + } + } + } + } + } +} diff --git a/app/src/main/java/exh/ui/metadata/MetadataViewScreenModel.kt b/app/src/main/java/exh/ui/metadata/MetadataViewScreenModel.kt new file mode 100644 index 000000000..74f4925c5 --- /dev/null +++ b/app/src/main/java/exh/ui/metadata/MetadataViewScreenModel.kt @@ -0,0 +1,55 @@ +package exh.ui.metadata + +import cafe.adriel.voyager.core.model.StateScreenModel +import cafe.adriel.voyager.core.model.coroutineScope +import eu.kanade.domain.manga.interactor.GetFlatMetadataById +import eu.kanade.domain.manga.interactor.GetManga +import eu.kanade.domain.manga.model.Manga +import eu.kanade.tachiyomi.source.SourceManager +import eu.kanade.tachiyomi.source.online.MetadataSource +import eu.kanade.tachiyomi.util.lang.launchIO +import exh.metadata.metadata.base.RaisedSearchMetadata +import exh.source.getMainSource +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get + +class MetadataViewScreenModel( + val mangaId: Long, + val sourceId: Long, + private val getFlatMetadataById: GetFlatMetadataById = Injekt.get(), + private val sourceManager: SourceManager = Injekt.get(), + private val getManga: GetManga = Injekt.get(), +) : StateScreenModel(MetadataViewState.Loading) { + private val _manga = MutableStateFlow(null) + val manga = _manga.asStateFlow() + + init { + coroutineScope.launchIO { + _manga.value = getManga.await(mangaId) + } + } + + init { + coroutineScope.launchIO { + val metadataSource = sourceManager.get(sourceId)?.getMainSource>() + if (metadataSource == null) { + mutableState.value = MetadataViewState.SourceNotFound + return@launchIO + } + + mutableState.value = when (val flatMetadata = getFlatMetadataById.await(mangaId)) { + null -> MetadataViewState.MetadataNotFound + else -> MetadataViewState.Success(flatMetadata.raise(metadataSource.metaClass)) + } + } + } +} + +sealed class MetadataViewState { + object Loading : MetadataViewState() + data class Success(val meta: RaisedSearchMetadata) : MetadataViewState() + object MetadataNotFound : MetadataViewState() + object SourceNotFound : MetadataViewState() +} diff --git a/app/src/main/java/exh/ui/smartsearch/SmartSearchController.kt b/app/src/main/java/exh/ui/smartsearch/SmartSearchController.kt index 85b60ad73..80990f166 100644 --- a/app/src/main/java/exh/ui/smartsearch/SmartSearchController.kt +++ b/app/src/main/java/exh/ui/smartsearch/SmartSearchController.kt @@ -1,42 +1,16 @@ package exh.ui.smartsearch import android.os.Bundle -import android.view.View -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.material3.CircularProgressIndicator -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.res.stringResource -import androidx.compose.ui.unit.dp import androidx.core.os.bundleOf -import eu.kanade.presentation.components.AppBar -import eu.kanade.presentation.components.Scaffold -import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.source.CatalogueSource -import eu.kanade.tachiyomi.source.SourceManager -import eu.kanade.tachiyomi.ui.base.controller.FullComposeController -import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction +import cafe.adriel.voyager.navigator.Navigator +import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController import eu.kanade.tachiyomi.ui.browse.source.SourcesController -import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController -import eu.kanade.tachiyomi.ui.manga.MangaController -import eu.kanade.tachiyomi.util.system.getParcelableCompat -import eu.kanade.tachiyomi.util.system.toast -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach -import uy.kohesive.injekt.injectLazy +import eu.kanade.tachiyomi.util.system.getSerializableCompat -class SmartSearchController(bundle: Bundle) : FullComposeController() { - private val sourceManager: SourceManager by injectLazy() - - private val source = sourceManager.get(bundle.getLong(ARG_SOURCE_ID, -1)) as CatalogueSource - private val smartSearchConfig = bundle.getParcelableCompat(ARG_SMART_SEARCH_CONFIG)!! +class SmartSearchController(bundle: Bundle) : BasicFullComposeController() { + private val sourceId = bundle.getLong(ARG_SOURCE_ID, -1) + private val smartSearchConfig = bundle.getSerializableCompat(ARG_SMART_SEARCH_CONFIG)!! constructor(sourceId: Long, smartSearchConfig: SourcesController.SmartSearchConfig) : this( bundleOf( @@ -45,58 +19,9 @@ class SmartSearchController(bundle: Bundle) : FullComposeController - AppBar( - title = source.name, - navigateUp = router::popCurrentController, - scrollBehavior = scrollBehavior, - ) - }, - ) { - Column( - modifier = Modifier - .fillMaxSize() - .padding(it), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterVertically), - ) { - Text( - text = stringResource(R.string.searching_source), - style = MaterialTheme.typography.titleLarge, - ) - CircularProgressIndicator(modifier = Modifier.size(56.dp)) - } - } - } - - override fun onViewCreated(view: View) { - super.onViewCreated(view) - - presenter.smartSearchFlow - .onEach { results -> - if (results is SmartSearchPresenter.SearchResults.Found) { - val transaction = MangaController(results.manga.id, true, smartSearchConfig).withFadeTransaction() - router.replaceTopController(transaction) - } else { - if (results is SmartSearchPresenter.SearchResults.NotFound) { - applicationContext?.toast(R.string.could_not_find_manga) - } else { - applicationContext?.toast(R.string.automatic_search_error) - } - val transaction = BrowseSourceController( - source, - smartSearchConfig.origTitle, - smartSearchConfig, - ).withFadeTransaction() - router.replaceTopController(transaction) - } - } - .launchIn(viewScope) + Navigator(screen = SmartSearchScreen(sourceId, smartSearchConfig)) } companion object { diff --git a/app/src/main/java/exh/ui/smartsearch/SmartSearchScreen.kt b/app/src/main/java/exh/ui/smartsearch/SmartSearchScreen.kt new file mode 100644 index 000000000..58ac44abe --- /dev/null +++ b/app/src/main/java/exh/ui/smartsearch/SmartSearchScreen.kt @@ -0,0 +1,88 @@ +package exh.ui.smartsearch + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import cafe.adriel.voyager.core.model.rememberScreenModel +import cafe.adriel.voyager.core.screen.Screen +import cafe.adriel.voyager.navigator.currentOrThrow +import eu.kanade.presentation.components.AppBar +import eu.kanade.presentation.components.Scaffold +import eu.kanade.presentation.util.LocalRouter +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction +import eu.kanade.tachiyomi.ui.browse.source.SourcesController +import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController +import eu.kanade.tachiyomi.ui.manga.MangaController +import eu.kanade.tachiyomi.util.system.toast + +class SmartSearchScreen(private val sourceId: Long, private val smartSearchConfig: SourcesController.SmartSearchConfig) : Screen { + + @Composable + override fun Content() { + val screenModel = rememberScreenModel { SmartSearchScreenModel(sourceId, smartSearchConfig) } + + val router = LocalRouter.currentOrThrow + val context = LocalContext.current + val state by screenModel.state.collectAsState() + LaunchedEffect(state) { + val results = state + if (results != null) { + if (results is SmartSearchScreenModel.SearchResults.Found) { + val transaction = MangaController(results.manga.id, true, smartSearchConfig).withFadeTransaction() + router.replaceTopController(transaction) + } else { + if (results is SmartSearchScreenModel.SearchResults.NotFound) { + context.toast(R.string.could_not_find_manga) + } else { + context.toast(R.string.automatic_search_error) + } + val transaction = BrowseSourceController( + screenModel.source, + smartSearchConfig.origTitle, + smartSearchConfig, + ).withFadeTransaction() + router.replaceTopController(transaction) + } + } + } + + Scaffold( + topBar = { scrollBehavior -> + AppBar( + title = screenModel.source.name, + navigateUp = router::popCurrentController, + scrollBehavior = scrollBehavior, + ) + }, + ) { + Column( + modifier = Modifier + .fillMaxSize() + .padding(it), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterVertically), + ) { + Text( + text = stringResource(R.string.searching_source), + style = MaterialTheme.typography.titleLarge, + ) + CircularProgressIndicator(modifier = Modifier.size(56.dp)) + } + } + } +} diff --git a/app/src/main/java/exh/ui/smartsearch/SmartSearchPresenter.kt b/app/src/main/java/exh/ui/smartsearch/SmartSearchScreenModel.kt similarity index 70% rename from app/src/main/java/exh/ui/smartsearch/SmartSearchPresenter.kt rename to app/src/main/java/exh/ui/smartsearch/SmartSearchScreenModel.kt index 52ce1c2e3..1cd9e6b47 100644 --- a/app/src/main/java/exh/ui/smartsearch/SmartSearchPresenter.kt +++ b/app/src/main/java/exh/ui/smartsearch/SmartSearchScreenModel.kt @@ -1,35 +1,30 @@ package exh.ui.smartsearch -import android.os.Bundle +import cafe.adriel.voyager.core.model.StateScreenModel +import cafe.adriel.voyager.core.model.coroutineScope import eu.kanade.domain.manga.interactor.NetworkToLocalManga import eu.kanade.domain.manga.model.Manga import eu.kanade.tachiyomi.source.CatalogueSource -import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter +import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.ui.browse.source.SourcesController import eu.kanade.tachiyomi.util.lang.launchIO import exh.smartsearch.SmartSearchEngine import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.flow.asSharedFlow import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get -class SmartSearchPresenter( - private val source: CatalogueSource, +class SmartSearchScreenModel( + private val sourceId: Long, private val config: SourcesController.SmartSearchConfig, private val networkToLocalManga: NetworkToLocalManga = Injekt.get(), -) : - BasePresenter() { - - private val _smartSearchFlow = MutableSharedFlow() - val smartSearchFlow = _smartSearchFlow.asSharedFlow() - + private val sourceManager: SourceManager = Injekt.get(), +) : StateScreenModel(null) { private val smartSearchEngine = SmartSearchEngine() - override fun onCreate(savedState: Bundle?) { - super.onCreate(savedState) + val source = sourceManager.get(sourceId) as CatalogueSource - presenterScope.launchIO { + init { + coroutineScope.launchIO { val result = try { val resultManga = smartSearchEngine.smartSearch(source, config.origTitle) if (resultManga != null) { @@ -46,7 +41,7 @@ class SmartSearchPresenter( } } - _smartSearchFlow.emit(result) + mutableState.value = result } }