change design

This commit is contained in:
Mekan1206
2026-06-02 12:54:28 +05:00
parent 627f1246db
commit 520b5a0916
3 changed files with 414 additions and 192 deletions

View File

@@ -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>
{!hasPermission && (
<TouchableOpacity style={[styles.reconnectButton, { backgroundColor: '#ffebee', marginLeft: 10 }]} onPress={checkAndRequestPermission}>
<Text style={[styles.reconnectText, { color: '#c62828' }]}>Req Permission</Text>
</TouchableOpacity>
)}
<SafeAreaView style={styles.container}>
<Stack.Screen options={{ headerShown: false }} />
<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,
}
});
});

View File

@@ -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.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>
<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.section}>
<Text style={styles.label}>Login</Text>
<TextInput
style={styles.input}
value={login}
onChangeText={setLogin}
placeholder="admin"
placeholderTextColor={COLORS.border}
autoCapitalize="none"
/>
</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}>Password</Text>
<TextInput
style={styles.input}
value={password}
onChangeText={setPassword}
placeholder="admin123"
placeholderTextColor={COLORS.border}
secureTextEntry
/>
</View>
</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>
<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,
}
});
});

View File

@@ -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"
@@ -29,4 +31,4 @@ export default function AppTabs() {
</NativeTabs.Trigger>
</NativeTabs>
);
}
}