[UNTESTED] Fixes notifiers and adds some additional methods. Adds tests.

Changes:
// ArcaneEnvironment
breaking: context.read<ArcaneEnvironment>() -> ArcaneEnvironment.of(context)
breaking: context.read<ArcaneEnvironment>().state -> ArcaneEnvironment.of(context).environment;

// Feature flag service
added: reset()

// Logging service
added: registerInterface()
added: unregisterInterfaces()
added: unregisterAllInterfaces()

// ArcaneReactiveTheme
fixed: currentMode, dark, light now actually emit new values when changed
added: getters for lightTheme, darkTheme, and systemTheme
TODO: test systemTheme

Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
2025-04-04 17:08:06 +02:00
parent b129639f1f
commit ac82e93b9d
16 changed files with 1233 additions and 89 deletions
@@ -2,7 +2,6 @@ import "dart:async";
import "package:arcane_framework/arcane_framework.dart";
import "package:flutter/widgets.dart";
import "package:flutter_bloc/flutter_bloc.dart";
part "authentication_enums.dart";
part "authentication_interface.dart";
@@ -59,12 +58,15 @@ class ArcaneAuthenticationService extends ArcaneService {
Future<String?> get refreshToken =>
authInterface?.refreshToken ?? Future.value("");
AuthenticationStatus? _previousModeWhenSettingDebug;
/// Removes any registered `ArcaneAuthInterface` and resets all values to
/// default.
Future<void> reset() async {
_authInterface = null;
_notifier.value = AuthenticationStatus.unauthenticated;
_isSignedIn.value = isAuthenticated;
_previousModeWhenSettingDebug = null;
notifyListeners();
}
@@ -85,17 +87,18 @@ class ArcaneAuthenticationService extends ArcaneService {
BuildContext context, {
Future<void> Function()? onDebugModeSet,
}) async {
ArcaneEnvironment? environment;
try {
environment = context.read<ArcaneEnvironment>();
final Environment previousEnvironment = environment.state;
final ArcaneEnvironment arcaneEnvironment = ArcaneEnvironment.of(context);
final Environment previousEnvironment = arcaneEnvironment.environment;
if (previousEnvironment == Environment.debug) return;
environment.enableDebugMode();
_previousModeWhenSettingDebug = status;
final Environment currentEnvironment = environment.state;
arcaneEnvironment.enableDebugMode();
final Environment currentEnvironment = arcaneEnvironment.environment;
if (previousEnvironment == currentEnvironment) {
throw Exception("Unable to switch to debug mode.");
@@ -103,8 +106,8 @@ class ArcaneAuthenticationService extends ArcaneService {
_setStatus(AuthenticationStatus.debug);
if (onDebugModeSet != null) await onDebugModeSet();
} catch (_) {
throw Exception("No ArcaneEnvironment found in BuildContext");
} catch (e) {
rethrow;
}
}
@@ -115,23 +118,24 @@ class ArcaneAuthenticationService extends ArcaneService {
BuildContext context, {
Future<void> Function()? onDebugModeUnset,
}) async {
ArcaneEnvironment? environment;
try {
environment = context.read<ArcaneEnvironment>();
final Environment previousEnvironment = environment.state;
final ArcaneEnvironment arcaneEnvironment = ArcaneEnvironment.of(context);
final Environment previousEnvironment = arcaneEnvironment.environment;
if (previousEnvironment == Environment.normal) return;
environment.disableDebugMode();
arcaneEnvironment.disableDebugMode();
final Environment currentEnvironment = environment.state;
final Environment currentEnvironment = arcaneEnvironment.environment;
if (previousEnvironment == currentEnvironment) {
throw Exception("Unable to switch to normal mode.");
}
_setStatus(AuthenticationStatus.debug);
_setStatus(
_previousModeWhenSettingDebug ?? AuthenticationStatus.unauthenticated,
);
if (onDebugModeUnset != null) await onDebugModeUnset();
} catch (_) {
throw Exception("No ArcaneEnvironment found in BuildContext");
@@ -174,6 +178,8 @@ class ArcaneAuthenticationService extends ArcaneService {
if (onLoggedOut != null) await onLoggedOut();
}
_previousModeWhenSettingDebug = null;
return loggedOut;
}
@@ -129,4 +129,16 @@ class ArcaneFeatureFlags extends ArcaneService {
I._initialized = true;
notifyListeners();
}
/// Resets the feature flags to their initial state.
///
/// This method clears all enabled features, resets notification values,
/// marks the flags as uninitialized, and notifies listeners of the changes.
void reset() {
_enabledFeatures.clear();
_notifier.value.clear();
I._initialized = false;
notifyListeners();
}
}
+56 -8
View File
@@ -1,6 +1,7 @@
import "dart:async";
import "package:arcane_helper_utils/arcane_helper_utils.dart";
import "package:flutter/foundation.dart";
part "logging_enums.dart";
part "logging_interface.dart";
@@ -174,9 +175,27 @@ class ArcaneLogger {
}
}
/// Registers a [LoggingInterface] with the [ArcaneLogger]. Due to iOS app
/// tracking permissions, permission to track must first be checked for
/// and (optionally) granted before the interface is automatically initialized.
/// Registers a [LoggingInterface] with the [ArcaneLogger].
/// Due to iOS app tracking permissions, permission to track must first be
/// checked for and (optionally) granted before the interface is automatically
/// initialized.
///
/// Once your [LoggingInterface] has been registered and initialized, logs
/// will automatically be sent to the interface.
Future<ArcaneLogger> registerInterface(
LoggingInterface loggingInterface,
) async {
if (!initialized) await _init();
I._interfaces.add(loggingInterface);
return I;
}
/// Registers a `List` of [LoggingInterface] with the [ArcaneLogger].
/// Due to iOS app tracking permissions, permission to track must first be
/// checked for and (optionally) granted before the interface is automatically
/// initialized.
///
/// Once your [LoggingInterface] has been registered and initialized, logs
/// will automatically be sent to the interface.
@@ -192,15 +211,37 @@ class ArcaneLogger {
return I;
}
/// Unregisters a `List` of [LoggingInterface] from the [ArcaneLogger], if
/// they were previously registered.
Future<ArcaneLogger> unregisterInterfaces(
List<LoggingInterface> interfaces,
) async {
if (!initialized) await _init();
for (final LoggingInterface i in interfaces) {
I._interfaces.remove(i);
}
return I;
}
/// Unregisters all previously registered [LoggingInterface] from the
/// [ArcaneLogger], if any were previously registered.
Future<ArcaneLogger> unregisterAllInterfaces() async {
if (!initialized) await _init();
I._interfaces.clear();
return I;
}
/// Initializes all registered [LoggingInterface]s by calling their
/// [LoggingInterface.init] methods.
Future<ArcaneLogger> initializeInterfaces() async {
assert(
I._interfaces.isNotEmpty,
"No logging interfaces have been registered.",
);
if (!initialized) await _init();
if (I._interfaces.isEmptyOrNull) {
throw Exception("No logging interfaces have been registered.");
}
if (!I._initialized) await _init();
for (final LoggingInterface i in I._interfaces) {
if (!i.initialized) await i.init();
}
@@ -245,4 +286,11 @@ class ArcaneLogger {
/// Clears all persistent metadata.
void clearPersistentMetadata() => _additionalMetadata.clear();
@visibleForTesting
void reset() {
I._interfaces.clear();
I._initialized = false;
I._additionalMetadata.clear();
}
}
@@ -18,29 +18,30 @@ class ArcaneReactiveTheme extends ArcaneService {
ArcaneReactiveTheme._internal();
/// Whether the current theme is dark.
bool _isDark = false;
final ValueNotifier<ThemeMode> _systemThemeNotifier =
ValueNotifier(ThemeMode.light);
/// Returns the current theme mode based on `_isDark`.
///
/// If `_isDark` is true, it returns `ThemeMode.dark`, otherwise it returns `ThemeMode.light`.
ThemeMode get currentMode => _isDark ? ThemeMode.dark : ThemeMode.light;
ThemeMode get currentMode => I._systemThemeNotifier.value;
/// The `ThemeData` for the dark theme.
ThemeData _darkTheme = ThemeData.dark();
final ValueNotifier<ThemeData> _darkTheme = ValueNotifier(ThemeData.dark());
/// The `ThemeData` for the light theme.
ThemeData _lightTheme = ThemeData.light();
final ValueNotifier<ThemeData> _lightTheme = ValueNotifier(ThemeData.light());
/// Returns the current dark theme `ThemeData`.
ThemeData get dark => _darkTheme;
ThemeData get dark => _darkTheme.value;
ValueNotifier<ThemeData> get darkTheme => I._darkTheme;
/// Returns the current light theme `ThemeData`.
ThemeData get light => _lightTheme;
ThemeData get light => _lightTheme.value;
ValueNotifier<ThemeData> get lightTheme => I._lightTheme;
/// A listenable that notifies listeners when the syste theme mode changes.
ValueListenable<ThemeMode> get systemTheme =>
ValueNotifier<ThemeMode>(_isDark ? ThemeMode.dark : ThemeMode.light);
ValueListenable<ThemeMode> get systemTheme => I._systemThemeNotifier;
/// Switches the current theme between light and dark modes.
///
@@ -52,7 +53,9 @@ class ArcaneReactiveTheme extends ArcaneService {
/// ArcaneReactiveTheme.I.switchTheme();
/// ```
ArcaneReactiveTheme switchTheme() {
_isDark = !_isDark;
_systemThemeNotifier.value = _systemThemeNotifier.value == ThemeMode.light
? ThemeMode.dark
: ThemeMode.light;
notifyListeners();
return I;
@@ -87,7 +90,7 @@ class ArcaneReactiveTheme extends ArcaneService {
/// ArcaneReactiveTheme.I.setDarkTheme(customDarkTheme);
/// ```
ArcaneReactiveTheme setDarkTheme(ThemeData theme) {
_darkTheme = theme;
_darkTheme.value = theme;
notifyListeners();
return I;
}
@@ -102,8 +105,16 @@ class ArcaneReactiveTheme extends ArcaneService {
/// ArcaneReactiveTheme.I.setLightTheme(customLightTheme);
/// ```
ArcaneReactiveTheme setLightTheme(ThemeData theme) {
_lightTheme = theme;
_lightTheme.value = theme;
notifyListeners();
return I;
}
@visibleForTesting
void reset() {
_darkTheme.value = ThemeData.dark();
_lightTheme.value = ThemeData.light();
_systemThemeNotifier.value = ThemeMode.light;
notifyListeners();
}
}