11/* eslint-disable react/react-in-jsx-scope */
22import { Env } from '@env' ;
33import { useColorScheme } from 'nativewind' ;
4- import React , { useEffect } from 'react' ;
4+ import React , { useCallback , useEffect } from 'react' ;
55import { useTranslation } from 'react-i18next' ;
66
77import { BackgroundGeolocationItem } from '@/components/settings/background-geolocation-item' ;
@@ -15,16 +15,35 @@ import { ThemeItem } from '@/components/settings/theme-item';
1515import { ToggleItem } from '@/components/settings/toggle-item' ;
1616import { UnitSelectionBottomSheet } from '@/components/settings/unit-selection-bottom-sheet' ;
1717import { FocusAwareStatusBar , ScrollView } from '@/components/ui' ;
18+ import { AlertDialog , AlertDialogBackdrop , AlertDialogBody , AlertDialogContent , AlertDialogFooter , AlertDialogHeader } from '@/components/ui/alert-dialog' ;
1819import { Box } from '@/components/ui/box' ;
20+ import { Button , ButtonText } from '@/components/ui/button' ;
1921import { Card } from '@/components/ui/card' ;
2022import { Heading } from '@/components/ui/heading' ;
23+ import { Text } from '@/components/ui/text' ;
2124import { VStack } from '@/components/ui/vstack' ;
2225import { useAnalytics } from '@/hooks/use-analytics' ;
2326import { useAuth , useAuthStore } from '@/lib' ;
2427import { logger } from '@/lib/logging' ;
25- import { getBaseApiUrl } from '@/lib/storage/app' ;
28+ import { storage } from '@/lib/storage' ;
29+ import { getBaseApiUrl , removeActiveCallId , removeActiveUnitId , removeDeviceUuid } from '@/lib/storage/app' ;
2630import { openLinkInBrowser } from '@/lib/utils' ;
31+ import { useAudioStreamStore } from '@/stores/app/audio-stream-store' ;
32+ import { useBluetoothAudioStore } from '@/stores/app/bluetooth-audio-store' ;
2733import { useCoreStore } from '@/stores/app/core-store' ;
34+ import { useLiveKitStore } from '@/stores/app/livekit-store' ;
35+ import { useLoadingStore } from '@/stores/app/loading-store' ;
36+ import { useLocationStore } from '@/stores/app/location-store' ;
37+ import { useCallsStore } from '@/stores/calls/store' ;
38+ import { useContactsStore } from '@/stores/contacts/store' ;
39+ import { useDispatchStore } from '@/stores/dispatch/store' ;
40+ import { useNotesStore } from '@/stores/notes/store' ;
41+ import { useOfflineQueueStore } from '@/stores/offline-queue/store' ;
42+ import { useProtocolsStore } from '@/stores/protocols/store' ;
43+ import { usePushNotificationModalStore } from '@/stores/push-notification/store' ;
44+ import { useRolesStore } from '@/stores/roles/store' ;
45+ import { securityStore } from '@/stores/security/store' ;
46+ import { useStatusBottomSheetStore } from '@/stores/status/store' ;
2847import { useUnitsStore } from '@/stores/units/store' ;
2948
3049export default function Settings ( ) {
@@ -36,6 +55,7 @@ export default function Settings() {
3655 const { login, status, isAuthenticated } = useAuth ( ) ;
3756 const [ showServerUrl , setShowServerUrl ] = React . useState ( false ) ;
3857 const [ showUnitSelection , setShowUnitSelection ] = React . useState ( false ) ;
58+ const [ showLogoutConfirm , setShowLogoutConfirm ] = React . useState ( false ) ;
3959 const activeUnit = useCoreStore ( ( state ) => state . activeUnit ) ;
4060 const { units } = useUnitsStore ( ) ;
4161
@@ -44,6 +64,223 @@ export default function Settings() {
4464 return activeUnit ?. Name || t ( 'common.unknown' ) ;
4565 } , [ activeUnit , t ] ) ;
4666
67+ /**
68+ * Clears all app data, cached values, settings, and stores
69+ * Called when user confirms logout
70+ */
71+ const clearAllAppData = useCallback ( async ( ) => {
72+ logger . info ( {
73+ message : 'Clearing all app data on logout' ,
74+ } ) ;
75+
76+ try {
77+ // Clear persisted storage items
78+ removeActiveUnitId ( ) ;
79+ removeActiveCallId ( ) ;
80+ removeDeviceUuid ( ) ;
81+
82+ // Clear all MMKV storage except first time flag and user preferences
83+ const allKeys = storage . getAllKeys ( ) ;
84+ const keysToPreserve = [ 'IS_FIRST_TIME' ] ;
85+ allKeys . forEach ( ( key ) => {
86+ if ( ! keysToPreserve . includes ( key ) ) {
87+ storage . delete ( key ) ;
88+ }
89+ } ) ;
90+
91+ // Reset all zustand stores to their initial states
92+ // Core stores
93+ useCoreStore . setState ( {
94+ activeUnitId : null ,
95+ activeUnit : null ,
96+ activeUnitStatus : null ,
97+ activeUnitStatusType : null ,
98+ activeCallId : null ,
99+ activeCall : null ,
100+ activePriority : null ,
101+ config : null ,
102+ isLoading : false ,
103+ isInitialized : false ,
104+ isInitializing : false ,
105+ error : null ,
106+ activeStatuses : null ,
107+ } ) ;
108+
109+ // Calls store
110+ useCallsStore . setState ( {
111+ calls : [ ] ,
112+ callPriorities : [ ] ,
113+ callTypes : [ ] ,
114+ isLoading : false ,
115+ error : null ,
116+ } ) ;
117+
118+ // Units store
119+ useUnitsStore . setState ( {
120+ units : [ ] ,
121+ unitStatuses : [ ] ,
122+ isLoading : false ,
123+ error : null ,
124+ } ) ;
125+
126+ // Contacts store
127+ useContactsStore . setState ( {
128+ contacts : [ ] ,
129+ contactNotes : { } ,
130+ searchQuery : '' ,
131+ selectedContactId : null ,
132+ isDetailsOpen : false ,
133+ isLoading : false ,
134+ isNotesLoading : false ,
135+ error : null ,
136+ } ) ;
137+
138+ // Notes store
139+ useNotesStore . setState ( {
140+ notes : [ ] ,
141+ searchQuery : '' ,
142+ selectedNoteId : null ,
143+ isDetailsOpen : false ,
144+ isLoading : false ,
145+ error : null ,
146+ } ) ;
147+
148+ // Roles store
149+ useRolesStore . setState ( {
150+ roles : [ ] ,
151+ unitRoleAssignments : [ ] ,
152+ users : [ ] ,
153+ isLoading : false ,
154+ error : null ,
155+ } ) ;
156+
157+ // Protocols store
158+ useProtocolsStore . setState ( {
159+ protocols : [ ] ,
160+ searchQuery : '' ,
161+ selectedProtocolId : null ,
162+ isDetailsOpen : false ,
163+ isLoading : false ,
164+ error : null ,
165+ } ) ;
166+
167+ // Dispatch store
168+ useDispatchStore . setState ( {
169+ data : {
170+ users : [ ] ,
171+ groups : [ ] ,
172+ roles : [ ] ,
173+ units : [ ] ,
174+ } ,
175+ selection : {
176+ everyone : false ,
177+ users : [ ] ,
178+ groups : [ ] ,
179+ roles : [ ] ,
180+ units : [ ] ,
181+ } ,
182+ isLoading : false ,
183+ error : null ,
184+ searchQuery : '' ,
185+ } ) ;
186+
187+ // Security store
188+ securityStore . setState ( {
189+ error : null ,
190+ rights : null ,
191+ } ) ;
192+
193+ // Status bottom sheet store
194+ useStatusBottomSheetStore . getState ( ) . reset ( ) ;
195+
196+ // Offline queue store
197+ useOfflineQueueStore . getState ( ) . clearAllEvents ( ) ;
198+
199+ // Loading store
200+ useLoadingStore . getState ( ) . resetLoading ( ) ;
201+
202+ // Location store
203+ useLocationStore . setState ( {
204+ latitude : null ,
205+ longitude : null ,
206+ heading : null ,
207+ accuracy : null ,
208+ speed : null ,
209+ altitude : null ,
210+ timestamp : null ,
211+ } ) ;
212+
213+ // LiveKit store - disconnect and reset
214+ const liveKitState = useLiveKitStore . getState ( ) ;
215+ if ( liveKitState . isConnected ) {
216+ liveKitState . disconnectFromRoom ( ) ;
217+ }
218+ useLiveKitStore . setState ( {
219+ isConnected : false ,
220+ isConnecting : false ,
221+ currentRoom : null ,
222+ currentRoomInfo : null ,
223+ isTalking : false ,
224+ availableRooms : [ ] ,
225+ isBottomSheetVisible : false ,
226+ } ) ;
227+
228+ // Audio stream store - cleanup and reset
229+ const audioStreamState = useAudioStreamStore . getState ( ) ;
230+ await audioStreamState . cleanup ( ) ;
231+ useAudioStreamStore . setState ( {
232+ availableStreams : [ ] ,
233+ currentStream : null ,
234+ isPlaying : false ,
235+ isLoading : false ,
236+ isBuffering : false ,
237+ isBottomSheetVisible : false ,
238+ } ) ;
239+
240+ // Bluetooth audio store
241+ useBluetoothAudioStore . setState ( {
242+ connectedDevice : null ,
243+ isScanning : false ,
244+ isConnecting : false ,
245+ availableDevices : [ ] ,
246+ connectionError : null ,
247+ isAudioRoutingActive : false ,
248+ } ) ;
249+
250+ // Push notification modal store
251+ usePushNotificationModalStore . setState ( {
252+ isOpen : false ,
253+ notification : null ,
254+ } ) ;
255+
256+ logger . info ( {
257+ message : 'Successfully cleared all app data' ,
258+ } ) ;
259+ } catch ( error ) {
260+ logger . error ( {
261+ message : 'Error clearing app data on logout' ,
262+ context : { error } ,
263+ } ) ;
264+ }
265+ } , [ ] ) ;
266+
267+ /**
268+ * Handles logout confirmation - clears all data and signs out
269+ */
270+ const handleLogoutConfirm = useCallback ( async ( ) => {
271+ setShowLogoutConfirm ( false ) ;
272+
273+ trackEvent ( 'user_logout_confirmed' , {
274+ hadActiveUnit : ! ! activeUnit ,
275+ } ) ;
276+
277+ // Clear all app data first
278+ await clearAllAppData ( ) ;
279+
280+ // Then sign out
281+ await signOut ( ) ;
282+ } , [ clearAllAppData , signOut , trackEvent , activeUnit ] ) ;
283+
47284 const handleLoginInfoSubmit = async ( data : { username : string ; password : string } ) => {
48285 logger . info ( {
49286 message : 'Updating login info' ,
@@ -89,7 +326,7 @@ export default function Settings() {
89326 < Item text = { t ( 'settings.server' ) } value = { getBaseApiUrl ( ) } onPress = { ( ) => setShowServerUrl ( true ) } textStyle = "text-info-600" />
90327 < Item text = { t ( 'settings.login_info' ) } onPress = { ( ) => setShowLoginInfo ( true ) } textStyle = "text-info-600" />
91328 < Item text = { t ( 'settings.active_unit' ) } value = { activeUnitName } onPress = { ( ) => setShowUnitSelection ( true ) } textStyle = "text-info-600" />
92- < Item text = { t ( 'settings.logout' ) } onPress = { signOut } textStyle = "text-error-600" />
329+ < Item text = { t ( 'settings.logout' ) } onPress = { ( ) => setShowLogoutConfirm ( true ) } textStyle = "text-error-600" />
93330 </ VStack >
94331 </ Card >
95332
@@ -122,6 +359,27 @@ export default function Settings() {
122359 < LoginInfoBottomSheet isOpen = { showLoginInfo } onClose = { ( ) => setShowLoginInfo ( false ) } onSubmit = { handleLoginInfoSubmit } />
123360 < ServerUrlBottomSheet isOpen = { showServerUrl } onClose = { ( ) => setShowServerUrl ( false ) } />
124361 < UnitSelectionBottomSheet isOpen = { showUnitSelection } onClose = { ( ) => setShowUnitSelection ( false ) } />
362+
363+ { /* Logout Confirmation Dialog */ }
364+ < AlertDialog isOpen = { showLogoutConfirm } onClose = { ( ) => setShowLogoutConfirm ( false ) } >
365+ < AlertDialogBackdrop />
366+ < AlertDialogContent >
367+ < AlertDialogHeader >
368+ < Heading size = "lg" > { t ( 'settings.logout_confirm_title' ) } </ Heading >
369+ </ AlertDialogHeader >
370+ < AlertDialogBody >
371+ < Text > { t ( 'settings.logout_confirm_message' ) } </ Text >
372+ </ AlertDialogBody >
373+ < AlertDialogFooter >
374+ < Button variant = "outline" action = "secondary" onPress = { ( ) => setShowLogoutConfirm ( false ) } >
375+ < ButtonText > { t ( 'common.cancel' ) } </ ButtonText >
376+ </ Button >
377+ < Button action = "negative" onPress = { handleLogoutConfirm } >
378+ < ButtonText > { t ( 'settings.logout' ) } </ ButtonText >
379+ </ Button >
380+ </ AlertDialogFooter >
381+ </ AlertDialogContent >
382+ </ AlertDialog >
125383 </ Box >
126384 ) ;
127385}
0 commit comments