Fix local dev config and ALOTTTT OFF FLUTTER, im tired boss..
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import '../network/api_client.dart';
|
||||
|
||||
enum HardwareShortcutAction {
|
||||
callGuardian,
|
||||
startWalkguide,
|
||||
stopWalkguide,
|
||||
sendSos,
|
||||
openNotification,
|
||||
}
|
||||
|
||||
class HardwareShortcutBinding {
|
||||
final HardwareShortcutAction action;
|
||||
final int buttonCode;
|
||||
final String buttonName;
|
||||
final bool enabled;
|
||||
|
||||
const HardwareShortcutBinding({
|
||||
required this.action,
|
||||
required this.buttonCode,
|
||||
required this.buttonName,
|
||||
required this.enabled,
|
||||
});
|
||||
}
|
||||
|
||||
class HardwareShortcutListener {
|
||||
final ApiClient _apiClient;
|
||||
final Map<int, HardwareShortcutBinding> _bindings = {};
|
||||
|
||||
bool _listening = false;
|
||||
void Function(HardwareShortcutAction action)? _onAction;
|
||||
void Function(int buttonCode, String buttonName)? _captureCallback;
|
||||
|
||||
HardwareShortcutListener(this._apiClient);
|
||||
|
||||
Future<void> startListening({
|
||||
required void Function(HardwareShortcutAction action) onAction,
|
||||
}) async {
|
||||
_onAction = onAction;
|
||||
if (!_listening) {
|
||||
HardwareKeyboard.instance.addHandler(_handleKeyEvent);
|
||||
_listening = true;
|
||||
}
|
||||
await loadFromBackend();
|
||||
}
|
||||
|
||||
void stopListening() {
|
||||
if (!_listening) return;
|
||||
HardwareKeyboard.instance.removeHandler(_handleKeyEvent);
|
||||
_listening = false;
|
||||
}
|
||||
|
||||
Future<void> loadFromBackend() async {
|
||||
final response = await _apiClient.dio.get('/user/shortcuts');
|
||||
final body = response.data;
|
||||
final data = body is Map ? body['data'] : body;
|
||||
if (data is! List) return;
|
||||
|
||||
_bindings
|
||||
..clear()
|
||||
..addEntries(
|
||||
data
|
||||
.whereType<Map>()
|
||||
.map((item) => _bindingFromJson(Map<String, dynamic>.from(item)))
|
||||
.whereType<HardwareShortcutBinding>()
|
||||
.map((binding) => MapEntry(binding.buttonCode, binding)),
|
||||
);
|
||||
}
|
||||
|
||||
void captureNextButton(void Function(int buttonCode, String buttonName) onCapture) {
|
||||
_captureCallback = onCapture;
|
||||
}
|
||||
|
||||
bool _handleKeyEvent(KeyEvent event) {
|
||||
if (event is! KeyDownEvent) return false;
|
||||
final code = _buttonCode(event.logicalKey);
|
||||
final name = event.logicalKey.keyLabel.isNotEmpty
|
||||
? event.logicalKey.keyLabel
|
||||
: event.logicalKey.debugName ?? 'Button $code';
|
||||
|
||||
final capture = _captureCallback;
|
||||
if (capture != null) {
|
||||
_captureCallback = null;
|
||||
capture(code, name);
|
||||
return true;
|
||||
}
|
||||
|
||||
final binding = _bindings[code];
|
||||
if (binding == null || !binding.enabled) return false;
|
||||
_onAction?.call(binding.action);
|
||||
return true;
|
||||
}
|
||||
|
||||
int _buttonCode(LogicalKeyboardKey key) {
|
||||
if (key == LogicalKeyboardKey.audioVolumeUp) return 24;
|
||||
if (key == LogicalKeyboardKey.audioVolumeDown) return 25;
|
||||
return key.keyId & 0xFFFFFFFF;
|
||||
}
|
||||
}
|
||||
|
||||
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() ?? '');
|
||||
if (action == null || code == null || code <= 0) return null;
|
||||
return HardwareShortcutBinding(
|
||||
action: action,
|
||||
buttonCode: code,
|
||||
buttonName: item['buttonName']?.toString() ?? 'Button $code',
|
||||
enabled: enabled,
|
||||
);
|
||||
}
|
||||
|
||||
HardwareShortcutAction? _actionFromBackend(String? key) {
|
||||
switch (key) {
|
||||
case 'CALL_GUARDIAN':
|
||||
return HardwareShortcutAction.callGuardian;
|
||||
case 'START_WALKGUIDE':
|
||||
return HardwareShortcutAction.startWalkguide;
|
||||
case 'STOP_WALKGUIDE':
|
||||
return HardwareShortcutAction.stopWalkguide;
|
||||
case 'SEND_SOS':
|
||||
return HardwareShortcutAction.sendSos;
|
||||
case 'OPEN_NOTIFICATION':
|
||||
return HardwareShortcutAction.openNotification;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,5 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import '../network/api_client.dart';
|
||||
import '../storage/local_database.dart';
|
||||
|
||||
class OfflineRequest {
|
||||
final String method;
|
||||
@@ -33,26 +30,35 @@ class OfflineRequest {
|
||||
}
|
||||
|
||||
class OfflineQueueService {
|
||||
static const _key = 'offline_request_queue';
|
||||
final LocalDatabase _database;
|
||||
|
||||
OfflineQueueService(this._database);
|
||||
|
||||
Future<void> enqueue(OfflineRequest request) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final queue = await readAll();
|
||||
queue.add(request);
|
||||
await prefs.setString(_key, jsonEncode(queue.map((e) => e.toJson()).toList()));
|
||||
await _database.offlineRequests.insert(OfflineRequestRecord(
|
||||
method: request.method,
|
||||
path: request.path,
|
||||
body: request.body,
|
||||
createdAt: request.createdAt,
|
||||
));
|
||||
}
|
||||
|
||||
Future<List<OfflineRequest>> readAll() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final raw = prefs.getString(_key);
|
||||
if (raw == null || raw.isEmpty) return [];
|
||||
final decoded = jsonDecode(raw) as List<dynamic>;
|
||||
return decoded.map((e) => OfflineRequest.fromJson(Map<String, dynamic>.from(e as Map))).toList();
|
||||
final records = await _database.offlineRequests.getAll();
|
||||
return records
|
||||
.map(
|
||||
(record) => OfflineRequest(
|
||||
method: record.method,
|
||||
path: record.path,
|
||||
body: record.body,
|
||||
createdAt: record.createdAt,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
}
|
||||
|
||||
Future<void> clear() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.remove(_key);
|
||||
await _database.offlineRequests.clear();
|
||||
}
|
||||
|
||||
Future<int> syncPending(ApiClient apiClient) async {
|
||||
@@ -79,11 +85,21 @@ class OfflineQueueService {
|
||||
}
|
||||
}
|
||||
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
if (remaining.isEmpty) {
|
||||
await prefs.remove(_key);
|
||||
await clear();
|
||||
} else {
|
||||
await prefs.setString(_key, jsonEncode(remaining.map((e) => e.toJson()).toList()));
|
||||
await _database.offlineRequests.replaceAll(
|
||||
remaining
|
||||
.map(
|
||||
(request) => OfflineRequestRecord(
|
||||
method: request.method,
|
||||
path: request.path,
|
||||
body: request.body,
|
||||
createdAt: request.createdAt,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
return synced;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user