change design
This commit is contained in:
@@ -1,17 +1,33 @@
|
|||||||
import { useState, useEffect, useRef } from 'react';
|
import { useState, useEffect, useRef } from 'react';
|
||||||
import { StyleSheet, View, Text, FlatList, TouchableOpacity, PermissionsAndroid, Platform } from 'react-native';
|
import { StyleSheet, View, Text, FlatList, TouchableOpacity, PermissionsAndroid, Platform, ScrollView } from 'react-native';
|
||||||
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||||
import { Stack, router } from 'expo-router';
|
import { Stack, router } from 'expo-router';
|
||||||
import { useKeepAwake } from 'expo-keep-awake';
|
import { useKeepAwake } from 'expo-keep-awake';
|
||||||
import io from 'socket.io-client';
|
import io from 'socket.io-client';
|
||||||
import { SendDirectSms } from 'react-native-send-direct-sms';
|
import { SendDirectSms } from 'react-native-send-direct-sms';
|
||||||
|
|
||||||
|
const COLORS = {
|
||||||
|
background: '#121416',
|
||||||
|
surface: '#1e2022',
|
||||||
|
surfaceHigh: '#282a2c',
|
||||||
|
primary: '#fefcff',
|
||||||
|
onPrimary: '#213148',
|
||||||
|
text: '#e2e2e5',
|
||||||
|
textMuted: '#c5c6cd',
|
||||||
|
success: '#69fc9b',
|
||||||
|
successDim: '#003919',
|
||||||
|
error: '#ffb4ab',
|
||||||
|
border: '#44474d',
|
||||||
|
};
|
||||||
|
|
||||||
export default function DashboardScreen() {
|
export default function DashboardScreen() {
|
||||||
useKeepAwake(); // Keep the screen awake
|
useKeepAwake();
|
||||||
|
|
||||||
const [status, setStatus] = useState('Disconnected');
|
const [status, setStatus] = useState('Disconnected');
|
||||||
const [hasPermission, setHasPermission] = useState(false);
|
const [hasPermission, setHasPermission] = useState(false);
|
||||||
const [logs, setLogs] = useState<{id: string, time: string, msg: string}[]>([]);
|
const [logs, setLogs] = useState<{id: string, time: string, msg: string, type: 'info' | 'success' | 'error'}[]>([]);
|
||||||
|
const [latestSms, setLatestSms] = useState<{phone: string, message: string} | null>(null);
|
||||||
const socketRef = useRef<any>(null);
|
const socketRef = useRef<any>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -40,24 +56,25 @@ export default function DashboardScreen() {
|
|||||||
);
|
);
|
||||||
if (granted === PermissionsAndroid.RESULTS.GRANTED) {
|
if (granted === PermissionsAndroid.RESULTS.GRANTED) {
|
||||||
setHasPermission(true);
|
setHasPermission(true);
|
||||||
addLog('SMS permission granted');
|
addLog('SMS permission granted', 'success');
|
||||||
} else {
|
} else {
|
||||||
setHasPermission(false);
|
setHasPermission(false);
|
||||||
addLog('SMS permission denied');
|
addLog('SMS permission denied', 'error');
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn(err);
|
console.warn(err);
|
||||||
addLog(`Permission error: ${err}`);
|
addLog(`Permission error: ${err}`, 'error');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const addLog = (msg: string) => {
|
const addLog = (msg: string, type: 'info' | 'success' | 'error' = 'info') => {
|
||||||
setLogs(prev => [{
|
setLogs(prev => [{
|
||||||
id: Math.random().toString(),
|
id: Math.random().toString(),
|
||||||
time: new Date().toLocaleTimeString(),
|
time: new Date().toLocaleTimeString('en-US', { hour12: false }),
|
||||||
msg
|
msg,
|
||||||
}, ...prev].slice(0, 50)); // Keep last 50 logs
|
type
|
||||||
|
}, ...prev].slice(0, 50));
|
||||||
};
|
};
|
||||||
|
|
||||||
const connectSocket = async () => {
|
const connectSocket = async () => {
|
||||||
@@ -68,7 +85,7 @@ export default function DashboardScreen() {
|
|||||||
|
|
||||||
if (!endpoint || !login || !password) {
|
if (!endpoint || !login || !password) {
|
||||||
setStatus('Missing Settings');
|
setStatus('Missing Settings');
|
||||||
addLog('Please configure settings first');
|
addLog('Please configure settings first', 'error');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,52 +94,53 @@ export default function DashboardScreen() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setStatus('Connecting...');
|
setStatus('Connecting...');
|
||||||
|
addLog('Attempting socket handshake', 'info');
|
||||||
|
|
||||||
const socket = io(endpoint, {
|
const socket = io(endpoint, {
|
||||||
auth: { login, password },
|
auth: { login, password },
|
||||||
reconnectionAttempts: Infinity, // Keep trying forever
|
reconnectionAttempts: Infinity,
|
||||||
reconnectionDelay: 1000,
|
reconnectionDelay: 1000,
|
||||||
reconnectionDelayMax: 5000,
|
reconnectionDelayMax: 5000,
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('connect', () => {
|
socket.on('connect', () => {
|
||||||
setStatus('Connected');
|
setStatus('Connected');
|
||||||
addLog('Connected to backend');
|
addLog('Connected to backend', 'success');
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('disconnect', () => {
|
socket.on('disconnect', () => {
|
||||||
setStatus('Disconnected');
|
setStatus('Disconnected');
|
||||||
addLog('Disconnected from backend');
|
addLog('Connection lost: Retrying...', 'error');
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('connect_error', (err) => {
|
socket.on('connect_error', (err) => {
|
||||||
setStatus('Error');
|
setStatus('Error');
|
||||||
addLog(`Connection error: ${err.message}`);
|
addLog(`Connection error: ${err.message}`, 'error');
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('send_sms', async (data) => {
|
socket.on('send_sms', async (data) => {
|
||||||
addLog(`Received SMS request ID: ${data.id} to ${data.phone}`);
|
addLog(`OTP received: ${data.message}`, 'info');
|
||||||
|
setLatestSms({ phone: data.phone || '+993 61 92 92 48', message: data.message });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Default to rule phone if empty, or use the one provided
|
|
||||||
const targetPhone = data.phone || '+993 61 92 92 48';
|
const targetPhone = data.phone || '+993 61 92 92 48';
|
||||||
|
|
||||||
if (Platform.OS === 'android') {
|
if (Platform.OS === 'android') {
|
||||||
SendDirectSms(targetPhone, data.message)
|
SendDirectSms(targetPhone, data.message)
|
||||||
.then((res: any) => {
|
.then((res: any) => {
|
||||||
addLog(`SMS sent successfully to ${targetPhone}`);
|
addLog(`OTP forwarded successfully`, 'success');
|
||||||
socket.emit('sms_status', { id: data.id, status: 'sent' });
|
socket.emit('sms_status', { id: data.id, status: 'sent' });
|
||||||
})
|
})
|
||||||
.catch((err: any) => {
|
.catch((err: any) => {
|
||||||
addLog(`SMS send failed: ${err}`);
|
addLog(`SMS send failed: ${err}`, 'error');
|
||||||
socket.emit('sms_status', { id: data.id, status: 'failed' });
|
socket.emit('sms_status', { id: data.id, status: 'failed' });
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
addLog('Direct SMS only supported on Android');
|
addLog('Direct SMS only supported on Android', 'error');
|
||||||
socket.emit('sms_status', { id: data.id, status: 'failed' });
|
socket.emit('sms_status', { id: data.id, status: 'failed' });
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
addLog(`Error sending SMS: ${error}`);
|
addLog(`Error sending SMS: ${error}`, 'error');
|
||||||
socket.emit('sms_status', { id: data.id, status: 'failed' });
|
socket.emit('sms_status', { id: data.id, status: 'failed' });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -130,141 +148,276 @@ export default function DashboardScreen() {
|
|||||||
socketRef.current = socket;
|
socketRef.current = socket;
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
addLog(`Setup error: ${e}`);
|
addLog(`Setup error: ${e}`, 'error');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<SafeAreaView style={styles.container}>
|
||||||
<View style={styles.statusCard}>
|
<Stack.Screen options={{ headerShown: false }} />
|
||||||
<Text style={styles.statusLabel}>Backend Status:</Text>
|
|
||||||
<Text style={[
|
<View style={styles.header}>
|
||||||
styles.statusValue,
|
<View style={styles.headerLeft}>
|
||||||
{ color: status === 'Connected' ? 'green' : (status === 'Error' || status === 'Disconnected' ? 'red' : 'orange') }
|
<Text style={styles.headerIcon}>🛡️</Text>
|
||||||
]}>
|
<Text style={styles.headerTitle}>Secure Relay</Text>
|
||||||
{status}
|
|
||||||
</Text>
|
|
||||||
<View style={styles.buttonRow}>
|
|
||||||
<TouchableOpacity style={styles.reconnectButton} onPress={connectSocket}>
|
|
||||||
<Text style={styles.reconnectText}>Reconnect</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
|
|
||||||
{!hasPermission && (
|
|
||||||
<TouchableOpacity style={[styles.reconnectButton, { backgroundColor: '#ffebee', marginLeft: 10 }]} onPress={checkAndRequestPermission}>
|
|
||||||
<Text style={[styles.reconnectText, { color: '#c62828' }]}>Req Permission</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
)}
|
|
||||||
</View>
|
</View>
|
||||||
</View>
|
<TouchableOpacity onPress={() => router.push('/settings')}>
|
||||||
|
<Text style={styles.settingsIcon}>⚙️</Text>
|
||||||
<View style={styles.logHeader}>
|
|
||||||
<Text style={styles.logTitle}>Activity Log</Text>
|
|
||||||
<TouchableOpacity onPress={() => setLogs([])} style={styles.clearButton}>
|
|
||||||
<Text style={styles.clearButtonText}>Clear</Text>
|
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
<FlatList
|
|
||||||
data={logs}
|
<ScrollView style={styles.scrollContent} contentContainerStyle={styles.scrollContentContainer}>
|
||||||
keyExtractor={item => item.id}
|
|
||||||
style={styles.logList}
|
{/* Status Card */}
|
||||||
renderItem={({item}) => (
|
<View style={styles.mainStatusCard}>
|
||||||
<View style={styles.logItem}>
|
<View style={[styles.statusIconContainer, { backgroundColor: status === 'Connected' ? COLORS.successDim : (status === 'Connecting...' ? '#4a3600' : '#4a0005') }]}>
|
||||||
<Text style={styles.logTime}>{item.time}</Text>
|
<Text style={styles.statusIcon}>{status === 'Connected' ? '✓' : (status === 'Connecting...' ? '⏳' : '✕')}</Text>
|
||||||
<Text style={styles.logMsg}>{item.msg}</Text>
|
|
||||||
</View>
|
</View>
|
||||||
|
<Text style={[styles.statusValue, { color: status === 'Connected' ? COLORS.success : (status === 'Connecting...' ? '#ffb4ab' : COLORS.error) }]}>
|
||||||
|
{status} {status === 'Connected' && '✅'}
|
||||||
|
</Text>
|
||||||
|
<Text style={styles.statusDesc}>
|
||||||
|
{status === 'Connected' ? 'Service is active and monitoring incoming SMS.' : 'Service is currently unavailable.'}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* System Status */}
|
||||||
|
<Text style={styles.sectionTitle}>System Status</Text>
|
||||||
|
<View style={styles.systemStatusRow}>
|
||||||
|
<TouchableOpacity style={styles.systemStatusCard} onPress={checkAndRequestPermission}>
|
||||||
|
<Text style={styles.systemStatusIcon}>💬</Text>
|
||||||
|
<View>
|
||||||
|
<Text style={styles.systemStatusLabel}>SMS Perm</Text>
|
||||||
|
<Text style={[styles.systemStatusValue, { color: hasPermission ? COLORS.success : COLORS.error }]}>
|
||||||
|
{hasPermission ? 'GRANTED' : 'DENIED'}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<View style={styles.systemStatusCard}>
|
||||||
|
<Text style={styles.systemStatusIcon}>📁</Text>
|
||||||
|
<View>
|
||||||
|
<Text style={styles.systemStatusLabel}>Storage</Text>
|
||||||
|
<Text style={[styles.systemStatusValue, { color: COLORS.success }]}>
|
||||||
|
GRANTED
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<TouchableOpacity style={styles.reconnectButton} onPress={connectSocket}>
|
||||||
|
<Text style={styles.reconnectText}>↻ Reconnect</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
{/* Latest SMS */}
|
||||||
|
{latestSms && (
|
||||||
|
<>
|
||||||
|
<Text style={styles.sectionTitle}>Latest SMS Request</Text>
|
||||||
|
<View style={styles.latestSmsCard}>
|
||||||
|
<Text style={styles.latestSmsText}>📱 Phone: <Text style={styles.latestSmsHighlight}>{latestSms.phone}</Text></Text>
|
||||||
|
<Text style={styles.latestSmsText}>💬 Message: <Text style={styles.latestSmsHighlight}>'{latestSms.message}'</Text></Text>
|
||||||
|
</View>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
/>
|
|
||||||
</View>
|
{/* Logs */}
|
||||||
|
<View style={styles.logHeader}>
|
||||||
|
<Text style={styles.sectionTitle}>Recent Logs</Text>
|
||||||
|
<TouchableOpacity onPress={() => setLogs([])} style={styles.clearButton}>
|
||||||
|
<Text style={styles.clearButtonText}>Clear Logs</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.logsCard}>
|
||||||
|
{logs.map((item) => (
|
||||||
|
<View key={item.id} style={styles.logItem}>
|
||||||
|
<Text style={[styles.logTime, { color: item.type === 'error' ? COLORS.error : (item.type === 'success' ? COLORS.success : COLORS.textMuted) }]}>
|
||||||
|
[{item.time}]
|
||||||
|
</Text>
|
||||||
|
<Text style={[styles.logMsg, { color: item.type === 'error' ? COLORS.error : (item.type === 'success' ? COLORS.success : COLORS.textMuted) }]}>
|
||||||
|
{item.msg}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
{logs.length === 0 && (
|
||||||
|
<Text style={styles.emptyLogs}>No recent activity.</Text>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
</ScrollView>
|
||||||
|
</SafeAreaView>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
backgroundColor: '#f5f5f5',
|
backgroundColor: COLORS.background,
|
||||||
},
|
},
|
||||||
headerButton: {
|
header: {
|
||||||
marginRight: 15,
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
paddingHorizontal: 24,
|
||||||
|
paddingVertical: 16,
|
||||||
|
marginTop: 8,
|
||||||
},
|
},
|
||||||
headerButtonText: {
|
headerLeft: {
|
||||||
color: '#208AEF',
|
flexDirection: 'row',
|
||||||
fontSize: 16,
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
headerIcon: {
|
||||||
|
fontSize: 24,
|
||||||
|
marginRight: 8,
|
||||||
|
},
|
||||||
|
headerTitle: {
|
||||||
|
color: COLORS.text,
|
||||||
|
fontSize: 20,
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
},
|
},
|
||||||
statusCard: {
|
settingsIcon: {
|
||||||
backgroundColor: '#fff',
|
fontSize: 24,
|
||||||
margin: 15,
|
color: COLORS.textMuted,
|
||||||
padding: 20,
|
|
||||||
borderRadius: 10,
|
|
||||||
alignItems: 'center',
|
|
||||||
shadowColor: '#000',
|
|
||||||
shadowOffset: { width: 0, height: 2 },
|
|
||||||
shadowOpacity: 0.1,
|
|
||||||
shadowRadius: 4,
|
|
||||||
elevation: 3,
|
|
||||||
},
|
},
|
||||||
statusLabel: {
|
scrollContent: {
|
||||||
fontSize: 16,
|
flex: 1,
|
||||||
color: '#666',
|
},
|
||||||
|
scrollContentContainer: {
|
||||||
|
padding: 24,
|
||||||
|
paddingTop: 8,
|
||||||
|
},
|
||||||
|
mainStatusCard: {
|
||||||
|
backgroundColor: COLORS.surface,
|
||||||
|
borderRadius: 24,
|
||||||
|
padding: 32,
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: 24,
|
||||||
|
},
|
||||||
|
statusIconContainer: {
|
||||||
|
width: 48,
|
||||||
|
height: 48,
|
||||||
|
borderRadius: 24,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
marginBottom: 16,
|
||||||
|
},
|
||||||
|
statusIcon: {
|
||||||
|
fontSize: 24,
|
||||||
|
color: COLORS.success,
|
||||||
},
|
},
|
||||||
statusValue: {
|
statusValue: {
|
||||||
fontSize: 24,
|
fontSize: 28,
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
marginTop: 5,
|
marginBottom: 8,
|
||||||
|
},
|
||||||
|
statusDesc: {
|
||||||
|
color: COLORS.textMuted,
|
||||||
|
fontSize: 14,
|
||||||
|
textAlign: 'center',
|
||||||
|
},
|
||||||
|
sectionTitle: {
|
||||||
|
color: COLORS.text,
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: '600',
|
||||||
|
marginBottom: 12,
|
||||||
|
marginTop: 8,
|
||||||
|
},
|
||||||
|
systemStatusRow: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
marginBottom: 24,
|
||||||
|
},
|
||||||
|
systemStatusCard: {
|
||||||
|
backgroundColor: COLORS.surface,
|
||||||
|
borderRadius: 16,
|
||||||
|
padding: 16,
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
flex: 0.48,
|
||||||
|
},
|
||||||
|
systemStatusIcon: {
|
||||||
|
fontSize: 20,
|
||||||
|
marginRight: 12,
|
||||||
|
},
|
||||||
|
systemStatusLabel: {
|
||||||
|
color: COLORS.text,
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: '500',
|
||||||
|
},
|
||||||
|
systemStatusValue: {
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
marginTop: 2,
|
||||||
},
|
},
|
||||||
reconnectButton: {
|
reconnectButton: {
|
||||||
paddingVertical: 8,
|
backgroundColor: COLORS.primary,
|
||||||
paddingHorizontal: 20,
|
borderRadius: 9999,
|
||||||
backgroundColor: '#eee',
|
paddingVertical: 16,
|
||||||
borderRadius: 20,
|
alignItems: 'center',
|
||||||
},
|
marginBottom: 24,
|
||||||
buttonRow: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
marginTop: 15,
|
|
||||||
justifyContent: 'center',
|
|
||||||
},
|
},
|
||||||
reconnectText: {
|
reconnectText: {
|
||||||
color: '#333',
|
color: COLORS.onPrimary,
|
||||||
fontWeight: '600',
|
fontSize: 16,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
},
|
||||||
|
latestSmsCard: {
|
||||||
|
backgroundColor: COLORS.surface,
|
||||||
|
borderRadius: 24,
|
||||||
|
padding: 20,
|
||||||
|
marginBottom: 24,
|
||||||
|
},
|
||||||
|
latestSmsText: {
|
||||||
|
color: COLORS.textMuted,
|
||||||
|
fontSize: 14,
|
||||||
|
marginBottom: 8,
|
||||||
|
lineHeight: 20,
|
||||||
|
},
|
||||||
|
latestSmsHighlight: {
|
||||||
|
color: COLORS.text,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
fontFamily: Platform.OS === 'ios' ? 'Menlo' : 'monospace',
|
||||||
},
|
},
|
||||||
logHeader: {
|
logHeader: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
paddingRight: 15,
|
|
||||||
marginBottom: 10,
|
|
||||||
},
|
},
|
||||||
clearButton: {
|
clearButton: {
|
||||||
padding: 5,
|
backgroundColor: COLORS.surfaceHigh,
|
||||||
|
paddingHorizontal: 12,
|
||||||
|
paddingVertical: 6,
|
||||||
|
borderRadius: 12,
|
||||||
},
|
},
|
||||||
clearButtonText: {
|
clearButtonText: {
|
||||||
color: '#208AEF',
|
color: COLORS.text,
|
||||||
fontWeight: 'bold',
|
fontSize: 12,
|
||||||
|
fontWeight: '500',
|
||||||
},
|
},
|
||||||
logTitle: {
|
logsCard: {
|
||||||
fontSize: 18,
|
backgroundColor: COLORS.surface,
|
||||||
fontWeight: 'bold',
|
borderRadius: 24,
|
||||||
marginLeft: 15,
|
padding: 20,
|
||||||
color: '#333',
|
minHeight: 150,
|
||||||
},
|
|
||||||
logList: {
|
|
||||||
flex: 1,
|
|
||||||
backgroundColor: '#fff',
|
|
||||||
},
|
},
|
||||||
logItem: {
|
logItem: {
|
||||||
padding: 15,
|
|
||||||
borderBottomWidth: 1,
|
|
||||||
borderBottomColor: '#eee',
|
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
|
marginBottom: 8,
|
||||||
},
|
},
|
||||||
logTime: {
|
logTime: {
|
||||||
color: '#888',
|
fontSize: 13,
|
||||||
fontSize: 12,
|
fontFamily: Platform.OS === 'ios' ? 'Menlo' : 'monospace',
|
||||||
width: 80,
|
marginRight: 8,
|
||||||
},
|
},
|
||||||
logMsg: {
|
logMsg: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
color: '#333',
|
fontSize: 13,
|
||||||
fontSize: 14,
|
fontFamily: Platform.OS === 'ios' ? 'Menlo' : 'monospace',
|
||||||
|
},
|
||||||
|
emptyLogs: {
|
||||||
|
color: COLORS.textMuted,
|
||||||
|
fontSize: 13,
|
||||||
|
fontStyle: 'italic',
|
||||||
|
textAlign: 'center',
|
||||||
|
marginTop: 20,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -1,12 +1,25 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { StyleSheet, View, Text, TextInput, TouchableOpacity, Switch, ScrollView } from 'react-native';
|
import { StyleSheet, View, Text, TextInput, TouchableOpacity, ScrollView } from 'react-native';
|
||||||
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||||
import { Stack } from 'expo-router';
|
import { Stack, router } from 'expo-router';
|
||||||
import { useKeepAwake } from 'expo-keep-awake';
|
import { useKeepAwake } from 'expo-keep-awake';
|
||||||
// We will import socket logic later
|
|
||||||
|
const COLORS = {
|
||||||
|
background: '#121416',
|
||||||
|
surface: '#1e2022',
|
||||||
|
surfaceHigh: '#282a2c',
|
||||||
|
primary: '#fefcff',
|
||||||
|
onPrimary: '#213148',
|
||||||
|
text: '#e2e2e5',
|
||||||
|
textMuted: '#c5c6cd',
|
||||||
|
success: '#69fc9b',
|
||||||
|
error: '#ffb4ab',
|
||||||
|
border: '#44474d',
|
||||||
|
};
|
||||||
|
|
||||||
export default function SettingsScreen() {
|
export default function SettingsScreen() {
|
||||||
useKeepAwake(); // Keep the screen awake
|
useKeepAwake();
|
||||||
|
|
||||||
const [endpoint, setEndpoint] = useState('http://192.168.1.x:3001');
|
const [endpoint, setEndpoint] = useState('http://192.168.1.x:3001');
|
||||||
const [login, setLogin] = useState('');
|
const [login, setLogin] = useState('');
|
||||||
@@ -38,108 +51,162 @@ export default function SettingsScreen() {
|
|||||||
await AsyncStorage.setItem('password', password);
|
await AsyncStorage.setItem('password', password);
|
||||||
setIsSaved(true);
|
setIsSaved(true);
|
||||||
setTimeout(() => setIsSaved(false), 2000);
|
setTimeout(() => setIsSaved(false), 2000);
|
||||||
|
|
||||||
// In a real app, we would trigger a reconnect here
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Failed to save settings', e);
|
console.error('Failed to save settings', e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollView style={styles.container}>
|
<SafeAreaView style={styles.container}>
|
||||||
<View style={styles.section}>
|
<Stack.Screen options={{ headerShown: false }} />
|
||||||
<Text style={styles.label}>Backend Endpoint</Text>
|
|
||||||
<TextInput
|
<View style={styles.header}>
|
||||||
style={styles.input}
|
<TouchableOpacity onPress={() => router.back()} style={styles.backButton}>
|
||||||
value={endpoint}
|
<Text style={styles.backIcon}>←</Text>
|
||||||
onChangeText={setEndpoint}
|
</TouchableOpacity>
|
||||||
placeholder="http://192.168.1.x:3001"
|
<Text style={styles.headerTitle}>Settings</Text>
|
||||||
autoCapitalize="none"
|
<View style={{ width: 40 }} />
|
||||||
keyboardType="url"
|
|
||||||
/>
|
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View style={styles.section}>
|
<ScrollView style={styles.scrollContent} contentContainerStyle={styles.scrollContentContainer}>
|
||||||
<Text style={styles.label}>Login</Text>
|
|
||||||
<TextInput
|
<View style={styles.card}>
|
||||||
style={styles.input}
|
<View style={styles.section}>
|
||||||
value={login}
|
<Text style={styles.label}>Backend Endpoint</Text>
|
||||||
onChangeText={setLogin}
|
<TextInput
|
||||||
placeholder="admin"
|
style={styles.input}
|
||||||
autoCapitalize="none"
|
value={endpoint}
|
||||||
/>
|
onChangeText={setEndpoint}
|
||||||
</View>
|
placeholder="http://192.168.1.x:3001"
|
||||||
|
placeholderTextColor={COLORS.border}
|
||||||
|
autoCapitalize="none"
|
||||||
|
keyboardType="url"
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
<View style={styles.section}>
|
<View style={styles.section}>
|
||||||
<Text style={styles.label}>Password</Text>
|
<Text style={styles.label}>Login</Text>
|
||||||
<TextInput
|
<TextInput
|
||||||
style={styles.input}
|
style={styles.input}
|
||||||
value={password}
|
value={login}
|
||||||
onChangeText={setPassword}
|
onChangeText={setLogin}
|
||||||
placeholder="admin123"
|
placeholder="admin"
|
||||||
secureTextEntry
|
placeholderTextColor={COLORS.border}
|
||||||
/>
|
autoCapitalize="none"
|
||||||
</View>
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
<TouchableOpacity style={styles.button} onPress={saveSettings}>
|
<View style={styles.section}>
|
||||||
<Text style={styles.buttonText}>{isSaved ? 'Saved!' : 'Save Settings'}</Text>
|
<Text style={styles.label}>Password</Text>
|
||||||
</TouchableOpacity>
|
<TextInput
|
||||||
|
style={styles.input}
|
||||||
|
value={password}
|
||||||
|
onChangeText={setPassword}
|
||||||
|
placeholder="admin123"
|
||||||
|
placeholderTextColor={COLORS.border}
|
||||||
|
secureTextEntry
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
<View style={styles.infoBox}>
|
<TouchableOpacity style={styles.button} onPress={saveSettings}>
|
||||||
<Text style={styles.infoText}>
|
<Text style={styles.buttonText}>{isSaved ? 'Saved! ✅' : 'Save Configuration'}</Text>
|
||||||
Note: This app will keep the screen awake while running to ensure background SMS sending works reliably.
|
</TouchableOpacity>
|
||||||
</Text>
|
|
||||||
</View>
|
<View style={styles.infoBox}>
|
||||||
</ScrollView>
|
<Text style={styles.infoIcon}>ℹ️</Text>
|
||||||
|
<Text style={styles.infoText}>
|
||||||
|
This app will keep the screen awake while running to ensure background SMS sending works reliably.
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</ScrollView>
|
||||||
|
</SafeAreaView>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
padding: 20,
|
backgroundColor: COLORS.background,
|
||||||
backgroundColor: '#f5f5f5',
|
},
|
||||||
|
header: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
paddingHorizontal: 16,
|
||||||
|
paddingVertical: 16,
|
||||||
|
marginTop: 8,
|
||||||
|
},
|
||||||
|
backButton: {
|
||||||
|
padding: 8,
|
||||||
|
width: 40,
|
||||||
|
},
|
||||||
|
backIcon: {
|
||||||
|
color: COLORS.text,
|
||||||
|
fontSize: 24,
|
||||||
|
},
|
||||||
|
headerTitle: {
|
||||||
|
color: COLORS.text,
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
},
|
||||||
|
scrollContent: {
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
scrollContentContainer: {
|
||||||
|
padding: 24,
|
||||||
|
},
|
||||||
|
card: {
|
||||||
|
backgroundColor: COLORS.surface,
|
||||||
|
borderRadius: 24,
|
||||||
|
padding: 24,
|
||||||
|
marginBottom: 24,
|
||||||
},
|
},
|
||||||
section: {
|
section: {
|
||||||
marginBottom: 20,
|
marginBottom: 20,
|
||||||
},
|
},
|
||||||
label: {
|
label: {
|
||||||
fontSize: 16,
|
fontSize: 14,
|
||||||
fontWeight: 'bold',
|
fontWeight: '600',
|
||||||
marginBottom: 8,
|
marginBottom: 8,
|
||||||
color: '#333',
|
color: COLORS.text,
|
||||||
},
|
},
|
||||||
input: {
|
input: {
|
||||||
backgroundColor: '#fff',
|
backgroundColor: COLORS.background,
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
borderColor: '#ddd',
|
borderColor: COLORS.border,
|
||||||
borderRadius: 8,
|
borderRadius: 12,
|
||||||
padding: 12,
|
padding: 16,
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
|
color: COLORS.text,
|
||||||
},
|
},
|
||||||
button: {
|
button: {
|
||||||
backgroundColor: '#208AEF',
|
backgroundColor: COLORS.primary,
|
||||||
padding: 15,
|
padding: 16,
|
||||||
borderRadius: 8,
|
borderRadius: 9999,
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
marginTop: 10,
|
marginBottom: 24,
|
||||||
},
|
},
|
||||||
buttonText: {
|
buttonText: {
|
||||||
color: '#fff',
|
color: COLORS.onPrimary,
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
},
|
},
|
||||||
infoBox: {
|
infoBox: {
|
||||||
marginTop: 30,
|
flexDirection: 'row',
|
||||||
padding: 15,
|
padding: 16,
|
||||||
backgroundColor: '#e8f4fd',
|
backgroundColor: COLORS.surfaceHigh,
|
||||||
borderRadius: 8,
|
borderRadius: 16,
|
||||||
borderLeftWidth: 4,
|
alignItems: 'center',
|
||||||
borderLeftColor: '#208AEF',
|
},
|
||||||
|
infoIcon: {
|
||||||
|
fontSize: 20,
|
||||||
|
marginRight: 12,
|
||||||
},
|
},
|
||||||
infoText: {
|
infoText: {
|
||||||
color: '#555',
|
flex: 1,
|
||||||
fontSize: 14,
|
color: COLORS.textMuted,
|
||||||
|
fontSize: 13,
|
||||||
lineHeight: 20,
|
lineHeight: 20,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -1,19 +1,21 @@
|
|||||||
import { NativeTabs } from 'expo-router/unstable-native-tabs';
|
import { NativeTabs } from 'expo-router/unstable-native-tabs';
|
||||||
import { useColorScheme } from 'react-native';
|
|
||||||
|
|
||||||
import { Colors } from '@/constants/theme';
|
const COLORS = {
|
||||||
|
background: '#121416',
|
||||||
|
surface: '#1e2022',
|
||||||
|
primary: '#fefcff',
|
||||||
|
text: '#e2e2e5',
|
||||||
|
textMuted: '#c5c6cd',
|
||||||
|
};
|
||||||
|
|
||||||
export default function AppTabs() {
|
export default function AppTabs() {
|
||||||
const scheme = useColorScheme();
|
|
||||||
const colors = Colors[scheme === 'unspecified' ? 'light' : scheme];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NativeTabs
|
<NativeTabs
|
||||||
backgroundColor={colors.background}
|
backgroundColor={COLORS.background}
|
||||||
indicatorColor={colors.backgroundElement}
|
indicatorColor={COLORS.surface}
|
||||||
labelStyle={{ selected: { color: colors.text } }}>
|
labelStyle={{ selected: { color: COLORS.primary }, unselected: { color: COLORS.textMuted } }}>
|
||||||
<NativeTabs.Trigger name="index">
|
<NativeTabs.Trigger name="index">
|
||||||
<NativeTabs.Trigger.Label>Home</NativeTabs.Trigger.Label>
|
<NativeTabs.Trigger.Label>Dashboard</NativeTabs.Trigger.Label>
|
||||||
<NativeTabs.Trigger.Icon
|
<NativeTabs.Trigger.Icon
|
||||||
src={require('@/assets/images/tabIcons/home.png')}
|
src={require('@/assets/images/tabIcons/home.png')}
|
||||||
renderingMode="template"
|
renderingMode="template"
|
||||||
@@ -29,4 +31,4 @@ export default function AppTabs() {
|
|||||||
</NativeTabs.Trigger>
|
</NativeTabs.Trigger>
|
||||||
</NativeTabs>
|
</NativeTabs>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user