mirror of
https://github.com/bulwarkmail/native.git
synced 2026-11-01 08:06:13 +00:00
117 lines
4.0 KiB
TypeScript
117 lines
4.0 KiB
TypeScript
// The iOS inbox widget (WidgetKit, plugins/ios-widget). It can't run JS, so
|
|
// the app hands it a small finished snapshot: the active account's inbox
|
|
// unread count and newest messages, cut from the data the Android widgets
|
|
// draw (./snapshot.ts), with the texts already translated and each message's
|
|
// deep link (./clicks.ts) ready to open.
|
|
//
|
|
// The snapshot sits in the keychain group the app already shares with its
|
|
// extensions (src/lib/ios-push-shared.ts): an app group would need a new
|
|
// capability on every provisioning profile, the keychain group needs none.
|
|
// After each write the app asks WidgetKit to redraw (BulwarkWidgetKit).
|
|
|
|
import { NativeModules, Platform } from 'react-native';
|
|
import * as SecureStore from 'expo-secure-store';
|
|
import { sharedStoreOptions } from '../lib/ios-push-shared';
|
|
import { links } from './clicks';
|
|
import { makeFmt } from './format';
|
|
import { normalizeHex } from './theme';
|
|
import type { WidgetSnapshot } from './snapshot';
|
|
|
|
/** Read by plugins/ios-widget/BulwarkWidget.swift. */
|
|
export const IOS_WIDGET_KEY = 'bulwark_widget_inbox';
|
|
|
|
/** Messages a large widget can show; smaller ones take the first few. */
|
|
const MAX_MESSAGES = 6;
|
|
|
|
export interface IosWidgetMessage {
|
|
id: string;
|
|
from: string;
|
|
initials: string;
|
|
color: string;
|
|
subject: string;
|
|
preview: string;
|
|
receivedAt: number;
|
|
unread: boolean;
|
|
/** Opens the message in the app. */
|
|
url: string;
|
|
}
|
|
|
|
export interface IosWidgetSnapshot {
|
|
version: 1;
|
|
generatedAt: number;
|
|
signedIn: boolean;
|
|
theme: 'light' | 'dark' | 'system';
|
|
locale: string;
|
|
hour12: boolean;
|
|
/** The active account's label; null with a single account. */
|
|
account: string | null;
|
|
unread: number;
|
|
inboxUrl: string;
|
|
composeUrl: string;
|
|
messages: IosWidgetMessage[];
|
|
strings: { inbox: string; unread: string; empty: string; signedOut: string; noSubject: string };
|
|
}
|
|
|
|
export function iosWidgetSnapshot(s: WidgetSnapshot): IosWidgetSnapshot {
|
|
const f = makeFmt(s.locale, s.hour12);
|
|
const noSubject = f.t('widgets.mail.no_subject', '(no subject)');
|
|
const inbox = s.mail.folders.find((folder) => folder.role === 'inbox');
|
|
const account = s.accounts.length > 1 ? s.accounts.find((a) => a.id === s.activeAccountId)?.label ?? null : null;
|
|
return {
|
|
version: 1,
|
|
generatedAt: s.generatedAt,
|
|
signedIn: s.signedIn,
|
|
theme: s.theme,
|
|
locale: s.locale,
|
|
hour12: s.hour12,
|
|
account,
|
|
unread: inbox?.unread ?? 0,
|
|
inboxUrl: links.inbox(),
|
|
composeUrl: links.compose(),
|
|
messages: s.mail.inbox.slice(0, MAX_MESSAGES).map((m) => ({
|
|
id: m.id,
|
|
from: m.fromName || m.fromEmail,
|
|
initials: m.initials,
|
|
color: normalizeHex(m.color),
|
|
subject: m.subject || noSubject,
|
|
preview: m.preview,
|
|
receivedAt: m.receivedAt,
|
|
unread: m.unread,
|
|
url: links.message(m),
|
|
})),
|
|
strings: {
|
|
inbox: f.t('widgets.mail.inbox', 'Inbox'),
|
|
unread: f.t('widgets.mail.unread', 'unread'),
|
|
empty: f.t('widgets.mail.empty_inbox', 'Your inbox is empty'),
|
|
signedOut: f.t('widgets.state.signed_out', 'Sign in to Bulwark'),
|
|
noSubject,
|
|
},
|
|
};
|
|
}
|
|
|
|
type WidgetKitModule = { reloadAll?: () => Promise<void>; placedCount?: () => Promise<number> };
|
|
|
|
function widgetKit(): WidgetKitModule | undefined {
|
|
return NativeModules.BulwarkWidgetKit as WidgetKitModule | undefined;
|
|
}
|
|
|
|
/** Whether an inbox widget is on the home screen (or the module can't tell). */
|
|
export async function iosWidgetPlaced(): Promise<boolean> {
|
|
const count = widgetKit()?.placedCount;
|
|
if (!count) return false;
|
|
try {
|
|
return (await count()) > 0;
|
|
} catch {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/** Hand the widget what it shows and have WidgetKit redraw it. */
|
|
export async function writeIosWidget(s: WidgetSnapshot): Promise<void> {
|
|
if (Platform.OS !== 'ios') return;
|
|
const options = sharedStoreOptions();
|
|
if (!options) return;
|
|
await SecureStore.setItemAsync(IOS_WIDGET_KEY, JSON.stringify(iosWidgetSnapshot(s)), options);
|
|
await widgetKit()?.reloadAll?.().catch(() => undefined);
|
|
}
|