Initial commit for Greenlens
This commit is contained in:
@@ -1,12 +1,36 @@
|
||||
import { Plant, Language } from '../types';
|
||||
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: (): Plant[] => {
|
||||
getPlants: async (): Promise<Plant[]> => {
|
||||
try {
|
||||
const json = localStorage.getItem(STORAGE_KEY);
|
||||
const json = await AsyncStorage.getItem(STORAGE_KEY);
|
||||
return json ? JSON.parse(json) : [];
|
||||
} catch (e) {
|
||||
console.error('Failed to load plants', e);
|
||||
@@ -14,37 +38,142 @@ export const StorageService = {
|
||||
}
|
||||
},
|
||||
|
||||
savePlant: (plant: Plant): void => {
|
||||
const plants = StorageService.getPlants();
|
||||
savePlant: async (plant: Plant): Promise<void> => {
|
||||
const plants = await StorageService.getPlants();
|
||||
const updatedPlants = [plant, ...plants];
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(updatedPlants));
|
||||
await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(updatedPlants));
|
||||
},
|
||||
|
||||
deletePlant: (id: string): void => {
|
||||
const plants = StorageService.getPlants();
|
||||
deletePlant: async (id: string): Promise<void> => {
|
||||
const plants = await StorageService.getPlants();
|
||||
const updatedPlants = plants.filter(p => p.id !== id);
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(updatedPlants));
|
||||
await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(updatedPlants));
|
||||
},
|
||||
|
||||
updatePlant: (updatedPlant: Plant): void => {
|
||||
const plants = StorageService.getPlants();
|
||||
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;
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(plants));
|
||||
await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(plants));
|
||||
}
|
||||
},
|
||||
|
||||
getLanguage: (): Language => {
|
||||
getLanguage: async (): Promise<Language> => {
|
||||
try {
|
||||
const lang = localStorage.getItem(LANG_KEY);
|
||||
return (lang as Language) || 'de';
|
||||
const lang = await AsyncStorage.getItem(LANG_KEY);
|
||||
return (lang as Language) || 'en';
|
||||
} catch (e) {
|
||||
return 'de';
|
||||
return 'en';
|
||||
}
|
||||
},
|
||||
|
||||
saveLanguage: (lang: Language): void => {
|
||||
localStorage.setItem(LANG_KEY, lang);
|
||||
}
|
||||
};
|
||||
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);
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user