Fuente publicada
Descargar fuente ZIP
CH-J SM Mobile
Busque directorios y archivos para obtener una versión de aplicación específica.
CH-J / SM Mobile
134 archivos
sm-mobile-20261016T050549Z-3678f2a05fa9
Añadido: · Construido:
1
import 'dart:async';2
import 'dart:convert';3
import 'package:flutter/material.dart';4
import 'package:local_auth/local_auth.dart';5
import '../services/legal.dart';6
import '../services/secure_store.dart';7
import '../services/ssh_service.dart';8
import '../services/vault.dart';9
import 'failure.dart';11
class AppController extends ChangeNotifier with WidgetsBindingObserver {12
final SecureStore store;13
late final Vault vault = Vault(store);14
late final Legal legal = Legal(store);15
late final SshService ssh = SshService(vault);16
bool ready = false, accepted = false, initialized = false, foreground = true;17
bool biometric = false, lockBackground = true, authenticating = false;18
bool systemDialog = false;19
String? startupError;20
Locale? locale;21
ThemeMode theme = ThemeMode.system;22
int idleMinutes = 5, monitorSeconds = 10;23
DateTime _lastActivity = DateTime.now();24
Timer? _timer;25
void Function()? onLocked;26
AppController(this.store);27
Future<void> initialize() async {28
WidgetsBinding.instance.addObserver(this);29
try {30
final prefs = jsonDecode(await store.read('preferences') ?? '{}');31
if (['cs', 'de', 'en'].contains(prefs['language'])) {32
locale = Locale(prefs['language']);33
}34
theme = ThemeMode.values.firstWhere(35
(t) => t.name == prefs['theme'],36
orElse: () => ThemeMode.system,37
);38
biometric = prefs['biometric'] == true;39
lockBackground = prefs['lockBackground'] != false;40
idleMinutes = [1, 5, 15].contains(prefs['idleMinutes'])41
? prefs['idleMinutes']42
: 5;43
monitorSeconds = [5, 10, 30, 60].contains(prefs['monitorSeconds'])44
? prefs['monitorSeconds']45
: 10;46
accepted = await legal.load();47
initialized = await vault.exists();48
} catch (e) {49
startupError = e is Failure ? e.code : 'storageError';50
}51
ready = true;52
_timer = Timer.periodic(const Duration(seconds: 1), (_) {53
if (vault.unlocked &&54
DateTime.now().difference(_lastActivity) >=55
Duration(minutes: idleMinutes)) {56
lock();57
}58
});59
notifyListeners();60
}62
void activity() {63
_lastActivity = DateTime.now();64
}66
void changed() {67
notifyListeners();68
}70
Future<T> withSystemDialog<T>(Future<T> Function() action) async {71
systemDialog = true;72
try {73
return await action();74
} finally {75
systemDialog = false;76
activity();77
}78
}80
Future<void> saveSettings() async {81
await store.write(82
'preferences',83
jsonEncode({84
'language': locale?.languageCode,85
'theme': theme.name,86
'biometric': biometric,87
'lockBackground': lockBackground,88
'idleMinutes': idleMinutes,89
'monitorSeconds': monitorSeconds,90
}),91
);92
notifyListeners();93
}95
Future<void> acceptLicense() async {96
await legal.accept(guidanceDisplayed: true);97
accepted = true;98
notifyListeners();99
}101
Future<void> openVault(String password, {bool create = false}) async {102
if (!accepted) throw const Failure('licenseError');103
if (create) {104
try {105
await vault.create(password);106
} finally {107
initialized = await vault.exists();108
notifyListeners();109
}110
} else {111
await vault.unlock(password);112
}113
activity();114
notifyListeners();115
}117
Future<void> biometricUnlock(String reason) async {118
if (!accepted) throw const Failure('licenseError');119
authenticating = true;120
try {121
await vault.unlockBiometric(DeviceStore.biometric(reason));122
activity();123
} finally {124
authenticating = false;125
notifyListeners();126
}127
}129
Future<void> setBiometric(bool enable, String reason) async {130
authenticating = true;131
try {132
if (enable) {133
if (!await LocalAuthentication().isDeviceSupported()) {134
throw const Failure('biometricUnavailable');135
}136
// This probe is only availability/UI. The key itself is gated by the OS.137
if (!await LocalAuthentication().authenticate(138
localizedReason: reason,139
)) {140
throw const Failure('unlockFailed');141
}142
await vault.enableBiometric(DeviceStore.biometric(reason));143
} else {144
await DeviceStore.biometric(reason).delete('key');145
}146
biometric = enable;147
await saveSettings();148
} finally {149
authenticating = false;150
notifyListeners();151
}152
}154
void lock() {155
ssh.disconnect(clear: true);156
vault.lock();157
onLocked?.call();158
notifyListeners();159
}161
@override162
void didChangeAppLifecycleState(AppLifecycleState state) {163
foreground = state == AppLifecycleState.resumed;164
if (state == AppLifecycleState.resumed) {165
if (vault.unlocked &&166
DateTime.now().difference(_lastActivity) >=167
Duration(minutes: idleMinutes)) {168
lock();169
}170
} else if ((state == AppLifecycleState.hidden ||171
state == AppLifecycleState.paused ||172
state == AppLifecycleState.detached) &&173
!systemDialog &&174
lockBackground) {175
lock();176
}177
notifyListeners();178
}180
@override181
void dispose() {182
_timer?.cancel();183
WidgetsBinding.instance.removeObserver(this);184
ssh.dispose();185
vault.lock();186
super.dispose();187
}188
}SHA-256
c6ab83939bf0be12eb1f819a4932fb0dca5cb419ceda11c3e9d26c8010fe9f9aCH-J Proprietary Software License 1.14 · La fuente se proporciona en el CH-J Proprietary Software License 1.14. Su disponibilidad no cambia los términos de la licencia ni otorga derechos adicionales.
Archivo SHA-256
c486a807e4828998b9c57ab0f2e760f7980b31965fe118e89d3e1a5a2b65be65