a
This commit is contained in:
@@ -1,98 +1,252 @@
|
||||
import * as Device from 'expo-device';
|
||||
import { Platform, StyleSheet } from 'react-native';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import { StyleSheet, View, Text, FlatList, TouchableOpacity, PermissionsAndroid, Platform } from 'react-native';
|
||||
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';
|
||||
|
||||
import { AnimatedIcon } from '@/components/animated-icon';
|
||||
import { HintRow } from '@/components/hint-row';
|
||||
import { ThemedText } from '@/components/themed-text';
|
||||
import { ThemedView } from '@/components/themed-view';
|
||||
import { WebBadge } from '@/components/web-badge';
|
||||
import { BottomTabInset, MaxContentWidth, Spacing } from '@/constants/theme';
|
||||
export default function DashboardScreen() {
|
||||
useKeepAwake(); // Keep the screen awake
|
||||
|
||||
const [status, setStatus] = useState('Disconnected');
|
||||
const [hasPermission, setHasPermission] = useState(false);
|
||||
const [logs, setLogs] = useState<{id: string, time: string, msg: string}[]>([]);
|
||||
const socketRef = useRef<any>(null);
|
||||
|
||||
useEffect(() => {
|
||||
checkAndRequestPermission();
|
||||
connectSocket();
|
||||
|
||||
return () => {
|
||||
if (socketRef.current) {
|
||||
socketRef.current.disconnect();
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
const checkAndRequestPermission = async () => {
|
||||
if (Platform.OS === 'android') {
|
||||
try {
|
||||
const granted = await PermissionsAndroid.request(
|
||||
PermissionsAndroid.PERMISSIONS.SEND_SMS,
|
||||
{
|
||||
title: 'SMS Permission',
|
||||
message: 'This app needs access to send SMS messages directly.',
|
||||
buttonNeutral: 'Ask Me Later',
|
||||
buttonNegative: 'Cancel',
|
||||
buttonPositive: 'OK',
|
||||
},
|
||||
);
|
||||
if (granted === PermissionsAndroid.RESULTS.GRANTED) {
|
||||
setHasPermission(true);
|
||||
addLog('SMS permission granted');
|
||||
} else {
|
||||
setHasPermission(false);
|
||||
addLog('SMS permission denied');
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn(err);
|
||||
addLog(`Permission error: ${err}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const addLog = (msg: string) => {
|
||||
setLogs(prev => [{
|
||||
id: Math.random().toString(),
|
||||
time: new Date().toLocaleTimeString(),
|
||||
msg
|
||||
}, ...prev].slice(0, 50)); // Keep last 50 logs
|
||||
};
|
||||
|
||||
const connectSocket = async () => {
|
||||
try {
|
||||
const endpoint = await AsyncStorage.getItem('endpoint');
|
||||
const login = await AsyncStorage.getItem('login');
|
||||
const password = await AsyncStorage.getItem('password');
|
||||
|
||||
if (!endpoint || !login || !password) {
|
||||
setStatus('Missing Settings');
|
||||
addLog('Please configure settings first');
|
||||
return;
|
||||
}
|
||||
|
||||
if (socketRef.current) {
|
||||
socketRef.current.disconnect();
|
||||
}
|
||||
|
||||
setStatus('Connecting...');
|
||||
|
||||
const socket = io(endpoint, {
|
||||
auth: { login, password },
|
||||
reconnectionAttempts: Infinity, // Keep trying forever
|
||||
reconnectionDelay: 1000,
|
||||
reconnectionDelayMax: 5000,
|
||||
});
|
||||
|
||||
socket.on('connect', () => {
|
||||
setStatus('Connected');
|
||||
addLog('Connected to backend');
|
||||
});
|
||||
|
||||
socket.on('disconnect', () => {
|
||||
setStatus('Disconnected');
|
||||
addLog('Disconnected from backend');
|
||||
});
|
||||
|
||||
socket.on('connect_error', (err) => {
|
||||
setStatus('Error');
|
||||
addLog(`Connection error: ${err.message}`);
|
||||
});
|
||||
|
||||
socket.on('send_sms', async (data) => {
|
||||
addLog(`Received SMS request ID: ${data.id} to ${data.phone}`);
|
||||
|
||||
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.sendDirectSms(targetPhone, data.message)
|
||||
.then((res: any) => {
|
||||
addLog(`SMS sent successfully to ${targetPhone}`);
|
||||
socket.emit('sms_status', { id: data.id, status: 'sent' });
|
||||
})
|
||||
.catch((err: any) => {
|
||||
addLog(`SMS send failed: ${err}`);
|
||||
socket.emit('sms_status', { id: data.id, status: 'failed' });
|
||||
});
|
||||
} else {
|
||||
addLog('Direct SMS only supported on Android');
|
||||
socket.emit('sms_status', { id: data.id, status: 'failed' });
|
||||
}
|
||||
} catch (error) {
|
||||
addLog(`Error sending SMS: ${error}`);
|
||||
socket.emit('sms_status', { id: data.id, status: 'failed' });
|
||||
}
|
||||
});
|
||||
|
||||
socketRef.current = socket;
|
||||
|
||||
} catch (e) {
|
||||
addLog(`Setup error: ${e}`);
|
||||
}
|
||||
};
|
||||
|
||||
function getDevMenuHint() {
|
||||
if (Platform.OS === 'web') {
|
||||
return <ThemedText type="small">use browser devtools</ThemedText>;
|
||||
}
|
||||
if (Device.isDevice) {
|
||||
return (
|
||||
<ThemedText type="small">
|
||||
shake device or press <ThemedText type="code">m</ThemedText> in terminal
|
||||
</ThemedText>
|
||||
);
|
||||
}
|
||||
const shortcut = Platform.OS === 'android' ? 'cmd+m (or ctrl+m)' : 'cmd+d';
|
||||
return (
|
||||
<ThemedText type="small">
|
||||
press <ThemedText type="code">{shortcut}</ThemedText>
|
||||
</ThemedText>
|
||||
);
|
||||
}
|
||||
<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>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
export default function HomeScreen() {
|
||||
return (
|
||||
<ThemedView style={styles.container}>
|
||||
<SafeAreaView style={styles.safeArea}>
|
||||
<ThemedView style={styles.heroSection}>
|
||||
<AnimatedIcon />
|
||||
<ThemedText type="title" style={styles.title}>
|
||||
Welcome to Expo
|
||||
</ThemedText>
|
||||
</ThemedView>
|
||||
|
||||
<ThemedText type="code" style={styles.code}>
|
||||
get started
|
||||
</ThemedText>
|
||||
|
||||
<ThemedView type="backgroundElement" style={styles.stepContainer}>
|
||||
<HintRow
|
||||
title="Try editing"
|
||||
hint={<ThemedText type="code">src/app/index.tsx</ThemedText>}
|
||||
/>
|
||||
<HintRow title="Dev tools" hint={getDevMenuHint()} />
|
||||
<HintRow
|
||||
title="Fresh start"
|
||||
hint={<ThemedText type="code">npm run reset-project</ThemedText>}
|
||||
/>
|
||||
</ThemedView>
|
||||
|
||||
{Platform.OS === 'web' && <WebBadge />}
|
||||
</SafeAreaView>
|
||||
</ThemedView>
|
||||
<Text style={styles.logTitle}>Activity Log</Text>
|
||||
<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>
|
||||
</View>
|
||||
)}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: '#f5f5f5',
|
||||
},
|
||||
headerButton: {
|
||||
marginRight: 15,
|
||||
},
|
||||
headerButtonText: {
|
||||
color: '#208AEF',
|
||||
fontSize: 16,
|
||||
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,
|
||||
},
|
||||
statusLabel: {
|
||||
fontSize: 16,
|
||||
color: '#666',
|
||||
},
|
||||
statusValue: {
|
||||
fontSize: 24,
|
||||
fontWeight: 'bold',
|
||||
marginTop: 5,
|
||||
},
|
||||
reconnectButton: {
|
||||
paddingVertical: 8,
|
||||
paddingHorizontal: 20,
|
||||
backgroundColor: '#eee',
|
||||
borderRadius: 20,
|
||||
},
|
||||
buttonRow: {
|
||||
flexDirection: 'row',
|
||||
marginTop: 15,
|
||||
justifyContent: 'center',
|
||||
},
|
||||
reconnectText: {
|
||||
color: '#333',
|
||||
fontWeight: '600',
|
||||
},
|
||||
logTitle: {
|
||||
fontSize: 18,
|
||||
fontWeight: 'bold',
|
||||
marginLeft: 15,
|
||||
marginBottom: 10,
|
||||
color: '#333',
|
||||
},
|
||||
logList: {
|
||||
flex: 1,
|
||||
backgroundColor: '#fff',
|
||||
},
|
||||
logItem: {
|
||||
padding: 15,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: '#eee',
|
||||
flexDirection: 'row',
|
||||
},
|
||||
safeArea: {
|
||||
logTime: {
|
||||
color: '#888',
|
||||
fontSize: 12,
|
||||
width: 80,
|
||||
},
|
||||
logMsg: {
|
||||
flex: 1,
|
||||
paddingHorizontal: Spacing.four,
|
||||
alignItems: 'center',
|
||||
gap: Spacing.three,
|
||||
paddingBottom: BottomTabInset + Spacing.three,
|
||||
maxWidth: MaxContentWidth,
|
||||
},
|
||||
heroSection: {
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
flex: 1,
|
||||
paddingHorizontal: Spacing.four,
|
||||
gap: Spacing.four,
|
||||
},
|
||||
title: {
|
||||
textAlign: 'center',
|
||||
},
|
||||
code: {
|
||||
textTransform: 'uppercase',
|
||||
},
|
||||
stepContainer: {
|
||||
gap: Spacing.three,
|
||||
alignSelf: 'stretch',
|
||||
paddingHorizontal: Spacing.three,
|
||||
paddingVertical: Spacing.four,
|
||||
borderRadius: Spacing.four,
|
||||
},
|
||||
color: '#333',
|
||||
fontSize: 14,
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user