mirror of
https://github.com/Boof2015/astra-mobile.git
synced 2026-08-12 05:10:52 +02:00
native android qr scan routing
This commit is contained in:
@@ -2,6 +2,7 @@ import {
|
||||
getNotificationClickRedirectPath,
|
||||
isNotificationClickPath,
|
||||
} from '@/audio/notificationIntent';
|
||||
import { getEQPresetShareRedirectPath } from '@/audio/eqShareIntent';
|
||||
|
||||
type RedirectSystemPathEvent = {
|
||||
path: string;
|
||||
@@ -9,7 +10,10 @@ type RedirectSystemPathEvent = {
|
||||
};
|
||||
|
||||
export async function redirectSystemPath({ path }: RedirectSystemPathEvent): Promise<string> {
|
||||
if (!isNotificationClickPath(path)) return path;
|
||||
if (isNotificationClickPath(path)) return getNotificationClickRedirectPath();
|
||||
|
||||
return getNotificationClickRedirectPath();
|
||||
const eqShareRedirect = getEQPresetShareRedirectPath(path);
|
||||
if (eqShareRedirect) return eqShareRedirect;
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
import { useMemo } from 'react';
|
||||
import { Pressable, StyleSheet, View } from 'react-native';
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import { useLocalSearchParams, useRouter } from 'expo-router';
|
||||
import { Screen } from '@/components/Screen';
|
||||
import { Text } from '@/components/Text';
|
||||
import { EQPresetPreviewSheet } from '@/components/eq/EQPresetPreviewSheet';
|
||||
import { decodeEQPresetQr, EQ_PRESET_QR_PREFIX } from '@/audio/eqShare';
|
||||
import { genEqId } from '@/audio/eqPresets';
|
||||
import { useEQStore } from '@/stores/eqStore';
|
||||
import { radius, spacing } from '@/theme';
|
||||
import { createThemedStyles, useColors } from '@/theme/themed';
|
||||
import type { EQPreset } from '@/types/audio';
|
||||
|
||||
export default function EQPresetImportScreen() {
|
||||
const styles = useStyles();
|
||||
const colors = useColors();
|
||||
const router = useRouter();
|
||||
const importPreset = useEQStore((state) => state.importPreset);
|
||||
const { data } = useLocalSearchParams<{ data?: string }>();
|
||||
|
||||
const preset = useMemo<EQPreset | null>(() => {
|
||||
if (!data) return null;
|
||||
try {
|
||||
return decodeEQPresetQr(`${EQ_PRESET_QR_PREFIX}${data}`, genEqId);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
const goToEq = () => router.replace('/eq' as never);
|
||||
|
||||
if (!preset) {
|
||||
return (
|
||||
<Screen>
|
||||
<View style={styles.header}>
|
||||
<Pressable style={styles.back} onPress={goToEq} hitSlop={8}>
|
||||
<Ionicons name="chevron-back" size={22} color={colors.textSecondary} />
|
||||
<Text variant="body" color={colors.textSecondary}>
|
||||
Equalizer
|
||||
</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
<View style={styles.errorCard}>
|
||||
<Ionicons name="alert-circle-outline" size={28} color={colors.warning} />
|
||||
<Text variant="body">This link does not contain a valid Astra EQ preset.</Text>
|
||||
<Pressable style={styles.primaryButton} onPress={goToEq}>
|
||||
<Text variant="body" color={colors.accentTextStrong}>
|
||||
Go to Equalizer
|
||||
</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
</Screen>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Screen>
|
||||
<EQPresetPreviewSheet
|
||||
preset={preset}
|
||||
title="Shared EQ preset"
|
||||
onConfirm={() => importPreset(preset)}
|
||||
onClose={goToEq}
|
||||
/>
|
||||
</Screen>
|
||||
);
|
||||
}
|
||||
|
||||
const useStyles = createThemedStyles((colors) => ({
|
||||
header: {
|
||||
marginTop: spacing.md,
|
||||
marginBottom: spacing.lg,
|
||||
},
|
||||
back: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 2,
|
||||
},
|
||||
errorCard: {
|
||||
borderRadius: radius.md,
|
||||
borderWidth: StyleSheet.hairlineWidth,
|
||||
borderColor: colors.glassBorder,
|
||||
backgroundColor: colors.glassBg,
|
||||
padding: spacing.lg,
|
||||
gap: spacing.md,
|
||||
},
|
||||
primaryButton: {
|
||||
minHeight: 44,
|
||||
borderRadius: radius.sm,
|
||||
backgroundColor: colors.accent,
|
||||
paddingHorizontal: spacing.lg,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
}));
|
||||
@@ -0,0 +1,29 @@
|
||||
import { EQ_PRESET_QR_PREFIX } from './eqShare';
|
||||
|
||||
// The QR marker without the `astra:` scheme, e.g. 'eq-preset:v1:'. The scheme is
|
||||
// stripped/normalized inconsistently by the OS before it reaches redirectSystemPath,
|
||||
// so we match on the scheme-less marker instead of the full prefix.
|
||||
const EQ_PRESET_MARKER = EQ_PRESET_QR_PREFIX.replace(/^astra:/, '');
|
||||
|
||||
/**
|
||||
* Returns a router path for an EQ-preset-share deep link (`astra:eq-preset:v1:<payload>`
|
||||
* scanned by the native camera), or null if `path` is not one.
|
||||
*
|
||||
* The exact shape handed to redirectSystemPath for an opaque `astra:...` URI varies across
|
||||
* OS/expo versions (`astra:`, `astra://`, bare, slash-prefixed, and colons possibly
|
||||
* percent-encoded), so we decode once, locate the marker, and pull the trailing base64url
|
||||
* run. That alphabet (`[A-Za-z0-9_-]`) is query-safe, so no re-encoding is needed.
|
||||
*/
|
||||
export function getEQPresetShareRedirectPath(path: string): string | null {
|
||||
let candidate = path.trim();
|
||||
try {
|
||||
candidate = decodeURIComponent(candidate);
|
||||
} catch {
|
||||
// Malformed percent-encoding — fall back to the raw string.
|
||||
}
|
||||
const idx = candidate.indexOf(EQ_PRESET_MARKER);
|
||||
if (idx === -1) return null;
|
||||
const payload = candidate.slice(idx + EQ_PRESET_MARKER.length).match(/^[A-Za-z0-9_-]+/)?.[0];
|
||||
if (!payload) return null;
|
||||
return `/eq/import?data=${payload}`;
|
||||
}
|
||||
Reference in New Issue
Block a user