Files
chanora/apps/chanora_flutter/lib/services/app_bootstrap.dart
T

153 lines
4.3 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;
StorageInitializer _storageInitializer = _defaultStorageInitializer;
Future<void> _defaultStorageInitializer(String dir) {
return rust.initStorage(dir: dir);
}
Future<File> _copyBundledAssetToDocuments({
required String assetPath,
required String fileName,
}) async {
final dir = await getApplicationDocumentsDirectory();
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 _copyBundledAssetToDocuments(
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,
StorageInitializer? storageInitializer,
}) {
_storageInitFuture = null;
_vadBootstrapFuture = null;
_storageDirectoryProvider =
storageDirectoryProvider ?? 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));
});
}