From f02736ede86fa916dbf887f115c133ad83b2a106 Mon Sep 17 00:00:00 2001 From: Whitewater Date: Mon, 10 Mar 2025 21:09:15 +0800 Subject: [PATCH] refactor(mobile): extract import and export functionality for data setting (#3015) --- .../src/modules/settings/routes/Data.tsx | 90 +------------------ apps/mobile/src/modules/settings/utils.ts | 88 ++++++++++++++++++ 2 files changed, 91 insertions(+), 87 deletions(-) create mode 100644 apps/mobile/src/modules/settings/utils.ts diff --git a/apps/mobile/src/modules/settings/routes/Data.tsx b/apps/mobile/src/modules/settings/routes/Data.tsx index bc23f8f5c..2f4333e50 100644 --- a/apps/mobile/src/modules/settings/routes/Data.tsx +++ b/apps/mobile/src/modules/settings/routes/Data.tsx @@ -1,6 +1,4 @@ -import * as DocumentPicker from "expo-document-picker" import * as FileSystem from "expo-file-system" -import * as Sharing from "expo-sharing" import { Alert, View } from "react-native" import { setDataSetting, useDataSettingKey } from "@/src/atoms/settings/data" @@ -16,20 +14,9 @@ import { } from "@/src/components/ui/grouped/GroupedList" import { Switch } from "@/src/components/ui/switch/Switch" import { getDbPath } from "@/src/database" -import { apiFetch, getBizFetchErrorMessage } from "@/src/lib/api-fetch" import { toast } from "@/src/lib/toast" -type FeedResponseList = { - id: string - url: string - title: string | null -}[] - -type FileUpload = { - uri: string - name: string - type: string -} +import { exportLocalDatabase, importOpml } from "../utils" export const DataScreen = () => { const sendAnonymousData = useDataSettingKey("sendAnonymousData") @@ -59,80 +46,9 @@ export const DataScreen = () => { - { - const result = await DocumentPicker.getDocumentAsync({ - type: ["application/octet-stream", "text/x-opml"], - }) - if (result.canceled) { - return - } + - try { - const formData = new FormData() - const file = result.assets[0] - - if (!file) { - toast.error("No file selected") - return - } - - formData.append("file", { - uri: file.uri, - type: file.mimeType || "application/octet-stream", - name: file.name, - } as FileUpload as any) - - const { data } = await apiFetch<{ - data: { - successfulItems: FeedResponseList - conflictItems: FeedResponseList - parsedErrorItems: FeedResponseList - } - }>("/subscriptions/import", { - method: "POST", - body: formData, - headers: { - "Content-Type": "multipart/form-data", - }, - }) - - const { successfulItems, conflictItems, parsedErrorItems } = data - toast.info( - `Import successful, ${successfulItems.length} feeds were imported, ${conflictItems.length} feeds were already subscribed, and ${parsedErrorItems.length} feeds failed to import.`, - ) - } catch (error) { - const bizError = getBizFetchErrorMessage(error as Error) - toast.error(`Import failed${bizError ? `: ${bizError}` : ""}`) - console.error(error) - } - }} - label="Import subscriptions from OPML" - /> - - { - const dbPath = getDbPath() - try { - const destinationUri = `${FileSystem.documentDirectory}follow.db` - await FileSystem.copyAsync({ - from: dbPath, - to: destinationUri, - }) - - await FileSystem.getInfoAsync(destinationUri) - await Sharing.shareAsync(destinationUri, { - UTI: "public.database", - mimeType: "application/x-sqlite3", - dialogTitle: "Export Database", - }) - } catch (error) { - console.error(error) - toast.error("Failed to export database") - } - }} - label="Export local database" - /> + diff --git a/apps/mobile/src/modules/settings/utils.ts b/apps/mobile/src/modules/settings/utils.ts new file mode 100644 index 000000000..7b4942a1a --- /dev/null +++ b/apps/mobile/src/modules/settings/utils.ts @@ -0,0 +1,88 @@ +import * as DocumentPicker from "expo-document-picker" +import * as FileSystem from "expo-file-system" +import * as Sharing from "expo-sharing" + +import { getDbPath } from "@/src/database" +import { apiFetch, getBizFetchErrorMessage } from "@/src/lib/api-fetch" +import { toast } from "@/src/lib/toast" + +type FeedResponseList = { + id: string + url: string + title: string | null +}[] + +type FileUpload = { + uri: string + name: string + type: string +} + +export const importOpml = async () => { + const result = await DocumentPicker.getDocumentAsync({ + type: ["application/octet-stream", "text/x-opml"], + }) + if (result.canceled) { + return + } + + try { + const formData = new FormData() + const file = result.assets[0] + + if (!file) { + toast.error("No file selected") + return + } + + formData.append("file", { + uri: file.uri, + type: file.mimeType || "application/octet-stream", + name: file.name, + } as FileUpload as any) + + const { data } = await apiFetch<{ + data: { + successfulItems: FeedResponseList + conflictItems: FeedResponseList + parsedErrorItems: FeedResponseList + } + }>("/subscriptions/import", { + method: "POST", + body: formData, + headers: { + "Content-Type": "multipart/form-data", + }, + }) + + const { successfulItems, conflictItems, parsedErrorItems } = data + toast.info( + `Import successful, ${successfulItems.length} feeds were imported, ${conflictItems.length} feeds were already subscribed, and ${parsedErrorItems.length} feeds failed to import.`, + ) + } catch (error) { + const bizError = getBizFetchErrorMessage(error as Error) + toast.error(`Import failed${bizError ? `: ${bizError}` : ""}`) + console.error(error) + } +} + +export const exportLocalDatabase = async () => { + const dbPath = getDbPath() + try { + const destinationUri = `${FileSystem.documentDirectory}follow.db` + await FileSystem.copyAsync({ + from: dbPath, + to: destinationUri, + }) + + await FileSystem.getInfoAsync(destinationUri) + await Sharing.shareAsync(destinationUri, { + UTI: "public.database", + mimeType: "application/x-sqlite3", + dialogTitle: "Export Database", + }) + } catch (error) { + console.error(error) + toast.error("Failed to export database") + } +}