381 lines
17 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

@startuml
skinparam sequenceMessageAlign center
skinparam responseMessageBelowArrow true
skinparam maxMessageSize 150
skinparam sequence {
ParticipantBackgroundColor LightBlue
ActorBackgroundColor LightGray
LifeLineBorderColor gray
GroupBorderColor DarkGray
}
title WalkGuide System — Complete Sequence Diagram
' ==========================================
' PARTICIPANTS
' ==========================================
actor "Tunanetra\n(ROLE_USER)" as User
actor "Pendamping\n(ROLE_GUARDIAN)" as Guardian
participant "Flutter App\n(User Side)" as AppUser
participant "Flutter App\n(Guardian Side)" as AppGuardian
participant "Spring Boot\nBackend" as Backend
participant "JWT Auth\nFilter" as JWT
participant "WebSocket\n(STOMP)" as WS
participant "Firebase FCM" as FCM
participant "Agora RTC" as Agora
participant "YOLOv8n\n(On-Device AI)" as YOLO
' ============================================================
' 1. SERVER CONNECT
' ============================================================
== 1. Server Connect (Pertama Kali Install) ==
User -> AppUser : Buka aplikasi
AppUser -> AppUser : Cek SharedPreferences\n(serverUrl ada?)
alt serverUrl TIDAK ada
AppUser -> User : Tampilkan ServerConnectScreen
User -> AppUser : Input URL server\n(http://202.46.28.160:8080)
AppUser -> Backend : GET /api/v1/health (ping)
alt Server OK
Backend --> AppUser : 200 OK
AppUser -> AppUser : Simpan serverUrl\nke SharedPreferences
AppUser -> User : Redirect ke LoginScreen
else Server Gagal
Backend --> AppUser : Timeout / Error
AppUser -> User : Tampilkan pesan error\n"Server tidak dapat dijangkau"
end
else serverUrl sudah ada
AppUser -> User : Langsung ke SplashScreen
end
' ============================================================
' 2. AUTH — REGISTER
' ============================================================
== 2. Register Akun ==
User -> AppUser : Isi form Register\n(email, password, role)
AppUser -> Backend : POST /api/v1/auth/register\n{email, password, displayName, role}
Backend -> Backend : Validasi @NotBlank, @Email
Backend -> Backend : Cek email unik
Backend -> Backend : BCrypt encode password
alt role = ROLE_USER
Backend -> Backend : Generate uniqueUserId\n(12 char alphanumeric)
end
Backend -> Backend : Simpan user ke DB
Backend -> Backend : Buat default UserSettings
Backend -> Backend : Generate JWT access token (1 jam)\n+ refresh token (30 hari)
Backend -> Backend : Simpan refresh token ke DB
Backend --> AppUser : 201 Created\n{accessToken, refreshToken, role, userId, uniqueUserId}
AppUser -> AppUser : Simpan token ke\nFlutterSecureStorage
AppUser -> Backend : PUT /api/v1/shared/fcm-token\n{fcmToken}
Backend --> AppUser : 200 OK
AppUser -> User : Redirect ke Home Screen\n(berdasarkan role)
' ============================================================
' 3. AUTH — LOGIN
' ============================================================
== 3. Login ==
User -> AppUser : Input email & password
AppUser -> Backend : POST /api/v1/auth/login\n{email, password}
Backend -> Backend : findByEmail → validasi BCrypt
Backend -> Backend : Generate JWT access token\n+ refresh token
Backend -> Backend : Simpan refresh token ke DB
Backend -> Backend : Log ActivityLog: LOGIN
Backend --> AppUser : 200 OK\n{accessToken, refreshToken, role, userId}
AppUser -> AppUser : Simpan token ke\nFlutterSecureStorage
AppUser -> Backend : PUT /api/v1/shared/fcm-token\n{fcmToken}
Backend --> AppUser : 200 OK
AppUser -> WS : STOMP connect\n(Authorization: Bearer token)
WS --> AppUser : Connected
AppUser -> User : Redirect ke Home\n(ROLE_USER → /user/home)
Guardian -> AppGuardian : Login
AppGuardian -> Backend : POST /api/v1/auth/login
Backend --> AppGuardian : 200 OK {accessToken, role=ROLE_GUARDIAN}
AppGuardian -> WS : STOMP connect
WS --> AppGuardian : Connected
AppGuardian -> Guardian : Redirect ke /guardian/home
' ============================================================
' 4. AUTH — JWT VALIDATE & REFRESH
' ============================================================
== 4. JWT Validate & Auto-Refresh ==
AppUser -> Backend : Request API (any endpoint)\n[Authorization: Bearer accessToken]
Backend -> JWT : Validasi token
alt Token valid
JWT --> Backend : OK, userId + role
Backend -> Backend : RBAC check role
Backend --> AppUser : 200 Response
else Token expired (401)
JWT --> Backend : 401 Unauthorized
Backend --> AppUser : 401 Unauthorized
AppUser -> Backend : POST /api/v1/auth/refresh\n{refreshToken}
Backend -> Backend : Cek refresh token di DB\n+ cek expired
alt Refresh token valid
Backend -> Backend : Generate access token baru
Backend --> AppUser : 200 OK {accessToken baru}
AppUser -> AppUser : Update token di SecureStorage
AppUser -> Backend : Retry request original
Backend --> AppUser : 200 Response
else Refresh token expired
Backend --> AppUser : 401 Unauthorized
AppUser -> AppUser : Clear semua token
AppUser -> User : Redirect ke /login
end
end
' ============================================================
' 5. PAIRING
' ============================================================
== 5. Pairing — Guardian Undang User ==
Guardian -> AppGuardian : Input uniqueUserId\n(12 char) milik User
AppGuardian -> Backend : POST /api/v1/guardian/pairing/invite\n{uniqueUserId}
Backend -> Backend : Cek User exist by uniqueUserId
Backend -> Backend : Cek belum ada pairing aktif
Backend -> Backend : Buat PairingRelation\n(status=PENDING)
Backend -> FCM : Kirim push notif ke User\n(type=PAIRING_INVITE)
FCM --> AppUser : Push notification diterima
AppUser -> AppUser : FCM Handler:\nrefresh PairingBloc
AppUser -> AppUser : TTS: "Anda mendapat\nundangan pairing"
Backend --> AppGuardian : 200 OK {pairingId, status=PENDING}
User -> AppUser : Buka notif / PairingScreen
AppUser -> Backend : GET /api/v1/user/pairing/pending
Backend --> AppUser : 200 OK {pairingId, guardianName}
User -> AppUser : Tap "Terima" atau "Tolak"
AppUser -> Backend : POST /api/v1/user/pairing/respond\n{pairingId, accept: true/false}
Backend -> Backend : Update PairingRelation\n(status=ACTIVE / REJECTED)
alt Accept
Backend -> Backend : Seed default VoiceCommandConfigs\n& HardwareShortcuts untuk pasangan ini
Backend -> FCM : Kirim notif ke Guardian\n(type=PAIRING_RESPONSE, accepted=true)
FCM --> AppGuardian : Push notification
AppGuardian -> AppGuardian : TTS Guardian:\n"User menerima pairing Anda"
Backend --> AppUser : 200 OK {status=ACTIVE}
AppUser -> User : TTS: "Pairing berhasil"
else Reject
Backend -> FCM : Kirim notif ke Guardian\n(type=PAIRING_RESPONSE, accepted=false)
Backend --> AppUser : 200 OK {status=REJECTED}
end
' ============================================================
' 6. WALK GUIDE MODE
' ============================================================
== 6. Aktifkan Walk Guide Mode ==
User -> AppUser : Tap tombol / ucap\n"Start Walkguide"
AppUser -> AppUser : VoiceCommandHandler:\nmatch trigger phrase
AppUser -> AppUser : WalkGuideBloc:\nadd(StartWalkGuide())
AppUser -> AppUser : Request permission:\nkamera, lokasi, mikrofon
AppUser -> YOLO : loadModel()\n(yolov8n.tflite dari assets)
YOLO --> AppUser : Model loaded
AppUser -> AppUser : Aktifkan kamera stream
AppUser -> AppUser : Aktifkan GPS stream\n(interval 5 detik)
AppUser -> User : TTS: "Walk Guide aktif.\nDeteksi obstacle dimulai."
loop Setiap frame kamera (max 5 FPS)
AppUser -> YOLO : detect(CameraImage frame)
YOLO -> YOLO : YUV420 → RGB → resize 640×640
YOLO -> YOLO : Normalize → run inference
YOLO -> YOLO : Post-process: NMS,\nfilter confidence threshold
YOLO --> AppUser : List<DetectionResult>\n{label, confidence, boundingBox}
alt Ada obstacle terdeteksi
AppUser -> AppUser : ObstacleAnalyzer:\nanalyzeDirection() + estimateDistance()
AppUser -> AppUser : buildTtsMessage():\n"Caution! {label} {direction}. {distance}."
AppUser -> AppUser : ttsService.speakImmediate(message)
AppUser -> User : 🔊 Audio TTS obstacle alert
AppUser -> AppUser : hapticService.obstacle\n{VeryClose/Close/Medium}()
AppUser -> User : 📳 Vibrasi sesuai jarak
AppUser -> Backend : POST /api/v1/user/obstacle-log\n{label, confidence, direction, dist, lat, lng}
Backend --> AppUser : 200 OK
end
end
' ============================================================
' 7. KIRIM LOKASI GPS REAL-TIME
' ============================================================
== 7. Kirim Lokasi GPS Real-time (WebSocket) ==
loop Setiap 5 detik (WalkGuide aktif) / 30 detik (idle)
AppUser -> AppUser : Geolocator:\ngetCurrentPosition()
AppUser -> WS : STOMP send\n/app/location/{userId}\n{lat, lng, accuracy, speed, heading}
WS -> Backend : Proses LocationUpdate
Backend -> Backend : Simpan ke location_history
WS -> AppGuardian : Broadcast ke\n/topic/location/{userId}
AppGuardian -> AppGuardian : Update marker\ndi flutter_map (real-time)
AppGuardian -> Guardian : 🗺️ Peta terupdate
alt User keluar radius Geofence
Backend -> Backend : Haversine: cek jarak\nke center geofence
Backend -> FCM : Push notif ke Guardian\n(type=GEOFENCE_EXIT)
FCM --> AppGuardian : Notifikasi masuk
AppGuardian -> Guardian : "User telah keluar\ndari area aman!"
end
end
' ============================================================
' 8. SOS DARURAT
' ============================================================
== 8. Kirim SOS Darurat ==
User -> AppUser : Ucap "Send SOS" /\ntap tombol SOS
AppUser -> AppUser : SosBloc: add(TriggerSos())
AppUser -> AppUser : hapticService.sosTriggered()\n[0,1000,200,1000,200,1000]
AppUser -> AppUser : ttsService.speak:\n"SOS dikirim ke Guardian"
AppUser -> Backend : POST /api/v1/user/sos\n{triggerType, lat, lng}
Backend -> Backend : Simpan SosEvent\n(status=TRIGGERED)
Backend -> Backend : Log ActivityLog: SOS_TRIGGERED
Backend -> FCM : Push notif ke Guardian\n(type=SOS_ALERT, lat, lng)
FCM --> AppGuardian : Push notification SOS
AppGuardian -> AppGuardian : FCM Handler:\nnavigate ke SOS/IncomingCallScreen
AppGuardian -> Guardian : 🚨 Alert SOS dengan lokasi User
Backend --> AppUser : 200 OK {sosEventId, status=TRIGGERED}
Guardian -> AppGuardian : Tap "Acknowledge"
AppGuardian -> Backend : PUT /api/v1/guardian/sos/{id}/acknowledge
Backend -> Backend : Update status=ACKNOWLEDGED\nsimpan acknowledged_at
Backend --> AppGuardian : 200 OK
AppGuardian -> Guardian : Tampilkan status\n"SOS Diakui"
Guardian -> AppGuardian : Tap "Resolved"
AppGuardian -> Backend : PUT /api/v1/guardian/sos/{id}/resolve
Backend -> Backend : Update status=RESOLVED
Backend --> AppGuardian : 200 OK
' ============================================================
' 9. VoIP CALL (AGORA)
' ============================================================
== 9. Panggil Guardian (VoIP Agora) ==
User -> AppUser : Ucap "Call Guardian" /\ntap tombol Call
AppUser -> Backend : POST /api/v1/shared/agora/token\n{channelName, uid}
Backend -> Agora : Generate Agora RTC token\n(server-side REST API)
Agora --> Backend : {token, channelName, uid, appId}
Backend --> AppUser : 200 OK {AgoraTokenResponse}
AppUser -> FCM : (via Backend) Push notif ke Guardian\n(type=INCOMING_CALL, channelName)
FCM --> AppGuardian : Push notification panggilan masuk
AppGuardian -> AppGuardian : Navigate ke IncomingCallScreen
AppGuardian -> Guardian : 📞 UI Panggilan Masuk\n(haptic + ringtone)
AppUser -> Agora : joinChannel(token, channelName, uid)\n(audio-only, speakerphone ON)
Guardian -> AppGuardian : Tap "Angkat"
AppGuardian -> Backend : POST /api/v1/shared/agora/token\n{channelName}
Backend --> AppGuardian : {AgoraTokenResponse}
AppGuardian -> Agora : joinChannel(token, channelName)
Agora --> AppUser : onUserJoined callback
Agora --> AppGuardian : onUserJoined callback
AppUser <-> AppGuardian : 🎙️ Audio call berlangsung\n(via Agora RTC)
alt Guardian tutup telepon
Guardian -> AppGuardian : Tap "Tutup"
AppGuardian -> Agora : leaveChannel()
Agora --> AppUser : onUserOffline callback
AppUser -> Agora : leaveChannel()
AppUser -> User : TTS: "Panggilan berakhir"
end
' ============================================================
' 10. NOTIFIKASI GUARDIAN → USER
' ============================================================
== 10. Kirim Notifikasi (Teks / Voice Note) ==
Guardian -> AppGuardian : Buka SendNotifScreen\nTulis pesan / rekam voice note
alt Notifikasi Teks
AppGuardian -> Backend : POST /api/v1/guardian/notification\n{notifType=TEXT, content}
else Voice Note
AppGuardian -> AppGuardian : record package:\nrekam audio
AppGuardian -> Backend : POST /api/v1/guardian/notification\n{notifType=VOICE_NOTE,\nvoiceNoteUrl, voiceNoteDuration}
end
Backend -> Backend : Simpan ke guardian_notifications
Backend -> FCM : Kirim push notif ke User\n(type=NOTIFICATION)
FCM --> AppUser : Push notification diterima
AppUser -> AppUser : FCM Handler: type=NOTIFICATION\n→ refresh NotificationBloc
AppUser -> AppUser : flutter_local_notifications:\ntampilkan notif lokal
AppUser -> AppUser : TTS: "Ada pesan baru\ndari Guardian"
AppUser -> User : 🔊 TTS pemberitahuan
Backend --> AppGuardian : 200 OK
User -> AppUser : Buka NotificationScreen\natau ucap "Read All My Notifications"
AppUser -> Backend : GET /api/v1/user/notifications
Backend --> AppUser : 200 OK List<NotificationResponse>
AppUser -> User : Tampilkan daftar notifikasi
alt Voice Note
User -> AppUser : Tap notifikasi voice note
AppUser -> AppUser : just_audio: play voiceNoteUrl
AppUser -> User : 🔊 Putar voice note Guardian
end
AppUser -> Backend : PUT /api/v1/user/notifications/read-all
Backend -> Backend : Update is_read=true semua notif
Backend --> AppUser : 200 OK
' ============================================================
' 11. GUARDIAN DASHBOARD
' ============================================================
== 11. Guardian Dashboard — Monitor & Konfigurasi ==
Guardian -> AppGuardian : Buka GuardianDashboardScreen
AppGuardian -> Backend : GET /api/v1/guardian/paired-user
Backend --> AppGuardian : 200 OK {UserProfileResponse}
AppGuardian -> Backend : GET /api/v1/guardian/location/latest
Backend --> AppGuardian : 200 OK {lat, lng, speed, heading}
AppGuardian -> AppGuardian : flutter_map: tampilkan\nmarker lokasi User
AppGuardian -> WS : Subscribe /topic/location/{userId}
AppGuardian -> Guardian : 🗺️ Dashboard dengan peta real-time
Guardian -> AppGuardian : Buka GuardianAiConfigScreen
AppGuardian -> Backend : GET /api/v1/guardian/ai-config
Backend --> AppGuardian : 200 OK {AiConfigResponse}
Guardian -> AppGuardian : Ubah threshold confidence,\ndistance, FPS, enabled labels
AppGuardian -> Backend : PUT /api/v1/guardian/ai-config\n{AiConfigUpdateRequest}
Backend -> Backend : Update ai_configs di DB
Backend -> FCM : Push notif ke User\n(type=SETTINGS_UPDATED)
FCM --> AppUser : Settings update diterima
AppUser -> Backend : GET /api/v1/user/ai-config
Backend --> AppUser : 200 OK {AiConfigResponse terbaru}
AppUser -> YOLO : Update confidenceThreshold\n& maxInferenceFps
Backend --> AppGuardian : 200 OK
Guardian -> AppGuardian : Buka GuardianGeofenceScreen
AppGuardian -> Backend : GET /api/v1/guardian/geofence
Backend --> AppGuardian : 200 OK {GeofenceResponse}
Guardian -> AppGuardian : Set center (tap peta),\nradius, enable
AppGuardian -> Backend : PUT /api/v1/guardian/geofence\n{centerLat, centerLng, radiusMeters, enabled}
Backend -> Backend : Update geofence_configs
Backend --> AppGuardian : 200 OK
AppGuardian -> Guardian : Tampilkan lingkaran\ngeofence di peta
Guardian -> AppGuardian : Buka GuardianVoiceCmdScreen
AppGuardian -> Backend : GET /api/v1/guardian/voice-commands
Backend --> AppGuardian : 200 OK List<VoiceCommandResponse>
Guardian -> AppGuardian : Ubah trigger phrase\n(misal: "mulai jalan")
AppGuardian -> Backend : PUT /api/v1/guardian/voice-commands\n{commandKey, triggerPhrase, enabled}
Backend -> Backend : Update voice_command_configs
Backend -> FCM : Push notif ke User\n(type=SETTINGS_UPDATED)
FCM --> AppUser : AppUser reload CachedVoiceCommands\nke SQLite lokal
Backend --> AppGuardian : 200 OK
Guardian -> AppGuardian : Buka GuardianActivityLogScreen
AppGuardian -> Backend : GET /api/v1/guardian/activity-logs?page=0&size=20
Backend --> AppGuardian : 200 OK Page<ActivityLogResponse>
AppGuardian -> Backend : GET /api/v1/guardian/obstacle-logs?page=0&size=20
Backend --> AppGuardian : 200 OK Page<ObstacleLogResponse>
AppGuardian -> Guardian : Tampilkan riwayat\naktivitas & deteksi obstacle
' ============================================================
' 12. LOGOUT
' ============================================================
== 12. Logout ==
User -> AppUser : Tap Logout
AppUser -> Backend : POST /api/v1/auth/logout
Backend -> Backend : Delete refresh token dari DB
Backend -> Backend : Log ActivityLog: LOGOUT
Backend --> AppUser : 200 OK
AppUser -> WS : disconnect()
AppUser -> AppUser : SecureStorage.clearAll()
AppUser -> AppUser : SharedPreferences: hapus token
AppUser -> User : Redirect ke /login
@enduml