From c8fa4552bb1dfc1c9473b85cda6269c91640c32b Mon Sep 17 00:00:00 2001 From: Pierre HUBERT Date: Wed, 2 Jul 2025 19:50:25 +0200 Subject: [PATCH] Add manual auth screen --- .../android/app/src/debug/AndroidManifest.xml | 5 +- .../android/app/src/main/AndroidManifest.xml | 3 + moneymgr_mobile/lib/main.dart | 9 ++ .../lib/routes/login/base_auth_page.dart | 22 +++-- .../lib/routes/login/login_model.dart | 16 ---- .../lib/routes/login/login_screen.dart | 1 - .../lib/routes/login/manual_auth_screen.dart | 85 +++++++++++++++++++ .../lib/services/api/account_api.dart | 4 + .../lib/services/api/api_client.dart | 7 ++ .../lib/services/api/api_token.dart | 16 ++++ moneymgr_mobile/lib/services/auth_state.dart | 11 ++- .../lib/services/router/router.dart | 8 +- moneymgr_mobile/lib/widgets/app_button.dart | 12 ++- moneymgr_mobile/pubspec.lock | 2 +- moneymgr_mobile/pubspec.yaml | 4 + 15 files changed, 163 insertions(+), 42 deletions(-) delete mode 100644 moneymgr_mobile/lib/routes/login/login_model.dart create mode 100644 moneymgr_mobile/lib/routes/login/manual_auth_screen.dart create mode 100644 moneymgr_mobile/lib/services/api/account_api.dart create mode 100644 moneymgr_mobile/lib/services/api/api_client.dart create mode 100644 moneymgr_mobile/lib/services/api/api_token.dart diff --git a/moneymgr_mobile/android/app/src/debug/AndroidManifest.xml b/moneymgr_mobile/android/app/src/debug/AndroidManifest.xml index 399f698..ad319dc 100644 --- a/moneymgr_mobile/android/app/src/debug/AndroidManifest.xml +++ b/moneymgr_mobile/android/app/src/debug/AndroidManifest.xml @@ -3,5 +3,8 @@ the Flutter tool needs it to communicate with the running application to allow setting breakpoints, to provide hot reload, etc. --> - + + + + diff --git a/moneymgr_mobile/android/app/src/main/AndroidManifest.xml b/moneymgr_mobile/android/app/src/main/AndroidManifest.xml index 41be915..126979c 100644 --- a/moneymgr_mobile/android/app/src/main/AndroidManifest.xml +++ b/moneymgr_mobile/android/app/src/main/AndroidManifest.xml @@ -42,4 +42,7 @@ + + + diff --git a/moneymgr_mobile/lib/main.dart b/moneymgr_mobile/lib/main.dart index 4f90848..9caaf26 100644 --- a/moneymgr_mobile/lib/main.dart +++ b/moneymgr_mobile/lib/main.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; 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:logging/logging.dart'; import 'package:moneymgr_mobile/providers/settings.dart'; import 'package:moneymgr_mobile/services/router/router.dart'; import 'package:moneymgr_mobile/services/storage/prefs.dart'; @@ -18,6 +19,14 @@ Future main() async { // app is inserted to the widget tree. FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding); + // Configure logger + Logger.root.level = Level.ALL; // defaults to Level.INFO + Logger.root.onRecord.listen( + (record) => + // ignore: avoid_print + print('${record.level.name}: ${record.time}: ${record.message}'), + ); + runApp( ProviderScope( observers: [AppProviderObserver()], diff --git a/moneymgr_mobile/lib/routes/login/base_auth_page.dart b/moneymgr_mobile/lib/routes/login/base_auth_page.dart index 4c61f97..a861ab2 100644 --- a/moneymgr_mobile/lib/routes/login/base_auth_page.dart +++ b/moneymgr_mobile/lib/routes/login/base_auth_page.dart @@ -7,8 +7,15 @@ import '../../services/router/routes_list.dart'; class BaseAuthPage extends StatelessWidget { final List children; + final String? title; + final bool? showSettings; - const BaseAuthPage({super.key, required this.children}); + const BaseAuthPage({ + super.key, + required this.children, + this.title, + this.showSettings, + }); @override Widget build(BuildContext context) { @@ -16,12 +23,15 @@ class BaseAuthPage extends StatelessWidget { return Scaffold( appBar: AppBar( - title: const Text('MoneyMgr'), + title: Text(title ?? 'MoneyMgr'), actions: [ - IconButton( - onPressed: onSettingsPressed, - icon: const Icon(Icons.settings), - ), + // Settings button + showSettings != false + ? IconButton( + onPressed: onSettingsPressed, + icon: const Icon(Icons.settings), + ) + : Container(), ], ), body: SeparatedColumn( diff --git a/moneymgr_mobile/lib/routes/login/login_model.dart b/moneymgr_mobile/lib/routes/login/login_model.dart deleted file mode 100644 index 556ec58..0000000 --- a/moneymgr_mobile/lib/routes/login/login_model.dart +++ /dev/null @@ -1,16 +0,0 @@ -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_screen.dart b/moneymgr_mobile/lib/routes/login/login_screen.dart index def0284..f818c4f 100644 --- a/moneymgr_mobile/lib/routes/login/login_screen.dart +++ b/moneymgr_mobile/lib/routes/login/login_screen.dart @@ -36,7 +36,6 @@ class LoginScreen extends HookConsumerWidget { class _LoginChoice extends StatelessWidget { const _LoginChoice({ - super.key, required this.route, required this.label, required this.icon, diff --git a/moneymgr_mobile/lib/routes/login/manual_auth_screen.dart b/moneymgr_mobile/lib/routes/login/manual_auth_screen.dart new file mode 100644 index 0000000..8cd0100 --- /dev/null +++ b/moneymgr_mobile/lib/routes/login/manual_auth_screen.dart @@ -0,0 +1,85 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gutter/flutter_gutter.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:logging/logging.dart'; +import 'package:moneymgr_mobile/routes/login/base_auth_page.dart'; +import 'package:moneymgr_mobile/services/api/api_token.dart'; +import 'package:moneymgr_mobile/services/auth_state.dart'; +import 'package:moneymgr_mobile/utils/extensions.dart'; +import 'package:moneymgr_mobile/widgets/app_button.dart'; + +class ManualAuthScreen extends HookConsumerWidget { + const ManualAuthScreen({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + var apiUrlController = useTextEditingController(); + var tokenIdController = useTextEditingController(); + var tokenValueController = useTextEditingController(); + + Future onSubmit() async { + try { + await ref + .read(currentAuthStateProvider.notifier) + .setAuthToken( + ApiToken( + apiUrl: apiUrlController.text, + tokenId: int.tryParse(tokenIdController.text) ?? 1, + tokenValue: tokenValueController.text, + ), + ); + } catch (e, s) { + Logger.root.severe("Failed to authenticate user! $e $s"); + if (context.mounted) { + context.showTextSnackBar("Failed to authenticate user! $e"); + } + } + } + + return BaseAuthPage( + title: "Manual authentication", + showSettings: false, + children: [ + Gutter(scaleFactor: 3), + Text( + "On this screen you can manually enter authentication information.", + ), + Gutter(), + TextField( + controller: apiUrlController, + keyboardType: TextInputType.url, + decoration: const InputDecoration( + labelText: 'API URL', + helperText: + "This URL has usually this format: http://moneymgr.corp.com/api", + ), + textInputAction: TextInputAction.next, + ), + Gutter(), + TextField( + controller: tokenIdController, + keyboardType: TextInputType.number, + decoration: const InputDecoration( + labelText: 'Token ID', + helperText: "The ID of the token", + ), + textInputAction: TextInputAction.next, + ), + Gutter(), + TextField( + controller: tokenIdController, + keyboardType: TextInputType.text, + decoration: const InputDecoration( + labelText: 'Token value', + helperText: "The value of the token itself", + ), + textInputAction: TextInputAction.done, + ), + Gutter(), + AppButton(onPressed: onSubmit, label: "Submit"), + Gutter(scaleFactor: 3), + ], + ); + } +} diff --git a/moneymgr_mobile/lib/services/api/account_api.dart b/moneymgr_mobile/lib/services/api/account_api.dart new file mode 100644 index 0000000..39de474 --- /dev/null +++ b/moneymgr_mobile/lib/services/api/account_api.dart @@ -0,0 +1,4 @@ +/// Account API +class AccountAPI { + +} \ No newline at end of file diff --git a/moneymgr_mobile/lib/services/api/api_client.dart b/moneymgr_mobile/lib/services/api/api_client.dart new file mode 100644 index 0000000..ba244f1 --- /dev/null +++ b/moneymgr_mobile/lib/services/api/api_client.dart @@ -0,0 +1,7 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +/// Client API +@riverpod +class ClientAPI { + +} \ No newline at end of file diff --git a/moneymgr_mobile/lib/services/api/api_token.dart b/moneymgr_mobile/lib/services/api/api_token.dart new file mode 100644 index 0000000..b8e388b --- /dev/null +++ b/moneymgr_mobile/lib/services/api/api_token.dart @@ -0,0 +1,16 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'api_token.freezed.dart'; +part 'api_token.g.dart'; + +@freezed +abstract class ApiToken with _$ApiToken { + const factory ApiToken({ + required String apiUrl, + required int tokenId, + required String tokenValue + }) = _ApiToken; + + factory ApiToken.fromJson(Map json) => + _$ApiTokenFromJson(json); +} diff --git a/moneymgr_mobile/lib/services/auth_state.dart b/moneymgr_mobile/lib/services/auth_state.dart index 7f54e11..e9c1347 100644 --- a/moneymgr_mobile/lib/services/auth_state.dart +++ b/moneymgr_mobile/lib/services/auth_state.dart @@ -1,4 +1,4 @@ -import 'package:moneymgr_mobile/routes/login/login_model.dart'; +import 'package:moneymgr_mobile/services/api/api_token.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'; @@ -8,7 +8,7 @@ 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. +/// to the storage through the [setAuthToken] and [logout] methods. @riverpod class CurrentAuthState extends _$CurrentAuthState { @override @@ -18,9 +18,9 @@ class CurrentAuthState extends _$CurrentAuthState { return token != null ? AuthState.authenticated : AuthState.unauthenticated; } - /// Attempts to log in with [data] and saves the token and profile info to storage. + /// Attempts to authenticate with [data] and saves the token and profile info to storage. /// Will invalidate the state if success. - Future login(LoginModel data) async { + Future setAuthToken(ApiToken data) async { // TODO : perform login /*final secureStorage = ref.read(secureStorageProvider).requireValue; final token = await ref.read(apiServiceProvider).login(data); @@ -53,13 +53,12 @@ class CurrentAuthState extends _$CurrentAuthState { } } - /// The possible authentication states of the app. enum AuthState { unknown(redirectPath: homePage, allowedPaths: [homePage]), unauthenticated( redirectPath: authPage, - allowedPaths: [authPage, settingsPage], + allowedPaths: [authPage, manualAuthPage, settingsPage], ), authenticated(redirectPath: homePage, allowedPaths: null); diff --git a/moneymgr_mobile/lib/services/router/router.dart b/moneymgr_mobile/lib/services/router/router.dart index c668301..b8d0dbe 100644 --- a/moneymgr_mobile/lib/services/router/router.dart +++ b/moneymgr_mobile/lib/services/router/router.dart @@ -2,13 +2,12 @@ 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_screen.dart'; +import 'package:moneymgr_mobile/routes/login/manual_auth_screen.dart'; import 'package:moneymgr_mobile/routes/settings/settings_screen.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. @@ -62,9 +61,10 @@ GoRouter router(Ref ref) { GoRoute(path: homePage, builder: (_, __) => const Scaffold()), GoRoute(path: authPage, builder: (_, __) => const LoginScreen()), GoRoute( - path: settingsPage, - builder: (_, __) => const SettingsScreen(), + path: manualAuthPage, + builder: (_, __) => const ManualAuthScreen(), ), + GoRoute(path: settingsPage, builder: (_, __) => const SettingsScreen()), // Configuration for the bottom navigation bar routes. The routes themselves // should be defined in [navigationItems]. Modification to this [ShellRoute] diff --git a/moneymgr_mobile/lib/widgets/app_button.dart b/moneymgr_mobile/lib/widgets/app_button.dart index 229bd04..2121634 100644 --- a/moneymgr_mobile/lib/widgets/app_button.dart +++ b/moneymgr_mobile/lib/widgets/app_button.dart @@ -6,25 +6,23 @@ 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, - }); + 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(); + final (:pending, :snapshot, :hasError) = useAsyncTask(); return FilledButton( onPressed: onPressed == null ? null : () => pending.value = onPressed!(), + style: ButtonStyle( + backgroundColor: hasError ? WidgetStatePropertyAll(Colors.red) : null, + ), child: SeparatedRow( separatorBuilder: () => const GutterSmall(), mainAxisAlignment: MainAxisAlignment.center, diff --git a/moneymgr_mobile/pubspec.lock b/moneymgr_mobile/pubspec.lock index c5f23c1..ddbee67 100644 --- a/moneymgr_mobile/pubspec.lock +++ b/moneymgr_mobile/pubspec.lock @@ -537,7 +537,7 @@ packages: source: hosted version: "5.1.1" logging: - dependency: transitive + dependency: "direct main" description: name: logging sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 diff --git a/moneymgr_mobile/pubspec.yaml b/moneymgr_mobile/pubspec.yaml index 0e7c64c..096d9f7 100644 --- a/moneymgr_mobile/pubspec.yaml +++ b/moneymgr_mobile/pubspec.yaml @@ -61,8 +61,12 @@ dependencies: # Help in models building freezed_annotation: ^3.0.0 + # For JSON serialization json_annotation: ^4.9.0 + # Logger + logging: ^1.3.0 + dev_dependencies: flutter_test: sdk: flutter