commit 90a3efe43e19b40c5dc37b2ad12b4ffbd85dd432
| author | 斟酌 鵬兄 <tgckpg@gmail.com> |
| date | 2026-10-14T09:04:04Z |
| subject | Sync SystemBar color |
commit 90a3efe43e19b40c5dc37b2ad12b4ffbd85dd432
Author: 斟酌 鵬兄 <tgckpg@gmail.com>
Date: 2026-10-14T09:04:04Z
Sync SystemBar color
---
app/src/main/assets/reader/content.js | 20 +++++++++-------
.../net/astropenguin/naroureader/MainActivity.java | 26 ++++++++++----------
tests/reader.test.cjs | 28 ++++++++++++++++++----
3 files changed, 48 insertions(+), 26 deletions(-)
diff --git a/app/src/main/assets/reader/content.js b/app/src/main/assets/reader/content.js
index e14841a..269a247 100644
--- a/app/src/main/assets/reader/content.js
+++ b/app/src/main/assets/reader/content.js
@@ -13,7 +13,7 @@
const defaults = {font: 20, spacing: 1.9, paragraph: 0.3, theme: 'paper', auto: true, normalColor: null, highlightColor: null, backgroundColor: null, language: i18n.language};
const textColors = {paper: ['#2d332f', '#17694f', '#e3e3df'], white: ['#222222', '#17694f', '#e3e3df'], dark: ['#e2e4de', '#79dfb5', '#000000']};
let settings = {...defaults}, opened = false, lastFocus;
- let nativeTheme = null;
+ let nativeBackgroundColor = null;
let controlsVisible = false, savedPosition = 0, restoring = false, saveTimer;
const positionKey = `narouPosition:${location.pathname}`;
const notify = data => browser.runtime.sendNativeMessage('narou', data).catch(() => {});
@@ -157,7 +157,7 @@
input.addEventListener('input', () => { settings[key] = input.value; applyTextColors(); save(); });
}
const resetColors = el('button', '@resetColors', {type: 'button', id: 'reading-reset-colors'});
- resetColors.addEventListener('click', () => { settings.normalColor = settings.highlightColor = null; applyTextColors(); save(); });
+ resetColors.addEventListener('click', () => { settings.normalColor = settings.highlightColor = settings.backgroundColor = null; applyTextColors(); save(); });
header.append(resetColors);
const appMenu = el('button', '☰', {type: 'button', 'aria-label': '@appMenu'});
appMenu.addEventListener('click', () => notify({type: 'menu'}));
@@ -465,7 +465,7 @@
pageActive = true;
touching = false;
resetMotion();
- if (opened) { notify({type: 'reader', open: true}); notify({type: 'theme', theme: settings.theme}); }
+ if (opened) { notify({type: 'reader', open: true}); notify({type: 'background', color: settings.backgroundColor || textColors[settings.theme][2]}); }
startMotion();
});
// Continuous scrolling must checkpoint periodically, not only after scrolling stops.
@@ -491,10 +491,6 @@
restoring = true;
panel.dataset.theme = settings.theme;
applyTextColors();
- if (nativeTheme !== settings.theme) {
- nativeTheme = settings.theme;
- notify({type: 'theme', theme: settings.theme});
- }
article.style.setProperty('--font', `${settings.font}px`);
article.style.setProperty('--spacing', settings.spacing);
article.style.setProperty('--paragraph', `${settings.paragraph}em`);
@@ -518,9 +514,15 @@
const [normal, highlight, background] = textColors[settings.theme];
article.style.setProperty('--normal-text', settings.normalColor || normal);
article.style.setProperty('--highlight-text', settings.highlightColor || highlight);
- panel.style.setProperty('--bg', settings.backgroundColor || background);
+ const backgroundColor = settings.backgroundColor || background;
+ panel.style.setProperty('--bg', backgroundColor);
colorInputs.normalColor.value = settings.normalColor || normal;
colorInputs.highlightColor.value = settings.highlightColor || highlight;
+ colorInputs.backgroundColor.value = backgroundColor;
+ if (nativeBackgroundColor !== backgroundColor) {
+ nativeBackgroundColor = backgroundColor;
+ notify({type: 'background', color: backgroundColor});
+ }
}
function open() {
if (opened) return;
@@ -611,7 +613,7 @@
settings.paragraph = Number.isFinite(stored.paragraph) ? Math.min(2, Math.max(0, stored.paragraph)) : defaults.paragraph;
settings.theme = ['paper', 'white', 'dark'].includes(stored.theme) ? stored.theme : defaults.theme;
settings.auto = typeof stored.auto === 'boolean' ? stored.auto : defaults.auto;
- for (const key of ['normalColor', 'highlightColor']) settings[key] = /^#[0-9a-f]{6}$/i.test(stored[key]) ? stored[key] : null;
+ for (const key of ['normalColor', 'highlightColor', 'backgroundColor']) settings[key] = /^#[0-9a-f]{6}$/i.test(stored[key]) ? stored[key] : null;
if (settings.auto && !opened) open(); else apply(savedPosition);
}).catch(() => { open(); });
notify({type: 'language', language: i18n.language});
diff --git a/app/src/main/java/net/astropenguin/naroureader/MainActivity.java b/app/src/main/java/net/astropenguin/naroureader/MainActivity.java
index ab9dfcb..2b21851 100644
--- a/app/src/main/java/net/astropenguin/naroureader/MainActivity.java
+++ b/app/src/main/java/net/astropenguin/naroureader/MainActivity.java
@@ -38,7 +38,7 @@ public final class MainActivity extends Activity {
private GeckoSession session;
private GeckoView geckoView;
private FrameLayout root;
- private boolean darkReaderTheme;
+ private boolean darkReaderBackground;
private Button menuButton;
private ProgressBar progress;
private SharedPreferences prefs;
@@ -91,7 +91,7 @@ public final class MainActivity extends Activity {
return insets;
});
setContentView(root);
- applyReaderTheme(prefs.getString("readerTheme", "paper"));
+ applyReaderBackground(prefs.getString("readerBackgroundColor", "#e3e3df"));
if (Build.VERSION.SDK_INT >= 33) getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
android.window.OnBackInvokedDispatcher.PRIORITY_DEFAULT, this::navigateBack);
immersive();
@@ -270,7 +270,7 @@ public final class MainActivity extends Activity {
menuButton.setContentDescription(tr("native.menu"));
}
if ("menu".equals(data.optString("type"))) showMenu();
- if ("theme".equals(data.optString("type"))) applyReaderTheme(data.optString("theme", "paper"));
+ if ("background".equals(data.optString("type"))) applyReaderBackground(data.optString("color"));
if ("reader".equals(data.optString("type"))) {
reading = data.optBoolean("open");
if (!reading) tilt.stop();
@@ -399,16 +399,16 @@ public final class MainActivity extends Activity {
if (session != null) session.close();
super.onDestroy();
}
- /** Match the native cutout/safe-area background to the extension's palette. */
- private void applyReaderTheme(String theme) {
- if (!"dark".equals(theme) && !"white".equals(theme)) theme = "paper";
- darkReaderTheme = "dark".equals(theme);
- int background = darkReaderTheme ? Color.rgb(24, 32, 30)
- : "white".equals(theme) ? Color.WHITE : Color.rgb(246, 240, 227);
+ /** Match the native cutout/safe-area background to the reader's actual color. */
+ private void applyReaderBackground(String color) {
+ if (color == null || !color.matches("#[0-9a-fA-F]{6}")) return;
+ int background = Color.parseColor(color);
+ darkReaderBackground = (299 * Color.red(background) + 587 * Color.green(background)
+ + 114 * Color.blue(background)) < 128000;
root.setBackgroundColor(background);
getWindow().setBackgroundDrawable(new android.graphics.drawable.ColorDrawable(background));
- if (!theme.equals(prefs.getString("readerTheme", "paper"))) {
- prefs.edit().putString("readerTheme", theme).apply();
+ if (!color.equals(prefs.getString("readerBackgroundColor", ""))) {
+ prefs.edit().putString("readerBackgroundColor", color).apply();
}
immersive();
}
@@ -421,7 +421,7 @@ public final class MainActivity extends Activity {
if (controller != null) {
int lightBars = WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
| WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
- controller.setSystemBarsAppearance(darkReaderTheme ? 0 : lightBars, lightBars);
+ controller.setSystemBarsAppearance(darkReaderBackground ? 0 : lightBars, lightBars);
controller.setSystemBarsBehavior(WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
controller.hide(WindowInsets.Type.systemBars());
}
@@ -430,7 +430,7 @@ public final class MainActivity extends Activity {
View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
- (darkReaderTheme ? 0 : View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR | View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR));
+ (darkReaderBackground ? 0 : View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR | View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR));
}
}
}
diff --git a/tests/reader.test.cjs b/tests/reader.test.cjs
index cf5c588..0fd9a3d 100644
--- a/tests/reader.test.cjs
+++ b/tests/reader.test.cjs
@@ -30,7 +30,7 @@ const ROOT=path.resolve(__dirname,'..');
assert.equal(await v.evaluate(e=>e.clientHeight),840);
assert.equal(await panel.locator('article p').evaluateAll(ps=>ps.filter(p=>!p.textContent.trim()&&!p.querySelector('img')).length),0);
assert(messages.some(m=>m.type==='reader'&&m.open));
- assert(messages.some(m=>m.type==='theme'&&m.theme==='paper'));
+ assert(messages.some(m=>m.type==='background'&&m.color==='#e3e3df'));
const width=await v.evaluate(e=>e.scrollWidth);
const firstParagraph=panel.locator('article p').first();
// A tap on ruby text focuses its paragraph without opening chrome.
@@ -54,9 +54,9 @@ const ROOT=path.resolve(__dirname,'..');
await panel.locator('#paragraph').evaluate(e=>{e.value='1.2';e.dispatchEvent(new Event('input'));});
await page.waitForTimeout(100);assert.equal(data.narouTategaki.paragraph,1.2);
await panel.locator('select').first().selectOption('white');
- assert(messages.some(m=>m.type==='theme'&&m.theme==='white'));
+ assert(messages.some(m=>m.type==='background'&&m.color==='#e3e3df'));
await panel.locator('select').first().selectOption('dark');
- assert(messages.some(m=>m.type==='theme'&&m.theme==='dark'));
+ assert(messages.some(m=>m.type==='background'&&m.color==='#000000'));
await panel.getByRole('button',{name:'App menu'}).click();assert(messages.some(m=>m.type==='menu'));
await panel.getByRole('button',{name:'Continue reading'}).click();
await v.focus();await page.keyboard.press('PageDown');await page.keyboard.press('PageDown');
@@ -68,8 +68,28 @@ const ROOT=path.resolve(__dirname,'..');
assert(Math.abs(before-restored)<.002,`${before} != ${restored}`);
assert.equal(await panel.getAttribute('data-theme'),'dark');
assert.equal(await panel.locator('#reading-highlightColor').inputValue(),'#ff7755','custom colors survive reload and theme change');
- assert(messages.filter(m=>m.type==='theme'&&m.theme==='dark').length>=2,'restored theme must reach the native app');
+ assert(messages.filter(m=>m.type==='background'&&m.color==='#000000').length>=2,'restored background must reach the native app');
await page.touchscreen.tap(5,5);
+ await panel.locator('#reading-backgroundColor').evaluate(e=>{e.value='#123456';e.dispatchEvent(new Event('input'));});
+ assert.equal(data.narouTategaki.backgroundColor,'#123456');
+ assert.equal(await panel.evaluate(e=>getComputedStyle(e).backgroundColor),'rgb(18, 52, 86)');
+ assert(messages.some(m=>m.type==='background'&&m.color==='#123456'));
+ await load();
+ assert.equal(await panel.locator('#reading-backgroundColor').inputValue(),'#123456','background survives reload');
+ const backgroundMessages=messages.filter(m=>m.type==='background').length;
+ await page.evaluate(()=>window.dispatchEvent(new PageTransitionEvent('pageshow',{persisted:true})));
+ await page.waitForTimeout(50);
+ assert.equal(messages.filter(m=>m.type==='background').length,backgroundMessages+1,'cached page resends background');
+ assert.equal(messages.filter(m=>m.type==='background').at(-1).color,'#123456');
+ await page.touchscreen.tap(5,5);
+ await panel.locator('select').first().selectOption('white');
+ assert.equal(await panel.locator('#reading-backgroundColor').inputValue(),'#123456','custom background overrides theme');
+ await panel.locator('#reading-reset-colors').click();
+ assert.equal(data.narouTategaki.backgroundColor,null);
+ assert.equal(await panel.locator('#reading-backgroundColor').inputValue(),'#e3e3df');
+ assert.equal(messages.filter(m=>m.type==='background').at(-1).color,'#e3e3df');
+ assert(!messages.some(m=>m.type==='theme'),'native protocol no longer sends themes');
+ await panel.locator('#reading-highlightColor').evaluate(e=>{e.value='#ff7755';e.dispatchEvent(new Event('input'));});
// Language switches update existing controls without losing focus, colors or position.
const language=panel.locator('#reading-language');
const positionBeforeLanguage=await v.evaluate(e=>e.scrollLeft);