feat: add mobile shell and unified search
Some checks failed
ci / 정본·보안·린트·타입·테스트 (push) Failing after 50s
ci / 워크스페이스 빌드 검증 (push) Has been skipped
ci / Supabase Edge Functions + Cloudflare Worker (push) Successful in 20s
ci / .NET API 서버 테스트 (push) Successful in 12s
deploy-site / deploy (push) Successful in 39s
ci / 모바일 린트·타입·Jest (push) Failing after 3h10m31s
Some checks failed
ci / 정본·보안·린트·타입·테스트 (push) Failing after 50s
ci / 워크스페이스 빌드 검증 (push) Has been skipped
ci / Supabase Edge Functions + Cloudflare Worker (push) Successful in 20s
ci / .NET API 서버 테스트 (push) Successful in 12s
deploy-site / deploy (push) Successful in 39s
ci / 모바일 린트·타입·Jest (push) Failing after 3h10m31s
This commit is contained in:
parent
f505a03d34
commit
5ca3709b32
98 changed files with 5168 additions and 2093 deletions
|
|
@ -7,19 +7,18 @@ import {
|
|||
StyleSheet,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import {
|
||||
AppStatusBar,
|
||||
Header,
|
||||
Led,
|
||||
NativeThemeProvider,
|
||||
PhosphorText,
|
||||
PhysicalButton,
|
||||
WaveBars,
|
||||
d3roNativeFonts,
|
||||
d3roNativeNumerals,
|
||||
} from '@d3ro/ui-native';
|
||||
import { useI18n } from '@d3ro/i18n';
|
||||
import { useAuth } from '../lib/auth-context';
|
||||
import { ScreenHeader } from '../components/ScreenHeader';
|
||||
import {
|
||||
audioRecorder,
|
||||
RecorderBusyError,
|
||||
|
|
@ -122,17 +121,21 @@ interface RecordScreenMeetingParams {
|
|||
meetingLanguage?: string;
|
||||
}
|
||||
|
||||
interface RecordScreenParams extends RecordScreenMeetingParams {
|
||||
incomingMediaId?: string;
|
||||
importRequest?: number;
|
||||
}
|
||||
|
||||
interface RecordScreenProps {
|
||||
route: { params?: RecordScreenMeetingParams & { incomingMediaId?: string } }
|
||||
route: { params?: RecordScreenParams }
|
||||
navigation: {
|
||||
getParent: () => { navigate: (name: 'MeetingDetail', params: { meetingId: string }) => void } | undefined
|
||||
/** Clears the meeting binding once its capture is committed. */
|
||||
setParams?: (params: RecordScreenMeetingParams) => void
|
||||
/** Clears one-shot route bindings once their action is committed. */
|
||||
setParams?: (params: Partial<RecordScreenParams>) => void
|
||||
}
|
||||
}
|
||||
|
||||
export default function RecordScreen({ route, navigation }: RecordScreenProps): React.ReactElement {
|
||||
const insets = useSafeAreaInsets();
|
||||
const { t, locale } = useI18n();
|
||||
const { user } = useAuth();
|
||||
const { palette } = useMobilePreferences();
|
||||
|
|
@ -166,7 +169,10 @@ export default function RecordScreen({ route, navigation }: RecordScreenProps):
|
|||
const routeMeetingId = route.params?.meetingId ?? null;
|
||||
const routeMeetingTitle = route.params?.meetingTitle ?? null;
|
||||
const routeIncomingMediaId = route.params?.incomingMediaId ?? null;
|
||||
const routeImportRequest = route.params?.importRequest;
|
||||
const handledIncomingMediaIds = useRef(new Set<string>());
|
||||
const handledImportRequests = useRef(new Set<number>());
|
||||
const importAudioFileRef = useRef<() => Promise<void>>(async () => undefined);
|
||||
// Latest route binding, read by callbacks registered on an earlier render.
|
||||
const routeMeetingIdRef = useRef<string | null>(routeMeetingId);
|
||||
routeMeetingIdRef.current = routeMeetingId;
|
||||
|
|
@ -643,6 +649,20 @@ export default function RecordScreen({ route, navigation }: RecordScreenProps):
|
|||
}
|
||||
}
|
||||
|
||||
importAudioFileRef.current = importAudioFile;
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
routeImportRequest === undefined ||
|
||||
handledImportRequests.current.has(routeImportRequest) ||
|
||||
actionPending ||
|
||||
!['idle', 'done', 'error'].includes(state)
|
||||
) return;
|
||||
handledImportRequests.current.add(routeImportRequest);
|
||||
navigation.setParams?.({ importRequest: undefined });
|
||||
void importAudioFileRef.current();
|
||||
}, [actionPending, navigation, routeImportRequest, state]);
|
||||
|
||||
async function importIncomingMedia(): Promise<void> {
|
||||
if (!beginAction()) return;
|
||||
const previousState = state;
|
||||
|
|
@ -792,15 +812,16 @@ export default function RecordScreen({ route, navigation }: RecordScreenProps):
|
|||
return (
|
||||
<NativeThemeProvider palette={palette}>
|
||||
<ScrollView style={styles.container} contentContainerStyle={styles.content} testID="record-screen">
|
||||
<Header
|
||||
<ScreenHeader
|
||||
title={captureActive ? t('mobile.rec.session') : t('mobile.rec.title')}
|
||||
paddingTop={insets.top}
|
||||
rightContent={captureActive ? (
|
||||
<View style={styles.headerRight}>
|
||||
<PhosphorText variant="label" color="amber">{formatTime(durationMs)}</PhosphorText>
|
||||
<Led color={state === 'recording' ? 'green' : 'amber'} size={6} />
|
||||
<PhosphorText variant="label" color="amber" style={styles.headerTime}>
|
||||
{formatTime(durationMs)}
|
||||
</PhosphorText>
|
||||
</View>
|
||||
) : undefined}
|
||||
testID="record-header"
|
||||
/>
|
||||
<WaveBars active={state === 'recording'} />
|
||||
|
||||
|
|
@ -983,10 +1004,6 @@ export default function RecordScreen({ route, navigation }: RecordScreenProps):
|
|||
/>
|
||||
)}
|
||||
</View>
|
||||
<AppStatusBar
|
||||
model={t('mobile.status.cloudModel')}
|
||||
label={t('mobile.status.secureDataLink')}
|
||||
/>
|
||||
</ScrollView>
|
||||
</NativeThemeProvider>
|
||||
);
|
||||
|
|
@ -999,12 +1016,13 @@ function createStyles(palette: Palette) {
|
|||
container: { flex: 1, backgroundColor: palette.bg.app },
|
||||
content: { paddingBottom: 100 },
|
||||
headerRight: { flexDirection: 'row', alignItems: 'center', gap: 8 },
|
||||
headerTime: { ...d3roNativeNumerals },
|
||||
meetingContext: { paddingHorizontal: 20, paddingTop: 12, gap: 4 },
|
||||
statusRow: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: 8, paddingHorizontal: 20, paddingTop: 12 },
|
||||
statusText: { letterSpacing: 2 },
|
||||
transcriptArea: { minHeight: 190, justifyContent: 'center', alignItems: 'center', gap: 14, paddingHorizontal: 20, paddingVertical: 24 },
|
||||
initLog: { fontFamily: d3roNativeFonts.mono, textAlign: 'center' },
|
||||
fileName: { fontFamily: d3roNativeFonts.mono, textAlign: 'center' },
|
||||
initLog: { fontFamily: d3roNativeFonts.sans, textAlign: 'center' },
|
||||
fileName: { fontFamily: d3roNativeFonts.sans, ...d3roNativeNumerals, textAlign: 'center' },
|
||||
transcriptBox: { backgroundColor: palette.bg.inset, padding: 14, borderRadius: 8, width: '100%', gap: 14 },
|
||||
resultMeta: { flexDirection: 'row', alignItems: 'center', flexWrap: 'wrap', gap: 12 },
|
||||
errorBox: { borderWidth: 1, borderColor: palette.tag.red, backgroundColor: palette.bg.card, padding: 10, borderRadius: 6, width: '100%' },
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue