Add base skeleton

This commit is contained in:
2025-07-01 20:40:00 +02:00
parent ab8974c0a8
commit 29fec99b8f
19 changed files with 722 additions and 24 deletions

View File

@ -45,4 +45,5 @@ app.*.map.json
/android/app/release
*.g.dart
*.g.dart
*.freezed.dart

View File

@ -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(
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,
);
}
}
}

View 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);
}

View 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',
),
],
),
);
}
}

View 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;
}

View 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;
}

View File

@ -0,0 +1,7 @@
const homePath = "/";
/// Authentication path
const authPath = "/login";
/// Settings path
const settingsPath = "/settings";

View 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);
}
}

View 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',
};
}

View 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);
}

View 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),
],
),
);
}
}

View 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 [],
});
}

View File

@ -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);
}

View File

@ -3,6 +3,7 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
flutter_secure_storage_linux
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST

View File

@ -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"))
}

View File

@ -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:

View File

@ -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

View File

@ -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"));
}

View File

@ -3,6 +3,7 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
flutter_secure_storage_windows
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST