fixing a ton of sloppy mess...

This commit is contained in:
Wowieee4
2026-05-17 18:40:03 +07:00
parent f970f1bac8
commit d2608e0188
37 changed files with 3099 additions and 1795 deletions
@@ -10,23 +10,68 @@ class CallService {
CallService(this._apiClient);
Future<Map<String, dynamic>?> requestToken(String channelName) async {
final res = await _apiClient.dio.post('/shared/call/token', data: {'channelName': channelName});
Future<Map<String, dynamic>?> requestToken({required int receiverId}) async {
final res = await _apiClient.dio.post(
'/shared/call/token',
data: {'receiverId': receiverId},
);
final data = res.data['data'];
return data is Map<String, dynamic> ? data : null;
}
Future<void> notifyIncomingCall({required int receiverId, required String channelName}) async {
Future<int?> getPairedReceiverId() async {
final res = await _apiClient.dio.get('/pairing/status');
final data = res.data['data'];
if (data is! Map<String, dynamic>) return null;
final id = data['pairedWithId'];
return id is num ? id.toInt() : int.tryParse(id?.toString() ?? '');
}
Future<void> notifyIncomingCall({
required int receiverId,
required String channelName,
String? agoraToken,
int receiverUid = 0,
}) async {
await _apiClient.dio.post('/shared/call/notify', data: {
'receiverId': receiverId,
'channelName': channelName,
'agoraToken': agoraToken,
'receiverUid': receiverUid,
});
}
Future<bool> joinChannel({required String channelName, int uid = 0}) async {
Future<bool> callPairedUser({int uid = 0}) async {
final receiverId = await getPairedReceiverId();
if (receiverId == null) return false;
final tokenData = await requestToken(receiverId: receiverId);
final channelName = tokenData?['channelName']?.toString();
final token = tokenData?['token']?.toString();
if (channelName == null || channelName.isEmpty) return false;
final joined = await joinChannel(
channelName: channelName,
token: token,
uid: uid,
);
if (joined) {
await notifyIncomingCall(
receiverId: receiverId,
channelName: channelName,
agoraToken: token,
receiverUid: uid,
);
}
return joined;
}
Future<bool> joinChannel({
required String channelName,
String? token,
int uid = 0,
}) async {
try {
final tokenData = await requestToken(channelName);
final token = tokenData?['token']?.toString();
_engine ??= createAgoraRtcEngine();
await _engine!.initialize(const RtcEngineContext(appId: AppConstants.agoraAppId));
await _engine!.enableAudio();
@@ -148,7 +148,7 @@ class _ActivityLogScreenState extends State<ActivityLogScreen> {
onSelected: (_) {
setState(() => _applyFilter(f));
},
selectedColor: AppColors.primary.withOpacity(0.15),
selectedColor: AppColors.primary.withValues(alpha: 0.15),
checkmarkColor: AppColors.primary,
labelStyle: TextStyle(
color: selected ? AppColors.primary : AppColors.muted,
@@ -232,7 +232,7 @@ class _LogCard extends StatelessWidget {
width: 36,
height: 36,
decoration: BoxDecoration(
color: meta.color.withOpacity(0.12),
color: meta.color.withValues(alpha: 0.12),
shape: BoxShape.circle,
),
child: Icon(meta.icon, color: meta.color, size: 18),
@@ -91,7 +91,7 @@ class _SplashScreenState extends State<SplashScreen>
width: 100,
height: 100,
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.12),
color: Colors.white.withValues(alpha: 0.12),
borderRadius: BorderRadius.circular(28),
),
child: const Icon(
@@ -1,4 +1,4 @@
// ignore_for_file: use_build_context_synchronously
// ignore_for_file: use_build_context_synchronously, prefer_const_constructors
// lib/features/call/call_screen.dart
//
// CallScreen — user memanggil Guardian via Agora
@@ -34,8 +34,6 @@ class CallScreen extends StatefulWidget {
class _CallScreenState extends State<CallScreen>
with SingleTickerProviderStateMixin {
static const _channelName = 'walkguide-call';
_CallPhase _phase = _CallPhase.calling;
bool _muted = false;
bool _speakerOn = true;
@@ -61,8 +59,7 @@ class _CallScreenState extends State<CallScreen>
}
Future<void> _startCall() async {
final joined =
await sl<CallService>().joinChannel(channelName: _channelName);
final joined = await sl<CallService>().callPairedUser();
if (!mounted) return;
@@ -1,6 +1,5 @@
// ignore_for_file: use_build_context_synchronously, deprecated_member_use
// ignore_for_file: use_build_context_synchronously, deprecated_member_use, prefer_const_constructors
import 'dart:async';
import 'dart:math' as math;
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
@@ -36,7 +35,6 @@ class _GuardianDashboardScreenState extends State<GuardianDashboardScreen>
bool _loading = true;
String? _error;
String _guardianName = 'Guardian';
int _refreshCount = 0;
// ── Live location (WebSocket) ────────────────────────────────────────────────
LatLng? _liveLatLng;
@@ -156,7 +154,6 @@ class _GuardianDashboardScreenState extends State<GuardianDashboardScreen>
_liveLatLng = newLatLng;
}
_loading = false;
_refreshCount++;
});
// If SOS pending, start flash
@@ -672,7 +669,7 @@ class _GuardianDashboardScreenState extends State<GuardianDashboardScreen>
Border.all(color: const Color(0xFFE2E8F0), width: 1),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.04),
color: Colors.black.withValues(alpha: 0.04),
blurRadius: 12,
offset: const Offset(0, 2)),
],
@@ -708,7 +705,7 @@ class _GuardianDashboardScreenState extends State<GuardianDashboardScreen>
decoration: BoxDecoration(
shape: BoxShape.circle,
color: const Color(0xFF1A56DB)
.withOpacity(0.25 * _pulseAnim.value),
.withValues(alpha: 0.25 * _pulseAnim.value),
),
),
Container(
@@ -760,11 +757,11 @@ class _GuardianDashboardScreenState extends State<GuardianDashboardScreen>
padding: const EdgeInsets.symmetric(
horizontal: 10, vertical: 5),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.92),
color: Colors.white.withValues(alpha: 0.92),
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
color: Colors.black.withValues(alpha: 0.1),
blurRadius: 8),
],
),
@@ -801,7 +798,7 @@ class _GuardianDashboardScreenState extends State<GuardianDashboardScreen>
horizontal: 10, vertical: 5),
decoration: BoxDecoration(
color:
const Color(0xFF1A56DB).withOpacity(0.9),
const Color(0xFF1A56DB).withValues(alpha: 0.9),
borderRadius: BorderRadius.circular(20),
),
child: Text('Buka Peta',
@@ -823,11 +820,11 @@ class _GuardianDashboardScreenState extends State<GuardianDashboardScreen>
padding: const EdgeInsets.symmetric(
horizontal: 10, vertical: 6),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.92),
color: Colors.white.withValues(alpha: 0.92),
borderRadius: BorderRadius.circular(8),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.08),
color: Colors.black.withValues(alpha: 0.08),
blurRadius: 6),
],
),
@@ -859,7 +856,7 @@ class _GuardianDashboardScreenState extends State<GuardianDashboardScreen>
border: Border.all(color: const Color(0xFFE2E8F0)),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.04),
color: Colors.black.withValues(alpha: 0.04),
blurRadius: 12,
offset: const Offset(0, 2)),
],
@@ -872,7 +869,7 @@ class _GuardianDashboardScreenState extends State<GuardianDashboardScreen>
CircleAvatar(
radius: 20,
backgroundColor:
const Color(0xFF1A56DB).withOpacity(0.1),
const Color(0xFF1A56DB).withValues(alpha: 0.1),
child: Text(
d.userName.isNotEmpty
? d.userName[0].toUpperCase()
@@ -1051,7 +1048,7 @@ class _GuardianDashboardScreenState extends State<GuardianDashboardScreen>
border: Border.all(color: const Color(0xFFE2E8F0)),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.04),
color: Colors.black.withValues(alpha: 0.04),
blurRadius: 12,
offset: const Offset(0, 2)),
],
@@ -1357,7 +1354,7 @@ class _KpiCard extends StatelessWidget {
width: 1),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.03),
color: Colors.black.withValues(alpha: 0.03),
blurRadius: 8),
],
),
@@ -1421,7 +1418,7 @@ class _ActivityTile extends StatelessWidget {
width: 32,
height: 32,
decoration: BoxDecoration(
color: cfg.color.withOpacity(0.1),
color: cfg.color.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(8),
),
child: Icon(cfg.icon, size: 16, color: cfg.color),
@@ -1485,7 +1482,7 @@ class _QuickActionCard extends StatelessWidget {
width: 28,
height: 28,
decoration: BoxDecoration(
color: item.color.withOpacity(0.1),
color: item.color.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(7),
),
child: Icon(item.icon,
@@ -1527,7 +1524,7 @@ class _SosBadge extends StatelessWidget {
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: const Color(0xFFDC2626).withOpacity(0.4),
color: const Color(0xFFDC2626).withValues(alpha: 0.4),
blurRadius: 8,
spreadRadius: 1),
],
@@ -100,7 +100,7 @@ class _NavState extends ChangeNotifier {
'heading': pos.heading,
})
.timeout(const Duration(seconds: 5))
.catchError((_) => null);
.ignore();
}
// ── search Nominatim ─────────────────────────────────────────────────────
@@ -4,10 +4,8 @@
import 'dart:async';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:intl/intl.dart';
import '../../app/injection_container.dart';
import '../../core/constants/app_constants.dart';
import '../../core/network/api_client.dart';
import '../../core/services/tts_service.dart';
import '../../core/theme/app_colors.dart';
@@ -254,13 +252,13 @@ class _NotifCard extends StatelessWidget {
borderRadius: BorderRadius.circular(14),
border: Border.all(
color: unread
? AppColors.primary.withOpacity(0.3)
? AppColors.primary.withValues(alpha: 0.3)
: const Color(0xFFE2E8F0),
width: unread ? 1.5 : 1,
),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.04),
color: Colors.black.withValues(alpha: 0.04),
blurRadius: 8,
offset: const Offset(0, 2),
),
@@ -279,8 +277,8 @@ class _NotifCard extends StatelessWidget {
height: 36,
decoration: BoxDecoration(
color: isVoice
? AppColors.success.withOpacity(0.12)
: AppColors.primary.withOpacity(0.12),
? AppColors.success.withValues(alpha: 0.12)
: AppColors.primary.withValues(alpha: 0.12),
borderRadius: BorderRadius.circular(10),
),
child: Icon(
@@ -1,10 +1,9 @@
// ignore_for_file: use_build_context_synchronously, deprecated_member_use
// ignore_for_file: use_build_context_synchronously, deprecated_member_use, prefer_const_constructors
import 'dart:async';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import '../../app/injection_container.dart';
import '../../core/network/api_client.dart';
@@ -382,10 +381,8 @@ class _Page extends StatelessWidget {
final String title;
final String? subtitle;
final Widget child;
final List<Widget>? actions;
const _Page(
{required this.title, required this.child, this.subtitle, this.actions});
const _Page({required this.title, required this.child, this.subtitle});
@override
Widget build(BuildContext context) {
@@ -412,7 +409,6 @@ class _Page extends StatelessWidget {
],
),
),
...?actions,
],
),
const SizedBox(height: 16),
@@ -2168,10 +2168,10 @@ class _MapStatus extends StatelessWidget {
Widget build(BuildContext context) {
return DecoratedBox(
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.92),
color: Colors.white.withValues(alpha: 0.92),
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(color: Colors.black.withOpacity(0.12), blurRadius: 18)
BoxShadow(color: Colors.black.withValues(alpha: 0.12), blurRadius: 18)
],
),
child: Padding(
@@ -2251,7 +2251,7 @@ class _Pill extends StatelessWidget {
Widget build(BuildContext context) {
return DecoratedBox(
decoration: BoxDecoration(
color: color.withOpacity(0.16),
color: color.withValues(alpha: 0.16),
borderRadius: BorderRadius.circular(999),
border: Border.all(color: color)),
child: Padding(
@@ -1,4 +1,4 @@
// ignore_for_file: use_build_context_synchronously
// ignore_for_file: use_build_context_synchronously, prefer_const_constructors, deprecated_member_use
// lib/features/settings/user_settings_screen.dart
import 'dart:async';
@@ -146,10 +146,10 @@ class _UserSettingsScreenState extends State<UserSettingsScreen> {
Future<void> _logout() async {
await sl<SecureStorage>().clearAll();
context.read<AppCubit>().clearSession();
unawaited(_api
_api
.post('/auth/logout')
.timeout(const Duration(seconds: 3))
.catchError((_) => null));
.ignore();
if (mounted) context.go('/login');
}
@@ -1,12 +1,11 @@
// lib/features/sos/sos_screen.dart
// ignore_for_file: use_build_context_synchronously
// ignore_for_file: use_build_context_synchronously, prefer_const_constructors, curly_braces_in_flow_control_structures
import 'dart:async';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
import 'package:go_router/go_router.dart';
import '../../app/injection_container.dart';
import '../../core/network/api_client.dart';
@@ -326,7 +325,7 @@ class _SosButton extends StatelessWidget {
backgroundColor:
active ? const Color(0xFFB91C1C) : const Color(0xFFDC2626),
elevation: active ? 12 : 4,
shadowColor: const Color(0xFFDC2626).withOpacity(0.5),
shadowColor: const Color(0xFFDC2626).withValues(alpha: 0.5),
),
onPressed: onPressed,
child: Column(
@@ -363,7 +362,7 @@ class _SendingIndicator extends StatelessWidget {
dimension: 200,
child: DecoratedBox(
decoration: BoxDecoration(
color: const Color(0xFFDC2626).withOpacity(0.15),
color: const Color(0xFFDC2626).withValues(alpha: 0.15),
shape: BoxShape.circle,
border: Border.all(color: const Color(0xFFDC2626), width: 3),
),
@@ -505,7 +504,7 @@ class _SosEventTile extends StatelessWidget {
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: statusColor.withOpacity(0.1),
color: statusColor.withValues(alpha: 0.1),
shape: BoxShape.circle,
),
child: Icon(statusIcon, color: statusColor, size: 20),
@@ -521,7 +520,7 @@ class _SosEventTile extends StatelessWidget {
padding: const EdgeInsets.symmetric(
horizontal: 8, vertical: 2),
decoration: BoxDecoration(
color: statusColor.withOpacity(0.1),
color: statusColor.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(99),
),
child: Text(
@@ -466,7 +466,7 @@ class _Pill extends StatelessWidget {
Widget build(BuildContext context) {
return DecoratedBox(
decoration: BoxDecoration(
color: color.withOpacity(0.16),
color: color.withValues(alpha: 0.16),
borderRadius: BorderRadius.circular(999),
border: Border.all(color: color)),
child: Padding(
@@ -506,12 +506,10 @@ class _EmptyPanel extends StatelessWidget {
final IconData icon;
final String title;
final String message;
final Widget? action;
const _EmptyPanel(
{required this.icon,
required this.title,
required this.message,
this.action});
required this.message});
@override
Widget build(BuildContext context) {
@@ -533,10 +531,6 @@ class _EmptyPanel extends StatelessWidget {
const TextStyle(fontWeight: FontWeight.w800, fontSize: 18)),
const SizedBox(height: 6),
Text(message, textAlign: TextAlign.center),
if (action != null) ...[
const SizedBox(height: 12),
action!,
],
],
),
);
+9 -9
View File
@@ -800,7 +800,7 @@ packages:
source: sdk
version: "0.0.0"
intl:
dependency: transitive
dependency: "direct main"
description:
name: intl
sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5"
@@ -947,10 +947,10 @@ packages:
dependency: transitive
description:
name: meta
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
url: "https://pub.dev"
source: hosted
version: "1.17.0"
version: "1.16.0"
mgrs_dart:
dependency: transitive
description:
@@ -1592,26 +1592,26 @@ packages:
dependency: transitive
description:
name: test
sha256: "75906bf273541b676716d1ca7627a17e4c4070a3a16272b7a3dc7da3b9f3f6b7"
sha256: "65e29d831719be0591f7b3b1a32a3cda258ec98c58c7b25f7b84241bc31215bb"
url: "https://pub.dev"
source: hosted
version: "1.26.3"
version: "1.26.2"
test_api:
dependency: transitive
description:
name: test_api
sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00"
url: "https://pub.dev"
source: hosted
version: "0.7.7"
version: "0.7.6"
test_core:
dependency: transitive
description:
name: test_core
sha256: "0cc24b5ff94b38d2ae73e1eb43cc302b77964fbf67abad1e296025b78deb53d0"
sha256: "80bf5a02b60af04b09e14f6fe68b921aad119493e26e490deaca5993fef1b05a"
url: "https://pub.dev"
source: hosted
version: "0.6.12"
version: "0.6.11"
tflite_flutter:
dependency: "direct main"
description:
@@ -75,6 +75,7 @@ dependencies:
cupertino_icons: ^1.0.8
cached_network_image: ^3.3.1
shimmer: ^3.0.0
intl: ^0.20.2
# STOMP client untuk WebSocket
stomp_dart_client: ^2.1.0
@@ -1,5 +1,6 @@
// integration_test/app_flow_test.dart
//
// integration_test/app_flow_test.dart
// ignore_for_file: prefer_const_constructors, no_leading_underscores_for_local_identifiers
//
// Integration Tests (E2E) untuk WalkGuide App — 3 alur utama:
// Flow 1: Login → Dashboard → Logout
// Flow 2: Login → WalkGuide → Start → Stop → SOS
@@ -1,5 +1,6 @@
// test/unit/obstacle_analyzer_test.dart
//
// test/unit/obstacle_analyzer_test.dart
// ignore_for_file: prefer_const_declarations
//
// Unit test untuk ObstacleAnalyzer — logika AI direction & distance.
// Jalankan: flutter test test/unit/obstacle_analyzer_test.dart
//
@@ -51,14 +51,14 @@ class _FakeStt {
// VoiceCommandHandler (mirror dari project)
typedef CommandCallback = void Function(VoiceCommandKey key);
class VoiceCommandHandler {
class _VoiceCommandHandler {
final _FakeStt _stt;
final _FakeTts _tts;
List<VoiceCommand> _commands = [];
CommandCallback? onCommand;
VoiceCommandHandler(this._stt, this._tts);
_VoiceCommandHandler(this._stt, this._tts);
void loadCommands(List<VoiceCommand> commands) {
_commands = commands;
@@ -137,7 +137,7 @@ class VoiceCommandHandler {
// ---------- Tests ----------
void main() {
late VoiceCommandHandler handler;
late _VoiceCommandHandler handler;
late _FakeStt fakeStt;
late _FakeTts fakeTts;
@@ -146,7 +146,7 @@ void main() {
setUp(() {
fakeStt = _FakeStt();
fakeTts = _FakeTts();
handler = VoiceCommandHandler(fakeStt, fakeTts);
handler = _VoiceCommandHandler(fakeStt, fakeTts);
dispatchedCommands.clear();
handler.loadDefaultCommands();
handler.onCommand = dispatchedCommands.add;
@@ -151,8 +151,6 @@ class _StubLoginScreenState extends State<_StubLoginScreen> {
}
// Workaround untuk SizedBox(height(12)) yang error — gunakan helper
SizedBox _gap(double h) => SizedBox(height: h);
// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------
@@ -397,4 +395,4 @@ void main() {
});
});
});
}
}
@@ -1,5 +1,6 @@
// test/widget/manual_screen_test.dart
//
// test/widget/manual_screen_test.dart
// ignore_for_file: prefer_const_constructors
//
// Widget tests untuk ManualScreen — halaman panduan perintah suara.
// Jalankan: flutter test test/widget/manual_screen_test.dart
@@ -1,5 +1,6 @@
// test/widget/notification_screen_test.dart
//
// test/widget/notification_screen_test.dart
// ignore_for_file: prefer_const_constructors
//
// Widget tests untuk NotificationScreen — menampilkan notifikasi dari Guardian.
// Jalankan: flutter test test/widget/notification_screen_test.dart
@@ -167,7 +167,7 @@ class _StubSosScreenState extends State<_StubSosScreen> {
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: Colors.red.withOpacity(0.4),
color: Colors.red.withValues(alpha: 0.4),
blurRadius: 20,
spreadRadius: 4,
),
@@ -1,5 +1,6 @@
// test/widget/walk_guide_screen_test.dart
//
// test/widget/walk_guide_screen_test.dart
// ignore_for_file: prefer_const_constructors, prefer_const_literals_to_create_immutables
//
// Widget tests untuk WalkGuideScreen — layar utama navigasi tunanetra.
// Jalankan: flutter test test/widget/walk_guide_screen_test.dart
//
@@ -264,7 +265,7 @@ class _DetectionOverlay extends StatelessWidget {
margin: const EdgeInsets.symmetric(horizontal: 16),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: detection.isAlert ? Colors.red.withOpacity(0.85) : Colors.black87,
color: detection.isAlert ? Colors.red.withValues(alpha: 0.85) : Colors.black87,
borderRadius: BorderRadius.circular(8),
),
child: Text(
@@ -578,4 +579,4 @@ void main() {
});
});
});
}
}