docs: finalize UML and ER diagrams for WalkGuide architecture
This commit is contained in:
parent
20e458e406
commit
2cbd6bda3a
Binary file not shown.
BIN
ooad-docs/diagrams/Er Diagram WalkGuide RILL FIX.drawio.pdf
Normal file
BIN
ooad-docs/diagrams/Er Diagram WalkGuide RILL FIX.drawio.pdf
Normal file
Binary file not shown.
@ -1,35 +1,542 @@
|
||||
@startuml
|
||||
@startuml WalkGuide_Component_Diagram
|
||||
title Component Diagram — WalkGuide System (v2.0)
|
||||
|
||||
skinparam componentStyle rectangle
|
||||
|
||||
component "Flutter Mobile App" {
|
||||
[Presentation Screens]
|
||||
[ApiClient + Interceptors]
|
||||
[TTS/STT/YOLO/Location Services]
|
||||
[WebSocketService]
|
||||
[CallService]
|
||||
skinparam backgroundColor #FAFAFA
|
||||
skinparam component {
|
||||
BackgroundColor #EEF2FF
|
||||
BorderColor #6366F1
|
||||
FontColor #1E1B4B
|
||||
FontSize 11
|
||||
}
|
||||
skinparam package {
|
||||
BackgroundColor #F0F4FF
|
||||
BorderColor #818CF8
|
||||
FontColor #1E1B4B
|
||||
FontSize 12
|
||||
FontStyle bold
|
||||
}
|
||||
skinparam interface {
|
||||
BackgroundColor #DBEAFE
|
||||
BorderColor #3B82F6
|
||||
}
|
||||
skinparam database {
|
||||
BackgroundColor #FEF3C7
|
||||
BorderColor #D97706
|
||||
}
|
||||
skinparam cloud {
|
||||
BackgroundColor #F0FDF4
|
||||
BorderColor #16A34A
|
||||
}
|
||||
skinparam arrow {
|
||||
Color #4B5563
|
||||
FontSize 9
|
||||
FontColor #374151
|
||||
}
|
||||
skinparam note {
|
||||
BackgroundColor #FFFBEB
|
||||
BorderColor #F59E0B
|
||||
FontSize 9
|
||||
}
|
||||
|
||||
component "Spring Boot Backend" {
|
||||
[Controllers]
|
||||
[Services]
|
||||
[Repositories]
|
||||
[Security/JWT]
|
||||
[WebSocket Broker]
|
||||
' ─────────────────────────────────────────────
|
||||
' MOBILE DEVICE — Flutter App
|
||||
' ─────────────────────────────────────────────
|
||||
package "Mobile Device (Flutter App)" as FLUTTER #E8EAF6 {
|
||||
|
||||
package "Presentation Layer" as PRESENTATION {
|
||||
[ServerConnectScreen] as SCS
|
||||
[SplashScreen] as SS
|
||||
[LoginScreen] as LS
|
||||
[RegisterScreen] as RS
|
||||
|
||||
package "User Screens" as US_SCREENS {
|
||||
[WalkGuideScreen] as WGS
|
||||
[SosScreen] as SOSS
|
||||
[ActivityLogScreen] as ALS
|
||||
[NotificationScreen] as NS
|
||||
[NavigationModeScreen] as NMS
|
||||
[UserSettingsScreen] as USS
|
||||
[CallScreen] as CS
|
||||
[ManualScreen] as MS
|
||||
[UserPairingScreen] as UPS
|
||||
}
|
||||
|
||||
package "Guardian Screens" as GS_SCREENS {
|
||||
[GuardianDashboardScreen] as GDS
|
||||
[GuardianMapScreen] as GMS
|
||||
[GuardianActivityLogScreen] as GALS
|
||||
[GuardianSendNotifScreen] as GSNS
|
||||
[GuardianAiConfigScreen] as GACS
|
||||
[GuardianVoiceCmdScreen] as GVCS
|
||||
[GuardianShortcutScreen] as GSHS
|
||||
[GuardianGeofenceScreen] as GGFS
|
||||
[GuardianPairingScreen] as GPS
|
||||
[IncomingCallScreen] as ICS
|
||||
}
|
||||
}
|
||||
|
||||
package "Application Layer (BLoC / Cubit)" as BLOC_LAYER {
|
||||
[AuthBloc] as AuthBloc
|
||||
[PairingBloc] as PairingBloc
|
||||
[WalkGuideBloc] as WGBloc
|
||||
[SosBloc] as SosBloc
|
||||
[LocationBloc] as LocBloc
|
||||
[NotificationBloc] as NotifBloc
|
||||
[ActivityLogBloc] as ActLogBloc
|
||||
[VoiceCommandBloc] as VCBloc
|
||||
[ServerConnectBloc] as SCBloc
|
||||
[CallBloc] as CallBloc
|
||||
[GuardianDashboardBloc] as GDBloc
|
||||
}
|
||||
|
||||
package "Domain Layer" as DOMAIN {
|
||||
package "Use Cases" as USECASES {
|
||||
[LoginUseCase] as LoginUC
|
||||
[RegisterUseCase] as RegUC
|
||||
[LogoutUseCase] as LogoutUC
|
||||
[InviteUserUseCase] as InviteUC
|
||||
[RespondPairingUseCase] as RespUC
|
||||
[UnpairUseCase] as UnpairUC
|
||||
[StartSessionUseCase] as StartUC
|
||||
[StopSessionUseCase] as StopUC
|
||||
[LogObstacleUseCase] as LogObsUC
|
||||
[TriggerSosUseCase] as TrigSosUC
|
||||
[UpdateLocationUseCase] as UpdLocUC
|
||||
}
|
||||
package "Repository Interfaces" as REPO_INTF {
|
||||
interface "IAuthRepository" as IAuthRepo
|
||||
interface "IPairingRepository" as IPairRepo
|
||||
interface "IWalkGuideRepository" as IWGRepo
|
||||
interface "IActivityLogRepository" as IActRepo
|
||||
interface "ISosRepository" as ISosRepo
|
||||
interface "ILocationRepository" as ILocRepo
|
||||
interface "INotificationRepository" as INotifRepo
|
||||
interface "IAiConfigRepository" as IAIRepo
|
||||
}
|
||||
}
|
||||
|
||||
package "Data Layer" as DATA {
|
||||
package "Repository Implementations" as REPO_IMPL {
|
||||
[AuthRepositoryImpl] as AuthRepoImpl
|
||||
[PairingRepositoryImpl] as PairRepoImpl
|
||||
[WalkGuideRepositoryImpl] as WGRepoImpl
|
||||
[ActivityLogRepositoryImpl] as ActRepoImpl
|
||||
[SosRepositoryImpl] as SosRepoImpl
|
||||
[LocationRepositoryImpl] as LocRepoImpl
|
||||
[NotificationRepositoryImpl] as NotifRepoImpl
|
||||
[AiConfigRepositoryImpl] as AIRepoImpl
|
||||
}
|
||||
|
||||
package "Remote Data Sources" as REMOTE_DS {
|
||||
[AuthRemoteDataSource] as AuthDS
|
||||
[WalkGuideRemoteDataSource] as WGDS
|
||||
[ActivityLogRemoteDataSource] as ActDS
|
||||
[SosRemoteDataSource] as SosDS
|
||||
[LocationRemoteDataSource] as LocDS
|
||||
[NotificationRemoteDataSource] as NotifDS
|
||||
[GuardianRemoteDataSource] as GrdDS
|
||||
}
|
||||
|
||||
package "Local Data Sources (Drift/SQLite)" as LOCAL_DS {
|
||||
[SecureStorageService] as SecStore
|
||||
[LocalDatabase (Drift)] as DriftDB
|
||||
[SharedPreferencesService] as SharedPref
|
||||
}
|
||||
|
||||
package "Core Services (Singletons)" as CORE_SVC {
|
||||
[TtsService\n(flutter_tts)] as TTS
|
||||
[SttService\n(speech_to_text)] as STT
|
||||
[VoiceCommandHandler] as VCH
|
||||
[HapticService\n(vibration)] as HAP
|
||||
[HardwareShortcutListener] as HSL
|
||||
[FcmService\n(firebase_messaging)] as FCMSvc
|
||||
[WebSocketService\n(stomp_dart_client)] as WSSvc
|
||||
[AgoraService\n(agora_rtc_engine)] as AgoraSvc
|
||||
}
|
||||
|
||||
package "AI Engine (On-Device)" as AI_ENGINE {
|
||||
[ModelLoader\n(tflite_flutter)] as ModelLoader
|
||||
[YoloDetector\n(YOLOv8n)] as YOLO
|
||||
[ObstacleAnalyzer] as ObsAnalyzer
|
||||
}
|
||||
|
||||
[ApiClient\n(Dio + Interceptors)] as DioClient
|
||||
}
|
||||
}
|
||||
|
||||
database "PostgreSQL" as DB
|
||||
cloud "Firebase FCM" as FCM
|
||||
cloud "Agora RTC" as Agora
|
||||
cloud "OpenStreetMap/OSRM" as Maps
|
||||
' ─────────────────────────────────────────────
|
||||
' BACKEND SERVER — Spring Boot
|
||||
' ─────────────────────────────────────────────
|
||||
package "Backend Server (Spring Boot @ 202.46.28.160:8080)" as BACKEND #E8F5E9 {
|
||||
|
||||
package "Security & Config" as SEC_CFG {
|
||||
[SecurityConfig\n(CORS, CSRF, RBAC)] as SecConfig
|
||||
[JwtAuthFilter\n(OncePerRequestFilter)] as JWTFilter
|
||||
[JwtUtil\n(JJWT)] as JWTUtil
|
||||
[CustomUserDetailsService] as CUDS
|
||||
[WebSocketConfig\n(STOMP)] as WSConfig
|
||||
[GlobalExceptionHandler\n(@ControllerAdvice)] as GEH
|
||||
[SwaggerConfig\n(Springdoc OpenAPI)] as SwagConfig
|
||||
}
|
||||
|
||||
package "Controller Layer" as CTRL {
|
||||
[AuthController\n/api/v1/auth] as AuthCtrl
|
||||
[PairingController\n/api/v1/shared/pairing] as PairCtrl
|
||||
[GuardianController\n/api/v1/guardian] as GrdCtrl
|
||||
[UserController\n/api/v1/user] as UserCtrl
|
||||
[CallController\n/api/v1/shared/call] as CallCtrl
|
||||
}
|
||||
|
||||
package "Service Layer" as SVC {
|
||||
[AuthService] as AuthSvc
|
||||
[PairingService] as PairSvc
|
||||
[ActivityLogService] as ActLogSvc
|
||||
[LocationService\n(+ Haversine)] as LocSvc
|
||||
[ObstacleLogService] as ObsLogSvc
|
||||
[NotificationService] as NotifSvc
|
||||
[SosService] as SosSvc
|
||||
[AiConfigService] as AICfgSvc
|
||||
[VoiceCommandService] as VCSvc
|
||||
[HardwareShortcutService] as HWSvc
|
||||
[GeofenceService\n(Haversine)] as GeoSvc
|
||||
[UserSettingsService] as UserSetSvc
|
||||
[FcmService\n(Firebase Admin)] as FcmBackSvc
|
||||
[AgoraTokenService] as AgoraSvcBE
|
||||
[GuardianDashboardService] as GDSvc
|
||||
}
|
||||
|
||||
package "WebSocket Broadcaster" as WS_BROADCAST {
|
||||
[LocationBroadcaster\n(SimpMessagingTemplate)] as LocBcast
|
||||
}
|
||||
|
||||
package "Repository Layer (Spring Data JPA)" as REPO {
|
||||
[UserRepository] as UserRepo
|
||||
[PairingRelationRepository] as PairRepo
|
||||
[ActivityLogRepository] as ActRepo
|
||||
[ObstacleLogRepository] as ObsRepo
|
||||
[LocationHistoryRepository] as LocRepo
|
||||
[GuardianNotificationRepository] as NotifRepo
|
||||
[SosEventRepository] as SosRepo
|
||||
[UserSettingsRepository] as UserSetRepo
|
||||
[AiConfigRepository] as AIRepo
|
||||
[VoiceCommandConfigRepository] as VCRepo
|
||||
[HardwareShortcutRepository] as HWRepo
|
||||
[GeofenceConfigRepository] as GeoRepo
|
||||
[RefreshTokenRepository] as RTRepo
|
||||
}
|
||||
|
||||
package "Domain / Entity" as ENTITY {
|
||||
[User] as UEnt
|
||||
[PairingRelation] as PRel
|
||||
[ActivityLog] as ActEnt
|
||||
[ObstacleLog] as ObsEnt
|
||||
[LocationHistory] as LocEnt
|
||||
[GuardianNotification] as NotifEnt
|
||||
[SosEvent] as SosEnt
|
||||
[UserSettings] as UserSetEnt
|
||||
[AiConfig] as AICfgEnt
|
||||
[VoiceCommandConfig] as VCEnt
|
||||
[HardwareShortcut] as HWEnt
|
||||
[GeofenceConfig] as GeoEnt
|
||||
[RefreshToken] as RTEnt
|
||||
}
|
||||
}
|
||||
|
||||
' ─────────────────────────────────────────────
|
||||
' EXTERNAL SERVICES
|
||||
' ─────────────────────────────────────────────
|
||||
package "External Services" as EXTERNAL #FFF8E1 {
|
||||
cloud "Firebase (FCM)\nPush Notifications" as FCM
|
||||
cloud "Agora RTC\nVoIP Audio Call" as AGORA
|
||||
cloud "OpenStreetMap\n(flutter_map tiles)" as OSM
|
||||
}
|
||||
|
||||
database "PostgreSQL\n202.46.28.160:2002\n(University Server)\nFlyway Migrations V1-V16" as PGDB
|
||||
|
||||
' ─────────────────────────────────────────────
|
||||
' PRESENTATION → BLOC
|
||||
' ─────────────────────────────────────────────
|
||||
SCS --> SCBloc
|
||||
SS --> AuthBloc
|
||||
LS --> AuthBloc
|
||||
RS --> AuthBloc
|
||||
WGS --> WGBloc
|
||||
WGS --> VCBloc
|
||||
SOSS --> SosBloc
|
||||
ALS --> ActLogBloc
|
||||
NS --> NotifBloc
|
||||
NMS --> LocBloc
|
||||
USS --> AuthBloc
|
||||
CS --> CallBloc
|
||||
UPS --> PairingBloc
|
||||
GDS --> GDBloc
|
||||
GMS --> LocBloc
|
||||
GALS --> ActLogBloc
|
||||
GSNS --> NotifBloc
|
||||
GACS --> AIRepoImpl
|
||||
GVCS --> VCBloc
|
||||
GSHS --> VCBloc
|
||||
GGFS --> GDBloc
|
||||
GPS --> PairingBloc
|
||||
ICS --> CallBloc
|
||||
|
||||
' ─────────────────────────────────────────────
|
||||
' BLOC → USE CASES
|
||||
' ─────────────────────────────────────────────
|
||||
AuthBloc --> LoginUC
|
||||
AuthBloc --> RegUC
|
||||
AuthBloc --> LogoutUC
|
||||
PairingBloc --> InviteUC
|
||||
PairingBloc --> RespUC
|
||||
PairingBloc --> UnpairUC
|
||||
WGBloc --> StartUC
|
||||
WGBloc --> StopUC
|
||||
WGBloc --> LogObsUC
|
||||
SosBloc --> TrigSosUC
|
||||
LocBloc --> UpdLocUC
|
||||
CallBloc --> AgoraSvc
|
||||
VCBloc --> VCH
|
||||
|
||||
' ─────────────────────────────────────────────
|
||||
' USE CASES → REPOSITORY INTERFACES
|
||||
' ─────────────────────────────────────────────
|
||||
LoginUC --> IAuthRepo
|
||||
RegUC --> IAuthRepo
|
||||
LogoutUC --> IAuthRepo
|
||||
InviteUC --> IPairRepo
|
||||
RespUC --> IPairRepo
|
||||
UnpairUC --> IPairRepo
|
||||
StartUC --> IWGRepo
|
||||
StopUC --> IWGRepo
|
||||
LogObsUC --> IWGRepo
|
||||
TrigSosUC --> ISosRepo
|
||||
UpdLocUC --> ILocRepo
|
||||
|
||||
' ─────────────────────────────────────────────
|
||||
' INTERFACES → IMPLEMENTATIONS
|
||||
' ─────────────────────────────────────────────
|
||||
IAuthRepo <|.. AuthRepoImpl
|
||||
IPairRepo <|.. PairRepoImpl
|
||||
IWGRepo <|.. WGRepoImpl
|
||||
IActRepo <|.. ActRepoImpl
|
||||
ISosRepo <|.. SosRepoImpl
|
||||
ILocRepo <|.. LocRepoImpl
|
||||
INotifRepo <|.. NotifRepoImpl
|
||||
IAIRepo <|.. AIRepoImpl
|
||||
|
||||
' ─────────────────────────────────────────────
|
||||
' REPO IMPL → DATA SOURCES
|
||||
' ─────────────────────────────────────────────
|
||||
AuthRepoImpl --> AuthDS
|
||||
AuthRepoImpl --> SecStore
|
||||
PairRepoImpl --> GrdDS
|
||||
WGRepoImpl --> WGDS
|
||||
WGRepoImpl --> DriftDB
|
||||
ActRepoImpl --> ActDS
|
||||
SosRepoImpl --> SosDS
|
||||
LocRepoImpl --> LocDS
|
||||
NotifRepoImpl --> NotifDS
|
||||
NotifRepoImpl --> DriftDB
|
||||
AIRepoImpl --> GrdDS
|
||||
|
||||
' ─────────────────────────────────────────────
|
||||
' REMOTE DATA SOURCES → DioClient
|
||||
' ─────────────────────────────────────────────
|
||||
AuthDS --> DioClient
|
||||
WGDS --> DioClient
|
||||
ActDS --> DioClient
|
||||
SosDS --> DioClient
|
||||
LocDS --> DioClient
|
||||
NotifDS --> DioClient
|
||||
GrdDS --> DioClient
|
||||
|
||||
' ─────────────────────────────────────────────
|
||||
' CORE SERVICES INTERCONNECT
|
||||
' ─────────────────────────────────────────────
|
||||
WGBloc --> YOLO
|
||||
WGBloc --> TTS
|
||||
WGBloc --> HAP
|
||||
YOLO --> ModelLoader
|
||||
YOLO --> ObsAnalyzer
|
||||
ObsAnalyzer --> TTS
|
||||
ObsAnalyzer --> HAP
|
||||
VCH --> STT
|
||||
VCH --> DriftDB
|
||||
HSL --> DriftDB
|
||||
FCMSvc --> DriftDB
|
||||
|
||||
SharedPref --> DioClient : baseUrl
|
||||
|
||||
' ─────────────────────────────────────────────
|
||||
' DioClient → Backend REST API
|
||||
' ─────────────────────────────────────────────
|
||||
DioClient -down-> AuthCtrl : "POST /api/v1/auth/register\nPOST /api/v1/auth/login\nPOST /api/v1/auth/refresh\nPOST /api/v1/auth/logout\nPUT /api/v1/auth/fcm-token\nGET /api/v1/auth/ping"
|
||||
DioClient -down-> PairCtrl : "POST /shared/pairing/invite\nPOST /shared/pairing/respond\nDELETE /shared/pairing/unpair\nGET /shared/pairing/status"
|
||||
DioClient -down-> UserCtrl : "GET /user/profile\nPOST /user/location\nPOST /user/obstacle\nPOST /user/sos\nGET /user/notifications\nPOST /user/walkguide/start\nPOST /user/walkguide/stop"
|
||||
DioClient -down-> GrdCtrl : "GET /guardian/dashboard\nGET /guardian/user-location\nPOST /guardian/notifications/send\nPUT /guardian/ai-config\nPUT /guardian/geofence"
|
||||
DioClient -down-> CallCtrl : "POST /shared/call/token\nPOST /shared/call/notify"
|
||||
|
||||
' WebSocket STOMP
|
||||
WSSvc -down-> WSConfig : "ws://host:8080/ws\n(STOMP)\n/topic/location/{userId}\n/queue/sos/{guardianId}\n/queue/notif/{userId}"
|
||||
|
||||
' ─────────────────────────────────────────────
|
||||
' BACKEND SECURITY FLOW
|
||||
' ─────────────────────────────────────────────
|
||||
JWTFilter --> JWTUtil : validate token
|
||||
JWTFilter --> CUDS : load user
|
||||
JWTFilter -up-> SecConfig : filter chain
|
||||
SecConfig -right-> JWTFilter
|
||||
|
||||
' ─────────────────────────────────────────────
|
||||
' CONTROLLERS → SERVICES
|
||||
' ─────────────────────────────────────────────
|
||||
AuthCtrl --> AuthSvc
|
||||
PairCtrl --> PairSvc
|
||||
GrdCtrl --> GDSvc
|
||||
GrdCtrl --> LocSvc
|
||||
GrdCtrl --> ActLogSvc
|
||||
GrdCtrl --> ObsLogSvc
|
||||
GrdCtrl --> NotifSvc
|
||||
GrdCtrl --> SosSvc
|
||||
GrdCtrl --> AICfgSvc
|
||||
GrdCtrl --> VCSvc
|
||||
GrdCtrl --> HWSvc
|
||||
GrdCtrl --> GeoSvc
|
||||
UserCtrl --> AuthSvc
|
||||
UserCtrl --> LocSvc
|
||||
UserCtrl --> ObsLogSvc
|
||||
UserCtrl --> SosSvc
|
||||
UserCtrl --> ActLogSvc
|
||||
UserCtrl --> NotifSvc
|
||||
UserCtrl --> UserSetSvc
|
||||
UserCtrl --> VCSvc
|
||||
UserCtrl --> HWSvc
|
||||
UserCtrl --> AICfgSvc
|
||||
CallCtrl --> AgoraSvcBE
|
||||
|
||||
' ─────────────────────────────────────────────
|
||||
' SERVICES CROSS-DEPENDENCIES
|
||||
' ─────────────────────────────────────────────
|
||||
AuthSvc --> PairSvc : seed defaults on register
|
||||
PairSvc --> VCSvc : seedDefaults()
|
||||
PairSvc --> HWSvc : seedDefaults()
|
||||
PairSvc --> AICfgSvc : create config on pair
|
||||
PairSvc --> FcmBackSvc : send FCM invite/response
|
||||
NotifSvc --> FcmBackSvc : send FCM notification
|
||||
SosSvc --> FcmBackSvc : send FCM SOS high-priority
|
||||
AICfgSvc --> FcmBackSvc : notify settings updated
|
||||
LocSvc --> GeoSvc : checkGeofence()
|
||||
LocSvc --> LocBcast : broadcastLocation()
|
||||
LocSvc --> ActLogSvc : log LOCATION_UPDATE
|
||||
NotifSvc --> LocBcast : broadcastNotification()
|
||||
SosSvc --> LocBcast : broadcastSos()
|
||||
SosSvc --> ActLogSvc : log SOS_TRIGGERED
|
||||
ActLogSvc --> LocBcast : broadcast to Guardian
|
||||
GDSvc --> LocSvc
|
||||
GDSvc --> ActLogSvc
|
||||
GDSvc --> NotifSvc
|
||||
GDSvc --> SosSvc
|
||||
|
||||
' ─────────────────────────────────────────────
|
||||
' SERVICES → REPOSITORIES
|
||||
' ─────────────────────────────────────────────
|
||||
AuthSvc --> UserRepo
|
||||
AuthSvc --> RTRepo
|
||||
AuthSvc --> ActLogSvc
|
||||
PairSvc --> PairRepo
|
||||
PairSvc --> UserRepo
|
||||
ActLogSvc --> ActRepo
|
||||
LocSvc --> LocRepo
|
||||
LocSvc --> PairRepo
|
||||
ObsLogSvc --> ObsRepo
|
||||
ObsLogSvc --> ActLogSvc
|
||||
NotifSvc --> NotifRepo
|
||||
NotifSvc --> PairRepo
|
||||
SosSvc --> SosRepo
|
||||
SosSvc --> PairRepo
|
||||
AICfgSvc --> AIRepo
|
||||
VCSvc --> VCRepo
|
||||
HWSvc --> HWRepo
|
||||
GeoSvc --> GeoRepo
|
||||
UserSetSvc --> UserSetRepo
|
||||
FcmBackSvc --> UserRepo
|
||||
AgoraSvcBE --> UserRepo
|
||||
|
||||
' ─────────────────────────────────────────────
|
||||
' REPOSITORIES → ENTITIES (JPA/Hibernate)
|
||||
' ─────────────────────────────────────────────
|
||||
UserRepo --> UEnt
|
||||
PairRepo --> PRel
|
||||
ActRepo --> ActEnt
|
||||
ObsRepo --> ObsEnt
|
||||
LocRepo --> LocEnt
|
||||
NotifRepo --> NotifEnt
|
||||
SosRepo --> SosEnt
|
||||
UserSetRepo --> UserSetEnt
|
||||
AIRepo --> AICfgEnt
|
||||
VCRepo --> VCEnt
|
||||
HWRepo --> HWEnt
|
||||
GeoRepo --> GeoEnt
|
||||
RTRepo --> RTEnt
|
||||
|
||||
' ─────────────────────────────────────────────
|
||||
' ENTITIES → DATABASE
|
||||
' ─────────────────────────────────────────────
|
||||
UEnt --> PGDB : JPA/Hibernate
|
||||
PRel --> PGDB
|
||||
ActEnt --> PGDB
|
||||
ObsEnt --> PGDB
|
||||
LocEnt --> PGDB
|
||||
NotifEnt --> PGDB
|
||||
SosEnt --> PGDB
|
||||
UserSetEnt --> PGDB
|
||||
AICfgEnt --> PGDB
|
||||
VCEnt --> PGDB
|
||||
HWEnt --> PGDB
|
||||
GeoEnt --> PGDB
|
||||
RTEnt --> PGDB
|
||||
|
||||
' ─────────────────────────────────────────────
|
||||
' EXTERNAL SERVICES
|
||||
' ─────────────────────────────────────────────
|
||||
FcmBackSvc -right-> FCM : "Firebase Admin SDK\nFirebaseMessaging.send()"
|
||||
AgoraSvcBE -right-> AGORA : "Generate RTC Token\n(server-side Agora SDK)"
|
||||
AgoraSvc --> AGORA : "agora_rtc_engine\nJoin Channel (audio-only)"
|
||||
FCMSvc --> FCM : "firebase_messaging\nReceive push"
|
||||
GMS --> OSM : "flutter_map\nOpenStreetMap tiles"
|
||||
|
||||
note top of PGDB
|
||||
Tables (Flyway V1–V16):
|
||||
users, pairing_relations,
|
||||
activity_logs, obstacle_logs,
|
||||
location_history, guardian_notifications,
|
||||
sos_events, user_settings, ai_configs,
|
||||
voice_command_configs, hardware_shortcuts,
|
||||
geofence_configs, refresh_tokens
|
||||
end note
|
||||
|
||||
note right of AI_ENGINE
|
||||
On-Device AI (No server call):
|
||||
YOLOv8n .tflite (640×640)
|
||||
→ detect obstacles
|
||||
→ direction: LEFT/CENTER/RIGHT
|
||||
→ TTS + Haptic alert
|
||||
NNAPI delegate (Android AI accel.)
|
||||
end note
|
||||
|
||||
note left of SharedPref
|
||||
Dynamic Server URL:
|
||||
User inputs "http://202.46.28.160:8080"
|
||||
Saved to SharedPreferences
|
||||
No hardcoded BASE_URL in APK
|
||||
end note
|
||||
|
||||
note bottom of WSSvc
|
||||
WebSocket STOMP:
|
||||
/topic/location/{userId} → Guardian map real-time
|
||||
/queue/sos/{guardianId} → SOS alert instant
|
||||
/queue/notif/{userId} → Notification push
|
||||
end note
|
||||
|
||||
[Presentation Screens] --> [ApiClient + Interceptors]
|
||||
[ApiClient + Interceptors] --> [Controllers] : REST /api/v1
|
||||
[WebSocketService] --> [WebSocket Broker] : STOMP /ws
|
||||
[CallService] --> [Controllers] : /shared/call
|
||||
[Controllers] --> [Services]
|
||||
[Services] --> [Repositories]
|
||||
[Repositories] --> DB
|
||||
[Services] --> FCM
|
||||
[CallService] --> Agora
|
||||
[Presentation Screens] --> Maps
|
||||
@enduml
|
||||
|
||||
381
ooad-docs/diagrams/sequence-diagram.puml
Normal file
381
ooad-docs/diagrams/sequence-diagram.puml
Normal file
@ -0,0 +1,381 @@
|
||||
@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
|
||||
470
ooad-docs/diagrams/state-machine.puml
Normal file
470
ooad-docs/diagrams/state-machine.puml
Normal file
@ -0,0 +1,470 @@
|
||||
@startuml WalkGuide_StateMachine
|
||||
title State Machine Diagram — WalkGuide System (Full Architecture)
|
||||
|
||||
skinparam state {
|
||||
BackgroundColor WhiteSmoke
|
||||
BorderColor #5B6AB0
|
||||
FontColor Black
|
||||
ArrowColor #5B6AB0
|
||||
StartColor Black
|
||||
EndColor Black
|
||||
}
|
||||
skinparam note {
|
||||
BackgroundColor LightYellow
|
||||
BorderColor #999
|
||||
}
|
||||
|
||||
' ============================================================
|
||||
' TOP LEVEL: APP LAUNCH
|
||||
' ============================================================
|
||||
[*] --> AppLaunched : App dibuka
|
||||
|
||||
state AppLaunched {
|
||||
[*] --> CheckingServerUrl
|
||||
|
||||
CheckingServerUrl : Cek SharedPreferences\nserver URL tersimpan?
|
||||
|
||||
CheckingServerUrl --> ServerConnectScreen : [Tidak ada URL]
|
||||
CheckingServerUrl --> CheckingToken : [URL tersedia]
|
||||
|
||||
ServerConnectScreen : Input server URL\n(http://202.46.28.160:8080)
|
||||
ServerConnectScreen --> PingingServer : Submit URL
|
||||
|
||||
PingingServer : GET /api/v1/auth/ping\n(cek koneksi ke backend)
|
||||
PingingServer --> ServerConnectScreen : [Ping Gagal / Timeout]
|
||||
PingingServer --> CheckingToken : [Ping OK → simpan URL ke SharedPreferences]
|
||||
|
||||
CheckingToken : Cek flutter_secure_storage\naccessToken ada?
|
||||
CheckingToken --> Unauthenticated : [Token Tidak Ada]
|
||||
CheckingToken --> ValidatingToken : [Token Ada]
|
||||
|
||||
ValidatingToken : Verifikasi JWT\n(decode expiry lokal)
|
||||
ValidatingToken --> Unauthenticated : [Token Expired / Invalid]
|
||||
ValidatingToken --> Authenticated : [Token Valid → decode role]
|
||||
}
|
||||
|
||||
' ============================================================
|
||||
' UNAUTHENTICATED
|
||||
' ============================================================
|
||||
state Unauthenticated {
|
||||
[*] --> LoginScreen
|
||||
|
||||
LoginScreen : Input email & password\n+ tombol ke RegisterScreen
|
||||
|
||||
LoginScreen --> RegisterScreen : Tap "Register"
|
||||
RegisterScreen --> LoginScreen : Tap "Back to Login"
|
||||
|
||||
RegisterScreen : Pilih role (ROLE_GUARDIAN / ROLE_USER)\nIsi email, password, displayName
|
||||
|
||||
LoginScreen --> ValidatingCredentials : Submit login
|
||||
RegisterScreen --> RegisteringUser : Submit register
|
||||
|
||||
ValidatingCredentials : POST /api/v1/auth/login\n{email, password}
|
||||
ValidatingCredentials --> LoginScreen : [401 Unauthorized → tampil error]
|
||||
ValidatingCredentials --> StoringAuthData : [200 OK + JWT]
|
||||
|
||||
RegisteringUser : POST /api/v1/auth/register\n{email, password, role, displayName}
|
||||
RegisteringUser --> LoginScreen : [400 / Email sudah terdaftar]
|
||||
RegisteringUser --> StoringAuthData : [201 Created + JWT]
|
||||
|
||||
StoringAuthData : Simpan accessToken + refreshToken\nke flutter_secure_storage\nUpdate FCM token → PUT /auth/fcm-token\nWebSocketService.connect(accessToken)
|
||||
StoringAuthData --> DecodingRole : JWT tersimpan
|
||||
|
||||
DecodingRole : Ekstrak role dari JWT claims
|
||||
DecodingRole --> [*]
|
||||
}
|
||||
|
||||
note on link
|
||||
Login Sukses
|
||||
200 OK + JWT
|
||||
(access + refresh)
|
||||
end note
|
||||
|
||||
Unauthenticated --> Authenticated : [Login / Register Sukses → role decoded]
|
||||
|
||||
' ============================================================
|
||||
' AUTHENTICATED (role router)
|
||||
' ============================================================
|
||||
state Authenticated {
|
||||
[*] --> CheckingPairing
|
||||
|
||||
CheckingPairing : GET /shared/pairing/status\ncek status pairing user ini
|
||||
|
||||
CheckingPairing --> UserShell : [Role = ROLE_USER]
|
||||
CheckingPairing --> GuardianShell : [Role = ROLE_GUARDIAN]
|
||||
}
|
||||
|
||||
' ============================================================
|
||||
' USER SHELL
|
||||
' ============================================================
|
||||
state UserShell {
|
||||
|
||||
[*] --> UserIdle
|
||||
|
||||
UserIdle : Bottom nav 6 tabs aktif\nSTT always-listening di background\nWebSocket subscribe /queue/notif/{userId}
|
||||
note right of UserIdle
|
||||
Voice commands aktif:\n"Start Walkguide", "Open SOS",\n"Call Guardian", "Where Am I", dll
|
||||
end note
|
||||
|
||||
' --- PAIRING CHECK ---
|
||||
UserIdle --> PairingScreen_User : [Belum paired → TTS peringatan]
|
||||
|
||||
state PairingScreen_User {
|
||||
[*] --> WaitingPairingInvite
|
||||
WaitingPairingInvite : GET /shared/pairing/status\nTampil uniqueUserId 12-char\n"Share this ID with your Guardian"
|
||||
WaitingPairingInvite --> ReceivingInvite : FCM: PAIRING_INVITE diterima
|
||||
ReceivingInvite --> WaitingPairingInvite : Tap "Reject" → POST /pairing/respond {accept:false}
|
||||
ReceivingInvite --> PairingAccepted : Tap "Accept" → POST /pairing/respond {accept:true}
|
||||
PairingAccepted : Backend seed 14 VoiceCommands\n+ 5 Shortcuts + AiConfig defaults\nFCM ke Guardian: "Pairing accepted"\nTTS: "Pairing successful. Guardian connected."
|
||||
PairingAccepted --> [*]
|
||||
}
|
||||
|
||||
PairingScreen_User --> UserIdle : Pairing berhasil / lewati
|
||||
|
||||
' --- WALKGUIDE ---
|
||||
UserIdle --> WalkGuideActive : Tap "Start Walk Guide"\nATAU voice "Start Walkguide"\nATAU Volume Down button
|
||||
|
||||
state WalkGuideActive {
|
||||
[*] --> InitializingCamera
|
||||
|
||||
InitializingCamera : camera.startImageStream()\nRequest permissions (kamera, lokasi)\nPOST /user/walkguide/start → ActivityLog: WALKGUIDE_START
|
||||
InitializingCamera --> [*] : [Permission Denied → kembali ke UserIdle]
|
||||
|
||||
InitializingCamera --> DetectionLoop : Kamera siap\nTTS: "3... 2... 1... WalkGuide started."
|
||||
|
||||
state DetectionLoop {
|
||||
[*] --> CapturingFrame
|
||||
|
||||
CapturingFrame : Terima CameraImage (YUV420)\nThrottle: maxInferenceFps (default 5fps)
|
||||
CapturingFrame --> ProcessingFrame : [YoloDetector tidak sedang running → kirim frame]
|
||||
CapturingFrame --> CapturingFrame : [YoloDetector sedang running → skip frame ini]
|
||||
|
||||
ProcessingFrame : YUV420→RGB→Resize 640×640\nNormalize 0.0-1.0\ninterpreter.run() [Isolate terpisah]\nPost-process + NMS\nFilter by confidenceThreshold
|
||||
|
||||
ProcessingFrame --> EvaluatingResult : Inference selesai
|
||||
|
||||
EvaluatingResult : ObstacleAnalyzer.prioritize(results)\nTentukan direction (LEFT/CENTER/RIGHT)\nEstimasi distance (Very Close/Close/Medium/Far)
|
||||
|
||||
EvaluatingResult --> ObstacleDetected : [Confidence ≥ threshold & ada deteksi]
|
||||
EvaluatingResult --> NoObstacleDetected : [Confidence < threshold / tidak ada deteksi]
|
||||
|
||||
ObstacleDetected : Build TTS: "Caution! {label} {direction}. {distance}. Please stop."\nttsService.speakImmediate()\nhapticService.obstacleVeryClose()\nDebounce 3 detik (jangan log yang sama terus)\nPOST /user/obstacle {label, confidence, direction, dist, lat, lng}
|
||||
|
||||
NoObstacleDetected : Lanjut deteksi
|
||||
|
||||
ObstacleDetected --> CapturingFrame : Feedback selesai
|
||||
NoObstacleDetected --> CapturingFrame : Lanjut deteksi
|
||||
|
||||
' Location update loop (parallel, setiap 5 detik)
|
||||
CapturingFrame --> SendingLocation : [Setiap 5 detik]
|
||||
SendingLocation : geolocator.getCurrentPosition()\nPOST /user/location {lat, lng, accuracy, speed, heading}\nBackend: simpan LocationHistory\nWebSocket broadcast → Guardian map\nGeofenceService.checkGeofence()
|
||||
SendingLocation --> CapturingFrame : Location terkirim
|
||||
}
|
||||
|
||||
DetectionLoop --> SavingSession : Tap "Stop Walk Guide"\nATAU voice "Stop Walkguide"
|
||||
|
||||
SavingSession : camera.stopImageStream()\nPOST /user/walkguide/stop → ActivityLog: WALKGUIDE_STOP\nLocation interval → 30 detik
|
||||
SavingSession --> [*]
|
||||
}
|
||||
|
||||
WalkGuideActive --> UserIdle : Sesi WalkGuide selesai
|
||||
|
||||
' --- SOS ---
|
||||
UserIdle --> SosFlow : Tap SOS tab\nATAU voice "Send SOS"\nATAU hardware shortcut
|
||||
|
||||
state SosFlow {
|
||||
[*] --> SosConfirm
|
||||
SosConfirm : SosScreen: tombol SOS merah besar\nTTS: "SOS screen. Press the big red button\nor say 'Send SOS' to alert your Guardian."
|
||||
SosConfirm --> TriggeringSos : Tap SOS button / voice "Send SOS"
|
||||
TriggeringSos : Get GPS position\nPOST /user/sos {triggerType, lat, lng}\nBackend: save SosEvent TRIGGERED\nFCM HIGH PRIORITY ke Guardian: "🚨 SOS ALERT!"\nWebSocket → /queue/sos/{guardianId}\nTTS: "SOS sent. Guardian has been alerted."\nhapticService.sosTriggered()
|
||||
TriggeringSos --> WaitingAcknowledge : SOS terkirim
|
||||
WaitingAcknowledge : Tunggu respons Guardian\nTampil status "Waiting for Guardian..."
|
||||
WaitingAcknowledge --> SosAcknowledged : FCM: Guardian acknowledge SOS\nTTS: "Your Guardian has acknowledged.\nHelp is coming."
|
||||
SosAcknowledged --> [*]
|
||||
}
|
||||
|
||||
SosFlow --> UserIdle : Kembali ke home
|
||||
|
||||
' --- NOTIFICATIONS ---
|
||||
UserIdle --> NotificationFlow : Tap Notifications tab\nATAU voice "Open Notifications"\nATAU FCM/WebSocket masuk
|
||||
|
||||
state NotificationFlow {
|
||||
[*] --> ViewingNotifications
|
||||
ViewingNotifications : GET /user/notifications\nGET /user/notifications/unread-count\nTampil list: TEXT / VOICE_NOTE\nBadge unread count\nTTS: "Notifications. You have {N} unread messages."
|
||||
ViewingNotifications --> ReadingAllTts : Tap "Read All" / voice "Read All My Notifications"
|
||||
ViewingNotifications --> ReadingOneTts : Tap satu notifikasi
|
||||
ReadingAllTts : Iterate unread list:\n- TEXT: TTS "From Guardian on {date}: {content}"\n- VOICE_NOTE: just_audio.play(voiceNoteUrl)\nPUT /user/notifications/{id}/read setelah selesai
|
||||
ReadingOneTts : TTS / play satu notifikasi\nPUT /user/notifications/{id}/read
|
||||
ReadingAllTts --> ViewingNotifications : Semua notif dibaca
|
||||
ReadingOneTts --> ViewingNotifications : Selesai
|
||||
ViewingNotifications --> MarkingAllRead : Tap "Mark All Read"\nPUT /user/notifications/mark-all-read
|
||||
MarkingAllRead --> ViewingNotifications : Done
|
||||
}
|
||||
|
||||
NotificationFlow --> UserIdle : Kembali
|
||||
|
||||
' --- NAVIGATION MODE ---
|
||||
UserIdle --> NavigationModeActive : Tap Navigate tab\nATAU voice "Open Navigation"
|
||||
|
||||
state NavigationModeActive {
|
||||
[*] --> NavigationIdle
|
||||
NavigationIdle : FlutterMap (OpenStreetMap tiles)\nTTS: "Navigation mode. Say a destination."
|
||||
NavigationIdle --> SearchingDestination : Tap search bar / voice "Navigate to {place}"
|
||||
SearchingDestination : OSM Nominatim API\nGET nominatim.openstreetmap.org/search?q=...
|
||||
SearchingDestination --> NavigatingRoute : Lokasi ditemukan → OSRM routing
|
||||
NavigatingRoute : OSRM turn-by-turn route\nTTS setiap instruksi: "In 50 meters, turn right"\ngeolocator: GPS real-time\nPOST /user/location setiap 5 detik
|
||||
NavigatingRoute --> NavigationIdle : Sampai tujuan / Stop
|
||||
}
|
||||
|
||||
NavigationModeActive --> UserIdle : Kembali
|
||||
|
||||
' --- CALL ---
|
||||
UserIdle --> OutgoingCallFlow : Tap "Call Guardian"\nATAU voice "Call Guardian"\nATAU Volume Up button
|
||||
|
||||
state OutgoingCallFlow {
|
||||
[*] --> RequestingAgoraToken
|
||||
RequestingAgoraToken : POST /shared/call/token\nGenerate Agora RTC token\nPOST /shared/call/notify → FCM ke Guardian "Incoming Call"
|
||||
RequestingAgoraToken --> CallingGuardian : Token dapat, join Agora channel
|
||||
CallingGuardian : CallScreen: "Calling Guardian..." + animasi\nAgoraRtcEngine.joinChannel()
|
||||
CallingGuardian --> InCall : Guardian pick up → CallConnected
|
||||
CallingGuardian --> UserIdle : Guardian decline / timeout 30 detik
|
||||
InCall : Timer durasi call\nTombol: Mute, Speaker, End Call\nTTS: "Connected to Guardian"
|
||||
InCall --> UserIdle : Tap End Call
|
||||
}
|
||||
|
||||
UserIdle --> IncomingCallHandled : FCM INCOMING_CALL diterima dari Guardian
|
||||
|
||||
state IncomingCallHandled {
|
||||
[*] --> ShowingIncomingCall
|
||||
ShowingIncomingCall : IncomingCallScreen\nTTS: "Incoming call from Guardian"\nHaptic vibration\nTombol Accept (hijau) + Decline (merah)\nAuto-answer setelah 30 detik
|
||||
ShowingIncomingCall --> InCallSession : Accept
|
||||
ShowingIncomingCall --> UserIdle : Decline
|
||||
InCallSession : Agora RTC connected\nTimer + Mute + Speaker + End
|
||||
InCallSession --> UserIdle : End call
|
||||
}
|
||||
|
||||
' --- SETTINGS ---
|
||||
UserIdle --> UserSettingsFlow : Tap Settings tab\nATAU voice "Open Settings"
|
||||
|
||||
state UserSettingsFlow {
|
||||
[*] --> ViewingSettings
|
||||
ViewingSettings : TTS Settings (read-only pitch/speed)\nPairing status link\nManual/Instructions link\nNo-Guardian Warning toggle\nAccount info + Logout
|
||||
ViewingSettings --> UpdatingTtsLanguage : Toggle ID/EN → PUT /user/settings
|
||||
ViewingSettings --> ViewingManual : Tap "Instructions"
|
||||
ViewingSettings --> LoggingOut : Tap Logout
|
||||
UpdatingTtsLanguage --> ViewingSettings : Saved
|
||||
ViewingManual : ManualScreen\nSemua voice commands + shortcuts\nTombol "Hear Instructions"\nTTS setiap instruksi 1-per-1
|
||||
ViewingManual --> ViewingSettings : Back
|
||||
LoggingOut : POST /auth/logout {refreshToken}\nSecureStorage.clearAll()\nWebSocketService.disconnect()\nGoRouter → /login
|
||||
LoggingOut --> [*]
|
||||
}
|
||||
|
||||
UserSettingsFlow --> UserIdle : Kembali
|
||||
UserSettingsFlow --> [*] : Logout
|
||||
|
||||
' --- ACTIVITY LOG ---
|
||||
UserIdle --> ActivityLogFlow : Tap Activity tab\nATAU voice "Open Activity Log"
|
||||
|
||||
state ActivityLogFlow {
|
||||
[*] --> ViewingActivityLog
|
||||
ViewingActivityLog : GET /user/activity-logs (paginated)\nInfinite scroll\nFilter by type/date\nTTS: "Activity log. {N} activities today."
|
||||
}
|
||||
|
||||
ActivityLogFlow --> UserIdle : Kembali
|
||||
}
|
||||
|
||||
' ============================================================
|
||||
' GUARDIAN SHELL
|
||||
' ============================================================
|
||||
state GuardianShell {
|
||||
|
||||
[*] --> GuardianIdle
|
||||
|
||||
GuardianIdle : GuardianDashboardScreen HOME\nCard: User status (online/offline, battery)\nCard: Last location (Nominatim reverse)\nCard: Unread SOS (badge merah)\nCard: Recent 5 activity logs\nQuick actions: Send Message, Call User, View Map\nWebSocket subscribe:\n /topic/location/{pairedUserId}\n /queue/sos/{guardianId}
|
||||
|
||||
' --- PAIRING (Guardian side) ---
|
||||
GuardianIdle --> GuardianPairingFlow : Tap "Manage Pairing"\nATAU belum punya paired user
|
||||
|
||||
state GuardianPairingFlow {
|
||||
[*] --> EnteringUserId
|
||||
EnteringUserId : Input uniqueUserId 12-char\nmilik paired User
|
||||
EnteringUserId --> SendingInvite : Tap "Send Invite"
|
||||
SendingInvite : POST /shared/pairing/invite {uniqueUserId}\nBackend: buat PairingRelation PENDING\nFCM ke User: "Pairing Request"
|
||||
SendingInvite --> WaitingUserResponse : Invite terkirim
|
||||
WaitingUserResponse : Tunggu User accept/reject
|
||||
WaitingUserResponse --> PairingSuccess : FCM: User accepted\nBackend seed VoiceCommands + Shortcuts + AiConfig
|
||||
WaitingUserResponse --> EnteringUserId : FCM: User rejected
|
||||
PairingSuccess : TTS: "Pairing successful. User is now connected."\nNavigate → GuardianIdle
|
||||
PairingSuccess --> [*]
|
||||
}
|
||||
|
||||
GuardianPairingFlow --> GuardianIdle : Selesai
|
||||
|
||||
' --- MAP / LIVE TRACKING ---
|
||||
GuardianIdle --> GuardianMapView : Tap "View Map"\nATAU quick action map
|
||||
|
||||
state GuardianMapView {
|
||||
[*] --> ViewingLiveMap
|
||||
ViewingLiveMap : FlutterMap + live location marker\nWebSocket /topic/location/{userId}\nGeofence circle overlay (jika enabled)\nRoute history polyline (location_history)
|
||||
ViewingLiveMap --> ViewingLiveMap : [WebSocket update → marker pindah]
|
||||
}
|
||||
|
||||
GuardianMapView --> GuardianIdle : Kembali
|
||||
|
||||
' --- ACTIVITY LOGS ---
|
||||
GuardianIdle --> GuardianActivityLog : Tap "View Activity Logs"
|
||||
|
||||
state GuardianActivityLog {
|
||||
[*] --> ViewingUserLogs
|
||||
ViewingUserLogs : GET /guardian/activity-logs (paginated)\nGET /guardian/obstacle-logs\nFilter by type, date
|
||||
}
|
||||
|
||||
GuardianActivityLog --> GuardianIdle : Kembali
|
||||
|
||||
' --- SEND NOTIFICATION ---
|
||||
GuardianIdle --> GuardianSendNotif : Tap "Send Notification"
|
||||
|
||||
state GuardianSendNotif {
|
||||
[*] --> ComposingNotif
|
||||
ComposingNotif : Toggle: TEXT / VOICE NOTE
|
||||
ComposingNotif --> SendingTextNotif : Ketik pesan → Send
|
||||
ComposingNotif --> RecordingVoiceNote : Tahan tombol Record
|
||||
SendingTextNotif : POST /guardian/notifications/send\n{type: TEXT, content}\nBackend: FCM ke User + WebSocket\nUser TTS: "New message from Guardian"
|
||||
RecordingVoiceNote : record package → audio file\nPreview → Upload → POST send\n{type: VOICE_NOTE, voiceNoteUrl}
|
||||
SendingTextNotif --> ComposingNotif : Sent
|
||||
RecordingVoiceNote --> ComposingNotif : Sent
|
||||
}
|
||||
|
||||
GuardianSendNotif --> GuardianIdle : Kembali
|
||||
|
||||
' --- AI CONFIG ---
|
||||
GuardianIdle --> GuardianAiConfig : Tap "Configure AI Settings"
|
||||
|
||||
state GuardianAiConfig {
|
||||
[*] --> EditingAiConfig
|
||||
EditingAiConfig : GET /guardian/ai-config\nSlider confidenceThreshold (0.3-0.9)\nSlider alertDistanceClose (0.5-3m)\nSlider alertDistanceMedium (1-6m)\nDropdown maxInferenceFps (1,2,3,5,10)\nMulti-select enabled labels
|
||||
EditingAiConfig --> SavingAiConfig : Tap Save
|
||||
SavingAiConfig : PUT /guardian/ai-config\nBackend: update + FCM ke User\n"Guardian updated AI settings"
|
||||
SavingAiConfig --> EditingAiConfig : Saved
|
||||
}
|
||||
|
||||
GuardianAiConfig --> GuardianIdle : Kembali
|
||||
|
||||
' --- VOICE COMMANDS CONFIG ---
|
||||
GuardianIdle --> GuardianVoiceCmdConfig : Tap "Voice Commands"
|
||||
|
||||
state GuardianVoiceCmdConfig {
|
||||
[*] --> EditingVoiceCommands
|
||||
EditingVoiceCommands : GET /guardian/voice-commands\nList 14 commands\nEdit trigger phrase\nToggle enabled/disabled
|
||||
EditingVoiceCommands --> SavingVoiceCmd : Save
|
||||
SavingVoiceCmd : PUT /guardian/voice-commands\nFCM ke User: "Voice commands updated"
|
||||
SavingVoiceCmd --> EditingVoiceCommands : Saved
|
||||
}
|
||||
|
||||
GuardianVoiceCmdConfig --> GuardianIdle : Kembali
|
||||
|
||||
' --- HARDWARE SHORTCUTS ---
|
||||
GuardianIdle --> GuardianShortcutConfig : Tap "Shortcuts"
|
||||
|
||||
state GuardianShortcutConfig {
|
||||
[*] --> EditingShortcuts
|
||||
EditingShortcuts : GET /guardian/shortcuts\nList 5 shortcuts (Vol Up, Vol Down, dll)\n"Unassigned" jika belum di-set
|
||||
EditingShortcuts --> CapturingButton : Tap "Configure" per shortcut
|
||||
CapturingButton : FCM ke User's phone "enter capture mode"\nUser tekan tombol fisik → keyCode\nPUT /user/shortcuts {shortcutKey, buttonCode}
|
||||
CapturingButton --> EditingShortcuts : Button captured & saved
|
||||
}
|
||||
|
||||
GuardianShortcutConfig --> GuardianIdle : Kembali
|
||||
|
||||
' --- GEOFENCE ---
|
||||
GuardianIdle --> GuardianGeofenceConfig : Tap "Geofence"
|
||||
|
||||
state GuardianGeofenceConfig {
|
||||
[*] --> EditingGeofence
|
||||
EditingGeofence : FlutterMap: tap untuk set center\nSlider radius (50m - 5000m)\nToggle enable/disable geofence
|
||||
EditingGeofence --> SavingGeofence : Tap Save
|
||||
SavingGeofence : PUT /guardian/geofence\nBackend: simpan GeofenceConfig
|
||||
SavingGeofence --> EditingGeofence : Saved
|
||||
}
|
||||
|
||||
GuardianGeofenceConfig --> GuardianIdle : Kembali
|
||||
|
||||
' --- SOS HANDLING ---
|
||||
GuardianIdle --> SosAlertReceived : FCM HIGH PRIORITY / WebSocket SOS alert\n/queue/sos/{guardianId}
|
||||
|
||||
state SosAlertReceived {
|
||||
[*] --> DisplayingSosAlert
|
||||
DisplayingSosAlert : GuardianDashboard: highlight merah\nMap otomatis buka ke lokasi User\nTampil: "🚨 {User} needs help! Location: lat,lng"\nGET /guardian/sos-events
|
||||
DisplayingSosAlert --> AcknowledgingSos : Tap "Acknowledge"
|
||||
AcknowledgingSos : PUT /guardian/sos/{id}/acknowledge\nBackend: status → ACKNOWLEDGED\nFCM ke User: "Guardian is on the way"\nUser TTS: "Help is coming."
|
||||
AcknowledgingSos --> [*]
|
||||
}
|
||||
|
||||
SosAlertReceived --> GuardianIdle : Selesai
|
||||
|
||||
' --- CALL (Guardian inisiasi) ---
|
||||
GuardianIdle --> GuardianOutgoingCall : Tap "Call User"
|
||||
|
||||
state GuardianOutgoingCall {
|
||||
[*] --> GuardianRequestToken
|
||||
GuardianRequestToken : POST /shared/call/token\nPOST /shared/call/notify → FCM ke User "Incoming Call"
|
||||
GuardianRequestToken --> GuardianCalling : Join Agora channel
|
||||
GuardianCalling : "Calling User..." + ring animasi
|
||||
GuardianCalling --> GuardianInCall : User accept
|
||||
GuardianCalling --> GuardianIdle : User decline / timeout
|
||||
GuardianInCall : Audio call aktif\nMute + Speaker + End
|
||||
GuardianInCall --> GuardianIdle : End call
|
||||
}
|
||||
|
||||
GuardianOutgoingCall --> GuardianIdle : Selesai
|
||||
|
||||
' --- INCOMING CALL (Guardian terima) ---
|
||||
GuardianIdle --> GuardianIncomingCall : FCM INCOMING_CALL dari User
|
||||
|
||||
state GuardianIncomingCall {
|
||||
[*] --> GuardianShowIncomingCall
|
||||
GuardianShowIncomingCall : IncomingCallScreen\nTTS: "Incoming call from User"\nAccept (hijau) / Decline (merah)
|
||||
GuardianShowIncomingCall --> GuardianInCallSession : Accept
|
||||
GuardianShowIncomingCall --> GuardianIdle : Decline
|
||||
GuardianInCallSession : Agora RTC connected
|
||||
GuardianInCallSession --> GuardianIdle : End call
|
||||
}
|
||||
|
||||
GuardianIncomingCall --> GuardianIdle : Selesai
|
||||
|
||||
' --- LOGOUT ---
|
||||
GuardianIdle --> GuardianLoggingOut : Tap Logout
|
||||
|
||||
GuardianLoggingOut : POST /auth/logout {refreshToken}\nSecureStorage.clearAll()\nWebSocket.disconnect()
|
||||
GuardianLoggingOut --> [*]
|
||||
}
|
||||
|
||||
' ============================================================
|
||||
' GLOBAL LOGOUT / TOKEN REFRESH
|
||||
' ============================================================
|
||||
UserShell --> Unauthenticated : Logout / Token Expired (401 → refresh gagal)
|
||||
GuardianShell --> Unauthenticated : Logout / Token Expired (401 → refresh gagal)
|
||||
|
||||
note as TokenRefreshNote
|
||||
**Token Refresh (AuthInterceptor):**
|
||||
Setiap request: inject "Authorization: Bearer {accessToken}"
|
||||
Jika 401 → POST /auth/refresh {refreshToken}
|
||||
Jika refresh OK → retry request dengan token baru
|
||||
Jika refresh 401 → navigate ke /login
|
||||
Access token: 1 jam | Refresh token: 30 hari
|
||||
end note
|
||||
|
||||
' ============================================================
|
||||
' GEOFENCE EXIT (Parallel event)
|
||||
' ============================================================
|
||||
note as GeofenceNote
|
||||
**Geofence Alert (Backend event):**
|
||||
Setiap POST /user/location (saat WalkGuide aktif, 5 detik):
|
||||
Backend: GeofenceService.checkGeofence()
|
||||
→ Haversine distance dari center ke current position
|
||||
→ Jika keluar radius: FCM ke Guardian "User has left geofence!"
|
||||
→ ActivityLog: GEOFENCE_EXIT
|
||||
→ Guardian: notifikasi di GuardianDashboard
|
||||
end note
|
||||
|
||||
@enduml
|
||||
176
ooad-docs/diagrams/usecase-diagram.puml
Normal file
176
ooad-docs/diagrams/usecase-diagram.puml
Normal file
@ -0,0 +1,176 @@
|
||||
@startuml
|
||||
left to right direction
|
||||
skinparam packageStyle rectangle
|
||||
skinparam rectangle {
|
||||
BackgroundColor<<actor>> LightGray
|
||||
BorderColor Black
|
||||
}
|
||||
|
||||
' ==========================================
|
||||
' AKTOR UTAMA (Kiri)
|
||||
' ==========================================
|
||||
actor "Tunanetra\n(ROLE_USER)" as User
|
||||
actor "Pendamping\n(ROLE_GUARDIAN)" as Guardian
|
||||
|
||||
' ==========================================
|
||||
' BATAS SISTEM (Tengah)
|
||||
' ==========================================
|
||||
rectangle "WalkGuide System" {
|
||||
|
||||
' --- AUTH ---
|
||||
usecase "Hubungkan ke Server" as UC_Server
|
||||
usecase "Daftar Akun" as UC_Register
|
||||
usecase "Masuk Sistem (Login)" as UC_Login
|
||||
usecase "Logout" as UC_Logout
|
||||
usecase "Validasi & Refresh JWT" as UC_JWT
|
||||
usecase "RBAC Role Routing" as UC_RBAC
|
||||
|
||||
' --- PAIRING ---
|
||||
usecase "Kirim Undangan Pairing" as UC_Invite
|
||||
usecase "Terima / Tolak Pairing" as UC_PairingResponse
|
||||
usecase "Putus Pairing (Unpair)" as UC_Unpair
|
||||
|
||||
' --- WALKGUIDE CORE ---
|
||||
usecase "Aktifkan Walk Guide Mode" as UC_Walk
|
||||
usecase "Deteksi Obstacle\nReal-time (YOLO)" as UC_Detect
|
||||
usecase "Analisa Arah & Jarak\nObstacle" as UC_Analyze
|
||||
usecase "Umpan Balik TTS (Audio)" as UC_TTS
|
||||
usecase "Umpan Balik Vibrasi\n(Haptic)" as UC_Vib
|
||||
usecase "Log Obstacle ke Backend" as UC_ObsLog
|
||||
|
||||
' --- LOKASI ---
|
||||
usecase "Kirim Lokasi GPS\nReal-time (WebSocket)" as UC_Location
|
||||
usecase "Mode Navigasi\n(OpenStreetMap)" as UC_Navigation
|
||||
usecase "\"Where Am I?\"\n(Reverse Geocode)" as UC_WhereAmI
|
||||
|
||||
' --- VOICE & SHORTCUT ---
|
||||
usecase "Perintah Suara\n(Speech-to-Text)" as UC_Voice
|
||||
usecase "Shortcut Tombol\nFisik Hardware" as UC_Hardware
|
||||
|
||||
' --- SOS & DARURAT ---
|
||||
usecase "Kirim SOS (Darurat)" as UC_SOS
|
||||
usecase "Panggil Guardian\n(VoIP Agora)" as UC_Call
|
||||
usecase "Terima Panggilan Masuk" as UC_IncomingCall
|
||||
|
||||
' --- NOTIFIKASI ---
|
||||
usecase "Terima Notifikasi FCM" as UC_FCMReceive
|
||||
usecase "Kirim Notifikasi\nTeks / Voice Note" as UC_SendNotif
|
||||
usecase "Baca Notifikasi via TTS" as UC_ReadNotif
|
||||
usecase "Tandai Notifikasi\nSudah Dibaca" as UC_MarkRead
|
||||
|
||||
' --- LOG ---
|
||||
usecase "Lihat Log Aktivitas" as UC_ViewLog
|
||||
usecase "Lihat Log Obstacle" as UC_ViewObsLog
|
||||
|
||||
' --- PENGATURAN ---
|
||||
usecase "Atur Preferensi TTS\n& Haptic" as UC_UserSettings
|
||||
usecase "Kelola Perintah\nSuara Kustom" as UC_VoiceConfig
|
||||
usecase "Kelola Shortcut\nTombol Fisik" as UC_ShortcutConfig
|
||||
|
||||
' --- GUARDIAN DASHBOARD ---
|
||||
usecase "Monitor Lokasi User\nReal-time (Map)" as UC_MonitorMap
|
||||
usecase "Kelola Konfigurasi AI\n(Threshold YOLO)" as UC_AIConfig
|
||||
usecase "Kelola Geofence\n(Radius Aman)" as UC_Geofence
|
||||
usecase "Kelola Akun\nUser Paired" as UC_ManageUser
|
||||
usecase "Konfirmasi/Acknowledge\nSOS" as UC_AckSOS
|
||||
}
|
||||
|
||||
' ==========================================
|
||||
' AKTOR SISTEM (Kanan)
|
||||
' ==========================================
|
||||
rectangle "YOLOv8n\n(On-Device AI)" as AI <<actor>>
|
||||
rectangle "Spring Boot\nBackend" as Backend <<actor>>
|
||||
rectangle "Firebase FCM" as FCM <<actor>>
|
||||
rectangle "Agora RTC" as Agora <<actor>>
|
||||
|
||||
' ==========================================
|
||||
' RELASI USER (ROLE_USER)
|
||||
' ==========================================
|
||||
User "*" -- "*" UC_Server
|
||||
User "*" -- "*" UC_Login
|
||||
User "*" -- "*" UC_Register
|
||||
User "*" -- "*" UC_Logout
|
||||
User "*" -- "*" UC_PairingResponse
|
||||
User "*" -- "*" UC_Unpair
|
||||
User "*" -- "*" UC_Walk
|
||||
User "*" -- "*" UC_Voice
|
||||
User "*" -- "*" UC_Hardware
|
||||
User "*" -- "*" UC_SOS
|
||||
User "*" -- "*" UC_Call
|
||||
User "*" -- "*" UC_IncomingCall
|
||||
User "*" -- "*" UC_Navigation
|
||||
User "*" -- "*" UC_WhereAmI
|
||||
User "*" -- "*" UC_FCMReceive
|
||||
User "*" -- "*" UC_ReadNotif
|
||||
User "*" -- "*" UC_MarkRead
|
||||
User "*" -- "*" UC_ViewLog
|
||||
User "*" -- "*" UC_UserSettings
|
||||
User "*" -- "*" UC_ShortcutConfig
|
||||
|
||||
' ==========================================
|
||||
' RELASI GUARDIAN (ROLE_GUARDIAN)
|
||||
' ==========================================
|
||||
Guardian "*" -- "*" UC_Login
|
||||
Guardian "*" -- "*" UC_Register
|
||||
Guardian "*" -- "*" UC_Logout
|
||||
Guardian "*" -- "*" UC_Invite
|
||||
Guardian "*" -- "*" UC_Unpair
|
||||
Guardian "*" -- "*" UC_MonitorMap
|
||||
Guardian "*" -- "*" UC_SendNotif
|
||||
Guardian "*" -- "*" UC_AIConfig
|
||||
Guardian "*" -- "*" UC_Geofence
|
||||
Guardian "*" -- "*" UC_VoiceConfig
|
||||
Guardian "*" -- "*" UC_ShortcutConfig
|
||||
Guardian "*" -- "*" UC_ManageUser
|
||||
Guardian "*" -- "*" UC_IncomingCall
|
||||
Guardian "*" -- "*" UC_ViewLog
|
||||
Guardian "*" -- "*" UC_ViewObsLog
|
||||
Guardian "*" -- "*" UC_Call
|
||||
Guardian "*" -- "*" UC_AckSOS
|
||||
|
||||
' ==========================================
|
||||
' INCLUDE & EXTEND
|
||||
' ==========================================
|
||||
UC_Login ..> UC_JWT : <<include>>
|
||||
UC_Register ..> UC_JWT : <<include>>
|
||||
UC_JWT ..> UC_RBAC : <<include>>
|
||||
|
||||
UC_Walk ..> UC_Detect : <<include>>
|
||||
UC_Detect ..> UC_Analyze : <<include>>
|
||||
UC_Analyze <.. UC_TTS : <<extend>>
|
||||
UC_Analyze <.. UC_Vib : <<extend>>
|
||||
UC_Detect ..> UC_ObsLog : <<include>>
|
||||
UC_Walk ..> UC_Location : <<include>>
|
||||
|
||||
UC_Walk <.. UC_Voice : <<extend>>
|
||||
UC_SOS <.. UC_Voice : <<extend>>
|
||||
UC_Call <.. UC_Voice : <<extend>>
|
||||
|
||||
UC_Walk <.. UC_Hardware : <<extend>>
|
||||
UC_SOS <.. UC_Hardware : <<extend>>
|
||||
UC_Call <.. UC_Hardware : <<extend>>
|
||||
|
||||
UC_SOS ..> UC_FCMReceive : <<include>>
|
||||
UC_SendNotif ..> UC_FCMReceive : <<include>>
|
||||
UC_FCMReceive <.. UC_TTS : <<extend>>
|
||||
UC_Call <.. UC_IncomingCall : <<extend>>
|
||||
UC_Geofence <.. UC_FCMReceive : <<extend>>
|
||||
|
||||
' ==========================================
|
||||
' RELASI KE SISTEM EKSTERNAL
|
||||
' ==========================================
|
||||
UC_Detect "*" ----- "*" AI
|
||||
UC_JWT "*" ----- "*" Backend
|
||||
UC_ObsLog "*" ----- "*" Backend
|
||||
UC_Location "*" ----- "*" Backend
|
||||
UC_SOS "*" ----- "*" Backend
|
||||
UC_SendNotif "*" ----- "*" Backend
|
||||
UC_ViewLog "*" ----- "*" Backend
|
||||
UC_ViewObsLog "*" ----- "*" Backend
|
||||
UC_AIConfig "*" ----- "*" Backend
|
||||
UC_FCMReceive "*" ----- "*" FCM
|
||||
UC_SendNotif "*" ----- "*" FCM
|
||||
UC_Call "*" ----- "*" Agora
|
||||
UC_IncomingCall "*" ----- "*" Agora
|
||||
|
||||
@enduml
|
||||
Loading…
x
Reference in New Issue
Block a user