feat: 统一权限申请提示组件
This commit is contained in:
parent
4a052844e9
commit
9985e0517f
@ -36,6 +36,7 @@ export default function OwnerPage() {
|
|||||||
// 设置弹窗
|
// 设置弹窗
|
||||||
const [modalVisible, setModalVisible] = useState(false);
|
const [modalVisible, setModalVisible] = useState(false);
|
||||||
|
|
||||||
|
|
||||||
// 数据统计
|
// 数据统计
|
||||||
const [countData, setCountData] = useState<CountData>({} as CountData);
|
const [countData, setCountData] = useState<CountData>({} as CountData);
|
||||||
|
|
||||||
@ -107,8 +108,10 @@ export default function OwnerPage() {
|
|||||||
</View>
|
</View>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
{/* 设置弹窗 */}
|
{/* 设置弹窗 - 使用条件渲染避免层级冲突 */}
|
||||||
<SettingModal modalVisible={modalVisible} setModalVisible={setModalVisible} userInfo={userInfoDetails.user_info} />
|
{modalVisible && (
|
||||||
|
<SettingModal modalVisible={modalVisible} setModalVisible={setModalVisible} userInfo={userInfoDetails.user_info} />
|
||||||
|
)}
|
||||||
|
|
||||||
{/* 导航栏 */}
|
{/* 导航栏 */}
|
||||||
<AskNavbar />
|
<AskNavbar />
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import { StatusBar } from 'expo-status-bar';
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import 'react-native-reanimated';
|
import 'react-native-reanimated';
|
||||||
import '../global.css';
|
import '../global.css';
|
||||||
|
import { PermissionProvider } from '@/context/PermissionContext';
|
||||||
import { Provider } from "../provider";
|
import { Provider } from "../provider";
|
||||||
|
|
||||||
export default function RootLayout() {
|
export default function RootLayout() {
|
||||||
@ -30,8 +31,9 @@ export default function RootLayout() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
|
<ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
|
||||||
<Provider>
|
<PermissionProvider>
|
||||||
<Stack>
|
<Provider>
|
||||||
|
<Stack>
|
||||||
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
|
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
|
||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
name="login"
|
name="login"
|
||||||
@ -41,8 +43,9 @@ export default function RootLayout() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Stack.Screen name="+not-found" />
|
<Stack.Screen name="+not-found" />
|
||||||
</Stack>
|
</Stack>
|
||||||
</Provider>
|
</Provider>
|
||||||
|
</PermissionProvider>
|
||||||
<StatusBar style="auto" />
|
<StatusBar style="auto" />
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
);
|
);
|
||||||
|
|||||||
111
components/common/PermissionAlert.tsx
Normal file
111
components/common/PermissionAlert.tsx
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { Pressable, StyleSheet, Text, View } from 'react-native';
|
||||||
|
|
||||||
|
interface PermissionAlertProps {
|
||||||
|
visible: boolean;
|
||||||
|
onConfirm: () => void;
|
||||||
|
onCancel: () => void;
|
||||||
|
title: string;
|
||||||
|
message: string;
|
||||||
|
confirmText?: string;
|
||||||
|
cancelText?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PermissionAlert: React.FC<PermissionAlertProps> = ({ visible, onConfirm, onCancel, title, message, confirmText, cancelText }) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
if (!visible) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={styles.overlay}>
|
||||||
|
<View style={styles.centeredView}>
|
||||||
|
<View style={styles.modalView}>
|
||||||
|
<Text style={styles.modalTitle}>{title}</Text>
|
||||||
|
<Text style={styles.modalMessage}>{message}</Text>
|
||||||
|
<View style={styles.buttonContainer}>
|
||||||
|
<Pressable style={[styles.button, styles.cancelButton]} onPress={onCancel}>
|
||||||
|
<Text style={styles.buttonText}>{cancelText || t('cancel', { ns: 'permission' })}</Text>
|
||||||
|
</Pressable>
|
||||||
|
<Pressable style={[styles.button, styles.confirmButton]} onPress={onConfirm}>
|
||||||
|
<Text style={styles.confirmButtonText}>{confirmText || t('goToSettings', { ns: 'permission' })}</Text>
|
||||||
|
</Pressable>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
overlay: {
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
zIndex: 99,
|
||||||
|
},
|
||||||
|
centeredView: {
|
||||||
|
flex: 1,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
backgroundColor: 'rgba(0,0,0,0.5)',
|
||||||
|
zIndex: 9999,
|
||||||
|
},
|
||||||
|
modalView: {
|
||||||
|
width: '80%',
|
||||||
|
backgroundColor: 'white',
|
||||||
|
borderRadius: 16,
|
||||||
|
padding: 24,
|
||||||
|
alignItems: 'center',
|
||||||
|
shadowColor: '#000',
|
||||||
|
shadowOffset: { width: 0, height: 2 },
|
||||||
|
shadowOpacity: 0.25,
|
||||||
|
shadowRadius: 4,
|
||||||
|
elevation: 5,
|
||||||
|
},
|
||||||
|
modalTitle: {
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
color: '#4C320C',
|
||||||
|
marginBottom: 12,
|
||||||
|
},
|
||||||
|
modalMessage: {
|
||||||
|
fontSize: 16,
|
||||||
|
color: '#4C320C',
|
||||||
|
textAlign: 'center',
|
||||||
|
marginBottom: 24,
|
||||||
|
},
|
||||||
|
buttonContainer: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
width: '100%',
|
||||||
|
},
|
||||||
|
button: {
|
||||||
|
borderRadius: 20,
|
||||||
|
paddingVertical: 12,
|
||||||
|
flex: 1,
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
cancelButton: {
|
||||||
|
backgroundColor: '#F5F5F5',
|
||||||
|
marginRight: 8,
|
||||||
|
},
|
||||||
|
confirmButton: {
|
||||||
|
backgroundColor: '#E2793F',
|
||||||
|
marginLeft: 8,
|
||||||
|
},
|
||||||
|
buttonText: {
|
||||||
|
color: '#4C320C',
|
||||||
|
fontWeight: '600',
|
||||||
|
},
|
||||||
|
confirmButtonText: {
|
||||||
|
color: 'white',
|
||||||
|
fontWeight: '600',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default PermissionAlert;
|
||||||
@ -1,7 +1,9 @@
|
|||||||
import * as Haptics from 'expo-haptics';
|
import * as Haptics from 'expo-haptics';
|
||||||
import * as MediaLibrary from 'expo-media-library';
|
import * as MediaLibrary from 'expo-media-library';
|
||||||
import React, { useRef } from 'react';
|
import React, { useRef } from 'react';
|
||||||
import { Alert, StyleSheet, TouchableOpacity, View } from 'react-native';
|
import { StyleSheet, TouchableOpacity, View } from 'react-native';
|
||||||
|
import i18n from '@/i18n';
|
||||||
|
import { PermissionService } from '@/lib/PermissionService';
|
||||||
import QRCode from 'react-native-qrcode-svg';
|
import QRCode from 'react-native-qrcode-svg';
|
||||||
import { captureRef } from 'react-native-view-shot';
|
import { captureRef } from 'react-native-view-shot';
|
||||||
|
|
||||||
@ -17,7 +19,7 @@ export default function QRDownloadScreen(prop: { url: string }) {
|
|||||||
// 请求相册写入权限
|
// 请求相册写入权限
|
||||||
const { status } = await MediaLibrary.requestPermissionsAsync();
|
const { status } = await MediaLibrary.requestPermissionsAsync();
|
||||||
if (status !== 'granted') {
|
if (status !== 'granted') {
|
||||||
Alert.alert('权限被拒绝', '需要保存图片到相册的权限');
|
PermissionService.show({ title: i18n.t('permission:title.permissionDenied'), message: i18n.t('permission:message.saveToAlbumPermissionRequired') });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,10 +35,10 @@ export default function QRDownloadScreen(prop: { url: string }) {
|
|||||||
// 保存到相册
|
// 保存到相册
|
||||||
await MediaLibrary.saveToLibraryAsync(uri);
|
await MediaLibrary.saveToLibraryAsync(uri);
|
||||||
|
|
||||||
Alert.alert('✅ 成功', '二维码已保存到相册!');
|
PermissionService.show({ title: i18n.t('permission:title.success'), message: i18n.t('permission:message.qrCodeSaved') });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('保存失败:', error);
|
console.error('保存失败:', error);
|
||||||
Alert.alert('❌ 失败', '无法保存图片,请重试');
|
PermissionService.show({ title: i18n.t('permission:title.error'), message: i18n.t('permission:message.saveImageFailed') });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,8 @@ import { uploadFileWithProgress } from '@/lib/background-uploader/uploader';
|
|||||||
import { compressImage } from '@/lib/image-process/imageCompress';
|
import { compressImage } from '@/lib/image-process/imageCompress';
|
||||||
import { createVideoThumbnailFile } from '@/lib/video-process/videoThumbnail';
|
import { createVideoThumbnailFile } from '@/lib/video-process/videoThumbnail';
|
||||||
import * as ImagePicker from 'expo-image-picker';
|
import * as ImagePicker from 'expo-image-picker';
|
||||||
import * as Location from 'expo-location';
|
import { requestLocationPermission, requestMediaLibraryPermission } from '@/components/owner/utils';
|
||||||
|
import { PermissionService } from '@/lib/PermissionService';
|
||||||
import * as MediaLibrary from 'expo-media-library';
|
import * as MediaLibrary from 'expo-media-library';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { Alert, Button, Platform, TouchableOpacity, View } from 'react-native';
|
import { Alert, Button, Platform, TouchableOpacity, View } from 'react-native';
|
||||||
@ -26,23 +27,6 @@ export const ImagesUploader: React.FC<ImagesuploaderProps> = ({
|
|||||||
const [files, setFiles] = useState<FileUploadItem[]>([]);
|
const [files, setFiles] = useState<FileUploadItem[]>([]);
|
||||||
const [uploadQueue, setUploadQueue] = useState<FileUploadItem[]>([]);
|
const [uploadQueue, setUploadQueue] = useState<FileUploadItem[]>([]);
|
||||||
|
|
||||||
// 请求权限
|
|
||||||
const requestPermissions = async () => {
|
|
||||||
if (Platform.OS !== 'web') {
|
|
||||||
const { status: mediaStatus } = await ImagePicker.requestMediaLibraryPermissionsAsync();
|
|
||||||
if (mediaStatus !== 'granted') {
|
|
||||||
Alert.alert('需要媒体库权限', '请允许访问媒体库以选择图片');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { status: locationStatus } = await Location.requestForegroundPermissionsAsync();;
|
|
||||||
if (locationStatus !== 'granted') {
|
|
||||||
Alert.alert('需要位置权限', '需要位置权限才能获取图片位置信息');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 处理单个资源
|
// 处理单个资源
|
||||||
const processSingleAsset = async (asset: ImagePicker.ImagePickerAsset): Promise<UploadResult | null> => {
|
const processSingleAsset = async (asset: ImagePicker.ImagePickerAsset): Promise<UploadResult | null> => {
|
||||||
console.log("asset111111", asset);
|
console.log("asset111111", asset);
|
||||||
@ -158,7 +142,7 @@ export const ImagesUploader: React.FC<ImagesuploaderProps> = ({
|
|||||||
try {
|
try {
|
||||||
// 统一通过 lib 的 uploadFileWithProgress 实现上传
|
// 统一通过 lib 的 uploadFileWithProgress 实现上传
|
||||||
const uploadUrlData = await getUploadUrl(task.file, { ...task.metadata, GPSVersionID: undefined });
|
const uploadUrlData = await getUploadUrl(task.file, { ...task.metadata, GPSVersionID: undefined });
|
||||||
const taskIndex = uploadTasks.indexOf(task);
|
const taskIndex = uploadTasks.indexOf(task);
|
||||||
const totalTasks = uploadTasks.length;
|
const totalTasks = uploadTasks.length;
|
||||||
const baseProgress = (taskIndex / totalTasks) * 100;
|
const baseProgress = (taskIndex / totalTasks) * 100;
|
||||||
|
|
||||||
@ -261,9 +245,13 @@ export const ImagesUploader: React.FC<ImagesuploaderProps> = ({
|
|||||||
const pickImage = async () => {
|
const pickImage = async () => {
|
||||||
try {
|
try {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
const hasPermission = await requestPermissions();
|
const hasMediaPermission = await requestMediaLibraryPermission();
|
||||||
console.log("hasPermission", hasPermission);
|
if (!hasMediaPermission) {
|
||||||
if (!hasPermission) return;
|
setIsLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 请求位置权限,但不强制要求
|
||||||
|
await requestLocationPermission();
|
||||||
|
|
||||||
const result = await ImagePicker.launchImageLibraryAsync({
|
const result = await ImagePicker.launchImageLibraryAsync({
|
||||||
mediaTypes: fileType,
|
mediaTypes: fileType,
|
||||||
@ -290,13 +278,13 @@ export const ImagesUploader: React.FC<ImagesuploaderProps> = ({
|
|||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Alert.alert('错误', '部分文件处理失败,请重试');
|
PermissionService.show({ title: '错误', message: '部分文件处理失败,请重试' });
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Alert.alert('错误', '选择图片时出错,请重试');
|
PermissionService.show({ title: '错误', message: '选择图片时出错,请重试' });
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
import * as MediaLibrary from 'expo-media-library';
|
import * as MediaLibrary from 'expo-media-library';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { ActivityIndicator, Alert, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
import { ActivityIndicator, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
||||||
|
import i18n from '@/i18n';
|
||||||
|
import { PermissionService } from '@/lib/PermissionService';
|
||||||
|
|
||||||
interface MediaStats {
|
interface MediaStats {
|
||||||
total: number;
|
total: number;
|
||||||
@ -46,7 +48,7 @@ const MediaStatsScreen = () => {
|
|||||||
// 1. 请求媒体库权限
|
// 1. 请求媒体库权限
|
||||||
const { status } = await MediaLibrary.requestPermissionsAsync();
|
const { status } = await MediaLibrary.requestPermissionsAsync();
|
||||||
if (status !== 'granted') {
|
if (status !== 'granted') {
|
||||||
Alert.alert('权限被拒绝', '需要访问媒体库权限来获取统计信息');
|
PermissionService.show({ title: i18n.t('permission:title.permissionDenied'), message: i18n.t('permission:message.getStatsPermissionRequired') });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,7 +118,7 @@ const MediaStatsScreen = () => {
|
|||||||
setStats(stats);
|
setStats(stats);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取媒体库统计信息失败:', error);
|
console.error('获取媒体库统计信息失败:', error);
|
||||||
Alert.alert('错误', '获取媒体库统计信息失败');
|
PermissionService.show({ title: i18n.t('permission:title.error'), message: i18n.t('permission:message.getStatsFailed') });
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,10 +2,11 @@ import { fetchApi } from '@/lib/server-api-util';
|
|||||||
import { ConfirmUpload, defaultExifData, ExifData, FileStatus, ImagesPickerProps, UploadResult, UploadUrlResponse } from '@/types/upload';
|
import { ConfirmUpload, defaultExifData, ExifData, FileStatus, ImagesPickerProps, UploadResult, UploadUrlResponse } from '@/types/upload';
|
||||||
import * as ImageManipulator from 'expo-image-manipulator';
|
import * as ImageManipulator from 'expo-image-manipulator';
|
||||||
import * as ImagePicker from 'expo-image-picker';
|
import * as ImagePicker from 'expo-image-picker';
|
||||||
import * as Location from 'expo-location';
|
import { requestLocationPermission, requestMediaLibraryPermission } from '@/components/owner/utils';
|
||||||
|
import { PermissionService } from '@/lib/PermissionService';
|
||||||
import * as MediaLibrary from 'expo-media-library';
|
import * as MediaLibrary from 'expo-media-library';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { Alert, Button, Platform, TouchableOpacity, View } from 'react-native';
|
import { Button, Platform, TouchableOpacity, View } from 'react-native';
|
||||||
import * as Progress from 'react-native-progress';
|
import * as Progress from 'react-native-progress';
|
||||||
|
|
||||||
export const ImagesPicker: React.FC<ImagesPickerProps> = ({
|
export const ImagesPicker: React.FC<ImagesPickerProps> = ({
|
||||||
@ -24,17 +25,13 @@ export const ImagesPicker: React.FC<ImagesPickerProps> = ({
|
|||||||
// 请求权限
|
// 请求权限
|
||||||
const requestPermissions = async () => {
|
const requestPermissions = async () => {
|
||||||
if (Platform.OS !== 'web') {
|
if (Platform.OS !== 'web') {
|
||||||
|
const hasMediaPermission = await requestMediaLibraryPermission();
|
||||||
const { status: mediaStatus } = await ImagePicker.requestMediaLibraryPermissionsAsync();
|
if (!hasMediaPermission) {
|
||||||
if (mediaStatus !== 'granted') {
|
setIsLoading(false);
|
||||||
Alert.alert('需要媒体库权限', '请允许访问媒体库以选择图片');
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
// 请求位置权限,但不强制要求
|
||||||
const { status: locationStatus } = await Location.requestForegroundPermissionsAsync();;
|
await requestLocationPermission();
|
||||||
if (locationStatus !== 'granted') {
|
|
||||||
Alert.alert('需要位置权限', '需要位置权限才能获取图片位置信息');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
@ -295,10 +292,10 @@ export const ImagesPicker: React.FC<ImagesPickerProps> = ({
|
|||||||
throw error; // 重新抛出错误,让外层 catch 处理
|
throw error; // 重新抛出错误,让外层 catch 处理
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Alert.alert('错误', '处理图片时出错');
|
PermissionService.show({ title: '错误', message: '处理图片时出错' });
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Alert.alert('错误', '选择图片时出错,请重试');
|
PermissionService.show({ title: '错误', message: '选择图片时出错,请重试' });
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,7 +20,13 @@ const AlbumComponent = ({ setModalVisible, style }: CategoryProps) => {
|
|||||||
<TouchableOpacity style={{ flex: 3 }}>
|
<TouchableOpacity style={{ flex: 3 }}>
|
||||||
<ThemedText style={styles.text}>{t('generalSetting.shareProfile', { ns: 'personal' })}</ThemedText>
|
<ThemedText style={styles.text}>{t('generalSetting.shareProfile', { ns: 'personal' })}</ThemedText>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
<TouchableOpacity onPress={() => setModalVisible(true)} style={[styles.text, { flex: 1, alignItems: "center", paddingVertical: 6 }]}>
|
<TouchableOpacity
|
||||||
|
onPress={() => {
|
||||||
|
setModalVisible(true);
|
||||||
|
}}
|
||||||
|
activeOpacity={0.7}
|
||||||
|
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
|
||||||
|
style={[styles.text, { flex: 1, alignItems: "center", paddingVertical: 6, zIndex: 999 }]}>
|
||||||
<SettingSvg />
|
<SettingSvg />
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import { checkNotificationPermission, getLocationPermission, getPermissions, req
|
|||||||
|
|
||||||
const SettingModal = (props: { modalVisible: boolean, setModalVisible: (visible: boolean) => void, userInfo: User }) => {
|
const SettingModal = (props: { modalVisible: boolean, setModalVisible: (visible: boolean) => void, userInfo: User }) => {
|
||||||
const { modalVisible, setModalVisible, userInfo } = props;
|
const { modalVisible, setModalVisible, userInfo } = props;
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [modalType, setModalType] = useState<'ai' | 'terms' | 'privacy' | 'user'>('ai');
|
const [modalType, setModalType] = useState<'ai' | 'terms' | 'privacy' | 'user'>('ai');
|
||||||
// 协议弹窗
|
// 协议弹窗
|
||||||
@ -36,28 +37,31 @@ const SettingModal = (props: { modalVisible: boolean, setModalVisible: (visible:
|
|||||||
};
|
};
|
||||||
// 通知消息权限开关
|
// 通知消息权限开关
|
||||||
const [notificationsEnabled, setNotificationsEnabled] = useState(false);
|
const [notificationsEnabled, setNotificationsEnabled] = useState(false);
|
||||||
const toggleNotifications = () => {
|
const toggleNotifications = async () => {
|
||||||
if (notificationsEnabled) {
|
if (notificationsEnabled) {
|
||||||
// 引导去设置关闭权限
|
// 引导去设置关闭权限
|
||||||
openAppSettings()
|
openAppSettings()
|
||||||
} else {
|
} else {
|
||||||
console.log('请求通知权限');
|
requestNotificationPermission()
|
||||||
requestNotificationPermission().then((res) => {
|
.then((granted) => {
|
||||||
setNotificationsEnabled(res as boolean);
|
setNotificationsEnabled(granted);
|
||||||
})
|
});
|
||||||
|
setModalVisible(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 相册权限
|
// 相册权限
|
||||||
const [albumEnabled, setAlbumEnabled] = useState(false);
|
const [albumEnabled, setAlbumEnabled] = useState(false);
|
||||||
const toggleAlbum = () => {
|
const toggleAlbum = async () => {
|
||||||
if (albumEnabled) {
|
if (albumEnabled) {
|
||||||
// 引导去设置关闭权限
|
// 引导去设置关闭权限
|
||||||
openAppSettings()
|
openAppSettings()
|
||||||
} else {
|
} else {
|
||||||
requestMediaLibraryPermission().then((res) => {
|
requestMediaLibraryPermission()
|
||||||
setAlbumEnabled(res as boolean);
|
.then((granted) => {
|
||||||
})
|
setAlbumEnabled(granted);
|
||||||
|
});
|
||||||
|
setModalVisible(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,12 +70,14 @@ const SettingModal = (props: { modalVisible: boolean, setModalVisible: (visible:
|
|||||||
// 位置权限更改
|
// 位置权限更改
|
||||||
const toggleLocation = async () => {
|
const toggleLocation = async () => {
|
||||||
if (locationEnabled) {
|
if (locationEnabled) {
|
||||||
// 引导去设置关闭权限
|
// 如果权限已开启,点击则引导用户去设置关闭
|
||||||
openAppSettings()
|
openAppSettings();
|
||||||
} else {
|
} else {
|
||||||
requestLocationPermission().then((res) => {
|
requestLocationPermission()
|
||||||
setLocationEnabled(res as boolean);
|
.then((granted) => {
|
||||||
})
|
setLocationEnabled(granted);
|
||||||
|
});
|
||||||
|
setModalVisible(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// 正在获取位置信息
|
// 正在获取位置信息
|
||||||
@ -92,16 +98,18 @@ const SettingModal = (props: { modalVisible: boolean, setModalVisible: (visible:
|
|||||||
let currentStatus = await getLocationPermission();
|
let currentStatus = await getLocationPermission();
|
||||||
console.log('当前权限状态:', currentStatus);
|
console.log('当前权限状态:', currentStatus);
|
||||||
|
|
||||||
// 2. 如果没有权限,则请求权限
|
// 2. 如果没有权限,则跳过获取位置
|
||||||
if (!currentStatus) {
|
if (!currentStatus) {
|
||||||
const newStatus = await requestLocationPermission();
|
console.log('没有权限,跳过获取位置')
|
||||||
setLocationEnabled(newStatus);
|
return;
|
||||||
currentStatus = newStatus;
|
// const newStatus = await requestLocationPermission();
|
||||||
|
// setLocationEnabled(newStatus);
|
||||||
|
// currentStatus = newStatus;
|
||||||
|
|
||||||
if (!currentStatus) {
|
// if (!currentStatus) {
|
||||||
alert('需要位置权限才能继续');
|
// // alert('需要位置权限才能继续');
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 确保位置服务已启用
|
// 3. 确保位置服务已启用
|
||||||
@ -161,10 +169,12 @@ const SettingModal = (props: { modalVisible: boolean, setModalVisible: (visible:
|
|||||||
if (modalVisible) {
|
if (modalVisible) {
|
||||||
// 位置权限
|
// 位置权限
|
||||||
getLocationPermission().then((res) => {
|
getLocationPermission().then((res) => {
|
||||||
|
console.log('位置权限:', res);
|
||||||
setLocationEnabled(res);
|
setLocationEnabled(res);
|
||||||
})
|
})
|
||||||
// 媒体库权限
|
// 媒体库权限
|
||||||
getPermissions().then((res) => {
|
getPermissions().then((res) => {
|
||||||
|
console.log('媒体库权限:', res);
|
||||||
setAlbumEnabled(res);
|
setAlbumEnabled(res);
|
||||||
})
|
})
|
||||||
// 通知权限
|
// 通知权限
|
||||||
@ -176,40 +186,41 @@ const SettingModal = (props: { modalVisible: boolean, setModalVisible: (visible:
|
|||||||
}, [modalVisible])
|
}, [modalVisible])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<>
|
||||||
animationType="slide"
|
<Modal
|
||||||
transparent={true}
|
animationType="slide"
|
||||||
visible={modalVisible}
|
transparent={true}
|
||||||
onRequestClose={() => {
|
visible={modalVisible}
|
||||||
setModalVisible(!modalVisible);
|
onRequestClose={() => {
|
||||||
}}>
|
setModalVisible(false);
|
||||||
<Pressable
|
}}>
|
||||||
style={styles.centeredView}
|
|
||||||
onPress={() => setModalVisible(false)}>
|
|
||||||
<Pressable
|
<Pressable
|
||||||
style={styles.modalView}
|
style={styles.centeredView}
|
||||||
onPress={(e) => e.stopPropagation()}>
|
onPress={() => setModalVisible(false)}>
|
||||||
<View style={styles.modalHeader}>
|
<Pressable
|
||||||
<Text style={{ opacity: 0 }}>Settings</Text>
|
style={styles.modalView}
|
||||||
<Text style={styles.modalTitle}>{t('generalSetting.allTitle', { ns: 'personal' })}</Text>
|
onPress={(e) => e.stopPropagation()}>
|
||||||
<TouchableOpacity onPress={() => setModalVisible(false)}>
|
<View style={styles.modalHeader}>
|
||||||
<Text style={styles.closeButton}>×</Text>
|
<Text style={{ opacity: 0 }}>Settings</Text>
|
||||||
</TouchableOpacity>
|
<Text style={styles.modalTitle}>{t('generalSetting.allTitle', { ns: 'personal' })}</Text>
|
||||||
</View>
|
<TouchableOpacity onPress={() => setModalVisible(false)}>
|
||||||
<ScrollView style={styles.modalContent} showsVerticalScrollIndicator={false}>
|
<Text style={styles.closeButton}>×</Text>
|
||||||
{/* 用户信息 */}
|
</TouchableOpacity>
|
||||||
<UserInfo
|
</View>
|
||||||
userInfo={userInfo}
|
<ScrollView style={styles.modalContent} showsVerticalScrollIndicator={false}>
|
||||||
setModalVisible={setModalVisible}
|
{/* 用户信息 */}
|
||||||
modalVisible={modalVisible}
|
<UserInfo
|
||||||
setCurrentLocation={setCurrentLocation}
|
userInfo={userInfo}
|
||||||
getCurrentLocation={getCurrentLocation}
|
setModalVisible={setModalVisible}
|
||||||
isLoading={isLoading}
|
modalVisible={modalVisible}
|
||||||
isRefreshing={isRefreshing}
|
setCurrentLocation={setCurrentLocation}
|
||||||
currentLocation={currentLocation}
|
getCurrentLocation={getCurrentLocation}
|
||||||
/>
|
isLoading={isLoading}
|
||||||
{/* 升级版本 */}
|
isRefreshing={isRefreshing}
|
||||||
{/* <View style={{ marginTop: 16 }}>
|
currentLocation={currentLocation}
|
||||||
|
/>
|
||||||
|
{/* 升级版本 */}
|
||||||
|
{/* <View style={{ marginTop: 16 }}>
|
||||||
<ThemedText style={{ marginLeft: 16, marginVertical: 8, color: '#AC7E35', fontSize: 14, fontWeight: '600' }}>{t('generalSetting.subscription', { ns: 'personal' })}</ThemedText>
|
<ThemedText style={{ marginLeft: 16, marginVertical: 8, color: '#AC7E35', fontSize: 14, fontWeight: '600' }}>{t('generalSetting.subscription', { ns: 'personal' })}</ThemedText>
|
||||||
<View style={styles.premium}>
|
<View style={styles.premium}>
|
||||||
<View>
|
<View>
|
||||||
@ -228,8 +239,8 @@ const SettingModal = (props: { modalVisible: boolean, setModalVisible: (visible:
|
|||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
</View> */}
|
</View> */}
|
||||||
{/* 消息通知 */}
|
{/* 消息通知 */}
|
||||||
{/* <View style={{ marginTop: 16 }}>
|
{/* <View style={{ marginTop: 16 }}>
|
||||||
<ThemedText style={{ marginLeft: 16, marginVertical: 8, color: '#AC7E35', fontSize: 14, fontWeight: '600' }}>{t('permission.pushNotification', { ns: 'personal' })}</ThemedText>
|
<ThemedText style={{ marginLeft: 16, marginVertical: 8, color: '#AC7E35', fontSize: 14, fontWeight: '600' }}>{t('permission.pushNotification', { ns: 'personal' })}</ThemedText>
|
||||||
<View style={styles.premium}>
|
<View style={styles.premium}>
|
||||||
<View>
|
<View>
|
||||||
@ -241,42 +252,42 @@ const SettingModal = (props: { modalVisible: boolean, setModalVisible: (visible:
|
|||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
</View> */}
|
</View> */}
|
||||||
{/* 权限信息 */}
|
{/* 权限信息 */}
|
||||||
<View style={{ marginTop: 16 }}>
|
<View style={{ marginTop: 16 }}>
|
||||||
<ThemedText style={{ marginLeft: 16, marginVertical: 8, color: '#AC7E35', fontSize: 14, fontWeight: '600' }}>{t('permission.permissionManagement', { ns: 'personal' })}</ThemedText>
|
<ThemedText style={{ marginLeft: 16, marginVertical: 8, color: '#AC7E35', fontSize: 14, fontWeight: '600' }}>{t('permission.permissionManagement', { ns: 'personal' })}</ThemedText>
|
||||||
<View style={styles.content}>
|
<View style={styles.content}>
|
||||||
{/* 相册权限 */}
|
{/* 相册权限 */}
|
||||||
<View style={styles.item}>
|
<View style={styles.item}>
|
||||||
<ThemedText style={styles.itemText}>{t('permission.galleryAccess', { ns: 'personal' })}</ThemedText>
|
<ThemedText style={styles.itemText}>{t('permission.galleryAccess', { ns: 'personal' })}</ThemedText>
|
||||||
<CustomSwitch
|
<CustomSwitch
|
||||||
isEnabled={albumEnabled}
|
isEnabled={albumEnabled}
|
||||||
toggleSwitch={toggleAlbum}
|
toggleSwitch={toggleAlbum}
|
||||||
/>
|
/>
|
||||||
</View>
|
|
||||||
{/* 分割线 */}
|
|
||||||
<Divider />
|
|
||||||
{/* 位置权限 */}
|
|
||||||
<View style={styles.item}>
|
|
||||||
<View>
|
|
||||||
<ThemedText style={styles.itemText}>{t('permission.locationPermission', { ns: 'personal' })}</ThemedText>
|
|
||||||
</View>
|
</View>
|
||||||
<CustomSwitch
|
{/* 分割线 */}
|
||||||
isEnabled={locationEnabled}
|
<Divider />
|
||||||
toggleSwitch={toggleLocation}
|
{/* 位置权限 */}
|
||||||
/>
|
<View style={styles.item}>
|
||||||
</View>
|
<View>
|
||||||
<Divider />
|
<ThemedText style={styles.itemText}>{t('permission.locationPermission', { ns: 'personal' })}</ThemedText>
|
||||||
<View style={styles.item}>
|
</View>
|
||||||
<View>
|
<CustomSwitch
|
||||||
<ThemedText style={styles.itemText}>{t('permission.pushNotification', { ns: 'personal' })}</ThemedText>
|
isEnabled={locationEnabled}
|
||||||
|
toggleSwitch={toggleLocation}
|
||||||
|
/>
|
||||||
</View>
|
</View>
|
||||||
<CustomSwitch
|
<Divider />
|
||||||
isEnabled={notificationsEnabled}
|
<View style={styles.item}>
|
||||||
toggleSwitch={toggleNotifications}
|
<View>
|
||||||
/>
|
<ThemedText style={styles.itemText}>{t('permission.pushNotification', { ns: 'personal' })}</ThemedText>
|
||||||
</View>
|
</View>
|
||||||
{/* 相册成片权限 */}
|
<CustomSwitch
|
||||||
{/* <View style={styles.item}>
|
isEnabled={notificationsEnabled}
|
||||||
|
toggleSwitch={toggleNotifications}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
{/* 相册成片权限 */}
|
||||||
|
{/* <View style={styles.item}>
|
||||||
<View>
|
<View>
|
||||||
<ThemedText style={styles.itemText}>Opus Permission</ThemedText>
|
<ThemedText style={styles.itemText}>Opus Permission</ThemedText>
|
||||||
</View>
|
</View>
|
||||||
@ -285,10 +296,10 @@ const SettingModal = (props: { modalVisible: boolean, setModalVisible: (visible:
|
|||||||
toggleSwitch={toggleAlbum}
|
toggleSwitch={toggleAlbum}
|
||||||
/>
|
/>
|
||||||
</View> */}
|
</View> */}
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
{/* 账号 */}
|
||||||
{/* 账号 */}
|
{/* <View style={{ marginTop: 16 }}>
|
||||||
{/* <View style={{ marginTop: 16 }}>
|
|
||||||
<ThemedText style={{ marginLeft: 16, marginVertical: 8, color: '#AC7E35', fontSize: 14, fontWeight: '600' }}>Account</ThemedText>
|
<ThemedText style={{ marginLeft: 16, marginVertical: 8, color: '#AC7E35', fontSize: 14, fontWeight: '600' }}>Account</ThemedText>
|
||||||
<View style={styles.content}>
|
<View style={styles.content}>
|
||||||
<View style={styles.item}>
|
<View style={styles.item}>
|
||||||
@ -307,78 +318,75 @@ const SettingModal = (props: { modalVisible: boolean, setModalVisible: (visible:
|
|||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</View> */}
|
</View> */}
|
||||||
{/* 协议 */}
|
{/* 协议 */}
|
||||||
<View style={{ marginTop: 16 }}>
|
<View style={{ marginTop: 16 }}>
|
||||||
<ThemedText style={{ marginLeft: 16, marginVertical: 8, color: '#AC7E35', fontSize: 14, fontWeight: '600' }}>{t('lcenses.title', { ns: 'personal' })}</ThemedText>
|
<ThemedText style={{ marginLeft: 16, marginVertical: 8, color: '#AC7E35', fontSize: 14, fontWeight: '600' }}>{t('lcenses.title', { ns: 'personal' })}</ThemedText>
|
||||||
<View style={styles.content}>
|
<View style={styles.content}>
|
||||||
<TouchableOpacity style={styles.item} onPress={() => { setModalType('privacy'); setPrivacyModalVisible(true) }} >
|
<TouchableOpacity style={styles.item} onPress={() => { setModalType('privacy'); setPrivacyModalVisible(true) }} >
|
||||||
<ThemedText style={styles.itemText}>{t('lcenses.privacyPolicy', { ns: 'personal' })}</ThemedText>
|
<ThemedText style={styles.itemText}>{t('lcenses.privacyPolicy', { ns: 'personal' })}</ThemedText>
|
||||||
<RightArrowSvg />
|
<RightArrowSvg />
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
<Divider />
|
<Divider />
|
||||||
<TouchableOpacity style={styles.item} onPress={() => { setModalType('terms'); setPrivacyModalVisible(true) }} >
|
<TouchableOpacity style={styles.item} onPress={() => { setModalType('terms'); setPrivacyModalVisible(true) }} >
|
||||||
<ThemedText style={styles.itemText}>{t('lcenses.applyPermission', { ns: 'personal' })}</ThemedText>
|
<ThemedText style={styles.itemText}>{t('lcenses.applyPermission', { ns: 'personal' })}</ThemedText>
|
||||||
<RightArrowSvg />
|
<RightArrowSvg />
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
<Divider />
|
<Divider />
|
||||||
<TouchableOpacity style={styles.item} onPress={() => { setModalType('user'); setPrivacyModalVisible(true) }} >
|
<TouchableOpacity style={styles.item} onPress={() => { setModalType('user'); setPrivacyModalVisible(true) }} >
|
||||||
<ThemedText style={styles.itemText}>{t('lcenses.userAgreement', { ns: 'personal' })}</ThemedText>
|
<ThemedText style={styles.itemText}>{t('lcenses.userAgreement', { ns: 'personal' })}</ThemedText>
|
||||||
<RightArrowSvg />
|
<RightArrowSvg />
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
<Divider />
|
<Divider />
|
||||||
<TouchableOpacity style={styles.item} onPress={() => { setModalType('ai'); setPrivacyModalVisible(true) }} >
|
<TouchableOpacity style={styles.item} onPress={() => { setModalType('ai'); setPrivacyModalVisible(true) }} >
|
||||||
<ThemedText style={styles.itemText}>{t('lcenses.aiPolicy', { ns: 'personal' })}</ThemedText>
|
<ThemedText style={styles.itemText}>{t('lcenses.aiPolicy', { ns: 'personal' })}</ThemedText>
|
||||||
<RightArrowSvg />
|
<RightArrowSvg />
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
<Divider />
|
<Divider />
|
||||||
<TouchableOpacity style={styles.item} onPress={() => { setLcensesModalVisible(true) }} >
|
<TouchableOpacity style={styles.item} onPress={() => { setLcensesModalVisible(true) }} >
|
||||||
<ThemedText style={styles.itemText}>{t('lcenses.qualification', { ns: 'personal' })}</ThemedText>
|
<ThemedText style={styles.itemText}>{t('lcenses.qualification', { ns: 'personal' })}</ThemedText>
|
||||||
<RightArrowSvg />
|
<RightArrowSvg />
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
<Divider />
|
<Divider />
|
||||||
<TouchableOpacity style={styles.item} onPress={() => Linking.openURL("https://beian.miit.gov.cn/")} >
|
<TouchableOpacity style={styles.item} onPress={() => Linking.openURL("https://beian.miit.gov.cn/")} >
|
||||||
<ThemedText style={styles.itemText}>{t('lcenses.ICP', { ns: 'personal' })}沪ICP备2023032876号-4</ThemedText>
|
<ThemedText style={styles.itemText}>{t('lcenses.ICP', { ns: 'personal' })}沪ICP备2023032876号-4</ThemedText>
|
||||||
<RightArrowSvg />
|
<RightArrowSvg />
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
{/* 其他信息 */}
|
|
||||||
<View style={{ marginTop: 16 }}>
|
|
||||||
<ThemedText style={{ marginLeft: 16, marginVertical: 8, color: '#AC7E35', fontSize: 14, fontWeight: '600' }}>{t('generalSetting.otherInformation', { ns: 'personal' })}</ThemedText>
|
|
||||||
<View style={styles.content}>
|
|
||||||
<TouchableOpacity style={styles.item} onPress={() => Linking.openURL("https://work.weixin.qq.com/kfid/kfca0ac87f4e05e8bfd")} >
|
|
||||||
<ThemedText style={styles.itemText}>{t('generalSetting.contactUs', { ns: 'personal' })}</ThemedText>
|
|
||||||
{/* <RightArrowSvg /> */}
|
|
||||||
</TouchableOpacity>
|
|
||||||
<Divider />
|
|
||||||
<View style={styles.item}>
|
|
||||||
<ThemedText style={styles.itemText}>{t('generalSetting.version', { ns: 'personal' })}</ThemedText>
|
|
||||||
<ThemedText style={styles.itemText}>{"0.5.0"}</ThemedText>
|
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
{/* 其他信息 */}
|
||||||
{/* 退出 */}
|
<View style={{ marginTop: 16 }}>
|
||||||
<TouchableOpacity style={[styles.premium, { marginVertical: 8 }]} onPress={handleLogout}>
|
<ThemedText style={{ marginLeft: 16, marginVertical: 8, color: '#AC7E35', fontSize: 14, fontWeight: '600' }}>{t('generalSetting.otherInformation', { ns: 'personal' })}</ThemedText>
|
||||||
<ThemedText style={{ color: '#E2793F', fontSize: 14, fontWeight: '600' }}>{t('generalSetting.logout', { ns: 'personal' })}</ThemedText>
|
<View style={styles.content}>
|
||||||
<LogoutSvg />
|
<TouchableOpacity style={styles.item} onPress={() => Linking.openURL("https://work.weixin.qq.com/kfid/kfca0ac87f4e05e8bfd")} >
|
||||||
</TouchableOpacity>
|
<ThemedText style={styles.itemText}>{t('generalSetting.contactUs', { ns: 'personal' })}</ThemedText>
|
||||||
{/* 注销账号 */}
|
{/* <RightArrowSvg /> */}
|
||||||
<TouchableOpacity style={[styles.premium, { marginVertical: 8 }]} onPress={() => setDeleteModalVisible(true)}>
|
</TouchableOpacity>
|
||||||
<ThemedText style={{ color: '#E2793F', fontSize: 14, fontWeight: '600' }}>{t('generalSetting.deleteAccount', { ns: 'personal' })}</ThemedText>
|
<Divider />
|
||||||
<DeleteSvg />
|
<View style={styles.item}>
|
||||||
</TouchableOpacity>
|
<ThemedText style={styles.itemText}>{t('generalSetting.version', { ns: 'personal' })}</ThemedText>
|
||||||
</ScrollView>
|
<ThemedText style={styles.itemText}>{"0.5.0"}</ThemedText>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
{/* 退出 */}
|
||||||
|
<TouchableOpacity style={[styles.premium, { marginVertical: 8 }]} onPress={handleLogout}>
|
||||||
|
<ThemedText style={{ color: '#E2793F', fontSize: 14, fontWeight: '600' }}>{t('generalSetting.logout', { ns: 'personal' })}</ThemedText>
|
||||||
|
<LogoutSvg />
|
||||||
|
</TouchableOpacity>
|
||||||
|
{/* 注销账号 */}
|
||||||
|
<TouchableOpacity style={[styles.premium, { marginVertical: 8 }]} onPress={() => setDeleteModalVisible(true)}>
|
||||||
|
<ThemedText style={{ color: '#E2793F', fontSize: 14, fontWeight: '600' }}>{t('generalSetting.deleteAccount', { ns: 'personal' })}</ThemedText>
|
||||||
|
<DeleteSvg />
|
||||||
|
</TouchableOpacity>
|
||||||
|
</ScrollView>
|
||||||
|
</Pressable>
|
||||||
</Pressable>
|
</Pressable>
|
||||||
</Pressable>
|
|
||||||
{/* 协议弹窗 */}
|
<PrivacyModal modalVisible={privacyModalVisible} setModalVisible={setPrivacyModalVisible} type={modalType} />
|
||||||
<PrivacyModal modalVisible={privacyModalVisible} setModalVisible={setPrivacyModalVisible} type={modalType} />
|
<LcensesModal modalVisible={lcensesModalVisible} setModalVisible={setLcensesModalVisible} />
|
||||||
{/* 许可证弹窗 */}
|
<DeleteModal modalVisible={deleteModalVisible} setModalVisible={setDeleteModalVisible} setSettingModalVisible={setModalVisible} />
|
||||||
<LcensesModal modalVisible={lcensesModalVisible} setModalVisible={setLcensesModalVisible} />
|
</Modal>
|
||||||
{/* 通知 */}
|
</>
|
||||||
{/* <AuthNotifications setNotificationsEnabled={setNotificationsEnabled} notificationsEnabled={notificationsEnabled} /> */}
|
|
||||||
{/* 退出登录 */}
|
|
||||||
<DeleteModal modalVisible={deleteModalVisible} setModalVisible={setDeleteModalVisible} setSettingModalVisible={setModalVisible} />
|
|
||||||
</Modal>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,12 @@
|
|||||||
// 地理位置逆编码
|
// 地理位置逆编码
|
||||||
|
import i18n from '@/i18n';
|
||||||
|
import { PermissionService } from '@/lib/PermissionService';
|
||||||
import { fetchApi } from '@/lib/server-api-util';
|
import { fetchApi } from '@/lib/server-api-util';
|
||||||
import * as ImagePicker from 'expo-image-picker';
|
import * as ImagePicker from 'expo-image-picker';
|
||||||
import * as Location from 'expo-location';
|
import * as Location from 'expo-location';
|
||||||
import * as Notifications from 'expo-notifications';
|
import * as Notifications from 'expo-notifications';
|
||||||
import * as SecureStore from 'expo-secure-store';
|
import * as SecureStore from 'expo-secure-store';
|
||||||
import { Alert, Linking, Platform } from 'react-native';
|
import { Linking, Platform } from 'react-native';
|
||||||
|
|
||||||
interface Address {
|
interface Address {
|
||||||
id: number;
|
id: number;
|
||||||
@ -69,27 +71,13 @@ export const requestLocationPermission = async () => {
|
|||||||
// 3. 如果用户之前选择了"拒绝且不再询问"
|
// 3. 如果用户之前选择了"拒绝且不再询问"
|
||||||
if (status === 'denied' && !canAskAgain) {
|
if (status === 'denied' && !canAskAgain) {
|
||||||
// 显示提示,引导用户去设置
|
// 显示提示,引导用户去设置
|
||||||
const openSettings = await new Promise(resolve => {
|
const confirmed = await PermissionService.show({
|
||||||
Alert.alert(
|
title: i18n.t('permission:title.locationPermissionRequired'),
|
||||||
'需要位置权限',
|
message: i18n.t('permission:message.locationPreviouslyDenied'),
|
||||||
'您之前拒绝了位置权限。要使用此功能,请在设置中启用位置权限。',
|
|
||||||
[
|
|
||||||
{
|
|
||||||
text: '取消',
|
|
||||||
style: 'cancel',
|
|
||||||
onPress: () => resolve(false)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: '去设置',
|
|
||||||
onPress: () => resolve(true)
|
|
||||||
}
|
|
||||||
]
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (openSettings) {
|
if (confirmed) {
|
||||||
// 打开应用设置
|
openAppSettings();
|
||||||
await Linking.openSettings();
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -100,24 +88,25 @@ export const requestLocationPermission = async () => {
|
|||||||
console.log('新权限状态:', newStatus);
|
console.log('新权限状态:', newStatus);
|
||||||
|
|
||||||
if (newStatus !== 'granted') {
|
if (newStatus !== 'granted') {
|
||||||
Alert.alert('需要位置权限', '请允许访问位置以使用此功能');
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('请求位置权限时出错:', error);
|
console.error('请求位置权限时出错:', error);
|
||||||
Alert.alert('错误', '请求位置权限时出错');
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const openAppSettings = () => {
|
||||||
|
Linking.openSettings();
|
||||||
|
};
|
||||||
|
|
||||||
// 获取媒体库权限
|
// 获取媒体库权限
|
||||||
export const getPermissions = async () => {
|
export const getPermissions = async () => {
|
||||||
if (Platform.OS !== 'web') {
|
if (Platform.OS !== 'web') {
|
||||||
const { status: mediaStatus } = await ImagePicker.getMediaLibraryPermissionsAsync();
|
const { status: mediaStatus } = await ImagePicker.getMediaLibraryPermissionsAsync();
|
||||||
if (mediaStatus !== 'granted') {
|
if (mediaStatus !== 'granted') {
|
||||||
// Alert.alert('需要媒体库权限', '请允许访问媒体库以继续');
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@ -130,7 +119,6 @@ export const requestPermissions = async () => {
|
|||||||
if (Platform.OS !== 'web') {
|
if (Platform.OS !== 'web') {
|
||||||
const mediaStatus = await ImagePicker.requestMediaLibraryPermissionsAsync();
|
const mediaStatus = await ImagePicker.requestMediaLibraryPermissionsAsync();
|
||||||
if (!mediaStatus.granted) {
|
if (!mediaStatus.granted) {
|
||||||
// Alert.alert('需要媒体库权限', '请允许访问媒体库以继续');
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@ -182,20 +170,10 @@ export const requestMediaLibraryPermission = async (showAlert: boolean = true):
|
|||||||
// 3. 如果之前被拒绝且不能再次询问
|
// 3. 如果之前被拒绝且不能再次询问
|
||||||
if (existingStatus === 'denied' && !canAskAgain) {
|
if (existingStatus === 'denied' && !canAskAgain) {
|
||||||
if (showAlert) {
|
if (showAlert) {
|
||||||
const openSettings = await new Promise<boolean>(resolve => {
|
await PermissionService.show({
|
||||||
Alert.alert(
|
title: i18n.t('permission:title.mediaLibraryPermissionRequired'),
|
||||||
'需要媒体库权限',
|
message: i18n.t('permission:message.mediaLibraryPreviouslyDenied'),
|
||||||
'您之前拒绝了媒体库访问权限。要选择照片,请在设置中启用媒体库权限。',
|
|
||||||
[
|
|
||||||
{ text: '取消', style: 'cancel', onPress: () => resolve(false) },
|
|
||||||
{ text: '去设置', onPress: () => resolve(true) }
|
|
||||||
]
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (openSettings) {
|
|
||||||
await Linking.openSettings();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -204,14 +182,20 @@ export const requestMediaLibraryPermission = async (showAlert: boolean = true):
|
|||||||
const { status: newStatus } = await ImagePicker.requestMediaLibraryPermissionsAsync();
|
const { status: newStatus } = await ImagePicker.requestMediaLibraryPermissionsAsync();
|
||||||
|
|
||||||
if (newStatus !== 'granted' && showAlert) {
|
if (newStatus !== 'granted' && showAlert) {
|
||||||
Alert.alert('需要媒体库权限', '请允许访问媒体库以方便后续操作');
|
await PermissionService.show({
|
||||||
|
title: i18n.t('permission:title.mediaLibraryPermissionRequired'),
|
||||||
|
message: i18n.t('permission:message.mediaLibraryPermissionRequired'),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return newStatus === 'granted';
|
return newStatus === 'granted';
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('请求媒体库权限时出错:', error);
|
console.error('请求媒体库权限时出错:', error);
|
||||||
if (showAlert) {
|
if (showAlert) {
|
||||||
Alert.alert('错误', '请求媒体库权限时出错');
|
await PermissionService.show({
|
||||||
|
title: i18n.t('permission:title.error'),
|
||||||
|
message: i18n.t('permission:message.requestPermissionError'),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -240,28 +224,10 @@ export const requestNotificationPermission = async () => {
|
|||||||
// 3. 如果用户之前选择了"拒绝且不再询问"
|
// 3. 如果用户之前选择了"拒绝且不再询问"
|
||||||
if (status === 'denied' && !canAskAgain) {
|
if (status === 'denied' && !canAskAgain) {
|
||||||
// 显示提示,引导用户去设置
|
// 显示提示,引导用户去设置
|
||||||
const openSettings = await new Promise(resolve => {
|
await PermissionService.show({
|
||||||
Alert.alert(
|
title: i18n.t('permission:title.notificationPermissionRequired'),
|
||||||
'需要通知权限',
|
message: i18n.t('permission:message.notificationPreviouslyDenied'),
|
||||||
'您之前拒绝了通知权限。要使用此功能,请在设置中启用通知权限。',
|
|
||||||
[
|
|
||||||
{
|
|
||||||
text: '取消',
|
|
||||||
style: 'cancel',
|
|
||||||
onPress: () => resolve(false)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: '去设置',
|
|
||||||
onPress: () => resolve(true)
|
|
||||||
}
|
|
||||||
]
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (openSettings) {
|
|
||||||
// 打开应用设置
|
|
||||||
await Linking.openSettings();
|
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -271,14 +237,17 @@ export const requestNotificationPermission = async () => {
|
|||||||
console.log('新通知权限状态:', newStatus);
|
console.log('新通知权限状态:', newStatus);
|
||||||
|
|
||||||
if (newStatus !== 'granted') {
|
if (newStatus !== 'granted') {
|
||||||
Alert.alert('需要通知权限', '请允许通知以使用此功能');
|
PermissionService.show({
|
||||||
|
title: '需要通知权限',
|
||||||
|
message: '请允许通知以使用此功能',
|
||||||
|
});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('请求通知权限时出错:', error);
|
console.error('请求通知权限时出错:', error);
|
||||||
Alert.alert('错误', '请求通知权限时出错');
|
PermissionService.show({ title: '错误', message: '请求通知权限时出错' });
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
82
context/PermissionContext.tsx
Normal file
82
context/PermissionContext.tsx
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
import PermissionAlert from '@/components/common/PermissionAlert';
|
||||||
|
import i18n from '@/i18n';
|
||||||
|
import { PermissionService } from '@/lib/PermissionService';
|
||||||
|
import React, { createContext, ReactNode, useCallback, useContext, useEffect, useState } from 'react';
|
||||||
|
import { Linking } from 'react-native';
|
||||||
|
|
||||||
|
interface PermissionAlertOptions {
|
||||||
|
title: string;
|
||||||
|
message: string;
|
||||||
|
confirmText?: string;
|
||||||
|
cancelText?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PermissionContextType {
|
||||||
|
showPermissionAlert: (options: PermissionAlertOptions) => Promise<boolean>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AlertData {
|
||||||
|
options: PermissionAlertOptions;
|
||||||
|
resolve: (value: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PermissionContext = createContext<PermissionContextType | undefined>(undefined);
|
||||||
|
|
||||||
|
export const PermissionProvider = ({ children }: { children: ReactNode }) => {
|
||||||
|
const [alertData, setAlertData] = useState<AlertData | null>(null);
|
||||||
|
|
||||||
|
const showPermissionAlert = useCallback((options: PermissionAlertOptions) => {
|
||||||
|
return new Promise<boolean>((resolve) => {
|
||||||
|
setAlertData({ options, resolve });
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
PermissionService.set(showPermissionAlert);
|
||||||
|
|
||||||
|
// Cleanup on unmount
|
||||||
|
return () => {
|
||||||
|
PermissionService.set(null as any); // or a no-op function
|
||||||
|
};
|
||||||
|
}, [showPermissionAlert]);
|
||||||
|
|
||||||
|
const handleConfirm = () => {
|
||||||
|
Linking.openSettings();
|
||||||
|
if (alertData?.resolve) {
|
||||||
|
alertData.resolve(true);
|
||||||
|
}
|
||||||
|
setAlertData(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
if (alertData?.resolve) {
|
||||||
|
alertData.resolve(false);
|
||||||
|
}
|
||||||
|
setAlertData(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PermissionContext.Provider value={{ showPermissionAlert }}>
|
||||||
|
{children}
|
||||||
|
{alertData && (
|
||||||
|
<PermissionAlert
|
||||||
|
visible={!!alertData}
|
||||||
|
title={alertData.options.title}
|
||||||
|
message={alertData.options.message}
|
||||||
|
confirmText={alertData.options.confirmText || i18n.t('permission:button.confirm')}
|
||||||
|
cancelText={alertData.options.cancelText || i18n.t('permission:button.cancel')}
|
||||||
|
onConfirm={handleConfirm}
|
||||||
|
onCancel={handleCancel}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</PermissionContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const usePermission = (): PermissionContextType => {
|
||||||
|
const context = useContext(PermissionContext);
|
||||||
|
if (!context) {
|
||||||
|
throw new Error('usePermission must be used within a PermissionProvider');
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
};
|
||||||
@ -7,6 +7,7 @@ import * as path from 'path';
|
|||||||
|
|
||||||
function generateImports() {
|
function generateImports() {
|
||||||
const localesPath = path.join(__dirname, 'locales');
|
const localesPath = path.join(__dirname, 'locales');
|
||||||
|
const namespaces = ['common', 'home', 'login', 'settings', 'upload', 'chat', 'me', 'permission'];
|
||||||
const languages = fs.readdirSync(localesPath);
|
const languages = fs.readdirSync(localesPath);
|
||||||
let imports = '';
|
let imports = '';
|
||||||
let translationsMap = 'const translations = {\n';
|
let translationsMap = 'const translations = {\n';
|
||||||
|
|||||||
@ -32,7 +32,7 @@ i18n
|
|||||||
resources: translations,
|
resources: translations,
|
||||||
|
|
||||||
// 支持命名空间
|
// 支持命名空间
|
||||||
ns: ['common', 'example', 'download'],
|
ns: ['common', 'example', 'download', 'permission'],
|
||||||
defaultNS: 'common',
|
defaultNS: 'common',
|
||||||
|
|
||||||
// 设置默认语言为中文
|
// 设置默认语言为中文
|
||||||
@ -96,14 +96,16 @@ export const preloadCommonTranslations = async () => {
|
|||||||
// 预加载 common 和 example 命名空间
|
// 预加载 common 和 example 命名空间
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
loadNamespaceForLanguage(currentLng, 'common'),
|
loadNamespaceForLanguage(currentLng, 'common'),
|
||||||
loadNamespaceForLanguage(currentLng, 'example')
|
loadNamespaceForLanguage(currentLng, 'example'),
|
||||||
|
loadNamespaceForLanguage(currentLng, 'permission')
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// 如果当前语言不是英语,也预加载英语作为备用
|
// 如果当前语言不是英语,也预加载英语作为备用
|
||||||
if (currentLng !== 'en') {
|
if (currentLng !== 'en') {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
loadNamespaceForLanguage('en', 'common'),
|
loadNamespaceForLanguage('en', 'common'),
|
||||||
loadNamespaceForLanguage('en', 'example')
|
loadNamespaceForLanguage('en', 'example'),
|
||||||
|
loadNamespaceForLanguage('en', 'permission')
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -17,7 +17,9 @@
|
|||||||
"login": "Login",
|
"login": "Login",
|
||||||
"trade": "沪ICP备2025133004号-2A",
|
"trade": "沪ICP备2025133004号-2A",
|
||||||
"logout": "Logout",
|
"logout": "Logout",
|
||||||
"self": "Personal Center"
|
"self": "Personal Center",
|
||||||
|
"goToSettings": "Go to Settings",
|
||||||
|
"cancel": "Cancel"
|
||||||
},
|
},
|
||||||
"welcome": {
|
"welcome": {
|
||||||
"welcome": "Welcome to MemoWake~",
|
"welcome": "Welcome to MemoWake~",
|
||||||
|
|||||||
29
i18n/locales/en/permission.json
Normal file
29
i18n/locales/en/permission.json
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"title": {
|
||||||
|
"permissionDenied": "Permission Denied",
|
||||||
|
"locationPermissionRequired": "Location Permission Required",
|
||||||
|
"mediaLibraryPermissionRequired": "Media Library Permission Required",
|
||||||
|
"notificationPermissionRequired": "Notification Permission Required",
|
||||||
|
"success": "✅ Success",
|
||||||
|
"error": "❌ Error",
|
||||||
|
"getMediaFailed": "Failed to Get Media"
|
||||||
|
},
|
||||||
|
"message": {
|
||||||
|
"locationPreviouslyDenied": "You have previously denied location permissions. To use this feature, please enable it in settings.",
|
||||||
|
"mediaLibraryPreviouslyDenied": "You have previously denied media library permissions. To use this feature, please enable it in settings.",
|
||||||
|
"notificationPreviouslyDenied": "You have previously denied notification permissions. To use this feature, please enable it in settings.",
|
||||||
|
"saveToAlbumPermissionRequired": "Permission is required to save images to the album.",
|
||||||
|
"qrCodeSaved": "QR code has been saved to the album!",
|
||||||
|
"saveImageFailed": "Failed to save the image, please try again.",
|
||||||
|
"getStatsPermissionRequired": "Permission to access the media library is required to get statistics.",
|
||||||
|
"getStatsFailed": "Failed to get media library statistics.",
|
||||||
|
"noMediaFound": "Could not retrieve any media. Please check permissions or your media library.",
|
||||||
|
"uploadError": "An error occurred during the upload process."
|
||||||
|
},
|
||||||
|
"button": {
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"goToSettings": "Go to Settings",
|
||||||
|
"ok": "OK",
|
||||||
|
"confirm": "Go to Settings"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -17,7 +17,9 @@
|
|||||||
"login": "登录",
|
"login": "登录",
|
||||||
"trade": "沪ICP备2025133004号-2A",
|
"trade": "沪ICP备2025133004号-2A",
|
||||||
"logout": "退出登录",
|
"logout": "退出登录",
|
||||||
"self": "个人中心"
|
"self": "个人中心",
|
||||||
|
"goToSettings": "去设置",
|
||||||
|
"cancel": "取消"
|
||||||
},
|
},
|
||||||
"welcome": {
|
"welcome": {
|
||||||
"welcome": "欢迎来到 MemoWake~",
|
"welcome": "欢迎来到 MemoWake~",
|
||||||
|
|||||||
29
i18n/locales/zh/permission.json
Normal file
29
i18n/locales/zh/permission.json
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"title": {
|
||||||
|
"permissionDenied": "权限被拒绝",
|
||||||
|
"locationPermissionRequired": "需要位置权限",
|
||||||
|
"mediaLibraryPermissionRequired": "需要媒体库权限",
|
||||||
|
"notificationPermissionRequired": "需要通知权限",
|
||||||
|
"success": "✅ 成功",
|
||||||
|
"error": "❌ 失败",
|
||||||
|
"getMediaFailed": "获取媒体资源失败"
|
||||||
|
},
|
||||||
|
"message": {
|
||||||
|
"locationPreviouslyDenied": "您之前拒绝了位置权限。要使用此功能,请在设置中启用位置权限。",
|
||||||
|
"mediaLibraryPreviouslyDenied": "您之前拒绝了媒体库权限。要使用此功能,请在设置中启用它。",
|
||||||
|
"notificationPreviouslyDenied": "您之前拒绝了通知权限。要使用此功能,请在设置中启用它。",
|
||||||
|
"saveToAlbumPermissionRequired": "需要保存图片到相册的权限",
|
||||||
|
"qrCodeSaved": "二维码已保存到相册!",
|
||||||
|
"saveImageFailed": "无法保存图片,请重试",
|
||||||
|
"getStatsPermissionRequired": "需要访问媒体库权限来获取统计信息",
|
||||||
|
"getStatsFailed": "获取媒体库统计信息失败",
|
||||||
|
"noMediaFound": "未能获取到任何媒体资源,请检查权限或媒体库。",
|
||||||
|
"uploadError": "上传过程中出现错误。"
|
||||||
|
},
|
||||||
|
"button": {
|
||||||
|
"cancel": "取消",
|
||||||
|
"goToSettings": "去设置",
|
||||||
|
"ok": "好的",
|
||||||
|
"confirm": "去设置"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -4,23 +4,25 @@ import enAdmin from './locales/en/admin.json';
|
|||||||
import enAsk from './locales/en/ask.json';
|
import enAsk from './locales/en/ask.json';
|
||||||
import enCommon from './locales/en/common.json';
|
import enCommon from './locales/en/common.json';
|
||||||
import enDownload from './locales/en/download.json';
|
import enDownload from './locales/en/download.json';
|
||||||
import enSupport from './locales/en/support.json';
|
|
||||||
import enExample from './locales/en/example.json';
|
import enExample from './locales/en/example.json';
|
||||||
import enFairclip from './locales/en/fairclip.json';
|
import enFairclip from './locales/en/fairclip.json';
|
||||||
import enLanding from './locales/en/landing.json';
|
import enLanding from './locales/en/landing.json';
|
||||||
import enLogin from './locales/en/login.json';
|
import enLogin from './locales/en/login.json';
|
||||||
|
import enPermission from './locales/en/permission.json';
|
||||||
import enPersonal from './locales/en/personal.json';
|
import enPersonal from './locales/en/personal.json';
|
||||||
|
import enSupport from './locales/en/support.json';
|
||||||
import enUpload from './locales/en/upload.json';
|
import enUpload from './locales/en/upload.json';
|
||||||
import zhAdmin from './locales/zh/admin.json';
|
import zhAdmin from './locales/zh/admin.json';
|
||||||
import zhAsk from './locales/zh/ask.json';
|
import zhAsk from './locales/zh/ask.json';
|
||||||
import zhCommon from './locales/zh/common.json';
|
import zhCommon from './locales/zh/common.json';
|
||||||
import zhDownload from './locales/zh/download.json';
|
import zhDownload from './locales/zh/download.json';
|
||||||
import zhSupport from './locales/zh/support.json';
|
|
||||||
import zhExample from './locales/zh/example.json';
|
import zhExample from './locales/zh/example.json';
|
||||||
import zhFairclip from './locales/zh/fairclip.json';
|
import zhFairclip from './locales/zh/fairclip.json';
|
||||||
import zhLanding from './locales/zh/landing.json';
|
import zhLanding from './locales/zh/landing.json';
|
||||||
import zhLogin from './locales/zh/login.json';
|
import zhLogin from './locales/zh/login.json';
|
||||||
|
import zhPermission from './locales/zh/permission.json';
|
||||||
import zhPersonal from './locales/zh/personal.json';
|
import zhPersonal from './locales/zh/personal.json';
|
||||||
|
import zhSupport from './locales/zh/support.json';
|
||||||
import zhUpload from './locales/zh/upload.json';
|
import zhUpload from './locales/zh/upload.json';
|
||||||
|
|
||||||
const translations = {
|
const translations = {
|
||||||
@ -29,12 +31,13 @@ const translations = {
|
|||||||
ask: enAsk,
|
ask: enAsk,
|
||||||
common: enCommon,
|
common: enCommon,
|
||||||
download: enDownload,
|
download: enDownload,
|
||||||
support: enSupport,
|
|
||||||
example: enExample,
|
example: enExample,
|
||||||
fairclip: enFairclip,
|
fairclip: enFairclip,
|
||||||
landing: enLanding,
|
landing: enLanding,
|
||||||
login: enLogin,
|
login: enLogin,
|
||||||
|
permission: enPermission,
|
||||||
personal: enPersonal,
|
personal: enPersonal,
|
||||||
|
support: enSupport,
|
||||||
upload: enUpload
|
upload: enUpload
|
||||||
},
|
},
|
||||||
zh: {
|
zh: {
|
||||||
@ -42,12 +45,13 @@ const translations = {
|
|||||||
ask: zhAsk,
|
ask: zhAsk,
|
||||||
common: zhCommon,
|
common: zhCommon,
|
||||||
download: zhDownload,
|
download: zhDownload,
|
||||||
support: zhSupport,
|
|
||||||
example: zhExample,
|
example: zhExample,
|
||||||
fairclip: zhFairclip,
|
fairclip: zhFairclip,
|
||||||
landing: zhLanding,
|
landing: zhLanding,
|
||||||
login: zhLogin,
|
login: zhLogin,
|
||||||
|
permission: zhPermission,
|
||||||
personal: zhPersonal,
|
personal: zhPersonal,
|
||||||
|
support: zhSupport,
|
||||||
upload: zhUpload
|
upload: zhUpload
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
21
lib/PermissionService.ts
Normal file
21
lib/PermissionService.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
interface PermissionAlertOptions {
|
||||||
|
title: string;
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type ShowPermissionAlertFunction = (options: PermissionAlertOptions) => Promise<boolean>;
|
||||||
|
|
||||||
|
let showPermissionAlertRef: ShowPermissionAlertFunction | null = null;
|
||||||
|
|
||||||
|
export const PermissionService = {
|
||||||
|
set: (fn: ShowPermissionAlertFunction) => {
|
||||||
|
showPermissionAlertRef = fn;
|
||||||
|
},
|
||||||
|
show: (options: PermissionAlertOptions): Promise<boolean> => {
|
||||||
|
if (!showPermissionAlertRef) {
|
||||||
|
console.error("PermissionAlert has not been set. Please ensure PermissionProvider is used at the root of your app.");
|
||||||
|
return Promise.resolve(false);
|
||||||
|
}
|
||||||
|
return showPermissionAlertRef(options);
|
||||||
|
},
|
||||||
|
};
|
||||||
@ -1,5 +1,6 @@
|
|||||||
import pLimit from 'p-limit';
|
import pLimit from 'p-limit';
|
||||||
import { Alert } from 'react-native';
|
import { PermissionService } from '../PermissionService';
|
||||||
|
import i18n from '@/i18n';
|
||||||
import { getUploadTaskStatus, insertUploadTask } from '../db';
|
import { getUploadTaskStatus, insertUploadTask } from '../db';
|
||||||
import { getMediaByDateRange } from './media';
|
import { getMediaByDateRange } from './media';
|
||||||
import { ExtendedAsset } from './types';
|
import { ExtendedAsset } from './types';
|
||||||
@ -24,7 +25,7 @@ export const triggerManualUpload = async (
|
|||||||
try {
|
try {
|
||||||
const media = await getMediaByDateRange(startDate, endDate);
|
const media = await getMediaByDateRange(startDate, endDate);
|
||||||
if (media.length === 0) {
|
if (media.length === 0) {
|
||||||
Alert.alert('提示', '在指定时间范围内未找到媒体文件');
|
PermissionService.show({ title: i18n.t('permission:title.getMediaFailed'), message: i18n.t('permission:message.noMediaFound') });
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,7 +77,7 @@ export const triggerManualUpload = async (
|
|||||||
return finalResults;
|
return finalResults;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('手动上传过程中出现错误:', error);
|
console.error('手动上传过程中出现错误:', error);
|
||||||
Alert.alert('错误', '上传过程中出现错误');
|
PermissionService.show({ title: i18n.t('permission:title.error'), message: i18n.t('permission:message.uploadError') });
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Loading…
x
Reference in New Issue
Block a user