diff --git a/ui/src/lib/app.svelte.ts b/ui/src/lib/app.svelte.ts index 64bebe3..8b251fd 100644 --- a/ui/src/lib/app.svelte.ts +++ b/ui/src/lib/app.svelte.ts @@ -22,6 +22,7 @@ import { telegramContentSafeAreaTop, telegramSafeAreaInset, telegramDisableVerticalSwipes, + telegramShowSettingsButton, telegramHaptic, telegramLaunch, type TelegramLaunch, @@ -647,6 +648,9 @@ export async function bootstrap(): Promise { telegramOnEvent('fullscreenChanged', syncTelegramSafeArea); // Re-apply the theme live when the user switches Telegram's light/dark mode while the app is open. telegramOnEvent('themeChanged', () => syncTelegramTheme()); + // Telegram's native Settings button (Bot API 7.0) opens our Settings screen; the in-app gear + // entry stays the primary path. No-op on clients predating the button. + telegramShowSettingsButton(() => navigate('/settings')); await bootTelegram(launch); app.ready = true; return; diff --git a/ui/src/lib/telegram.test.ts b/ui/src/lib/telegram.test.ts index fec4340..35ceb3a 100644 --- a/ui/src/lib/telegram.test.ts +++ b/ui/src/lib/telegram.test.ts @@ -9,6 +9,7 @@ import { telegramOpenExternalLink, telegramThemeParams, telegramSafeAreaInset, + telegramShowSettingsButton, } from './telegram'; function stubWebApp(initData: string, startParam?: string) { @@ -77,6 +78,24 @@ describe('telegramOpenExternalLink', () => { }); }); +describe('telegramShowSettingsButton', () => { + afterEach(() => vi.unstubAllGlobals()); + + it('shows the native Settings button and wires its click inside Telegram', () => { + const onClick = vi.fn(); + const show = vi.fn(); + const handler = vi.fn(); + vi.stubGlobal('window', { Telegram: { WebApp: { SettingsButton: { onClick, show } } } }); + telegramShowSettingsButton(handler); + expect(onClick).toHaveBeenCalledWith(handler); + expect(show).toHaveBeenCalled(); + }); + + it('is a no-op without the SDK button (older client / outside Telegram)', () => { + expect(() => telegramShowSettingsButton(() => {})).not.toThrow(); + }); +}); + describe('routeExternalLinkInTelegram', () => { afterEach(() => vi.unstubAllGlobals()); diff --git a/ui/src/lib/telegram.ts b/ui/src/lib/telegram.ts index 40b3efe..9efbb92 100644 --- a/ui/src/lib/telegram.ts +++ b/ui/src/lib/telegram.ts @@ -41,6 +41,12 @@ interface TelegramWebApp { onClick?: (cb: () => void) => void; offClick?: (cb: () => void) => void; }; + SettingsButton?: { + show?: () => void; + hide?: () => void; + onClick?: (cb: () => void) => void; + offClick?: (cb: () => void) => void; + }; } function webApp(): TelegramWebApp | undefined { @@ -293,6 +299,19 @@ export function telegramDisableVerticalSwipes(): void { webApp()?.disableVerticalSwipes?.(); } +/** + * telegramShowSettingsButton reveals Telegram's native Settings button (in the Mini App's ⋮ menu, + * Bot API 7.0) and routes its taps to handler. A no-op outside Telegram or on a client predating + * the button, so the app's own in-app settings entry stays the primary path. The app registers it + * once per launch (Telegram hides the button when the Mini App closes), so there is no offClick. + */ +export function telegramShowSettingsButton(handler: () => void): void { + const b = webApp()?.SettingsButton; + if (!b?.show) return; + b.onClick?.(handler); + b.show(); +} + /** Haptic is the set of feedbacks the app triggers. */ export type Haptic = 'select' | 'success' | 'error' | 'warning' | 'light' | 'medium' | 'heavy';