Greenlens/services/storageService.ts

180 lines
5.6 KiB
TypeScript

import AsyncStorage from '@react-native-async-storage/async-storage';
import { Plant, Language, AppearanceMode, ColorPalette } from '../types';
const STORAGE_KEY = 'greenlens_plants';
const LANG_KEY = 'greenlens_language';
const ONBOARDING_KEY = 'greenlens_onboarding_complete';
const APPEARANCE_MODE_KEY = 'greenlens_appearance_mode';
const COLOR_PALETTE_KEY = 'greenlens_color_palette';
const PROFILE_IMAGE_KEY = 'greenlens_profile_image';
const PROFILE_NAME_KEY = 'greenlens_profile_name';
const LEXICON_SEARCH_HISTORY_KEY = 'greenlens_lexicon_search_history';
const LEXICON_SEARCH_HISTORY_LIMIT = 10;
const DEFAULT_PROFILE_NAME = 'GreenLens User';
const isAppearanceMode = (value: string | null): value is AppearanceMode =>
value === 'system' || value === 'light' || value === 'dark';
const isColorPalette = (value: string | null): value is ColorPalette =>
value === 'forest' || value === 'ocean' || value === 'sunset' || value === 'mono';
const normalizeSearchQuery = (value: string): string => {
return value
.toLowerCase()
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '')
.trim()
.replace(/\s+/g, ' ');
};
export const StorageService = {
getPlants: async (): Promise<Plant[]> => {
try {
const json = await AsyncStorage.getItem(STORAGE_KEY);
return json ? JSON.parse(json) : [];
} catch (e) {
console.error('Failed to load plants', e);
return [];
}
},
savePlant: async (plant: Plant): Promise<void> => {
const plants = await StorageService.getPlants();
const updatedPlants = [plant, ...plants];
await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(updatedPlants));
},
deletePlant: async (id: string): Promise<void> => {
const plants = await StorageService.getPlants();
const updatedPlants = plants.filter(p => p.id !== id);
await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(updatedPlants));
},
updatePlant: async (updatedPlant: Plant): Promise<void> => {
const plants = await StorageService.getPlants();
const index = plants.findIndex(p => p.id === updatedPlant.id);
if (index !== -1) {
plants[index] = updatedPlant;
await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(plants));
}
},
getLanguage: async (): Promise<Language> => {
try {
const lang = await AsyncStorage.getItem(LANG_KEY);
return (lang as Language) || 'en';
} catch (e) {
return 'en';
}
},
saveLanguage: async (lang: Language): Promise<void> => {
await AsyncStorage.setItem(LANG_KEY, lang);
},
getAppearanceMode: async (): Promise<AppearanceMode> => {
try {
const mode = await AsyncStorage.getItem(APPEARANCE_MODE_KEY);
return isAppearanceMode(mode) ? mode : 'system';
} catch (e) {
return 'system';
}
},
saveAppearanceMode: async (mode: AppearanceMode): Promise<void> => {
await AsyncStorage.setItem(APPEARANCE_MODE_KEY, mode);
},
getColorPalette: async (): Promise<ColorPalette> => {
try {
const palette = await AsyncStorage.getItem(COLOR_PALETTE_KEY);
return isColorPalette(palette) ? palette : 'forest';
} catch (e) {
return 'forest';
}
},
saveColorPalette: async (palette: ColorPalette): Promise<void> => {
await AsyncStorage.setItem(COLOR_PALETTE_KEY, palette);
},
getProfileImage: async (): Promise<string | null> => {
try {
const imageUri = await AsyncStorage.getItem(PROFILE_IMAGE_KEY);
return imageUri || null;
} catch (e) {
return null;
}
},
saveProfileImage: async (imageUri: string | null): Promise<void> => {
if (!imageUri) {
await AsyncStorage.removeItem(PROFILE_IMAGE_KEY);
return;
}
await AsyncStorage.setItem(PROFILE_IMAGE_KEY, imageUri);
},
getProfileName: async (): Promise<string> => {
try {
const profileName = await AsyncStorage.getItem(PROFILE_NAME_KEY);
const normalized = profileName?.trim();
return normalized || DEFAULT_PROFILE_NAME;
} catch (e) {
return DEFAULT_PROFILE_NAME;
}
},
saveProfileName: async (name: string): Promise<void> => {
const normalized = name.trim();
await AsyncStorage.setItem(PROFILE_NAME_KEY, normalized || DEFAULT_PROFILE_NAME);
},
getOnboardingComplete: async (): Promise<boolean> => {
try {
const value = await AsyncStorage.getItem(ONBOARDING_KEY);
return value === 'true';
} catch (e) {
return false;
}
},
setOnboardingComplete: async (complete: boolean): Promise<void> => {
await AsyncStorage.setItem(ONBOARDING_KEY, complete ? 'true' : 'false');
},
getLexiconSearchHistory: async (): Promise<string[]> => {
try {
const value = await AsyncStorage.getItem(LEXICON_SEARCH_HISTORY_KEY);
if (!value) return [];
const parsed = JSON.parse(value);
if (!Array.isArray(parsed)) return [];
return parsed.filter((item): item is string => typeof item === 'string');
} catch (e) {
console.error('Failed to load lexicon search history', e);
return [];
}
},
saveLexiconSearchQuery: async (query: string): Promise<void> => {
const trimmed = query.trim();
if (!trimmed) return;
const history = await StorageService.getLexiconSearchHistory();
const normalized = normalizeSearchQuery(trimmed);
const deduped = history.filter(
item => normalizeSearchQuery(item) !== normalized
);
const updated = [trimmed, ...deduped].slice(0, LEXICON_SEARCH_HISTORY_LIMIT);
await AsyncStorage.setItem(LEXICON_SEARCH_HISTORY_KEY, JSON.stringify(updated));
},
clearLexiconSearchHistory: async (): Promise<void> => {
await AsyncStorage.removeItem(LEXICON_SEARCH_HISTORY_KEY);
},
};