Rename app from "Shoppy" to "Shoppinguin" across codebase and documentation. Handle migration of legacy storage keys.

This commit is contained in:
2026-10-16 15:40:55 +02:00
parent 9b80580f9c
commit 6938afb26c
9 changed files with 37 additions and 28 deletions
+2 -2
View File
@@ -85,7 +85,7 @@ export default function App() {
setManualPrices(nextPrices || {}); setManualPrices(nextPrices || {});
setRecentProducts(Array.isArray(nextRecent) ? nextRecent : []); setRecentProducts(Array.isArray(nextRecent) ? nextRecent : []);
setIsHydrated(true); setIsHydrated(true);
}).catch(() => setStorageError('Bitte starte Shoppy erneut, um die lokalen Daten zu laden.')); }).catch(() => setStorageError('Bitte starte Shoppinguin erneut, um die lokalen Daten zu laden.'));
} }
useEffect(() => { useEffect(() => {
@@ -378,7 +378,7 @@ export default function App() {
if (!isHydrated) { if (!isHydrated) {
return ( return (
<View style={styles.loading}> <View style={styles.loading}>
<Text style={styles.loadingText}>{storageError || 'Shoppy wird geladen...'}</Text> <Text style={styles.loadingText}>{storageError || 'Shoppinguin wird geladen...'}</Text>
{storageError ? <Text onPress={retryHydration} style={styles.retryText}>Erneut versuchen</Text> : null} {storageError ? <Text onPress={retryHydration} style={styles.retryText}>Erneut versuchen</Text> : null}
</View> </View>
); );
+5 -5
View File
@@ -1,16 +1,16 @@
# Shoppy # Shoppinguin
Smart Shopping Companion Smart Shopping Companion
## App-Idee ## App-Idee
Shoppy ist eine mobile Einkaufshilfe für den Supermarkt. Nutzerinnen und Nutzer scannen Produkte per Smartphone-Kamera oder Barcode, erhalten direkt eine persönliche Einschätzung und treffen dadurch bessere Kaufentscheidungen. Shoppinguin ist eine mobile Einkaufshilfe für den Supermarkt. Nutzerinnen und Nutzer scannen Produkte per Smartphone-Kamera oder Barcode, erhalten direkt eine persönliche Einschätzung und treffen dadurch bessere Kaufentscheidungen.
Die Bewertung berücksichtigt Zutaten, Preis, Nachhaltigkeitsmerkmale und individuelle Ziele wie Allergien, Ernährung, Budget und Nachhaltigkeit. Die Bewertung berücksichtigt Zutaten, Preis, Nachhaltigkeitsmerkmale und individuelle Ziele wie Allergien, Ernährung, Budget und Nachhaltigkeit.
## Zielgruppe ## Zielgruppe
Die App richtet sich an Privatpersonen, die im Supermarkt schnell bessere Kaufentscheidungen treffen möchten. Besonders relevant ist Shoppy für Menschen mit Allergien oder Unverträglichkeiten, ernährungsbewusste Käuferinnen und Käufer, Studierende und Familien mit Budgetfokus sowie Konsumentinnen und Konsumenten mit Nachhaltigkeitsanspruch. Die App richtet sich an Privatpersonen, die im Supermarkt schnell bessere Kaufentscheidungen treffen möchten. Besonders relevant ist Shoppinguin für Menschen mit Allergien oder Unverträglichkeiten, ernährungsbewusste Käuferinnen und Käufer, Studierende und Familien mit Budgetfokus sowie Konsumentinnen und Konsumenten mit Nachhaltigkeitsanspruch.
## Wichtigste Funktionen ## Wichtigste Funktionen
@@ -49,7 +49,7 @@ Der One-Pager aus Aufgabe 6.1 beschreibt das Go-to-Market-Konzept:
## Ordnerstruktur ## Ordnerstruktur
```text ```text
Shoppy/ Shoppinguin/
|-- assets/ |-- assets/
|-- src/ |-- src/
| |-- assets/ | |-- assets/
@@ -92,7 +92,7 @@ Der Workflow in `.gitea/workflows/android-release.yml` startet bei jedem Push
in ein Gitea-Repository. Er installiert die Abhängigkeiten mit `npm ci`, in ein Gitea-Repository. Er installiert die Abhängigkeiten mit `npm ci`,
erzeugt das nicht versionierte Android-Projekt per Expo und kompiliert mit erzeugt das nicht versionierte Android-Projekt per Expo und kompiliert mit
Gradle eine Release-APK. Die APK ist im jeweiligen Gitea-Actions-Lauf als Gradle eine Release-APK. Die APK ist im jeweiligen Gitea-Actions-Lauf als
Artefakt `shoppy-android-release-<Laufnummer>` herunterladbar. Artefakt `shoppinguin-android-release-<Laufnummer>` herunterladbar.
Voraussetzungen: Gitea Actions ist für das Repository aktiviert und ein Voraussetzungen: Gitea Actions ist für das Repository aktiviert und ein
Linux-Runner mit dem Label `ubuntu-latest` ist registriert. Der Runner benötigt Linux-Runner mit dem Label `ubuntu-latest` ist registriert. Der Runner benötigt
+5 -5
View File
@@ -1,7 +1,7 @@
{ {
"expo": { "expo": {
"name": "Shoppy", "name": "Shoppinguin",
"slug": "shoppy", "slug": "shoppinguin",
"version": "1.0.0", "version": "1.0.0",
"orientation": "portrait", "orientation": "portrait",
"userInterfaceStyle": "automatic", "userInterfaceStyle": "automatic",
@@ -14,10 +14,10 @@
"ios": { "ios": {
"supportsTablet": true, "supportsTablet": true,
"infoPlist": { "infoPlist": {
"NSCameraUsageDescription": "Shoppy nutzt die Kamera, um Produkt-Barcodes im Supermarkt zu scannen.", "NSCameraUsageDescription": "Shoppinguin nutzt die Kamera, um Produkt-Barcodes im Supermarkt zu scannen.",
"ITSAppUsesNonExemptEncryption": false "ITSAppUsesNonExemptEncryption": false
}, },
"bundleIdentifier": "net.fretux.shoppy" "bundleIdentifier": "net.fretux.shoppinguin"
}, },
"android": { "android": {
"permissions": [ "permissions": [
@@ -26,7 +26,7 @@
"adaptiveIcon": { "adaptiveIcon": {
"backgroundColor": "#f7f3ea" "backgroundColor": "#f7f3ea"
}, },
"package": "net.fretux.shoppy" "package": "net.fretux.shoppinguin"
}, },
"extra": { "extra": {
"eas": { "eas": {
+2 -2
View File
@@ -1,11 +1,11 @@
{ {
"name": "shoppy", "name": "shoppinguin",
"version": "1.0.0", "version": "1.0.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "shoppy", "name": "shoppinguin",
"version": "1.0.0", "version": "1.0.0",
"dependencies": { "dependencies": {
"@expo/metro-runtime": "~6.1.2", "@expo/metro-runtime": "~6.1.2",
+1 -1
View File
@@ -1,5 +1,5 @@
{ {
"name": "shoppy", "name": "shoppinguin",
"version": "1.0.0", "version": "1.0.0",
"private": true, "private": true,
"main": "node_modules/expo/AppEntry.js", "main": "node_modules/expo/AppEntry.js",
+1 -1
View File
@@ -17,7 +17,7 @@ export default function HomeScreen({ preferences, recentProducts, shoppingList,
return ( return (
<ScreenContainer> <ScreenContainer>
<AppHeader <AppHeader
title="Shoppy" title="Shoppinguin"
subtitle="Scanne Produkte im Supermarkt und erhalte eine persönliche Kaufempfehlung." subtitle="Scanne Produkte im Supermarkt und erhalte eine persönliche Kaufempfehlung."
/> />
+1 -1
View File
@@ -77,7 +77,7 @@ export default function ScanScreen({ preferences, productCache, onScanBarcode, o
<View style={styles.permissionBox}> <View style={styles.permissionBox}>
<Text style={styles.permissionTitle}>Kamera-Berechtigung benötigt</Text> <Text style={styles.permissionTitle}>Kamera-Berechtigung benötigt</Text>
<Text style={styles.permissionCopy}> <Text style={styles.permissionCopy}>
Shoppy braucht die Kamera, um Barcodes direkt im Laden zu erfassen. Shoppinguin braucht die Kamera, um Barcodes direkt im Laden zu erfassen.
</Text> </Text>
<PrimaryButton label="Kamera erlauben" onPress={requestPermission} /> <PrimaryButton label="Kamera erlauben" onPress={requestPermission} />
</View> </View>
+3 -3
View File
@@ -6,7 +6,7 @@ const dailyLimitRules = [
unit: 'mg', unit: 'mg',
nutritionKey: 'caffeineMgPer100g', nutritionKey: 'caffeineMgPer100g',
severity: 'critical', severity: 'critical',
message: 'Mehr Koffein als der Tagesrichtwert in Shoppy. Auf mehrere Tage aufteilen.' message: 'Mehr Koffein als der Tagesrichtwert in Shoppinguin. Auf mehrere Tage aufteilen.'
}, },
{ {
key: 'sugar', key: 'sugar',
@@ -15,7 +15,7 @@ const dailyLimitRules = [
unit: 'g', unit: 'g',
nutritionKey: 'sugarPer100g', nutritionKey: 'sugarPer100g',
severity: 'warning', severity: 'warning',
message: 'Mehr Zucker als der Tagesrichtwert in Shoppy. Auf mehrere Tage aufteilen.' message: 'Mehr Zucker als der Tagesrichtwert in Shoppinguin. Auf mehrere Tage aufteilen.'
}, },
{ {
key: 'salt', key: 'salt',
@@ -24,7 +24,7 @@ const dailyLimitRules = [
unit: 'g', unit: 'g',
nutritionKey: 'saltPer100g', nutritionKey: 'saltPer100g',
severity: 'warning', severity: 'warning',
message: 'Mehr Salz als der Tagesrichtwert in Shoppy. Auf mehrere Tage aufteilen.' message: 'Mehr Salz als der Tagesrichtwert in Shoppinguin. Auf mehrere Tage aufteilen.'
} }
]; ];
+17 -8
View File
@@ -1,21 +1,30 @@
import AsyncStorage from '@react-native-async-storage/async-storage'; import AsyncStorage from '@react-native-async-storage/async-storage';
const keys = { const keys = {
preferences: 'shoppy.preferences', preferences: 'shoppinguin.preferences',
shoppingList: 'shoppy.shoppingList', shoppingList: 'shoppinguin.shoppingList',
productCache: 'shoppy.productCache', productCache: 'shoppinguin.productCache',
manualPrices: 'shoppy.manualPrices', manualPrices: 'shoppinguin.manualPrices',
recentProducts: 'shoppy.recentProducts' recentProducts: 'shoppinguin.recentProducts'
}; };
const legacyPrefix = 'shoppy.';
async function readJson(key, fallbackValue) { async function readJson(key, fallbackValue) {
try { try {
const rawValue = await AsyncStorage.getItem(key); let rawValue = await AsyncStorage.getItem(key);
if (rawValue === null) {
const legacyKey = key.replace(/^shoppinguin\./, legacyPrefix);
rawValue = await AsyncStorage.getItem(legacyKey);
if (rawValue !== null) {
await AsyncStorage.setItem(key, rawValue);
}
}
return rawValue ? JSON.parse(rawValue) : fallbackValue; return rawValue ? JSON.parse(rawValue) : fallbackValue;
} catch (error) { } catch (error) {
// A damaged value should not prevent the app from starting. The next save // A damaged value should not prevent the app from starting. The next save
// will replace it with a valid value. // will replace it with a valid value.
console.warn(`Shoppy konnte ${key} nicht laden.`, error); console.warn(`Shoppinguin konnte ${key} nicht laden.`, error);
return fallbackValue; return fallbackValue;
} }
} }
@@ -25,7 +34,7 @@ async function writeJson(key, value) {
await AsyncStorage.setItem(key, JSON.stringify(value)); await AsyncStorage.setItem(key, JSON.stringify(value));
} catch (error) { } catch (error) {
// Storage is best-effort; the in-memory state remains usable. // Storage is best-effort; the in-memory state remains usable.
console.warn(`Shoppy konnte ${key} nicht speichern.`, error); console.warn(`Shoppinguin konnte ${key} nicht speichern.`, error);
} }
} }