From 3749cee28f269aabe5ea18ffb62483a28d0c75e7 Mon Sep 17 00:00:00 2001 From: arkon Date: Sat, 10 Dec 2022 12:08:33 -0500 Subject: [PATCH] Add Assistant content URLs This is surfaced in recents on Pixel devices for example. Docs: https://developer.android.com/guide/app-actions/assistant-sharing Co-authored-by: Jays2Kings --- .../eu/kanade/presentation/util/Navigator.kt | 4 ++ .../presentation/webview/WebViewScreen.kt | 7 +++ .../source/browse/BrowseSourceScreen.kt | 13 ++++- .../kanade/tachiyomi/ui/main/MainActivity.kt | 12 ++++ .../kanade/tachiyomi/ui/manga/MangaScreen.kt | 57 ++++++++++++++----- .../tachiyomi/ui/reader/ReaderActivity.kt | 9 +++ .../tachiyomi/ui/webview/WebViewActivity.kt | 9 +++ 7 files changed, 95 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/eu/kanade/presentation/util/Navigator.kt b/app/src/main/java/eu/kanade/presentation/util/Navigator.kt index 30daf481a3..6a7d281c63 100644 --- a/app/src/main/java/eu/kanade/presentation/util/Navigator.kt +++ b/app/src/main/java/eu/kanade/presentation/util/Navigator.kt @@ -18,6 +18,10 @@ interface Tab : cafe.adriel.voyager.navigator.tab.Tab { suspend fun onReselect(navigator: Navigator) {} } +interface AssistContentScreen { + fun onProvideAssistUrl(): String? +} + @Composable fun DefaultNavigatorScreenTransition(navigator: Navigator) { val slideDistance = rememberSlideDistance() diff --git a/app/src/main/java/eu/kanade/presentation/webview/WebViewScreen.kt b/app/src/main/java/eu/kanade/presentation/webview/WebViewScreen.kt index 4c9e393c5b..b05dab2133 100644 --- a/app/src/main/java/eu/kanade/presentation/webview/WebViewScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/webview/WebViewScreen.kt @@ -1,6 +1,7 @@ package eu.kanade.presentation.webview import android.content.pm.ApplicationInfo +import android.graphics.Bitmap import android.webkit.WebResourceRequest import android.webkit.WebView import androidx.compose.foundation.layout.Box @@ -35,6 +36,7 @@ fun WebViewScreen( initialTitle: String?, url: String, headers: Map = emptyMap(), + onUrlChange: (String) -> Unit = {}, onShare: (String) -> Unit, onOpenInBrowser: (String) -> Unit, onClearCookies: (String) -> Unit, @@ -112,6 +114,11 @@ fun WebViewScreen( ) { contentPadding -> val webClient = remember { object : AccompanistWebViewClient() { + override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) { + super.onPageStarted(view, url, favicon) + url?.let { onUrlChange(it) } + } + override fun shouldOverrideUrlLoading( view: WebView?, request: WebResourceRequest?, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreen.kt index ec1d9af2c7..990da2398a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreen.kt @@ -47,6 +47,7 @@ import eu.kanade.presentation.components.ChangeCategoryDialog import eu.kanade.presentation.components.Divider import eu.kanade.presentation.components.DuplicateMangaDialog import eu.kanade.presentation.components.Scaffold +import eu.kanade.presentation.util.AssistContentScreen import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.source.LocalSource import eu.kanade.tachiyomi.source.online.HttpSource @@ -62,10 +63,14 @@ import kotlinx.coroutines.flow.receiveAsFlow data class BrowseSourceScreen( private val sourceId: Long, private val query: String? = null, -) : Screen { +) : Screen, AssistContentScreen { + + private var assistUrl: String? = null override val key = uniqueScreenKey + override fun onProvideAssistUrl() = assistUrl + @Composable override fun Content() { val navigator = LocalNavigator.currentOrThrow @@ -74,7 +79,7 @@ data class BrowseSourceScreen( val haptic = LocalHapticFeedback.current val uriHandler = LocalUriHandler.current - val screenModel = rememberScreenModel { BrowseSourceScreenModel(sourceId = sourceId, searchQuery = query) } + val screenModel = rememberScreenModel { BrowseSourceScreenModel(sourceId, query) } val state by screenModel.state.collectAsState() val snackbarHostState = remember { SnackbarHostState() } @@ -87,6 +92,10 @@ data class BrowseSourceScreen( context.startActivity(intent) } + LaunchedEffect(screenModel.source) { + assistUrl = (screenModel.source as? HttpSource)?.baseUrl + } + Scaffold( topBar = { Column(modifier = Modifier.background(MaterialTheme.colorScheme.surface)) { 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 fee5c4286b..b17297d086 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 @@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.ui.main import android.animation.ValueAnimator import android.app.SearchManager +import android.app.assist.AssistContent import android.content.Intent import android.graphics.Color import android.os.Build @@ -32,6 +33,7 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.core.animation.doOnEnd +import androidx.core.net.toUri import androidx.core.splashscreen.SplashScreen import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.core.view.WindowCompat @@ -49,6 +51,7 @@ import eu.kanade.domain.library.service.LibraryPreferences import eu.kanade.domain.source.service.SourcePreferences import eu.kanade.domain.ui.UiPreferences import eu.kanade.presentation.components.AppStateBanners +import eu.kanade.presentation.util.AssistContentScreen import eu.kanade.presentation.util.DefaultNavigatorScreenTransition import eu.kanade.presentation.util.collectAsState import eu.kanade.tachiyomi.BuildConfig @@ -261,6 +264,15 @@ class MainActivity : BaseActivity() { setSplashScreenExitAnimation(splashScreen) } + override fun onProvideAssistContent(outContent: AssistContent) { + super.onProvideAssistContent(outContent) + when (val screen = navigator.lastItem) { + is AssistContentScreen -> { + screen.onProvideAssistUrl()?.let { outContent.webUri = it.toUri() } + } + } + } + private fun showSettingsSheet(category: Category? = null) { if (category != null) { settingsSheet?.show(category) 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 9adbb36479..c466060304 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 @@ -2,11 +2,11 @@ package eu.kanade.tachiyomi.ui.manga import android.content.Context import android.content.Intent -import android.net.Uri import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.layout.systemBarsPadding import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember @@ -15,6 +15,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalHapticFeedback +import androidx.core.net.toUri import cafe.adriel.voyager.core.model.rememberScreenModel import cafe.adriel.voyager.core.screen.Screen import cafe.adriel.voyager.core.screen.uniqueScreenKey @@ -34,6 +35,7 @@ import eu.kanade.presentation.manga.MangaScreen import eu.kanade.presentation.manga.components.DeleteChaptersDialog import eu.kanade.presentation.manga.components.DownloadCustomAmountDialog import eu.kanade.presentation.manga.components.MangaCoverDialog +import eu.kanade.presentation.util.AssistContentScreen import eu.kanade.presentation.util.isTabletUi import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.source.Source @@ -47,18 +49,25 @@ import eu.kanade.tachiyomi.ui.home.HomeScreen import eu.kanade.tachiyomi.ui.manga.track.TrackInfoDialogHomeScreen import eu.kanade.tachiyomi.ui.reader.ReaderActivity import eu.kanade.tachiyomi.ui.webview.WebViewActivity +import eu.kanade.tachiyomi.util.lang.withIOContext import eu.kanade.tachiyomi.util.system.copyToClipboard +import eu.kanade.tachiyomi.util.system.logcat import eu.kanade.tachiyomi.util.system.toShareIntent import eu.kanade.tachiyomi.util.system.toast import kotlinx.coroutines.launch +import logcat.LogPriority class MangaScreen( private val mangaId: Long, val fromSource: Boolean = false, -) : Screen { +) : Screen, AssistContentScreen { + + private var assistUrl: String? = null override val key = uniqueScreenKey + override fun onProvideAssistUrl() = assistUrl + @Composable override fun Content() { val navigator = LocalNavigator.currentOrThrow @@ -77,6 +86,18 @@ class MangaScreen( val successState = state as MangaScreenState.Success val isHttpSource = remember { successState.source is HttpSource } + LaunchedEffect(successState.manga, screenModel.source) { + if (isHttpSource) { + try { + withIOContext { + assistUrl = getMangaUrl(screenModel.manga, screenModel.source) + } + } catch (e: Exception) { + logcat(LogPriority.ERROR, e) { "Failed to get manga URL" } + } + } + } + MangaScreen( state = successState, snackbarHostState = screenModel.snackbarHostState, @@ -208,27 +229,35 @@ class MangaScreen( context.startActivity(ReaderActivity.newIntent(context, chapter.mangaId, chapter.id)) } - private fun openMangaInWebView(context: Context, manga_: Manga?, source_: Source?) { - val manga = manga_ ?: return - val source = source_ as? HttpSource ?: return + private fun getMangaUrl(manga_: Manga?, source_: Source?): String? { + val manga = manga_ ?: return null + val source = source_ as? HttpSource ?: return null - val url = try { + return try { source.getMangaUrl(manga.toSManga()) } catch (e: Exception) { - return + null } + } - val intent = WebViewActivity.newIntent(context, url, source.id, manga.title) - context.startActivity(intent) + private fun openMangaInWebView(context: Context, manga_: Manga?, source_: Source?) { + getMangaUrl(manga_, source_)?.let { url -> + val intent = WebViewActivity.newIntent(context, url, source_?.id, manga_?.title) + context.startActivity(intent) + } } private fun shareManga(context: Context, manga_: Manga?, source_: Source?) { - val manga = manga_ ?: return - val source = source_ as? HttpSource ?: return try { - val uri = Uri.parse(source.getMangaUrl(manga.toSManga())) - val intent = uri.toShareIntent(context, type = "text/plain") - context.startActivity(Intent.createChooser(intent, context.getString(R.string.action_share))) + getMangaUrl(manga_, source_)?.let { url -> + val intent = url.toUri().toShareIntent(context, type = "text/plain") + context.startActivity( + Intent.createChooser( + intent, + context.getString(R.string.action_share), + ), + ) + } } catch (e: Exception) { context.toast(e.message) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt index e331a69a41..e05f024a8d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt @@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.ui.reader import android.annotation.SuppressLint import android.annotation.TargetApi import android.app.ProgressDialog +import android.app.assist.AssistContent import android.content.Context import android.content.Intent import android.content.res.ColorStateList @@ -29,6 +30,7 @@ import android.widget.FrameLayout import android.widget.Toast import androidx.activity.viewModels import androidx.core.graphics.ColorUtils +import androidx.core.net.toUri import androidx.core.transition.doOnEnd import androidx.core.view.WindowCompat import androidx.core.view.WindowInsetsCompat @@ -296,6 +298,13 @@ class ReaderActivity : BaseActivity() { } } + override fun onProvideAssistContent(outContent: AssistContent) { + super.onProvideAssistContent(outContent) + viewModel.getChapterUrl()?.let { url -> + outContent.webUri = url.toUri() + } + } + /** * Called when the options menu of the toolbar is being created. It adds our custom menu. */ diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/webview/WebViewActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/webview/WebViewActivity.kt index dd0d54f795..6ccfebb209 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/webview/WebViewActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/webview/WebViewActivity.kt @@ -1,5 +1,6 @@ package eu.kanade.tachiyomi.ui.webview +import android.app.assist.AssistContent import android.content.Context import android.content.Intent import android.os.Bundle @@ -25,6 +26,8 @@ class WebViewActivity : BaseActivity() { private val sourceManager: SourceManager by injectLazy() private val network: NetworkHelper by injectLazy() + private var assistUrl: String? = null + init { registerSecureActivity(this) } @@ -52,6 +55,7 @@ class WebViewActivity : BaseActivity() { initialTitle = intent.extras?.getString(TITLE_KEY), url = url, headers = headers, + onUrlChange = { assistUrl = it }, onShare = this::shareWebpage, onOpenInBrowser = this::openInBrowser, onClearCookies = this::clearCookies, @@ -59,6 +63,11 @@ class WebViewActivity : BaseActivity() { } } + override fun onProvideAssistContent(outContent: AssistContent) { + super.onProvideAssistContent(outContent) + assistUrl?.let { outContent.webUri = it.toUri() } + } + override fun finish() { super.finish() overridePendingTransition(R.anim.shared_axis_x_pop_enter, R.anim.shared_axis_x_pop_exit)