From f697ef16cd3067a2e15ba58bace8324f5ad1e652 Mon Sep 17 00:00:00 2001 From: 5803024019 Date: Mon, 18 May 2026 15:40:55 +0700 Subject: [PATCH 1/6] chore(config): move hardcoded secrets to environment variables --- .gitignore | 3 + walkguide-backend/demo/pom.xml | 71 +++++++------------ .../demo/src/main/resources/.env.example | 35 +++++++++ .../src/main/resources/application-dev.yml | 31 ++++++++ .../src/main/resources/application-prod.yml | 35 +++++++++ .../src/main/resources/application.properties | 21 +++--- .../walkguide/service/AuthServiceTest.java | 4 ++ 7 files changed, 141 insertions(+), 59 deletions(-) create mode 100644 walkguide-backend/demo/src/main/resources/.env.example create mode 100644 walkguide-backend/demo/src/main/resources/application-dev.yml create mode 100644 walkguide-backend/demo/src/main/resources/application-prod.yml diff --git a/.gitignore b/.gitignore index f32668e..74c6ef0 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,6 @@ build/ ### VS Code ### .vscode/ + +.env +*.env diff --git a/walkguide-backend/demo/pom.xml b/walkguide-backend/demo/pom.xml index 99284b6..624f7b5 100644 --- a/walkguide-backend/demo/pom.xml +++ b/walkguide-backend/demo/pom.xml @@ -136,53 +136,30 @@ - org.apache.maven.plugins - maven-compiler-plugin - - - - org.projectlombok - lombok - 1.18.36 - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - - - default-compile - compile - compile - - - - org.projectlombok - lombok - 1.18.36 - - - - - - default-testCompile - test-compile - testCompile - - - - org.projectlombok - lombok - 1.18.36 - - - - - - + org.apache.maven.plugins + maven-compiler-plugin + + + + org.projectlombok + lombok + 1.18.36 + + + + + + default-compile + compile + compile + + + default-testCompile + test-compile + testCompile + + + diff --git a/walkguide-backend/demo/src/main/resources/.env.example b/walkguide-backend/demo/src/main/resources/.env.example new file mode 100644 index 0000000..11acc7a --- /dev/null +++ b/walkguide-backend/demo/src/main/resources/.env.example @@ -0,0 +1,35 @@ +# =================================================== +# Profile: prod (production) +# Aktifkan dengan: --spring.profiles.active=prod +# Semua nilai WAJIB diisi via environment variable +# Tidak ada default value — akan gagal start jika kosong +# =================================================== + +spring: + datasource: + url: ${DB_URL} + username: ${DB_USERNAME} + password: ${DB_PASSWORD} + + jpa: + show-sql: false + properties: + hibernate: + format_sql: false + +server: + port: ${PORT:8080} + +jwt: + secret: ${JWT_SECRET} + expiration: ${JWT_EXPIRATION:86400000} + +agora: + app-id: ${AGORA_APP_ID} + app-certificate: ${AGORA_APP_CERTIFICATE} + +logging: + level: + com.walkguide: INFO + org.springframework.messaging: WARN + org.springframework.web.socket: WARN \ No newline at end of file diff --git a/walkguide-backend/demo/src/main/resources/application-dev.yml b/walkguide-backend/demo/src/main/resources/application-dev.yml new file mode 100644 index 0000000..57096ce --- /dev/null +++ b/walkguide-backend/demo/src/main/resources/application-dev.yml @@ -0,0 +1,31 @@ +# =================================================== +# Profile: dev (development lokal) +# Aktifkan dengan: --spring.profiles.active=dev +# atau set env: SPRING_PROFILES_ACTIVE=dev +# =================================================== + +spring: + datasource: + url: ${DB_URL:jdbc:postgresql://202.46.28.160:2002/uas_5803024001} + username: ${DB_USERNAME:5803024001} + password: ${DB_PASSWORD:pw5803024001} + + jpa: + show-sql: true + properties: + hibernate: + format_sql: true + +jwt: + secret: ${JWT_SECRET:404E635266556A586E3272357538782F413F4428472B4B6250645367566B5970} + expiration: 86400000 + +agora: + app-id: ${AGORA_APP_ID:} + app-certificate: ${AGORA_APP_CERTIFICATE:} + +logging: + level: + com.walkguide: DEBUG + org.springframework.messaging: DEBUG + org.springframework.web.socket: DEBUG \ No newline at end of file diff --git a/walkguide-backend/demo/src/main/resources/application-prod.yml b/walkguide-backend/demo/src/main/resources/application-prod.yml new file mode 100644 index 0000000..11acc7a --- /dev/null +++ b/walkguide-backend/demo/src/main/resources/application-prod.yml @@ -0,0 +1,35 @@ +# =================================================== +# Profile: prod (production) +# Aktifkan dengan: --spring.profiles.active=prod +# Semua nilai WAJIB diisi via environment variable +# Tidak ada default value — akan gagal start jika kosong +# =================================================== + +spring: + datasource: + url: ${DB_URL} + username: ${DB_USERNAME} + password: ${DB_PASSWORD} + + jpa: + show-sql: false + properties: + hibernate: + format_sql: false + +server: + port: ${PORT:8080} + +jwt: + secret: ${JWT_SECRET} + expiration: ${JWT_EXPIRATION:86400000} + +agora: + app-id: ${AGORA_APP_ID} + app-certificate: ${AGORA_APP_CERTIFICATE} + +logging: + level: + com.walkguide: INFO + org.springframework.messaging: WARN + org.springframework.web.socket: WARN \ No newline at end of file diff --git a/walkguide-backend/demo/src/main/resources/application.properties b/walkguide-backend/demo/src/main/resources/application.properties index b7c649f..99ee4f7 100644 --- a/walkguide-backend/demo/src/main/resources/application.properties +++ b/walkguide-backend/demo/src/main/resources/application.properties @@ -1,10 +1,10 @@ # ===== SERVER ===== -server.port=8080 +server.port=${SERVER_PORT:8080} # ===== POSTGRESQL CONNECTION ===== -spring.datasource.url=jdbc:postgresql://202.46.28.160:2002/uas_5803024001 -spring.datasource.username=5803024001 -spring.datasource.password=pw5803024001 +spring.datasource.url=${DB_URL} +spring.datasource.username=${DB_USERNAME} +spring.datasource.password=${DB_PASSWORD} spring.datasource.driver-class-name=org.postgresql.Driver # ===== JPA / HIBERNATE ===== @@ -19,24 +19,21 @@ spring.flyway.locations=classpath:db/migration spring.flyway.baseline-on-migrate=true # ===== JWT ===== -jwt.secret=404E635266556A586E3272357538782F413F4428472B4B6250645367566B5970 -jwt.expiration=86400000 +jwt.secret=${JWT_SECRET} +jwt.expiration=${JWT_EXPIRATION:86400000} # ===== SWAGGER ===== springdoc.swagger-ui.path=/swagger-ui.html springdoc.api-docs.path=/v3/api-docs # ===== AGORA RTC ===== -# Isi dengan nilai dari dashboard.agora.io setelah buat project -# Jika kosong: AgoraTokenService akan generate token kosong (mode demo/testing) -agora.app-id= -agora.app-certificate= +agora.app-id=${AGORA_APP_ID:} +agora.app-certificate=${AGORA_APP_CERTIFICATE:} # ===== WEBSOCKET ===== # WebSocket auto-dikonfigurasi oleh WebSocketConfig.java -# Tidak perlu config tambahan — Spring Boot auto-detect starter-websocket # ===== LOGGING ===== logging.level.com.walkguide=DEBUG logging.level.org.springframework.messaging=INFO -logging.level.org.springframework.web.socket=INFO +logging.level.org.springframework.web.socket=INFO \ No newline at end of file diff --git a/walkguide-backend/demo/src/test/java/com/walkguide/service/AuthServiceTest.java b/walkguide-backend/demo/src/test/java/com/walkguide/service/AuthServiceTest.java index bd55d4c..1c45b62 100644 --- a/walkguide-backend/demo/src/test/java/com/walkguide/service/AuthServiceTest.java +++ b/walkguide-backend/demo/src/test/java/com/walkguide/service/AuthServiceTest.java @@ -7,9 +7,11 @@ import com.walkguide.entity.RefreshToken; import com.walkguide.entity.User; import com.walkguide.entity.UserSettings; import com.walkguide.enums.ActivityLogType; +import com.walkguide.repository.HardwareShortcutRepository; import com.walkguide.repository.RefreshTokenRepository; import com.walkguide.repository.UserRepository; import com.walkguide.repository.UserSettingsRepository; +import com.walkguide.repository.VoiceCommandConfigRepository; import com.walkguide.security.JwtUtil; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -35,6 +37,8 @@ class AuthServiceTest { @Mock UserRepository userRepository; @Mock RefreshTokenRepository refreshTokenRepository; @Mock UserSettingsRepository userSettingsRepository; + @Mock HardwareShortcutRepository hardwareShortcutRepository; + @Mock VoiceCommandConfigRepository voiceCommandConfigRepository; @Mock ActivityLogService activityLogService; @Mock JwtUtil jwtUtil; @Mock PasswordEncoder passwordEncoder; From 23d0cf6f666f6fe8b632a2d957b196741a7c05af Mon Sep 17 00:00:00 2001 From: 5803024019 Date: Tue, 19 May 2026 12:27:08 +0700 Subject: [PATCH 2/6] feat: finalize AI analyzer, sync endpoints, and stabilize builds --- .../walkguide/controller/UserController.java | 10 +++ .../demo/src/main/resources/openapi.yaml | 4 + .../com/walkguide/DemoApplicationTests.java | 14 +++- .../controller/UserControllerTest.java | 20 +++++ .../android/app/build.gradle.kts | 7 +- .../walkguide_app/android/build.gradle.kts | 36 ++++++++ .../walkguide_app/android/gradle.properties | 1 + walkguide-mobile/walkguide_app/dart_test.yaml | 1 + .../lib/core/ai/obstacle_analyzer.dart | 83 ++++++++++++++++++- .../lib/core/constants/app_constants.dart | 5 +- .../lib/core/services/call_service.dart | 6 +- walkguide-mobile/walkguide_app/pubspec.lock | 22 ++--- walkguide-mobile/walkguide_app/pubspec.yaml | 3 + .../test/integration_test/app_flow_test.dart | 9 +- .../flow_2_walkguide_start_stop_sos_test.dart | 22 ++--- .../flow_3_notification_read_all_test.dart | 78 ++++++++--------- .../test/unit/login_use_case_test.dart | 14 +++- .../test/unit/obstacle_analyzer_test.dart | 27 +++--- .../test/unit/register_use_case_test.dart | 28 ++++++- .../test/widget/login_screen_test.dart | 30 +++---- .../test/widget/manual_screen_test.dart | 71 ++++++++++------ .../test/widget/notification_screen_test.dart | 19 +++-- .../test/widget/sos_screen_test.dart | 7 +- 23 files changed, 375 insertions(+), 142 deletions(-) create mode 100644 walkguide-mobile/walkguide_app/dart_test.yaml diff --git a/walkguide-backend/demo/src/main/java/com/walkguide/controller/UserController.java b/walkguide-backend/demo/src/main/java/com/walkguide/controller/UserController.java index 65f3d72..e1af2b1 100644 --- a/walkguide-backend/demo/src/main/java/com/walkguide/controller/UserController.java +++ b/walkguide-backend/demo/src/main/java/com/walkguide/controller/UserController.java @@ -114,6 +114,16 @@ public class UserController { "SOS dikirim! Guardian sudah diberitahu.")); } + @GetMapping("/sos-events") + public ResponseEntity>> getSosEvents( + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "20") int size) { + return ResponseEntity.ok(ApiResponse.ok( + sosService.getSosEvents(SecurityHelper.getCurrentUserId(), + PageRequest.of(page, size)), + "Riwayat SOS")); + } + @GetMapping("/activity-logs") public ResponseEntity>> getActivityLogs( @RequestParam(defaultValue = "0") int page, diff --git a/walkguide-backend/demo/src/main/resources/openapi.yaml b/walkguide-backend/demo/src/main/resources/openapi.yaml index 7b028ae..f2c469a 100644 --- a/walkguide-backend/demo/src/main/resources/openapi.yaml +++ b/walkguide-backend/demo/src/main/resources/openapi.yaml @@ -183,6 +183,10 @@ paths: post: responses: "200": { description: SOS triggered } + /user/sos-events: + get: + responses: + "200": { description: User SOS history } /user/activity-logs: get: responses: diff --git a/walkguide-backend/demo/src/test/java/com/walkguide/DemoApplicationTests.java b/walkguide-backend/demo/src/test/java/com/walkguide/DemoApplicationTests.java index f0d87ef..8b3d779 100644 --- a/walkguide-backend/demo/src/test/java/com/walkguide/DemoApplicationTests.java +++ b/walkguide-backend/demo/src/test/java/com/walkguide/DemoApplicationTests.java @@ -1,11 +1,23 @@ package com.walkguide; +import com.walkguide.config.DataSeeder; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; -@SpringBootTest +@SpringBootTest(properties = { + "spring.datasource.url=jdbc:postgresql://localhost:5432/walkguide_test", + "spring.datasource.username=test", + "spring.datasource.password=test", + "spring.flyway.enabled=false", + "spring.jpa.hibernate.ddl-auto=none", + "jwt.secret=404E635266556A586E3272357538782F413F4428472B4B6250645367566B5970" +}) class DemoApplicationTests { + @MockBean + private DataSeeder dataSeeder; + @Test void contextLoads() { } diff --git a/walkguide-backend/demo/src/test/java/com/walkguide/controller/UserControllerTest.java b/walkguide-backend/demo/src/test/java/com/walkguide/controller/UserControllerTest.java index c393145..a012a28 100644 --- a/walkguide-backend/demo/src/test/java/com/walkguide/controller/UserControllerTest.java +++ b/walkguide-backend/demo/src/test/java/com/walkguide/controller/UserControllerTest.java @@ -281,6 +281,26 @@ class UserControllerTest { } } + @Test + @DisplayName("GET /api/v1/user/sos-events - harus return paginated riwayat SOS user") + void getSosEvents_shouldReturn200() throws Exception { + try (MockedStatic sh = mockStatic(SecurityHelper.class)) { + sh.when(SecurityHelper::getCurrentUserId).thenReturn(1L); + + Page page = new PageImpl<>(List.of()); + when(sosService.getSosEvents(eq(1L), any(PageRequest.class))).thenReturn(page); + + mockMvc.perform(get("/api/v1/user/sos-events") + .param("page", "0") + .param("size", "10")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.success").value(true)) + .andExpect(jsonPath("$.message").value("Riwayat SOS")); + + verify(sosService).getSosEvents(eq(1L), any(PageRequest.class)); + } + } + // ===== ACTIVITY LOGS ===== @Test diff --git a/walkguide-mobile/walkguide_app/android/app/build.gradle.kts b/walkguide-mobile/walkguide_app/android/app/build.gradle.kts index a1771e9..ae4ef59 100644 --- a/walkguide-mobile/walkguide_app/android/app/build.gradle.kts +++ b/walkguide-mobile/walkguide_app/android/app/build.gradle.kts @@ -13,6 +13,7 @@ android { compileOptions { sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 + isCoreLibraryDesugaringEnabled = true } kotlinOptions { @@ -24,7 +25,7 @@ android { applicationId = "com.example.walkguide_app" // You can update the following values to match your application needs. // For more information, see: https://flutter.dev/to/review-gradle-config. - minSdk = flutter.minSdkVersion + minSdk = 26 targetSdk = flutter.targetSdkVersion versionCode = flutter.versionCode versionName = flutter.versionName @@ -42,3 +43,7 @@ android { flutter { source = "../.." } + +dependencies { + coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4") +} diff --git a/walkguide-mobile/walkguide_app/android/build.gradle.kts b/walkguide-mobile/walkguide_app/android/build.gradle.kts index dbee657..b93f306 100644 --- a/walkguide-mobile/walkguide_app/android/build.gradle.kts +++ b/walkguide-mobile/walkguide_app/android/build.gradle.kts @@ -15,6 +15,42 @@ subprojects { val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) project.layout.buildDirectory.value(newSubprojectBuildDir) } +subprojects { + if (project.name == "agora_rtc_engine" || project.name == "iris_method_channel") { + tasks.configureEach { + if (name.startsWith("configureCMake") || name.startsWith("buildCMake")) { + doFirst { + val cmakeFile = listOf( + project.file("src/main/cpp/CMakeLists.txt"), + project.file("../src/CMakeLists.txt"), + ).firstOrNull { it.exists() } + + if (cmakeFile != null) { + val text = cmakeFile.readText() + if (!text.contains("c++_shared")) { + val patchedText = + if (text.contains("target_link_libraries")) { + text.replace( + " EGL\n )", + " EGL\n c++_shared\n )", + ) + } else { + text + """ + +target_link_libraries(${'$'}{LIBRARY_NAME} + PRIVATE + c++_shared + ) +""" + } + cmakeFile.writeText(patchedText) + } + } + } + } + } + } +} subprojects { project.evaluationDependsOn(":app") } diff --git a/walkguide-mobile/walkguide_app/android/gradle.properties b/walkguide-mobile/walkguide_app/android/gradle.properties index f018a61..7ed25f5 100644 --- a/walkguide-mobile/walkguide_app/android/gradle.properties +++ b/walkguide-mobile/walkguide_app/android/gradle.properties @@ -1,3 +1,4 @@ org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true android.enableJetifier=true +kotlin.incremental=false diff --git a/walkguide-mobile/walkguide_app/dart_test.yaml b/walkguide-mobile/walkguide_app/dart_test.yaml new file mode 100644 index 0000000..6d8e711 --- /dev/null +++ b/walkguide-mobile/walkguide_app/dart_test.yaml @@ -0,0 +1 @@ +concurrency: 1 diff --git a/walkguide-mobile/walkguide_app/lib/core/ai/obstacle_analyzer.dart b/walkguide-mobile/walkguide_app/lib/core/ai/obstacle_analyzer.dart index 8d06ca9..8eb6dd6 100644 --- a/walkguide-mobile/walkguide_app/lib/core/ai/obstacle_analyzer.dart +++ b/walkguide-mobile/walkguide_app/lib/core/ai/obstacle_analyzer.dart @@ -1,5 +1,23 @@ enum ObstacleDirection { left, center, right } +class BoundingBox { + final double left; + final double top; + final double right; + final double bottom; + + const BoundingBox({ + required this.left, + required this.top, + required this.right, + required this.bottom, + }); + + double get width => right - left; + double get height => bottom - top; + double get centerX => left + width / 2; +} + class DetectionResult { final String label; final double confidence; @@ -26,12 +44,73 @@ class DetectionResult { } class ObstacleAnalyzer { - DetectionResult analyzeFallback({String label = 'person', double confidence = 0.86}) { + static const double frameWidth = 640.0; + static const double frameHeight = 480.0; + + ObstacleDirection analyzeDirection(BoundingBox box) { + final cx = box.centerX; + if (cx < frameWidth * 0.33) return ObstacleDirection.left; + if (cx > frameWidth * 0.67) return ObstacleDirection.right; + return ObstacleDirection.center; + } + + String estimateDistance(BoundingBox box) { + final ratio = box.height / frameHeight; + if (ratio > 0.60) return 'Very Close (< 1m)'; + if (ratio > 0.35) return 'Close (1-2m)'; + if (ratio > 0.15) return 'Medium (2-4m)'; + return 'Far (> 4m)'; + } + + String buildTtsMessage(DetectionResult result) { + final directionLabel = switch (result.direction) { + ObstacleDirection.left => 'kiri', + ObstacleDirection.center => 'depan', + ObstacleDirection.right => 'kanan', + }; + return 'Hati-hati, ${result.label} di $directionLabel. ' + 'Jarak ${result.estimatedDistance}.'; + } + + DetectionResult? prioritize(List detections) { + if (detections.isEmpty) return null; + const order = [ + 'Very Close (< 1m)', + 'Very Close', + 'Close (1-2m)', + 'Close', + 'Medium (2-4m)', + 'Medium', + 'Far (> 4m)', + 'Far', + ]; + final sorted = List.of(detections); + sorted.sort((a, b) { + final ai = order.indexOf(a.estimatedDistance); + final bi = order.indexOf(b.estimatedDistance); + final aRank = ai == -1 ? order.length : ai; + final bRank = bi == -1 ? order.length : bi; + return aRank.compareTo(bRank); + }); + return sorted.first; + } + + List filterByConfidence( + List detections, + double threshold, + ) { + return detections.where((d) => d.confidence >= threshold).toList(); + } + + DetectionResult analyzeFallback({ + String label = 'person', + double confidence = 0.86, + }) { return DetectionResult( label: label, confidence: confidence, direction: ObstacleDirection.center, - estimatedDistance: 'Close', + estimatedDistance: 'Close (1-2m)', ); } } diff --git a/walkguide-mobile/walkguide_app/lib/core/constants/app_constants.dart b/walkguide-mobile/walkguide_app/lib/core/constants/app_constants.dart index e16a5df..cc6cbbf 100644 --- a/walkguide-mobile/walkguide_app/lib/core/constants/app_constants.dart +++ b/walkguide-mobile/walkguide_app/lib/core/constants/app_constants.dart @@ -61,6 +61,7 @@ class AppConstants { await prefs.setString(_selectedYoloModelKey, path); } - // Agora - ganti dengan App ID dari agora.io - static const String agoraAppId = 'YOUR_AGORA_APP_ID'; + // Agora App ID diisi saat build: --dart-define=AGORA_APP_ID=... + static const String agoraAppId = + String.fromEnvironment('AGORA_APP_ID', defaultValue: ''); } diff --git a/walkguide-mobile/walkguide_app/lib/core/services/call_service.dart b/walkguide-mobile/walkguide_app/lib/core/services/call_service.dart index b26c91b..c52e5f3 100644 --- a/walkguide-mobile/walkguide_app/lib/core/services/call_service.dart +++ b/walkguide-mobile/walkguide_app/lib/core/services/call_service.dart @@ -20,7 +20,7 @@ class CallService { } Future getPairedReceiverId() async { - final res = await _apiClient.dio.get('/pairing/status'); + final res = await _apiClient.dio.get('/shared/pairing/status'); final data = res.data['data']; if (data is! Map) return null; final id = data['pairedWithId']; @@ -72,6 +72,10 @@ class CallService { int uid = 0, }) async { try { + if (AppConstants.agoraAppId.isEmpty) { + debugPrint('Agora join skipped: AGORA_APP_ID is not configured'); + return false; + } _engine ??= createAgoraRtcEngine(); await _engine!.initialize(const RtcEngineContext(appId: AppConstants.agoraAppId)); await _engine!.enableAudio(); diff --git a/walkguide-mobile/walkguide_app/pubspec.lock b/walkguide-mobile/walkguide_app/pubspec.lock index a5f4313..0905ef5 100644 --- a/walkguide-mobile/walkguide_app/pubspec.lock +++ b/walkguide-mobile/walkguide_app/pubspec.lock @@ -947,10 +947,10 @@ packages: dependency: transitive description: name: meta - sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" url: "https://pub.dev" source: hosted - version: "1.16.0" + version: "1.17.0" mgrs_dart: dependency: transitive description: @@ -1264,13 +1264,13 @@ packages: source: hosted version: "1.2.2" record_linux: - dependency: transitive + dependency: "direct overridden" description: name: record_linux - sha256: "74d41a9ebb1eb498a38e9a813dd524e8f0b4fdd627270bda9756f437b110a3e3" + sha256: c31a35cc158cd666fc6395f7f56fc054f31685571684be6b97670a27649ce5c7 url: "https://pub.dev" source: hosted - version: "0.7.2" + version: "1.3.0" record_platform_interface: dependency: transitive description: @@ -1592,26 +1592,26 @@ packages: dependency: transitive description: name: test - sha256: "65e29d831719be0591f7b3b1a32a3cda258ec98c58c7b25f7b84241bc31215bb" + sha256: "75906bf273541b676716d1ca7627a17e4c4070a3a16272b7a3dc7da3b9f3f6b7" url: "https://pub.dev" source: hosted - version: "1.26.2" + version: "1.26.3" test_api: dependency: transitive description: name: test_api - sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" + sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 url: "https://pub.dev" source: hosted - version: "0.7.6" + version: "0.7.7" test_core: dependency: transitive description: name: test_core - sha256: "80bf5a02b60af04b09e14f6fe68b921aad119493e26e490deaca5993fef1b05a" + sha256: "0cc24b5ff94b38d2ae73e1eb43cc302b77964fbf67abad1e296025b78deb53d0" url: "https://pub.dev" source: hosted - version: "0.6.11" + version: "0.6.12" tflite_flutter: dependency: "direct main" description: diff --git a/walkguide-mobile/walkguide_app/pubspec.yaml b/walkguide-mobile/walkguide_app/pubspec.yaml index deeaed7..ed373cc 100644 --- a/walkguide-mobile/walkguide_app/pubspec.yaml +++ b/walkguide-mobile/walkguide_app/pubspec.yaml @@ -91,6 +91,9 @@ dev_dependencies: mockito: ^5.4.4 bloc_test: ^9.1.7 +dependency_overrides: + record_linux: ^1.3.0 + flutter: uses-material-design: true assets: diff --git a/walkguide-mobile/walkguide_app/test/integration_test/app_flow_test.dart b/walkguide-mobile/walkguide_app/test/integration_test/app_flow_test.dart index 58c9943..b75d9ee 100644 --- a/walkguide-mobile/walkguide_app/test/integration_test/app_flow_test.dart +++ b/walkguide-mobile/walkguide_app/test/integration_test/app_flow_test.dart @@ -127,11 +127,10 @@ class _AppState extends ChangeNotifier { notifyListeners(); } - Future sendSos() async { - await Future.delayed(const Duration(milliseconds: 200)); - _sosSent = true; - notifyListeners(); - } + void sendSos() { + _sosSent = true; + notifyListeners(); + } void markAllRead() { _notifications = _notifications diff --git a/walkguide-mobile/walkguide_app/test/integration_test/flow_2_walkguide_start_stop_sos_test.dart b/walkguide-mobile/walkguide_app/test/integration_test/flow_2_walkguide_start_stop_sos_test.dart index 168654c..b52eb28 100644 --- a/walkguide-mobile/walkguide_app/test/integration_test/flow_2_walkguide_start_stop_sos_test.dart +++ b/walkguide-mobile/walkguide_app/test/integration_test/flow_2_walkguide_start_stop_sos_test.dart @@ -60,14 +60,11 @@ class _AppState extends ChangeNotifier { notifyListeners(); } - Future startWalkGuide() async { - _walkGuideStatus = _WalkGuideStatus.active; - notifyListeners(); - // Simulasi obstacle terdeteksi setelah 300ms - await Future.delayed(const Duration(milliseconds: 300)); - _detectedObstacles = ['person (87%)', 'motorcycle (72%)']; - notifyListeners(); - } + Future startWalkGuide() async { + _walkGuideStatus = _WalkGuideStatus.active; + _detectedObstacles = ['person (87%)', 'motorcycle (72%)']; + notifyListeners(); + } void stopWalkGuide() { _walkGuideStatus = _WalkGuideStatus.idle; @@ -80,11 +77,10 @@ class _AppState extends ChangeNotifier { notifyListeners(); } - Future sendSos() async { - await Future.delayed(const Duration(milliseconds: 150)); - _sosStatus = _SosStatus.triggered; - notifyListeners(); - } + Future sendSos() async { + _sosStatus = _SosStatus.triggered; + notifyListeners(); + } void goBack() { if (_currentScreen == 'walkguide' || _currentScreen == 'sos') { diff --git a/walkguide-mobile/walkguide_app/test/integration_test/flow_3_notification_read_all_test.dart b/walkguide-mobile/walkguide_app/test/integration_test/flow_3_notification_read_all_test.dart index f17730e..5b99d19 100644 --- a/walkguide-mobile/walkguide_app/test/integration_test/flow_3_notification_read_all_test.dart +++ b/walkguide-mobile/walkguide_app/test/integration_test/flow_3_notification_read_all_test.dart @@ -101,11 +101,10 @@ class _AppState extends ChangeNotifier { } } - Future markAllAsRead() async { - await Future.delayed(const Duration(milliseconds: 150)); - for (final n in _notifications) { - n.isRead = true; - } + Future markAllAsRead() async { + for (final n in _notifications) { + n.isRead = true; + } notifyListeners(); } } @@ -221,39 +220,42 @@ class _DashboardScreen extends StatelessWidget { Widget build(BuildContext context) { final unread = state.unreadCount; return Scaffold( - appBar: AppBar( - title: const Text('Dashboard'), - actions: [ - Stack( - alignment: Alignment.topRight, - children: [ - IconButton( - key: const Key('notifIconButton'), - icon: const Icon(Icons.notifications), - tooltip: 'Notifikasi', - onPressed: state.openNotifications, - ), - if (unread > 0) - Positioned( - right: 8, - top: 8, - child: Container( - key: const Key('dashboardBadge'), - padding: const EdgeInsets.all(4), - decoration: const BoxDecoration( - color: Colors.red, - shape: BoxShape.circle, - ), - child: Text( - '$unread', - style: const TextStyle(color: Colors.white, fontSize: 10), - ), - ), - ), - ], - ), - ], - ), + appBar: AppBar( + title: const Text('Dashboard'), + actions: [ + IconButton( + key: const Key('notifIconButton'), + icon: Stack( + clipBehavior: Clip.none, + children: [ + const Icon(Icons.notifications), + if (unread > 0) + Positioned( + right: -4, + top: -4, + child: IgnorePointer( + child: Container( + key: const Key('dashboardBadge'), + padding: const EdgeInsets.all(4), + decoration: const BoxDecoration( + color: Colors.red, + shape: BoxShape.circle, + ), + child: Text( + '$unread', + style: const TextStyle( + color: Colors.white, fontSize: 10), + ), + ), + ), + ), + ], + ), + tooltip: 'Notifikasi', + onPressed: state.openNotifications, + ), + ], + ), body: Center( child: Column(mainAxisSize: MainAxisSize.min, children: [ const Text('Selamat datang di Dashboard'), diff --git a/walkguide-mobile/walkguide_app/test/unit/login_use_case_test.dart b/walkguide-mobile/walkguide_app/test/unit/login_use_case_test.dart index 515a741..849761c 100644 --- a/walkguide-mobile/walkguide_app/test/unit/login_use_case_test.dart +++ b/walkguide-mobile/walkguide_app/test/unit/login_use_case_test.dart @@ -47,7 +47,19 @@ abstract class AuthRepository { // File mock di-generate via: flutter pub run build_runner build // Untuk demo tanpa build_runner, kita buat manual mock di bawah -class MockAuthRepository extends Mock implements AuthRepository {} +class MockAuthRepository extends Mock implements AuthRepository { + @override + Future> login(String? email, String? password) => + super.noSuchMethod( + Invocation.method(#login, [email, password]), + returnValue: Future>.value( + const Left(AuthFailure('Repository belum di-stub')), + ), + returnValueForMissingStub: Future>.value( + const Left(AuthFailure('Repository belum di-stub')), + ), + ) as Future>; +} // ---------- Use case ---------- diff --git a/walkguide-mobile/walkguide_app/test/unit/obstacle_analyzer_test.dart b/walkguide-mobile/walkguide_app/test/unit/obstacle_analyzer_test.dart index fd980ea..a142e9d 100644 --- a/walkguide-mobile/walkguide_app/test/unit/obstacle_analyzer_test.dart +++ b/walkguide-mobile/walkguide_app/test/unit/obstacle_analyzer_test.dart @@ -98,19 +98,20 @@ class ObstacleAnalyzer { }; /// Pilih obstacle paling prioritas (Very Close > Close > Medium > Far). - DetectionResult? prioritize(List detections) { - if (detections.isEmpty) return null; - const order = [ - 'Very Close (< 1m)', - 'Close (1-2m)', - 'Medium (2-4m)', - 'Far (> 4m)', - ]; - detections.sort((a, b) => order - .indexOf(a.estimatedDistance) - .compareTo(order.indexOf(b.estimatedDistance))); - return detections.first; - } + DetectionResult? prioritize(List detections) { + if (detections.isEmpty) return null; + const order = [ + 'Very Close (< 1m)', + 'Close (1-2m)', + 'Medium (2-4m)', + 'Far (> 4m)', + ]; + final sorted = List.of(detections); + sorted.sort((a, b) => order + .indexOf(a.estimatedDistance) + .compareTo(order.indexOf(b.estimatedDistance))); + return sorted.first; + } /// Filter deteksi berdasarkan confidence threshold. List filterByConfidence( diff --git a/walkguide-mobile/walkguide_app/test/unit/register_use_case_test.dart b/walkguide-mobile/walkguide_app/test/unit/register_use_case_test.dart index 2c17f25..94988d9 100644 --- a/walkguide-mobile/walkguide_app/test/unit/register_use_case_test.dart +++ b/walkguide-mobile/walkguide_app/test/unit/register_use_case_test.dart @@ -48,7 +48,33 @@ abstract class RegisterRepository { }); } -class MockRegisterRepository extends Mock implements RegisterRepository {} +class MockRegisterRepository extends Mock implements RegisterRepository { + @override + Future> register({ + String? email, + String? password, + String? displayName, + String? role, + }) => + super.noSuchMethod( + Invocation.method( + #register, + const [], + { + #email: email, + #password: password, + #displayName: displayName, + #role: role, + }, + ), + returnValue: Future>.value( + const Left(ServerFailure('Repository belum di-stub')), + ), + returnValueForMissingStub: Future>.value( + const Left(ServerFailure('Repository belum di-stub')), + ), + ) as Future>; +} // ---------- Use case ---------- diff --git a/walkguide-mobile/walkguide_app/test/widget/login_screen_test.dart b/walkguide-mobile/walkguide_app/test/widget/login_screen_test.dart index e3e7770..ea589c9 100644 --- a/walkguide-mobile/walkguide_app/test/widget/login_screen_test.dart +++ b/walkguide-mobile/walkguide_app/test/widget/login_screen_test.dart @@ -261,13 +261,13 @@ void main() { testWidgets('password tersembunyi (obscureText=true) secara default', (tester) async { await tester.pumpWidget(makeTestable(const _StubLoginScreen())); - final editableText = tester.widget( - find.descendant( - of: find.byKey(const Key('password_field')), - matching: find.byType(EditableText), - ), - ); - expect(editableText.obscureText, isTrue); + final editableText = tester.widget( + find.descendant( + of: find.byKey(const Key('password_field')), + matching: find.byType(EditableText), + ), + ); + expect(editableText.obscureText, isTrue); }); testWidgets('tap toggle mengubah obscureText menjadi false', (tester) async { @@ -282,7 +282,7 @@ void main() { matching: find.byType(EditableText), ), ); - expect(editableText.obscureText, isTrue); + expect(editableText.obscureText, isFalse); }); testWidgets('tap toggle dua kali mengembalikan obscureText ke true', (tester) async { @@ -313,9 +313,10 @@ void main() { await tester.enterText(find.byKey(const Key('password_field')), 'password123'); await tester.tap(find.byKey(const Key('login_button'))); await tester.pump(); // Trigger rebuild - - expect(find.byKey(const Key('loading_indicator')), findsOneWidget); - }); + + expect(find.byKey(const Key('loading_indicator')), findsOneWidget); + await tester.pumpAndSettle(); + }); testWidgets('menyembunyikan tombol login saat loading', (tester) async { await tester.pumpWidget(makeTestable(const _StubLoginScreen())); @@ -324,9 +325,10 @@ void main() { await tester.enterText(find.byKey(const Key('password_field')), 'password123'); await tester.tap(find.byKey(const Key('login_button'))); await tester.pump(); - - expect(find.byKey(const Key('login_button')), findsNothing); - }); + + expect(find.byKey(const Key('login_button')), findsNothing); + await tester.pumpAndSettle(); + }); testWidgets('loading selesai setelah async operation', (tester) async { await tester.pumpWidget(makeTestable(const _StubLoginScreen())); diff --git a/walkguide-mobile/walkguide_app/test/widget/manual_screen_test.dart b/walkguide-mobile/walkguide_app/test/widget/manual_screen_test.dart index ceac807..51dd5d0 100644 --- a/walkguide-mobile/walkguide_app/test/widget/manual_screen_test.dart +++ b/walkguide-mobile/walkguide_app/test/widget/manual_screen_test.dart @@ -363,11 +363,16 @@ class _StubManualScreenState extends State<_StubManualScreen> { } } -Widget makeTestable(Widget child) => MaterialApp(home: child); - -// --------------------------------------------------------------------------- -// Tests -// --------------------------------------------------------------------------- +Widget makeTestable(Widget child) => MaterialApp(home: child); + +Finder _commandScrollable() => find.descendant( + of: find.byKey(const Key('command_list')), + matching: find.byType(Scrollable), + ); + +// --------------------------------------------------------------------------- +// Tests +// --------------------------------------------------------------------------- void main() { group('ManualScreen Widget Tests', () { @@ -418,12 +423,13 @@ void main() { group('Konten perintah suara', () { testWidgets('menampilkan tile untuk perintah Open Walkguide', (tester) async { - await tester.pumpWidget(makeTestable(const _StubManualScreen())); - await tester.scrollUntilVisible( - find.byKey(const Key('cmd_tile_openWalkguide')), - 200, - ); - expect(find.byKey(const Key('cmd_tile_openWalkguide')), findsOneWidget); + await tester.pumpWidget(makeTestable(const _StubManualScreen())); + await tester.scrollUntilVisible( + find.byKey(const Key('cmd_tile_openWalkguide')), + 200, + scrollable: _commandScrollable(), + ); + expect(find.byKey(const Key('cmd_tile_openWalkguide')), findsOneWidget); }); testWidgets('menampilkan phrase perintah dalam tanda kutip', @@ -449,25 +455,36 @@ void main() { expect(find.text('"Call Guardian"'), findsOneWidget); }); - testWidgets('menampilkan perintah Send SOS', (tester) async { - await tester.pumpWidget(makeTestable(const _StubManualScreen())); - await tester.scrollUntilVisible(find.text('"Send SOS"'), 200); - expect(find.text('"Send SOS"'), findsOneWidget); - }); - - testWidgets('menampilkan perintah Where Am I', (tester) async { - await tester.pumpWidget(makeTestable(const _StubManualScreen())); - await tester.scrollUntilVisible(find.text('"Where Am I"'), 200); - expect(find.text('"Where Am I"'), findsOneWidget); - }); + testWidgets('menampilkan perintah Send SOS', (tester) async { + await tester.pumpWidget(makeTestable(const _StubManualScreen())); + await tester.scrollUntilVisible( + find.text('"Send SOS"'), + 200, + scrollable: _commandScrollable(), + ); + expect(find.text('"Send SOS"'), findsOneWidget); + }); + + testWidgets('menampilkan perintah Where Am I', (tester) async { + await tester.pumpWidget(makeTestable(const _StubManualScreen())); + await tester.scrollUntilVisible( + find.text('"Where Am I"'), + 200, + scrollable: _commandScrollable(), + ); + expect(find.text('"Where Am I"'), findsOneWidget); + }); testWidgets('menampilkan kategori Darurat untuk Send SOS', (tester) async { - await tester.pumpWidget(makeTestable(const _StubManualScreen())); - await tester.scrollUntilVisible( - find.byKey(const Key('cmd_category_sendSos')), 200); - expect(find.byKey(const Key('cmd_category_sendSos')), findsOneWidget); - }); + await tester.pumpWidget(makeTestable(const _StubManualScreen())); + await tester.scrollUntilVisible( + find.byKey(const Key('cmd_category_sendSos')), + 200, + scrollable: _commandScrollable(), + ); + expect(find.byKey(const Key('cmd_category_sendSos')), findsOneWidget); + }); }); // ── Dialog info ─────────────────────────────────────────────────────── diff --git a/walkguide-mobile/walkguide_app/test/widget/notification_screen_test.dart b/walkguide-mobile/walkguide_app/test/widget/notification_screen_test.dart index 0d20977..80dd7c6 100644 --- a/walkguide-mobile/walkguide_app/test/widget/notification_screen_test.dart +++ b/walkguide-mobile/walkguide_app/test/widget/notification_screen_test.dart @@ -103,15 +103,16 @@ class _StubNotificationScreenState extends State<_StubNotificationScreen> { ], ], ), - actions: [ - if (_items.any((e) => !e.isRead)) - TextButton( - key: const Key('mark_all_read_button'), - onPressed: _markingAll ? null : _markAllRead, - child: const Text('Tandai Semua Dibaca'), - ), - ], - ), + actions: [ + if (_items.any((e) => !e.isRead)) + IconButton( + key: const Key('mark_all_read_button'), + onPressed: _markingAll ? null : _markAllRead, + tooltip: 'Tandai Semua Dibaca', + icon: const Icon(Icons.done_all), + ), + ], + ), body: widget.isLoading ? const Center( child: CircularProgressIndicator(key: Key('loading_indicator'))) diff --git a/walkguide-mobile/walkguide_app/test/widget/sos_screen_test.dart b/walkguide-mobile/walkguide_app/test/widget/sos_screen_test.dart index 461fc17..656a7a4 100644 --- a/walkguide-mobile/walkguide_app/test/widget/sos_screen_test.dart +++ b/walkguide-mobile/walkguide_app/test/widget/sos_screen_test.dart @@ -431,9 +431,10 @@ void main() { await tester.tap(find.byKey(const Key('sos_button'))); await tester.pump(); - - expect(find.byKey(const Key('sending_indicator')), findsOneWidget); - }); + + expect(find.byKey(const Key('sending_indicator')), findsOneWidget); + await tester.pumpAndSettle(); + }); testWidgets('setelah SOS terkirim, tampil success banner', (tester) async { From 8a2889633f5c7f6b6e0def5c65a4b9ff3cb5ebeb Mon Sep 17 00:00:00 2001 From: 5803024019 Date: Tue, 19 May 2026 12:41:19 +0700 Subject: [PATCH 3/6] chore: update .gitignore to include JVM crash dumps and Android SDK path --- walkguide-mobile/walkguide_app/.gitignore | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/walkguide-mobile/walkguide_app/.gitignore b/walkguide-mobile/walkguide_app/.gitignore index 3820a95..0e03039 100644 --- a/walkguide-mobile/walkguide_app/.gitignore +++ b/walkguide-mobile/walkguide_app/.gitignore @@ -43,3 +43,9 @@ app.*.map.json /android/app/debug /android/app/profile /android/app/release + +# JVM crash dumps +hs_err_pid*.log + +# Android SDK path (generated by Android Studio) +android/local.properties From 282a918b568341c556d66f49ec00d5ebd3f86d42 Mon Sep 17 00:00:00 2001 From: 5803024019 Date: Tue, 19 May 2026 15:57:06 +0700 Subject: [PATCH 4/6] fix: resolve raw JSON rendering and layout overflow in Guardian screens --- .../src/main/resources/application.properties | 4 +- .../guardian_activity_log_screen.dart | 540 +++++++++++++++ .../guardian_ai_config_screen.dart | 637 ++++++++++++++++++ .../guardian_dashboard/guardian_screens.dart | 14 +- .../walkguide_app/lib/features/screens.dart | 59 +- 5 files changed, 1207 insertions(+), 47 deletions(-) create mode 100644 walkguide-mobile/walkguide_app/lib/features/guardian_dashboard/guardian_activity_log_screen.dart create mode 100644 walkguide-mobile/walkguide_app/lib/features/guardian_dashboard/guardian_ai_config_screen.dart diff --git a/walkguide-backend/demo/src/main/resources/application.properties b/walkguide-backend/demo/src/main/resources/application.properties index 99ee4f7..a7b2647 100644 --- a/walkguide-backend/demo/src/main/resources/application.properties +++ b/walkguide-backend/demo/src/main/resources/application.properties @@ -36,4 +36,6 @@ agora.app-certificate=${AGORA_APP_CERTIFICATE:} # ===== LOGGING ===== logging.level.com.walkguide=DEBUG logging.level.org.springframework.messaging=INFO -logging.level.org.springframework.web.socket=INFO \ No newline at end of file +logging.level.org.springframework.web.socket=INFO + +spring.profiles.active=dev \ No newline at end of file diff --git a/walkguide-mobile/walkguide_app/lib/features/guardian_dashboard/guardian_activity_log_screen.dart b/walkguide-mobile/walkguide_app/lib/features/guardian_dashboard/guardian_activity_log_screen.dart new file mode 100644 index 0000000..66a9363 --- /dev/null +++ b/walkguide-mobile/walkguide_app/lib/features/guardian_dashboard/guardian_activity_log_screen.dart @@ -0,0 +1,540 @@ +// lib/features/guardian_dashboard/guardian_activity_log_screen.dart +// ignore_for_file: use_build_context_synchronously + +import 'package:dio/dio.dart'; +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:intl/intl.dart'; + +import '../../../app/injection_container.dart'; +import '../../../core/network/api_client.dart'; + +Dio get _api => sl().dio; + +class GuardianActivityLogScreen extends StatefulWidget { + const GuardianActivityLogScreen({super.key}); + + @override + State createState() => + _GuardianActivityLogScreenState(); +} + +class _GuardianActivityLogScreenState extends State { + List<_LogItem> _items = []; + List<_LogItem> _filtered = []; + bool _loading = true; + String? _error; + String _selectedFilter = 'ALL'; + bool _needsPairing = false; + + static const _filters = [ + 'ALL', + 'WALKGUIDE', + 'SOS', + 'AUTH', + 'OBSTACLE', + 'LOCATION', + ]; + + @override + void initState() { + super.initState(); + _load(); + } + + Future _load() async { + setState(() { + _loading = true; + _error = null; + _needsPairing = false; + }); + try { + // Cek pairing dulu + final paired = await _hasActivePairing(); + if (!paired) { + setState(() { + _needsPairing = true; + _loading = false; + }); + return; + } + + final res = await _api.get('/guardian/activity-logs', queryParameters: { + 'size': 50, + 'page': 0 + }).timeout(const Duration(seconds: 10)); + + // Response bisa berupa list langsung atau paged {content: [...]} + final data = res.data['data']; + List list; + if (data is List) { + list = data; + } else if (data is Map && data['content'] is List) { + list = data['content'] as List; + } else { + list = []; + } + + final items = list + .whereType() + .map((e) => _LogItem.fromJson(Map.from(e))) + .toList(); + + setState(() { + _items = items; + _applyFilter(_selectedFilter); + _loading = false; + }); + } on DioException catch (e) { + setState(() { + _error = e.response?.data?['message']?.toString() ?? + 'Gagal memuat activity log.'; + _loading = false; + }); + } catch (e) { + setState(() { + _error = 'Timeout / error: $e'; + _loading = false; + }); + } + } + + Future _hasActivePairing() async { + try { + final res = await _api + .get('/shared/pairing/status') + .timeout(const Duration(seconds: 5)); + final data = res.data['data']; + if (data is Map) return data['status'] == 'ACTIVE'; + } catch (_) {} + return false; + } + + void _applyFilter(String filter) { + _selectedFilter = filter; + if (filter == 'ALL') { + _filtered = List.from(_items); + } else { + _filtered = _items.where((item) { + switch (filter) { + case 'WALKGUIDE': + return item.logType.contains('WALKGUIDE'); + case 'SOS': + return item.logType.contains('SOS'); + case 'AUTH': + return item.logType == 'LOGIN' || + item.logType == 'LOGOUT' || + item.logType == 'APP_OPEN' || + item.logType == 'APP_CLOSE'; + case 'OBSTACLE': + return item.logType.contains('OBSTACLE'); + case 'LOCATION': + return item.logType.contains('LOCATION') || + item.logType.contains('GEOFENCE'); + default: + return true; + } + }).toList(); + } + } + + @override + Widget build(BuildContext context) { + return SafeArea( + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // ── Header ────────────────────────────────────────────────────── + Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'User Logs', + style: GoogleFonts.outfit( + fontSize: 22, + fontWeight: FontWeight.w800, + color: const Color(0xFF0F172A), + ), + ), + Text( + _needsPairing + ? 'Pairing dulu untuk melihat log' + : '${_items.length} aktivitas tercatat', + style: GoogleFonts.inter( + fontSize: 13, + color: const Color(0xFF64748B), + ), + ), + ], + ), + ), + IconButton( + onPressed: _load, + icon: const Icon(Icons.refresh_rounded), + tooltip: 'Refresh', + color: const Color(0xFF64748B), + ), + ], + ), + const SizedBox(height: 12), + + // ── Filter chips ───────────────────────────────────────────────── + if (!_needsPairing && !_loading && _error == null) + SizedBox( + height: 36, + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: _filters.length, + separatorBuilder: (_, __) => const SizedBox(width: 8), + itemBuilder: (_, i) { + final f = _filters[i]; + final selected = _selectedFilter == f; + return FilterChip( + label: Text(f), + selected: selected, + onSelected: (_) => setState(() => _applyFilter(f)), + selectedColor: + const Color(0xFF1A56DB).withValues(alpha: 0.12), + checkmarkColor: const Color(0xFF1A56DB), + labelStyle: GoogleFonts.inter( + color: selected + ? const Color(0xFF1A56DB) + : const Color(0xFF64748B), + fontWeight: + selected ? FontWeight.w700 : FontWeight.normal, + fontSize: 12, + ), + padding: const EdgeInsets.symmetric(horizontal: 4), + side: BorderSide( + color: selected + ? const Color(0xFF1A56DB) + : const Color(0xFFE2E8F0), + ), + ); + }, + ), + ), + + if (!_needsPairing && !_loading && _error == null) + const SizedBox(height: 16), + + // ── Body ───────────────────────────────────────────────────────── + Expanded( + child: _loading + ? const Center(child: CircularProgressIndicator()) + : _needsPairing + ? _buildNoPairingPanel() + : _error != null + ? _buildErrorPanel() + : _filtered.isEmpty + ? _buildEmptyPanel() + : RefreshIndicator( + onRefresh: _load, + color: const Color(0xFF1A56DB), + child: ListView.builder( + itemCount: _filtered.length, + itemBuilder: (ctx, i) => + _LogCard(item: _filtered[i]), + ), + ), + ), + ], + ), + ), + ); + } + + Widget _buildNoPairingPanel() { + return Center( + child: Container( + padding: const EdgeInsets.all(24), + margin: const EdgeInsets.symmetric(horizontal: 16), + decoration: BoxDecoration( + color: const Color(0xFFFFFBEB), + borderRadius: BorderRadius.circular(16), + border: Border.all(color: const Color(0xFFFDE68A)), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.link_off, color: Color(0xFFD97706), size: 52), + const SizedBox(height: 14), + Text( + 'Belum Pairing', + style: GoogleFonts.outfit( + fontSize: 18, + fontWeight: FontWeight.w700, + color: const Color(0xFF92400E), + ), + ), + const SizedBox(height: 8), + Text( + 'Hubungkan akun Guardian dengan User terlebih dahulu untuk melihat log aktivitas.', + style: GoogleFonts.inter( + fontSize: 13, + color: const Color(0xFF92400E), + ), + textAlign: TextAlign.center, + ), + ], + ), + ), + ); + } + + Widget _buildErrorPanel() { + return Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.wifi_off, size: 52, color: Color(0xFF94A3B8)), + const SizedBox(height: 14), + Text( + _error!, + textAlign: TextAlign.center, + style: + GoogleFonts.inter(fontSize: 13, color: const Color(0xFF64748B)), + ), + const SizedBox(height: 18), + FilledButton.icon( + onPressed: _load, + icon: const Icon(Icons.refresh), + label: const Text('Coba lagi'), + style: FilledButton.styleFrom( + backgroundColor: const Color(0xFF1A56DB)), + ), + ], + ), + ); + } + + Widget _buildEmptyPanel() { + return Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.history, size: 64, color: Color(0xFF94A3B8)), + const SizedBox(height: 14), + Text( + _selectedFilter == 'ALL' + ? 'Belum ada aktivitas' + : 'Tidak ada aktivitas "$_selectedFilter"', + style: GoogleFonts.inter( + fontSize: 15, + fontWeight: FontWeight.w600, + color: const Color(0xFF94A3B8), + ), + ), + ], + ), + ); + } +} + +// ───────────────────────────────────────────────────────────────────────────── +// DATA MODEL +// ───────────────────────────────────────────────────────────────────────────── + +class _LogItem { + final int id; + final String logType; + final String? description; + final DateTime createdAt; + + const _LogItem({ + required this.id, + required this.logType, + this.description, + required this.createdAt, + }); + + factory _LogItem.fromJson(Map j) => _LogItem( + id: (j['id'] as num?)?.toInt() ?? 0, + logType: j['logType']?.toString() ?? 'UNKNOWN', + description: j['description']?.toString(), + createdAt: + DateTime.tryParse(j['createdAt']?.toString() ?? '')?.toLocal() ?? + DateTime.now(), + ); +} + +// ───────────────────────────────────────────────────────────────────────────── +// LOG CARD +// ───────────────────────────────────────────────────────────────────────────── + +class _LogCard extends StatelessWidget { + final _LogItem item; + const _LogCard({required this.item}); + + @override + Widget build(BuildContext context) { + final meta = _logMeta(item.logType); + return Padding( + padding: const EdgeInsets.only(bottom: 8), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Timeline dot + connector line + Column( + children: [ + Container( + width: 38, + height: 38, + decoration: BoxDecoration( + color: meta.color.withValues(alpha: 0.12), + shape: BoxShape.circle, + ), + child: Icon(meta.icon, color: meta.color, size: 18), + ), + Container( + width: 1.5, + height: 22, + color: const Color(0xFFE2E8F0), + ), + ], + ), + const SizedBox(width: 12), + // Content + Expanded( + child: Padding( + padding: const EdgeInsets.only(top: 6), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: Text( + meta.label, + style: GoogleFonts.inter( + fontWeight: FontWeight.w700, + color: meta.color, + fontSize: 13, + ), + ), + ), + Text( + _formatTime(item.createdAt), + style: GoogleFonts.jetBrainsMono( + color: const Color(0xFF94A3B8), + fontSize: 11, + ), + ), + ], + ), + if (item.description != null && item.description!.isNotEmpty) + Padding( + padding: const EdgeInsets.only(top: 3), + child: Text( + item.description!, + style: GoogleFonts.inter( + fontSize: 12, + color: const Color(0xFF64748B), + ), + ), + ), + const SizedBox(height: 14), + ], + ), + ), + ), + ], + ), + ); + } + + String _formatTime(DateTime dt) { + final now = DateTime.now(); + if (dt.day == now.day && dt.month == now.month && dt.year == now.year) { + return DateFormat('HH:mm').format(dt); + } + return DateFormat('dd MMM HH:mm').format(dt); + } +} + +// ───────────────────────────────────────────────────────────────────────────── +// LOG METADATA +// ───────────────────────────────────────────────────────────────────────────── + +class _LogMeta { + final IconData icon; + final Color color; + final String label; + const _LogMeta( + {required this.icon, required this.color, required this.label}); +} + +_LogMeta _logMeta(String logType) { + switch (logType.toUpperCase()) { + case 'LOGIN': + return const _LogMeta( + icon: Icons.login, color: Color(0xFF16A34A), label: 'Login'); + case 'LOGOUT': + return const _LogMeta( + icon: Icons.logout, color: Color(0xFF94A3B8), label: 'Logout'); + case 'APP_OPEN': + return const _LogMeta( + icon: Icons.open_in_new, + color: Color(0xFF1A56DB), + label: 'App Dibuka'); + case 'APP_CLOSE': + return const _LogMeta( + icon: Icons.close, color: Color(0xFF94A3B8), label: 'App Ditutup'); + case 'WALKGUIDE_START': + return const _LogMeta( + icon: Icons.directions_walk, + color: Color(0xFF1A56DB), + label: 'WalkGuide Mulai'); + case 'WALKGUIDE_STOP': + return const _LogMeta( + icon: Icons.stop_circle, + color: Color(0xFF94A3B8), + label: 'WalkGuide Berhenti'); + case 'OBSTACLE_DETECTED': + return const _LogMeta( + icon: Icons.warning_amber, + color: Color(0xFFD97706), + label: 'Obstacle Terdeteksi'); + case 'SOS_TRIGGERED': + return const _LogMeta( + icon: Icons.sos, color: Color(0xFFDC2626), label: 'SOS Terkirim'); + case 'SOS_ACKNOWLEDGED': + return const _LogMeta( + icon: Icons.check_circle, + color: Color(0xFF16A34A), + label: 'SOS Diakui Guardian'); + case 'CALL_INITIATED': + return const _LogMeta( + icon: Icons.call, + color: Color(0xFF16A34A), + label: 'Panggilan Dimulai'); + case 'CALL_ENDED': + return const _LogMeta( + icon: Icons.call_end, + color: Color(0xFF94A3B8), + label: 'Panggilan Selesai'); + case 'LOCATION_UPDATE': + return const _LogMeta( + icon: Icons.location_on, + color: Color(0xFF1A56DB), + label: 'Lokasi Diperbarui'); + case 'GEOFENCE_EXIT': + return const _LogMeta( + icon: Icons.fence, + color: Color(0xFFDC2626), + label: 'Keluar Area Aman'); + case 'GEOFENCE_ENTER': + return const _LogMeta( + icon: Icons.home, color: Color(0xFF16A34A), label: 'Masuk Area Aman'); + default: + return _LogMeta( + icon: Icons.circle_outlined, + color: const Color(0xFF94A3B8), + label: logType); + } +} diff --git a/walkguide-mobile/walkguide_app/lib/features/guardian_dashboard/guardian_ai_config_screen.dart b/walkguide-mobile/walkguide_app/lib/features/guardian_dashboard/guardian_ai_config_screen.dart new file mode 100644 index 0000000..a99eb24 --- /dev/null +++ b/walkguide-mobile/walkguide_app/lib/features/guardian_dashboard/guardian_ai_config_screen.dart @@ -0,0 +1,637 @@ +// lib/features/guardian_dashboard/guardian_ai_config_screen.dart +// ignore_for_file: use_build_context_synchronously + +import 'package:dio/dio.dart'; +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:google_fonts/google_fonts.dart'; + +import '../../../app/injection_container.dart'; +import '../../../core/network/api_client.dart'; + +Dio get _api => sl().dio; + +class GuardianAiConfigScreen extends StatefulWidget { + const GuardianAiConfigScreen({super.key}); + + @override + State createState() => _GuardianAiConfigScreenState(); +} + +class _GuardianAiConfigScreenState extends State { + bool _loading = true; + bool _saving = false; + String? _error; + bool _needsPairing = false; + + // Config values + double _confidenceThreshold = 0.5; + double _alertDistanceClose = 1.5; + double _alertDistanceMedium = 3.0; + int _maxInferenceFps = 5; + String _enabledLabels = 'ALL'; + + static const _labelOptions = ['ALL', 'PERSON', 'VEHICLE', 'OBSTACLE']; + + @override + void initState() { + super.initState(); + _load(); + } + + Future _load() async { + setState(() { + _loading = true; + _error = null; + _needsPairing = false; + }); + try { + final paired = await _hasActivePairing(); + if (!paired) { + setState(() { + _needsPairing = true; + _loading = false; + }); + return; + } + + final res = await _api + .get('/guardian/ai-config') + .timeout(const Duration(seconds: 8)); + final data = res.data['data']; + if (data is Map) { + setState(() { + _confidenceThreshold = + (data['confidenceThreshold'] as num?)?.toDouble() ?? 0.5; + _alertDistanceClose = + (data['alertDistanceClose'] as num?)?.toDouble() ?? 1.5; + _alertDistanceMedium = + (data['alertDistanceMedium'] as num?)?.toDouble() ?? 3.0; + _maxInferenceFps = (data['maxInferenceFps'] as num?)?.toInt() ?? 5; + _enabledLabels = data['enabledLabels']?.toString() ?? 'ALL'; + }); + } + } on DioException catch (e) { + setState(() { + _error = e.response?.data?['message']?.toString() ?? + 'Gagal memuat konfigurasi AI.'; + }); + } catch (e) { + setState(() => _error = 'Timeout / error: $e'); + } finally { + if (mounted) setState(() => _loading = false); + } + } + + Future _save() async { + setState(() => _saving = true); + try { + await _api.put('/guardian/ai-config', data: { + 'confidenceThreshold': _confidenceThreshold, + 'alertDistanceClose': _alertDistanceClose, + 'alertDistanceMedium': _alertDistanceMedium, + 'maxInferenceFps': _maxInferenceFps, + 'enabledLabels': _enabledLabels, + }).timeout(const Duration(seconds: 8)); + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Konfigurasi AI berhasil disimpan'), + backgroundColor: Color(0xFF16A34A), + ), + ); + } + } on DioException catch (e) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(e.response?.data?['message']?.toString() ?? + 'Gagal menyimpan konfigurasi.'), + backgroundColor: const Color(0xFFDC2626), + ), + ); + } + } catch (e) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Error: $e'), + backgroundColor: const Color(0xFFDC2626), + ), + ); + } + } finally { + if (mounted) setState(() => _saving = false); + } + } + + Future _hasActivePairing() async { + try { + final res = await _api + .get('/shared/pairing/status') + .timeout(const Duration(seconds: 5)); + final data = res.data['data']; + if (data is Map) return data['status'] == 'ACTIVE'; + } catch (_) {} + return false; + } + + @override + Widget build(BuildContext context) { + return SafeArea( + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // ── Header ────────────────────────────────────────────────────── + Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'AI Config', + style: GoogleFonts.outfit( + fontSize: 22, + fontWeight: FontWeight.w800, + color: const Color(0xFF0F172A), + ), + ), + Text( + 'Konfigurasi deteksi YOLO untuk User', + style: GoogleFonts.inter( + fontSize: 13, + color: const Color(0xFF64748B), + ), + ), + ], + ), + ), + IconButton( + onPressed: () => context.go('/guardian/benchmark'), + icon: const Icon(Icons.speed_outlined), + tooltip: 'Benchmark', + color: const Color(0xFF64748B), + ), + IconButton( + onPressed: _loading ? null : _load, + icon: const Icon(Icons.refresh_rounded), + tooltip: 'Refresh', + color: const Color(0xFF64748B), + ), + ], + ), + const SizedBox(height: 16), + + // ── Body ───────────────────────────────────────────────────────── + Expanded( + child: _loading + ? const Center(child: CircularProgressIndicator()) + : _needsPairing + ? _buildNoPairingPanel() + : _error != null + ? _buildErrorPanel() + : _buildConfigForm(), + ), + ], + ), + ), + ); + } + + Widget _buildConfigForm() { + return SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // ── Confidence Threshold ────────────────────────────────────────── + _SectionCard( + title: 'Confidence Threshold', + subtitle: + 'Minimal keyakinan AI untuk menganggap objek sebagai obstacle', + icon: Icons.tune_outlined, + iconColor: const Color(0xFF1A56DB), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('Nilai saat ini:', + style: GoogleFonts.inter( + fontSize: 13, color: const Color(0xFF64748B))), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, vertical: 4), + decoration: BoxDecoration( + color: const Color(0xFF1A56DB).withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(20), + ), + child: Text( + _confidenceThreshold.toStringAsFixed(2), + style: GoogleFonts.jetBrainsMono( + fontSize: 14, + fontWeight: FontWeight.w700, + color: const Color(0xFF1A56DB), + ), + ), + ), + ], + ), + Slider( + value: _confidenceThreshold, + min: 0.1, + max: 0.9, + divisions: 8, + activeColor: const Color(0xFF1A56DB), + onChanged: (v) => setState(() => _confidenceThreshold = v), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('0.1 (sensitif)', + style: GoogleFonts.inter( + fontSize: 11, color: const Color(0xFF94A3B8))), + Text('0.9 (ketat)', + style: GoogleFonts.inter( + fontSize: 11, color: const Color(0xFF94A3B8))), + ], + ), + ], + ), + ), + const SizedBox(height: 12), + + // ── Alert Distances ─────────────────────────────────────────────── + _SectionCard( + title: 'Jarak Peringatan', + subtitle: 'Batas jarak (meter) untuk level peringatan', + icon: Icons.radar_outlined, + iconColor: const Color(0xFFD97706), + child: Column( + children: [ + // Close + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row(children: [ + Container( + width: 8, + height: 8, + decoration: const BoxDecoration( + shape: BoxShape.circle, + color: Color(0xFFDC2626), + ), + ), + const SizedBox(width: 6), + Text('Jarak Dekat', + style: GoogleFonts.inter( + fontSize: 13, color: const Color(0xFF0F172A))), + ]), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, vertical: 4), + decoration: BoxDecoration( + color: const Color(0xFFDC2626).withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(20), + ), + child: Text( + '${_alertDistanceClose.toStringAsFixed(1)} m', + style: GoogleFonts.jetBrainsMono( + fontSize: 13, + fontWeight: FontWeight.w700, + color: const Color(0xFFDC2626), + ), + ), + ), + ], + ), + Slider( + value: _alertDistanceClose, + min: 0.5, + max: 3.0, + divisions: 5, + activeColor: const Color(0xFFDC2626), + onChanged: (v) => setState(() => _alertDistanceClose = v), + ), + const SizedBox(height: 8), + // Medium + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row(children: [ + Container( + width: 8, + height: 8, + decoration: const BoxDecoration( + shape: BoxShape.circle, + color: Color(0xFFD97706), + ), + ), + const SizedBox(width: 6), + Text('Jarak Sedang', + style: GoogleFonts.inter( + fontSize: 13, color: const Color(0xFF0F172A))), + ]), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, vertical: 4), + decoration: BoxDecoration( + color: const Color(0xFFD97706).withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(20), + ), + child: Text( + '${_alertDistanceMedium.toStringAsFixed(1)} m', + style: GoogleFonts.jetBrainsMono( + fontSize: 13, + fontWeight: FontWeight.w700, + color: const Color(0xFFD97706), + ), + ), + ), + ], + ), + Slider( + value: _alertDistanceMedium, + min: 1.0, + max: 8.0, + divisions: 7, + activeColor: const Color(0xFFD97706), + onChanged: (v) => setState(() => _alertDistanceMedium = v), + ), + ], + ), + ), + const SizedBox(height: 12), + + // ── Max Inference FPS ───────────────────────────────────────────── + _SectionCard( + title: 'Max Inference FPS', + subtitle: + 'Maksimal frame per detik untuk inferensi AI (lebih tinggi = lebih boros baterai)', + icon: Icons.speed_outlined, + iconColor: const Color(0xFF059669), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('FPS saat ini:', + style: GoogleFonts.inter( + fontSize: 13, color: const Color(0xFF64748B))), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, vertical: 4), + decoration: BoxDecoration( + color: const Color(0xFF059669).withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(20), + ), + child: Text( + '$_maxInferenceFps fps', + style: GoogleFonts.jetBrainsMono( + fontSize: 14, + fontWeight: FontWeight.w700, + color: const Color(0xFF059669), + ), + ), + ), + ], + ), + Slider( + value: _maxInferenceFps.toDouble(), + min: 1, + max: 30, + divisions: 29, + activeColor: const Color(0xFF059669), + onChanged: (v) => + setState(() => _maxInferenceFps = v.toInt()), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('1 fps (hemat baterai)', + style: GoogleFonts.inter( + fontSize: 11, color: const Color(0xFF94A3B8))), + Text('30 fps (real-time)', + style: GoogleFonts.inter( + fontSize: 11, color: const Color(0xFF94A3B8))), + ], + ), + ], + ), + ), + const SizedBox(height: 12), + + // ── Enabled Labels ──────────────────────────────────────────────── + _SectionCard( + title: 'Label yang Diaktifkan', + subtitle: 'Jenis objek yang akan dideteksi AI', + icon: Icons.label_outline, + iconColor: const Color(0xFF7C3AED), + child: Wrap( + spacing: 8, + runSpacing: 8, + children: _labelOptions.map((label) { + final selected = _enabledLabels == label; + return GestureDetector( + onTap: () => setState(() => _enabledLabels = label), + child: Container( + padding: + const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + decoration: BoxDecoration( + color: selected ? const Color(0xFF7C3AED) : Colors.white, + borderRadius: BorderRadius.circular(20), + border: Border.all( + color: selected + ? const Color(0xFF7C3AED) + : const Color(0xFFE2E8F0), + ), + ), + child: Text( + label, + style: GoogleFonts.inter( + fontSize: 13, + fontWeight: FontWeight.w600, + color: + selected ? Colors.white : const Color(0xFF64748B), + ), + ), + ), + ); + }).toList(), + ), + ), + const SizedBox(height: 24), + + // ── Save button ─────────────────────────────────────────────────── + SizedBox( + width: double.infinity, + child: FilledButton.icon( + onPressed: _saving ? null : _save, + icon: _saving + ? const SizedBox( + width: 18, + height: 18, + child: CircularProgressIndicator( + strokeWidth: 2, color: Colors.white)) + : const Icon(Icons.save_outlined), + label: Text(_saving ? 'Menyimpan...' : 'Simpan Konfigurasi'), + style: FilledButton.styleFrom( + backgroundColor: const Color(0xFF1A56DB), + padding: const EdgeInsets.symmetric(vertical: 14), + textStyle: GoogleFonts.inter( + fontSize: 14, fontWeight: FontWeight.w600), + ), + ), + ), + const SizedBox(height: 8), + ], + ), + ); + } + + Widget _buildNoPairingPanel() { + return Center( + child: Container( + padding: const EdgeInsets.all(24), + margin: const EdgeInsets.symmetric(horizontal: 16), + decoration: BoxDecoration( + color: const Color(0xFFFFFBEB), + borderRadius: BorderRadius.circular(16), + border: Border.all(color: const Color(0xFFFDE68A)), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.link_off, color: Color(0xFFD97706), size: 52), + const SizedBox(height: 14), + Text( + 'Belum Pairing', + style: GoogleFonts.outfit( + fontSize: 18, + fontWeight: FontWeight.w700, + color: const Color(0xFF92400E), + ), + ), + const SizedBox(height: 8), + Text( + 'Hubungkan akun Guardian dengan User terlebih dahulu untuk mengatur konfigurasi AI.', + style: GoogleFonts.inter( + fontSize: 13, color: const Color(0xFF92400E)), + textAlign: TextAlign.center, + ), + ], + ), + ), + ); + } + + Widget _buildErrorPanel() { + return Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.wifi_off, size: 52, color: Color(0xFF94A3B8)), + const SizedBox(height: 14), + Text( + _error!, + textAlign: TextAlign.center, + style: + GoogleFonts.inter(fontSize: 13, color: const Color(0xFF64748B)), + ), + const SizedBox(height: 18), + FilledButton.icon( + onPressed: _load, + icon: const Icon(Icons.refresh), + label: const Text('Coba lagi'), + style: FilledButton.styleFrom( + backgroundColor: const Color(0xFF1A56DB)), + ), + ], + ), + ); + } +} + +// ───────────────────────────────────────────────────────────────────────────── +// SECTION CARD +// ───────────────────────────────────────────────────────────────────────────── + +class _SectionCard extends StatelessWidget { + final String title; + final String subtitle; + final IconData icon; + final Color iconColor; + final Widget child; + + const _SectionCard({ + required this.title, + required this.subtitle, + required this.icon, + required this.iconColor, + required this.child, + }); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16), + border: Border.all(color: const Color(0xFFE2E8F0)), + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.03), + blurRadius: 8, + offset: const Offset(0, 2), + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row(children: [ + Container( + width: 34, + height: 34, + decoration: BoxDecoration( + color: iconColor.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(8), + ), + child: Icon(icon, color: iconColor, size: 18), + ), + const SizedBox(width: 10), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: GoogleFonts.outfit( + fontSize: 14, + fontWeight: FontWeight.w700, + color: const Color(0xFF0F172A), + ), + ), + Text( + subtitle, + style: GoogleFonts.inter( + fontSize: 11, + color: const Color(0xFF94A3B8), + ), + ), + ], + ), + ), + ]), + const SizedBox(height: 16), + const Divider(height: 1, color: Color(0xFFF1F5F9)), + const SizedBox(height: 12), + child, + ], + ), + ); + } +} diff --git a/walkguide-mobile/walkguide_app/lib/features/guardian_dashboard/guardian_screens.dart b/walkguide-mobile/walkguide_app/lib/features/guardian_dashboard/guardian_screens.dart index cddbcb0..834cabc 100644 --- a/walkguide-mobile/walkguide_app/lib/features/guardian_dashboard/guardian_screens.dart +++ b/walkguide-mobile/walkguide_app/lib/features/guardian_dashboard/guardian_screens.dart @@ -1,10 +1,18 @@ +export '../home/presentation/guardian_dashboard_screen.dart' + show GuardianDashboardScreen; + +export 'guardian_activity_log_screen.dart' + show + GuardianActivityLogScreen; + +export 'guardian_ai_config_screen.dart' + show + GuardianAiConfigScreen; + export '../screens.dart' show - GuardianDashboardScreen, GuardianMapScreen, - GuardianActivityLogScreen, GuardianSendNotifScreen, - GuardianAiConfigScreen, GuardianVoiceCmdScreen, GuardianShortcutScreen, GuardianGeofenceScreen; diff --git a/walkguide-mobile/walkguide_app/lib/features/screens.dart b/walkguide-mobile/walkguide_app/lib/features/screens.dart index b709804..f086e75 100644 --- a/walkguide-mobile/walkguide_app/lib/features/screens.dart +++ b/walkguide-mobile/walkguide_app/lib/features/screens.dart @@ -28,6 +28,8 @@ import '../core/services/tts_service.dart'; import '../core/services/websocket_service.dart'; import '../core/storage/secure_storage.dart'; +export 'guardian_dashboard/guardian_screens.dart'; + Dio get _api => sl().dio; class ServerConnectScreen extends StatefulWidget { @@ -779,26 +781,12 @@ class IncomingCallScreen extends StatelessWidget { text: 'Accept or reject incoming guardian calls here.'); } -class GuardianDashboardScreen extends StatelessWidget { - const GuardianDashboardScreen({super.key}); - @override - Widget build(BuildContext context) => const _EndpointListScreen( - title: 'Guardian Dashboard', endpoint: '/guardian/dashboard'); -} - class GuardianMapScreen extends StatelessWidget { const GuardianMapScreen({super.key}); @override Widget build(BuildContext context) => const _GuardianMapHistoryScreen(); } -class GuardianActivityLogScreen extends StatelessWidget { - const GuardianActivityLogScreen({super.key}); - @override - Widget build(BuildContext context) => const _EndpointListScreen( - title: 'User Logs', endpoint: '/guardian/activity-logs'); -} - class GuardianSendNotifScreen extends StatefulWidget { const GuardianSendNotifScreen({super.key}); @@ -850,23 +838,6 @@ class _GuardianSendNotifScreenState extends State { } } -class GuardianAiConfigScreen extends StatelessWidget { - const GuardianAiConfigScreen({super.key}); - @override - Widget build(BuildContext context) { - return _Page( - title: 'AI Config', - subtitle: '/guardian/ai-config', - actions: [ - IconButton( - onPressed: () => context.go('/guardian/benchmark'), - icon: const Icon(Icons.speed)) - ], - child: const _EndpointList(endpoint: '/guardian/ai-config'), - ); - } -} - class GuardianVoiceCmdScreen extends StatelessWidget { const GuardianVoiceCmdScreen({super.key}); @override @@ -977,7 +948,7 @@ class _GuardianMapHistoryScreen extends StatelessWidget { child: _MapScreenBody(guardianEndpoint: '/guardian/user-location'), ), SizedBox(height: 12), - Expanded(flex: 2, child: _LocationTimeline()), + Expanded(flex: 2, child: ClipRect(child: _LocationTimeline())), ], ), ); @@ -1872,8 +1843,8 @@ class _EmptyPanel extends StatelessWidget { Widget build(BuildContext context) { return Container( width: double.infinity, - constraints: const BoxConstraints(minHeight: 180), - padding: const EdgeInsets.all(18), + constraints: const BoxConstraints(minHeight: 0), + padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 8), decoration: BoxDecoration( color: const Color(0xFFF8FAFC), borderRadius: BorderRadius.circular(12), @@ -2196,15 +2167,17 @@ class _JsonCard extends StatelessWidget { @override Widget build(BuildContext context) { - return Container( - width: double.infinity, - constraints: const BoxConstraints(minHeight: 220), - padding: const EdgeInsets.all(14), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(12), - border: Border.all(color: const Color(0xFFE2E8F0))), - child: SingleChildScrollView(child: Text(data?.toString() ?? 'No data')), + return Expanded( + child: Container( + width: double.infinity, + padding: const EdgeInsets.all(14), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: const Color(0xFFE2E8F0))), + child: + SingleChildScrollView(child: Text(data?.toString() ?? 'No data')), + ), ); } } From 20e458e4064ac5e89af4b3397346efd3ce3befdd Mon Sep 17 00:00:00 2001 From: 5803024019 Date: Tue, 19 May 2026 16:30:25 +0700 Subject: [PATCH 5/6] docs: add WalkGuide academic report LaTeX source files --- report/main.tex | 1471 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1471 insertions(+) create mode 100644 report/main.tex diff --git a/report/main.tex b/report/main.tex new file mode 100644 index 0000000..0e1e342 --- /dev/null +++ b/report/main.tex @@ -0,0 +1,1471 @@ +\documentclass[12pt,a4paper]{report} + +% ─── Packages ─────────────────────────────────────────────────────────────── +\usepackage[T1]{fontenc} +\usepackage[utf8]{inputenc} +\usepackage[a4paper, top=2.5cm, bottom=2.5cm, left=3cm, right=2.5cm]{geometry} +\usepackage{lmodern} +\usepackage{microtype} +\usepackage{setspace} +\usepackage{parskip} +\usepackage{titlesec} +\usepackage{titletoc} +\usepackage{fancyhdr} +\usepackage{graphicx} +\usepackage{float} +\usepackage{caption} +\usepackage{subcaption} +\usepackage{booktabs} +\usepackage{longtable} +\usepackage{array} +\usepackage{tabularx} +\usepackage{multirow} +\usepackage{xcolor} +\usepackage{listings} +\usepackage{mdframed} +\usepackage{enumitem} +\usepackage{amsmath} +\usepackage{amssymb} +\usepackage{hyperref} +\usepackage{tcolorbox} +\usepackage{pgfplots} +\usepackage{tikz} +\usepackage{pifont} +\usepackage{fontawesome5} +\usepackage{soul} +\pgfplotsset{compat=1.18} +\tcbuselibrary{skins, breakable} +\usetikzlibrary{shapes.geometric, arrows.meta, positioning, fit, backgrounds} + +% ─── Colors ──────────────────────────────────────────────────────────────── +\definecolor{primaryblue}{RGB}{25, 82, 148} +\definecolor{accentblue}{RGB}{41, 128, 185} +\definecolor{lightblue}{RGB}{214, 234, 248} +\definecolor{darkgray}{RGB}{44, 62, 80} +\definecolor{medgray}{RGB}{127, 140, 141} +\definecolor{lightgray}{RGB}{245, 246, 250} +\definecolor{successgreen}{RGB}{39, 174, 96} +\definecolor{warnyellow}{RGB}{230, 126, 34} +\definecolor{dangerred}{RGB}{192, 57, 43} +\definecolor{codebg}{RGB}{248, 249, 250} +\definecolor{codefg}{RGB}{36, 41, 47} + +% ─── Hyperref ─────────────────────────────────────────────────────────────── +\hypersetup{ + colorlinks=true, + linkcolor=primaryblue, + urlcolor=accentblue, + citecolor=accentblue, + pdfauthor={Kelompok 08 -- Evan, Japson, Bambang}, + pdftitle={WalkGuide -- Final Exam Report}, + pdfsubject={Integrated Mobile Application Project}, + bookmarksnumbered=true, +} + +% ─── Typography ───────────────────────────────────────────────────────────── +\onehalfspacing +\setlength{\parindent}{0pt} +\setlength{\parskip}{6pt} + +% ─── Section Formatting ───────────────────────────────────────────────────── +\titleformat{\chapter}[display] + {\normalfont\huge\bfseries\color{primaryblue}} + {\chaptertitlename\ \thechapter}{16pt}{\Huge} +\titlespacing*{\chapter}{0pt}{10pt}{20pt} + +\titleformat{\section} + {\normalfont\Large\bfseries\color{primaryblue}}{\thesection}{1em}{} +\titleformat{\subsection} + {\normalfont\large\bfseries\color{accentblue}}{\thesubsection}{1em}{} +\titleformat{\subsubsection} + {\normalfont\normalsize\bfseries\color{darkgray}}{\thesubsubsection}{1em}{} + +% ─── Header & Footer ──────────────────────────────────────────────────────── +\pagestyle{fancy} +\fancyhf{} +\fancyhead[L]{\small\color{medgray}WalkGuide -- Final Exam Report} +\fancyhead[R]{\small\color{medgray}Kelompok 08} +\fancyfoot[C]{\thepage} +\renewcommand{\headrulewidth}{0.4pt} +\renewcommand{\footrulewidth}{0pt} + +% ─── Code Listing ─────────────────────────────────────────────────────────── +\lstdefinestyle{javastyle}{ + language=Java, + backgroundcolor=\color{codebg}, + basicstyle=\ttfamily\footnotesize\color{codefg}, + keywordstyle=\bfseries\color{primaryblue}, + commentstyle=\itshape\color{medgray}, + stringstyle=\color{successgreen}, + numberstyle=\tiny\color{medgray}, + numbers=left, + numbersep=8pt, + frame=single, + framerule=0.5pt, + rulecolor=\color{medgray}, + breaklines=true, + captionpos=b, + tabsize=4, + showstringspaces=false, +} +\lstdefinestyle{dartstyle}{ + language=Java, + backgroundcolor=\color{codebg}, + basicstyle=\ttfamily\footnotesize\color{codefg}, + keywordstyle=\bfseries\color{accentblue}, + commentstyle=\itshape\color{medgray}, + stringstyle=\color{successgreen}, + numbers=left, + numbersep=8pt, + frame=single, + framerule=0.5pt, + rulecolor=\color{medgray}, + breaklines=true, + captionpos=b, + tabsize=2, + showstringspaces=false, +} +\lstset{style=javastyle} + +% ─── Custom Boxes ──────────────────────────────────────────────────────────── +\tcbset{ + infobox/.style={ + enhanced, breakable, + colback=lightblue!40, colframe=primaryblue, + fonttitle=\bfseries, coltitle=white, + attach boxed title to top left={yshift=-2mm, xshift=4mm}, + boxed title style={colback=primaryblue, rounded corners}, + arc=4pt, boxrule=0.8pt, + }, + warnbox/.style={ + enhanced, breakable, + colback=warnyellow!10, colframe=warnyellow, + fonttitle=\bfseries, coltitle=white, + attach boxed title to top left={yshift=-2mm, xshift=4mm}, + boxed title style={colback=warnyellow, rounded corners}, + arc=4pt, boxrule=0.8pt, + }, + codebox/.style={ + enhanced, breakable, + colback=codebg, colframe=medgray, + arc=2pt, boxrule=0.5pt, + fontupper=\ttfamily\small, + } +} + +% ─── Custom Commands ───────────────────────────────────────────────────────── +\newcommand{\done}{\textcolor{successgreen}{\ding{51}}} +\newcommand{\partial}{\textcolor{warnyellow}{\ding{115}}} +\newcommand{\missing}{\textcolor{dangerred}{\ding{55}}} +\newcommand{\code}[1]{\texttt{\small#1}} +\newcommand{\filepath}[1]{\texttt{\small\color{accentblue}#1}} +\newcommand{\apipath}[1]{\texttt{\small\color{primaryblue}#1}} + +% ─── Table Column Types ────────────────────────────────────────────────────── +\newcolumntype{L}[1]{>{\raggedright\arraybackslash}p{#1}} +\newcolumntype{C}[1]{>{\centering\arraybackslash}p{#1}} +\newcolumntype{R}[1]{>{\raggedleft\arraybackslash}p{#1}} + +% ════════════════════════════════════════════════════════════════════════════ +\begin{document} +% ════════════════════════════════════════════════════════════════════════════ + +% ─── Cover Page ───────────────────────────────────────────────────────────── +\begin{titlepage} +\pagecolor{primaryblue} +\color{white} +\centering +\vspace*{2cm} + +{\fontsize{14}{18}\selectfont\textbf{LAPORAN FINAL EXAM}}\\[0.4cm] +{\fontsize{12}{16}\selectfont Object-Oriented Analysis and Design}\\[0.3cm] +{\fontsize{11}{14}\selectfont Flutter $\times$ Spring Boot $\times$ Artificial Intelligence} + +\vspace{1.5cm} +\begin{tikzpicture} + \draw[white, line width=2pt] (0,0) circle (2.8cm); + \node[white, font=\fontsize{40}{44}\selectfont] at (0,0.3) {\faEye}; + \node[white, font=\fontsize{12}{14}\selectfont\bfseries] at (0,-1.2) {WalkGuide}; +\end{tikzpicture} + +\vspace{1cm} +{\fontsize{36}{42}\selectfont\bfseries WalkGuide}\\[0.3cm] +{\fontsize{16}{20}\selectfont AI-Powered Navigation for the Visually Impaired} + +\vspace{2cm} +\begin{tcolorbox}[colback=white!15, colframe=white!40, arc=8pt, width=12cm] + \centering\color{white} + \begin{tabular}{ll} + \textbf{Kelompok} & 08 \\[4pt] + \textbf{Anggota 1} & Evan \\ + \textbf{Anggota 2} & Japson \\ + \textbf{Anggota 3} & Bambang \\[4pt] + \textbf{Mata Kuliah} & Object-Oriented Analysis and Design \\ + \textbf{Tanggal} & 19 Mei 2026 \\ + \end{tabular} +\end{tcolorbox} + +\vfill +{\small\color{white!70} Universitas $\cdot$ Surabaya $\cdot$ 2026} +\end{titlepage} +\nopagecolor + +% ─── Front Matter ──────────────────────────────────────────────────────────── +\pagenumbering{roman} +\fancyhead[L]{\small\color{medgray}WalkGuide -- Final Exam Report} + +% Abstract +\chapter*{Abstract} +\addcontentsline{toc}{chapter}{Abstract} + +WalkGuide is an AI-powered mobile navigation system designed to assist visually impaired individuals in safely traversing their environment. The system comprises a Flutter mobile application and a Spring Boot RESTful backend, connected in real-time through WebSocket (STOMP) and Firebase Cloud Messaging (FCM). The mobile application runs YOLOv8n object detection entirely on-device using TensorFlow Lite, providing obstacle alerts through Text-to-Speech and haptic feedback without requiring an internet connection for core navigation. + +The backend, deployed on a university PostgreSQL server, exposes 26 versioned REST endpoints secured with JWT access and refresh tokens and role-based access control (RBAC) for two distinct user roles: \textit{User} (visually impaired individual) and \textit{Guardian} (caregiver or companion). Guardians can monitor the User's real-time location on an interactive map, configure AI detection parameters, send text or voice-note notifications, set up geofence boundaries, and initiate VoIP audio calls via Agora RTC. An SOS alert system allows Users to instantly notify their Guardian with a single voice command or hardware button press. + +The system implements seven Gang-of-Four (GoF) design patterns spanning all three categories --- Creational, Structural, and Behavioral --- across both the Flutter and Spring Boot codebases. The backend is tested with JUnit 5, Mockito, MockMvc, and Testcontainers, achieving above 70\% JaCoCo line coverage. Load testing was conducted using k6 with up to 30 virtual users. The Flutter application follows Clean Architecture principles with BLoC/Cubit state management and is benchmarked on a physical Android device in profile mode. + +\textbf{Keywords:} Flutter, Spring Boot, YOLOv8, TensorFlow Lite, Obstacle Detection, Text-to-Speech, Visually Impaired, WebSocket, JWT, Clean Architecture, BLoC, GoF Design Patterns. + +\tableofcontents +\listoftables +\listoffigures +\newpage + +% ─── Main Content ───────────────────────────────────────────────────────────── +\pagenumbering{arabic} +\setcounter{page}{1} + +% ═══════════════════════════════════════════════════════════════════════════ +\chapter{Introduction} +% ═══════════════════════════════════════════════════════════════════════════ + +\section{Problem Background} + +According to the World Health Organization (WHO), over 2.2 billion people worldwide live with a visual impairment, of which at least 1 billion have conditions that could have been prevented or are yet to be addressed. For individuals with complete or severe visual impairment, navigating public spaces poses significant risks: unexpected obstacles such as vehicles, pedestrians, street furniture, and road hazards create daily challenges that can result in injury. + +Traditional mobility aids such as the white cane and guide dogs provide partial assistance but offer no real-time digital awareness or remote monitoring capabilities. Existing mobile applications for the visually impaired tend to focus on text recognition or turn-by-turn navigation without addressing real-time obstacle detection or caregiver connectivity. + +WalkGuide addresses this gap by combining on-device computer vision (YOLOv8n via TensorFlow Lite), real-time audio feedback (Text-to-Speech), and a connected guardian ecosystem into a single, cohesive system --- all running on consumer Android hardware without requiring dedicated or expensive specialized equipment. + +\section{Objectives} + +\begin{enumerate} + \item Design and implement an Android mobile application in Flutter that performs real-time obstacle detection using on-device AI inference. + \item Build a Spring Boot backend exposing a secure, versioned REST API that manages authentication, pairing, location tracking, notifications, SOS events, and AI configuration. + \item Connect the two systems in real-time using WebSocket (STOMP) for live location updates and SOS alerts, supplemented by Firebase Cloud Messaging (FCM) for background push notifications. + \item Apply rigorous Object-Oriented Analysis and Design (OOAD) methodology --- producing use case, class, sequence, state, ERD, and component diagrams --- before commencing development. + \item Implement at least four GoF design patterns (one per category: Creational, Structural, Behavioral) and document each with a traceability audit. + \item Demonstrate functional testing (JUnit, widget tests, integration tests) and performance benchmarking (k6 load tests, Flutter profile-mode metrics). +\end{enumerate} + +\section{Target Users} + +\begin{description} + \item[\textbf{User (ROLE\_USER):}] A visually impaired individual who uses the WalkGuide app as their primary navigation aid. They interact with the app primarily through voice commands and audio feedback (Text-to-Speech), with hardware button shortcuts as a fallback. + \item[\textbf{Guardian (ROLE\_GUARDIAN):}] A sighted caregiver, family member, or companion who monitors the User remotely through the Guardian dashboard. They can send messages, configure AI settings, set geofences, and initiate calls. +\end{description} + +\section{Scope and Limitations} + +\textbf{In scope:} +\begin{itemize} + \item Android-only Flutter mobile application (iOS not targeted in this release). + \item On-device YOLOv8n inference using TensorFlow Lite; 80 COCO object labels. + \item RESTful Spring Boot backend deployed on the university server (\code{202.46.28.160}). + \item PostgreSQL database managed through Flyway migrations (V1--V16). + \item Real-time communication via STOMP WebSocket and FCM. + \item VoIP audio calls via Agora RTC. +\end{itemize} + +\textbf{Out of scope / known limitations:} +\begin{itemize} + \item iOS support: requires a separate Firebase configuration and Agora entitlements. + \item Firebase FCM backend: currently log-only until production Firebase Admin credentials are supplied. + \item Agora live call: implemented at API and service level; requires production Agora App ID and Certificate for live RTC. + \item Depth estimation via dual-camera: planned as a future enhancement. + \item AI benchmark of multiple model variants: documented in Chapter 12 (AI Benchmark Analysis). +\end{itemize} + +% ═══════════════════════════════════════════════════════════════════════════ +\chapter{OOAD --- Pre-Development Design Artifacts} +% ═══════════════════════════════════════════════════════════════════════════ + +All design artifacts in this chapter were produced before development commenced, using PlantUML 1.2024.x. Source \code{.puml} files are committed to \filepath{ooad-docs/diagrams/} in the project repository. + +\section{Use Case Diagram} + +Figure~\ref{fig:usecase} depicts the primary actors and use cases of the WalkGuide system. The two human actors are \textit{User} (visually impaired individual) and \textit{Guardian} (caregiver). Three external system actors --- Firebase FCM, Agora RTC, and OpenStreetMap/OSRM --- are included as secondary actors to represent the external services consumed by the system. + +\begin{figure}[H] +\centering +\begin{tikzpicture}[ + actor/.style={draw, circle, minimum size=1cm, font=\small}, + usecase/.style={draw, ellipse, minimum width=3cm, minimum height=0.8cm, font=\small, align=center}, + system/.style={draw, rectangle, dashed, rounded corners, inner sep=8pt}, + >=Latex +] +% Actors left +\node[actor] (user) at (-6, 2) {}; +\node[below=2pt of user, font=\scriptsize\bfseries] {User}; +\node[actor] (guardian) at (-6, -3) {}; +\node[below=2pt of guardian, font=\scriptsize\bfseries] {Guardian}; + +% Actors right +\node[actor] (fcm) at (7, 2) {}; +\node[below=2pt of fcm, font=\scriptsize] {Firebase FCM}; +\node[actor] (agora) at (7, 0) {}; +\node[below=2pt of agora, font=\scriptsize] {Agora RTC}; +\node[actor] (maps) at (7, -2) {}; +\node[below=2pt of maps, font=\scriptsize] {OpenStreetMap}; + +% System boundary +\begin{scope}[on background layer] +\node[system, fit={(0,4.5)(5,-5.5)}, fill=lightblue!30, label={[font=\small\bfseries, color=primaryblue]above:WalkGuide System}] (sys) {}; +\end{scope} + +% Use cases +\node[usecase, fill=white] (ucauth) at (2.5, 4) {Register / Login}; +\node[usecase, fill=white] (ucpair) at (2.5, 3) {Pair Guardian \& User}; +\node[usecase, fill=white] (ucwalk) at (2.5, 2) {Start WalkGuide}; +\node[usecase, fill=white] (ucdetect) at (2.5, 1) {Detect Obstacle}; +\node[usecase, fill=white] (ucloc) at (2.5, 0) {Report Location}; +\node[usecase, fill=white] (ucsos) at (2.5, -1) {Trigger SOS}; +\node[usecase, fill=white] (ucnotif) at (2.5, -2) {Read Notifications}; +\node[usecase, fill=white] (uccall) at (2.5, -3) {Call Partner}; +\node[usecase, fill=white] (ucdash) at (2.5, -4) {Monitor Dashboard}; +\node[usecase, fill=white] (ucconfig) at (2.5, -5) {Configure AI / Geofence}; + +% Arrows - User +\draw[->] (user) -- (ucauth); +\draw[->] (user) -- (ucpair); +\draw[->] (user) -- (ucwalk); +\draw[->] (user) -- (ucsos); +\draw[->] (user) -- (ucnotif); +\draw[->] (user) -- (uccall); + +% include +\draw[->, dashed] (ucwalk) -- node[right, font=\scriptsize]{$\langle\langle$include$\rangle\rangle$} (ucdetect); +\draw[->, dashed] (ucwalk) -- node[right, font=\scriptsize]{$\langle\langle$include$\rangle\rangle$} (ucloc); + +% Arrows - Guardian +\draw[->] (guardian) -- (ucauth); +\draw[->] (guardian) -- (ucpair); +\draw[->] (guardian) -- (uccall); +\draw[->] (guardian) -- (ucdash); +\draw[->] (guardian) -- (ucconfig); + +% Arrows - External +\draw[->] (ucsos) -- (fcm); +\draw[->] (ucnotif) -- (fcm); +\draw[->] (uccall) -- (agora); +\draw[->] (uccall) -- (fcm); +\draw[->] (ucloc) -- (maps); +\end{tikzpicture} +\caption{WalkGuide Use Case Diagram} +\label{fig:usecase} +\end{figure} + +\section{Class Diagram} + +The domain model consists of 13 core entity classes, 5 controller classes, and 14 service classes. Table~\ref{tab:entities} summarizes the primary entities and their key attributes. The full class diagram is rendered from \filepath{ooad-docs/diagrams/class-diagram.puml}. + +\begin{table}[H] +\centering +\caption{Core Domain Entity Classes} +\label{tab:entities} +\begin{tabularx}{\textwidth}{L{3.2cm} L{4.5cm} L{5.5cm}} +\toprule +\textbf{Entity} & \textbf{Key Attributes} & \textbf{Key Relationships} \\ +\midrule +\code{User} & id, email, role, uniqueUserId, displayName, fcmToken & One-to-many with LocationHistory, ActivityLog, SosEvent \\ +\code{PairingRelation} & id, status, invitedAt, respondedAt & Many-to-one with guardian (User) and user (User) \\ +\code{LocationHistory} & id, lat, lng, accuracy, speed, heading & Many-to-one with User \\ +\code{ActivityLog} & id, logType, description, metadata (JSONB) & Many-to-one with User \\ +\code{ObstacleLog} & id, label, confidence, direction, estimatedDist & Many-to-one with User \\ +\code{GuardianNotification} & id, notifType, content, voiceNoteUrl, isRead & Linked via PairingRelation \\ +\code{SosEvent} & id, triggerType, lat, lng, status & Many-to-one with User \\ +\code{UserSettings} & ttsLanguage, ttsPitch, ttsSpeed, hapticEnabled & One-to-one with User \\ +\code{AiConfig} & confidenceThreshold, alertDistanceClose, maxInferenceFps & Linked via PairingRelation \\ +\code{VoiceCommandConfig} & commandKey, triggerPhrase, enabled & Linked via PairingRelation \\ +\code{HardwareShortcut} & shortcutKey, buttonName, buttonCode, enabled & Linked via PairingRelation \\ +\code{GeofenceConfig} & centerLat, centerLng, radiusMeters, enabled & Linked via PairingRelation \\ +\code{RefreshToken} & token, expiresAt & Many-to-one with User \\ +\bottomrule +\end{tabularx} +\end{table} + +\section{Sequence Diagrams} + +\subsection{Sequence 1: Login Flow} + +\begin{figure}[H] +\centering +\begin{tikzpicture}[ + lifeline/.style={draw, rectangle, minimum width=2cm, minimum height=0.6cm, font=\scriptsize, fill=lightblue!40}, + msg/.style={->, font=\scriptsize}, + retmsg/.style={->, dashed, font=\scriptsize}, +] +\node[lifeline] (user) at (0,0) {User}; +\node[lifeline] (ui) at (2.5,0) {LoginScreen}; +\node[lifeline] (dio) at (5.5,0) {ApiClient}; +\node[lifeline] (ctrl) at (8.5,0) {AuthController}; +\node[lifeline] (svc) at (11,0) {AuthService}; +\node[lifeline] (db) at (13.5,0) {PostgreSQL}; + +\foreach \x in {0,2.5,5.5,8.5,11,13.5} + \draw[dashed, medgray] (\x,-0.3) -- (\x,-6.5); + +\draw[msg] (0,-0.7) -- node[above, font=\scriptsize]{enter credentials} (2.5,-0.7); +\draw[msg] (2.5,-1.2) -- node[above, font=\scriptsize]{POST /auth/login} (5.5,-1.2); +\draw[msg] (5.5,-1.7) -- node[above, font=\scriptsize]{LoginRequest} (8.5,-1.7); +\draw[msg] (8.5,-2.2) -- node[above, font=\scriptsize]{login(req)} (11,-2.2); +\draw[msg] (11,-2.7) -- node[above, font=\scriptsize]{findByEmail + tokens} (13.5,-2.7); +\draw[retmsg] (13.5,-3.2) -- node[above, font=\scriptsize]{user entity} (11,-3.2); +\draw[retmsg] (11,-3.7) -- node[above, font=\scriptsize]{AuthDataResponse} (8.5,-3.7); +\draw[retmsg] (8.5,-4.2) -- node[above, font=\scriptsize]{ApiResponse} (5.5,-4.2); +\draw[retmsg] (5.5,-4.7) -- node[above, font=\scriptsize]{tokens + role} (2.5,-4.7); +\draw[msg] (2.5,-5.2) -- node[above, font=\scriptsize]{save tokens, route by role} (0,-5.2); +\end{tikzpicture} +\caption{Sequence Diagram: Login Flow} +\label{fig:seq-login} +\end{figure} + +\subsection{Sequence 2: Guardian Invites User (Pairing)} + +The pairing flow is initiated by a Guardian who inputs the User's 12-character unique ID. Upon acceptance, the backend seeds 14 default voice command configurations, 5 hardware shortcut configurations, and one AI configuration record for the pair. + +\subsection{Sequence 3: SOS Alert End-to-End} + +\begin{figure}[H] +\centering +\begin{tikzpicture}[ + lifeline/.style={draw, rectangle, minimum width=1.8cm, minimum height=0.6cm, font=\scriptsize, fill=lightblue!40}, + msg/.style={->, font=\scriptsize}, + retmsg/.style={->, dashed, font=\scriptsize}, +] +\node[lifeline] (user) at (0,0) {User}; +\node[lifeline] (ui) at (2.3,0) {SosScreen}; +\node[lifeline] (ctrl) at (5,0) {UserController}; +\node[lifeline] (svc) at (7.7,0) {SosService}; +\node[lifeline] (db) at (10,0) {PostgreSQL}; +\node[lifeline] (fcm) at (12.3,0) {FCM}; +\node[lifeline] (dash) at (14.6,0) {GuardianDash}; + +\foreach \x in {0,2.3,5,7.7,10,12.3,14.6} + \draw[dashed, medgray] (\x,-0.3) -- (\x,-7.5); + +\draw[msg] (0,-0.7) -- node[above, font=\tiny]{press SOS} (2.3,-0.7); +\draw[msg] (2.3,-1.2) -- node[above, font=\tiny]{POST /user/sos} (5,-1.2); +\draw[msg] (5,-1.7) -- node[above, font=\tiny]{triggerSos()} (7.7,-1.7); +\draw[msg] (7.7,-2.2) -- node[above, font=\tiny]{save SosEvent} (10,-2.2); +\draw[retmsg] (10,-2.7) -- (7.7,-2.7); +\draw[msg] (7.7,-3.2) -- node[above, font=\tiny]{high-priority push} (12.3,-3.2); +\draw[msg] (7.7,-3.7) -- node[above, font=\tiny]{WS /queue/sos/\{guardianId\}} (14.6,-3.7); +\draw[retmsg] (7.7,-4.2) -- node[above, font=\tiny]{SosEventResponse} (5,-4.2); +\draw[retmsg] (5,-4.7) -- node[above, font=\tiny]{201 Created} (2.3,-4.7); +\draw[msg] (2.3,-5.2) -- node[above, font=\tiny]{TTS + haptic} (0,-5.2); +\draw[msg] (14.6,-5.7) -- node[above, font=\tiny]{PUT /sos/\{id\}/acknowledge} (5,-5.7); +\draw[msg] (5,-6.2) -- node[above, font=\tiny]{acknowledgeSos()} (7.7,-6.2); +\draw[msg] (7.7,-6.7) -- node[above, font=\tiny]{FCM to User: help coming} (12.3,-6.7); +\end{tikzpicture} +\caption{Sequence Diagram: SOS Alert Flow} +\label{fig:seq-sos} +\end{figure} + +\section{State Machine Diagram} + +The \code{SosEvent} entity transitions through three states: \texttt{TRIGGERED} (initial), \texttt{ACKNOWLEDGED} (Guardian confirms), and \texttt{RESOLVED} (incident closed). Figure~\ref{fig:state} illustrates these transitions. + +\begin{figure}[H] +\centering +\begin{tikzpicture}[ + state/.style={draw, rounded rectangle, minimum width=2.5cm, minimum height=0.8cm, font=\small, fill=lightblue!40}, + >=Latex +] +\node[fill=darkgray, circle, minimum size=0.4cm] (start) at (-2, 0) {}; +\node[state, fill=warnyellow!30] (triggered) at (1.5, 0) {TRIGGERED}; +\node[state, fill=accentblue!20] (acked) at (6, 0) {ACKNOWLEDGED}; +\node[state, fill=successgreen!20] (resolved) at (10, 0) {RESOLVED}; +\node[fill=darkgray, circle, minimum size=0.4cm, label=right:{}] (end) at (13, 0) {}; +\draw[thick, double distance=2pt] (end) circle (0.55cm); + +\draw[->] (start) -- (triggered); +\draw[->] (triggered) -- node[above, font=\scriptsize]{Guardian acknowledges} (acked); +\draw[->] (acked) -- node[above, font=\scriptsize]{incident handled} (resolved); +\draw[->, bend right=30] (triggered) to node[below, font=\scriptsize]{auto / manual close} (resolved); +\draw[->] (resolved) -- (end); +\end{tikzpicture} +\caption{State Machine Diagram: SosEvent} +\label{fig:state} +\end{figure} + +\section{Entity-Relationship Diagram} + +The database schema consists of 16 tables managed by Flyway migrations V1--V16. All tables use \code{BIGSERIAL} primary keys and \code{TIMESTAMP} audit fields. The central entity is \code{users}, which branches into both personal data (settings, tokens, logs) and relational data (pairing, notifications, configurations). + +\begin{table}[H] +\centering +\caption{Database Tables Summary (Flyway V1--V16)} +\label{tab:db} +\begin{tabularx}{\textwidth}{C{0.5cm} L{4cm} L{8.5cm}} +\toprule +\textbf{V\#} & \textbf{Table / Action} & \textbf{Description} \\ +\midrule +V1 & \code{users} & Core user table with email, password (BCrypt), role, display name, FCM token \\ +V2 & seed data & Seed default users for development testing \\ +V3 & guardian-user link & Initial guardian-user relationship scaffolding \\ +V4 & \code{ALTER users} & Add \code{unique\_user\_id} CHAR(12) column \\ +V5 & \code{pairing\_relations} & Guardian-User pairing with PENDING/ACTIVE/REJECTED status \\ +V6 & \code{activity\_logs} & All user activity events with JSONB metadata \\ +V7 & \code{obstacle\_logs} & YOLO detection records with direction and estimated distance \\ +V8 & \code{location\_history} & GPS coordinates with accuracy, speed, heading \\ +V9 & \code{guardian\_notifications} & Text and voice-note messages from Guardian to User \\ +V10 & \code{sos\_events} & SOS triggers with location and status transitions \\ +V11 & \code{user\_settings} & TTS language, pitch, speed, haptic preferences \\ +V12 & \code{ai\_configs} & YOLO confidence threshold, alert distances, max FPS \\ +V13 & \code{voice\_command\_configs} & 14 configurable voice trigger phrases per user pair \\ +V14 & \code{hardware\_shortcuts} & 5 hardware button assignments per user pair \\ +V15 & \code{geofence\_configs} & Geofence center coordinates and radius per user pair \\ +V16 & \code{refresh\_tokens} & JWT refresh tokens with 30-day expiry \\ +\bottomrule +\end{tabularx} +\end{table} + +\section{Component Diagram} + +The system is composed of four major runtime components: the Flutter mobile application, the Spring Boot backend, the PostgreSQL database, and three external cloud services (Firebase FCM, Agora RTC, OpenStreetMap/OSRM). Communication channels are shown in Figure~\ref{fig:component}. + +\begin{figure}[H] +\centering +\begin{tikzpicture}[ + comp/.style={draw, rectangle, rounded corners, minimum width=3cm, minimum height=1cm, font=\small, fill=lightblue!30}, + subcomp/.style={draw, rectangle, minimum width=2.4cm, minimum height=0.7cm, font=\scriptsize, fill=white}, + cloud/.style={draw, ellipse, minimum width=2.5cm, minimum height=1cm, font=\scriptsize, fill=warnyellow!20}, + db/.style={draw, cylinder, shape border rotate=90, minimum width=2cm, minimum height=1.2cm, font=\scriptsize, fill=successgreen!20}, + >=Latex +] +% Flutter +\node[comp, minimum width=4.5cm, minimum height=3.5cm, label=center:\textbf{}] (flutter) at (-4, 1) {}; +\node[font=\small\bfseries, color=primaryblue] at (-4, 2.5) {Flutter Mobile}; +\node[subcomp] at (-4, 1.7) {Screens + BLoC}; +\node[subcomp] at (-4, 0.9) {ApiClient (Dio)}; +\node[subcomp] at (-4, 0.1) {YOLO / TTS / STT}; +\node[subcomp] at (-4, -0.7) {WebSocketService}; + +% Spring Boot +\node[comp, minimum width=4.5cm, minimum height=3.5cm] (spring) at (4, 1) {}; +\node[font=\small\bfseries, color=primaryblue] at (4, 2.5) {Spring Boot}; +\node[subcomp] at (4, 1.7) {Controllers}; +\node[subcomp] at (4, 0.9) {Services}; +\node[subcomp] at (4, 0.1) {Repositories}; +\node[subcomp] at (4, -0.7) {WebSocket Broker}; + +% DB +\node[db] (db) at (4, -2.8) {PostgreSQL}; + +% Clouds +\node[cloud] (fcm) at (-4, -3) {Firebase FCM}; +\node[cloud] (agora) at (0, -3) {Agora RTC}; +\node[cloud] (maps) at (-4, -4.5) {OpenStreetMap}; + +% Arrows +\draw[<->, thick, color=primaryblue] (-2, 1) -- node[above, font=\scriptsize]{REST /api/v1} (2, 1); +\draw[<->, thick, color=accentblue] (-2, -0.3) -- node[below, font=\scriptsize]{STOMP /ws} (2, -0.3); +\draw[->] (spring) -- (db); +\draw[->] (spring) -- (fcm); +\draw[->] (flutter) -- (agora); +\draw[->] (flutter) -- (maps); +\end{tikzpicture} +\caption{WalkGuide System Component Diagram} +\label{fig:component} +\end{figure} + +% ═══════════════════════════════════════════════════════════════════════════ +\chapter{OOAD --- Design Pattern Documentation} +% ═══════════════════════════════════════════════════════════════════════════ + +Seven GoF design patterns are implemented across the WalkGuide codebase, covering all three categories. Table~\ref{tab:patterns} provides a summary; detailed documentation follows. + +\begin{table}[H] +\centering +\caption{GoF Design Patterns in WalkGuide} +\label{tab:patterns} +\begin{tabularx}{\textwidth}{L{2.5cm} L{2.2cm} L{4cm} L{5cm}} +\toprule +\textbf{Pattern} & \textbf{Category} & \textbf{Location} & \textbf{Justification} \\ +\midrule +Builder & Creational & \code{User.java}, \code{FcmService.java} & Many optional fields; fluent construction \\ +Singleton & Creational & \code{injection\_container.dart} & One shared lifecycle for resource-heavy services \\ +Facade & Structural & \code{TtsService}, \code{VoiceCommandHandler} & Hides plugin complexity from UI layer \\ +Repository & Structural & All \code{*Repository.java} interfaces & Decouples JPA from service logic \\ +Observer & Behavioral & BLoC/Cubit, \code{WebSocketService} & Reactive state; real-time push without polling \\ +Strategy & Behavioral & \code{obstacle\_analyzer.dart} & Swappable direction/distance thresholds \\ +Chain of Responsibility & Behavioral & Dio interceptors, Spring Security filter chain & Sequential request handling with pass-through \\ +\bottomrule +\end{tabularx} +\end{table} + +\section{Creational: Builder Pattern} + +\textbf{Location:} \filepath{walkguide-backend/demo/src/main/java/com/walkguide/entity/User.java}, \filepath{service/FcmService.java} + +\textbf{Implementation:} Lombok's \code{@Builder} annotation is applied to the \code{User} entity and all response DTO classes. The Firebase \code{Message} object in \code{FcmService} is constructed using the Agora SDK's native builder. + +\textbf{Justification:} The \code{User} entity has eight fields, five of which are optional (e.g., \code{displayName}, \code{uniqueUserId}, \code{fcmToken}). Using a builder prevents telescoping constructors and makes the construction intent explicit at every call site. + +\begin{lstlisting}[style=javastyle, caption={Builder Pattern -- User entity construction}] +// entity/User.java +@Entity @Builder @Data @NoArgsConstructor @AllArgsConstructor +public class User { + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + private String email; + private String password; + private String role; + private String uniqueUserId; // optional: ROLE_USER only + private String displayName; + private String fcmToken; // optional: updated post-login + // ... +} + +// Usage in AuthService.java +User newUser = User.builder() + .email(req.getEmail()) + .password(encoder.encode(req.getPassword())) + .role(req.getRole()) + .displayName(req.getDisplayName()) + .build(); +\end{lstlisting} + +\section{Creational: Singleton Pattern} + +\textbf{Location:} \filepath{walkguide-mobile/walkguide\_app/lib/app/injection\_container.dart} + +\textbf{Implementation:} All resource-heavy services are registered as singletons in the GetIt service locator. This ensures that \code{TtsService}, \code{WebSocketService}, \code{YoloDetector}, and \code{CallService} each have exactly one instance throughout the application lifecycle. + +\textbf{Justification:} The YOLO interpreter holds the entire model in memory ($\sim$6 MB). Creating multiple instances would exhaust mobile heap. The WebSocket connection must be shared across screens to avoid duplicate STOMP subscriptions. + +\begin{lstlisting}[style=dartstyle, caption={Singleton Pattern -- GetIt registration}] +// injection_container.dart +void setupInjection(String baseUrl) { + getIt.registerLazySingleton(() => TtsService()); + getIt.registerLazySingleton(() => YoloDetector()); + getIt.registerLazySingleton(() => WebSocketService()); + getIt.registerLazySingleton(() => ApiClient(baseUrl)); + // Factories for BLoCs (new instance per screen): + getIt.registerFactory(() => AuthBloc(getIt())); +} +\end{lstlisting} + +\section{Structural: Facade Pattern} + +\textbf{Location:} \filepath{core/services/tts\_service.dart}, \filepath{core/services/voice\_command\_handler.dart} + +\textbf{Implementation:} \code{TtsService} wraps the \code{flutter\_tts} plugin behind a simple interface: \code{speak()}, \code{speakImmediate()}, \code{stop()}, and \code{setLanguage()}. Callers never interact with the raw plugin. Similarly, \code{VoiceCommandHandler} acts as a facade over STT recognition, voice command matching, BLoC event dispatching, and GoRouter navigation. + +\textbf{Justification:} Without the facade, WalkGuide screen widgets would need to manage plugin lifecycle, language configuration, and queue management directly --- coupling UI code to implementation details. + +\section{Structural: Repository Pattern} + +\textbf{Location:} All files in \filepath{walkguide-backend/demo/src/main/java/com/walkguide/repository/} + +\textbf{Implementation:} Each Spring Data JPA repository interface extends \code{JpaRepository} and defines domain-specific query methods. Services depend only on repository interfaces, never on concrete JPA or SQL APIs. + +\textbf{Justification:} Applying the Repository pattern enables full Mockito mocking of the persistence layer in unit tests. Swapping PostgreSQL for another database would require changing only the repository implementations, not the service layer. + +\section{Behavioral: Observer Pattern} + +\textbf{Location:} \filepath{lib/app/app\_cubit.dart}, all BLoC files, \filepath{core/services/websocket\_service.dart}, \filepath{walkguide-backend/.../websocket/LocationBroadcaster.java} + +\textbf{Implementation:} The BLoC/Cubit pattern is itself an implementation of Observer: the BLoC is the Subject, and all \code{BlocBuilder}/\code{BlocConsumer} widgets are Observers. On the backend, the Spring WebSocket broker pushes location, SOS, and notification updates to all subscribed Guardian clients without polling. + +\section{Behavioral: Strategy Pattern} + +\textbf{Location:} \filepath{core/ai/obstacle\_analyzer.dart} + +\textbf{Implementation:} \code{ObstacleAnalyzer} encapsulates two independent strategies: \code{analyzeDirection()} (which divides the camera frame into thirds) and \code{estimateDistance()} (which uses bounding-box height ratio against configurable thresholds from \code{AiConfig}). Changing the Guardian's AI configuration updates the threshold values without touching the analyzer algorithm. + +\section{Behavioral: Chain of Responsibility Pattern} + +\textbf{Location:} \filepath{core/network/api\_client.dart} (Dio interceptors), \filepath{security/JwtAuthFilter.java} + +\textbf{Implementation:} Every outgoing HTTP request passes through a chain: \textit{AuthInterceptor} (inject JWT Bearer token) $\to$ \textit{ErrorInterceptor} (normalize HTTP errors to typed \code{Failure} objects) $\to$ \textit{LogInterceptor} (development logging). On the backend, Spring Security's filter chain processes each request through \code{JwtAuthFilter} before reaching controllers. + +% ═══════════════════════════════════════════════════════════════════════════ +\chapter{OOAD --- Design Traceability Audit} +% ═══════════════════════════════════════════════════════════════════════════ + +\section{Use Case to Code Mapping} + +\begin{longtable}{L{3.5cm} L{3.5cm} L{4cm} C{2cm}} +\caption{Use Case Traceability Matrix} \label{tab:traceability} \\ +\toprule +\textbf{Use Case} & \textbf{Flutter Entry} & \textbf{Backend Entry} & \textbf{Status} \\ +\midrule +\endfirsthead +\multicolumn{4}{c}{\textit{(continued from previous page)}} \\ +\toprule +\textbf{Use Case} & \textbf{Flutter Entry} & \textbf{Backend Entry} & \textbf{Status} \\ +\midrule +\endhead +\bottomrule +\endfoot +Register / Login & \code{features/auth/*} & \code{AuthController}, \code{AuthService} & \done \\ +Pair Guardian \& User & \code{features/pairing/pairing\_screens.dart} & \code{PairingController}, \code{PairingService} & \done \\ +Start / Stop WalkGuide & \code{features/walk\_guide/walk\_guide\_screen.dart} & \code{POST /user/walkguide/start,stop} & \done \\ +Detect Obstacle & \code{core/ai/*}, \code{walk\_guide\_screen.dart} & \code{POST /user/obstacle} & \textcolor{warnyellow}{Partial}* \\ +Report Location & \code{LocationReporterService} & \code{LocationService}, \code{LocationBroadcaster} & \done \\ +Trigger SOS & \code{features/sos/sos\_screen.dart} & \code{SosService}, \code{GuardianController} & \done \\ +Read Notifications & \code{features/notifications/notification\_screen.dart} & \code{NotificationService}, \code{FcmService} & \done \\ +Monitor Dashboard & \code{guardian\_dashboard\_screen.dart} & \code{GuardianDashboardService} & \done \\ +Call Partner & \code{features/call/call\_screen.dart}, \code{CallService} & \code{CallController}, \code{AgoraTokenService} & \textcolor{warnyellow}{Partial}** \\ +Configure AI / Geofence & \code{guardian\_ai\_config\_screen.dart} & \code{AiConfigService}, \code{GeofenceService} & \done \\ +Navigate Route & \code{features/navigation\_mode/} & \code{POST /user/location} + OSRM & \done \\ +\end{longtable} + +\textit{* Obstacle detection code complete; requires \code{yolov8n.tflite} asset file for live inference.}\\ +\textit{** Call API and service complete; requires production Agora App ID and Certificate for live RTC.} + +\section{Design Deviations} + +\begin{tcolorbox}[warnbox, title={Documented Design Deviations}] +\begin{enumerate} + \item \textbf{State Management (Partial BLoC):} The architecture document specifies BLoC for all screens. In implementation, several guardian dashboard screens use \code{StatefulWidget} with direct service calls for simplicity during the exam sprint. This trades architectural purity for development speed; the deviation is localized to screens that do not require complex state transitions. + + \item \textbf{Offline Storage (Partial Drift):} The full architecture specifies Drift (SQLite ORM) for offline-first entity caching. The current implementation uses \code{SharedPreferences} for a lightweight offline queue (\code{offline\_queue\_service.dart}) instead of a full Drift database. The Drift dependency is present in \code{pubspec.yaml} for future extension. + + \item \textbf{Backend FCM (Log-Only):} The \code{FcmService} currently logs the push notification payload instead of calling \code{FirebaseMessaging.send()}. This is an intentional deferral pending the provisioning of a production Firebase Admin credential file (\code{google-services-admin.json}). + + \item \textbf{i18n (Dependency Only):} Full internationalization with \code{.arb} locale files was not completed within the exam timeline. The \code{flutter\_localizations} dependency is declared; UI strings remain in English. +\end{enumerate} +\end{tcolorbox} + +% ═══════════════════════════════════════════════════════════════════════════ +\chapter{System Architecture} +% ═══════════════════════════════════════════════════════════════════════════ + +\section{Flutter Clean Architecture} + +The Flutter application is organized in a feature-first directory structure. Each feature module contains three sub-layers following Clean Architecture: + +\begin{description} + \item[\textbf{Domain Layer}] Contains entities (pure Dart classes with no framework dependencies), abstract repository interfaces, and use cases. This layer has zero external dependencies. + \item[\textbf{Data Layer}] Contains concrete repository implementations, data source classes (remote API and local storage), and data models (JSON serialization). This layer depends on Dio, SharedPreferences, and platform services. + \item[\textbf{Presentation Layer}] Contains BLoC/Cubit classes, screen widgets, and local widget components. This layer depends only on the domain layer (via use cases) and never imports from the data layer directly. +\end{description} + +\begin{table}[H] +\centering +\caption{Flutter Project Structure Summary} +\label{tab:flutter-structure} +\begin{tabularx}{\textwidth}{L{5cm} L{8.5cm}} +\toprule +\textbf{Directory} & \textbf{Contents} \\ +\midrule +\code{lib/app/} & App entry, router (GoRouter), GetIt injection container \\ +\code{lib/core/ai/} & YoloDetector, ObstacleAnalyzer, model loader \\ +\code{lib/core/network/} & ApiClient (Dio), AuthInterceptor, ErrorInterceptor \\ +\code{lib/core/services/} & TtsService, SttService, VoiceCommandHandler, WebSocketService, FCMService, HapticService \\ +\code{lib/core/storage/} & SecureStorage (JWT tokens), OfflineQueueService \\ +\code{lib/features/auth/} & Login, Register, Splash screens + AuthBloc \\ +\code{lib/features/walk\_guide/} & WalkGuide screen with camera stream + YOLO pipeline \\ +\code{lib/features/sos/} & SOS screen + SosCubit \\ +\code{lib/features/notifications/} & Notification screen + NotificationBloc \\ +\code{lib/features/pairing/} & User/Guardian pairing screens + PairingBloc \\ +\code{lib/features/guardian\_dashboard/} & Dashboard, map, AI config, voice cmd, shortcut, geofence screens \\ +\code{lib/features/call/} & VoIP call screen + CallService (Agora) \\ +\code{lib/features/navigation\_mode/} & OpenStreetMap navigation screen \\ +\code{lib/shared/widgets/} & Reusable shells, navigation bars \\ +\bottomrule +\end{tabularx} +\end{table} + +\section{Spring Boot Layered Architecture} + +The backend follows a strict three-layer architecture: + +\begin{enumerate} + \item \textbf{Controller Layer:} Handles HTTP request/response, input validation (\code{@Valid}), and delegates to services. Contains no business logic. + \item \textbf{Service Layer:} Implements all business logic, orchestrates repository calls, publishes WebSocket events, and calls FCM/Agora services. + \item \textbf{Repository Layer:} Spring Data JPA interfaces. Contains only query method declarations; no SQL strings in the Java code. +\end{enumerate} + +\section{Real-Time Architecture} + +Two complementary mechanisms provide real-time connectivity: + +\begin{description} + \item[\textbf{WebSocket (STOMP):}] Used for low-latency, high-frequency updates when both parties are online and the app is in the foreground. Topics: \code{/topic/location/\{userId\}} (live location for Guardian), \code{/queue/sos/\{guardianId\}} (SOS alerts), \code{/queue/notif/\{userId\}} (incoming notifications). + \item[\textbf{Firebase Cloud Messaging (FCM):}] Used for background delivery when the app is not in the foreground, and for high-priority alerts (SOS, incoming calls). FCM payloads include a \code{type} field (\code{SOS\_ALERT}, \code{INCOMING\_CALL}, \code{NOTIFICATION}, etc.) that drives the Flutter notification handler. +\end{description} + +% ═══════════════════════════════════════════════════════════════════════════ +\chapter{API Contract} +% ═══════════════════════════════════════════════════════════════════════════ + +\section{Response Envelope} + +All endpoints return a consistent JSON envelope: +\begin{tcblisting}{codebox} +{ + "success": true, + "data": { ... }, + "message": "Operation successful", + "timestamp": "2026-05-19T10:30:00Z" +} +\end{tcblisting} + +Error responses use \code{success: false} with an \code{errorCode} field (\code{AUTH\_ERROR}, \code{NOT\_FOUND}, \code{PAIRING\_ERROR}, \code{VALIDATION\_ERROR}, \code{INTERNAL\_ERROR}). + +\section{Endpoint Summary} + +\begin{longtable}{L{1cm} L{5cm} L{4.5cm} C{2cm}} +\caption{REST API Endpoints (26 Total)} \label{tab:api} \\ +\toprule +\textbf{Method} & \textbf{Path} & \textbf{Description} & \textbf{Auth} \\ +\midrule +\endfirsthead +\multicolumn{4}{c}{\textit{(continued)}} \\ +\toprule +\textbf{Method} & \textbf{Path} & \textbf{Description} & \textbf{Auth} \\ +\midrule +\endhead +\bottomrule +\endfoot +\multicolumn{4}{l}{\textit{Auth (/api/v1/auth)}} \\ +GET & /auth/ping & Health check / server test & None \\ +POST & /auth/register & Register Guardian or User & None \\ +POST & /auth/login & Login, receive JWT tokens & None \\ +POST & /auth/refresh & Refresh access token & None \\ +POST & /auth/logout & Logout, revoke refresh token & JWT \\ +PUT & /auth/fcm-token & Update FCM device token & JWT \\ +\midrule +\multicolumn{4}{l}{\textit{Pairing (/api/v1/shared/pairing)}} \\ +POST & /pairing/invite & Guardian invites User by unique ID & GUARDIAN \\ +POST & /pairing/respond & User accepts / rejects invite & USER \\ +DELETE & /pairing/unpair & Dissolve active pairing & JWT \\ +GET & /pairing/status & Get current pairing status & JWT \\ +\midrule +\multicolumn{4}{l}{\textit{Guardian (/api/v1/guardian)}} \\ +GET & /guardian/dashboard & Combined dashboard data & GUARDIAN \\ +GET & /guardian/user-status & Paired user status & GUARDIAN \\ +GET & /guardian/user-location & Last known GPS location & GUARDIAN \\ +GET & /guardian/location-history & Location history (paginated) & GUARDIAN \\ +GET & /guardian/activity-logs & User activity logs & GUARDIAN \\ +POST & /guardian/notifications/send & Send text or voice note & GUARDIAN \\ +GET & /guardian/sos-events & All SOS events & GUARDIAN \\ +PUT & /guardian/sos/\{id\}/acknowledge & Acknowledge SOS & GUARDIAN \\ +PUT & /guardian/ai-config & Update AI configuration & GUARDIAN \\ +PUT & /guardian/voice-commands & Update voice command phrase & GUARDIAN \\ +PUT & /guardian/geofence & Update geofence config & GUARDIAN \\ +\midrule +\multicolumn{4}{l}{\textit{User (/api/v1/user)}} \\ +GET & /user/profile & User profile & USER \\ +GET & /user/settings & TTS and app settings & USER \\ +PUT & /user/settings & Update user settings & USER \\ +POST & /user/location & Send GPS update & USER \\ +POST & /user/obstacle & Log detected obstacle & USER \\ +POST & /user/sos & Trigger SOS alert & USER \\ +GET & /user/notifications & Notifications from Guardian & USER \\ +PUT & /user/notifications/mark-all-read & Mark all as read & USER \\ +POST & /user/walkguide/start & Log WalkGuide session start & USER \\ +POST & /user/walkguide/stop & Log WalkGuide session stop & USER \\ +\midrule +\multicolumn{4}{l}{\textit{Shared (/api/v1/shared)}} \\ +POST & /call/token & Generate Agora RTC token & JWT \\ +POST & /call/notify & Send FCM incoming call push & JWT \\ +\end{longtable} + +The full OpenAPI 3.0 specification is committed to \filepath{walkguide-backend/demo/src/main/resources/openapi.yaml} and rendered by Swagger UI at \code{/swagger-ui.html}. + +% ═══════════════════════════════════════════════════════════════════════════ +\chapter{Flutter Implementation} +% ═══════════════════════════════════════════════════════════════════════════ + +\section{Key Features} + +\subsection{WalkGuide --- On-Device AI Obstacle Detection} + +The core feature of WalkGuide is real-time obstacle detection using YOLOv8n (nano variant) running entirely on-device via TensorFlow Lite. The inference pipeline is: + +\begin{enumerate} + \item Camera stream delivers \code{CameraImage} frames in YUV420 format. + \item \code{YoloDetector.detect()} converts YUV to RGB, resizes to $640 \times 640$, normalizes to $[0.0, 1.0]$, and runs the TFLite interpreter. + \item Output tensor $[1, 84, 8400]$ is post-processed: 8400 anchor boxes $\times$ 84 values (4 coordinates + 80 COCO class probabilities). + \item Non-Maximum Suppression (NMS, IoU threshold 0.45) removes duplicate detections. + \item \code{ObstacleAnalyzer} computes direction (LEFT/CENTER/RIGHT from bounding box center-x) and distance estimate (Very Close / Close / Medium / Far from bounding box height ratio). + \item TTS announcement is generated: \textit{"Caution! Person ahead center. Very close. Please stop."} + \item Haptic pattern fires proportional to distance severity. + \item Obstacle is logged to the backend via \code{POST /user/obstacle} with a 3-second debounce per label. +\end{enumerate} + +\begin{table}[H] +\centering +\caption{YOLO Inference Configuration} +\label{tab:yolo} +\begin{tabular}{ll} +\toprule +\textbf{Parameter} & \textbf{Value} \\ +\midrule +Model & YOLOv8n (nano) \\ +Format & TensorFlow Lite Float32 \\ +Input shape & $[1, 640, 640, 3]$ \\ +Output shape & $[1, 84, 8400]$ \\ +CPU threads & 4 \\ +Delegate & NNAPI (Android AI accelerator) \\ +Default confidence threshold & 0.5 (configurable by Guardian) \\ +Default max inference FPS & 5 \\ +Model size & $\approx$ 6 MB \\ +Labels & 80 COCO classes \\ +\bottomrule +\end{tabular} +\end{table} + +\subsection{Voice Command System} + +The always-on STT listener (\code{SttService}) runs continuously while the app is in the foreground. Recognized text is passed to \code{VoiceCommandHandler}, which performs case-insensitive substring matching against the 14 configurable trigger phrases stored in the local voice command cache. Matched commands dispatch events to the appropriate BLoC or trigger GoRouter navigation. + +Default trigger phrases include: \textit{"Start WalkGuide"}, \textit{"Send SOS"}, \textit{"Call Guardian"}, \textit{"Where Am I"}, \textit{"Read All My Notifications"}, and 9 others --- all configurable by the Guardian remotely. + +\subsection{Guardian Dashboard} + +The Guardian application provides a real-time monitoring interface: +\begin{itemize} + \item \textbf{Live Map:} OpenStreetMap tiles (no API key required) with a moving marker updated via STOMP WebSocket subscription (\code{/topic/location/\{userId\}}). + \item \textbf{AI Configuration:} Sliders for confidence threshold, alert distances, and max FPS; multi-select for enabled object labels. + \item \textbf{Geofence:} Tap-to-set center on the map with a configurable radius (50m--5000m); exit triggers an FCM push notification. + \item \textbf{Notifications:} Send text messages or voice notes to the User; voice notes are recorded using the \code{record} package and played on the User side via \code{just\_audio}. +\end{itemize} + +\subsection{Advanced Features} + +\begin{table}[H] +\centering +\caption{Advanced Features Implementation Status} +\label{tab:advanced} +\begin{tabularx}{\textwidth}{L{5cm} C{2cm} L{6cm}} +\toprule +\textbf{Feature} & \textbf{Status} & \textbf{Implementation} \\ +\midrule +Real-time WebSocket & \done & STOMP via \code{stomp\_dart\_client}; live location + SOS + notifications \\ +Push Notifications (FCM) & \textcolor{warnyellow}{\ding{115}} & Flutter side complete; backend log-only pending credentials \\ +Offline-first with sync & \textcolor{warnyellow}{\ding{115}} & \code{OfflineQueueService} with \code{SharedPreferences} queue \\ +Animated transitions & \done & \code{flutter\_animate} + custom \code{AnimatedContainer} for detection overlay \\ +Internationalization & \textcolor{dangerred}{\ding{55}} & Dependency declared; \code{.arb} files not yet created \\ +\bottomrule +\end{tabularx} +\end{table} + +\section{State Management} + +The application uses flutter\_bloc (BLoC and Cubit) as the primary state management solution. Key BLoCs include \code{AuthBloc} (login/register/logout), \code{PairingBloc} (invite/respond/unpair), and \code{NotificationBloc} (load/read/TTS). Screen-level Cubits handle simpler local state (e.g., \code{SosCubit}, \code{AppCubit}). The global \code{AppCubit} tracks authentication state across route guards. + +\section{Navigation (GoRouter)} + +GoRouter is configured with redirect guards: unauthenticated users are redirected to \code{/server-connect} (if no server URL is saved) or \code{/login}; authenticated users are routed to \code{/user/home} or \code{/guardian/home} based on their role claim from the JWT. + +% ═══════════════════════════════════════════════════════════════════════════ +\chapter{Spring Boot Implementation} +% ═══════════════════════════════════════════════════════════════════════════ + +\section{Security Configuration} + +Spring Security is configured with stateless session management (no server-side sessions). JWT access tokens expire in 1 hour; refresh tokens expire in 30 days and are stored in the \code{refresh\_tokens} table. Role-based routing: + +\begin{itemize} + \item \code{/api/v1/auth/**} --- \code{permitAll()} (public) + \item \code{/api/v1/guardian/**} --- \code{hasRole("GUARDIAN")} + \item \code{/api/v1/user/**} --- \code{hasRole("USER")} + \item \code{/api/v1/shared/**} --- \code{authenticated()} (either role) + \item \code{/ws/**} --- \code{permitAll()} (WebSocket handshake uses token in STOMP CONNECT frame) +\end{itemize} + +\section{Database and Flyway} + +The application connects to a PostgreSQL instance at the university server (\code{202.46.28.160:2002}). Flyway manages all schema changes; Hibernate is set to \code{ddl-auto=validate} to ensure the live schema matches entity definitions. Sixteen migration scripts (V1--V16) create all tables in dependency order. + +\section{Key Service Implementations} + +\subsection{PairingService} + +When a Guardian accepts a pairing, \code{PairingService.respondToPairing()} performs an atomic sequence: +\begin{enumerate} + \item Update \code{PairingRelation} status to \code{ACTIVE}. + \item Seed 14 default \code{VoiceCommandConfig} records. + \item Seed 5 default \code{HardwareShortcut} records. + \item Create one \code{AiConfig} record with default thresholds. + \item Create one \code{GeofenceConfig} record (disabled by default). + \item Send FCM notification to the Guardian: "User accepted pairing request." +\end{enumerate} + +\subsection{LocationService} + +On each \code{POST /user/location} call, \code{LocationService}: +\begin{enumerate} + \item Saves a \code{LocationHistory} record. + \item Broadcasts the position via WebSocket to \code{/topic/location/\{userId\}}. + \item Checks \code{GeofenceConfig} if enabled, computing Haversine distance: +\end{enumerate} + +\begin{equation} +d = 2r \cdot \arcsin\!\left(\sqrt{\sin^2\!\!\left(\frac{\Delta\phi}{2}\right) + \cos\phi_1\cos\phi_2\sin^2\!\!\left(\frac{\Delta\lambda}{2}\right)}\right) +\end{equation} + +where $r = 6{,}371{,}000$ m (Earth radius), $\phi$ is latitude in radians, and $\lambda$ is longitude in radians. If $d > \text{radiusMeters}$, an FCM push is sent to the Guardian. + +% ═══════════════════════════════════════════════════════════════════════════ +\chapter{Flutter Testing \& Benchmarking} +% ═══════════════════════════════════════════════════════════════════════════ + +\section{Unit Tests} + +Unit tests are located in \filepath{walkguide-mobile/walkguide\_app/test/unit/}. All tests use the \code{flutter\_test} framework with Mockito for repository mocking. + +\begin{table}[H] +\centering +\caption{Flutter Unit Test Coverage} +\label{tab:flutter-unit} +\begin{tabularx}{\textwidth}{L{5cm} L{8cm}} +\toprule +\textbf{Test File} & \textbf{Scenarios Covered} \\ +\midrule +\code{login\_use\_case\_test.dart} & Successful login, wrong password, network failure \\ +\code{register\_use\_case\_test.dart} & ROLE\_USER (generates uniqueUserId), ROLE\_GUARDIAN, duplicate email \\ +\code{obstacle\_analyzer\_test.dart} & LEFT/CENTER/RIGHT direction from bounding box positions; Very Close/Close/Medium/Far distance ratios \\ +\code{voice\_command\_handler\_test.dart} & Case-insensitive matching: "start walkguide", "Start WalkGuide", "START WALK GUIDE"; no false positives \\ +\code{geofence\_calculation\_test.dart} & Haversine accuracy at known coordinates; boundary conditions at radius edge \\ +\bottomrule +\end{tabularx} +\end{table} + +\section{Widget Tests} + +Widget tests in \filepath{test/widget/} verify that core UI components render correctly and dispatch the expected BLoC events. + +\begin{table}[H] +\centering +\caption{Flutter Widget Test Coverage} +\label{tab:flutter-widget} +\begin{tabular}{ll} +\toprule +\textbf{Test File} & \textbf{Key Assertions} \\ +\midrule +\code{login\_screen\_test.dart} & Form renders, submit button triggers \code{LoginRequested} event \\ +\code{walk\_guide\_screen\_test.dart} & Camera preview widget renders, stop button visible \\ +\code{notification\_screen\_test.dart} & Notification list renders, unread badge visible \\ +\code{sos\_screen\_test.dart} & SOS button visible; tap dispatches \code{TriggerSos} event \\ +\code{manual\_screen\_test.dart} & All 14 default voice commands displayed \\ +\bottomrule +\end{tabular} +\end{table} + +\section{Integration Tests} + +Integration tests in \filepath{test/integration\_test/} exercise full end-to-end flows against the live Spring Boot backend. + +\begin{table}[H] +\centering +\caption{Flutter Integration Test Flows} +\label{tab:flutter-integration} +\begin{tabularx}{\textwidth}{L{5cm} L{8cm}} +\toprule +\textbf{Test File} & \textbf{Flow Covered} \\ +\midrule +\code{flow\_1\_login\_dashboard\_logout.dart} & Login with User credentials $\to$ dashboard loads $\to$ logout clears tokens \\ +\code{flow\_2\_walkguide\_start\_stop\_sos.dart} & Start WalkGuide $\to$ receive detection state $\to$ stop WalkGuide $\to$ trigger SOS \\ +\code{flow\_3\_notification\_read\_all.dart} & Guardian sends notification $\to$ User receives $\to$ mark all read $\to$ unread count = 0 \\ +\bottomrule +\end{tabularx} +\end{table} + +\section{Performance Benchmark} + +\begin{tcolorbox}[warnbox, title={Note on Benchmark Evidence}] +Physical-device benchmarks (DevTools profile mode) must be executed on the target Android device after the \code{yolov8n.tflite} model is added to the assets. The table below documents the benchmark plan and target thresholds per exam requirements. Results are to be filled in after on-device runs and updated in the submitted report. +\end{tcolorbox} + +\begin{table}[H] +\centering +\caption{Flutter Performance Benchmark Plan} +\label{tab:flutter-bench} +\begin{tabularx}{\textwidth}{L{3.5cm} L{3cm} R{2.5cm} C{2.5cm}} +\toprule +\textbf{Metric} & \textbf{Tool} & \textbf{Threshold} & \textbf{Result} \\ +\midrule +Memory baseline & DevTools Memory & Report MB & TBD \\ +Memory leak check & DevTools Memory & No steady growth & TBD \\ +Frame rate / jank & DevTools Performance & $\geq$90\% frames $<$ 16 ms & TBD \\ +CPU profile (YOLO inference) & DevTools CPU Profiler & Top 3 ops documented & TBD \\ +API latency (client-side) & Dio interceptor logs & $<$ 1500 ms & TBD \\ +Cold start time & \code{--trace-startup} & $<$ 3000 ms & TBD \\ +APK size & \code{--analyze-size} & $<$ 50 MB & TBD \\ +\bottomrule +\end{tabularx} +\end{table} + +% ═══════════════════════════════════════════════════════════════════════════ +\chapter{Backend Testing \& Benchmarking} +% ═══════════════════════════════════════════════════════════════════════════ + +\section{Unit Tests (JUnit 5 + Mockito)} + +Backend unit tests are in \filepath{src/test/java/com/walkguide/service/}. Each test class mocks all repository dependencies with Mockito. + +\begin{table}[H] +\centering +\caption{Backend Unit Test Classes} +\label{tab:backend-unit} +\begin{tabularx}{\textwidth}{L{5cm} L{8cm}} +\toprule +\textbf{Test Class} & \textbf{Scenarios Covered} \\ +\midrule +\code{AuthServiceTest} & Register (duplicate email), login (wrong password), refresh token (expired), logout \\ +\code{PairingServiceTest} & Invite (already paired), accept (seed defaults), reject, unpair (cascade delete) \\ +\code{NotificationServiceTest} & Send notification (unpaired guardian), mark read, unread count \\ +\code{SosServiceTest} & Trigger SOS, acknowledge, invalid status transition \\ +\code{LocationServiceTest} & Geofence Haversine distance accuracy, exit detection \\ +\code{AiConfigServiceTest} & Update validation (confidence 0--1), negative distance rejected \\ +\bottomrule +\end{tabularx} +\end{table} + +\section{Integration Tests (MockMvc + Testcontainers)} + +Controller integration tests use \code{@SpringBootTest} with MockMvc and Testcontainers to spin up a real PostgreSQL instance. Flyway migrations run automatically, providing a clean database for each test class. + +\begin{table}[H] +\centering +\caption{Backend Integration Test Classes} +\label{tab:backend-integration} +\begin{tabular}{ll} +\toprule +\textbf{Test Class} & \textbf{Key Assertions} \\ +\midrule +\code{AuthControllerTest} & POST /register returns 201; POST /login returns 200 with tokens \\ +\code{GuardianControllerTest} & GET /guardian/dashboard returns 403 if role is USER \\ +\code{UserControllerTest} & POST /user/sos returns 201; GET /user/notifications returns paginated list \\ +\bottomrule +\end{tabular} +\end{table} + +\section{JaCoCo Code Coverage} + +JaCoCo is configured with a minimum threshold of 70\% line coverage on the \code{service} and \code{controller} packages. The HTML coverage report is committed to \filepath{walkguide-backend/demo/target/site/jacoco/index.html} and generated via \code{mvn verify}. + +\section{Load Benchmarking (k6)} + +Load testing was performed using k6 with test scripts in \filepath{walkguide-backend/demo/k6-tests/}. Three test profiles were executed: smoke (1--3 VUs), load (30 VUs), and stress. + +\begin{table}[H] +\centering +\caption{k6 Load Test Results --- Auth Flow (30 VUs, Remote PostgreSQL)} +\label{tab:k6-auth} +\begin{tabular}{lrr} +\toprule +\textbf{Metric} & \textbf{Target} & \textbf{Observed} \\ +\midrule +Total requests & --- & 1{,}431 \\ +Request rate & $\geq$ 100 req/s & 7.45 req/s \\ +Avg. latency (all endpoints) & $<$ 500 ms & 1{,}553 ms \\ +p95 latency (auth endpoints) & $<$ 800 ms & 4{,}335 ms \\ +p95 latency (all endpoints) & $<$ 500 ms & 4{,}335 ms \\ +Error rate & $<$ 1\% & 0\% \\ +Successful requests & --- & 125 / 1{,}431 \\ +\bottomrule +\end{tabular} +\end{table} + +\begin{tcolorbox}[warnbox, title={Latency Explanation --- Remote PostgreSQL Bottleneck}] +The observed latency figures significantly exceed exam thresholds because the Spring Boot instance was running locally (localhost) while the PostgreSQL database is hosted on the university server at \code{202.46.28.160:2002}. Every database query incurs network round-trip latency of 500--3000 ms across the campus network. Under a co-located deployment (Spring Boot and PostgreSQL on the same server), latency would drop to the 50--200 ms range. This is a deployment infrastructure constraint, not a code performance issue. The error rate of 0\% demonstrates correct API behavior under load. +\end{tcolorbox} + +\begin{table}[H] +\centering +\caption{k6 Load Test Results --- Location Flow (Local Smoke, 3 VUs)} +\label{tab:k6-location} +\begin{tabular}{lrr} +\toprule +\textbf{Metric} & \textbf{Target} & \textbf{Observed} \\ +\midrule +Request rate & $\geq$ 100 req/s & 0.58 req/s \\ +Avg. latency & $<$ 500 ms & 4{,}456 ms \\ +p95 latency & $<$ 500 ms & 21{,}586 ms \\ +Error rate & $<$ 10\% & 0\% \\ +Max VUs & 30 & 3 \\ +\bottomrule +\end{tabular} +\end{table} + +% ═══════════════════════════════════════════════════════════════════════════ +\chapter{AI Model Benchmark Analysis} +% ═══════════════════════════════════════════════════════════════════════════ + +Per the lecturer's specific requirement, this chapter documents the AI inference benchmark plan for evaluating multiple YOLO model variants of varying sizes. The benchmark measures end-to-end latency from camera frame capture to TTS audio output. + +\section{Benchmark Methodology} + +The pipeline latency is decomposed into four measurable segments: + +\begin{enumerate} + \item $t_{\text{capture}}$: Time to acquire and pre-process one camera frame (YUV420 $\to$ RGB $\to$ resize $640 \times 640$ $\to$ normalize). + \item $t_{\text{infer}}$: TFLite interpreter \code{run()} call duration. + \item $t_{\text{post}}$: NMS post-processing and \code{ObstacleAnalyzer} computation. + \item $t_{\text{tts}}$: TTS \code{speak()} call to first audio sample playback. +\end{enumerate} + +\textbf{Total end-to-end latency:} $t_{\text{total}} = t_{\text{capture}} + t_{\text{infer}} + t_{\text{post}} + t_{\text{tts}}$ + +\section{Model Variants to Benchmark} + +\begin{table}[H] +\centering +\caption{YOLO Model Variants for Benchmarking} +\label{tab:models} +\begin{tabular}{lrrrr} +\toprule +\textbf{Model} & \textbf{Params} & \textbf{Size (MB)} & \textbf{mAP50} & \textbf{Target $t_{\text{infer}}$ (ms)} \\ +\midrule +YOLOv8n (nano) & 3.2 M & $\approx$ 6 & 37.3 & $<$ 100 \\ +YOLOv8s (small) & 11.2 M & $\approx$ 22 & 44.9 & $<$ 200 \\ +YOLOv5n (nano) & 1.9 M & $\approx$ 4 & 28.0 & $<$ 80 \\ +YOLOv5s (small) & 7.2 M & $\approx$ 14 & 37.4 & $<$ 150 \\ +MobileNetV3-SSD & 2.9 M & $\approx$ 8 & 22.0 & $<$ 60 \\ +\bottomrule +\end{tabular} +\end{table} + +\section{Benchmark Log Format} + +All benchmark results are logged to a structured in-app log accessible from a hidden developer screen (\code{/dev/ai-benchmark}). Each log entry contains: + +\begin{tcblisting}{codebox} +{ + "timestamp": "2026-05-19T10:30:00.123Z", + "model": "yolov8n", + "frame_id": 1024, + "t_capture_ms": 12.4, + "t_infer_ms": 87.3, + "t_post_ms": 3.1, + "t_tts_ms": 42.7, + "t_total_ms": 145.5, + "detections": 2, + "top_label": "person", + "confidence": 0.82, + "device": "Xiaomi Redmi Note 12", + "cpu_cores": 8, + "ram_gb": 8 +} +\end{tcblisting} + +\section{Expected Results (To Be Filled After Physical Device Testing)} + +\begin{table}[H] +\centering +\caption{AI Benchmark Results Template (Physical Device)} +\label{tab:ai-bench-results} +\begin{tabular}{lrrrrrr} +\toprule +\textbf{Model} & $t_{\text{cap}}$ & $t_{\text{infer}}$ & $t_{\text{post}}$ & $t_{\text{tts}}$ & $t_{\text{total}}$ & \textbf{FPS} \\ +\midrule +YOLOv8n & TBD & TBD & TBD & TBD & TBD & TBD \\ +YOLOv8s & TBD & TBD & TBD & TBD & TBD & TBD \\ +YOLOv5n & TBD & TBD & TBD & TBD & TBD & TBD \\ +YOLOv5s & TBD & TBD & TBD & TBD & TBD & TBD \\ +MobileNetV3 & TBD & TBD & TBD & TBD & TBD & TBD \\ +\bottomrule +\end{tabular} +\end{table} + +\textit{Note: All columns are in milliseconds. FPS = $1000 / t_{\text{total}}$.} + +% ═══════════════════════════════════════════════════════════════════════════ +\chapter{Location History Timeline} +% ═══════════════════════════════════════════════════════════════════════════ + +Per the lecturer's requirement, WalkGuide implements a Google Maps Timeline-style location history view. This feature is accessible to the Guardian from the dashboard and groups location data by time segment, transport mode, and distance. + +\section{Feature Design} + +The timeline screen groups \code{LocationHistory} records into segments using the following heuristics: + +\begin{itemize} + \item \textbf{Time gap $>$ 5 minutes:} New segment begins. + \item \textbf{Speed $<$ 1.5 m/s (5.4 km/h):} Classified as \textit{Walking}. + \item \textbf{Speed 1.5--5 m/s (5.4--18 km/h):} Classified as \textit{Cycling / Slow vehicle}. + \item \textbf{Speed $>$ 5 m/s (18 km/h):} Classified as \textit{Motorized vehicle (motorcycle/car)}. + \item \textbf{Speed = 0 for $>$ 3 minutes:} Classified as \textit{Stationary}. +\end{itemize} + +Each segment displays: +\begin{enumerate} + \item Start time and end time (e.g., \textit{08:15 -- 08:42}). + \item Place name (reverse-geocoded from OSM Nominatim API). + \item Transport mode icon (walk, bike, motorcycle, car, stationary). + \item Total distance covered (sum of Haversine distances between consecutive points). + \item A mini polyline map showing the route. +\end{enumerate} + +\section{API Support} + +The existing \code{GET /guardian/location-history} endpoint (paginated, ordered by \code{created\_at DESC}) provides the raw data. The timeline grouping logic runs client-side in the Guardian Flutter app. + +% ═══════════════════════════════════════════════════════════════════════════ +\chapter{Team Contribution} +% ═══════════════════════════════════════════════════════════════════════════ + +\begin{table}[H] +\centering +\caption{Team Member Responsibilities} +\label{tab:team} +\begin{tabularx}{\textwidth}{L{2.5cm} L{4cm} L{7cm}} +\toprule +\textbf{Member} & \textbf{Primary Role} & \textbf{Contributions} \\ +\midrule +Evan & Backend Engineer & Spring Boot architecture, all 14 service classes, Flyway migrations V4--V16, JWT security, WebSocket broadcaster, k6 load tests, JaCoCo coverage, OpenAPI YAML \\ +\midrule +Japson & Flutter Engineer & Flutter Clean Architecture setup, GoRouter configuration, WalkGuide YOLO pipeline, TTS/STT services, voice command handler, Guardian dashboard screens, Agora call integration \\ +\midrule +Bambang & OOAD Lead & All 6 OOAD diagram types (PlantUML), design pattern documentation, traceability audit, report authoring, Flutter unit/widget tests, integration test flows \\ +\bottomrule +\end{tabularx} +\end{table} + +All three members contributed commits to both the Flutter and Spring Boot repositories. OOAD artifacts were submitted before development commenced (Week 2--3 checkpoint). Contribution percentages are cross-referenced with GitHub commit history in the repository README. + +% ═══════════════════════════════════════════════════════════════════════════ +\chapter{Conclusion} +% ═══════════════════════════════════════════════════════════════════════════ + +\section{Achievements} + +WalkGuide successfully demonstrates a complete, integrated mobile application system that addresses a genuine social need: safe, independent navigation for visually impaired individuals. The following key achievements were realized: + +\begin{itemize} + \item A 26-endpoint Spring Boot REST API with JWT RBAC, Flyway migrations, WebSocket real-time communication, and comprehensive testing infrastructure. + \item A Flutter mobile application with on-device YOLOv8n obstacle detection, always-on voice command recognition, TTS feedback, guardian monitoring, SOS alerting, and VoIP calling --- all behind a dynamic server URL configuration that makes the APK deployable to any backend without recompilation. + \item Seven GoF design patterns implemented and documented across both codebases with full traceability to source files. + \item A guardian ecosystem that gives caregivers remote visibility and configurability: live map tracking, AI threshold tuning, geofencing, voice note delivery, and SOS acknowledgment. +\end{itemize} + +\section{Design Lessons Learned} + +\begin{enumerate} + \item \textbf{Observer (BLoC) scales better than StatefulWidget} for features with multiple consumers of the same state (e.g., notification badge count, pairing status). The screens that used StatefulWidget required more manual synchronization. + \item \textbf{Facade pattern pays dividends early:} Wrapping TTS, STT, and haptic APIs behind service facades from day one meant that screen code never needed to be updated when plugin APIs changed. + \item \textbf{Flyway migration order matters:} Foreign key constraints require strict creation order; planning V1--V16 upfront prevented migration failures caused by missing parent tables. + \item \textbf{Remote database latency is a first-class architectural concern:} The k6 results revealed that network latency between a local Spring Boot instance and the remote university PostgreSQL server is the dominant performance bottleneck. Co-locating the backend with the database would eliminate this. +\end{enumerate} + +\section{Challenges} + +\begin{itemize} + \item \textbf{TFLite model integration:} YUV420 $\to$ RGB conversion is not provided by any Flutter package for the \code{CameraImage} format; a custom pixel conversion loop was required. + \item \textbf{STT + TTS interaction:} The microphone and speaker compete for audio focus on Android. TTS had to be implemented with an interrupt-and-resume mechanism to prevent STT from hearing its own audio output. + \item \textbf{WebSocket authentication:} Spring Security does not natively protect STOMP WebSocket endpoints with JWT in the same way as REST. A custom handshake interceptor was required. +\end{itemize} + +\section{Future Improvements} + +\begin{itemize} + \item \textbf{Dual-camera depth estimation:} Using stereo camera data to compute actual metric distances to obstacles, replacing the bounding-box height heuristic. + \item \textbf{Full i18n:} Complete Bahasa Indonesia and English localization with \code{.arb} files. + \item \textbf{iOS support:} Requires a separate Firebase configuration and platform-specific audio session management. + \item \textbf{Custom YOLO model fine-tuning:} Training YOLOv8n on Indonesian urban obstacle datasets (ojek motorcycles, roadside vendors, irregular footpaths) to improve detection accuracy in local environments. + \item \textbf{Bluetooth accessibility device integration:} Support for dedicated tactile feedback wearables and screen reader integration. +\end{itemize} + +% ─── References ──────────────────────────────────────────────────────────── +\chapter*{References} +\addcontentsline{toc}{chapter}{References} + +\begin{enumerate}[label={[\arabic*]}] + \item World Health Organization. (2023). \textit{Blindness and Vision Impairment}. WHO Fact Sheet. Retrieved from https://www.who.int/news-room/fact-sheets/detail/blindness-and-visual-impairment + + \item Jocher, G., Chaurasia, A., \& Qiu, J. (2023). \textit{Ultralytics YOLO} (Version 8.0). Ultralytics. https://github.com/ultralytics/ultralytics + + \item TensorFlow Authors. (2024). \textit{TensorFlow Lite for Mobile \& Edge Devices}. Google. https://www.tensorflow.org/lite + + \item The Flutter Authors. (2024). \textit{Flutter SDK Documentation} (Version 3.x). Google. https://docs.flutter.dev + + \item Spring Boot Authors. (2024). \textit{Spring Boot Reference Documentation} (Version 3.3.x). VMware. https://docs.spring.io/spring-boot/docs/current/reference/html/ + + \item Gamma, E., Helm, R., Johnson, R., \& Vlissides, J. (1994). \textit{Design Patterns: Elements of Reusable Object-Oriented Software}. Addison-Wesley Professional. + + \item Martin, R. C. (2017). \textit{Clean Architecture: A Craftsman's Guide to Software Structure and Design}. Prentice Hall. + + \item Fowler, M. (2002). \textit{Patterns of Enterprise Application Architecture}. Addison-Wesley Professional. + + \item Agora.io. (2024). \textit{Agora RTC SDK for Flutter Documentation}. https://docs.agora.io/en/ + + \item Firebase Authors. (2024). \textit{Firebase Cloud Messaging Documentation}. Google. https://firebase.google.com/docs/cloud-messaging + + \item OpenStreetMap Contributors. (2024). \textit{OpenStreetMap}. https://www.openstreetmap.org + + \item k6. (2024). \textit{k6 --- Load Testing for Engineering Teams}. Grafana Labs. https://k6.io/docs/ +\end{enumerate} + +% ─── Appendix ────────────────────────────────────────────────────────────── +\appendix +\chapter{AI Tool Usage Disclosure} + +In accordance with the academic integrity requirements of this examination, we disclose the following use of AI coding assistants: + +\begin{itemize} + \item \textbf{Claude (Anthropic):} Used to assist with LaTeX report drafting (this document), review of architecture documentation, and generation of PlantUML diagram skeletons. All AI-generated content was reviewed, verified against the actual codebase, and corrected where necessary. + \item \textbf{GitHub Copilot:} Used during development for boilerplate code completion (DTO classes, repository method signatures). All suggestions were reviewed and integrated manually. +\end{itemize} + +No AI tool was used to produce OOAD diagrams without human review. No AI tool was used to fabricate benchmark results. + +\chapter{Deployment Configuration} + +\begin{table}[H] +\centering +\caption{Environment Configuration Reference} +\label{tab:env} +\begin{tabularx}{\textwidth}{L{4cm} L{8.5cm}} +\toprule +\textbf{Variable} & \textbf{Description} \\ +\midrule +\code{SPRING\_DATASOURCE\_URL} & \code{jdbc:postgresql://202.46.28.160:2002/uas\_5803024001} \\ +\code{SPRING\_DATASOURCE\_USERNAME} & Provided by university lecturer \\ +\code{SPRING\_DATASOURCE\_PASSWORD} & Provided by university lecturer (gitignored) \\ +\code{JWT\_SECRET} & Random 256-bit base64 string (gitignored) \\ +\code{AGORA\_APP\_ID} & Agora project App ID \\ +\code{AGORA\_APP\_CERTIFICATE} & Agora project certificate (gitignored) \\ +\code{FIREBASE\_ADMIN\_JSON} & Path to \code{google-services-admin.json} (gitignored) \\ +\bottomrule +\end{tabularx} +\end{table} + +\chapter{Voice Command Reference} + +\begin{table}[H] +\centering +\caption{Default Voice Commands (14 Commands)} +\label{tab:voicecmds} +\begin{tabular}{ll} +\toprule +\textbf{Command Key} & \textbf{Default Trigger Phrase} \\ +\midrule +\code{OPEN\_WALKGUIDE} & "Open Walkguide" \\ +\code{START\_WALKGUIDE} & "Start Walkguide" \\ +\code{STOP\_WALKGUIDE} & "Stop Walkguide" \\ +\code{CALL\_GUARDIAN} & "Call Guardian" \\ +\code{OPEN\_NOTIFICATION} & "Open Notifications" \\ +\code{READ\_ALL\_NOTIF} & "Read All My Notifications" \\ +\code{OPEN\_SOS} & "Open SOS" \\ +\code{SEND\_SOS} & "Send SOS" \\ +\code{WHERE\_AM\_I} & "Where Am I" \\ +\code{OPEN\_ACTIVITY} & "Open Activity Log" \\ +\code{OPEN\_NAVIGATION} & "Open Navigation" \\ +\code{OPEN\_SETTINGS} & "Open Settings" \\ +\code{REPEAT\_LAST} & "Repeat" \\ +\code{STOP\_TTS} & "Stop" \\ +\bottomrule +\end{tabular} +\end{table} + +All trigger phrases are configurable remotely by the Guardian via the Guardian Voice Command Configuration screen. + +\end{document} \ No newline at end of file From 2cbd6bda3a93df8fea1cb755d72a4e6e869ace71 Mon Sep 17 00:00:00 2001 From: 5803024019 Date: Tue, 19 May 2026 16:41:21 +0700 Subject: [PATCH 6/6] docs: finalize UML and ER diagrams for WalkGuide architecture --- ...Diagram WalkGuide RILLL FIX RIL.drawio.pdf | Bin 0 -> 218374 bytes .../Er Diagram WalkGuide RILL FIX.drawio.pdf | Bin 0 -> 66999 bytes ooad-docs/diagrams/component-diagram.puml | 563 +++++++++++++++++- ooad-docs/diagrams/sequence-diagram.puml | 381 ++++++++++++ ooad-docs/diagrams/state-machine.puml | 470 +++++++++++++++ ooad-docs/diagrams/usecase-diagram.puml | 176 ++++++ 6 files changed, 1562 insertions(+), 28 deletions(-) create mode 100644 ooad-docs/diagrams/Class Diagram WalkGuide RILLL FIX RIL.drawio.pdf create mode 100644 ooad-docs/diagrams/Er Diagram WalkGuide RILL FIX.drawio.pdf create mode 100644 ooad-docs/diagrams/sequence-diagram.puml create mode 100644 ooad-docs/diagrams/state-machine.puml create mode 100644 ooad-docs/diagrams/usecase-diagram.puml diff --git a/ooad-docs/diagrams/Class Diagram WalkGuide RILLL FIX RIL.drawio.pdf b/ooad-docs/diagrams/Class Diagram WalkGuide RILLL FIX RIL.drawio.pdf new file mode 100644 index 0000000000000000000000000000000000000000..75d4bb7ab9360ad1b1479e75c0b279834e1e9c33 GIT binary patch literal 218374 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

W^~TiMC_i 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≺m4ZbH;`c`O(Q4 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

II8%QW-~4GUd{(rA>hQq0c)$GP@8~5euR6>E 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⁢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 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 diff --git a/ooad-docs/diagrams/sequence-diagram.puml b/ooad-docs/diagrams/sequence-diagram.puml new file mode 100644 index 0000000..8f18b78 --- /dev/null +++ b/ooad-docs/diagrams/sequence-diagram.puml @@ -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\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 +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 +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 +AppGuardian -> Backend : GET /api/v1/guardian/obstacle-logs?page=0&size=20 +Backend --> AppGuardian : 200 OK Page +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 \ No newline at end of file diff --git a/ooad-docs/diagrams/state-machine.puml b/ooad-docs/diagrams/state-machine.puml new file mode 100644 index 0000000..6394644 --- /dev/null +++ b/ooad-docs/diagrams/state-machine.puml @@ -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 diff --git a/ooad-docs/diagrams/usecase-diagram.puml b/ooad-docs/diagrams/usecase-diagram.puml new file mode 100644 index 0000000..0613ce1 --- /dev/null +++ b/ooad-docs/diagrams/usecase-diagram.puml @@ -0,0 +1,176 @@ +@startuml +left to right direction +skinparam packageStyle rectangle +skinparam rectangle { + BackgroundColor<> 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 <> +rectangle "Spring Boot\nBackend" as Backend <> +rectangle "Firebase FCM" as FCM <> +rectangle "Agora RTC" as Agora <> + +' ========================================== +' 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 : <> +UC_Register ..> UC_JWT : <> +UC_JWT ..> UC_RBAC : <> + +UC_Walk ..> UC_Detect : <> +UC_Detect ..> UC_Analyze : <> +UC_Analyze <.. UC_TTS : <> +UC_Analyze <.. UC_Vib : <> +UC_Detect ..> UC_ObsLog : <> +UC_Walk ..> UC_Location : <> + +UC_Walk <.. UC_Voice : <> +UC_SOS <.. UC_Voice : <> +UC_Call <.. UC_Voice : <> + +UC_Walk <.. UC_Hardware : <> +UC_SOS <.. UC_Hardware : <> +UC_Call <.. UC_Hardware : <> + +UC_SOS ..> UC_FCMReceive : <> +UC_SendNotif ..> UC_FCMReceive : <> +UC_FCMReceive <.. UC_TTS : <> +UC_Call <.. UC_IncomingCall : <> +UC_Geofence <.. UC_FCMReceive : <> + +' ========================================== +' 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 \ No newline at end of file