diff --git a/app/settings.tsx b/app/settings.tsx
index c929676..968aa09 100644
--- a/app/settings.tsx
+++ b/app/settings.tsx
@@ -1,26 +1,23 @@
-import { useAppStore } from '@/src/store';
+import { useRouter } from 'expo-router';
import React from 'react';
-import { Switch, Text, View } from 'react-native';
+import { SafeAreaView } from 'react-native-safe-area-context';
+import { MobileSettings } from '@/src/components/mobile/MobileSettings';
+import { useAppStore } from '@/src/store';
+import mobileAuthService from '@/src/services/mobileAuth';
export default function SettingsScreen() {
- const { theme, setTheme } = useAppStore();
- const isDark = theme === 'dark';
+ const router = useRouter();
+ const { logout } = useAppStore();
- return (
-
-
- Settings
-
+ const handleSignOut = async () => {
+ await mobileAuthService.logout();
+ logout();
+ router.replace('/');
+ };
-
-
- Dark Mode
-
- setTheme(value ? 'dark' : 'light')}
- />
-
-
- );
+ return (
+
+
+
+ );
}
diff --git a/src/components/mobile/MobileSettings.tsx b/src/components/mobile/MobileSettings.tsx
index 9f16842..7c35d99 100644
--- a/src/components/mobile/MobileSettings.tsx
+++ b/src/components/mobile/MobileSettings.tsx
@@ -5,6 +5,7 @@ import {
ScrollView,
TouchableOpacity,
Alert,
+ ActivityIndicator,
} from 'react-native';
import {
User,
@@ -26,10 +27,12 @@ import {
Play,
Vibrate,
LogOut,
+ FingerprintPattern,
} from 'lucide-react-native';
import { useAppStore } from '../../store';
import { useSettingsStore } from '../../store/settingsStore';
import { useNotificationStore } from '../../store/notificationStore';
+import { useBiometricAuth } from '../../hooks/useBiometricAuth';
import { NativeToggle } from './NativeToggle';
import { SettingsPicker, PickerOption } from './SettingsPicker';
import { SettingsSection } from './SettingsSection';
@@ -154,6 +157,29 @@ export function MobileSettings({
}: MobileSettingsProps) {
const { theme, setTheme } = useAppStore();
+ const {
+ isAvailable: biometricAvailable,
+ isEnabled: biometricEnabled,
+ biometricType,
+ enable: enableBiometric,
+ disable: disableBiometric,
+ isLoading: biometricLoading,
+ } = useBiometricAuth();
+
+ const handleBiometricToggle = async (value: boolean) => {
+ if (value) {
+ const success = await enableBiometric();
+ if (!success) {
+ Alert.alert(
+ 'Biometric Login',
+ 'Could not enable biometric login. Please check your device settings.',
+ );
+ }
+ } else {
+ await disableBiometric();
+ }
+ };
+
const {
profileVisibility, setProfileVisibility,
twoFactorEnabled, setTwoFactorEnabled,
@@ -240,6 +266,31 @@ export function MobileSettings({
/>
}
/>
+ {biometricAvailable && (
+
+ :
+ }
+ label={
+ biometricType === 'face'
+ ? 'Face ID Login'
+ : biometricType === 'iris'
+ ? 'Iris Login'
+ : 'Fingerprint Login'
+ }
+ description={biometricEnabled ? 'Enabled — sign in without a password' : 'Disabled'}
+ right={
+
+ }
+ />
+ )}
}
diff --git a/src/services/socket/index.ts b/src/services/socket/index.ts
index 2bbd149..4582493 100644
--- a/src/services/socket/index.ts
+++ b/src/services/socket/index.ts
@@ -2,30 +2,92 @@ import { io, Socket } from "socket.io-client";
import logger from "../../utils/logger";
import { getEnv } from "../../config";
+// ─── Reconnection config ──────────────────────────────────────────────────────
+
+const RECONNECTION_ATTEMPTS = 10;
+const RECONNECTION_DELAY_MS = 1_000; // initial delay
+const RECONNECTION_DELAY_MAX_MS = 30_000; // cap at 30 s
+
class SocketService {
private socket: Socket | null = null;
connect() {
+ if (this.socket?.connected) return this.socket;
+
if (!this.socket) {
const socketUrl = getEnv("EXPO_PUBLIC_SOCKET_URL");
this.socket = io(socketUrl, {
transports: ["websocket"],
autoConnect: true,
+ // ── Reconnection ──────────────────────────────────────────────────
+ reconnection: true,
+ reconnectionAttempts: RECONNECTION_ATTEMPTS,
+ reconnectionDelay: RECONNECTION_DELAY_MS,
+ reconnectionDelayMax: RECONNECTION_DELAY_MAX_MS,
+ randomizationFactor: 0.5, // jitter to avoid thundering herd
});
+ // ── Connection lifecycle ──────────────────────────────────────────
+
this.socket.on("connect", () => {
logger.info("Socket connected:", this.socket?.id);
});
- this.socket.on("disconnect", () => {
- logger.info("Socket disconnected");
+ this.socket.on("disconnect", (reason: string) => {
+ logger.warn("Socket disconnected:", reason);
+ // socket.io auto-reconnects unless the server explicitly closed it
+ if (reason === "io server disconnect") {
+ // Server forced disconnect — reconnect manually
+ this.socket?.connect();
+ }
});
- this.socket.on("error", (error) => {
+ this.socket.on("error", (error: unknown) => {
logger.error("Socket error:", error);
});
+
+ // ── Reconnection listeners ────────────────────────────────────────
+
+ this.socket.on("reconnect_attempt", (attempt: number) => {
+ logger.info(`Socket reconnection attempt #${attempt}`);
+ });
+
+ this.socket.on("reconnect", (attempt: number) => {
+ logger.info(`Socket reconnected after ${attempt} attempt(s)`);
+ });
+
+ this.socket.on("reconnect_error", (error: unknown) => {
+ logger.warn("Socket reconnection error:", error);
+ });
+
+ this.socket.on("reconnect_failed", () => {
+ logger.error(
+ `Socket failed to reconnect after ${RECONNECTION_ATTEMPTS} attempts`
+ );
+ });
+
+ // ── Real-time event handlers ──────────────────────────────────────
+
+ this.socket.on("notification_created", (notification: any) => {
+ logger.info("New notification received:", notification);
+ // TODO: Handle notification display/storage
+ // This could trigger a notification banner, update notification count, etc.
+ });
+
+ this.socket.on("course_updated", (courseData: any) => {
+ logger.info("Course updated:", courseData);
+ // TODO: Handle course data refresh
+ // This could update cached course data, refresh UI components, etc.
+ });
+
+ this.socket.on("message_received", (message: any) => {
+ logger.info("New message received:", message);
+ // TODO: Handle new message
+ // This could update chat UI, show message notification, etc.
+ });
}
+
return this.socket;
}
@@ -53,6 +115,11 @@ class SocketService {
this.socket.off(event);
}
}
+
+ /** Returns true when the underlying socket is currently connected. */
+ get isConnected(): boolean {
+ return this.socket?.connected ?? false;
+ }
}
-export default new SocketService();
\ No newline at end of file
+export default new SocketService();