158 lines
4.6 KiB
Dart
158 lines
4.6 KiB
Dart
import 'dart:io' show Directory, File;
|
|
|
|
import 'package:connectivity_plus/connectivity_plus.dart';
|
|
import 'package:flutter/foundation.dart' show visibleForTesting;
|
|
import 'package:flutter/services.dart';
|
|
import 'package:package_info_plus/package_info_plus.dart';
|
|
import 'package:path_provider/path_provider.dart';
|
|
|
|
import '../src/rust/api.dart' as rust;
|
|
|
|
const String appSemverBaseline = 'v0.1.0';
|
|
const String _sileroVadAsset = 'assets/models/silero_vad.onnx';
|
|
|
|
typedef StorageDirectoryProvider = Future<Directory> Function();
|
|
typedef StorageInitializer = Future<void> Function(String dir);
|
|
|
|
Future<void>? _storageInitFuture;
|
|
Future<void>? _vadBootstrapFuture;
|
|
StorageDirectoryProvider _storageDirectoryProvider =
|
|
getApplicationSupportDirectory;
|
|
StorageDirectoryProvider _vadModelDirectoryProvider =
|
|
getApplicationSupportDirectory;
|
|
StorageInitializer _storageInitializer = _defaultStorageInitializer;
|
|
|
|
Future<void> _defaultStorageInitializer(String dir) {
|
|
return rust.initStorage(dir: dir);
|
|
}
|
|
|
|
Future<File> _copyBundledAssetToManagedDirectory({
|
|
required String assetPath,
|
|
required String fileName,
|
|
}) async {
|
|
final dir = await _vadModelDirectoryProvider();
|
|
final file = File('${dir.path}/$fileName');
|
|
final data = await rootBundle.load(assetPath);
|
|
final bytes = data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes);
|
|
|
|
if (await file.exists() && await file.length() == bytes.length) {
|
|
return file;
|
|
}
|
|
|
|
await file.writeAsBytes(bytes, flush: true);
|
|
return file;
|
|
}
|
|
|
|
Future<void> configureBundledVadModels() async {
|
|
final existing = _vadBootstrapFuture;
|
|
if (existing != null) {
|
|
await existing;
|
|
return;
|
|
}
|
|
|
|
final bootstrap = _configureBundledVadModelsImpl();
|
|
_vadBootstrapFuture = bootstrap;
|
|
try {
|
|
await bootstrap;
|
|
} catch (_) {
|
|
_vadBootstrapFuture = null;
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
Future<void> _configureBundledVadModelsImpl() async {
|
|
final silero = await _copyBundledAssetToManagedDirectory(
|
|
assetPath: _sileroVadAsset,
|
|
fileName: 'silero_vad.onnx',
|
|
);
|
|
await rust.setVadModelPath(path: silero.path);
|
|
}
|
|
|
|
/// Resolve the human-readable app version displayed in About.
|
|
///
|
|
/// The semver baseline is kept in code because iOS strips pre-release
|
|
/// identifiers from `CFBundleShortVersionString`; `package_info_plus`
|
|
/// still provides the platform build counter.
|
|
Future<String> resolveAppVersion({
|
|
String semverBaseline = appSemverBaseline,
|
|
}) async {
|
|
try {
|
|
final info = await PackageInfo.fromPlatform();
|
|
return appVersionFromBuildNumber(
|
|
semverBaseline: semverBaseline,
|
|
buildNumber: info.buildNumber,
|
|
);
|
|
} catch (_) {
|
|
return semverBaseline;
|
|
}
|
|
}
|
|
|
|
String appVersionFromBuildNumber({
|
|
required String semverBaseline,
|
|
required String buildNumber,
|
|
}) {
|
|
final build = buildNumber.isEmpty ? '' : '+$buildNumber';
|
|
return '$semverBaseline$build';
|
|
}
|
|
|
|
Future<void> wireStorage() async {
|
|
final existing = _storageInitFuture;
|
|
if (existing != null) {
|
|
await existing;
|
|
return;
|
|
}
|
|
|
|
final initFuture = _wireStorageImpl();
|
|
_storageInitFuture = initFuture;
|
|
await initFuture;
|
|
}
|
|
|
|
Future<void> _wireStorageImpl() async {
|
|
var initialized = false;
|
|
try {
|
|
final dir = await _storageDirectoryProvider();
|
|
await _storageInitializer(dir.path);
|
|
initialized = true;
|
|
} catch (_) {
|
|
// Best-effort; missing storage just means no identity persistence
|
|
// and no bookmark list this session.
|
|
} finally {
|
|
if (!initialized) {
|
|
_storageInitFuture = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
@visibleForTesting
|
|
void debugResetStorageBootstrap({
|
|
StorageDirectoryProvider? storageDirectoryProvider,
|
|
StorageDirectoryProvider? vadModelDirectoryProvider,
|
|
StorageInitializer? storageInitializer,
|
|
}) {
|
|
_storageInitFuture = null;
|
|
_vadBootstrapFuture = null;
|
|
_storageDirectoryProvider =
|
|
storageDirectoryProvider ?? getApplicationSupportDirectory;
|
|
_vadModelDirectoryProvider =
|
|
vadModelDirectoryProvider ?? getApplicationSupportDirectory;
|
|
_storageInitializer = storageInitializer ?? _defaultStorageInitializer;
|
|
}
|
|
|
|
rust.BridgeNetworkState _mapConnectivity(List<ConnectivityResult> results) {
|
|
if (results.isEmpty) return rust.BridgeNetworkState.unknown;
|
|
final allNone = results.every((r) => r == ConnectivityResult.none);
|
|
if (allNone) return rust.BridgeNetworkState.offline;
|
|
return rust.BridgeNetworkState.online;
|
|
}
|
|
|
|
Future<void> wireConnectivity() async {
|
|
final connectivity = Connectivity();
|
|
try {
|
|
final initial = await connectivity.checkConnectivity();
|
|
rust.setNetworkState(state: _mapConnectivity(initial));
|
|
} catch (_) {}
|
|
connectivity.onConnectivityChanged.listen((results) {
|
|
rust.setNetworkState(state: _mapConnectivity(results));
|
|
});
|
|
}
|