feat(mobile): add pre-login legal pages, multi-image selection, file detail view, search, i18n, HEIC support

- Add Privacy Policy, Terms of Service, and Imprint links to WelcomeScreen
  and LoginScreen for GDPR/Apple compliance (pre-login access)
- Enable multiple image selection in photo library picker
- Add HEIC/HEIF image support to backend (allowed_types, convert_to_pdf, upload handler)
- Create FileDetailScreen with processing status and logs
- Add search bar to FilesScreen with debounced search
- Set up i18n with expo-localization (EN, DE, ES, FR, IT)
- Add language selector to ProfileScreen settings
- Add Imprint link to ProfileScreen legal section
- Update docs and tests

Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-03-19 19:11:44 +00:00
parent 0252f11cc0
commit 67c17e7baa
22 changed files with 1502 additions and 56 deletions
+89 -1
View File
@@ -4,7 +4,7 @@
import Constants from "expo-constants";
import * as Linking from "expo-linking";
import React from "react";
import React, { useState } from "react";
import {
Alert,
Image,
@@ -15,14 +15,17 @@ import {
View,
} from "react-native";
import { useAuth } from "../context/AuthContext";
import { getLanguage, getSupportedLanguages, setLanguage } from "../i18n";
const DEFAULT_SERVER_URL = "https://app.docuelevate.org";
export default function ProfileScreen() {
const { user, signOut, baseUrl } = useAuth();
const [selectedLanguage, setSelectedLanguage] = useState(getLanguage());
const effectiveBaseUrl = baseUrl || DEFAULT_SERVER_URL;
const appVersion = Constants.expoConfig?.version ?? "1.0.0";
const languages = getSupportedLanguages();
function handleSignOut() {
Alert.alert("Sign out", "Are you sure you want to sign out?", [
@@ -66,6 +69,12 @@ export default function ProfileScreen() {
});
}
function openImprint() {
Linking.openURL(`${effectiveBaseUrl}/imprint`).catch(() => {
Alert.alert("Error", "Could not open the imprint page. Please try again.");
});
}
if (!user) {
return (
<View style={styles.center}>
@@ -113,6 +122,39 @@ export default function ProfileScreen() {
</View>
</View>
{/* Settings */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>Settings</Text>
<Text style={styles.settingLabel}>Language</Text>
<View style={styles.languageGrid}>
{languages.map((lang) => (
<Pressable
key={lang.code}
style={[
styles.languageChip,
selectedLanguage === lang.code && styles.languageChipActive,
]}
onPress={() => {
setLanguage(lang.code);
setSelectedLanguage(lang.code);
}}
accessibilityRole="button"
accessibilityLabel={`Set language to ${lang.label}`}
accessibilityState={{ selected: selectedLanguage === lang.code }}
>
<Text
style={[
styles.languageChipText,
selectedLanguage === lang.code && styles.languageChipTextActive,
]}
>
{lang.label}
</Text>
</Pressable>
))}
</View>
</View>
{/* Legal & Privacy */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>Legal</Text>
@@ -134,6 +176,15 @@ export default function ProfileScreen() {
<Text style={styles.linkText}>Terms of Service</Text>
<Text style={styles.linkChevron}>›</Text>
</Pressable>
<Pressable
style={[styles.linkRow, styles.linkRowLast]}
onPress={openImprint}
accessibilityRole="link"
accessibilityLabel="Imprint"
>
<Text style={styles.linkText}>Imprint</Text>
<Text style={styles.linkChevron}>›</Text>
</Pressable>
</View>
{/* Sign out */}
@@ -264,6 +315,9 @@ const styles = StyleSheet.create({
borderBottomColor: "#f3f4f6",
minHeight: 44,
},
linkRowLast: {
borderBottomWidth: 0,
},
linkText: {
fontSize: 15,
color: "#1e40af",
@@ -305,4 +359,38 @@ const styles = StyleSheet.create({
textAlign: "center",
marginTop: 8,
},
settingLabel: {
fontSize: 14,
color: "#374151",
fontWeight: "500",
marginBottom: 10,
},
languageGrid: {
flexDirection: "row",
flexWrap: "wrap",
gap: 8,
},
languageChip: {
paddingHorizontal: 14,
paddingVertical: 8,
borderRadius: 20,
backgroundColor: "#f3f4f6",
borderWidth: 1,
borderColor: "#e5e7eb",
minHeight: 36,
justifyContent: "center",
},
languageChipActive: {
backgroundColor: "#dbeafe",
borderColor: "#1e40af",
},
languageChipText: {
fontSize: 13,
color: "#6b7280",
fontWeight: "500",
},
languageChipTextActive: {
color: "#1e40af",
fontWeight: "700",
},
});