Merge pull request 'feat(ui): paint VK status bar to the app theme (VKWebAppSetViewSettings)' (#152) from feature/vk-status-bar-chrome into development
This commit was merged in pull request #152.
This commit is contained in:
+5
-1
@@ -153,7 +153,11 @@ e2e and the screenshots.
|
|||||||
`…NotificationOccurred` / `…SelectionChanged`), through the shared `lib/haptics.ts` dispatcher; and
|
`…NotificationOccurred` / `…SelectionChanged`), through the shared `lib/haptics.ts` dispatcher; and
|
||||||
VK's **horizontal swipe-back** is disabled at launch (`VKWebAppSetSwipeSettings`) so it does not
|
VK's **horizontal swipe-back** is disabled at launch (`VKWebAppSetSwipeSettings`) so it does not
|
||||||
fight the app's own edge-swipe-back and tile drag — parity with Telegram's disabled vertical
|
fight the app's own edge-swipe-back and tile drag — parity with Telegram's disabled vertical
|
||||||
swipes, the app owning navigation through its back chevron.
|
swipes, the app owning navigation through its back chevron. VK's **status bar** (and, on Android,
|
||||||
|
the action / navigation bars) is painted to the app theme via `VKWebAppSetViewSettings` on launch
|
||||||
|
and every theme change — parity with the Telegram chrome painting (`syncVKChrome` mirrors
|
||||||
|
`syncTelegramChrome`); the status-bar appearance follows the `--bg` token's luminance
|
||||||
|
(`appearanceForBg`).
|
||||||
|
|
||||||
## Tiles & board
|
## Tiles & board
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ import {
|
|||||||
telegramCloudGet,
|
telegramCloudGet,
|
||||||
telegramCloudSet,
|
telegramCloudSet,
|
||||||
} from './telegram';
|
} from './telegram';
|
||||||
import { onVKPath, insideVK, vkInit, vkDisableSwipeBack, vkLaunchParams, vkUserName, vkStartParam, vkOnScheme, vkOnInsets } from './vk';
|
import { onVKPath, insideVK, vkInit, vkDisableSwipeBack, vkLaunchParams, vkUserName, vkStartParam, vkOnScheme, vkOnInsets, vkSetViewSettings, appearanceForBg } from './vk';
|
||||||
import { haptic } from './haptics';
|
import { haptic } from './haptics';
|
||||||
import { CLOUD_PREFS_KEY, decodeClientPrefs, encodeClientPrefs } from './cloudprefs';
|
import { CLOUD_PREFS_KEY, decodeClientPrefs, encodeClientPrefs } from './cloudprefs';
|
||||||
import { parseStartParam } from './deeplink';
|
import { parseStartParam } from './deeplink';
|
||||||
@@ -552,6 +552,19 @@ function syncTelegramChrome(): void {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* syncVKChrome paints VK's status bar (and, on Android, the action/navigation bars) from the app's
|
||||||
|
* live theme tokens, so the surrounding VK chrome matches the UI. A no-op outside VK. Parity with
|
||||||
|
* syncTelegramChrome.
|
||||||
|
*/
|
||||||
|
function syncVKChrome(): void {
|
||||||
|
if (!insideVK() || typeof document === 'undefined') return;
|
||||||
|
const cs = getComputedStyle(document.documentElement);
|
||||||
|
const bg = cs.getPropertyValue('--bg').trim();
|
||||||
|
const bgElev = cs.getPropertyValue('--bg-elev').trim();
|
||||||
|
void vkSetViewSettings(appearanceForBg(bg), bgElev, bg);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* syncTelegramSafeArea mirrors Telegram's safe-area insets into CSS vars: the content-safe-area top
|
* syncTelegramSafeArea mirrors Telegram's safe-area insets into CSS vars: the content-safe-area top
|
||||||
* (the height Telegram's native nav overlays the viewport in fullscreen) into --tg-content-top
|
* (the height Telegram's native nav overlays the viewport in fullscreen) into --tg-content-top
|
||||||
@@ -694,6 +707,9 @@ export async function bootstrap(): Promise<void> {
|
|||||||
// VK mobile webview's prefers-color-scheme does not track it).
|
// VK mobile webview's prefers-color-scheme does not track it).
|
||||||
void vkOnScheme((scheme) => {
|
void vkOnScheme((scheme) => {
|
||||||
if (app.theme === 'auto') applyTheme(scheme);
|
if (app.theme === 'auto') applyTheme(scheme);
|
||||||
|
// Repaint VK's status/nav bars to the resolved theme; this handler also fires on launch, so it
|
||||||
|
// covers the initial paint (auto → the VK scheme just applied, explicit → the theme from boot).
|
||||||
|
syncVKChrome();
|
||||||
});
|
});
|
||||||
// Clear the device safe area from VK's insets — the VK mobile webview does not surface them via
|
// Clear the device safe area from VK's insets — the VK mobile webview does not surface them via
|
||||||
// CSS env() (notably the Android home bar). max() keeps whichever of env() / the VK value is real.
|
// CSS env() (notably the Android home bar). max() keeps whichever of env() / the VK value is real.
|
||||||
@@ -995,6 +1011,7 @@ async function reconcileCloudPrefs(): Promise<void> {
|
|||||||
export function setTheme(theme: ThemePref): void {
|
export function setTheme(theme: ThemePref): void {
|
||||||
app.theme = theme;
|
app.theme = theme;
|
||||||
applyTheme(theme);
|
applyTheme(theme);
|
||||||
|
syncVKChrome();
|
||||||
persistPrefs();
|
persistPrefs();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+23
-1
@@ -1,5 +1,5 @@
|
|||||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||||
import { insideVK, onVKPath, vkAppId, vkLaunchParams, vkStartParam } from './vk';
|
import { insideVK, onVKPath, vkAppId, vkLaunchParams, vkStartParam, appearanceForBg } from './vk';
|
||||||
|
|
||||||
describe('vk launch detection', () => {
|
describe('vk launch detection', () => {
|
||||||
afterEach(() => vi.unstubAllGlobals());
|
afterEach(() => vi.unstubAllGlobals());
|
||||||
@@ -49,3 +49,25 @@ describe('vk launch params', () => {
|
|||||||
expect(vkStartParam()).toBe('');
|
expect(vkStartParam()).toBe('');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('appearanceForBg', () => {
|
||||||
|
it('maps a dark background to the dark appearance (light status-bar icons)', () => {
|
||||||
|
expect(appearanceForBg('#0f1115')).toBe('dark');
|
||||||
|
expect(appearanceForBg('#171a21')).toBe('dark');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('maps a light background to the light appearance (dark status-bar icons)', () => {
|
||||||
|
expect(appearanceForBg('#f3f4f6')).toBe('light');
|
||||||
|
expect(appearanceForBg('#ffffff')).toBe('light');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('accepts shorthand hex and surrounding whitespace (a raw CSS custom-property value)', () => {
|
||||||
|
expect(appearanceForBg('#000')).toBe('dark');
|
||||||
|
expect(appearanceForBg(' #fff ')).toBe('light');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('defaults to light for an unparseable colour', () => {
|
||||||
|
expect(appearanceForBg('')).toBe('light');
|
||||||
|
expect(appearanceForBg('rgb(0, 0, 0)')).toBe('light');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -198,3 +198,42 @@ export async function vkDisableSwipeBack(): Promise<void> {
|
|||||||
// Outside VK / unsupported: VK's default gesture handling stays as-is.
|
// Outside VK / unsupported: VK's default gesture handling stays as-is.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* appearanceForBg maps a background colour to the VK view appearance: a dark background wants the
|
||||||
|
* 'dark' appearance (light status-bar icons), a light one 'light'. It accepts a `#rgb` / `#rrggbb`
|
||||||
|
* string (the app's `--bg` token); anything unparseable defaults to 'light'. Pure, so it is
|
||||||
|
* unit-tested.
|
||||||
|
*/
|
||||||
|
export function appearanceForBg(bg: string): 'light' | 'dark' {
|
||||||
|
const hex = bg.trim().replace(/^#/, '');
|
||||||
|
const full = hex.length === 3 ? [...hex].map((c) => c + c).join('') : hex;
|
||||||
|
if (full.length !== 6 || /[^0-9a-fA-F]/.test(full)) return 'light';
|
||||||
|
const r = parseInt(full.slice(0, 2), 16);
|
||||||
|
const g = parseInt(full.slice(2, 4), 16);
|
||||||
|
const b = parseInt(full.slice(4, 6), 16);
|
||||||
|
// Rec. 601 luma; below the midpoint is a dark background.
|
||||||
|
return 0.299 * r + 0.587 * g + 0.114 * b < 128 ? 'dark' : 'light';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* vkSetViewSettings paints VK's status bar — and, on Android, the action and navigation bars — to
|
||||||
|
* match the app. appearance is the VK status-bar appearance ('light' | 'dark'); actionBarColor and
|
||||||
|
* navigationBarColor are Android-only hex colours (ignored elsewhere). Best-effort; a no-op outside
|
||||||
|
* VK or on a client without the method.
|
||||||
|
*/
|
||||||
|
export async function vkSetViewSettings(
|
||||||
|
appearance: 'light' | 'dark',
|
||||||
|
actionBarColor: string,
|
||||||
|
navigationBarColor: string,
|
||||||
|
): Promise<void> {
|
||||||
|
try {
|
||||||
|
await (await bridge()).send('VKWebAppSetViewSettings', {
|
||||||
|
status_bar_style: appearance,
|
||||||
|
action_bar_color: actionBarColor,
|
||||||
|
navigation_bar_color: navigationBarColor,
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
// Outside VK / unsupported: the client keeps its default bars.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user