diff --git a/app/build.gradle b/app/build.gradle index c8ff0a6e8..abea849de 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -38,7 +38,7 @@ android { minSdkVersion 23 targetSdkVersion 34 versionCode 130 // is updated automatically by BitRise; only used when building locally - versionName '1.18.12' + versionName '1.18.13' def includeObjectBoxBrowser = System.getenv("INCLUDE_OBJECTBOX_BROWSER") ?: "false" def includeLeakCanary = System.getenv("INCLUDE_LEAK_CANARY") ?: "false" diff --git a/app/customssiv/src/main/java/me/devsaki/hentoid/customssiv/CustomSubsamplingScaleImageView.java b/app/customssiv/src/main/java/me/devsaki/hentoid/customssiv/CustomSubsamplingScaleImageView.java index 7d6ea9a03..6ee029e60 100644 --- a/app/customssiv/src/main/java/me/devsaki/hentoid/customssiv/CustomSubsamplingScaleImageView.java +++ b/app/customssiv/src/main/java/me/devsaki/hentoid/customssiv/CustomSubsamplingScaleImageView.java @@ -21,7 +21,6 @@ import android.os.Message; import android.provider.MediaStore; import android.util.AttributeSet; -import android.util.DisplayMetrics; import android.util.TypedValue; import android.view.GestureDetector; import android.view.MotionEvent; @@ -391,6 +390,8 @@ public class CustomSubsamplingScaleImageView extends View { private final int screenWidth; private final int screenHeight; + private final float screenDpi; + private final CompositeDisposable loadDisposable = new CompositeDisposable(); // GPUImage instance to use to smoothen images; sharp mode will be used if not set private GPUImage glEsRenderer; @@ -401,6 +402,7 @@ public CustomSubsamplingScaleImageView(@NonNull Context context, @Nullable Attri density = getResources().getDisplayMetrics().density; screenWidth = context.getResources().getDisplayMetrics().widthPixels; screenHeight = context.getResources().getDisplayMetrics().heightPixels; + screenDpi = Helper.getScreenDpi(context); setMinimumDpi(160); setDoubleTapZoomDpi(160); @@ -454,7 +456,7 @@ public CustomSubsamplingScaleImageView(@NonNull Context context, @Nullable Attri }); } - public CustomSubsamplingScaleImageView(Context context) { + public CustomSubsamplingScaleImageView(@NonNull Context context) { this(context, null); } @@ -1698,9 +1700,7 @@ private void preDraw() { */ private int calculateInSampleSize(float scale) { if (minimumTileDpi > 0) { - DisplayMetrics metrics = getResources().getDisplayMetrics(); - float averageDpi = (metrics.xdpi + metrics.ydpi) / 2; - scale = (minimumTileDpi / averageDpi) * scale; + scale = (minimumTileDpi / screenDpi) * scale; } int reqWidth = (int) (sWidth() * scale); @@ -2570,6 +2570,7 @@ private float minScale() { private float limitedScale(float targetScale) { targetScale = Math.max(minScale(), targetScale); targetScale = Math.min(maxScale, targetScale); + return targetScale; } @@ -2732,9 +2733,7 @@ public final void setMinScale(float minScale) { * @param dpi Source image pixel density at maximum zoom. */ public final void setMinimumDpi(int dpi) { - DisplayMetrics metrics = getResources().getDisplayMetrics(); - float averageDpi = (metrics.xdpi + metrics.ydpi) / 2; - setMaxScale(averageDpi / dpi); + setMaxScale(screenDpi / dpi); } /** @@ -2744,9 +2743,7 @@ public final void setMinimumDpi(int dpi) { * @param dpi Source image pixel density at minimum zoom. */ public final void setMaximumDpi(int dpi) { - DisplayMetrics metrics = getResources().getDisplayMetrics(); - float averageDpi = (metrics.xdpi + metrics.ydpi) / 2; - setMinScale(averageDpi / dpi); + setMinScale(screenDpi / dpi); } @@ -2778,9 +2775,7 @@ public final float getMinScale() { * @param minimumTileDpi Tile loading threshold. */ public void setMinimumTileDpi(int minimumTileDpi) { - DisplayMetrics metrics = getResources().getDisplayMetrics(); - float averageDpi = (metrics.xdpi + metrics.ydpi) / 2; - this.minimumTileDpi = (int) Math.min(averageDpi, minimumTileDpi); + this.minimumTileDpi = (int) Math.min(screenDpi, minimumTileDpi); if (isReady()) { reset(false); invalidate(); @@ -3114,9 +3109,7 @@ public final void setDoubleTapZoomScale(float doubleTapZoomScale) { * @param dpi New value for double tap gesture zoom scale. */ public final void setDoubleTapZoomDpi(int dpi) { - DisplayMetrics metrics = getResources().getDisplayMetrics(); - float averageDpi = (metrics.xdpi + metrics.ydpi) / 2; - setDoubleTapZoomScale(averageDpi / dpi); + setDoubleTapZoomScale(screenDpi / dpi); } /** diff --git a/app/customssiv/src/main/java/me/devsaki/hentoid/customssiv/util/Helper.java b/app/customssiv/src/main/java/me/devsaki/hentoid/customssiv/util/Helper.java index 579bdd897..bd5e981c6 100644 --- a/app/customssiv/src/main/java/me/devsaki/hentoid/customssiv/util/Helper.java +++ b/app/customssiv/src/main/java/me/devsaki/hentoid/customssiv/util/Helper.java @@ -1,6 +1,10 @@ package me.devsaki.hentoid.customssiv.util; +import android.content.Context; +import android.os.Build; import android.os.Looper; +import android.util.DisplayMetrics; +import android.view.WindowManager; import androidx.annotation.NonNull; @@ -36,4 +40,23 @@ public static void copy(@NonNull InputStream in, @NonNull OutputStream out) thro } out.flush(); } + + public static float getScreenDpi(@NonNull Context context) { + DisplayMetrics metrics = context.getResources().getDisplayMetrics(); + float averageDpi = (metrics.xdpi + metrics.ydpi) / 2; + + WindowManager wMgr = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + float generalDpi; + if (Build.VERSION.SDK_INT >= 34) { + generalDpi = wMgr.getCurrentWindowMetrics().getDensity() * 160; + } else { + DisplayMetrics metrics3 = new DisplayMetrics(); + wMgr.getDefaultDisplay().getRealMetrics(metrics3); + generalDpi = metrics3.densityDpi; + } + + // Dimensions retrieved by metrics.xdpi/ydpi might be expressed as ppi (as per specs) and not dpi (as per naming) + // In that case, values are off scale => fallback to general dpi + return ((Math.abs(generalDpi - averageDpi) / averageDpi) > 1) ? generalDpi : averageDpi; + } } diff --git a/app/src/main/java/me/devsaki/hentoid/activities/UnlockActivity.kt b/app/src/main/java/me/devsaki/hentoid/activities/UnlockActivity.kt index c5aa10503..64a97cc6a 100644 --- a/app/src/main/java/me/devsaki/hentoid/activities/UnlockActivity.kt +++ b/app/src/main/java/me/devsaki/hentoid/activities/UnlockActivity.kt @@ -86,12 +86,13 @@ class UnlockActivity : AppCompatActivity(), UnlockPinDialogFragment.Parent { @Suppress("DEPRECATION") private fun goToNextActivity() { val parcelableExtra: Intent? = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) - intent.getParcelableExtra(EXTRA_INTENT) as Intent + intent.getParcelableExtra(EXTRA_INTENT) as Intent? else intent.getParcelableExtra(EXTRA_INTENT, Intent::class.java) val targetIntent: Intent - if (parcelableExtra != null) targetIntent = parcelableExtra else { + if (parcelableExtra != null) targetIntent = parcelableExtra + else { val siteCode = intent.getIntExtra(EXTRA_SITE_CODE, Site.NONE.code) if (siteCode == Site.NONE.code) { finish() diff --git a/app/src/main/java/me/devsaki/hentoid/activities/sources/AnchiraActivity.java b/app/src/main/java/me/devsaki/hentoid/activities/sources/AnchiraActivity.java index 9342b0fa8..41027dcd6 100644 --- a/app/src/main/java/me/devsaki/hentoid/activities/sources/AnchiraActivity.java +++ b/app/src/main/java/me/devsaki/hentoid/activities/sources/AnchiraActivity.java @@ -9,7 +9,9 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import java.io.ByteArrayInputStream; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.Map; import me.devsaki.hentoid.database.domains.Content; @@ -66,25 +68,60 @@ public AnchiraWebClient(Site site, String[] galleryUrl, WebResultConsumer result AnchiraWebClient(Site site, String[] galleryUrl, CustomWebActivity activity) { super(site, galleryUrl, activity); setJsStartupScripts("wysiwyg_parser.js"); - addJsReplacement("$interface",AnchiraBackgroundWebView.Companion.getInterfaceName()); - addJsReplacement("$fun",AnchiraBackgroundWebView.functionName); + addJsReplacement("$interface", AnchiraBackgroundWebView.Companion.getInterfaceName()); + addJsReplacement("$fun", AnchiraBackgroundWebView.functionName); } @Override public WebResourceResponse shouldInterceptRequest(@NonNull WebView view, @NonNull WebResourceRequest request) { String url = request.getUrl().toString(); + HttpHelper.UriParts uriParts = new HttpHelper.UriParts(url, true); + if (uriParts.getEntireFileName().startsWith("app.") && uriParts.getExtension().equals("js")) { + try ( + Response response = HttpHelper.getOnlineResourceFast( + url, + HttpHelper.webkitRequestHeadersToOkHttpHeaders(request.getRequestHeaders(), url), + Site.ANCHIRA.useMobileAgent(), Site.ANCHIRA.useHentoidAgent(), Site.ANCHIRA.useWebviewAgent() + )) { + // Scram if the response is a redirection or an error + if (response.code() >= 300) return null; + + // Scram if the response is empty + ResponseBody body = response.body(); + if (null == body) throw new IOException("Empty body"); + + var jsFile = body.source().readString(StandardCharsets.UTF_8); + + jsFile = jsFile.replace(".isTrusted", ".cancelable"); + + return HttpHelper.okHttpResponseToWebkitResponse( + response, new ByteArrayInputStream( + jsFile.getBytes(StandardCharsets.UTF_8) + ) + ); + } catch (IOException e) { + Timber.w(e); + } + } + if (url.contains(AnchiraGalleryMetadata.IMG_HOST)) { String[] parts = url.split("/"); if (parts.length > 7) { + // TODO that's ugly; find a more suitable interface; e.g. onImagesReady + Content c = new Content(); + c.setSite(Site.ANCHIRA); + c.setCoverImageUrl(url); + resConsumer.onContentReady(c, false); + // Kill CORS if (request.getMethod().equalsIgnoreCase("options")) { try { Response response = HttpHelper.optOnlineResourceFast( url, HttpHelper.webkitRequestHeadersToOkHttpHeaders(request.getRequestHeaders(), url), - Site.ANCHIRA.useMobileAgent(), Site.ANCHIRA.useHentoidAgent(), Site.ANCHIRA.useWebviewAgent() - ); + Site.ANCHIRA.useMobileAgent(), Site.ANCHIRA.useHentoidAgent(), Site.ANCHIRA.useWebviewAgent()); + // Scram if the response is a redirection or an error if (response.code() >= 300) return null; @@ -93,15 +130,10 @@ public WebResourceResponse shouldInterceptRequest(@NonNull WebView view, @NonNul if (null == body) throw new IOException("Empty body"); return HttpHelper.okHttpResponseToWebkitResponse(response, body.byteStream()); - } catch (IOException e) { + } catch (Exception e) { Timber.w(e); } } - // TODO that's ugly; find a more suitable interface; e.g. onImagesReady - Content c = new Content(); - c.setSite(Site.ANCHIRA); - c.setCoverImageUrl(url); - resConsumer.onContentReady(c, false); } } @@ -109,7 +141,10 @@ public WebResourceResponse shouldInterceptRequest(@NonNull WebView view, @NonNul } @Override - protected WebResourceResponse parseResponse(@NonNull String urlStr, @Nullable Map requestHeaders, boolean analyzeForDownload, boolean quickDownload) { + protected WebResourceResponse parseResponse(@NonNull String urlStr, + @Nullable Map requestHeaders, + boolean analyzeForDownload, + boolean quickDownload) { // Complete override of default behaviour because // - There's no HTML to be parsed for ads // - The interesting parts are loaded by JS, not now diff --git a/app/src/main/java/me/devsaki/hentoid/activities/sources/CustomWebViewClient.java b/app/src/main/java/me/devsaki/hentoid/activities/sources/CustomWebViewClient.java index e5ce0415b..6011d8bc1 100644 --- a/app/src/main/java/me/devsaki/hentoid/activities/sources/CustomWebViewClient.java +++ b/app/src/main/java/me/devsaki/hentoid/activities/sources/CustomWebViewClient.java @@ -684,6 +684,7 @@ protected WebResourceResponse parseResponse(@NonNull String urlStr, @Nullable Ma content2 -> resConsumer.onContentReady(content2, quickDownload), throwable -> { Timber.e(throwable, "Error parsing content."); + parserStream.close(); isHtmlLoaded.set(true); resConsumer.onResultFailed(); }) @@ -696,8 +697,6 @@ protected WebResourceResponse parseResponse(@NonNull String urlStr, @Nullable Ma return result; } catch (IOException | IllegalStateException e) { Timber.e(e); - } finally { - response.close(); } } return null; diff --git a/app/src/main/java/me/devsaki/hentoid/adapters/ImagePagerAdapter.kt b/app/src/main/java/me/devsaki/hentoid/adapters/ImagePagerAdapter.kt index 406e27495..d4641c192 100644 --- a/app/src/main/java/me/devsaki/hentoid/adapters/ImagePagerAdapter.kt +++ b/app/src/main/java/me/devsaki/hentoid/adapters/ImagePagerAdapter.kt @@ -573,7 +573,7 @@ class ImagePagerAdapter(val context: Context) : Timber.d( e, "Picture %d : Glide loading failed : %s", absoluteAdapterPosition, img!!.fileUri ) - noImgTxt?.visibility = View.VISIBLE + if (isImageView) noImgTxt?.visibility = View.VISIBLE return false } diff --git a/app/src/main/java/me/devsaki/hentoid/util/ContentHelper.java b/app/src/main/java/me/devsaki/hentoid/util/ContentHelper.java index 3e673e930..17f3af7fc 100644 --- a/app/src/main/java/me/devsaki/hentoid/util/ContentHelper.java +++ b/app/src/main/java/me/devsaki/hentoid/util/ContentHelper.java @@ -1672,7 +1672,7 @@ public static void computeAndSaveCoverHash(@NonNull final Context context, */ public static boolean isDownloadable(@NonNull final Content content) { List images = content.getImageFiles(); - if (null == images) return false; + if (null == images || images.isEmpty()) return false; // Pick a random picture ImageFile img = images.get(Helper.getRandomInt(images.size()));