diff --git a/app/build.gradle b/app/build.gradle index 27b5a6b1..411ee9b5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -25,7 +25,7 @@ android { dependenciesInfo { includeInApk = false } - compileSdk 35 + compileSdk 36 splits { abi { enable true @@ -55,7 +55,7 @@ android { defaultConfig { applicationId "com.cylonid.nativealpha" minSdkVersion 28 - targetSdkVersion 35 + targetSdkVersion 36 versionCode 1520 versionName "1.5.2" diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 933beb54..9d98b97b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -80,7 +80,7 @@ android:parentActivityName=".MainActivity" android:icon="@mipmap/native_alpha" android:roundIcon="@mipmap/native_alpha" - android:theme="@style/AppTheme"> + android:theme="@style/AppTheme.News"> =0.5;}var e=document.body||document.documentElement,c=\"\";while(e){var v=getComputedStyle(e).backgroundColor;if(opaque(v)){c=v;break;}e=e.parentElement;}var m=document.querySelector('meta[name=\"theme-color\"]');return ((m&&m.content)||\"\")+\"|\"+c;})()", value -> { + String[] colors = value.split("\\|", -1); + WindowInsetsUtils.applyBarColors(this, colors[0], colors.length > 1 ? colors[1] : null); + }); + } + @SuppressLint("RequiresFeature") private void setDarkModeIfNeeded() { if (!BuildConfig.FLAVOR.contains("extended")) { @@ -464,7 +476,7 @@ private void showWebViewPopupMenu() { wv.goForward(); return true; case R.id.cmItemBack: - onBackPressed(); + backCallback.handleOnBackPressed(); return true; case R.id.cmItemReload: wv.reload(); @@ -513,25 +525,26 @@ public void onConfigurationChanged(@NonNull Configuration newConfig) { this.setDarkModeIfNeeded(); } - @Override - public void onBackPressed() { - WebApp webapp = DataManager.getInstance().getWebApp(webappID); - - if(wv.canGoBack()) { - wv.goBack(); - return; - } + private final OnBackPressedCallback backCallback = new OnBackPressedCallback(true) { + @Override + public void handleOnBackPressed() { + WebApp webapp = DataManager.getInstance().getWebApp(webappID); - if(quitOnNextBackpress) { - quitOnNextBackpress = false; - moveTaskToBack(true); - return; - } + if (wv.canGoBack()) { + wv.goBack(); + return; + } - loadURL(wv, webapp.getBaseUrl()); - quitOnNextBackpress = true; + if (quitOnNextBackpress) { + quitOnNextBackpress = false; + moveTaskToBack(true); + return; + } - } + loadURL(wv, webapp.getBaseUrl()); + quitOnNextBackpress = true; + } + }; @Override protected void onResume() { @@ -646,7 +659,6 @@ private void showSystemBars() { if(webapp.isShowFullscreen()) return; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - getWindow().setDecorFitsSystemWindows(true); WindowInsetsController controller = getWindow().getInsetsController(); if (controller != null) { controller.show(WindowInsets.Type.statusBars() | WindowInsets.Type.navigationBars()); @@ -896,8 +908,10 @@ public void onPageFinished(WebView view, String url) { if(url.equals("about:blank")) { String langExtension = LocaleUtils.getFileEnding(); wv.loadUrl("file:///android_asset/errorSite/error_" + langExtension + ".html"); + return; } wv.evaluateJavascript("document.addEventListener(\"visibilitychange\",function (event) {event.stopImmediatePropagation();},true);", null); + if (!webapp.isShowFullscreen()) matchBarsToPage(); super.onPageFinished(view, url); } diff --git a/app/src/main/java/com/cylonid/nativealpha/util/WindowInsetsUtils.kt b/app/src/main/java/com/cylonid/nativealpha/util/WindowInsetsUtils.kt new file mode 100644 index 00000000..e5b8eed7 --- /dev/null +++ b/app/src/main/java/com/cylonid/nativealpha/util/WindowInsetsUtils.kt @@ -0,0 +1,124 @@ +package com.cylonid.nativealpha.util + +import android.app.Activity +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.ColorFilter +import android.graphics.Paint +import android.graphics.PixelFormat +import android.graphics.drawable.Drawable +import android.os.Build +import android.view.View +import androidx.core.graphics.Insets +import androidx.core.view.ViewCompat +import androidx.core.view.WindowCompat +import androidx.core.view.WindowInsetsCompat +import androidx.core.view.WindowInsetsControllerCompat +import com.google.android.material.R +import com.google.android.material.color.MaterialColors + +object WindowInsetsUtils { + + private val INSET_TYPES = WindowInsetsCompat.Type.systemBars() or + WindowInsetsCompat.Type.displayCutout() or + WindowInsetsCompat.Type.ime() + + private val RGB = Regex("""rgba?\((\d+)[,\s]+(\d+)[,\s]+(\d+)(?:[,/\s]+([\d.]+))?\)""") + + /** + * Pads the content view by the insets it handles and reports those as zero, so that a WebView + * further down does not pad them twice. Pass [includeTop] = false to leave the top inset to a + * fitsSystemWindows app bar drawing behind the status bar. + */ + @JvmStatic + @JvmOverloads + fun applyAsPadding( + activity: Activity, + includeTop: Boolean = true, + types: Int = INSET_TYPES, + ) { + WindowCompat.setDecorFitsSystemWindows(activity.window, false) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + activity.window.isNavigationBarContrastEnforced = false + } + ViewCompat.setOnApplyWindowInsetsListener(activity.findViewById(android.R.id.content)) { v, windowInsets -> + val insets = windowInsets.getInsets(types) + v.setPadding(insets.left, if (includeTop) insets.top else 0, insets.right, insets.bottom) + val unhandled = if (includeTop) Insets.NONE else Insets.of(0, insets.top, 0, 0) + WindowInsetsCompat.Builder(windowInsets).setInsets(types, unhandled).build() + } + } + + @JvmStatic + fun applyKeyboardPadding(activity: Activity) { + applyAsPadding(activity, types = WindowInsetsCompat.Type.ime()) + } + + /** Paints each area kept free for the system bars and adapts the icon colour. */ + @JvmStatic + fun applyBarColors(activity: Activity, topCss: String?, bottomCss: String?) { + val content = activity.findViewById(android.R.id.content) + val parsedBottom = parseCssColor(bottomCss) + val top = parseCssColor(topCss) ?: parsedBottom + if (top == null) { + content.background = null + val surface = MaterialColors.getColor(content, R.attr.colorSurface, Color.WHITE) + setBarAppearance(activity, surface, surface) + return + } + val bottom = parsedBottom ?: top + (content.background as? BarBackground)?.setColors(top, bottom) + ?: run { content.background = BarBackground(top, bottom, content) } + setBarAppearance(activity, top, bottom) + } + + private fun setBarAppearance(activity: Activity, top: Int, bottom: Int) { + WindowInsetsControllerCompat(activity.window, activity.window.decorView).apply { + isAppearanceLightStatusBars = MaterialColors.isColorLight(top) + isAppearanceLightNavigationBars = MaterialColors.isColorLight(bottom) + } + } + + private class BarBackground( + private var top: Int, + private var bottom: Int, + private val host: View, + ) : Drawable() { + private val paint = Paint() + + fun setColors(top: Int, bottom: Int) { + if (top == this.top && bottom == this.bottom) return + this.top = top + this.bottom = bottom + invalidateSelf() + } + + override fun draw(canvas: Canvas) { + val b = bounds + paint.color = top + canvas.drawRect(b.left.toFloat(), b.top.toFloat(), b.right.toFloat(), (b.top + host.paddingTop).toFloat(), paint) + paint.color = bottom + canvas.drawRect(b.left.toFloat(), (b.bottom - host.paddingBottom).toFloat(), b.right.toFloat(), b.bottom.toFloat(), paint) + } + + override fun setAlpha(alpha: Int) = Unit + override fun setColorFilter(colorFilter: ColorFilter?) = Unit + override fun getOpacity() = PixelFormat.TRANSLUCENT + } + + private fun parseCssColor(value: String?): Int? { + val v = value?.trim { it == '"' || it.isWhitespace() }?.takeIf { it.isNotEmpty() } ?: return null + RGB.matchEntire(v)?.let { m -> + if ((m.groupValues[4].toFloatOrNull() ?: 1f) < 0.5f) return null + val rgb = (1..3).map { m.groupValues[it].toIntOrNull() ?: return null } + if (rgb.any { it > 255 }) return null + return Color.rgb(rgb[0], rgb[1], rgb[2]) + } + val hex = if (v.length == 4 && v[0] == '#') { + v.drop(1).map { "$it$it" }.joinToString(separator = "", prefix = "#") + } else { + v + } + return runCatching { Color.parseColor(hex) }.getOrNull() + } +} diff --git a/app/src/main/kotlin/com/cylonid/nativealpha/activities/NewsActivity.kt b/app/src/main/kotlin/com/cylonid/nativealpha/activities/NewsActivity.kt index 3f645301..45d413a7 100644 --- a/app/src/main/kotlin/com/cylonid/nativealpha/activities/NewsActivity.kt +++ b/app/src/main/kotlin/com/cylonid/nativealpha/activities/NewsActivity.kt @@ -17,6 +17,7 @@ import com.cylonid.nativealpha.BuildConfig import com.cylonid.nativealpha.databinding.NewsActivityBinding import com.cylonid.nativealpha.model.DataManager import com.cylonid.nativealpha.util.LocaleUtils +import com.cylonid.nativealpha.util.WindowInsetsUtils class NewsActivity : AppCompatActivity(), View.OnTouchListener { @@ -38,6 +39,7 @@ class NewsActivity : AppCompatActivity(), View.OnTouchListener { binding = NewsActivityBinding.inflate(layoutInflater) val view = binding.root setContentView(view) + WindowInsetsUtils.applyAsPadding(this) binding.newsContent.settings.javaScriptEnabled = true binding.newsContent.isLongClickable = false diff --git a/app/src/main/kotlin/com/cylonid/nativealpha/activities/ToolbarBaseActivity.kt b/app/src/main/kotlin/com/cylonid/nativealpha/activities/ToolbarBaseActivity.kt index 2c7d2f3b..13d5b68e 100644 --- a/app/src/main/kotlin/com/cylonid/nativealpha/activities/ToolbarBaseActivity.kt +++ b/app/src/main/kotlin/com/cylonid/nativealpha/activities/ToolbarBaseActivity.kt @@ -6,6 +6,7 @@ import androidx.activity.addCallback import androidx.appcompat.app.AppCompatActivity import androidx.viewbinding.ViewBinding import com.cylonid.nativealpha.databinding.ActivityToolbarBaseBinding +import com.cylonid.nativealpha.util.WindowInsetsUtils abstract class ToolbarBaseActivity : AppCompatActivity() { @@ -21,6 +22,7 @@ abstract class ToolbarBaseActivity : AppCompatActivity() { val baseBinding = ActivityToolbarBaseBinding.inflate(layoutInflater) setContentView(baseBinding.root) + WindowInsetsUtils.applyAsPadding(this, false) _binding = inflateBinding(layoutInflater) baseBinding.activityContent.addView(_binding.root) diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index b04ab3e3..db8e8252 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -4,11 +4,15 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" + android:fitsSystemWindows="true" tools:context=".MainActivity"> + android:layout_height="wrap_content" + android:background="@color/colorSignature" + android:fitsSystemWindows="true" + app:liftOnScroll="false"> + android:theme="@style/ThemeOverlay.AppTheme.Toolbar" /> diff --git a/app/src/main/res/layout/fragment_adblock_list.xml b/app/src/main/res/layout/fragment_adblock_list.xml index f256e2ca..de042a3b 100644 --- a/app/src/main/res/layout/fragment_adblock_list.xml +++ b/app/src/main/res/layout/fragment_adblock_list.xml @@ -9,7 +9,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" app:behind_swiped_item_icon="@drawable/ic_delete_black_24dp" - app:behind_swiped_item_bg_color="@color/swipe_action_negative" + app:behind_swiped_item_bg_color="?attr/colorErrorContainer" app:item_layout="@layout/adblock_list_item" app:swiped_item_opacity_fades_on_swiping="true" app:behind_swiped_item_icon_margin="@dimen/margin_swipe_icon" diff --git a/app/src/main/res/layout/fragment_web_app_list.xml b/app/src/main/res/layout/fragment_web_app_list.xml index f8b639cf..03276f33 100644 --- a/app/src/main/res/layout/fragment_web_app_list.xml +++ b/app/src/main/res/layout/fragment_web_app_list.xml @@ -10,8 +10,8 @@ android:layout_height="match_parent" app:item_layout="@layout/web_app_list_item" app:behind_swiped_item_icon="@drawable/ic_delete_black_24dp" - app:behind_swiped_item_bg_color="@color/swipe_action_negative" - app:behind_swiped_item_bg_color_secondary="@color/swipe_action_neutral" + app:behind_swiped_item_bg_color="?attr/colorErrorContainer" + app:behind_swiped_item_bg_color_secondary="?attr/colorTertiaryContainer" app:behind_swiped_item_icon_secondary="@drawable/ic_settings_black_24dp" app:behind_swiped_item_icon_margin="@dimen/margin_swipe_icon" app:swiped_item_opacity_fades_on_swiping="true" diff --git a/app/src/main/res/layout/global_settings.xml b/app/src/main/res/layout/global_settings.xml index 421d50bd..73647c65 100644 --- a/app/src/main/res/layout/global_settings.xml +++ b/app/src/main/res/layout/global_settings.xml @@ -18,7 +18,8 @@ @@ -33,7 +34,8 @@ @@ -165,24 +167,30 @@ + +