diff --git a/src/components/AssignmentBoard.tsx b/src/components/AssignmentBoard.tsx index c2ce526b..1b1723ce 100644 --- a/src/components/AssignmentBoard.tsx +++ b/src/components/AssignmentBoard.tsx @@ -66,8 +66,8 @@ const AssignmentBoard: FunctionComponent< ext }); } else { - showToast("文件下载中……", 3000); - const success = await shareFile(url, ext); + showToast("文件下载中……", 1000); + const success = await shareFile(url, filename, ext); if (!success) { showToast("文件下载失败", 3000); } diff --git a/src/components/NoticeBoard.tsx b/src/components/NoticeBoard.tsx index de8f3b78..f29254c4 100644 --- a/src/components/NoticeBoard.tsx +++ b/src/components/NoticeBoard.tsx @@ -56,8 +56,8 @@ const NoticeBoard: FunctionComponent< ext }); } else { - showToast("文件下载中……", 3000); - const success = await shareFile(url, ext); + showToast("文件下载中……", 1000); + const success = await shareFile(url, filename, ext); if (!success) { showToast("文件下载失败", 3000); } diff --git a/src/helpers/share.ts b/src/helpers/share.ts index 0bcf7be5..39b12fa9 100644 --- a/src/helpers/share.ts +++ b/src/helpers/share.ts @@ -1,3 +1,4 @@ +import { Platform } from "react-native"; import Share from "react-native-share"; import RNFetchBlob from "rn-fetch-blob"; @@ -22,30 +23,52 @@ export const mimeTypes: any = { rar: "application/x-rar-compressed" }; -export const shareFile = (url: string, ext: string) => { +export const shareFile = (url: string, name: string, ext: string) => { return new Promise(async (resolve, reject) => { if (!supportedFileTypes.includes(ext.toLowerCase())) { reject("Unsupported file type"); } - const res = await RNFetchBlob.fetch("GET", url); - const status = res.respInfo.status; + const filePath = await downloadFile(url, name, ext).catch(() => + reject("Download failed") + ); - if (status !== 200) { - reject(); - } else { - const base64Str = res.base64(); + if (filePath) { Share.open({ - url: `data:${mimeTypes[ext]};base64,${base64Str}`, + url: Platform.OS === "android" ? "file://" + filePath : filePath, type: mimeTypes[ext], title: "打开文件", showAppsToView: true }); + resolve(true); } }); }; +export const downloadFile = (url: string, name: string, ext: string) => { + return new Promise(async (resolve, reject) => { + const dirs = RNFetchBlob.fs.dirs; + const filePath = `${dirs.DocumentDir}/files/${name}.${ext}`; + const exists = await RNFetchBlob.fs.exists(filePath); + + if (!exists) { + const res = await RNFetchBlob.config({ + fileCache: true, + path: filePath + }).fetch("GET", url); + const status = res.respInfo.status; + if (status !== 200) { + reject(); + } else { + resolve(res.path()); + } + } else { + resolve(filePath); + } + }); +}; + export const getExtension = (filename: string) => { return filename.split(".").pop(); }; diff --git a/src/screens/CourseDetailScreen.tsx b/src/screens/CourseDetailScreen.tsx index 879c2b66..ccea97b4 100644 --- a/src/screens/CourseDetailScreen.tsx +++ b/src/screens/CourseDetailScreen.tsx @@ -128,8 +128,8 @@ const CourseDetailScreen: INavigationScreen< ext }); } else { - showToast("文件下载中……", 3000); - const success = await shareFile(url, ext); + showToast("文件下载中……", 1000); + const success = await shareFile(url, filename, ext); if (!success) { showToast("文件下载失败", 3000); } diff --git a/src/screens/FilesScreen.tsx b/src/screens/FilesScreen.tsx index 7e6a2f17..cfcdeff2 100644 --- a/src/screens/FilesScreen.tsx +++ b/src/screens/FilesScreen.tsx @@ -115,8 +115,8 @@ const FilesScreen: INavigationScreen = props => { ext }); } else { - showToast("文件下载中……", 3000); - const success = await shareFile(url, ext); + showToast("文件下载中……", 1000); + const success = await shareFile(url, filename, ext); if (!success) { showToast("文件下载失败", 3000); } diff --git a/src/screens/SettingsScreen.tsx b/src/screens/SettingsScreen.tsx index 7692543a..1aa55356 100644 --- a/src/screens/SettingsScreen.tsx +++ b/src/screens/SettingsScreen.tsx @@ -12,6 +12,7 @@ import { iOSColors } from "react-native-typography"; import MaterialCommunityIcons from "react-native-vector-icons/MaterialCommunityIcons"; import MaterialIcons from "react-native-vector-icons/MaterialIcons"; import { connect } from "react-redux"; +import RNFetchBlob from "rn-fetch-blob"; import packageConfig from "../../package.json"; import Divider from "../components/Divider"; import SettingsListItem from "../components/SettingsListItem"; @@ -135,6 +136,29 @@ const SettingsScreen: INavigationScreen = props => { } }; + const onClearFileCachePress = () => { + Alert.alert( + "清空文件缓存", + "确定要清空文件缓存吗?", + [ + { + text: "取消", + style: "cancel" + }, + { + text: "确定", + onPress: async () => { + await RNFetchBlob.fs.unlink( + `${RNFetchBlob.fs.dirs.DocumentDir}/files` + ); + showToast("成功清空文件缓存", 1500); + } + } + ], + { cancelable: true } + ); + }; + const renderListItem: ListRenderItem<{}> = ({ index }) => { switch (index) { case 0: @@ -169,6 +193,15 @@ const SettingsScreen: INavigationScreen = props => { /> ); case 3: + return ( + } + text="清空文件缓存" + onPress={onClearFileCachePress} + /> + ); + case 4: return Platform.OS === "android" ? ( = props => { onPress={onCheckUpdatePress} /> ) : null; - case 4: + case 5: return ( = props => { onPress={onAcknowledgementsPress} /> ); - case 5: + case 6: return ( = props => { { key: "autoRefreshing" }, { key: "calendarSync" }, { key: "logout" }, + { key: "clearFileCache" }, { key: "checkUpdate" }, { key: "acknowledgement" }, { key: "about" } diff --git a/src/screens/WebViewScreen.tsx b/src/screens/WebViewScreen.tsx index b4df959e..587142e4 100644 --- a/src/screens/WebViewScreen.tsx +++ b/src/screens/WebViewScreen.tsx @@ -1,31 +1,41 @@ -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; import { View } from "react-native"; import Icon from "react-native-vector-icons/MaterialIcons"; import { WebView } from "react-native-webview"; import MediumPlaceholder from "../components/MediumPlaceholder"; -import { shareFile } from "../helpers/share"; +import { downloadFile, shareFile } from "../helpers/share"; import { showToast } from "../redux/actions/toast"; import { store } from "../redux/store"; import { INavigationScreen } from "../types/NavigationScreen"; -export interface IWebViewScreenProps { - readonly url: string; -} - -const WebViewScreen: INavigationScreen = props => { +const WebViewScreen: INavigationScreen<{}> = props => { const url = props.navigation.getParam("url"); + const name = props.navigation.getParam("filename"); + const ext = props.navigation.getParam("ext"); const [loading, setLoading] = useState(true); + const [filePath, setFilePath] = useState(""); + + useEffect(() => { + if (loading) { + (async () => { + const filePath = await downloadFile(url, name, ext); + if (filePath) { + setFilePath(filePath); + setLoading(false); + } + })(); + } + }, [loading]); return ( <> setLoading(false)} + originWhitelist={["*"]} /> {loading && ( { // tslint:disable-next-line: jsx-no-lambda onPress={() => { store.dispatch(showToast("准备文件中……", 1500)); - shareFile(navigation.getParam("url"), navigation.getParam("ext")); + shareFile( + navigation.getParam("url"), + navigation.getParam("filename"), + navigation.getParam("ext") + ); }} color="white" size={24}