Files
imexmobile/components/settings/settings.jsx
2025-11-04 13:51:17 -08:00

268 lines
8.4 KiB
JavaScript

import { selectDeleteAfterUpload } from "@/redux/app/app.selectors";
import { signOutStart } from "@/redux/user/user.actions";
import { selectBodyshop, selectCurrentUser } from "@/redux/user/user.selectors";
import { registerForPushNotificationsAsync } from "@/util/notificationHandler";
import { formatBytes } from "@/util/uploadUtils";
import AsyncStorage from "@react-native-async-storage/async-storage";
import * as Application from "expo-application";
import Constants from "expo-constants";
import * as Notifications from "expo-notifications";
import * as Updates from "expo-updates";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { Alert, ScrollView, StyleSheet, View } from "react-native";
import { Button, Card, Divider, List, Text } from "react-native-paper";
import { SafeAreaView } from "react-native-safe-area-context";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { ThemeSelector } from "../theme-selector/theme-selector";
import UploadDeleteSwitch from "./upload-delete-switch";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
deleteAfterUpload: selectDeleteAfterUpload,
currentUser: selectCurrentUser,
});
const mapDispatchToProps = (dispatch) => ({
signOutStart: () => dispatch(signOutStart()),
});
export default connect(mapStateToProps, mapDispatchToProps)(Tab);
function Tab({ bodyshop, currentUser, signOutStart }) {
const { t } = useTranslation();
const [permissionState, setPermissionState] = useState(null);
useEffect(() => {
(async () => {
const status = await Notifications.getPermissionsAsync();
setPermissionState(status);
})();
}, []);
const handleClearStorage = () => {
Alert.alert(
"Clear Local Cache",
"This will remove cache and settings. Continue?",
[
{ text: "Cancel", style: "cancel" },
{
text: "Clear",
style: "destructive",
onPress: async () => {
try {
await AsyncStorage.removeItem("persist:root");
Alert.alert(
"Cleared",
"Local cache cleared. Please restart the app."
);
} catch (_e) {
Alert.alert("Error", "Unable to clear local cache.");
}
},
},
]
);
};
return (
<SafeAreaView style={{ flex: 1 }} edges={["top"]}>
<Text variant="headlineMedium" style={styles.title}>
{t("settings.titles.settings")}
</Text>
<ScrollView contentContainerStyle={styles.container}>
<Card style={styles.section} mode="outlined">
<Card.Title title="Storage" />
<Card.Content>
<List.Section>
<View style={styles.inlineRow}>
<Text style={styles.switchLabel}>
{t("mediabrowser.labels.deleteafterupload")}
</Text>
<UploadDeleteSwitch />
</View>
<List.Item
title="Media Storage"
description={
bodyshop?.uselocalmediaserver
? bodyshop.localmediaserverhttp
: "Cloud"
}
left={(props) => <List.Icon {...props} icon="database" />}
/>
{!bodyshop?.uselocalmediaserver && (
<List.Item
title="Job Size Limit"
description={
bodyshop?.jobsizelimit
? formatBytes(bodyshop.jobsizelimit)
: "Not specified"
}
left={(props) => <List.Icon {...props} icon="harddisk" />}
/>
)}
</List.Section>
</Card.Content>
<Card.Actions>
<Button onPress={handleClearStorage} icon="delete-outline">
{t("settings.actions.clearcache")}
</Button>
</Card.Actions>
</Card>
<Card style={styles.section} mode="outlined">
<Card.Title title={t("settings.labels.theme")} />
<Card.Content>
<List.Section>
<View style={styles.inlineRow}>
<ThemeSelector />
</View>
</List.Section>
</Card.Content>
<Card.Actions></Card.Actions>
</Card>
<Card style={styles.section} mode="outlined">
<Card.Title title="Notifications" />
<Card.Content>
<List.Section>
<View style={styles.inlineRow}>
<Text>{`${t("settings.labels.notificationsenabled")}: ${
permissionState?.granted === true ? "Yes" : "No"
}`}</Text>
</View>
</List.Section>
</Card.Content>
<Card.Actions>
<Button
icon="bell-outline"
disabled={permissionState?.granted === true}
onPress={async () => {
try {
await registerForPushNotificationsAsync();
} catch (error) {
Alert.alert(
"Error",
`Unable to register for notifications: ${error.message}`
);
console.log(
"Notification registration error:",
error,
error.stack
);
}
}}
>
{t("settings.actions.enablenotifications")}
</Button>
</Card.Actions>
</Card>
<Card style={styles.section} mode="outlined">
<Card.Content>
<Text style={styles.paragraph}>
{`${t("settings.labels.signedinshop")} ${
bodyshop?.shopname || bodyshop?.id || "Unknown"
}`}
</Text>
<Text style={styles.paragraph}>
{`${t("settings.labels.signedinuser")} ${
currentUser?.email || "Unknown"
}`}
</Text>
</Card.Content>
<Card.Actions>
<Button onPress={signOutStart}>
{t("general.actions.signout")}
</Button>
</Card.Actions>
</Card>
<Divider style={styles.divider} />
<Text style={styles.footerNote} variant="bodySmall">
{t("settings.labels.version", {
number: `${Constants.expoConfig.version}(${Application.nativeBuildVersion} - ${Constants.expoConfig.extra.expover})`,
})}
</Text>
<Button
mode="text"
onPress={() => {
Updates.checkForUpdateAsync()
.then(async (update) => {
if (update.isAvailable) {
const reloaded = await Updates.fetchUpdateAsync();
if (reloaded.isNew) {
Alert.alert(
"Update downloaded",
"The app will now restart to apply the update.",
[
{
text: "Restart Now",
onPress: () => {
Updates.reloadAsync();
},
},
]
);
}
} else {
Alert.alert(
"No Update Available",
"You are using the latest version of the app."
);
}
})
.catch((error) => {
Alert.alert(
"Update Error",
`An error occurred while checking for updates: ${error.message}`
);
});
}}
>
Check for Update
</Button>
</ScrollView>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
paddingVertical: 24,
paddingHorizontal: 12,
paddingBottom: 96,
},
title: {
marginHorizontal: 12,
fontWeight: "600",
},
section: {
marginBottom: 20,
},
inlineRow: {
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
paddingVertical: 8,
paddingHorizontal: 4,
},
switchLabel: {
fontSize: 16,
fontWeight: "500",
},
paragraph: {
marginBottom: 8,
},
actionsRow: {
justifyContent: "flex-end",
},
divider: {
marginTop: 8,
},
footerNote: {
textAlign: "center",
// color: "#666",
marginTop: 16,
},
});