mirror of
https://github.com/aniyomiorg/aniyomi.git
synced 2024-11-21 12:17:12 +03:00
parent
72a54cbb6e
commit
4cbf9a813e
11 changed files with 119 additions and 83 deletions
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Episode>): String {
|
||||
|
|
|
@ -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<Chapter>): String {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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}")
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -905,6 +905,7 @@
|
|||
<string name="information_empty_category">You have no categories. Tap the plus button to create one for organizing your library.</string>
|
||||
<string name="information_empty_category_dialog">You don\'t have any categories yet.</string>
|
||||
<string name="information_cloudflare_bypass_failure">Failed to bypass Cloudflare</string>
|
||||
<string name="information_cloudflare_help">Tap here for help with Cloudflare</string>
|
||||
<string name="information_required_plain">*required</string>
|
||||
<!-- Do not translate "WebView" -->
|
||||
<string name="information_webview_outdated">Please update the WebView app for better compatibility</string>
|
||||
|
|
Loading…
Reference in a new issue