From 9e55bb71ef01887520fb6635f6bc092af94e9b46 Mon Sep 17 00:00:00 2001 From: DS Boyce <260543580+ds-boyce@users.noreply.github.com> Date: Wed, 13 May 2026 12:30:00 -0700 Subject: [PATCH] Add export data dialog to chat settings (#10478) --- src/screens/Messages/Settings.tsx | 99 ++++++------------- .../Settings/components/ExportCarDialog.tsx | 77 +++++++++------ 2 files changed, 77 insertions(+), 99 deletions(-) diff --git a/src/screens/Messages/Settings.tsx b/src/screens/Messages/Settings.tsx index 7c96c2f995..c070ff3c91 100644 --- a/src/screens/Messages/Settings.tsx +++ b/src/screens/Messages/Settings.tsx @@ -1,24 +1,23 @@ -import {useCallback, useState} from 'react' +import {useCallback} from 'react' import {View} from 'react-native' import {Trans, useLingui} from '@lingui/react/macro' import {type NativeStackScreenProps} from '@react-navigation/native-stack' -import {DM_SERVICE_HEADERS} from '#/lib/constants' -import {saveBytesToDisk} from '#/lib/media/manip' import {type CommonNavigatorParams} from '#/lib/routes/types' -import {logger} from '#/logger' import {useUpdateActorDeclaration} from '#/state/queries/messages/actor-declaration' import {useProfileQuery} from '#/state/queries/profile' -import {useAgent, useSession} from '#/state/session' +import {useSession} from '#/state/session' +import {ExportCarDialog} from '#/screens/Settings/components/ExportCarDialog' import {atoms as a, useTheme} from '#/alf' import {AgeRestrictedScreen} from '#/components/ageAssurance/AgeRestrictedScreen' import {useAgeAssuranceCopy} from '#/components/ageAssurance/useAgeAssuranceCopy' -import {Button, ButtonIcon, ButtonText} from '#/components/Button' +import * as Dialog from '#/components/Dialog' import {Divider} from '#/components/Divider' import * as Toggle from '#/components/forms/Toggle' -import {Download_Stroke2_Corner0_Rounded as DownloadIcon} from '#/components/icons/Download' +import {Bell_Stroke2_Corner0_Rounded as BellIcon} from '#/components/icons/Bell' +import {Car_Stroke2_Corner2_Rounded as CarIcon} from '#/components/icons/Car' +import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRightIcon} from '#/components/icons/Chevron' import * as Layout from '#/components/Layout' -import {Loader} from '#/components/Loader' import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' import {useAnalytics} from '#/analytics' @@ -47,16 +46,14 @@ export function MessagesSettingsScreenInner({}: Props) { const {t: l} = useLingui() const ax = useAnalytics() const {currentAccount} = useSession() - const agent = useAgent() const {data: profile} = useProfileQuery({ did: currentAccount!.did, }) const {preferences, setPref} = useBackgroundNotificationPreferences() + const exportCarControl = Dialog.useDialogControl() const isGroupChatEnabled = ax.features.enabled(ax.features.GroupChatsEnable) - const [loading, setLoading] = useState(false) - const allowMessagesFromOptions: {name: AllowIncoming; label: string}[] = [ { name: 'all', @@ -90,34 +87,6 @@ export function MessagesSettingsScreenInner({}: Props) { }, ] - const onDownloadChatData = async () => { - if (!agent.session) return - try { - setLoading(true) - const res = await agent.sessionManager.fetchHandler( - '/xrpc/chat.bsky.actor.exportAccountData', - {headers: DM_SERVICE_HEADERS}, - ) - if (!res.ok) { - throw new Error(`HTTP ${res.status}`) - } - const data = new Uint8Array(await res.arrayBuffer()) - const saveRes = await saveBytesToDisk( - 'chat.jsonl', - data, - res.headers.get('content-type') || 'application/jsonl', - ) - if (saveRes) { - Toast.show(l`File saved successfully!`) - } - } catch (e) { - logger.error('Error occurred while downloading chat data', {message: e}) - Toast.show(l`Error occurred while saving file`, {type: 'error'}) - } finally { - setLoading(false) - } - } - const {mutate: updateDeclaration} = useUpdateActorDeclaration({ onError: () => { Toast.show(l`Failed to update settings`, { @@ -264,7 +233,14 @@ export function MessagesSettingsScreenInner({}: Props) { value={preferences.playSoundChat} style={[a.flex_row, a.align_center, a.justify_between]} onChange={onSelectSoundSetting}> - + + Notification sounds @@ -274,37 +250,26 @@ export function MessagesSettingsScreenInner({}: Props) { )} - - Download chat data - - - - This file only includes chat messages that you have sent and - does not include chat messages that you have received. - - - + { + exportCarControl.open() + }}> + + + Export chat data + + + + ) } diff --git a/src/screens/Settings/components/ExportCarDialog.tsx b/src/screens/Settings/components/ExportCarDialog.tsx index df064e3a0f..f0b2a8f54d 100644 --- a/src/screens/Settings/components/ExportCarDialog.tsx +++ b/src/screens/Settings/components/ExportCarDialog.tsx @@ -1,8 +1,6 @@ import {useCallback, useState} from 'react' import {View} from 'react-native' -import {msg} from '@lingui/core/macro' -import {useLingui} from '@lingui/react' -import {Trans} from '@lingui/react/macro' +import {Trans, useLingui} from '@lingui/react/macro' import {DM_SERVICE_HEADERS} from '#/lib/constants' import {saveBytesToDisk} from '#/lib/media/manip' @@ -22,14 +20,14 @@ export function ExportCarDialog({ }: { control: Dialog.DialogControlProps }) { - const {_} = useLingui() + const {t: l} = useLingui() const t = useTheme() const agent = useAgent() const [loading, setLoading] = useState<'repo' | 'chat' | false>(false) const download = useCallback(async () => { if (!agent.session) { - return // shouldnt ever happen + return // shouldn't ever happen } try { setLoading('repo') @@ -42,16 +40,15 @@ export function ExportCarDialog({ ) if (saveRes) { - Toast.show(_(msg`File saved successfully!`)) + Toast.show(l`File saved successfully!`) } } catch (e) { logger.error('Error occurred while downloading CAR file', {message: e}) - Toast.show(_(msg`Error occurred while saving file`), {type: 'error'}) + Toast.show(l`Error occurred while saving file`, {type: 'error'}) } finally { setLoading(false) - control.close() } - }, [_, control, agent]) + }, [l, agent]) const downloadChatData = useCallback(async () => { if (!agent.session) { @@ -76,16 +73,15 @@ export function ExportCarDialog({ ) if (saveRes) { - Toast.show(_(msg`File saved successfully!`)) + Toast.show(l`File saved successfully!`) } } catch (e) { logger.error('Error occurred while downloading chat data', {message: e}) - Toast.show(_(msg`Error occurred while saving file`), {type: 'error'}) + Toast.show(l`Error occurred while saving file`, {type: 'error'}) } finally { setLoading(false) - control.close() } - }, [_, control, agent]) + }, [l, agent]) return ( @@ -94,13 +90,20 @@ export function ExportCarDialog({ accessibilityDescribedBy="dialog-description" accessibilityLabelledBy="dialog-title" style={web({maxWidth: 500})}> - - - Export My Data + + + Export my profile data + style={[ + a.mb_lg, + a.text_sm, + a.leading_snug, + t.atoms.text_contrast_high, + ]}> Your account repository, containing all public data records, can be downloaded as a "CAR" file. This file does not include media @@ -112,17 +115,27 @@ export function ExportCarDialog({ - + + Export my chat data + + You can also download your chat data as a "JSONL" file. This file only includes chat messages that you have sent and does not @@ -131,30 +144,30 @@ export function ExportCarDialog({ This feature is in beta. You can read more about repository exports in{' '} this blogpost