Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
2024-09-19 13:34:51 +02:00
commit c7ed42b1c7
16 changed files with 1390 additions and 0 deletions
+55
View File
@@ -0,0 +1,55 @@
import "package:flutter/foundation.dart";
import "package:flutter_dotenv/flutter_dotenv.dart";
class AppEnv {
/// Fetches the environment variable value for the given [EnvVar]. If the
/// value is not set, defaults to an empty string.
static String valueOf(EnvVar val) {
return dotenv.maybeGet(val.key) ?? "";
}
/// Returns [true] if all of the [EnvVar] variables are set.
static bool get hasEnv =>
dotenv.isEveryDefined(EnvVar.values.map((e) => e.key).toList());
static Future<void> init() async {
await dotenv.load();
}
static const FlutterMode flutterMode = kDebugMode
? FlutterMode.debug
: kReleaseMode
? FlutterMode.release
: kProfileMode
? FlutterMode.profile
: FlutterMode.unknown;
}
enum FlutterMode {
debug,
profile,
release,
unknown,
}
extension EnvVarValue on EnvVar {
/// The value of the [EnvVar] as a string. If the environment variable is not
/// set, returns an empty string.
String get value => AppEnv.valueOf(this);
}
enum EnvVar {
/// The environment to use for the API calls. Returns either [dev] or [prod].
///
/// Example `.env` configuration:
/// ```
/// API_ENVIRONMENT="dev"
/// ```
apiEnvironment("API_ENVIRONMENT"),
;
/// The environment variable to use when retrieving the value of this [EnvVar].
final String key;
const EnvVar(this.key);
}
+87
View File
@@ -0,0 +1,87 @@
import "package:flutter/material.dart";
import "package:flutter_adaptive_scaffold/flutter_adaptive_scaffold.dart";
import "package:flutter_gen/gen_l10n/app_localizations.dart";
import "package:flutter_svg/flutter_svg.dart";
import "package:go_router/go_router.dart";
class AppScaffold extends StatelessWidget {
final Widget body;
final Widget? secondaryBody;
const AppScaffold({
required this.body,
this.secondaryBody,
super.key = const ValueKey("AppScaffold"),
});
@override
Widget build(BuildContext context) {
return AdaptiveLayout(
internalAnimations: false,
body: SlotLayout(
config: <Breakpoint, SlotLayoutConfig>{
Breakpoints.small: SlotLayout.from(
key: const Key("Body Small"),
builder: (context) => secondaryBody ?? body,
),
Breakpoints.mediumAndUp: SlotLayout.from(
key: const Key("Body Medium and Up"),
builder: (context) => body,
),
},
),
secondaryBody: SlotLayout(
config: <Breakpoint, SlotLayoutConfig>{
Breakpoints.small: SlotLayout.from(
key: const Key("Body Small"),
builder: null,
),
Breakpoints.mediumAndUp: SlotLayout.from(
key: const Key("Body Medium"),
builder: secondaryBody,
),
},
),
);
}
}
class AppScaffoldShell extends StatelessWidget {
final StatefulNavigationShell navigationShell;
const AppScaffoldShell({
required this.navigationShell,
super.key = const ValueKey("AppScaffoldShell"),
});
@override
Widget build(BuildContext context) {
return AdaptiveScaffold(
useDrawer: false,
internalAnimations: false,
selectedIndex: navigationShell.currentIndex,
onSelectedIndexChange: onNavigationEvent,
destinations: [
NavigationDestination(
label: AppLocalizations.of(context).tabHome,
icon: const Icon(Icons.home),
),
NavigationDestination(
label: AppLocalizations.of(context).tabHistory,
icon: const Icon(Icons.change_history_rounded),
),
NavigationDestination(
label: AppLocalizations.of(context).tabProfile,
icon: const Icon(Icons.account_circle),
),
],
body: (_) => navigationShell,
);
}
void onNavigationEvent(int index) {
navigationShell.goBranch(
index,
initialLocation: index == navigationShell.currentIndex,
);
}
}
+13
View File
@@ -0,0 +1,13 @@
enum Feature {
/// Prints the current auth token to the debug log each time the API makes a
/// request.
debugPrintAuthToken(kDebugMode),
;
/// Determines whether the given [Feature] is enabled by default when the
/// application launches. Features can be enabled or disabled during runtime,
/// regardless of this value.
final bool enabledAtStartup;
const Feature(this.enabledAtStartup);
}
+83
View File
@@ -0,0 +1,83 @@
import "package:arcane_framework/arcane_framework.dart";
import "package:dio/dio.dart";
import "package:flutter_secure_storage/flutter_secure_storage.dart";
import "package:get_it/get_it.dart";
abstract class AppInjector {
static final GetIt getIt = GetIt.I;
static void _registerApis() {
getIt.registerSingleton<MyApi>(
SetupApi(getIt<SecureStorageRepository>()),
);
}
static void _registerHelpers() {
getIt.registerSingleton<FlutterSecureStorage>(
const FlutterSecureStorage(
aOptions: AndroidOptions(
encryptedSharedPreferences: true,
),
),
);
getIt.registerSingleton<SecureStorageRepository>(
SecureStorageRepository(getIt<FlutterSecureStorage>()),
);
getIt.registerLazySingleton<AuthorizationInterceptor>(
() => AuthorizationInterceptor(),
);
getIt.registerLazySingleton<Dio>(
() => DioHelper.createDioInstance(getIt),
);
}
static Future<void> init() async {
Arcane.log(
"Initializing injector...",
level: Level.info,
);
await getIt.reset();
_registerHelpers();
_registerApis();
await getIt.allReady();
await IdService.I.init();
Arcane.log(
"Injector initialized.",
level: Level.info,
);
}
static void resetDio() {
if (getIt.isRegistered<Dio>()) {
getIt.unregister<Dio>();
}
getIt.registerLazySingleton<Dio>(
() => DioHelper.createDioInstance(getIt),
);
}
static Future<void> configureAsDebug() async {
Arcane.log(
"Unregistering production APIs and replacing with Debug versions",
level: Level.fatal,
);
getIt.unregister<MyApi>();
await getIt.allReady();
getIt.registerSingleton<SecureStorageRepository>(
DebugSecureStorageRepository(),
);
final SecureStorageRepository storage = getIt<SecureStorageRepository>();
getIt.registerSingleton<MyApi>(DebugMyApi(storage));
await getIt.allReady();
}
}
+30
View File
@@ -0,0 +1,30 @@
import "package:arcane_framework/arcane_framework.dart";
import "package:flutter/material.dart";
import "package:flutter_bloc/flutter_bloc.dart";
import "package:get_it/get_it.dart";
class AppStateProvider extends StatelessWidget {
final Widget child;
const AppStateProvider({
required this.child,
super.key,
});
@override
Widget build(BuildContext context) {
final ValueKey key = ValueKey(
"${context.watch<ArcaneEnvironment>().state.name}-${IdService.I.sessionId.value}",
);
return MultiBlocProvider(
providers: [
BlocProvider(
key: key,
create: (context) => MyBloc(GetIt.I<MyApi>()),
),
],
child: child,
);
}
}