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