Update lot features

This commit is contained in:
2026-05-28 16:29:23 +07:00
parent 6272ece15d
commit 1110e5a42d
81 changed files with 4306 additions and 29813 deletions
@@ -1,13 +1,18 @@
import 'package:shared_preferences/shared_preferences.dart';
class AppConstants {
static const String defaultServerUrl = 'http://202.46.28.170:8080';
static const String _serverUrlKey = 'server_base_url';
static const String _selectedYoloModelKey = 'selected_yolo_model';
// Ambil base URL dari SharedPreferences
static Future<String?> getServerUrl() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getString(_serverUrlKey);
final saved = prefs.getString(_serverUrlKey);
if (saved == null || saved.trim().isEmpty) {
return defaultServerUrl;
}
return saved;
}
// Simpan URL setelah berhasil connect
@@ -33,7 +38,7 @@ class AppConstants {
static Future<void> clearServerUrl() async {
final prefs = await SharedPreferences.getInstance();
await prefs.remove(_serverUrlKey);
await prefs.setString(_serverUrlKey, defaultServerUrl);
}
static String buildApiUrl(String baseUrl) => '$baseUrl/api/v1';
@@ -44,14 +44,19 @@ class CallService {
required int receiverId,
required String channelName,
String? agoraToken,
String? agoraAppId,
int receiverUid = 0,
}) async {
await _apiClient.dio.post('/shared/call/notify', data: {
'receiverId': receiverId,
'channelName': channelName,
'agoraToken': agoraToken,
'receiverUid': receiverUid,
});
await _apiClient.dio.post(
'/shared/call/notify',
data: {
'receiverId': receiverId,
'channelName': channelName,
'agoraToken': agoraToken,
'agoraAppId': agoraAppId,
'receiverUid': receiverUid,
},
);
}
Future<Map<String, dynamic>?> startPairedCall({int uid = 0}) async {
@@ -61,12 +66,14 @@ class CallService {
final tokenData = await requestToken(receiverId: receiverId);
final channelName = tokenData?['channelName']?.toString();
final token = tokenData?['token']?.toString();
final appId = tokenData?['appId']?.toString();
final localUid = (tokenData?['uid'] as num?)?.toInt() ?? uid;
if (channelName == null || channelName.isEmpty) return null;
final joined = await joinChannel(
channelName: channelName,
token: token,
appId: appId,
uid: localUid,
);
if (!joined) return null;
@@ -75,6 +82,7 @@ class CallService {
receiverId: receiverId,
channelName: channelName,
agoraToken: token,
agoraAppId: appId,
receiverUid: 0,
);
@@ -94,10 +102,10 @@ class CallService {
required int callerId,
required String channelName,
}) async {
await _apiClient.dio.post('/shared/call/accept', data: {
'callerId': callerId.toString(),
'channelName': channelName,
});
await _apiClient.dio.post(
'/shared/call/accept',
data: {'callerId': callerId.toString(), 'channelName': channelName},
);
}
Future<Map<String, dynamic>?> getAcceptedCall() async {
@@ -126,21 +134,27 @@ class CallService {
Future<void> endCall(int? otherId, {String? channelName}) async {
if (otherId == null) return;
await _apiClient.dio.post('/shared/call/end', data: {
'otherId': otherId.toString(),
if (channelName != null && channelName.isNotEmpty)
'channelName': channelName,
});
await _apiClient.dio.post(
'/shared/call/end',
data: {
'otherId': otherId.toString(),
if (channelName != null && channelName.isNotEmpty)
'channelName': channelName,
},
);
}
Future<bool> joinChannel({
required String channelName,
String? token,
String? appId,
int uid = 0,
}) async {
final joinCompleter = Completer<bool>();
try {
if (AppConstants.agoraAppId.isEmpty) {
final resolvedAppId =
(appId != null && appId.isNotEmpty) ? appId : AppConstants.agoraAppId;
if (resolvedAppId.isEmpty) {
debugPrint('Agora join skipped: AGORA_APP_ID is not configured');
return false;
}
@@ -150,9 +164,7 @@ class CallService {
}
_engine ??= createAgoraRtcEngine();
await _engine!.initialize(
const RtcEngineContext(appId: AppConstants.agoraAppId),
);
await _engine!.initialize(RtcEngineContext(appId: resolvedAppId));
_engine!.registerEventHandler(
RtcEngineEventHandler(
onJoinChannelSuccess: (_, __) {
@@ -183,12 +195,17 @@ class CallService {
await _engine!.setClientRole(role: ClientRoleType.clientRoleBroadcaster);
await _engine!.setAudioProfile(
profile: AudioProfileType.audioProfileDefault,
scenario: AudioScenarioType.audioScenarioMeeting,
scenario: AudioScenarioType.audioScenarioDefault,
);
await _engine!.enableAudio();
await _engine!.enableLocalAudio(true);
await _engine!.muteAllRemoteAudioStreams(false);
await _engine!.muteLocalAudioStream(false);
await _engine!.enableAudioVolumeIndication(
interval: 500,
smooth: 3,
reportVad: true,
);
await _engine!.adjustRecordingSignalVolume(100);
await _engine!.adjustPlaybackSignalVolume(100);
await _engine!.setDefaultAudioRouteToSpeakerphone(true);
@@ -14,7 +14,7 @@ class IncomingCallPollingService {
void start() {
if (_timer != null) return;
_timer = Timer.periodic(const Duration(seconds: 2), (_) => _check());
_timer = Timer.periodic(const Duration(seconds: 1), (_) => _check());
unawaited(_check());
}
@@ -28,7 +28,7 @@ class IncomingCallPollingService {
try {
final res = await _apiClient.dio
.get('/shared/call/pending')
.timeout(const Duration(seconds: 3));
.timeout(const Duration(seconds: 2));
final data = res.data['data'];
if (data is! Map) return;
if (data['type']?.toString() != 'INCOMING_CALL') return;
@@ -42,4 +42,4 @@ class IncomingCallPollingService {
debugPrint('Incoming call polling skipped: $e');
}
}
}
}