im so tired man
This commit is contained in:
@@ -22,6 +22,9 @@ class AppConstants {
|
||||
if (!cleaned.startsWith('http://') && !cleaned.startsWith('https://')) {
|
||||
cleaned = 'http://$cleaned';
|
||||
}
|
||||
cleaned = cleaned
|
||||
.replaceFirst('://localhost', '://127.0.0.1')
|
||||
.replaceFirst('://0.0.0.0', '://127.0.0.1');
|
||||
while (cleaned.endsWith('/')) {
|
||||
cleaned = cleaned.substring(0, cleaned.length - 1);
|
||||
}
|
||||
@@ -61,7 +64,6 @@ class AppConstants {
|
||||
await prefs.setString(_selectedYoloModelKey, path);
|
||||
}
|
||||
|
||||
// Agora App ID tetap bisa dioverride saat build: --dart-define=AGORA_APP_ID=...
|
||||
static const String agoraAppId = String.fromEnvironment('AGORA_APP_ID',
|
||||
defaultValue: 'e36c2b6592e34cfda1f6ea6432a5e68d');
|
||||
// Set at build time with: --dart-define=AGORA_APP_ID=your_app_id
|
||||
static const String agoraAppId = String.fromEnvironment('AGORA_APP_ID');
|
||||
}
|
||||
|
||||
@@ -75,6 +75,8 @@ bool _looksTechnical(String message) {
|
||||
'duplicate key',
|
||||
'constraint',
|
||||
'sql [',
|
||||
'illegal base64',
|
||||
'base64 character',
|
||||
];
|
||||
return blocked.any(lower.contains);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,19 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
class AppStrings {
|
||||
final String localeCode;
|
||||
|
||||
const AppStrings(this.localeCode);
|
||||
|
||||
static const supportedLocales = ['id-ID', 'en-US'];
|
||||
static const supportedLocales = [
|
||||
Locale('id', 'ID'),
|
||||
Locale('en', 'US'),
|
||||
];
|
||||
|
||||
static AppStrings of(BuildContext context) {
|
||||
return Localizations.of<AppStrings>(context, AppStrings) ??
|
||||
const AppStrings('id-ID');
|
||||
}
|
||||
|
||||
String get walkGuideStarted => _pick(
|
||||
id: 'WalkGuide dimulai',
|
||||
@@ -29,3 +39,21 @@ class AppStrings {
|
||||
return localeCode == 'en-US' ? en : id;
|
||||
}
|
||||
}
|
||||
|
||||
class AppStringsDelegate extends LocalizationsDelegate<AppStrings> {
|
||||
const AppStringsDelegate();
|
||||
|
||||
@override
|
||||
bool isSupported(Locale locale) {
|
||||
return locale.languageCode == 'id' || locale.languageCode == 'en';
|
||||
}
|
||||
|
||||
@override
|
||||
Future<AppStrings> load(Locale locale) async {
|
||||
final code = locale.languageCode == 'en' ? 'en-US' : 'id-ID';
|
||||
return AppStrings(code);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldReload(covariant LocalizationsDelegate<AppStrings> old) => false;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import '../constants/app_constants.dart';
|
||||
import '../storage/secure_storage.dart';
|
||||
|
||||
@@ -24,8 +25,15 @@ class ApiClient {
|
||||
_dio.interceptors.addAll([
|
||||
_AuthInterceptor(_secureStorage, _dio),
|
||||
_ErrorInterceptor(),
|
||||
LogInterceptor(requestBody: true, responseBody: true),
|
||||
]);
|
||||
if (kDebugMode) {
|
||||
_dio.interceptors.add(LogInterceptor(
|
||||
requestBody: false,
|
||||
responseBody: false,
|
||||
requestHeader: false,
|
||||
responseHeader: false,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Dio get dio => _dio;
|
||||
@@ -42,7 +50,8 @@ class _AuthInterceptor extends Interceptor {
|
||||
_AuthInterceptor(this._storage, this._dio);
|
||||
|
||||
@override
|
||||
void onRequest(RequestOptions options, RequestInterceptorHandler handler) async {
|
||||
void onRequest(
|
||||
RequestOptions options, RequestInterceptorHandler handler) async {
|
||||
final token = await _storage.getAccessToken();
|
||||
if (token != null) {
|
||||
options.headers['Authorization'] = 'Bearer $token';
|
||||
|
||||
@@ -166,6 +166,11 @@ class CallService {
|
||||
debugPrint('Agora remote user offline: $remoteUid $reason');
|
||||
_onRemoteUserOffline?.call();
|
||||
},
|
||||
onRemoteAudioStateChanged: (_, remoteUid, state, reason, __) {
|
||||
debugPrint(
|
||||
'Agora remote audio state: uid=$remoteUid state=$state reason=$reason',
|
||||
);
|
||||
},
|
||||
onError: (type, msg) {
|
||||
debugPrint('Agora error: $type $msg');
|
||||
if (!joinCompleter.isCompleted) joinCompleter.complete(false);
|
||||
@@ -175,9 +180,18 @@ class CallService {
|
||||
await _engine!.setChannelProfile(
|
||||
ChannelProfileType.channelProfileCommunication,
|
||||
);
|
||||
await _engine!.setClientRole(role: ClientRoleType.clientRoleBroadcaster);
|
||||
await _engine!.setAudioProfile(
|
||||
profile: AudioProfileType.audioProfileDefault,
|
||||
scenario: AudioScenarioType.audioScenarioMeeting,
|
||||
);
|
||||
await _engine!.enableAudio();
|
||||
await _engine!.enableLocalAudio(true);
|
||||
await _engine!.muteAllRemoteAudioStreams(false);
|
||||
await _engine!.muteLocalAudioStream(false);
|
||||
await _engine!.adjustRecordingSignalVolume(100);
|
||||
await _engine!.adjustPlaybackSignalVolume(100);
|
||||
await _engine!.setDefaultAudioRouteToSpeakerphone(true);
|
||||
await _engine!.setEnableSpeakerphone(true);
|
||||
await _engine!.joinChannel(
|
||||
token: token ?? '',
|
||||
|
||||
@@ -9,7 +9,6 @@ import '../network/api_client.dart';
|
||||
|
||||
class FcmService {
|
||||
final ApiClient _apiClient;
|
||||
final FirebaseMessaging _messaging = FirebaseMessaging.instance;
|
||||
final FlutterLocalNotificationsPlugin _localNotifications =
|
||||
FlutterLocalNotificationsPlugin();
|
||||
|
||||
@@ -18,6 +17,7 @@ class FcmService {
|
||||
Future<void> init() async {
|
||||
if (kIsWeb) return;
|
||||
try {
|
||||
final messaging = FirebaseMessaging.instance;
|
||||
await _localNotifications.initialize(
|
||||
const InitializationSettings(
|
||||
android: AndroidInitializationSettings('@mipmap/ic_launcher'),
|
||||
@@ -31,10 +31,10 @@ class FcmService {
|
||||
} catch (_) {}
|
||||
},
|
||||
);
|
||||
await _messaging.requestPermission(alert: true, badge: true, sound: true);
|
||||
final token = await _messaging.getToken();
|
||||
await messaging.requestPermission(alert: true, badge: true, sound: true);
|
||||
final token = await messaging.getToken();
|
||||
if (token != null) await syncToken(token);
|
||||
FirebaseMessaging.instance.onTokenRefresh.listen(syncToken);
|
||||
messaging.onTokenRefresh.listen(syncToken);
|
||||
FirebaseMessaging.onMessage.listen((message) {
|
||||
debugPrint('FCM foreground: ${message.data}');
|
||||
_showLocalNotification(message);
|
||||
@@ -55,6 +55,10 @@ class FcmService {
|
||||
|
||||
Future<void> syncToken(String token) async {
|
||||
try {
|
||||
if (_apiClient.baseUrl == null) {
|
||||
debugPrint('FCM token sync skipped: server URL is not ready.');
|
||||
return;
|
||||
}
|
||||
await _apiClient.dio.put('/auth/fcm-token', data: {'fcmToken': token});
|
||||
} catch (e) {
|
||||
debugPrint('FCM token sync skipped: $e');
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:battery_plus/battery_plus.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
|
||||
@@ -10,6 +11,7 @@ import 'offline_queue_service.dart';
|
||||
class LocationReporterService {
|
||||
final ApiClient _apiClient;
|
||||
final OfflineQueueService _offlineQueue;
|
||||
final Battery _battery = Battery();
|
||||
Timer? _timer;
|
||||
|
||||
LocationReporterService(this._apiClient, this._offlineQueue);
|
||||
@@ -32,12 +34,14 @@ class LocationReporterService {
|
||||
try {
|
||||
await Geolocator.requestPermission();
|
||||
final position = await Geolocator.getCurrentPosition();
|
||||
final batteryLevel = await _readBatteryLevel();
|
||||
await _apiClient.dio.post('/user/location', data: {
|
||||
'lat': position.latitude,
|
||||
'lng': position.longitude,
|
||||
'accuracy': position.accuracy,
|
||||
'speed': position.speed,
|
||||
'heading': position.heading,
|
||||
if (batteryLevel != null) 'batteryLevel': batteryLevel,
|
||||
});
|
||||
} on DioException catch (_) {
|
||||
await _offlineQueue.enqueue(OfflineRequest(
|
||||
@@ -50,4 +54,12 @@ class LocationReporterService {
|
||||
// GPS permission can be unavailable during desktop/web testing.
|
||||
}
|
||||
}
|
||||
|
||||
Future<int?> _readBatteryLevel() async {
|
||||
try {
|
||||
return await _battery.batteryLevel;
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ class VoiceCommand {
|
||||
/// Callback yang dipanggil saat command terdeteksi
|
||||
/// Registered oleh router/screen yang relevan
|
||||
typedef CommandCallback = void Function(VoiceCommandKey key);
|
||||
typedef CommandRouter = void Function(String route);
|
||||
typedef CommandAction = void Function();
|
||||
|
||||
class VoiceCommandHandler {
|
||||
final SttService _stt;
|
||||
@@ -26,9 +28,19 @@ class VoiceCommandHandler {
|
||||
|
||||
List<VoiceCommand> _commands = [];
|
||||
CommandCallback? onCommand;
|
||||
CommandRouter? _router;
|
||||
final Map<VoiceCommandKey, CommandAction> _actions = {};
|
||||
|
||||
VoiceCommandHandler(this._stt, this._tts);
|
||||
|
||||
void registerRouter(CommandRouter router) {
|
||||
_router = router;
|
||||
}
|
||||
|
||||
void registerAction(VoiceCommandKey key, CommandAction action) {
|
||||
_actions[key] = action;
|
||||
}
|
||||
|
||||
void loadCommands(List<VoiceCommand> commands) {
|
||||
_commands = commands;
|
||||
_stt.onResult = _processText;
|
||||
@@ -66,9 +78,28 @@ class VoiceCommandHandler {
|
||||
}
|
||||
|
||||
void _handleCommand(VoiceCommandKey key) {
|
||||
_routeFor(key);
|
||||
_actions[key]?.call();
|
||||
onCommand?.call(key);
|
||||
// Built-in actions for TTS-only commands
|
||||
if (key == VoiceCommandKey.repeatLast) _tts.repeatLast();
|
||||
if (key == VoiceCommandKey.stopTts) _tts.stop();
|
||||
}
|
||||
|
||||
void _routeFor(VoiceCommandKey key) {
|
||||
final route = switch (key) {
|
||||
VoiceCommandKey.openWalkguide || VoiceCommandKey.startWalkguide =>
|
||||
'/user/walkguide',
|
||||
VoiceCommandKey.openNotification => '/user/notifications',
|
||||
VoiceCommandKey.openSos || VoiceCommandKey.sendSos => '/user/sos',
|
||||
VoiceCommandKey.openActivity => '/user/activity',
|
||||
VoiceCommandKey.openNavigation => '/user/navigation',
|
||||
VoiceCommandKey.openSettings => '/user/settings',
|
||||
VoiceCommandKey.callGuardian => '/call',
|
||||
_ => null,
|
||||
};
|
||||
if (route != null) {
|
||||
_router?.call(route);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user