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