From 29fec99b8f9add02c5c921dcf85c31cef81d1be0 Mon Sep 17 00:00:00 2001 From: Pierre HUBERT Date: Tue, 1 Jul 2025 20:40:00 +0200 Subject: [PATCH] Add base skeleton --- moneymgr_mobile/.gitignore | 3 +- moneymgr_mobile/lib/main.dart | 40 +++--- .../lib/routes/login/login_model.dart | 16 +++ .../lib/routes/login/login_screens.dart | 85 +++++++++++ moneymgr_mobile/lib/services/auth_state.dart | 75 ++++++++++ .../lib/services/router/router.dart | 107 ++++++++++++++ .../lib/services/router/routes_list.dart | 7 + .../lib/services/storage/secure_storage.dart | 42 ++++++ moneymgr_mobile/lib/utils/extensions.dart | 38 +++++ moneymgr_mobile/lib/utils/hooks.dart | 19 +++ moneymgr_mobile/lib/widgets/app_button.dart | 45 ++++++ .../lib/widgets/scaffold_with_navigation.dart | 100 +++++++++++++ .../flutter/generated_plugin_registrant.cc | 4 + .../linux/flutter/generated_plugins.cmake | 1 + .../Flutter/GeneratedPluginRegistrant.swift | 4 + moneymgr_mobile/pubspec.lock | 136 +++++++++++++++++- moneymgr_mobile/pubspec.yaml | 20 +++ .../flutter/generated_plugin_registrant.cc | 3 + .../windows/flutter/generated_plugins.cmake | 1 + 19 files changed, 722 insertions(+), 24 deletions(-) create mode 100644 moneymgr_mobile/lib/routes/login/login_model.dart create mode 100644 moneymgr_mobile/lib/routes/login/login_screens.dart create mode 100644 moneymgr_mobile/lib/services/auth_state.dart create mode 100644 moneymgr_mobile/lib/services/router/router.dart create mode 100644 moneymgr_mobile/lib/services/router/routes_list.dart create mode 100644 moneymgr_mobile/lib/services/storage/secure_storage.dart create mode 100644 moneymgr_mobile/lib/utils/extensions.dart create mode 100644 moneymgr_mobile/lib/utils/hooks.dart create mode 100644 moneymgr_mobile/lib/widgets/app_button.dart create mode 100644 moneymgr_mobile/lib/widgets/scaffold_with_navigation.dart diff --git a/moneymgr_mobile/.gitignore b/moneymgr_mobile/.gitignore index a975a0e..81af82d 100644 --- a/moneymgr_mobile/.gitignore +++ b/moneymgr_mobile/.gitignore @@ -45,4 +45,5 @@ app.*.map.json /android/app/release -*.g.dart \ No newline at end of file +*.g.dart +*.freezed.dart \ No newline at end of file diff --git a/moneymgr_mobile/lib/main.dart b/moneymgr_mobile/lib/main.dart index c0e84d9..4f90848 100644 --- a/moneymgr_mobile/lib/main.dart +++ b/moneymgr_mobile/lib/main.dart @@ -3,7 +3,9 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_native_splash/flutter_native_splash.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:moneymgr_mobile/providers/settings.dart'; +import 'package:moneymgr_mobile/services/router/router.dart'; import 'package:moneymgr_mobile/services/storage/prefs.dart'; +import 'package:moneymgr_mobile/services/storage/secure_storage.dart'; import 'package:moneymgr_mobile/utils/provider_observer.dart'; import 'package:moneymgr_mobile/utils/theme_utils.dart'; @@ -16,10 +18,12 @@ Future main() async { // app is inserted to the widget tree. FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding); - runApp(ProviderScope( - observers: [AppProviderObserver()], - child: const MoneyMgrApp(), - )); + runApp( + ProviderScope( + observers: [AppProviderObserver()], + child: const MoneyMgrApp(), + ), + ); } class MoneyMgrApp extends StatelessWidget { @@ -27,21 +31,18 @@ class MoneyMgrApp extends StatelessWidget { @override Widget build(BuildContext context) { - return const _EagerInitialization( - child: _MainApp(), - ); + return const _EagerInitialization(child: _MainApp()); } } class _EagerInitialization extends ConsumerWidget { const _EagerInitialization({required this.child}); + final Widget child; @override Widget build(BuildContext context, WidgetRef ref) { - final values = [ - ref.watch(prefsProvider), - ]; + final values = [ref.watch(prefsProvider), ref.watch(secureStorageProvider)]; if (values.every((value) => value.hasValue)) { return child; @@ -58,7 +59,6 @@ class _MainApp extends StatefulHookConsumerWidget { ConsumerState<_MainApp> createState() => _MainAppState(); } - class _MainAppState extends ConsumerState<_MainApp> { @override void initState() { @@ -71,15 +71,17 @@ class _MainAppState extends ConsumerState<_MainApp> { final router = ref.watch(routerProvider); final themeMode = ref.watch(currentThemeModeProvider); - final (lightTheme, darkTheme) = useMemoized(() => createDualThemeData( - seedColor: Colors.blue, - useMaterial3: true, - transformer: (data) => data.copyWith( - inputDecorationTheme: const InputDecorationTheme( - border: OutlineInputBorder(), + final (lightTheme, darkTheme) = useMemoized( + () => createDualThemeData( + seedColor: Colors.blue, + useMaterial3: true, + transformer: (data) => data.copyWith( + inputDecorationTheme: const InputDecorationTheme( + border: OutlineInputBorder(), + ), ), ), - )); + ); return MaterialApp.router( title: 'MoneyMgr', @@ -90,4 +92,4 @@ class _MainAppState extends ConsumerState<_MainApp> { debugShowCheckedModeBanner: false, ); } -} \ No newline at end of file +} diff --git a/moneymgr_mobile/lib/routes/login/login_model.dart b/moneymgr_mobile/lib/routes/login/login_model.dart new file mode 100644 index 0000000..556ec58 --- /dev/null +++ b/moneymgr_mobile/lib/routes/login/login_model.dart @@ -0,0 +1,16 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'login_model.freezed.dart'; +part 'login_model.g.dart'; + +@freezed +abstract class LoginModel with _$LoginModel { + const factory LoginModel({ + // TODO : replace + required String username, + required String password, + }) = _LoginModel; + + factory LoginModel.fromJson(Map json) => + _$LoginModelFromJson(json); +} diff --git a/moneymgr_mobile/lib/routes/login/login_screens.dart b/moneymgr_mobile/lib/routes/login/login_screens.dart new file mode 100644 index 0000000..486e10e --- /dev/null +++ b/moneymgr_mobile/lib/routes/login/login_screens.dart @@ -0,0 +1,85 @@ +import 'package:flextras/flextras.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_gutter/flutter_gutter.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:go_router/go_router.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:moneymgr_mobile/routes/login/login_model.dart'; +import 'package:moneymgr_mobile/widgets/app_button.dart'; + +import '../../../services/auth_state.dart'; +import '../../../utils/extensions.dart'; + +class LoginScreen extends HookConsumerWidget { + const LoginScreen({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final isPasswordVisible = useState(false); + + final usernameController = useTextEditingController(); + final passwordController = useTextEditingController(); + + void onSettingsPressed() => context.push('/settings'); + + Future onLoginPressed() async { + try { + await ref.read(currentAuthStateProvider.notifier).login(LoginModel( + username: usernameController.text, + password: passwordController.text, + )); + } on Exception catch (e) { + if (!context.mounted) return; + context.showTextSnackBar(e.toString()); + } + } + + return Scaffold( + appBar: AppBar( + title: const Text('Login'), + actions: [ + IconButton( + onPressed: onSettingsPressed, + icon: const Icon(Icons.settings), + ), + ], + ), + body: SeparatedColumn( + padding: EdgeInsets.all(context.gutter), + separatorBuilder: () => const Gutter(), + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + TextField( + controller: usernameController, + decoration: const InputDecoration(labelText: 'Username'), + textInputAction: TextInputAction.next, + ), + TextField( + controller: passwordController, + decoration: InputDecoration( + labelText: 'Password', + suffixIcon: IconButton( + icon: Icon( + isPasswordVisible.value + ? Icons.visibility_off + : Icons.visibility, + ), + onPressed: () => + isPasswordVisible.value = !isPasswordVisible.value, + ), + ), + obscureText: !isPasswordVisible.value, + keyboardType: TextInputType.visiblePassword, + textInputAction: TextInputAction.done, + ), + const SizedBox.shrink(), + AppButton( + onPressed: onLoginPressed, + label: 'Login', + ), + ], + ), + ); + } +} diff --git a/moneymgr_mobile/lib/services/auth_state.dart b/moneymgr_mobile/lib/services/auth_state.dart new file mode 100644 index 0000000..46fd23d --- /dev/null +++ b/moneymgr_mobile/lib/services/auth_state.dart @@ -0,0 +1,75 @@ +import 'package:moneymgr_mobile/routes/login/login_model.dart'; +import 'package:moneymgr_mobile/services/router/routes_list.dart'; +import 'package:moneymgr_mobile/services/storage/secure_storage.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'auth_state.g.dart'; + +/// The current authentication state of the app. +/// +/// This notifier is responsible for saving/removing the token and profile info +/// to the storage through the [login] and [logout] methods. +@riverpod +class CurrentAuthState extends _$CurrentAuthState { + @override + AuthState build() { + final secureStorage = ref.watch(secureStorageProvider).requireValue; + final token = secureStorage.get('token'); + return token != null ? AuthState.authenticated : AuthState.unauthenticated; + } + + /// Attempts to log in with [data] and saves the token and profile info to storage. + /// Will invalidate the state if success. + Future login(LoginModel data) async { + // TODO : perform login + /*final secureStorage = ref.read(secureStorageProvider).requireValue; + final token = await ref.read(apiServiceProvider).login(data); + + // Save the new [token] and [profile] to secure storage. + secureStorage.set('token', token); + + ref + // Invalidate the state so the auth state will be updated to authenticated. + ..invalidateSelf() + // Invalidate the token provider so the API service will use the new token. + ..invalidate(tokenProvider);*/ + } + + /// Logs out, deletes the saved token and profile info from storage, and invalidates + /// the state. + void logout() { + // TODO : implement logic + /*final secureStorage = ref.read(secureStorageProvider).requireValue; + + // Delete the current [token] and [profile] from secure storage. + secureStorage.remove('token'); + + ref + // Invalidate the state so the auth state will be updated to unauthenticated. + ..invalidateSelf() + // Invalidate the token provider so the API service will no longer use the + // previous token. + ..invalidate(tokenProvider);*/ + } +} + + +/// The possible authentication states of the app. +enum AuthState { + unknown(redirectPath: homePath, allowedPaths: [homePath]), + unauthenticated( + redirectPath: authPath, + allowedPaths: [authPath, settingsPath], + ), + authenticated(redirectPath: homePath, allowedPaths: null); + + const AuthState({required this.redirectPath, required this.allowedPaths}); + + /// The target path to redirect when the current route is not allowed in this + /// auth state. + final String redirectPath; + + /// List of paths allowed when the app is in this auth state. May be set to null if there is no + /// restriction applicable + final List? allowedPaths; +} diff --git a/moneymgr_mobile/lib/services/router/router.dart b/moneymgr_mobile/lib/services/router/router.dart new file mode 100644 index 0000000..4ff6d87 --- /dev/null +++ b/moneymgr_mobile/lib/services/router/router.dart @@ -0,0 +1,107 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:moneymgr_mobile/routes/login/login_screens.dart'; +import 'package:moneymgr_mobile/services/auth_state.dart'; +import 'package:moneymgr_mobile/services/router/routes_list.dart'; +import 'package:moneymgr_mobile/widgets/scaffold_with_navigation.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + + +part 'router.g.dart'; + +/// The router config for the app. +@riverpod +GoRouter router(Ref ref) { + // Local notifier for the current auth state. The purpose of this notifier is + // to provide a [Listenable] to the [GoRouter] exposed by this provider. + final authStateNotifier = ValueNotifier(AuthState.unknown); + ref + ..onDispose(authStateNotifier.dispose) + ..listen(currentAuthStateProvider, (_, value) { + authStateNotifier.value = value; + }); + + // This is the only place you need to define your navigation items. The items + // will be propagated automatically to the router and the navigation bar/rail + // of the scaffold. + // + // To configure the authentication state needed to access a particular item, + // see [AuthState] enum. + final navigationItems = [ + NavigationItem( + path: '/products', + body: (_) => const Text("product screen"), + icon: Icons.widgets_outlined, + selectedIcon: Icons.widgets, + label: 'Products', + routes: [ + GoRoute( + path: ':id', + builder: (_, state) { + final id = int.parse(state.pathParameters['id']!); + return Text("product screen $id"); + }, + ), + ], + ), + NavigationItem( + path: '/profile', + body: (_) => const Text("Profile"), + icon: Icons.person_outline, + selectedIcon: Icons.person, + label: 'Profile', + ), + ]; + + final router = GoRouter( + debugLogDiagnostics: true, + initialLocation: navigationItems.first.path, + routes: [ + GoRoute(path: homePath, builder: (_, __) => const Scaffold()), + GoRoute(path: authPath, builder: (_, __) => const LoginScreen()), + GoRoute( + path: settingsPath, + builder: (_, __) => const Text("settings screen"), + ), + + // Configuration for the bottom navigation bar routes. The routes themselves + // should be defined in [navigationItems]. Modification to this [ShellRoute] + // config is rarely needed. + ShellRoute( + builder: (_, __, child) => child, + routes: [ + for (final (index, item) in navigationItems.indexed) + GoRoute( + path: item.path, + pageBuilder: (context, _) => NoTransitionPage( + child: ScaffoldWithNavigation( + selectedIndex: index, + navigationItems: navigationItems, + child: item.body(context), + ), + ), + routes: item.routes, + ), + ], + ), + ], + refreshListenable: authStateNotifier, + redirect: (_, state) { + // Get the current auth state. + final authState = ref.read(currentAuthStateProvider); + + // Check if the current path is allowed for the current auth state. If not, + // redirect to the redirect target of the current auth state. + if (authState.allowedPaths?.contains(state.fullPath) == false) { + return authState.redirectPath; + } + + // If the current path is allowed for the current auth state, don't redirect. + return null; + }, + ); + ref.onDispose(router.dispose); + + return router; +} diff --git a/moneymgr_mobile/lib/services/router/routes_list.dart b/moneymgr_mobile/lib/services/router/routes_list.dart new file mode 100644 index 0000000..74ccc9a --- /dev/null +++ b/moneymgr_mobile/lib/services/router/routes_list.dart @@ -0,0 +1,7 @@ +const homePath = "/"; + +/// Authentication path +const authPath = "/login"; + +/// Settings path +const settingsPath = "/settings"; \ No newline at end of file diff --git a/moneymgr_mobile/lib/services/storage/secure_storage.dart b/moneymgr_mobile/lib/services/storage/secure_storage.dart new file mode 100644 index 0000000..4a83fa7 --- /dev/null +++ b/moneymgr_mobile/lib/services/storage/secure_storage.dart @@ -0,0 +1,42 @@ +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'secure_storage.g.dart'; + +@riverpod +Future secureStorage(Ref ref) => + SecureStorage.getInstance(keys: {'token'}); + +class SecureStorage { + SecureStorage._(this._flutterSecureStorage, this._cache); + + late final FlutterSecureStorage _flutterSecureStorage; + + late final Map _cache; + + static Future getInstance({required Set keys}) async { + const flutterSecureStorage = FlutterSecureStorage(); + final cache = {}; + await keys + .map((key) => flutterSecureStorage.read(key: key).then((value) { + if (value != null) { + cache[key] = value; + } + })) + .wait; + return SecureStorage._(flutterSecureStorage, cache); + } + + String? get(String key) => _cache[key]; + + Future set(String key, String value) { + _cache[key] = value; + return _flutterSecureStorage.write(key: key, value: value); + } + + Future remove(String key) { + _cache.remove(key); + return _flutterSecureStorage.delete(key: key); + } +} diff --git a/moneymgr_mobile/lib/utils/extensions.dart b/moneymgr_mobile/lib/utils/extensions.dart new file mode 100644 index 0000000..9dd0c0a --- /dev/null +++ b/moneymgr_mobile/lib/utils/extensions.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; + +extension BuildContextX on BuildContext { + /// A convenient way to access [ThemeData.colorScheme] of the current context. + /// + /// This also prevents confusion with a bunch of other properties of [ThemeData] + /// that are less commonly used. + ColorScheme get colorScheme => Theme.of(this).colorScheme; + + /// A convenient way to access [ThemeData.textTheme] of the current context. + /// + /// This also prevents confusion with a bunch of other properties of [ThemeData] + /// that are less commonly used. + TextTheme get textTheme => Theme.of(this).textTheme; + + /// Shows a floating snack bar with text as its content. + ScaffoldFeatureController showTextSnackBar( + String text, + ) => + ScaffoldMessenger.of(this).showSnackBar(SnackBar( + behavior: SnackBarBehavior.floating, + content: Text(text), + )); + + void showAppLicensePage() => showLicensePage( + context: this, + useRootNavigator: true, + applicationName: 'DummyMart', + ); +} + +extension ThemeModeX on ThemeMode { + String get label => switch (this) { + ThemeMode.system => 'System', + ThemeMode.light => 'Light', + ThemeMode.dark => 'Dark', + }; +} diff --git a/moneymgr_mobile/lib/utils/hooks.dart b/moneymgr_mobile/lib/utils/hooks.dart new file mode 100644 index 0000000..5103a87 --- /dev/null +++ b/moneymgr_mobile/lib/utils/hooks.dart @@ -0,0 +1,19 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; + +typedef AsyncTask = ({ +ValueNotifier?> pending, +AsyncSnapshot snapshot, +bool hasError, +}); + +/// Creates a hook that provides a [snapshot] of the current asynchronous task passed +/// to [pending] and a [hasError] value. +AsyncTask useAsyncTask() { + final pending = useState?>(null); + final snapshot = useFuture(pending.value); + final hasError = + snapshot.hasError && snapshot.connectionState != ConnectionState.waiting; + + return (pending: pending, snapshot: snapshot, hasError: hasError); +} diff --git a/moneymgr_mobile/lib/widgets/app_button.dart b/moneymgr_mobile/lib/widgets/app_button.dart new file mode 100644 index 0000000..229bd04 --- /dev/null +++ b/moneymgr_mobile/lib/widgets/app_button.dart @@ -0,0 +1,45 @@ +import 'package:flextras/flextras.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_gutter/flutter_gutter.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:moneymgr_mobile/utils/extensions.dart'; +import 'package:moneymgr_mobile/utils/hooks.dart'; + + +/// A button that shows a circular progress indicator when the [onPressed] callback +/// is pending. +class AppButton extends HookWidget { + const AppButton({ + super.key, + required this.onPressed, + required this.label, + }); + + final AsyncCallback? onPressed; + final String label; + + @override + Widget build(BuildContext context) { + final (:pending, :snapshot, hasError: _) = useAsyncTask(); + + return FilledButton( + onPressed: onPressed == null ? null : () => pending.value = onPressed!(), + child: SeparatedRow( + separatorBuilder: () => const GutterSmall(), + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (snapshot.connectionState == ConnectionState.waiting) + SizedBox.square( + dimension: 12, + child: CircularProgressIndicator( + strokeWidth: 2, + color: context.colorScheme.onPrimary, + ), + ), + Text(label), + ], + ), + ); + } +} diff --git a/moneymgr_mobile/lib/widgets/scaffold_with_navigation.dart b/moneymgr_mobile/lib/widgets/scaffold_with_navigation.dart new file mode 100644 index 0000000..9254a81 --- /dev/null +++ b/moneymgr_mobile/lib/widgets/scaffold_with_navigation.dart @@ -0,0 +1,100 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; + +/// A scaffold that shows navigation bar/rail when the current path is a navigation +/// item. +/// +/// When in a navigation item, a [NavigationBar] will be shown if the width of the +/// screen is less than 600dp. Otherwise, a [NavigationRail] will be shown. +class ScaffoldWithNavigation extends StatelessWidget { + const ScaffoldWithNavigation({ + super.key, + required this.child, + required this.selectedIndex, + required this.navigationItems, + }); + + final Widget child; + final int selectedIndex; + final List navigationItems; + + @override + Widget build(BuildContext context) { + void onDestinationSelected(int index) => + context.go(navigationItems[index].path); + + // Use navigation rail instead of navigation bar when the screen width is + // larger than 600dp. + if (MediaQuery.sizeOf(context).width > 600) { + return Scaffold( + body: Row( + children: [ + NavigationRail( + selectedIndex: selectedIndex, + onDestinationSelected: onDestinationSelected, + destinations: [ + for (final item in navigationItems) + NavigationRailDestination( + icon: Icon(item.icon), + selectedIcon: item.selectedIcon != null + ? Icon(item.selectedIcon) + : null, + label: Text(item.label), + ) + ], + extended: true, + ), + Expanded(child: child), + ], + ), + ); + } + + return Scaffold( + body: child, + bottomNavigationBar: NavigationBar( + selectedIndex: selectedIndex, + onDestinationSelected: onDestinationSelected, + destinations: [ + for (final item in navigationItems) + NavigationDestination( + icon: Icon(item.icon), + selectedIcon: + item.selectedIcon != null ? Icon(item.selectedIcon) : null, + label: item.label, + ) + ], + ), + ); + } +} + +/// An item that represents a navigation destination in a navigation bar/rail. +class NavigationItem { + /// Path in the router. + final String path; + + /// Widget to show when navigating to this [path]. + final WidgetBuilder body; + + /// Icon in the navigation bar. + final IconData icon; + + /// Icon in the navigation bar when selected. + final IconData? selectedIcon; + + /// Label in the navigation bar. + final String label; + + /// The subroutes of the route from this [path]. + final List routes; + + NavigationItem({ + required this.path, + required this.body, + required this.icon, + this.selectedIcon, + required this.label, + this.routes = const [], + }); +} diff --git a/moneymgr_mobile/linux/flutter/generated_plugin_registrant.cc b/moneymgr_mobile/linux/flutter/generated_plugin_registrant.cc index e71a16d..d0e7f79 100644 --- a/moneymgr_mobile/linux/flutter/generated_plugin_registrant.cc +++ b/moneymgr_mobile/linux/flutter/generated_plugin_registrant.cc @@ -6,6 +6,10 @@ #include "generated_plugin_registrant.h" +#include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin"); + flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar); } diff --git a/moneymgr_mobile/linux/flutter/generated_plugins.cmake b/moneymgr_mobile/linux/flutter/generated_plugins.cmake index 2e1de87..b29e9ba 100644 --- a/moneymgr_mobile/linux/flutter/generated_plugins.cmake +++ b/moneymgr_mobile/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + flutter_secure_storage_linux ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/moneymgr_mobile/macos/Flutter/GeneratedPluginRegistrant.swift b/moneymgr_mobile/macos/Flutter/GeneratedPluginRegistrant.swift index 724bb2a..37af1fe 100644 --- a/moneymgr_mobile/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/moneymgr_mobile/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,8 +5,12 @@ import FlutterMacOS import Foundation +import flutter_secure_storage_macos +import path_provider_foundation import shared_preferences_foundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin")) + PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) } diff --git a/moneymgr_mobile/pubspec.lock b/moneymgr_mobile/pubspec.lock index 5c221f4..c5f23c1 100644 --- a/moneymgr_mobile/pubspec.lock +++ b/moneymgr_mobile/pubspec.lock @@ -257,11 +257,35 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.1" + flextras: + dependency: "direct main" + description: + name: flextras + sha256: e73b5c86dd9419569d2a48db470059b41b496012513e4e1bdc56ba2c661048d9 + url: "https://pub.dev" + source: hosted + version: "1.0.0" flutter: dependency: "direct main" description: flutter source: sdk version: "0.0.0" + flutter_adaptive_scaffold: + dependency: transitive + description: + name: flutter_adaptive_scaffold + sha256: "5eb1d1d174304a4e67c4bb402ed38cb4a5ebdac95ce54099e91460accb33d295" + url: "https://pub.dev" + source: hosted + version: "0.3.3+1" + flutter_gutter: + dependency: "direct main" + description: + name: flutter_gutter + sha256: "2aa99181796d6f7d2de66da962b71b0feb996ec69b7a1ad2ac1c2119f25b041b" + url: "https://pub.dev" + source: hosted + version: "2.2.0" flutter_hooks: dependency: "direct main" description: @@ -294,6 +318,54 @@ packages: url: "https://pub.dev" source: hosted version: "2.6.1" + flutter_secure_storage: + dependency: "direct main" + description: + name: flutter_secure_storage + sha256: "9cad52d75ebc511adfae3d447d5d13da15a55a92c9410e50f67335b6d21d16ea" + url: "https://pub.dev" + source: hosted + version: "9.2.4" + flutter_secure_storage_linux: + dependency: transitive + description: + name: flutter_secure_storage_linux + sha256: be76c1d24a97d0b98f8b54bce6b481a380a6590df992d0098f868ad54dc8f688 + url: "https://pub.dev" + source: hosted + version: "1.2.3" + flutter_secure_storage_macos: + dependency: transitive + description: + name: flutter_secure_storage_macos + sha256: "6c0a2795a2d1de26ae202a0d78527d163f4acbb11cde4c75c670f3a0fc064247" + url: "https://pub.dev" + source: hosted + version: "3.1.3" + flutter_secure_storage_platform_interface: + dependency: transitive + description: + name: flutter_secure_storage_platform_interface + sha256: cf91ad32ce5adef6fba4d736a542baca9daf3beac4db2d04be350b87f69ac4a8 + url: "https://pub.dev" + source: hosted + version: "1.1.2" + flutter_secure_storage_web: + dependency: transitive + description: + name: flutter_secure_storage_web + sha256: f4ebff989b4f07b2656fb16b47852c0aab9fed9b4ec1c70103368337bc1886a9 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + flutter_secure_storage_windows: + dependency: transitive + description: + name: flutter_secure_storage_windows + sha256: b20b07cb5ed4ed74fc567b78a72936203f587eba460af1df11281c9326cd3709 + url: "https://pub.dev" + source: hosted + version: "3.1.2" flutter_test: dependency: "direct dev" description: flutter @@ -304,8 +376,16 @@ packages: description: flutter source: sdk version: "0.0.0" + freezed: + dependency: "direct dev" + description: + name: freezed + sha256: "6022db4c7bfa626841b2a10f34dd1e1b68e8f8f9650db6112dcdeeca45ca793c" + url: "https://pub.dev" + source: hosted + version: "3.0.6" freezed_annotation: - dependency: transitive + dependency: "direct main" description: name: freezed_annotation sha256: c87ff004c8aa6af2d531668b46a4ea379f7191dc6dfa066acd53d506da6e044b @@ -404,18 +484,26 @@ packages: dependency: transitive description: name: js - sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 url: "https://pub.dev" source: hosted - version: "0.7.2" + version: "0.6.7" json_annotation: - dependency: transitive + dependency: "direct main" description: name: json_annotation sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" url: "https://pub.dev" source: hosted version: "4.9.0" + json_serializable: + dependency: "direct dev" + description: + name: json_serializable + sha256: c50ef5fc083d5b5e12eef489503ba3bf5ccc899e487d691584699b4bdefeea8c + url: "https://pub.dev" + source: hosted + version: "6.9.5" leak_tracker: dependency: transitive description: @@ -504,6 +592,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.1" + path_provider: + dependency: transitive + description: + name: path_provider + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9 + url: "https://pub.dev" + source: hosted + version: "2.2.17" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" + url: "https://pub.dev" + source: hosted + version: "2.4.1" path_provider_linux: dependency: transitive description: @@ -701,6 +813,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.0" + source_helper: + dependency: transitive + description: + name: source_helper + sha256: "86d247119aedce8e63f4751bd9626fc9613255935558447569ad42f9f5b48b3c" + url: "https://pub.dev" + source: hosted + version: "1.3.5" source_span: dependency: transitive description: @@ -853,6 +973,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.3" + win32: + dependency: transitive + description: + name: win32 + sha256: "66814138c3562338d05613a6e368ed8cfb237ad6d64a9e9334be3f309acfca03" + url: "https://pub.dev" + source: hosted + version: "5.14.0" xdg_directories: dependency: transitive description: diff --git a/moneymgr_mobile/pubspec.yaml b/moneymgr_mobile/pubspec.yaml index c38f087..0e7c64c 100644 --- a/moneymgr_mobile/pubspec.yaml +++ b/moneymgr_mobile/pubspec.yaml @@ -38,6 +38,9 @@ dependencies: # Preferences management shared_preferences: ^2.5.3 + # Credentials storage + flutter_secure_storage: ^9.2.4 + # Splash screen flutter_native_splash: ^2.4.6 @@ -51,6 +54,15 @@ dependencies: # Router go_router: ^15.2.4 + # Flutter extras widgets for columns and rows + flextras: ^1.0.0 + flutter_gutter: ^2.2.0 + + # Help in models building + freezed_annotation: ^3.0.0 + + json_annotation: ^4.9.0 + dev_dependencies: flutter_test: sdk: flutter @@ -68,6 +80,14 @@ dev_dependencies: # Riverpod code generation riverpod_generator: ^2.6.5 + # Freezed code generation + freezed: ^3.0.6 + + # JSON serialization + json_serializable: ^6.9.5 + + + # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/moneymgr_mobile/windows/flutter/generated_plugin_registrant.cc b/moneymgr_mobile/windows/flutter/generated_plugin_registrant.cc index 8b6d468..0c50753 100644 --- a/moneymgr_mobile/windows/flutter/generated_plugin_registrant.cc +++ b/moneymgr_mobile/windows/flutter/generated_plugin_registrant.cc @@ -6,6 +6,9 @@ #include "generated_plugin_registrant.h" +#include void RegisterPlugins(flutter::PluginRegistry* registry) { + FlutterSecureStorageWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin")); } diff --git a/moneymgr_mobile/windows/flutter/generated_plugins.cmake b/moneymgr_mobile/windows/flutter/generated_plugins.cmake index b93c4c3..4fc759c 100644 --- a/moneymgr_mobile/windows/flutter/generated_plugins.cmake +++ b/moneymgr_mobile/windows/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + flutter_secure_storage_windows ) list(APPEND FLUTTER_FFI_PLUGIN_LIST