import { Stack } from 'expo-router'; import { View, Text, StyleSheet, ScrollView, TextInput, Switch, Pressable, Linking, Platform } from 'react-native'; import { useState, useEffect } from 'react'; import AsyncStorage from '@react-native-async-storage/async-storage'; import * as SecureStore from 'expo-secure-store'; import Constants from 'expo-constants'; import { useTimmy } from '@/context/TimmyContext'; import { Ionicons } from '@expo/vector-icons'; import { ConnectionBadge } from '@/components/ConnectionBadge'; import { Colors } from '@/constants/colors'; const STORAGE_KEYS = { SERVER_URL: 'settings_server_url', NOTIFICATIONS_JOB_COMPLETION: 'settings_notifications_job_completion', NOTIFICATIONS_LOW_BALANCE: 'settings_notifications_low_balance', NOSTR_PRIVATE_KEY: 'settings_nostr_private_key', // Use SecureStore for this }; export default function SettingsScreen() { const { apiBaseUrl, setApiBaseUrl, isConnected, nostrPublicKey, connectNostr, disconnectNostr } = useTimmy(); const C = Colors.dark; const [serverUrl, setServerUrl] = useState(apiBaseUrl); const [jobCompletionNotifications, setJobCompletionNotifications] = useState(false); const [lowBalanceWarning, setLowBalanceWarning] = useState(false); const [currentNpub, setCurrentNpub] = useState(nostrPublicKey); useEffect(() => { // Load settings from AsyncStorage and SecureStore const loadSettings = async () => { const storedServerUrl = await AsyncStorage.getItem(STORAGE_KEYS.SERVER_URL); if (storedServerUrl) { setServerUrl(storedServerUrl); } const storedJobCompletion = await AsyncStorage.getItem(STORAGE_KEYS.NOTIFICATIONS_JOB_COMPLETION); if (storedJobCompletion !== null) { setJobCompletionNotifications(JSON.parse(storedJobCompletion)); } const storedLowBalance = await AsyncStorage.getItem(STORAGE_KEYS.NOTIFICATIONS_LOW_BALANCE); if (storedLowBalance !== null) { setLowBalanceWarning(JSON.parse(storedLowBalance)); } // Nostr npub is handled by TimmyContext, so we just use the provided nostrPublicKey setCurrentNpub(nostrPublicKey); }; loadSettings(); }, [nostrPublicKey]); // Update apiBaseUrl in context when serverUrl changes and is saved useEffect(() => { if (serverUrl !== apiBaseUrl) { setApiBaseUrl(serverUrl); AsyncStorage.setItem(STORAGE_KEYS.SERVER_URL, serverUrl); } }, [serverUrl, setApiBaseUrl, apiBaseUrl]); const handleServerUrlChange = (text: string) => { setServerUrl(text); }; const toggleJobCompletionNotifications = async () => { const newValue = !jobCompletionNotifications; setJobCompletionNotifications(newValue); await AsyncStorage.setItem(STORAGE_KEYS.NOTIFICATIONS_JOB_COMPLETION, JSON.stringify(newValue)); }; const toggleLowBalanceWarning = async () => { const newValue = !lowBalanceWarning; setLowBalanceWarning(newValue); await AsyncStorage.setItem(STORAGE_KEYS.NOTIFICATIONS_LOW_BALANCE, JSON.stringify(newValue)); }; const handleConnectNostr = async () => { // This will ideally link to a dedicated Nostr connection flow console.log('Connect Nostr button pressed'); // For now, simulate connection if not connected if (!currentNpub) { // This is a placeholder. Real implementation would involve generating/importing keys. const simulatedNpub = 'npub1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'; connectNostr(simulatedNpub, 'private_key_placeholder'); // Pass a placeholder private key setCurrentNpub(simulatedNpub); // In a real app, the private key would be securely stored and managed by the context // For now, just a placeholder to show connected state } }; const handleDisconnectNostr = async () => { await disconnectNostr(); setCurrentNpub(null); }; const appVersion = Constants.expoConfig?.version || 'N/A'; const buildCommitHash = Constants.expoConfig?.extra?.gitCommitHash || 'N/A'; const giteaRepoUrl = 'http://143.198.27.163:3000/replit/timmy-tower'; const openGiteaLink = () => { Linking.openURL(giteaRepoUrl); }; return ( Connection Server URL Notifications Job Completion Push Notifications Low Balance Warning Identity Nostr Public Key {currentNpub ? `${currentNpub.substring(0, 10)}...${currentNpub.substring(currentNpub.length - 5)}` : 'Not connected'} {!currentNpub ? ( [styles.button, { backgroundColor: C.accent, opacity: pressed ? 0.8 : 1 }]}> Connect Nostr ) : ( [styles.button, { backgroundColor: C.destructive, opacity: pressed ? 0.8 : 1 }]}> Disconnect Nostr )} About App Version {appVersion} Build Commit Hash {buildCommitHash} [styles.linkButton, { opacity: pressed ? 0.8 : 1 }]}> View project on Gitea ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: Colors.dark.background, // Use background color from Colors }, scrollContent: { padding: 20, paddingBottom: 40, }, sectionHeader: { fontSize: 18, fontWeight: 'bold', color: Colors.dark.text, marginTop: 20, marginBottom: 10, }, settingItem: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingVertical: 12, borderBottomWidth: 0.5, borderBottomColor: Colors.dark.border, }, settingLabel: { fontSize: 16, color: Colors.dark.text, flex: 1, }, settingValue: { fontSize: 16, }, serverUrlContainer: { flexDirection: 'row', alignItems: 'center', flex: 2, }, input: { flex: 1, borderWidth: 1, borderColor: Colors.dark.border, borderRadius: 8, padding: 8, fontSize: 14, marginRight: 10, }, buttonContainer: { marginTop: 20, alignItems: 'flex-start', }, button: { paddingVertical: 10, paddingHorizontal: 15, borderRadius: 8, }, buttonText: { fontSize: 16, fontWeight: 'bold', }, linkButton: { flexDirection: 'row', alignItems: 'center', marginTop: 15, paddingVertical: 8, }, linkButtonText: { marginLeft: 5, fontSize: 16, }, });