diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 07e296b89..f8034f711 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -65,10 +65,10 @@ android:exported="false" /> diff --git a/app/src/main/java/eu/kanade/presentation/browse/ExtensionsScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/ExtensionsScreen.kt index 433add984..fab831da7 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/ExtensionsScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/ExtensionsScreen.kt @@ -76,7 +76,7 @@ fun ExtensionScreen( enabled = !state.isLoading, ) { when { - state.isLoading -> LoadingScreen(modifier = Modifier.padding(contentPadding)) + state.isLoading -> LoadingScreen(Modifier.padding(contentPadding)) state.isEmpty -> { val msg = if (!searchQuery.isNullOrEmpty()) { R.string.no_results_found diff --git a/app/src/main/java/eu/kanade/presentation/browse/MigrateSourceScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/MigrateSourceScreen.kt index aaf23646a..2c4f4fc46 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/MigrateSourceScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/MigrateSourceScreen.kt @@ -51,7 +51,7 @@ fun MigrateSourceScreen( ) { val context = LocalContext.current when { - state.isLoading -> LoadingScreen(modifier = Modifier.padding(contentPadding)) + state.isLoading -> LoadingScreen(Modifier.padding(contentPadding)) state.isEmpty -> EmptyScreen( textResource = R.string.information_empty_library, modifier = Modifier.padding(contentPadding), diff --git a/app/src/main/java/eu/kanade/presentation/browse/SourcesScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/SourcesScreen.kt index 12175e4bd..de5434595 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/SourcesScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/SourcesScreen.kt @@ -47,7 +47,7 @@ fun SourcesScreen( onLongClickItem: (Source) -> Unit, ) { when { - state.isLoading -> LoadingScreen(modifier = Modifier.padding(contentPadding)) + state.isLoading -> LoadingScreen(Modifier.padding(contentPadding)) state.isEmpty -> EmptyScreen( textResource = R.string.source_empty_screen, modifier = Modifier.padding(contentPadding), diff --git a/app/src/main/java/eu/kanade/presentation/history/HistoryScreen.kt b/app/src/main/java/eu/kanade/presentation/history/HistoryScreen.kt index 1a80c8f01..3bac670d0 100644 --- a/app/src/main/java/eu/kanade/presentation/history/HistoryScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/history/HistoryScreen.kt @@ -65,7 +65,7 @@ fun HistoryScreen( ) { contentPadding -> state.list.let { if (it == null) { - LoadingScreen(modifier = Modifier.padding(contentPadding)) + LoadingScreen(Modifier.padding(contentPadding)) } else if (it.isEmpty()) { val msg = if (!state.searchQuery.isNullOrEmpty()) { R.string.no_results_found diff --git a/app/src/main/java/eu/kanade/presentation/updates/UpdatesScreen.kt b/app/src/main/java/eu/kanade/presentation/updates/UpdatesScreen.kt index dbb5e3305..1f4a56d5f 100644 --- a/app/src/main/java/eu/kanade/presentation/updates/UpdatesScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/updates/UpdatesScreen.kt @@ -81,7 +81,7 @@ fun UpdateScreen( snackbarHost = { SnackbarHost(hostState = snackbarHostState) }, ) { contentPadding -> when { - state.isLoading -> LoadingScreen(modifier = Modifier.padding(contentPadding)) + state.isLoading -> LoadingScreen(Modifier.padding(contentPadding)) state.items.isEmpty() -> EmptyScreen( textResource = R.string.information_no_recent, modifier = Modifier.padding(contentPadding), diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/main/DeepLinkActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/deeplink/DeepLinkActivity.kt similarity index 84% rename from app/src/main/java/eu/kanade/tachiyomi/ui/main/DeepLinkActivity.kt rename to app/src/main/java/eu/kanade/tachiyomi/ui/deeplink/DeepLinkActivity.kt index 1e381c09a..0635b03bb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/main/DeepLinkActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/deeplink/DeepLinkActivity.kt @@ -1,8 +1,9 @@ -package eu.kanade.tachiyomi.ui.main +package eu.kanade.tachiyomi.ui.deeplink import android.app.Activity import android.content.Intent import android.os.Bundle +import eu.kanade.tachiyomi.ui.main.MainActivity class DeepLinkActivity : Activity() { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/deeplink/DeepLinkScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/deeplink/DeepLinkScreen.kt new file mode 100644 index 000000000..4b7c989dc --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/deeplink/DeepLinkScreen.kt @@ -0,0 +1,59 @@ +package eu.kanade.tachiyomi.ui.deeplink + +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import cafe.adriel.voyager.core.model.rememberScreenModel +import cafe.adriel.voyager.navigator.LocalNavigator +import cafe.adriel.voyager.navigator.currentOrThrow +import eu.kanade.presentation.components.AppBar +import eu.kanade.presentation.util.Screen +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchScreen +import eu.kanade.tachiyomi.ui.manga.MangaScreen +import tachiyomi.presentation.core.components.material.Scaffold +import tachiyomi.presentation.core.screens.LoadingScreen + +class DeepLinkScreen( + val query: String = "", +) : Screen() { + + @Composable + override fun Content() { + val navigator = LocalNavigator.currentOrThrow + + val screenModel = rememberScreenModel { + DeepLinkScreenModel(query = query) + } + val state by screenModel.state.collectAsState() + Scaffold( + topBar = { scrollBehavior -> + AppBar( + title = stringResource(R.string.action_search_hint), + navigateUp = navigator::pop, + scrollBehavior = scrollBehavior, + ) + }, + ) { contentPadding -> + when (state) { + is DeepLinkScreenModel.State.Loading -> { + LoadingScreen(Modifier.padding(contentPadding)) + } + is DeepLinkScreenModel.State.NoResults -> { + navigator.replace(GlobalSearchScreen(query)) + } + is DeepLinkScreenModel.State.Result -> { + navigator.replace( + MangaScreen( + (state as DeepLinkScreenModel.State.Result).manga.id, + true, + ), + ) + } + } + } + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/deeplink/DeepLinkScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/deeplink/DeepLinkScreenModel.kt new file mode 100644 index 000000000..4446c28a4 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/deeplink/DeepLinkScreenModel.kt @@ -0,0 +1,47 @@ +package eu.kanade.tachiyomi.ui.deeplink + +import androidx.compose.runtime.Immutable +import cafe.adriel.voyager.core.model.StateScreenModel +import cafe.adriel.voyager.core.model.coroutineScope +import eu.kanade.domain.manga.model.toDomainManga +import eu.kanade.tachiyomi.source.online.ResolvableSource +import kotlinx.coroutines.flow.update +import tachiyomi.core.util.lang.launchIO +import tachiyomi.domain.manga.model.Manga +import tachiyomi.domain.source.service.SourceManager +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get + +class DeepLinkScreenModel( + query: String = "", + private val sourceManager: SourceManager = Injekt.get(), +) : StateScreenModel(State.Loading) { + + init { + coroutineScope.launchIO { + val manga = sourceManager.getCatalogueSources() + .filterIsInstance() + .filter { it.canResolveUri(query) } + .firstNotNullOfOrNull { it.getManga(query)?.toDomainManga(it.id) } + + mutableState.update { + if (manga == null) { + State.NoResults + } else { + State.Result(manga) + } + } + } + } + + sealed interface State { + @Immutable + data object Loading : State + + @Immutable + data object NoResults : State + + @Immutable + data class Result(val manga: Manga) : State + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryTab.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryTab.kt index c3a11d034..cde9762d5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryTab.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryTab.kt @@ -148,7 +148,7 @@ object LibraryTab : Tab { snackbarHost = { SnackbarHost(hostState = snackbarHostState) }, ) { contentPadding -> when { - state.isLoading -> LoadingScreen(modifier = Modifier.padding(contentPadding)) + state.isLoading -> LoadingScreen(Modifier.padding(contentPadding)) state.searchQuery.isNullOrEmpty() && !state.hasActiveFilters && state.isLibraryEmpty -> { val handler = LocalUriHandler.current EmptyScreen( diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt index e2516ea7c..868a8cee7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt @@ -71,6 +71,7 @@ import eu.kanade.tachiyomi.extension.api.ExtensionGithubApi import eu.kanade.tachiyomi.ui.base.activity.BaseActivity import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreen import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchScreen +import eu.kanade.tachiyomi.ui.deeplink.DeepLinkScreen import eu.kanade.tachiyomi.ui.home.HomeScreen import eu.kanade.tachiyomi.ui.manga.MangaScreen import eu.kanade.tachiyomi.ui.more.NewUpdateScreen @@ -409,7 +410,7 @@ class MainActivity : BaseActivity() { val query = intent.getStringExtra(SearchManager.QUERY) ?: intent.getStringExtra(Intent.EXTRA_TEXT) if (!query.isNullOrEmpty()) { navigator.popUntilRoot() - navigator.push(GlobalSearchScreen(query)) + navigator.push(DeepLinkScreen(query)) } null } diff --git a/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/online/ResolvableSource.kt b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/online/ResolvableSource.kt new file mode 100644 index 000000000..6a00c2e55 --- /dev/null +++ b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/online/ResolvableSource.kt @@ -0,0 +1,26 @@ +package eu.kanade.tachiyomi.source.online + +import eu.kanade.tachiyomi.source.Source +import eu.kanade.tachiyomi.source.model.SManga + +/** + * A source that may handle opening an SManga for a given URI. + * + * @since extensions-lib 1.5 + */ +interface ResolvableSource : Source { + + /** + * Whether this source may potentially handle the given URI. + * + * @since extensions-lib 1.5 + */ + fun canResolveUri(uri: String): Boolean + + /** + * Called if canHandleUri is true. Returns the corresponding SManga, if possible. + * + * @since extensions-lib 1.5 + */ + suspend fun getManga(uri: String): SManga? +}