Add base skeleton
This commit is contained in:
		
							
								
								
									
										1
									
								
								moneymgr_mobile/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								moneymgr_mobile/.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -46,3 +46,4 @@ app.*.map.json
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
*.g.dart
 | 
			
		||||
*.freezed.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<void> main() async {
 | 
			
		||||
  // app is inserted to the widget tree.
 | 
			
		||||
  FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding);
 | 
			
		||||
 | 
			
		||||
  runApp(ProviderScope(
 | 
			
		||||
  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,7 +71,8 @@ class _MainAppState extends ConsumerState<_MainApp> {
 | 
			
		||||
    final router = ref.watch(routerProvider);
 | 
			
		||||
    final themeMode = ref.watch(currentThemeModeProvider);
 | 
			
		||||
 | 
			
		||||
    final (lightTheme, darkTheme) = useMemoized(() => createDualThemeData(
 | 
			
		||||
    final (lightTheme, darkTheme) = useMemoized(
 | 
			
		||||
      () => createDualThemeData(
 | 
			
		||||
        seedColor: Colors.blue,
 | 
			
		||||
        useMaterial3: true,
 | 
			
		||||
        transformer: (data) => data.copyWith(
 | 
			
		||||
@@ -79,7 +80,8 @@ class _MainAppState extends ConsumerState<_MainApp> {
 | 
			
		||||
            border: OutlineInputBorder(),
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
    ));
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    return MaterialApp.router(
 | 
			
		||||
      title: 'MoneyMgr',
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										16
									
								
								moneymgr_mobile/lib/routes/login/login_model.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								moneymgr_mobile/lib/routes/login/login_model.dart
									
									
									
									
									
										Normal file
									
								
							@@ -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<String, dynamic> json) =>
 | 
			
		||||
      _$LoginModelFromJson(json);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										85
									
								
								moneymgr_mobile/lib/routes/login/login_screens.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								moneymgr_mobile/lib/routes/login/login_screens.dart
									
									
									
									
									
										Normal file
									
								
							@@ -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<void> 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',
 | 
			
		||||
          ),
 | 
			
		||||
        ],
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										75
									
								
								moneymgr_mobile/lib/services/auth_state.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								moneymgr_mobile/lib/services/auth_state.dart
									
									
									
									
									
										Normal file
									
								
							@@ -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<void> 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<String>? allowedPaths;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										107
									
								
								moneymgr_mobile/lib/services/router/router.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								moneymgr_mobile/lib/services/router/router.dart
									
									
									
									
									
										Normal file
									
								
							@@ -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;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										7
									
								
								moneymgr_mobile/lib/services/router/routes_list.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								moneymgr_mobile/lib/services/router/routes_list.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
			
		||||
const homePath = "/";
 | 
			
		||||
 | 
			
		||||
/// Authentication path
 | 
			
		||||
const authPath = "/login";
 | 
			
		||||
 | 
			
		||||
/// Settings path
 | 
			
		||||
const settingsPath = "/settings";
 | 
			
		||||
							
								
								
									
										42
									
								
								moneymgr_mobile/lib/services/storage/secure_storage.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								moneymgr_mobile/lib/services/storage/secure_storage.dart
									
									
									
									
									
										Normal file
									
								
							@@ -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> secureStorage(Ref ref) =>
 | 
			
		||||
    SecureStorage.getInstance(keys: {'token'});
 | 
			
		||||
 | 
			
		||||
class SecureStorage {
 | 
			
		||||
  SecureStorage._(this._flutterSecureStorage, this._cache);
 | 
			
		||||
 | 
			
		||||
  late final FlutterSecureStorage _flutterSecureStorage;
 | 
			
		||||
 | 
			
		||||
  late final Map<String, String> _cache;
 | 
			
		||||
 | 
			
		||||
  static Future<SecureStorage> getInstance({required Set<String> keys}) async {
 | 
			
		||||
    const flutterSecureStorage = FlutterSecureStorage();
 | 
			
		||||
    final cache = <String, String>{};
 | 
			
		||||
    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<void> set(String key, String value) {
 | 
			
		||||
    _cache[key] = value;
 | 
			
		||||
    return _flutterSecureStorage.write(key: key, value: value);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future<void> remove(String key) {
 | 
			
		||||
    _cache.remove(key);
 | 
			
		||||
    return _flutterSecureStorage.delete(key: key);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										38
									
								
								moneymgr_mobile/lib/utils/extensions.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								moneymgr_mobile/lib/utils/extensions.dart
									
									
									
									
									
										Normal file
									
								
							@@ -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<SnackBar, SnackBarClosedReason> 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',
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										19
									
								
								moneymgr_mobile/lib/utils/hooks.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								moneymgr_mobile/lib/utils/hooks.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,19 @@
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:flutter_hooks/flutter_hooks.dart';
 | 
			
		||||
 | 
			
		||||
typedef AsyncTask = ({
 | 
			
		||||
ValueNotifier<Future<void>?> pending,
 | 
			
		||||
AsyncSnapshot<void> 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<Future<void>?>(null);
 | 
			
		||||
  final snapshot = useFuture(pending.value);
 | 
			
		||||
  final hasError =
 | 
			
		||||
      snapshot.hasError && snapshot.connectionState != ConnectionState.waiting;
 | 
			
		||||
 | 
			
		||||
  return (pending: pending, snapshot: snapshot, hasError: hasError);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										45
									
								
								moneymgr_mobile/lib/widgets/app_button.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								moneymgr_mobile/lib/widgets/app_button.dart
									
									
									
									
									
										Normal file
									
								
							@@ -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),
 | 
			
		||||
        ],
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										100
									
								
								moneymgr_mobile/lib/widgets/scaffold_with_navigation.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								moneymgr_mobile/lib/widgets/scaffold_with_navigation.dart
									
									
									
									
									
										Normal file
									
								
							@@ -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<NavigationItem> 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<RouteBase> routes;
 | 
			
		||||
 | 
			
		||||
  NavigationItem({
 | 
			
		||||
    required this.path,
 | 
			
		||||
    required this.body,
 | 
			
		||||
    required this.icon,
 | 
			
		||||
    this.selectedIcon,
 | 
			
		||||
    required this.label,
 | 
			
		||||
    this.routes = const [],
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
@@ -6,6 +6,10 @@
 | 
			
		||||
 | 
			
		||||
#include "generated_plugin_registrant.h"
 | 
			
		||||
 | 
			
		||||
#include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h>
 | 
			
		||||
 | 
			
		||||
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);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,7 @@
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
list(APPEND FLUTTER_PLUGIN_LIST
 | 
			
		||||
  flutter_secure_storage_linux
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
 | 
			
		||||
 
 | 
			
		||||
@@ -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"))
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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:
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,9 @@
 | 
			
		||||
 | 
			
		||||
#include "generated_plugin_registrant.h"
 | 
			
		||||
 | 
			
		||||
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
 | 
			
		||||
 | 
			
		||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
 | 
			
		||||
  FlutterSecureStorageWindowsPluginRegisterWithRegistrar(
 | 
			
		||||
      registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin"));
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,7 @@
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
list(APPEND FLUTTER_PLUGIN_LIST
 | 
			
		||||
  flutter_secure_storage_windows
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user