diff --git a/components/download/app.tsx b/components/download/app.tsx index d0f2ddd..40de423 100644 --- a/components/download/app.tsx +++ b/components/download/app.tsx @@ -3,8 +3,8 @@ import LogoSvg from "@/assets/icons/svg/logo.svg"; import UserinfoTotalSvg from "@/assets/icons/svg/userinfoTotal.svg"; import { useTranslation } from 'react-i18next'; import { Linking, Platform, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; -import QRCode from 'react-native-qrcode-svg'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import QRDownloadScreen from "./qrCode"; interface AppDownloadProps { IOS_APP_STORE_URL: string, @@ -39,7 +39,8 @@ export const AppDownload = (props: AppDownloadProps) => { - + {/* { logoSize={50} logoBorderRadius={10} logoBackgroundColor="#f0f0f0" - /> + /> */} {/* Description */} diff --git a/components/download/qrCode.tsx b/components/download/qrCode.tsx new file mode 100644 index 0000000..419fd8c --- /dev/null +++ b/components/download/qrCode.tsx @@ -0,0 +1,90 @@ +import * as Haptics from 'expo-haptics'; +import * as MediaLibrary from 'expo-media-library'; +import React, { useRef } from 'react'; +import { Alert, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; +import QRCode from 'react-native-qrcode-svg'; +import { captureRef } from 'react-native-view-shot'; + +export default function QRDownloadScreen(prop: { url: string }) { + const qrViewRef = useRef(null); // 用于截图的引用 + const [qrValue] = React.useState(prop.url); // 二维码内容 + + const saveQRToGallery = async () => { + try { + // 触发轻震,提升交互感 + await Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); + + // 请求相册写入权限 + const { status } = await MediaLibrary.requestPermissionsAsync(); + if (status !== 'granted') { + Alert.alert('权限被拒绝', '需要保存图片到相册的权限'); + return; + } + + if (!qrViewRef.current) return; + + // 截取二维码视图 + const uri = await captureRef(qrViewRef, { + format: 'png', + quality: 1, + result: 'tmpfile', // 返回临时文件路径 + }); + + // 保存到相册 + await MediaLibrary.saveToLibraryAsync(uri); + + Alert.alert('✅ 成功', '二维码已保存到相册!'); + } catch (error) { + console.error('保存失败:', error); + Alert.alert('❌ 失败', '无法保存图片,请重试'); + } + }; + + return ( + + 长按保存二维码 + + {/* 可截图的容器 */} + + + + + + + 👉 长按二维码即可保存 + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + padding: 20, + backgroundColor: '#f8f8f8', + }, + title: { + fontSize: 18, + fontWeight: 'bold', + marginBottom: 20, + textAlign: 'center', + }, + qrContainer: { + padding: 16, + backgroundColor: '#fff', + borderRadius: 12, + borderWidth: 1, + borderColor: '#ddd', + shadowColor: '#000', + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.1, + shadowRadius: 4, + elevation: 3, + }, + tip: { + marginTop: 20, + color: '#666', + fontSize: 14, + }, +}); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 7d9d7bf..1a876dc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -69,6 +69,7 @@ "react-native-svg": "^15.11.2", "react-native-toast-message": "^2.3.0", "react-native-uuid": "^2.0.3", + "react-native-view-shot": "4.0.3", "react-native-web": "~0.20.0", "react-native-webview": "13.13.5", "react-redux": "^9.2.0" @@ -6150,6 +6151,15 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "license": "MIT" }, + "node_modules/base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -7066,6 +7076,15 @@ "hyphenate-style-name": "^1.0.3" } }, + "node_modules/css-line-break": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", + "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==", + "license": "MIT", + "dependencies": { + "utrie": "^1.0.2" + } + }, "node_modules/css-select": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", @@ -10002,6 +10021,19 @@ "void-elements": "3.1.0" } }, + "node_modules/html2canvas": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", + "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==", + "license": "MIT", + "dependencies": { + "css-line-break": "^2.1.0", + "text-segmentation": "^1.0.3" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/htmlparser2": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-7.2.0.tgz", @@ -15166,6 +15198,19 @@ "npm": ">=6.0.0" } }, + "node_modules/react-native-view-shot": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/react-native-view-shot/-/react-native-view-shot-4.0.3.tgz", + "integrity": "sha512-USNjYmED7C0me02c1DxKA0074Hw+y/nxo+xJKlffMvfUWWzL5ELh/TJA/pTnVqFurIrzthZDPtDM7aBFJuhrHQ==", + "license": "MIT", + "dependencies": { + "html2canvas": "^1.4.1" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, "node_modules/react-native-web": { "version": "0.20.0", "resolved": "https://registry.npmjs.org/react-native-web/-/react-native-web-0.20.0.tgz", @@ -17009,6 +17054,15 @@ "deprecated": "no longer maintained", "license": "(Unlicense OR Apache-2.0)" }, + "node_modules/text-segmentation": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", + "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==", + "license": "MIT", + "dependencies": { + "utrie": "^1.0.2" + } + }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -17683,6 +17737,15 @@ "node": ">= 0.4.0" } }, + "node_modules/utrie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", + "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", + "license": "MIT", + "dependencies": { + "base64-arraybuffer": "^1.0.2" + } + }, "node_modules/uuid": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz", diff --git a/package.json b/package.json index 20247a6..1d563ba 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,8 @@ "react-native-uuid": "^2.0.3", "react-native-web": "~0.20.0", "react-native-webview": "13.13.5", - "react-redux": "^9.2.0" + "react-redux": "^9.2.0", + "react-native-view-shot": "4.0.3" }, "devDependencies": { "@babel/core": "^7.25.2",