refactor(mobile): extract import and export functionality for data setting (#3015)

This commit is contained in:
Whitewater 2025-03-10 21:09:15 +08:00 committed by GitHub
parent f562f4d397
commit f02736ede8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 91 additions and 87 deletions

View File

@ -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 = () => {
<View className="mt-6">
<GroupedInsetListSectionHeader label="Data Sources" />
<GroupedInsetListCard>
<GroupedInsetListActionCell
onPress={async () => {
const result = await DocumentPicker.getDocumentAsync({
type: ["application/octet-stream", "text/x-opml"],
})
if (result.canceled) {
return
}
<GroupedInsetListActionCell onPress={importOpml} label="Import subscriptions from OPML" />
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"
/>
<GroupedInsetListActionCell
onPress={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")
}
}}
label="Export local database"
/>
<GroupedInsetListActionCell onPress={exportLocalDatabase} label="Export local database" />
</GroupedInsetListCard>
</View>

View File

@ -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")
}
}