merged develop
This commit is contained in:
@@ -2,17 +2,15 @@ import 'dart:developer';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:hydrated_bloc/hydrated_bloc.dart';
|
||||
import 'package:localstore/localstore.dart';
|
||||
|
||||
import '../api/request_cache.dart';
|
||||
import '../api/cache_store.dart';
|
||||
import '../session/account_codec.dart';
|
||||
import 'hydrated_storage_bootstrap.dart';
|
||||
|
||||
/// Per-account local data: every account gets its own HydratedBloc box and
|
||||
/// Localstore cache collection, so switching back is instant and offline.
|
||||
/// Only the app settings live in the shared [global] box.
|
||||
/// request cache directory (see [CacheStore]), so switching back is instant
|
||||
/// and offline. Only the app settings live in the shared [global] box.
|
||||
abstract final class AccountStorage {
|
||||
static const _legacyCollection = 'MarianumMobile';
|
||||
static const _settingsKey = 'SettingsCubit';
|
||||
|
||||
static late String _baseDir;
|
||||
@@ -36,17 +34,13 @@ abstract final class AccountStorage {
|
||||
}
|
||||
}
|
||||
|
||||
/// Points HydratedBloc and the request cache at [account]'s data. Blocs
|
||||
/// keep the storage they were created with, so the per-account blocs have
|
||||
/// to be recreated afterwards.
|
||||
/// Points HydratedBloc at [account]'s data; the request cache follows the
|
||||
/// active account on its own. Blocs keep the storage they were created
|
||||
/// with, so the per-account blocs have to be recreated afterwards.
|
||||
static Future<void> activate(AccountEntry? account) async {
|
||||
if (account == null) {
|
||||
HydratedBloc.storage = InMemoryStorage();
|
||||
RequestCache.collection = _legacyCollection;
|
||||
return;
|
||||
}
|
||||
HydratedBloc.storage = await _storageFor(account.namespace);
|
||||
RequestCache.collection = cacheCollection(account.namespace);
|
||||
HydratedBloc.storage = account == null
|
||||
? InMemoryStorage()
|
||||
: await _storageFor(account.namespace);
|
||||
}
|
||||
|
||||
/// Removes all local data of the account with [namespace].
|
||||
@@ -62,15 +56,12 @@ abstract final class AccountStorage {
|
||||
final dir = Directory(_dirFor(namespace));
|
||||
if (dir.existsSync()) await dir.delete(recursive: true);
|
||||
}
|
||||
await Localstore.instance.collection(cacheCollection(namespace)).delete();
|
||||
} on Object catch (e, s) {
|
||||
log('Account storage delete failed: $e', stackTrace: s);
|
||||
}
|
||||
await CacheStore.instance.deleteNamespace(namespace);
|
||||
}
|
||||
|
||||
static String cacheCollection(String namespace) =>
|
||||
namespace.isEmpty ? _legacyCollection : '$_legacyCollection-$namespace';
|
||||
|
||||
static Future<Storage> _storageFor(String namespace) async =>
|
||||
_open[namespace] ??= await buildHydratedStorageWithFallback(
|
||||
_ensureDir(_dirFor(namespace)),
|
||||
|
||||
@@ -8,8 +8,22 @@ enum MarianumConnectEndpoint { live, beta, custom }
|
||||
@JsonSerializable()
|
||||
class DevToolsSettings {
|
||||
bool showPerformanceOverlay;
|
||||
bool checkerboardOffscreenLayers;
|
||||
bool checkerboardRasterCacheImages;
|
||||
|
||||
@JsonKey(defaultValue: false)
|
||||
bool showFrameStats;
|
||||
|
||||
@JsonKey(defaultValue: false)
|
||||
bool showMemoryStats;
|
||||
|
||||
@JsonKey(defaultValue: false)
|
||||
bool showSemanticsDebugger;
|
||||
|
||||
@JsonKey(defaultValue: false)
|
||||
bool slowAnimations;
|
||||
|
||||
/// Forced text scale factor; `0` follows the system setting.
|
||||
@JsonKey(defaultValue: 0)
|
||||
double textScaleOverride;
|
||||
|
||||
@JsonKey(defaultValue: MarianumConnectEndpoint.live)
|
||||
MarianumConnectEndpoint marianumConnectEndpoint;
|
||||
@@ -24,8 +38,11 @@ class DevToolsSettings {
|
||||
|
||||
DevToolsSettings({
|
||||
required this.showPerformanceOverlay,
|
||||
required this.checkerboardOffscreenLayers,
|
||||
required this.checkerboardRasterCacheImages,
|
||||
this.showFrameStats = false,
|
||||
this.showMemoryStats = false,
|
||||
this.showSemanticsDebugger = false,
|
||||
this.slowAnimations = false,
|
||||
this.textScaleOverride = 0,
|
||||
this.marianumConnectEndpoint = MarianumConnectEndpoint.live,
|
||||
this.marianumConnectCustomUrl = '',
|
||||
this.emergencyNoticeUrl = '',
|
||||
@@ -51,7 +68,8 @@ class DevToolsSettings {
|
||||
|
||||
/// Compiled-in source for the backend-independent emergency notice. Must live
|
||||
/// on infrastructure that is reachable even when MarianumConnect is down.
|
||||
static const String emergencyNoticeDefaultUrl = 'https://www.marianum-fulda.de/~aushang/marMobile.override';
|
||||
static const String emergencyNoticeDefaultUrl =
|
||||
'https://www.marianum-fulda.de/~aushang/marMobile.override';
|
||||
|
||||
String? resolveEmergencyNoticeUrl() =>
|
||||
sanitizeCustomUrl(emergencyNoticeUrl) ??
|
||||
|
||||
@@ -6,27 +6,33 @@ part of 'dev_tools_settings.dart';
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
DevToolsSettings _$DevToolsSettingsFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => DevToolsSettings(
|
||||
showPerformanceOverlay: json['showPerformanceOverlay'] as bool,
|
||||
checkerboardOffscreenLayers: json['checkerboardOffscreenLayers'] as bool,
|
||||
checkerboardRasterCacheImages: json['checkerboardRasterCacheImages'] as bool,
|
||||
marianumConnectEndpoint:
|
||||
$enumDecodeNullable(
|
||||
_$MarianumConnectEndpointEnumMap,
|
||||
json['marianumConnectEndpoint'],
|
||||
) ??
|
||||
MarianumConnectEndpoint.live,
|
||||
marianumConnectCustomUrl: json['marianumConnectCustomUrl'] as String? ?? '',
|
||||
emergencyNoticeUrl: json['emergencyNoticeUrl'] as String? ?? '',
|
||||
);
|
||||
DevToolsSettings _$DevToolsSettingsFromJson(Map<String, dynamic> json) =>
|
||||
DevToolsSettings(
|
||||
showPerformanceOverlay: json['showPerformanceOverlay'] as bool,
|
||||
showFrameStats: json['showFrameStats'] as bool? ?? false,
|
||||
showMemoryStats: json['showMemoryStats'] as bool? ?? false,
|
||||
showSemanticsDebugger: json['showSemanticsDebugger'] as bool? ?? false,
|
||||
slowAnimations: json['slowAnimations'] as bool? ?? false,
|
||||
textScaleOverride: (json['textScaleOverride'] as num?)?.toDouble() ?? 0,
|
||||
marianumConnectEndpoint:
|
||||
$enumDecodeNullable(
|
||||
_$MarianumConnectEndpointEnumMap,
|
||||
json['marianumConnectEndpoint'],
|
||||
) ??
|
||||
MarianumConnectEndpoint.live,
|
||||
marianumConnectCustomUrl:
|
||||
json['marianumConnectCustomUrl'] as String? ?? '',
|
||||
emergencyNoticeUrl: json['emergencyNoticeUrl'] as String? ?? '',
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$DevToolsSettingsToJson(DevToolsSettings instance) =>
|
||||
<String, dynamic>{
|
||||
'showPerformanceOverlay': instance.showPerformanceOverlay,
|
||||
'checkerboardOffscreenLayers': instance.checkerboardOffscreenLayers,
|
||||
'checkerboardRasterCacheImages': instance.checkerboardRasterCacheImages,
|
||||
'showFrameStats': instance.showFrameStats,
|
||||
'showMemoryStats': instance.showMemoryStats,
|
||||
'showSemanticsDebugger': instance.showSemanticsDebugger,
|
||||
'slowAnimations': instance.slowAnimations,
|
||||
'textScaleOverride': instance.textScaleOverride,
|
||||
'marianumConnectEndpoint':
|
||||
_$MarianumConnectEndpointEnumMap[instance.marianumConnectEndpoint]!,
|
||||
'marianumConnectCustomUrl': instance.marianumConnectCustomUrl,
|
||||
|
||||
@@ -9,19 +9,43 @@ import 'package:hydrated_bloc/hydrated_bloc.dart';
|
||||
Future<Storage> buildHydratedStorageWithFallback(String directoryPath) async {
|
||||
final directory = HydratedStorageDirectory(directoryPath);
|
||||
try {
|
||||
return await HydratedStorage.build(storageDirectory: directory);
|
||||
return await _build(directory);
|
||||
} catch (e, s) {
|
||||
log('HydratedStorage open failed, rebuilding: $e', stackTrace: s);
|
||||
}
|
||||
try {
|
||||
await _deleteHydratedBox(directoryPath);
|
||||
return await HydratedStorage.build(storageDirectory: directory);
|
||||
return await _build(directory);
|
||||
} catch (e, s) {
|
||||
log('HydratedStorage rebuild failed, using memory: $e', stackTrace: s);
|
||||
return InMemoryStorage();
|
||||
}
|
||||
}
|
||||
|
||||
final Expando<List<String> Function()> _keyReaders = Expando();
|
||||
|
||||
Future<Storage> _build(HydratedStorageDirectory directory) async {
|
||||
final storage = await HydratedStorage.build(storageDirectory: directory);
|
||||
// hydrated_bloc exposes no key listing; the Hive instance is the only way
|
||||
// in. Every build replaces the static one, so it is captured per storage.
|
||||
// ignore: invalid_use_of_visible_for_testing_member
|
||||
final hive = HydratedStorage.hive;
|
||||
_keyReaders[storage] = () =>
|
||||
hive.box<dynamic>('hydrated_box').keys.map((k) => '$k').toList();
|
||||
return storage;
|
||||
}
|
||||
|
||||
/// All keys persisted in [storage], sorted; `null` when they can't be
|
||||
/// enumerated.
|
||||
List<String>? hydratedStorageKeys(Storage storage) {
|
||||
if (storage is InMemoryStorage) return storage.keys.toList()..sort();
|
||||
try {
|
||||
return _keyReaders[storage]?.call()?..sort();
|
||||
} on Object {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _deleteHydratedBox(String directoryPath) async {
|
||||
final directory = Directory(directoryPath);
|
||||
if (!directory.existsSync()) return;
|
||||
@@ -37,6 +61,8 @@ Future<void> _deleteHydratedBox(String directoryPath) async {
|
||||
class InMemoryStorage implements Storage {
|
||||
final Map<String, dynamic> _values = {};
|
||||
|
||||
Iterable<String> get keys => _values.keys;
|
||||
|
||||
@override
|
||||
dynamic read(String key) => _values[key];
|
||||
|
||||
|
||||
@@ -10,12 +10,16 @@ class ModulesSettings {
|
||||
List<Modules> hiddenModules;
|
||||
bool autoFillBottomBar;
|
||||
int fixedBottomBarSlots;
|
||||
// Set once the teacher module defaults were applied, so a module the teacher
|
||||
// re-enables afterwards stays visible.
|
||||
bool teacherDefaultsApplied;
|
||||
|
||||
ModulesSettings({
|
||||
required this.moduleOrder,
|
||||
required this.hiddenModules,
|
||||
this.autoFillBottomBar = true,
|
||||
this.fixedBottomBarSlots = 3,
|
||||
this.teacherDefaultsApplied = false,
|
||||
});
|
||||
|
||||
factory ModulesSettings.fromJson(Map<String, dynamic> json) =>
|
||||
|
||||
@@ -16,6 +16,7 @@ ModulesSettings _$ModulesSettingsFromJson(Map<String, dynamic> json) =>
|
||||
.toList(),
|
||||
autoFillBottomBar: json['autoFillBottomBar'] as bool? ?? true,
|
||||
fixedBottomBarSlots: (json['fixedBottomBarSlots'] as num?)?.toInt() ?? 3,
|
||||
teacherDefaultsApplied: json['teacherDefaultsApplied'] as bool? ?? false,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$ModulesSettingsToJson(
|
||||
@@ -27,6 +28,7 @@ Map<String, dynamic> _$ModulesSettingsToJson(
|
||||
.toList(),
|
||||
'autoFillBottomBar': instance.autoFillBottomBar,
|
||||
'fixedBottomBarSlots': instance.fixedBottomBarSlots,
|
||||
'teacherDefaultsApplied': instance.teacherDefaultsApplied,
|
||||
};
|
||||
|
||||
const _$ModulesEnumMap = {
|
||||
|
||||
Reference in New Issue
Block a user