diff --git a/app/src/main/java/eu/kanade/presentation/webview/WebViewScreenContent.kt b/app/src/main/java/eu/kanade/presentation/webview/WebViewScreenContent.kt index 2647384ee..439809bbd 100644 --- a/app/src/main/java/eu/kanade/presentation/webview/WebViewScreenContent.kt +++ b/app/src/main/java/eu/kanade/presentation/webview/WebViewScreenContent.kt @@ -4,8 +4,9 @@ import android.content.pm.ApplicationInfo import android.graphics.Bitmap import android.webkit.WebResourceRequest import android.webkit.WebView +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons @@ -17,9 +18,11 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.stringResource import com.google.accompanist.web.AccompanistWebViewClient import com.google.accompanist.web.LoadingState @@ -28,9 +31,12 @@ import com.google.accompanist.web.rememberWebViewNavigator import com.google.accompanist.web.rememberWebViewState import eu.kanade.presentation.components.AppBar import eu.kanade.presentation.components.AppBarActions +import eu.kanade.presentation.components.WarningBanner import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.util.system.getHtml import eu.kanade.tachiyomi.util.system.setDefaultSettings +import kotlinx.coroutines.launch import tachiyomi.presentation.core.components.material.Scaffold @Composable @@ -46,7 +52,53 @@ fun WebViewScreenContent( ) { val state = rememberWebViewState(url = url, additionalHttpHeaders = headers) val navigator = rememberWebViewNavigator() + val uriHandler = LocalUriHandler.current + val scope = rememberCoroutineScope() + var currentUrl by remember { mutableStateOf(url) } + var showCloudflareHelp by remember { mutableStateOf(false) } + + val webClient = remember { + object : AccompanistWebViewClient() { + override fun onPageStarted(view: WebView, url: String?, favicon: Bitmap?) { + super.onPageStarted(view, url, favicon) + url?.let { + currentUrl = it + onUrlChange(it) + } + } + + override fun onPageFinished(view: WebView, url: String?) { + super.onPageFinished(view, url) + scope.launch { + val html = view.getHtml() + showCloudflareHelp = "Checking if the site connection is secure" in html + } + } + + override fun doUpdateVisitedHistory( + view: WebView, + url: String?, + isReload: Boolean, + ) { + super.doUpdateVisitedHistory(view, url, isReload) + url?.let { + currentUrl = it + onUrlChange(it) + } + } + + override fun shouldOverrideUrlLoading( + view: WebView?, + request: WebResourceRequest?, + ): Boolean { + request?.let { + view?.loadUrl(it.url.toString(), headers) + } + return super.shouldOverrideUrlLoading(view, request) + } + } + } Scaffold( topBar = { @@ -116,67 +168,38 @@ fun WebViewScreenContent( } }, ) { contentPadding -> - val webClient = remember { - object : AccompanistWebViewClient() { - override fun onPageStarted(view: WebView, url: String?, favicon: Bitmap?) { - super.onPageStarted(view, url, favicon) - url?.let { - currentUrl = it - onUrlChange(it) - } - } - - override fun doUpdateVisitedHistory( - view: WebView, - url: String?, - isReload: Boolean, - ) { - super.doUpdateVisitedHistory(view, url, isReload) - url?.let { - currentUrl = it - onUrlChange(it) - } - } - - override fun shouldOverrideUrlLoading( - view: WebView?, - request: WebResourceRequest?, - ): Boolean { - request?.let { - // Don't attempt to open blobs as webpages - if (it.url.toString().startsWith("blob:http")) { - return false - } - - // Continue with request, but with custom headers - view?.loadUrl(it.url.toString(), headers) - } - return super.shouldOverrideUrlLoading(view, request) - } + Column( + modifier = Modifier.padding(contentPadding), + ) { + if (showCloudflareHelp) { + WarningBanner( + textRes = R.string.information_cloudflare_help, + modifier = Modifier.clickable { + uriHandler.openUri("https://aniyomi.org/docs/guides/troubleshooting/#solving-cloudflare-issues") + }, + ) } + + WebView( + state = state, + modifier = Modifier.weight(1f), + navigator = navigator, + onCreated = { webView -> + webView.setDefaultSettings() + + // Debug mode (chrome://inspect/#devices) + if (BuildConfig.DEBUG && + 0 != webView.context.applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE + ) { + WebView.setWebContentsDebuggingEnabled(true) + } + + headers["user-agent"]?.let { + webView.settings.userAgentString = it + } + }, + client = webClient, + ) } - - WebView( - state = state, - modifier = Modifier - .padding(contentPadding) - .fillMaxSize(), - navigator = navigator, - onCreated = { webView -> - webView.setDefaultSettings() - - // Debug mode (chrome://inspect/#devices) - if (BuildConfig.DEBUG && - 0 != webView.context.applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE - ) { - WebView.setWebContentsDebuggingEnabled(true) - } - - headers["user-agent"]?.let { - webView.settings.userAgentString = it - } - }, - client = webClient, - ) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/library/anime/AnimeLibraryUpdateNotifier.kt b/app/src/main/java/eu/kanade/tachiyomi/data/library/anime/AnimeLibraryUpdateNotifier.kt index 25debcfff..70e48e8bb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/library/anime/AnimeLibraryUpdateNotifier.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/library/anime/AnimeLibraryUpdateNotifier.kt @@ -6,7 +6,6 @@ import android.content.Context import android.content.Intent import android.graphics.Bitmap import android.graphics.BitmapFactory -import android.graphics.drawable.BitmapDrawable import android.net.Uri import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat @@ -24,6 +23,7 @@ import eu.kanade.tachiyomi.data.notification.Notifications import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.util.lang.chop import eu.kanade.tachiyomi.util.system.cancelNotification +import eu.kanade.tachiyomi.util.system.getBitmapOrNull import eu.kanade.tachiyomi.util.system.notificationBuilder import eu.kanade.tachiyomi.util.system.notify import tachiyomi.core.util.lang.launchUI @@ -274,7 +274,7 @@ class AnimeLibraryUpdateNotifier(private val context: Context) { .size(NOTIF_ANIME_ICON_SIZE) .build() val drawable = context.imageLoader.execute(request).drawable - return (drawable as? BitmapDrawable)?.bitmap + return drawable?.getBitmapOrNull() } private fun getNewEpisodesDescription(episodes: Array): String { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/library/manga/MangaLibraryUpdateNotifier.kt b/app/src/main/java/eu/kanade/tachiyomi/data/library/manga/MangaLibraryUpdateNotifier.kt index 6c511aedb..2150ac845 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/library/manga/MangaLibraryUpdateNotifier.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/library/manga/MangaLibraryUpdateNotifier.kt @@ -6,7 +6,6 @@ import android.content.Context import android.content.Intent import android.graphics.Bitmap import android.graphics.BitmapFactory -import android.graphics.drawable.BitmapDrawable import android.net.Uri import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat @@ -24,6 +23,7 @@ import eu.kanade.tachiyomi.data.notification.Notifications import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.util.lang.chop import eu.kanade.tachiyomi.util.system.cancelNotification +import eu.kanade.tachiyomi.util.system.getBitmapOrNull import eu.kanade.tachiyomi.util.system.notificationBuilder import eu.kanade.tachiyomi.util.system.notify import tachiyomi.core.util.lang.launchUI @@ -283,7 +283,7 @@ class MangaLibraryUpdateNotifier(private val context: Context) { .size(NOTIF_MANGA_ICON_SIZE) .build() val drawable = context.imageLoader.execute(request).drawable - return (drawable as? BitmapDrawable)?.bitmap + return drawable?.getBitmapOrNull() } private fun getNewChaptersDescription(chapters: Array): String { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/entries/anime/AnimeCoverScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/entries/anime/AnimeCoverScreenModel.kt index 83fe1290b..93d2ea268 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/entries/anime/AnimeCoverScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/entries/anime/AnimeCoverScreenModel.kt @@ -1,7 +1,6 @@ package eu.kanade.tachiyomi.ui.entries.anime import android.content.Context -import android.graphics.drawable.BitmapDrawable import android.net.Uri import androidx.compose.material3.SnackbarHostState import cafe.adriel.voyager.core.model.StateScreenModel @@ -16,6 +15,7 @@ import eu.kanade.tachiyomi.data.saver.Image import eu.kanade.tachiyomi.data.saver.ImageSaver import eu.kanade.tachiyomi.data.saver.Location import eu.kanade.tachiyomi.util.editCover +import eu.kanade.tachiyomi.util.system.getBitmapOrNull import eu.kanade.tachiyomi.util.system.toShareIntent import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch @@ -98,7 +98,7 @@ class AnimeCoverScreenModel( val result = context.imageLoader.execute(req).drawable // TODO: Handle animated cover - val bitmap = (result as? BitmapDrawable)?.bitmap ?: return@withIOContext null + val bitmap = result?.getBitmapOrNull() ?: return@withIOContext null imageSaver.save( Image.Cover( bitmap = bitmap, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/entries/manga/MangaCoverScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/entries/manga/MangaCoverScreenModel.kt index 3dbff634b..87f1ab389 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/entries/manga/MangaCoverScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/entries/manga/MangaCoverScreenModel.kt @@ -1,7 +1,6 @@ package eu.kanade.tachiyomi.ui.entries.manga import android.content.Context -import android.graphics.drawable.BitmapDrawable import android.net.Uri import androidx.compose.material3.SnackbarHostState import cafe.adriel.voyager.core.model.StateScreenModel @@ -16,6 +15,7 @@ import eu.kanade.tachiyomi.data.saver.Image import eu.kanade.tachiyomi.data.saver.ImageSaver import eu.kanade.tachiyomi.data.saver.Location import eu.kanade.tachiyomi.util.editCover +import eu.kanade.tachiyomi.util.system.getBitmapOrNull import eu.kanade.tachiyomi.util.system.toShareIntent import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch @@ -98,7 +98,7 @@ class MangaCoverScreenModel( val result = context.imageLoader.execute(req).drawable // TODO: Handle animated cover - val bitmap = (result as? BitmapDrawable)?.bitmap ?: return@withIOContext null + val bitmap = result?.getBitmapOrNull() ?: return@withIOContext null imageSaver.save( Image.Cover( bitmap = bitmap, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/SaveImageNotifier.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/SaveImageNotifier.kt index 24a6441a1..6e1e8fafa 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/SaveImageNotifier.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/SaveImageNotifier.kt @@ -2,7 +2,6 @@ package eu.kanade.tachiyomi.ui.reader import android.content.Context import android.graphics.Bitmap -import android.graphics.drawable.BitmapDrawable import android.net.Uri import androidx.core.app.NotificationCompat import coil.imageLoader @@ -13,6 +12,7 @@ import eu.kanade.tachiyomi.data.notification.NotificationHandler import eu.kanade.tachiyomi.data.notification.NotificationReceiver import eu.kanade.tachiyomi.data.notification.Notifications import eu.kanade.tachiyomi.util.system.cancelNotification +import eu.kanade.tachiyomi.util.system.getBitmapOrNull import eu.kanade.tachiyomi.util.system.notificationBuilder import eu.kanade.tachiyomi.util.system.notify @@ -35,12 +35,8 @@ class SaveImageNotifier(private val context: Context) { .memoryCachePolicy(CachePolicy.DISABLED) .size(720, 1280) .target( - onSuccess = { result -> - showCompleteNotification(uri, (result as BitmapDrawable).bitmap) - }, - onError = { - onError(null) - }, + onSuccess = { showCompleteNotification(uri, it.getBitmapOrNull()) }, + onError = { onError(null) }, ) .build() context.imageLoader.enqueue(request) @@ -67,11 +63,11 @@ class SaveImageNotifier(private val context: Context) { updateNotification() } - private fun showCompleteNotification(uri: Uri, image: Bitmap) { + private fun showCompleteNotification(uri: Uri, image: Bitmap?) { with(notificationBuilder) { setContentTitle(context.getString(R.string.picture_saved)) setSmallIcon(R.drawable.ic_photo_24dp) - setStyle(NotificationCompat.BigPictureStyle().bigPicture(image)) + image?.let { setStyle(NotificationCompat.BigPictureStyle().bigPicture(it)) } setLargeIcon(image) setAutoCancel(true) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderPageImageView.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderPageImageView.kt index f010e9c36..02d4cce9e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderPageImageView.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderPageImageView.kt @@ -277,10 +277,7 @@ open class ReaderPageImageView @JvmOverloads constructor( ) when (image) { - is Drawable -> { - val bitmap = (image as BitmapDrawable).bitmap - setImage(ImageSource.bitmap(bitmap)) - } + is BitmapDrawable -> setImage(ImageSource.bitmap(image.bitmap)) is InputStream -> setImage(ImageSource.inputStream(image)) else -> throw IllegalArgumentException("Not implemented for class ${image::class.simpleName}") } diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/system/DrawableExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/DrawableExtensions.kt new file mode 100644 index 000000000..2a09aeca9 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/util/system/DrawableExtensions.kt @@ -0,0 +1,13 @@ +package eu.kanade.tachiyomi.util.system + +import android.graphics.Bitmap +import android.graphics.drawable.BitmapDrawable +import android.graphics.drawable.Drawable +import androidx.core.graphics.drawable.toBitmap +import coil.drawable.ScaleDrawable + +fun Drawable.getBitmapOrNull(): Bitmap? = when (this) { + is BitmapDrawable -> bitmap + is ScaleDrawable -> child.toBitmap() + else -> null +} diff --git a/core/src/main/java/eu/kanade/tachiyomi/network/interceptor/CloudflareInterceptor.kt b/core/src/main/java/eu/kanade/tachiyomi/network/interceptor/CloudflareInterceptor.kt index 381431901..25b5a140a 100644 --- a/core/src/main/java/eu/kanade/tachiyomi/network/interceptor/CloudflareInterceptor.kt +++ b/core/src/main/java/eu/kanade/tachiyomi/network/interceptor/CloudflareInterceptor.kt @@ -44,7 +44,7 @@ class CloudflareInterceptor( // Because OkHttp's enqueue only handles IOExceptions, wrap the exception so that // we don't crash the entire app catch (e: CloudflareBypassException) { - throw IOException(context.getString(R.string.information_cloudflare_bypass_failure)) + throw IOException(context.getString(R.string.information_cloudflare_bypass_failure), e) } catch (e: Exception) { throw IOException(e) } diff --git a/core/src/main/java/eu/kanade/tachiyomi/util/system/WebViewUtil.kt b/core/src/main/java/eu/kanade/tachiyomi/util/system/WebViewUtil.kt index 7da98f810..4fcaaceed 100644 --- a/core/src/main/java/eu/kanade/tachiyomi/util/system/WebViewUtil.kt +++ b/core/src/main/java/eu/kanade/tachiyomi/util/system/WebViewUtil.kt @@ -6,8 +6,10 @@ import android.content.pm.PackageManager import android.webkit.CookieManager import android.webkit.WebSettings import android.webkit.WebView +import kotlinx.coroutines.suspendCancellableCoroutine import logcat.LogPriority import tachiyomi.core.util.system.logcat +import kotlin.coroutines.resume object WebViewUtil { const val SPOOF_PACKAGE_NAME = "org.chromium.chrome" @@ -32,6 +34,10 @@ fun WebView.isOutdated(): Boolean { return getWebViewMajorVersion() < WebViewUtil.MINIMUM_WEBVIEW_VERSION } +suspend fun WebView.getHtml(): String = suspendCancellableCoroutine { + evaluateJavascript("document.documentElement.outerHTML") { html -> it.resume(html) } +} + @SuppressLint("SetJavaScriptEnabled") fun WebView.setDefaultSettings() { with(settings) { diff --git a/i18n/src/main/res/values/strings.xml b/i18n/src/main/res/values/strings.xml index 7d5f99a8f..9b5237fcf 100644 --- a/i18n/src/main/res/values/strings.xml +++ b/i18n/src/main/res/values/strings.xml @@ -905,6 +905,7 @@ You have no categories. Tap the plus button to create one for organizing your library. You don\'t have any categories yet. Failed to bypass Cloudflare + Tap here for help with Cloudflare *required Please update the WebView app for better compatibility