项目文件夹

文件
wehub-resource-sync 2114b14ee0
Sync main into demo / sync (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:35:26 +08:00

115 行
4.2 KiB
TypeScript

import React, { useMemo, useSyncExternalStore } from 'react';
import { IcNavForward } from '../res/icons';
import { SettingsHeader } from './SettingsHeader';
import { PreferenceCategory } from './PreferenceCategory';
import NotificationService from '../../../os/NotificationService';
import { AppIcon } from '../../../os/components/AppIcon';
import { APP_REGISTRY, getLocalizedAppName } from '../../../os/data/appRegistry';
import { useOsStateStore } from '../../../os/OsStateStore';
import type { AppId } from '../../../os/types';
import type { AppManifest } from '../../../os/types/manifest';
import { strings } from '../res/strings';
import { stringsEn } from '../res/strings.en';
import { useAppStrings } from '@/os/useAppStrings';
import { useSettingsGestures } from '../hooks/useSettingsGestures';
function prefKey(appId: string, field: string): string {
return `notif.app.${appId}.${field}`;
}
function getBoolPref(prefs: Record<string, any>, key: string, fallback: boolean): boolean {
const v = prefs[key];
return typeof v === 'boolean' ? v : fallback;
}
const AppRow: React.FC<{
manifest: AppManifest;
value?: string;
showDivider: boolean;
rowProps?: React.HTMLAttributes<HTMLDivElement>;
}> = ({ manifest, value, showDivider, rowProps }) => {
return (
<div>
<div
className="flex items-center px-4 py-3.5 active:bg-gray-50 min-h-(--app-preference-item-min-height)"
{...rowProps}
>
<div className="mr-3 flex-shrink-0">
<AppIcon manifest={manifest} size={34} radius={10} showShadow />
</div>
<div className="flex-1 min-w-0">
<div className="text-[15px] text-app-text leading-tight">{getLocalizedAppName(manifest.id)}</div>
{manifest.packageName ? (
<div className="text-[12px] text-gray-400 mt-0.5 leading-tight line-clamp-1">
{manifest.packageName}
</div>
) : null}
</div>
<div className="flex items-center ml-2 flex-shrink-0">
{value ? <span className="text-[13px] text-gray-400 mr-1">{value}</span> : null}
<IcNavForward size={16} className="text-gray-300" />
</div>
</div>
{showDivider ? <div className="h-px bg-gray-100 ml-[60px] mr-4" /> : null}
</div>
);
};
export const NotificationManagingPage: React.FC = () => {
const { bindTap } = useSettingsGestures();
const s = useAppStrings(strings, stringsEn);
const prefs = useOsStateStore((state) => state.preferences);
const snapshot = useSyncExternalStore(
(onChange) => NotificationService.subscribe(() => onChange()),
() => NotificationService.getState(),
);
const unreadByApp = useMemo(() => {
const map = new Map<AppId, number>();
for (const it of snapshot.items) {
if (!it.appId) continue;
if (it.read) continue;
map.set(it.appId, (map.get(it.appId) || 0) + 1);
}
return map;
}, [snapshot.items]);
const apps = useMemo(() => {
const arr = [...APP_REGISTRY];
arr.sort((a, b) => {
// System apps first, then by name.
if (a.type !== b.type) return a.type === 'system' ? -1 : 1;
return getLocalizedAppName(a.id).localeCompare(getLocalizedAppName(b.id), 'zh-Hans-CN');
});
return arr;
}, []);
return (
<div className="h-full bg-app-bg flex flex-col">
<SettingsHeader title={s.notification_management} />
<div className="flex-1 overflow-y-auto no-scrollbar pb-8">
<PreferenceCategory title={s.app_notifications}>
{apps.map((app, idx) => {
const enabled = getBoolPref(prefs, prefKey(app.id, 'enabled'), true);
const unread = unreadByApp.get(app.id) || 0;
const value = enabled ? (unread > 0 ? `${unread} ${s.unread_count_suffix}` : s.on_2) : s.off_2;
return (
<AppRow
key={app.id}
manifest={app}
value={value}
showDivider={idx < apps.length - 1}
rowProps={bindTap<HTMLDivElement>('page.open', {
params: { pageId: `notification_app__${encodeURIComponent(app.id)}` },
})}
/>
);
})}
</PreferenceCategory>
</div>
</div>
);
};
export default NotificationManagingPage;