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 <Jays2Kings@users.noreply.github.com>
This commit is contained in:
arkon 2022-12-10 12:08:33 -05:00
parent ca500da4d8
commit 3749cee28f
7 changed files with 95 additions and 16 deletions

View File

@ -18,6 +18,10 @@ interface Tab : cafe.adriel.voyager.navigator.tab.Tab {
suspend fun onReselect(navigator: Navigator) {} suspend fun onReselect(navigator: Navigator) {}
} }
interface AssistContentScreen {
fun onProvideAssistUrl(): String?
}
@Composable @Composable
fun DefaultNavigatorScreenTransition(navigator: Navigator) { fun DefaultNavigatorScreenTransition(navigator: Navigator) {
val slideDistance = rememberSlideDistance() val slideDistance = rememberSlideDistance()

View File

@ -1,6 +1,7 @@
package eu.kanade.presentation.webview package eu.kanade.presentation.webview
import android.content.pm.ApplicationInfo import android.content.pm.ApplicationInfo
import android.graphics.Bitmap
import android.webkit.WebResourceRequest import android.webkit.WebResourceRequest
import android.webkit.WebView import android.webkit.WebView
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
@ -35,6 +36,7 @@ fun WebViewScreen(
initialTitle: String?, initialTitle: String?,
url: String, url: String,
headers: Map<String, String> = emptyMap(), headers: Map<String, String> = emptyMap(),
onUrlChange: (String) -> Unit = {},
onShare: (String) -> Unit, onShare: (String) -> Unit,
onOpenInBrowser: (String) -> Unit, onOpenInBrowser: (String) -> Unit,
onClearCookies: (String) -> Unit, onClearCookies: (String) -> Unit,
@ -112,6 +114,11 @@ fun WebViewScreen(
) { contentPadding -> ) { contentPadding ->
val webClient = remember { val webClient = remember {
object : AccompanistWebViewClient() { object : AccompanistWebViewClient() {
override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
super.onPageStarted(view, url, favicon)
url?.let { onUrlChange(it) }
}
override fun shouldOverrideUrlLoading( override fun shouldOverrideUrlLoading(
view: WebView?, view: WebView?,
request: WebResourceRequest?, request: WebResourceRequest?,

View File

@ -47,6 +47,7 @@ import eu.kanade.presentation.components.ChangeCategoryDialog
import eu.kanade.presentation.components.Divider import eu.kanade.presentation.components.Divider
import eu.kanade.presentation.components.DuplicateMangaDialog import eu.kanade.presentation.components.DuplicateMangaDialog
import eu.kanade.presentation.components.Scaffold import eu.kanade.presentation.components.Scaffold
import eu.kanade.presentation.util.AssistContentScreen
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.LocalSource import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
@ -62,10 +63,14 @@ import kotlinx.coroutines.flow.receiveAsFlow
data class BrowseSourceScreen( data class BrowseSourceScreen(
private val sourceId: Long, private val sourceId: Long,
private val query: String? = null, private val query: String? = null,
) : Screen { ) : Screen, AssistContentScreen {
private var assistUrl: String? = null
override val key = uniqueScreenKey override val key = uniqueScreenKey
override fun onProvideAssistUrl() = assistUrl
@Composable @Composable
override fun Content() { override fun Content() {
val navigator = LocalNavigator.currentOrThrow val navigator = LocalNavigator.currentOrThrow
@ -74,7 +79,7 @@ data class BrowseSourceScreen(
val haptic = LocalHapticFeedback.current val haptic = LocalHapticFeedback.current
val uriHandler = LocalUriHandler.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 state by screenModel.state.collectAsState()
val snackbarHostState = remember { SnackbarHostState() } val snackbarHostState = remember { SnackbarHostState() }
@ -87,6 +92,10 @@ data class BrowseSourceScreen(
context.startActivity(intent) context.startActivity(intent)
} }
LaunchedEffect(screenModel.source) {
assistUrl = (screenModel.source as? HttpSource)?.baseUrl
}
Scaffold( Scaffold(
topBar = { topBar = {
Column(modifier = Modifier.background(MaterialTheme.colorScheme.surface)) { Column(modifier = Modifier.background(MaterialTheme.colorScheme.surface)) {

View File

@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.ui.main
import android.animation.ValueAnimator import android.animation.ValueAnimator
import android.app.SearchManager import android.app.SearchManager
import android.app.assist.AssistContent
import android.content.Intent import android.content.Intent
import android.graphics.Color import android.graphics.Color
import android.os.Build import android.os.Build
@ -32,6 +33,7 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.core.animation.doOnEnd import androidx.core.animation.doOnEnd
import androidx.core.net.toUri
import androidx.core.splashscreen.SplashScreen import androidx.core.splashscreen.SplashScreen
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.core.view.WindowCompat 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.source.service.SourcePreferences
import eu.kanade.domain.ui.UiPreferences import eu.kanade.domain.ui.UiPreferences
import eu.kanade.presentation.components.AppStateBanners import eu.kanade.presentation.components.AppStateBanners
import eu.kanade.presentation.util.AssistContentScreen
import eu.kanade.presentation.util.DefaultNavigatorScreenTransition import eu.kanade.presentation.util.DefaultNavigatorScreenTransition
import eu.kanade.presentation.util.collectAsState import eu.kanade.presentation.util.collectAsState
import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.BuildConfig
@ -261,6 +264,15 @@ class MainActivity : BaseActivity() {
setSplashScreenExitAnimation(splashScreen) 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) { private fun showSettingsSheet(category: Category? = null) {
if (category != null) { if (category != null) {
settingsSheet?.show(category) settingsSheet?.show(category)

View File

@ -2,11 +2,11 @@ package eu.kanade.tachiyomi.ui.manga
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri
import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.systemBarsPadding import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
@ -15,6 +15,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.core.net.toUri
import cafe.adriel.voyager.core.model.rememberScreenModel import cafe.adriel.voyager.core.model.rememberScreenModel
import cafe.adriel.voyager.core.screen.Screen import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.core.screen.uniqueScreenKey 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.DeleteChaptersDialog
import eu.kanade.presentation.manga.components.DownloadCustomAmountDialog import eu.kanade.presentation.manga.components.DownloadCustomAmountDialog
import eu.kanade.presentation.manga.components.MangaCoverDialog import eu.kanade.presentation.manga.components.MangaCoverDialog
import eu.kanade.presentation.util.AssistContentScreen
import eu.kanade.presentation.util.isTabletUi import eu.kanade.presentation.util.isTabletUi
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.Source 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.manga.track.TrackInfoDialogHomeScreen
import eu.kanade.tachiyomi.ui.reader.ReaderActivity import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import eu.kanade.tachiyomi.ui.webview.WebViewActivity 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.copyToClipboard
import eu.kanade.tachiyomi.util.system.logcat
import eu.kanade.tachiyomi.util.system.toShareIntent import eu.kanade.tachiyomi.util.system.toShareIntent
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import logcat.LogPriority
class MangaScreen( class MangaScreen(
private val mangaId: Long, private val mangaId: Long,
val fromSource: Boolean = false, val fromSource: Boolean = false,
) : Screen { ) : Screen, AssistContentScreen {
private var assistUrl: String? = null
override val key = uniqueScreenKey override val key = uniqueScreenKey
override fun onProvideAssistUrl() = assistUrl
@Composable @Composable
override fun Content() { override fun Content() {
val navigator = LocalNavigator.currentOrThrow val navigator = LocalNavigator.currentOrThrow
@ -77,6 +86,18 @@ class MangaScreen(
val successState = state as MangaScreenState.Success val successState = state as MangaScreenState.Success
val isHttpSource = remember { successState.source is HttpSource } 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( MangaScreen(
state = successState, state = successState,
snackbarHostState = screenModel.snackbarHostState, snackbarHostState = screenModel.snackbarHostState,
@ -208,27 +229,35 @@ class MangaScreen(
context.startActivity(ReaderActivity.newIntent(context, chapter.mangaId, chapter.id)) context.startActivity(ReaderActivity.newIntent(context, chapter.mangaId, chapter.id))
} }
private fun openMangaInWebView(context: Context, manga_: Manga?, source_: Source?) { private fun getMangaUrl(manga_: Manga?, source_: Source?): String? {
val manga = manga_ ?: return val manga = manga_ ?: return null
val source = source_ as? HttpSource ?: return val source = source_ as? HttpSource ?: return null
val url = try { return try {
source.getMangaUrl(manga.toSManga()) source.getMangaUrl(manga.toSManga())
} catch (e: Exception) { } catch (e: Exception) {
return null
} }
}
val intent = WebViewActivity.newIntent(context, url, source.id, manga.title) private fun openMangaInWebView(context: Context, manga_: Manga?, source_: Source?) {
context.startActivity(intent) 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?) { private fun shareManga(context: Context, manga_: Manga?, source_: Source?) {
val manga = manga_ ?: return
val source = source_ as? HttpSource ?: return
try { try {
val uri = Uri.parse(source.getMangaUrl(manga.toSManga())) getMangaUrl(manga_, source_)?.let { url ->
val intent = uri.toShareIntent(context, type = "text/plain") val intent = url.toUri().toShareIntent(context, type = "text/plain")
context.startActivity(Intent.createChooser(intent, context.getString(R.string.action_share))) context.startActivity(
Intent.createChooser(
intent,
context.getString(R.string.action_share),
),
)
}
} catch (e: Exception) { } catch (e: Exception) {
context.toast(e.message) context.toast(e.message)
} }

View File

@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.ui.reader
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.annotation.TargetApi import android.annotation.TargetApi
import android.app.ProgressDialog import android.app.ProgressDialog
import android.app.assist.AssistContent
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.res.ColorStateList import android.content.res.ColorStateList
@ -29,6 +30,7 @@ import android.widget.FrameLayout
import android.widget.Toast import android.widget.Toast
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.core.graphics.ColorUtils import androidx.core.graphics.ColorUtils
import androidx.core.net.toUri
import androidx.core.transition.doOnEnd import androidx.core.transition.doOnEnd
import androidx.core.view.WindowCompat import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat 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. * Called when the options menu of the toolbar is being created. It adds our custom menu.
*/ */

View File

@ -1,5 +1,6 @@
package eu.kanade.tachiyomi.ui.webview package eu.kanade.tachiyomi.ui.webview
import android.app.assist.AssistContent
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
@ -25,6 +26,8 @@ class WebViewActivity : BaseActivity() {
private val sourceManager: SourceManager by injectLazy() private val sourceManager: SourceManager by injectLazy()
private val network: NetworkHelper by injectLazy() private val network: NetworkHelper by injectLazy()
private var assistUrl: String? = null
init { init {
registerSecureActivity(this) registerSecureActivity(this)
} }
@ -52,6 +55,7 @@ class WebViewActivity : BaseActivity() {
initialTitle = intent.extras?.getString(TITLE_KEY), initialTitle = intent.extras?.getString(TITLE_KEY),
url = url, url = url,
headers = headers, headers = headers,
onUrlChange = { assistUrl = it },
onShare = this::shareWebpage, onShare = this::shareWebpage,
onOpenInBrowser = this::openInBrowser, onOpenInBrowser = this::openInBrowser,
onClearCookies = this::clearCookies, 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() { override fun finish() {
super.finish() super.finish()
overridePendingTransition(R.anim.shared_axis_x_pop_enter, R.anim.shared_axis_x_pop_exit) overridePendingTransition(R.anim.shared_axis_x_pop_enter, R.anim.shared_axis_x_pop_exit)