diff --git a/app/src/main/assets/app.css b/app/src/main/assets/app.css index 3f98668..a3f5753 100644 --- a/app/src/main/assets/app.css +++ b/app/src/main/assets/app.css @@ -21,6 +21,10 @@ * outline / outlineVariant */ :root { + /* Prevent Android WebView algorithmic dark mode from recoloring + pseudo-elements. This page manages its own dark theme. */ + color-scheme: dark; + /* ── M3 Semantic Color Roles (dark scheme, hue 0° defaults) ── */ --md-primary: hsl(0, 70%, 80%); /* tone 80 */ --md-on-primary: hsl(0, 70%, 15%); /* tone 20 */ diff --git a/app/src/main/assets/index.html b/app/src/main/assets/index.html index e5d7cbb..590858f 100644 --- a/app/src/main/assets/index.html +++ b/app/src/main/assets/index.html @@ -4,6 +4,7 @@ + LastWave diff --git a/app/src/main/assets/settings/settings.css b/app/src/main/assets/settings/settings.css index e579409..bb4e4e6 100644 --- a/app/src/main/assets/settings/settings.css +++ b/app/src/main/assets/settings/settings.css @@ -134,6 +134,8 @@ /* * TOGGLE WRAPPER — fixed 52×52 touch target, centres switch inside. + * Track is 52×32; wrapper provides M3 minimum touch area with the + * height difference absorbed as vertical padding. */ .toggle-wrap { width: 52px; @@ -144,44 +146,75 @@ flex-shrink: 0; } -/* ── Material You Toggle ──────────────────────────────────── */ +/* ── Material You Toggle Switch ─────────────────────────────── */ -/* OFF track */ +/* + * Track 52 × 32 dp (border-box, 2 dp border) + * Thumb 20 × 20 dp — fixed size both states, only position changes + * Inner content area: 48 × 28 dp + * + * ON track uses --md-primary-container (deep muted tonal surface from + * Android dynamic color) — not raw --md-primary, which is too harsh. + * ON thumb uses --md-on-primary-container so it reads cleanly on top. + */ + +/* ── Track ───────────────────────────────────────────────────── */ .toggle { - width: 44px; - height: 24px; + width: 52px; + height: 32px; border-radius: 999px; - background: rgba(120, 120, 120, 0.35); - border: none; + background: rgba(255, 255, 255, 0.07); + border: 2px solid rgba(255, 255, 255, 0.28); position: relative; - transition: background 0.25s ease; flex-shrink: 0; cursor: pointer; box-sizing: border-box; + -webkit-tap-highlight-color: transparent; + transition: + background 0.25s cubic-bezier(0.2, 0, 0, 1), + border-color 0.25s cubic-bezier(0.2, 0, 0, 1); } -/* ON track — direct var(), NO color-mix() */ +/* ON track — soft tonal container, never harsh primary */ .toggle.active { - background: var(--md-primary, var(--accent, #4CAF50)); + background: var(--md-primary-container, rgba(76, 175, 80, 0.30)); + border-color: transparent; } -/* OFF thumb */ +/* ── Thumb ───────────────────────────────────────────────────── */ +/* + * Fixed 20 × 20 dp in both states — no size change, no scaling illusion. + * top = (28 − 20) / 2 = 4 dp (same both states) + * OFF left = 4 dp + * ON left = 48 − 20 − 4 = 24 dp + * Only `left` and `background` animate — nothing else shifts. + */ .toggle::after { content: ''; position: absolute; - top: 3px; - left: 3px; - width: 18px; - height: 18px; border-radius: 50%; - background: #ffffff; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.30); - transition: transform 0.25s cubic-bezier(0.34, 1.3, 0.64, 1); -} - -/* ON thumb — slide right */ + width: 20px; + height: 20px; + top: 4px; + left: 4px; + background: #ffffff !important; + opacity: 1 !important; + filter: none !important; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.22); + transition: + left 0.25s cubic-bezier(0.2, 0, 0, 1), + box-shadow 0.25s ease; +} + +/* ON thumb — slides right only */ .toggle.active::after { - transform: translateX(20px); + left: 24px; + background: #ffffff !important; + opacity: 1 !important; + filter: none !important; + box-shadow: + 0 2px 6px rgba(0, 0, 0, 0.28), + 0 0 0 1px rgba(0, 0, 0, 0.05); } /* ── Material You Palette Grid ────────────────────────────── */ diff --git a/app/src/main/java/com/lastwave/app/MainActivity.java b/app/src/main/java/com/lastwave/app/MainActivity.java index b805b2b..714019d 100644 --- a/app/src/main/java/com/lastwave/app/MainActivity.java +++ b/app/src/main/java/com/lastwave/app/MainActivity.java @@ -27,6 +27,8 @@ import androidx.core.view.WindowInsetsCompat; import androidx.webkit.WebViewAssetLoader; import androidx.webkit.WebViewClientCompat; +import androidx.webkit.WebSettingsCompat; +import androidx.webkit.WebViewFeature; import java.io.*; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -119,6 +121,20 @@ protected void onCreate(Bundle savedInstanceState) { // https://appassets.androidplatform.net. Enabling them would allow // file:// URLs to make cross-origin requests, creating XSS risk. settings.setCacheMode(WebSettings.LOAD_DEFAULT); + + // ── Disable WebView algorithmic dark mode ───────────────────────────── + // Without this, Android 10+ WebView applies a system-level color + // transformation pass to pseudo-elements (::after, ::before) that runs + // BELOW the CSS cascade and ignores !important — causing the toggle + // thumb and other white pseudo-elements to appear dark/grey in ON state. + // API 29-32: setForceDark(OFF) + // API 33+ : setAlgorithmicDarkeningAllowed(false) + if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) { + WebSettingsCompat.setForceDark(settings, WebSettingsCompat.FORCE_DARK_OFF); + } + if (WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING)) { + WebSettingsCompat.setAlgorithmicDarkeningAllowed(settings, false); + } // FIX: Removed MIXED_CONTENT_ALWAYS_ALLOW — redundant because // android:usesCleartextTraffic="false" already blocks HTTP at the OS // level, and the setting contradicts the manifest's intent. @@ -174,12 +190,17 @@ public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) { @Override public void handleOnBackPressed() { + // evaluateJavascript JSON-encodes its return value. + // Returning a JS boolean (not a string) means the callback + // receives exactly "true" or "false" — no extra quotes. + // Using String() would wrap it in a JS string, giving + // "\"true\"" — breaking the equality check below. webView.evaluateJavascript( - "window._lwHandleBack ? String(window._lwHandleBack()) : 'false'", + "typeof window._lwHandleBack === 'function' ? !!window._lwHandleBack() : false", result -> { - // result is a JSON string — "true" or "false" + // result is the JSON-encoded JS boolean: "true" or "false" if (!"true".equals(result)) { - // JS has nothing to go back to — exit normally + // JS stack is empty — let Android exit normally setEnabled(false); getOnBackPressedDispatcher().onBackPressed(); } @@ -374,15 +395,33 @@ public void clearSession() { // ── Wallpaper Colors ────────────────────────────────────────────────── @JavascriptInterface public String getWallpaperColors() { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O_MR1) return ""; try { - WallpaperManager wm = WallpaperManager.getInstance(MainActivity.this); - WallpaperColors colors = wm.getWallpaperColors(WallpaperManager.FLAG_SYSTEM); - if (colors == null) return ""; - String primary = colorToHex(colors.getPrimaryColor().toArgb()); - String secondary = colors.getSecondaryColor() != null ? colorToHex(colors.getSecondaryColor().toArgb()) : primary; - String tertiary = colors.getTertiaryColor() != null ? colorToHex(colors.getTertiaryColor().toArgb()) : secondary; - return "{\"primary\":\"" + primary + "\",\"secondary\":\"" + secondary + "\",\"tertiary\":\"" + tertiary + "\"}"; + // Android 12+ (API 31): read the real Monet / Dynamic Color tonal + // palette directly from system resource slots. These are exactly what + // DynamicColors / dynamicDarkColorScheme() uses — NOT the raw dominant + // wallpaper hues from WallpaperManager which are pre-Monet colors. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + int primary = ContextCompat.getColor(MainActivity.this, android.R.color.system_accent1_400); + int secondary = ContextCompat.getColor(MainActivity.this, android.R.color.system_accent2_400); + int tertiary = ContextCompat.getColor(MainActivity.this, android.R.color.system_accent3_400); + return "{\"primary\":\"" + colorToHex(primary) + + "\",\"secondary\":\"" + colorToHex(secondary) + + "\",\"tertiary\":\"" + colorToHex(tertiary) + "\"}"; + } + // Android 8.1–11 (API 27–30): best available fallback via WallpaperColors. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) { + WallpaperManager wm = WallpaperManager.getInstance(MainActivity.this); + WallpaperColors colors = wm.getWallpaperColors(WallpaperManager.FLAG_SYSTEM); + if (colors == null) return ""; + String primary = colorToHex(colors.getPrimaryColor().toArgb()); + String secondary = colors.getSecondaryColor() != null + ? colorToHex(colors.getSecondaryColor().toArgb()) : primary; + String tertiary = colors.getTertiaryColor() != null + ? colorToHex(colors.getTertiaryColor().toArgb()) : secondary; + return "{\"primary\":\"" + primary + "\",\"secondary\":\"" + secondary + + "\",\"tertiary\":\"" + tertiary + "\"}"; + } + return ""; } catch (Exception e) { return ""; } }