mirror of
https://github.com/mudabbir-ahmad/Media-Aggregation-and-Sorting-System.git
synced 2026-10-07 19:40:20 +00:00
feat: refactor service glyphs and improve loading states; replace inline components with CenteredState and ServiceGlyph
This commit is contained in:
1 parent
bb1eb375ad
commit
126f545c44
9 files changed
+62
-138
No files matched your search
@@ -3,13 +3,13 @@ import { StatusBar } from 'expo-status-bar';
|
||||
import { NavigationContainer } from '@react-navigation/native';
|
||||
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
||||
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
|
||||
import { ActivityIndicator, View, Text, TouchableOpacity } from 'react-native';
|
||||
import { registerRootComponent } from 'expo';
|
||||
import * as WebBrowser from 'expo-web-browser';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import { ThemeProvider, useTheme } from './src/services/ThemeContext';
|
||||
import DatabaseService from './src/services/DatabaseService';
|
||||
import { getStoredUserId } from './src/services/AuthSession';
|
||||
import CenteredState from './src/components/CenteredState';
|
||||
import LoginScreen from './src/screens/LoginScreen';
|
||||
import RegisterScreen from './src/screens/RegisterScreen';
|
||||
import OnboardingScreen from './src/screens/OnboardingScreen';
|
||||
@@ -32,6 +32,7 @@ function MainTabs({ route }) {
|
||||
|
||||
return (
|
||||
<Tab.Navigator
|
||||
id="MainTabsNavigator"
|
||||
screenOptions={{
|
||||
headerShown: false,
|
||||
tabBarActiveTintColor: theme.tabActive,
|
||||
@@ -104,24 +105,18 @@ function AppInner() {
|
||||
};
|
||||
|
||||
if ((!isReady || !bootChecked) && !error) {
|
||||
return (
|
||||
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: theme.background }}>
|
||||
<ActivityIndicator size="large" color={theme.primary} />
|
||||
</View>
|
||||
);
|
||||
return <CenteredState loading title="Loading app…" containerStyle={{ backgroundColor: theme.background }} />;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center', paddingHorizontal: 20, backgroundColor: theme.background }}>
|
||||
<Text style={{ fontSize: 16, color: theme.text, marginBottom: 12, textAlign: 'center' }}>{error}</Text>
|
||||
<TouchableOpacity
|
||||
onPress={initializeApp}
|
||||
style={{ backgroundColor: theme.primary, paddingHorizontal: 16, paddingVertical: 10, borderRadius: 6 }}
|
||||
>
|
||||
<Text style={{ color: '#fff', fontWeight: '600' }}>Retry</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<CenteredState
|
||||
title="Unable to start app"
|
||||
description={error}
|
||||
actionLabel="Retry"
|
||||
onAction={initializeApp}
|
||||
containerStyle={{ backgroundColor: theme.background }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -129,6 +124,7 @@ function AppInner() {
|
||||
<>
|
||||
<NavigationContainer>
|
||||
<Stack.Navigator
|
||||
id="RootStackNavigator"
|
||||
screenOptions={{ headerShown: false }}
|
||||
initialRouteName={bootUserId ? 'MainTabs' : 'Login'}
|
||||
>
|
||||
@@ -143,7 +139,7 @@ function AppInner() {
|
||||
<Stack.Screen name="Settings" component={SettingsScreen} />
|
||||
</Stack.Navigator>
|
||||
</NavigationContainer>
|
||||
<StatusBar style={theme.statusBarStyle} backgroundColor={theme.background} />
|
||||
<StatusBar style={theme.statusBarStyle} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -158,4 +154,3 @@ function App() {
|
||||
|
||||
registerRootComponent(App);
|
||||
|
||||
export default App;
|
||||
@@ -20,7 +20,6 @@
|
||||
"expo-blur": "~55.0.14",
|
||||
"expo-crypto": "~55.0.14",
|
||||
"expo-file-system": "~55.0.16",
|
||||
"expo-image-picker": "~55.0.18",
|
||||
"expo-linking": "~55.0.13",
|
||||
"expo-media-library": "~55.0.14",
|
||||
"expo-secure-store": "~55.0.13",
|
||||
@@ -28,7 +27,6 @@
|
||||
"expo-status-bar": "~55.0.5",
|
||||
"expo-system-ui": "^55.0.15",
|
||||
"expo-web-browser": "~55.0.14",
|
||||
"metro-react-native-babel-transformer": "^0.77.0",
|
||||
"react": "19.2.0",
|
||||
"react-native": "0.83.4",
|
||||
"react-native-gesture-handler": "~2.30.0",
|
||||
|
||||
@@ -4,22 +4,13 @@ import {
|
||||
} from 'react-native';
|
||||
import { useTheme } from '../services/ThemeContext';
|
||||
import MediaMetadataService from '../services/MediaMetadataService';
|
||||
import ServiceGlyph from './ServiceGlyph';
|
||||
import CameraIcon from '../../assets/icons/camera.svg';
|
||||
import CheckIcon from '../../assets/icons/check.svg';
|
||||
import DeviceIcon from '../../assets/icons/device.svg';
|
||||
import GooglePhotosIcon from '../../assets/icons/google-photos.svg';
|
||||
import ICloudIcon from '../../assets/icons/icloud.svg';
|
||||
import NasIcon from '../../assets/icons/nas.svg';
|
||||
|
||||
const { width } = Dimensions.get('window');
|
||||
const GRID_GAP = 2;
|
||||
|
||||
const ServiceGlyph = ({ serviceType, size }) => {
|
||||
if (serviceType === 'GOOGLE_PHOTOS') return <GooglePhotosIcon width={size} height={size} />;
|
||||
if (serviceType === 'ICLOUD_PHOTOS') return <ICloudIcon width={size} height={size} />;
|
||||
if (serviceType === 'NAS') return <NasIcon width={size} height={size} />;
|
||||
return <DeviceIcon width={size} height={size} />;
|
||||
};
|
||||
|
||||
const PhotoGrid = ({
|
||||
data,
|
||||
|
||||
+13
-30
@@ -1,21 +1,19 @@
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import {
|
||||
StyleSheet, View, Text, SectionList, FlatList, TouchableOpacity,
|
||||
Dimensions, Image, ActivityIndicator, RefreshControl,
|
||||
Dimensions, Image, RefreshControl,
|
||||
} from 'react-native';
|
||||
import { useFocusEffect } from '@react-navigation/native';
|
||||
import { useTheme } from '../services/ThemeContext';
|
||||
import DatabaseService from '../services/DatabaseService';
|
||||
import MediaMetadataService from '../services/MediaMetadataService';
|
||||
import CenteredState from '../components/CenteredState';
|
||||
import MediaViewer from '../components/MediaViewer';
|
||||
import ServiceGlyph from '../components/ServiceGlyph';
|
||||
import CameraIcon from '../../assets/icons/camera.svg';
|
||||
import EmptyPhotoIcon from '../../assets/icons/empty-photo.svg';
|
||||
import FolderIcon from '../../assets/icons/folder.svg';
|
||||
import FolderOpenIcon from '../../assets/icons/folder-open.svg';
|
||||
import DeviceIcon from '../../assets/icons/device.svg';
|
||||
import GooglePhotosIcon from '../../assets/icons/google-photos.svg';
|
||||
import ICloudIcon from '../../assets/icons/icloud.svg';
|
||||
import NasIcon from '../../assets/icons/nas.svg';
|
||||
import HiddenIcon from '../../assets/icons/hidden.svg';
|
||||
import DuplicatesIcon from '../../assets/icons/duplicates.svg';
|
||||
import TrashIcon from '../../assets/icons/trash.svg';
|
||||
@@ -26,12 +24,6 @@ const ALBUM_COLUMNS = 2;
|
||||
const ALBUM_GAP = 12;
|
||||
const albumSize = (width - ALBUM_GAP * (ALBUM_COLUMNS + 1)) / ALBUM_COLUMNS;
|
||||
const SERVICE_NAMES = { DEVICE: 'On Device', GOOGLE_PHOTOS: 'Google Photos', ICLOUD_PHOTOS: 'iCloud Photos', NAS: 'NAS Storage' };
|
||||
const ServiceGlyph = ({ serviceType, size = 20 }) => {
|
||||
if (serviceType === 'GOOGLE_PHOTOS') return <GooglePhotosIcon width={size} height={size} />;
|
||||
if (serviceType === 'ICLOUD_PHOTOS') return <ICloudIcon width={size} height={size} />;
|
||||
if (serviceType === 'NAS') return <NasIcon width={size} height={size} />;
|
||||
return <DeviceIcon width={size} height={size} />;
|
||||
};
|
||||
|
||||
const UtilityGlyph = ({ name, size = 20, color = '#8B949E' }) => {
|
||||
if (name === 'hidden') return <HiddenIcon width={size} height={size} color={color} />;
|
||||
@@ -145,10 +137,10 @@ const CollectionsScreen = ({ route }) => {
|
||||
</View>
|
||||
</View>
|
||||
{albumMedia.length === 0 ? (
|
||||
<View style={styles.centerContainer}>
|
||||
<UtilityGlyph name="emptyMail" size={48} />
|
||||
<Text style={[styles.emptyTitle, { color: theme.text }]}>No items in this collection</Text>
|
||||
</View>
|
||||
<CenteredState
|
||||
icon={<UtilityGlyph name="emptyMail" size={48} />}
|
||||
title="No items in this collection"
|
||||
/>
|
||||
) : (
|
||||
<FlatList
|
||||
data={albumMedia}
|
||||
@@ -179,10 +171,7 @@ const CollectionsScreen = ({ route }) => {
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<View style={[styles.centerContainer, { backgroundColor: theme.background }]}>
|
||||
<ActivityIndicator size="large" color={theme.primary} />
|
||||
<Text style={[styles.loadingText, { color: theme.textSecondary }]}>Loading collections...</Text>
|
||||
</View>
|
||||
<CenteredState loading title="Loading collections…" containerStyle={{ backgroundColor: theme.background }} />
|
||||
);
|
||||
}
|
||||
|
||||
@@ -243,13 +232,11 @@ const CollectionsScreen = ({ route }) => {
|
||||
|
||||
{sections.length === 0
|
||||
|| !sections.some((sec) => sec.data?.[0]?.albums?.some((a) => (a.count || 0) > 0)) ? (
|
||||
<View style={styles.centerContainer}>
|
||||
<FolderIcon width={48} height={48} color="#8B949E" />
|
||||
<Text style={[styles.emptyTitle, { color: theme.text }]}>No Collections</Text>
|
||||
<Text style={[styles.emptyText, { color: theme.textSecondary }]}>
|
||||
Collections from your connected services will appear here
|
||||
</Text>
|
||||
</View>
|
||||
<CenteredState
|
||||
icon={<FolderIcon width={48} height={48} color="#8B949E" />}
|
||||
title="No Collections"
|
||||
description="Collections from your connected services will appear here"
|
||||
/>
|
||||
) : (
|
||||
<SectionList
|
||||
sections={sections}
|
||||
@@ -269,7 +256,6 @@ const CollectionsScreen = ({ route }) => {
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: { flex: 1 },
|
||||
centerContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', paddingHorizontal: 24 },
|
||||
header: { paddingHorizontal: 16, paddingTop: 52, paddingBottom: 12 },
|
||||
headerTitle: { fontSize: 24, fontWeight: '700' },
|
||||
listContent: { paddingHorizontal: ALBUM_GAP, paddingBottom: 20 },
|
||||
@@ -299,9 +285,6 @@ const styles = StyleSheet.create({
|
||||
gridItem: { margin: 1 },
|
||||
gridImage: { width: '100%', height: '100%' },
|
||||
gridPlaceholder: { width: '100%', height: '100%', justifyContent: 'center', alignItems: 'center' },
|
||||
loadingText: { marginTop: 12, fontSize: 14 },
|
||||
emptyTitle: { fontSize: 18, fontWeight: '600', marginBottom: 6 },
|
||||
emptyText: { fontSize: 13, textAlign: 'center' },
|
||||
});
|
||||
|
||||
export default CollectionsScreen;
|
||||
+17
-39
@@ -9,6 +9,9 @@ import DeviceMediaService from '../services/DeviceMediaService';
|
||||
import GoogleAUTH from '../AUTH/GoogleAUTH';
|
||||
import NASAUTH from '../AUTH/NASAUTH';
|
||||
import { File } from 'expo-file-system';
|
||||
import CenteredState from '../components/CenteredState';
|
||||
import MediaSortControls from '../components/MediaSortControls';
|
||||
import ServiceGlyph from '../components/ServiceGlyph';
|
||||
import TopBar from '../components/TopBar';
|
||||
import PhotoGrid from '../components/PhotoGrid';
|
||||
import MediaViewer from '../components/MediaViewer';
|
||||
@@ -18,18 +21,8 @@ import MediaMetadataService from '../services/MediaMetadataService';
|
||||
import { logError } from '../services/ErrorHandler';
|
||||
import CameraIcon from '../../assets/icons/camera.svg';
|
||||
import EmptyPhotoIcon from '../../assets/icons/empty-photo.svg';
|
||||
import DeviceIcon from '../../assets/icons/device.svg';
|
||||
import GooglePhotosIcon from '../../assets/icons/google-photos.svg';
|
||||
import ICloudIcon from '../../assets/icons/icloud.svg';
|
||||
import NasIcon from '../../assets/icons/nas.svg';
|
||||
|
||||
const SERVICE_NAMES = { DEVICE: 'On Device', GOOGLE_PHOTOS: 'Google Photos', ICLOUD_PHOTOS: 'iCloud', NAS: 'NAS' };
|
||||
const ServiceGlyph = ({ serviceType, size = 14 }) => {
|
||||
if (serviceType === 'GOOGLE_PHOTOS') return <GooglePhotosIcon width={size} height={size} />;
|
||||
if (serviceType === 'ICLOUD_PHOTOS') return <ICloudIcon width={size} height={size} />;
|
||||
if (serviceType === 'NAS') return <NasIcon width={size} height={size} />;
|
||||
return <DeviceIcon width={size} height={size} />;
|
||||
};
|
||||
|
||||
/** Keeps device library timestamps and aligns `created_date` with resolved capture time after merges. */
|
||||
const mergeCaptureFields = (target, donor) => {
|
||||
@@ -270,10 +263,10 @@ const HomeScreen = ({ navigation, route }) => {
|
||||
}
|
||||
};
|
||||
|
||||
const refreshMedia = useCallback(() => {
|
||||
const refreshMedia = () => {
|
||||
setRefreshing(true);
|
||||
loadAllMedia();
|
||||
}, [loadAllMedia]);
|
||||
};
|
||||
|
||||
const importGooglePhotos = async () => {
|
||||
const googleAccount = linkedAccounts.find((a) => a.service_type === 'GOOGLE_PHOTOS');
|
||||
@@ -509,10 +502,7 @@ const HomeScreen = ({ navigation, route }) => {
|
||||
|
||||
if (loading && allMedia.length === 0) {
|
||||
return (
|
||||
<View style={[styles.centerContainer, { backgroundColor: theme.background }]}>
|
||||
<ActivityIndicator size="large" color={theme.primary} />
|
||||
<Text style={[styles.loadingText, { color: theme.textSecondary }]}>Loading photos...</Text>
|
||||
</View>
|
||||
<CenteredState loading title="Loading photos…" containerStyle={{ backgroundColor: theme.background }} />
|
||||
);
|
||||
}
|
||||
|
||||
@@ -556,6 +546,7 @@ const HomeScreen = ({ navigation, route }) => {
|
||||
onMenuPress={() => navigation.navigate('Settings', { userId })}
|
||||
/>
|
||||
|
||||
<MediaSortControls value={sortMode} onChange={setSortMode} />
|
||||
<FilterChips />
|
||||
|
||||
<SelectionToolbar
|
||||
@@ -584,23 +575,16 @@ const HomeScreen = ({ navigation, route }) => {
|
||||
)}
|
||||
|
||||
{filteredMedia.length === 0 ? (
|
||||
<View style={styles.centerContainer}>
|
||||
<View style={{ marginBottom: 16 }}>
|
||||
<EmptyPhotoIcon width={56} height={56} color="#9CA3AF" />
|
||||
</View>
|
||||
<Text style={[styles.emptyTitle, { color: theme.text }]}>No Photos Yet</Text>
|
||||
<Text style={[styles.emptyText, { color: theme.textSecondary }]}>
|
||||
{hasGoogleAccount
|
||||
? 'Tap "Import from Google Photos" above to select cloud photos,\nor grant media library permission to see on-device photos.'
|
||||
: 'Grant media library permission or take some photos to get started'}
|
||||
</Text>
|
||||
<TouchableOpacity
|
||||
style={[styles.retryButton, { backgroundColor: theme.primary }]}
|
||||
onPress={refreshMedia}
|
||||
>
|
||||
<Text style={styles.retryButtonText}>Refresh</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<CenteredState
|
||||
icon={<EmptyPhotoIcon width={56} height={56} color="#9CA3AF" />}
|
||||
title="No Photos Yet"
|
||||
description={hasGoogleAccount
|
||||
? 'Tap "Import from Google Photos" above to select cloud photos, or grant media library permission to see on-device photos.'
|
||||
: 'Grant media library permission or take some photos to get started'}
|
||||
actionLabel="Refresh"
|
||||
onAction={refreshMedia}
|
||||
containerStyle={{ backgroundColor: theme.background }}
|
||||
/>
|
||||
) : (
|
||||
<PhotoGrid
|
||||
data={sortMode === 'date_taken' ? [] : filteredMedia}
|
||||
@@ -645,8 +629,6 @@ const HomeScreen = ({ navigation, route }) => {
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: { flex: 1 },
|
||||
centerContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', paddingHorizontal: 24 },
|
||||
loadingText: { marginTop: 12, fontSize: 14 },
|
||||
filterContainer: { maxHeight: 40 },
|
||||
filterRow: { paddingHorizontal: 12, paddingVertical: 6, gap: 6, alignItems: 'center' },
|
||||
chip: {
|
||||
@@ -660,10 +642,6 @@ const styles = StyleSheet.create({
|
||||
borderRadius: 12, gap: 8, minHeight: 42,
|
||||
},
|
||||
importText: { color: '#fff', fontSize: 14, fontWeight: '600' },
|
||||
emptyTitle: { fontSize: 18, fontWeight: '600', marginBottom: 6 },
|
||||
emptyText: { fontSize: 13, textAlign: 'center', marginBottom: 20, lineHeight: 20 },
|
||||
retryButton: { paddingHorizontal: 24, paddingVertical: 10, borderRadius: 20 },
|
||||
retryButtonText: { color: '#fff', fontSize: 14, fontWeight: '600' },
|
||||
});
|
||||
|
||||
export default HomeScreen;
|
||||
|
||||
@@ -9,11 +9,8 @@ import GoogleAUTH from '../AUTH/GoogleAUTH';
|
||||
import NASAUTH from '../AUTH/NASAUTH';
|
||||
import NASSetupInstructions from '../components/NASSetupInstructions';
|
||||
import ServiceHealthIndicator from '../components/ServiceHealthIndicator';
|
||||
import ServiceGlyph from '../components/ServiceGlyph';
|
||||
import CheckIcon from '../../assets/icons/check.svg';
|
||||
import DeviceIcon from '../../assets/icons/device.svg';
|
||||
import GooglePhotosIcon from '../../assets/icons/google-photos.svg';
|
||||
import ICloudIcon from '../../assets/icons/icloud.svg';
|
||||
import NasIcon from '../../assets/icons/nas.svg';
|
||||
|
||||
const SERVICES = {
|
||||
GOOGLE_PHOTOS: { name: 'Google Photos', type: 'GOOGLE_PHOTOS' },
|
||||
@@ -25,12 +22,6 @@ const ONBOARDING_STEPS = [
|
||||
{ title: 'Connect Services', description: 'Set up each selected service' },
|
||||
{ title: 'Automatic Backup', description: 'Choose where new photos are backed up' },
|
||||
];
|
||||
const ServiceGlyph = ({ serviceType, size = 20 }) => {
|
||||
if (serviceType === 'GOOGLE_PHOTOS') return <GooglePhotosIcon width={size} height={size} />;
|
||||
if (serviceType === 'ICLOUD_PHOTOS') return <ICloudIcon width={size} height={size} />;
|
||||
if (serviceType === 'NAS') return <NasIcon width={size} height={size} />;
|
||||
return <DeviceIcon width={size} height={size} />;
|
||||
};
|
||||
|
||||
const OnboardingScreen = ({ navigation, route }) => {
|
||||
const userId = route?.params?.userId;
|
||||
|
||||
+11
-15
@@ -4,6 +4,7 @@ import {
|
||||
} from 'react-native';
|
||||
import { useTheme } from '../services/ThemeContext';
|
||||
import DatabaseService from '../services/DatabaseService';
|
||||
import CenteredState from '../components/CenteredState';
|
||||
import PhotoGrid from '../components/PhotoGrid';
|
||||
import MediaViewer from '../components/MediaViewer';
|
||||
import SearchIcon from '../../assets/icons/search.svg';
|
||||
@@ -66,21 +67,19 @@ const SearchScreen = ({ route }) => {
|
||||
)}
|
||||
|
||||
{!searching && query.length > 0 && results.length === 0 && (
|
||||
<View style={styles.emptyContainer}>
|
||||
<SearchIcon width={48} height={48} color={theme.textTertiary} />
|
||||
<Text style={[styles.emptyTitle, { color: theme.text }]}>No results</Text>
|
||||
<Text style={[styles.emptyText, { color: theme.textSecondary }]}>Try different keywords</Text>
|
||||
</View>
|
||||
<CenteredState
|
||||
icon={<SearchIcon width={48} height={48} color={theme.textTertiary} />}
|
||||
title="No results"
|
||||
description="Try different keywords"
|
||||
/>
|
||||
)}
|
||||
|
||||
{query.length === 0 && (
|
||||
<View style={styles.emptyContainer}>
|
||||
<SearchIcon width={48} height={48} color={theme.textTertiary} />
|
||||
<Text style={[styles.emptyTitle, { color: theme.text }]}>Search your photos</Text>
|
||||
<Text style={[styles.emptyText, { color: theme.textSecondary }]}>
|
||||
Search by filename and indexed metadata
|
||||
</Text>
|
||||
</View>
|
||||
<CenteredState
|
||||
icon={<SearchIcon width={48} height={48} color={theme.textTertiary} />}
|
||||
title="Search your photos"
|
||||
description="Search by filename and indexed metadata"
|
||||
/>
|
||||
)}
|
||||
|
||||
{results.length > 0 && (
|
||||
@@ -110,9 +109,6 @@ const styles = StyleSheet.create({
|
||||
searchInput: { flex: 1, fontSize: 15, padding: 0 },
|
||||
clearButton: { fontSize: 16, padding: 4 },
|
||||
loadingContainer: { padding: 20, alignItems: 'center' },
|
||||
emptyContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', paddingHorizontal: 24 },
|
||||
emptyTitle: { fontSize: 18, fontWeight: '600', marginBottom: 6 },
|
||||
emptyText: { fontSize: 13, textAlign: 'center' },
|
||||
resultCount: { paddingHorizontal: 16, paddingVertical: 8, fontSize: 13 },
|
||||
});
|
||||
|
||||
|
||||
@@ -8,14 +8,11 @@ import { useTheme } from '../services/ThemeContext';
|
||||
import DatabaseService from '../services/DatabaseService';
|
||||
import NASSetupInstructions from '../components/NASSetupInstructions';
|
||||
import ServiceHealthIndicator from '../components/ServiceHealthIndicator';
|
||||
import ServiceGlyph from '../components/ServiceGlyph';
|
||||
import NASAUTH from '../AUTH/NASAUTH';
|
||||
import GoogleAUTH from '../AUTH/GoogleAUTH';
|
||||
import { clearStoredUserId } from '../services/AuthSession';
|
||||
import CheckIcon from '../../assets/icons/check.svg';
|
||||
import DeviceIcon from '../../assets/icons/device.svg';
|
||||
import GooglePhotosIcon from '../../assets/icons/google-photos.svg';
|
||||
import ICloudIcon from '../../assets/icons/icloud.svg';
|
||||
import NasIcon from '../../assets/icons/nas.svg';
|
||||
|
||||
const SERVICE_NAMES = { GOOGLE_PHOTOS: 'Google Photos', ICLOUD_PHOTOS: 'iCloud Photos', NAS: 'NAS Storage' };
|
||||
const AUTO_BACKUP_SERVICES = [
|
||||
@@ -23,12 +20,6 @@ const AUTO_BACKUP_SERVICES = [
|
||||
{ type: 'ICLOUD_PHOTOS', name: 'iCloud Photos' },
|
||||
{ type: 'NAS', name: 'NAS Storage' },
|
||||
];
|
||||
const ServiceGlyph = ({ serviceType, size = 20 }) => {
|
||||
if (serviceType === 'GOOGLE_PHOTOS') return <GooglePhotosIcon width={size} height={size} />;
|
||||
if (serviceType === 'ICLOUD_PHOTOS') return <ICloudIcon width={size} height={size} />;
|
||||
if (serviceType === 'NAS') return <NasIcon width={size} height={size} />;
|
||||
return <DeviceIcon width={size} height={size} />;
|
||||
};
|
||||
|
||||
const SettingsScreen = ({ navigation, route }) => {
|
||||
const userId = route?.params?.userId;
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import * as Crypto from 'expo-crypto';
|
||||
import { requireNativeModule } from 'expo-modules-core';
|
||||
|
||||
const ExpoCrypto = requireNativeModule('ExpoCrypto');
|
||||
const SHA_256 = 'SHA-256';
|
||||
const HEX_ENCODING = 'hex';
|
||||
|
||||
class CredentialEncryption {
|
||||
async hashPassword(password) {
|
||||
try {
|
||||
if (!password) throw new Error('Password is required');
|
||||
return await Crypto.digestStringAsync(
|
||||
Crypto.CryptoDigestAlgorithm.SHA256,
|
||||
password
|
||||
);
|
||||
return await ExpoCrypto.digestStringAsync(SHA_256, password, { encoding: HEX_ENCODING });
|
||||
} catch (error) {
|
||||
console.error('Password hashing error:', error);
|
||||
throw new Error('Failed to hash password: ' + error.message);
|
||||
|
||||
Reference in new issue
Block a user