WIP: Claude Code progress on #65

Automated salvage commit — agent session ended (exit 124).
Work in progress, may need continuation.
This commit is contained in:
Alexander Whitestone
2026-03-23 22:26:20 -04:00
parent 94d2e48455
commit ad2a5e23fa
8 changed files with 185 additions and 260 deletions

View File

@@ -2,7 +2,6 @@ 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';
@@ -13,49 +12,30 @@ 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 { connectionStatus } = useTimmy();
const C = Colors.dark;
const [serverUrl, setServerUrl] = useState(apiBaseUrl);
const [serverUrl, setServerUrl] = useState('');
const [jobCompletionNotifications, setJobCompletionNotifications] = useState(false);
const [lowBalanceWarning, setLowBalanceWarning] = useState(false);
const [currentNpub, setCurrentNpub] = useState<string | null>(nostrPublicKey);
useEffect(() => {
// Load settings from AsyncStorage and SecureStore
const loadSettings = async () => {
const storedServerUrl = await AsyncStorage.getItem(STORAGE_KEYS.SERVER_URL);
if (storedServerUrl) {
setServerUrl(storedServerUrl);
}
if (storedServerUrl) setServerUrl(storedServerUrl);
const storedJobCompletion = await AsyncStorage.getItem(STORAGE_KEYS.NOTIFICATIONS_JOB_COMPLETION);
if (storedJobCompletion !== null) {
setJobCompletionNotifications(JSON.parse(storedJobCompletion));
}
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);
if (storedLowBalance !== null) setLowBalanceWarning(JSON.parse(storedLowBalance));
};
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 handleServerUrlSave = async () => {
await AsyncStorage.setItem(STORAGE_KEYS.SERVER_URL, serverUrl);
};
const toggleJobCompletionNotifications = async () => {
@@ -70,32 +50,11 @@ export default function SettingsScreen() {
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 appVersion = Constants.expoConfig?.version ?? 'N/A';
const buildCommitHash = (Constants.expoConfig?.extra as Record<string, string> | undefined)?.gitCommitHash ?? 'N/A';
const giteaRepoUrl = 'http://143.198.27.163:3000/replit/timmy-tower';
const openGiteaLink = () => {
Linking.openURL(giteaRepoUrl);
};
const openGiteaLink = () => { Linking.openURL(giteaRepoUrl); };
return (
<View style={styles.container}>
@@ -106,15 +65,16 @@ export default function SettingsScreen() {
<Text style={styles.settingLabel}>Server URL</Text>
<View style={styles.serverUrlContainer}>
<TextInput
style={[styles.input, { color: C.text, backgroundColor: C.field }]} // Apply text and background color from Colors
style={[styles.input, { color: C.text, backgroundColor: C.surface }]}
value={serverUrl}
onChangeText={handleServerUrlChange}
onChangeText={setServerUrl}
onBlur={handleServerUrlSave}
placeholder="Enter server URL"
placeholderTextColor={C.textMuted}
autoCapitalize="none"
autoCorrect={false}
/>
<ConnectionBadge isConnected={isConnected} />
<ConnectionBadge status={connectionStatus} />
</View>
</View>
@@ -124,7 +84,7 @@ export default function SettingsScreen() {
<Switch
trackColor={{ false: C.surface, true: C.accentGlow }}
thumbColor={Platform.OS === 'android' ? C.text : ''}
ios_backgroundColor={C.field}
ios_backgroundColor={C.surface}
onValueChange={toggleJobCompletionNotifications}
value={jobCompletionNotifications}
/>
@@ -134,31 +94,12 @@ export default function SettingsScreen() {
<Switch
trackColor={{ false: C.surface, true: C.accentGlow }}
thumbColor={Platform.OS === 'android' ? C.text : ''}
ios_backgroundColor={C.field}
ios_backgroundColor={C.surface}
onValueChange={toggleLowBalanceWarning}
value={lowBalanceWarning}
/>
</View>
<Text style={styles.sectionHeader}>Identity</Text>
<View style={styles.settingItem}>
<Text style={styles.settingLabel}>Nostr Public Key</Text>
<Text style={[styles.settingValue, { color: C.textMuted }]}>
{currentNpub ? `${currentNpub.substring(0, 10)}...${currentNpub.substring(currentNpub.length - 5)}` : 'Not connected'}
</Text>
</View>
<View style={styles.buttonContainer}>
{!currentNpub ? (
<Pressable onPress={handleConnectNostr} style={({ pressed }) => [styles.button, { backgroundColor: C.accent, opacity: pressed ? 0.8 : 1 }]}>
<Text style={[styles.buttonText, { color: C.textInverted }]}>Connect Nostr</Text>
</Pressable>
) : (
<Pressable onPress={handleDisconnectNostr} style={({ pressed }) => [styles.button, { backgroundColor: C.destructive, opacity: pressed ? 0.8 : 1 }]}>
<Text style={[styles.buttonText, { color: C.textInverted }]}>Disconnect Nostr</Text>
</Pressable>
)}
</View>
<Text style={styles.sectionHeader}>About</Text>
<View style={styles.settingItem}>
<Text style={styles.settingLabel}>App Version</Text>
@@ -170,7 +111,7 @@ export default function SettingsScreen() {
</View>
<Pressable onPress={openGiteaLink} style={({ pressed }) => [styles.linkButton, { opacity: pressed ? 0.8 : 1 }]}>
<Ionicons name="link" size={16} color={C.text} />
<Text style={[styles.linkButtonText, { color: C.link }]}>View project on Gitea</Text>
<Text style={[styles.linkButtonText, { color: C.accentGlow }]}>View project on Gitea</Text>
</Pressable>
</ScrollView>
</View>
@@ -180,7 +121,7 @@ export default function SettingsScreen() {
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: Colors.dark.background, // Use background color from Colors
backgroundColor: Colors.dark.background,
},
scrollContent: {
padding: 20,
@@ -223,27 +164,13 @@ const styles = StyleSheet.create({
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,
gap: 6,
paddingVertical: 12,
},
linkButtonText: {
marginLeft: 5,
fontSize: 16,
},
});