feat: Implement Android share functionality and background sync
- Add ShareRoomPicker component for selecting rooms to share Android intents. - Introduce AndroidShare utility for handling shared files and payloads from Android. - Implement background synchronization with UnifiedPush for Android notifications. - Enhance notification handling in Tauri and Capacitor environments. - Update TypeScript configuration for better module resolution. - Create Capacitor configuration file for Android integration. - Add SVG icon for notifications. - Refactor media URL authentication checks in matrix utility.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { Box, Text, Switch, Button, color, Spinner } from 'folds';
|
||||
import { IPusherRequest } from 'matrix-js-sdk';
|
||||
import { SequenceCard } from '../../../components/sequence-card';
|
||||
@@ -10,6 +10,13 @@ import { getNotificationState, usePermissionState } from '../../../hooks/usePerm
|
||||
import { useEmailNotifications } from '../../../hooks/useEmailNotifications';
|
||||
import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
|
||||
import { useMatrixClient } from '../../../hooks/useMatrixClient';
|
||||
import { isCapacitorNative, requestSystemNotificationPermission } from '../../../utils/tauri';
|
||||
import {
|
||||
isBackgroundSyncSupported,
|
||||
getBackgroundSyncStatus,
|
||||
requestResetPushRegistration,
|
||||
triggerBackgroundSyncPing,
|
||||
} from '../../../utils/backgroundSync';
|
||||
|
||||
function EmailNotification() {
|
||||
const mx = useMatrixClient();
|
||||
@@ -86,14 +93,15 @@ function EmailNotification() {
|
||||
|
||||
export function SystemNotification() {
|
||||
const notifPermission = usePermissionState('notifications', getNotificationState());
|
||||
const capacitorNative = isCapacitorNative();
|
||||
const [showNotifications, setShowNotifications] = useSetting(settingsAtom, 'showNotifications');
|
||||
const [isNotificationSounds, setIsNotificationSounds] = useSetting(
|
||||
settingsAtom,
|
||||
'isNotificationSounds'
|
||||
);
|
||||
|
||||
const requestNotificationPermission = () => {
|
||||
window.Notification.requestPermission();
|
||||
const requestNotificationPermission = async () => {
|
||||
await requestSystemNotificationPermission();
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -125,7 +133,7 @@ export function SystemNotification() {
|
||||
</Button>
|
||||
) : (
|
||||
<Switch
|
||||
disabled={notifPermission !== 'granted'}
|
||||
disabled={!capacitorNative && notifPermission !== 'granted'}
|
||||
value={showNotifications}
|
||||
onChange={setShowNotifications}
|
||||
/>
|
||||
@@ -145,6 +153,7 @@ export function SystemNotification() {
|
||||
after={<Switch value={isNotificationSounds} onChange={setIsNotificationSounds} />}
|
||||
/>
|
||||
</SequenceCard>
|
||||
{isBackgroundSyncSupported() && <AndroidPushNotifications />}
|
||||
<SequenceCard
|
||||
className={SequenceCardStyle}
|
||||
variant="SurfaceVariant"
|
||||
@@ -156,3 +165,126 @@ export function SystemNotification() {
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
type PushStatus = {
|
||||
registered: boolean;
|
||||
distributor: string;
|
||||
endpoint: string;
|
||||
};
|
||||
|
||||
/** Android-only section showing UnifiedPush registration status and controls. */
|
||||
function AndroidPushNotifications() {
|
||||
const [status, setStatus] = useState<PushStatus | undefined>(undefined);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const refresh = useCallback(async () => {
|
||||
setLoading(true);
|
||||
const s = await getBackgroundSyncStatus();
|
||||
setStatus(
|
||||
s
|
||||
? {
|
||||
registered: s.registered,
|
||||
distributor: s.distributor || '',
|
||||
endpoint: s.endpoint || '',
|
||||
}
|
||||
: undefined
|
||||
);
|
||||
setLoading(false);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
void refresh();
|
||||
}, [refresh]);
|
||||
|
||||
const [resetState, reset] = useAsyncCallback(
|
||||
useCallback(async () => {
|
||||
await requestResetPushRegistration();
|
||||
await refresh();
|
||||
}, [refresh])
|
||||
);
|
||||
|
||||
const [pingState, ping] = useAsyncCallback(
|
||||
useCallback(async () => {
|
||||
await triggerBackgroundSyncPing('manual-test');
|
||||
}, [])
|
||||
);
|
||||
|
||||
const isBusy =
|
||||
loading ||
|
||||
resetState.status === AsyncStatus.Loading ||
|
||||
pingState.status === AsyncStatus.Loading;
|
||||
|
||||
return (
|
||||
<Box direction="Column" gap="100">
|
||||
<Text size="L400">Android Push (UnifiedPush)</Text>
|
||||
<SequenceCard
|
||||
className={SequenceCardStyle}
|
||||
variant="SurfaceVariant"
|
||||
direction="Column"
|
||||
gap="400"
|
||||
>
|
||||
<SettingTile
|
||||
title="Background Notifications"
|
||||
description={
|
||||
loading ? (
|
||||
'Loading status…'
|
||||
) : status === undefined ? (
|
||||
<Text as="span" style={{ color: color.Critical.Main }} size="T200">
|
||||
Failed to read push status.
|
||||
</Text>
|
||||
) : status.registered ? (
|
||||
<>
|
||||
<Text as="span" size="T200">
|
||||
{`Distributor: ${status.distributor || 'unknown'}`}
|
||||
</Text>
|
||||
</>
|
||||
) : (
|
||||
<Text as="span" style={{ color: color.Warning?.Main ?? color.Critical.Main }} size="T200">
|
||||
Not registered. Tap "Reset" to choose a distributor app.
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
after={loading ? <Spinner variant="Secondary" /> : undefined}
|
||||
/>
|
||||
<SettingTile
|
||||
title="Change Distributor"
|
||||
description="Re-open the UnifiedPush distributor selection dialog."
|
||||
after={
|
||||
<Button
|
||||
size="300"
|
||||
radii="300"
|
||||
variant="Secondary"
|
||||
disabled={isBusy}
|
||||
onClick={() => void reset()}
|
||||
>
|
||||
{resetState.status === AsyncStatus.Loading ? (
|
||||
<Spinner variant="Secondary" size="200" />
|
||||
) : (
|
||||
<Text size="B300">Reset</Text>
|
||||
)}
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
<SettingTile
|
||||
title="Test Notification"
|
||||
description="Trigger a test push ping to verify the pipeline works."
|
||||
after={
|
||||
<Button
|
||||
size="300"
|
||||
radii="300"
|
||||
variant="Secondary"
|
||||
disabled={isBusy}
|
||||
onClick={() => void ping()}
|
||||
>
|
||||
{pingState.status === AsyncStatus.Loading ? (
|
||||
<Spinner variant="Secondary" size="200" />
|
||||
) : (
|
||||
<Text size="B300">Send Test</Text>
|
||||
)}
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</SequenceCard>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user