POLISHING STUFF and Smarter AI, and alot i mean.. ALOT OF BUG FIX AND OPTIMIZE

This commit is contained in:
Wowieee4
2026-05-29 15:50:51 +07:00
parent 1110e5a42d
commit d2b3534dde
65 changed files with 4150 additions and 2974 deletions
@@ -586,14 +586,31 @@ const Set<String> _walkGuideObstacleLabels = {
'bicycle',
'car',
'motorcycle',
'truck',
'bus',
'train',
'truck',
'boat',
'traffic light',
'fire hydrant',
'stop sign',
'parking meter',
'bench',
'stairs',
'stair',
'pothole',
'curb',
'pole',
'bollard',
'cone',
'road cone',
'barrier',
'fence',
'door',
'trash can',
'signboard',
'crosswalk',
'sidewalk',
'wall',
'backpack',
'umbrella',
'handbag',
@@ -608,6 +625,7 @@ const Set<String> _walkGuideObstacleLabels = {
'bottle',
'cup',
'book',
'object',
};
const Map<int, String> _cocoObstacleLabels = {
@@ -1,7 +1,8 @@
import 'package:shared_preferences/shared_preferences.dart';
class AppConstants {
static const String defaultServerUrl = 'http://202.46.28.170:8080';
static const String defaultServerUrl = 'http://127.0.0.1:8080';
static const String publicServerUrl = 'http://202.46.28.170:8080';
static const String _serverUrlKey = 'server_base_url';
static const String _selectedYoloModelKey = 'selected_yolo_model';
@@ -10,7 +11,7 @@ class AppConstants {
final prefs = await SharedPreferences.getInstance();
final saved = prefs.getString(_serverUrlKey);
if (saved == null || saved.trim().isEmpty) {
return defaultServerUrl;
return null;
}
return saved;
}
@@ -38,7 +39,7 @@ class AppConstants {
static Future<void> clearServerUrl() async {
final prefs = await SharedPreferences.getInstance();
await prefs.setString(_serverUrlKey, defaultServerUrl);
await prefs.remove(_serverUrlKey);
}
static String buildApiUrl(String baseUrl) => '$baseUrl/api/v1';
@@ -61,7 +61,11 @@ class _AuthInterceptor extends Interceptor {
@override
void onError(DioException err, ErrorInterceptorHandler handler) async {
if (err.response?.statusCode == 401 && !_refreshing) {
final status = err.response?.statusCode;
final canRefresh = (status == 401 || status == 403) &&
!_refreshing &&
!err.requestOptions.path.startsWith('/auth/');
if (canRefresh) {
_refreshing = true;
try {
final refresh = await _storage.getRefreshToken();
@@ -87,14 +91,20 @@ class _AuthInterceptor extends Interceptor {
// Retry original request
err.requestOptions.headers['Authorization'] =
'Bearer ${data['accessToken']}';
final retryRes = await _dio.fetch(err.requestOptions);
_refreshing = false;
handler.resolve(retryRes);
try {
final retryRes = await _dio.fetch(err.requestOptions);
_refreshing = false;
handler.resolve(retryRes);
} on DioException catch (retryErr) {
_refreshing = false;
handler.next(retryErr);
}
return;
}
} catch (_) {}
} catch (_) {
await _storage.clearAll();
}
_refreshing = false;
await _storage.clearAll();
}
handler.next(err);
}
@@ -1,5 +1,6 @@
import 'dart:convert';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
@@ -7,6 +8,11 @@ import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import '../../app/router.dart';
import '../network/api_client.dart';
@pragma('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
await Firebase.initializeApp();
}
class FcmService {
final ApiClient _apiClient;
final FlutterLocalNotificationsPlugin _localNotifications =
@@ -17,6 +23,9 @@ class FcmService {
Future<void> init() async {
if (kIsWeb) return;
try {
await Firebase.initializeApp();
FirebaseMessaging.onBackgroundMessage(
_firebaseMessagingBackgroundHandler);
final messaging = FirebaseMessaging.instance;
await _localNotifications.initialize(
const InitializationSettings(
@@ -1,36 +1,107 @@
import 'package:flutter/services.dart';
import 'package:vibration/vibration.dart';
class HapticService {
Future<bool> get _hasVibrator async => Vibration.hasVibrator();
bool _enabled = true;
DateTime _lastObstacleVibrationAt = DateTime.fromMillisecondsSinceEpoch(0);
static const _obstacleCooldown = Duration(seconds: 3);
bool get enabled => _enabled;
void setEnabled(bool enabled) {
_enabled = enabled;
if (!enabled) {
Vibration.cancel().ignore();
}
}
Future<bool> get _hasVibrator async {
try {
final hasVibrator = await Vibration.hasVibrator();
return hasVibrator == true;
} catch (_) {
return false;
}
}
bool _canRunObstacleVibration() {
final now = DateTime.now();
if (now.difference(_lastObstacleVibrationAt) < _obstacleCooldown) {
return false;
}
_lastObstacleVibrationAt = now;
return true;
}
Future<void> _vibrate({
int? duration,
List<int>? pattern,
required Future<void> Function() fallback,
bool obstacle = false,
}) async {
if (!_enabled) return;
if (obstacle && !_canRunObstacleVibration()) return;
try {
if (await _hasVibrator) {
if (pattern != null) {
await Vibration.vibrate(pattern: pattern);
} else if (duration != null) {
await Vibration.vibrate(duration: duration);
}
return;
}
} catch (_) {
// Use Flutter's platform haptics below when the vibration plugin fails.
}
await fallback();
}
Future<void> obstacleVeryClose() async {
if (!await _hasVibrator) return;
Vibration.vibrate(pattern: [0, 500, 100, 500, 100, 500]);
await _vibrate(
pattern: [0, 500, 100, 500, 100, 500],
fallback: HapticFeedback.heavyImpact,
obstacle: true,
);
}
Future<void> obstacleClose() async {
if (!await _hasVibrator) return;
Vibration.vibrate(pattern: [0, 300, 100, 300]);
await _vibrate(
pattern: [0, 300, 100, 300],
fallback: HapticFeedback.mediumImpact,
obstacle: true,
);
}
Future<void> obstacleMedium() async {
if (!await _hasVibrator) return;
Vibration.vibrate(duration: 150);
await _vibrate(
duration: 150,
fallback: HapticFeedback.lightImpact,
obstacle: true,
);
}
Future<void> sosTriggered() async {
if (!await _hasVibrator) return;
Vibration.vibrate(pattern: [0, 1000, 200, 1000, 200, 1000]);
await _vibrate(
pattern: [0, 1000, 200, 1000, 200, 1000],
fallback: HapticFeedback.heavyImpact,
);
}
Future<void> callIncoming() async {
if (!await _hasVibrator) return;
Vibration.vibrate(pattern: [0, 500, 500, 500, 500, 500, 500, 500]);
await _vibrate(
pattern: [0, 500, 500, 500, 500, 500, 500, 500],
fallback: HapticFeedback.mediumImpact,
);
}
Future<void> success() async {
if (!await _hasVibrator) return;
Vibration.vibrate(duration: 80);
await _vibrate(
duration: 80,
fallback: HapticFeedback.selectionClick,
);
}
Future<void> stop() async => Vibration.cancel();
@@ -27,11 +27,14 @@ class HardwareShortcutBinding {
class HardwareShortcutListener {
final ApiClient _apiClient;
final Map<int, HardwareShortcutBinding> _bindings = {};
final Map<int, DateTime> _lastHandledAt = {};
bool _listening = false;
void Function(HardwareShortcutAction action)? _onAction;
void Function(int buttonCode, String buttonName)? _captureCallback;
static const Duration _repeatDebounce = Duration(milliseconds: 900);
HardwareShortcutListener(this._apiClient);
Future<void> startListening({
@@ -68,7 +71,8 @@ class HardwareShortcutListener {
);
}
void captureNextButton(void Function(int buttonCode, String buttonName) onCapture) {
void captureNextButton(
void Function(int buttonCode, String buttonName) onCapture) {
_captureCallback = onCapture;
}
@@ -88,6 +92,12 @@ class HardwareShortcutListener {
final binding = _bindings[code];
if (binding == null || !binding.enabled) return false;
final now = DateTime.now();
final lastHandled = _lastHandledAt[code];
if (lastHandled != null && now.difference(lastHandled) < _repeatDebounce) {
return true;
}
_lastHandledAt[code] = now;
_onAction?.call(binding.action);
return true;
}
@@ -103,7 +113,8 @@ HardwareShortcutBinding? _bindingFromJson(Map<String, dynamic> item) {
final action = _actionFromBackend(item['shortcutKey']?.toString());
final rawCode = item['buttonCode'];
final enabled = item['enabled'] != false;
final code = rawCode is int ? rawCode : int.tryParse(rawCode?.toString() ?? '');
final code =
rawCode is int ? rawCode : int.tryParse(rawCode?.toString() ?? '');
if (action == null || code == null || code <= 0) return null;
return HardwareShortcutBinding(
action: action,
@@ -6,17 +6,24 @@ class SttService {
final SpeechToText _stt = SpeechToText();
bool _available = false;
bool _listening = false;
bool _shouldListen = false;
bool _initializing = false;
Function(String)? onResult;
Future<bool> init() async {
if (_available) return true;
if (_initializing) return _available;
_initializing = true;
_available = await _stt.initialize(
onError: (e) => _onError(e),
onStatus: (s) => _onStatus(s),
);
_initializing = false;
return _available;
}
Future<void> startListening() async {
_shouldListen = true;
if (!_available || _listening) return;
_listening = true;
await _stt.listen(
@@ -25,14 +32,15 @@ class SttService {
onResult?.call(result.recognizedWords.toLowerCase().trim());
}
},
listenFor: const Duration(seconds: 10),
pauseFor: const Duration(seconds: 3),
listenFor: const Duration(seconds: 60),
pauseFor: const Duration(seconds: 8),
localeId: 'id_ID',
cancelOnError: false,
);
}
Future<void> stopListening() async {
_shouldListen = false;
_listening = false;
await _stt.stop();
}
@@ -42,15 +50,17 @@ class SttService {
void _onError(dynamic error) {
_listening = false;
// Auto-restart setelah error
Future.delayed(const Duration(seconds: 1), startListening);
if (_shouldListen) {
Future.delayed(const Duration(seconds: 2), startListening);
}
}
void _onStatus(String status) {
if (status == 'done' || status == 'notListening') {
_listening = false;
// Auto-restart agar selalu mendengarkan
Future.delayed(const Duration(milliseconds: 500), startListening);
if (_shouldListen) {
Future.delayed(const Duration(seconds: 2), startListening);
}
}
}
}
@@ -4,9 +4,15 @@ class TtsService {
final FlutterTts _tts = FlutterTts();
final List<String> _queue = [];
bool _speaking = false;
bool _initialized = false;
String _lastSpoken = '';
DateTime _lastQueuedAt = DateTime.fromMillisecondsSinceEpoch(0);
Future<void> init({String language = 'id-ID', double pitch = 1.0, double rate = 0.5}) async {
Future<void> init(
{String language = 'id-ID',
double pitch = 1.0,
double rate = 0.5}) async {
if (_initialized) return;
await _tts.setLanguage(language);
await _tts.setPitch(pitch);
await _tts.setSpeechRate(rate);
@@ -15,11 +21,25 @@ class TtsService {
_speaking = false;
_processQueue();
});
_initialized = true;
}
/// Tambah ke antrian - tidak memotong yg sedang bicara
void speak(String text) {
if (text.isEmpty) return;
if (!_initialized) {
init().then((_) => speak(text));
return;
}
final now = DateTime.now();
if (text == _lastSpoken &&
now.difference(_lastQueuedAt) < const Duration(milliseconds: 900)) {
return;
}
_lastQueuedAt = now;
if (_queue.length >= 3) {
_queue.removeAt(0);
}
_queue.add(text);
if (!_speaking) _processQueue();
}
@@ -27,6 +47,7 @@ class TtsService {
/// Potong TTS sekarang dan langsung ucapkan ini - untuk obstacle alert
Future<void> speakImmediate(String text) async {
if (text.isEmpty) return;
await init();
_queue.clear();
await _tts.stop();
_speaking = true;
@@ -43,9 +64,20 @@ class TtsService {
String get lastSpoken => _lastSpoken;
bool get isSpeaking => _speaking;
Future<void> setLanguage(String lang) async => _tts.setLanguage(lang);
Future<void> setPitch(double pitch) async => _tts.setPitch(pitch);
Future<void> setRate(double rate) async => _tts.setSpeechRate(rate);
Future<void> setLanguage(String lang) async {
await init(language: lang);
await _tts.setLanguage(lang);
}
Future<void> setPitch(double pitch) async {
await init();
await _tts.setPitch(pitch);
}
Future<void> setRate(double rate) async {
await init();
await _tts.setSpeechRate(rate);
}
void repeatLast() {
if (_lastSpoken.isNotEmpty) speak(_lastSpoken);
@@ -58,4 +90,4 @@ class TtsService {
_lastSpoken = text;
_tts.speak(text);
}
}
}
@@ -1,15 +1,28 @@
import 'package:flutter/material.dart';
class AppColors {
static const primary = Color(0xFF2563EB);
static const primaryDark = Color(0xFF0F3EA8);
static const accent = Color(0xFF0891B2);
static const primaryBlue = Color(0xFF4A90D9);
static const softBlueBg = Color(0xFFEBF4FF);
static const softPinkBg = Color(0xFFFFF0F5);
static const softYellowBg = Color(0xFFFFFBEB);
static const cardWhite = Color(0xFFFFFFFF);
static const textDark = Color(0xFF2D3748);
static const textMuted = Color(0xFFA0AEC0);
static const gold = Color(0xFFF6C90E);
static const gradientBlueStart = Color(0xFF6BB8FF);
static const gradientBlueEnd = Color(0xFF4A90D9);
static const gradientPinkStart = Color(0xFFFFB3C6);
static const gradientPinkEnd = Color(0xFFFF6B9D);
static const primary = primaryBlue;
static const primaryDark = Color(0xFF256FB8);
static const accent = Color(0xFFFF6B9D);
static const warning = Color(0xFFD97706);
static const danger = Color(0xFFDC2626);
static const success = Color(0xFF059669);
static const surface = Color(0xFFF7FAFC);
static const surfaceRaised = Color(0xFFFFFFFF);
static const text = Color(0xFF0F172A);
static const muted = Color(0xFF64748B);
static const surface = softBlueBg;
static const surfaceRaised = cardWhite;
static const text = textDark;
static const muted = textMuted;
static const border = Color(0xFFE2E8F0);
}
@@ -0,0 +1,74 @@
import 'package:flutter/material.dart';
import 'app_colors.dart';
class AppDecorations {
static const cardRadius = BorderRadius.all(Radius.circular(20));
static const pillRadius = BorderRadius.all(Radius.circular(50));
static const inputRadius = BorderRadius.all(Radius.circular(14));
static const iconCircleSize = 44.0;
static const cardShadow = [
BoxShadow(
color: Color(0x14000000),
blurRadius: 20,
offset: Offset(0, 4),
),
];
static const avatarShadow = [
BoxShadow(
color: Color(0x18000000),
blurRadius: 18,
offset: Offset(0, 6),
),
];
static const blueGradient = LinearGradient(
colors: [AppColors.gradientBlueStart, AppColors.gradientBlueEnd],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
);
static const pinkGradient = LinearGradient(
colors: [AppColors.gradientPinkStart, AppColors.gradientPinkEnd],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
);
static const cardShape = RoundedRectangleBorder(
borderRadius: cardRadius,
);
static BoxDecoration get card => const BoxDecoration(
color: AppColors.cardWhite,
borderRadius: cardRadius,
boxShadow: cardShadow,
);
static BoxDecoration pillGradient({
Gradient gradient = blueGradient,
}) =>
BoxDecoration(
gradient: gradient,
borderRadius: pillRadius,
);
static BoxDecoration iconCircle({
Color color = AppColors.softBlueBg,
}) =>
BoxDecoration(
color: color,
borderRadius: pillRadius,
);
static BoxDecoration avatar({
Color borderColor = Colors.white,
}) =>
BoxDecoration(
shape: BoxShape.circle,
color: AppColors.cardWhite,
border: Border.all(color: borderColor, width: 3),
boxShadow: avatarShadow,
);
}
@@ -0,0 +1,46 @@
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'app_colors.dart';
class AppTextStyles {
static TextStyle get heading => GoogleFonts.poppins(
fontSize: 24,
fontWeight: FontWeight.w700,
color: AppColors.textDark,
letterSpacing: 0,
height: 1.2,
);
static TextStyle get subheading => GoogleFonts.poppins(
fontSize: 18,
fontWeight: FontWeight.w600,
color: AppColors.textDark,
letterSpacing: 0,
height: 1.25,
);
static TextStyle get body => GoogleFonts.poppins(
fontSize: 14,
fontWeight: FontWeight.w400,
color: AppColors.textDark,
letterSpacing: 0,
height: 1.45,
);
static TextStyle get caption => GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w400,
color: AppColors.textMuted,
letterSpacing: 0,
height: 1.35,
);
static TextTheme get textTheme => GoogleFonts.poppinsTextTheme().copyWith(
headlineSmall: heading,
titleMedium: subheading,
bodyMedium: body,
bodySmall: caption,
labelLarge: body.copyWith(fontWeight: FontWeight.w700),
);
}