WIP: Add mobile application #47

Draft
pierre wants to merge 12 commits from mobile-app into main
160 changed files with 6796 additions and 18 deletions
Showing only changes of commit ce1c175c62 - Show all commits

View File

@ -0,0 +1,36 @@
import 'package:flextras/flextras.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gutter/flutter_gutter.dart';
import 'package:go_router/go_router.dart';
import '../../services/router/routes_list.dart';
class BaseAuthPage extends StatelessWidget {
final List<Widget> children;
const BaseAuthPage({super.key, required this.children});
@override
Widget build(BuildContext context) {
void onSettingsPressed() => context.push(settingsPage);
return Scaffold(
appBar: AppBar(
title: const Text('MoneyMgr'),
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: children,
),
);
}
}

View File

@ -1,86 +1,61 @@
import 'package:flextras/flextras.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_gutter/flutter_gutter.dart'; import 'package:flutter_gutter/flutter_gutter.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:moneymgr_mobile/routes/login/login_model.dart'; import 'package:moneymgr_mobile/routes/login/base_auth_page.dart';
import 'package:moneymgr_mobile/services/router/routes_list.dart'; import 'package:moneymgr_mobile/services/router/routes_list.dart';
import 'package:moneymgr_mobile/widgets/app_button.dart';
import '../../../services/auth_state.dart';
import '../../../utils/extensions.dart';
class LoginScreen extends HookConsumerWidget { class LoginScreen extends HookConsumerWidget {
const LoginScreen({super.key}); const LoginScreen({super.key});
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final isPasswordVisible = useState(false); return BaseAuthPage(
children: [
final usernameController = useTextEditingController(); Gutter(scaleFactor: 3),
final passwordController = useTextEditingController(); Text(
"This application requires a token from MoneyMgr to be used.\n\nPlease create a token on your Money Manager instance and make sure to click on \"For mobile app\" button. You can then enter here generated credentials.",
void onSettingsPressed() => context.push(settingsPage); textAlign: TextAlign.justify,
),
Future<void> onLoginPressed() async { Expanded(child: Container()),
try { _LoginChoice(
await ref.read(currentAuthStateProvider.notifier).login(LoginModel( route: manualAuthPage,
username: usernameController.text, icon: Icons.edit_document,
password: passwordController.text, label: "Enter manually authentication information",
)); ),
} on Exception catch (e) { _LoginChoice(
if (!context.mounted) return; route: manualAuthPage,
context.showTextSnackBar(e.toString()); icon: Icons.qr_code_2,
} label: "Scan authentication Qr Code",
} ),
Gutter(scaleFactor: 3),
return Scaffold( ],
appBar: AppBar( );
title: const Text('MoneyMgr'), }
actions: [ }
IconButton(
onPressed: onSettingsPressed, class _LoginChoice extends StatelessWidget {
icon: const Icon(Icons.settings), const _LoginChoice({
), super.key,
], required this.route,
), required this.label,
body: SeparatedColumn( required this.icon,
padding: EdgeInsets.all(context.gutter), });
separatorBuilder: () => const Gutter(),
mainAxisAlignment: MainAxisAlignment.center, final String route;
crossAxisAlignment: CrossAxisAlignment.stretch, final String label;
children: [ final IconData icon;
TextField(
controller: usernameController, @override
decoration: const InputDecoration(labelText: 'Username'), Widget build(BuildContext context) {
textInputAction: TextInputAction.next, return FilledButton(
), onPressed: () => context.push(route),
TextField( style: ButtonStyle(
controller: passwordController, padding: WidgetStatePropertyAll(
decoration: InputDecoration( EdgeInsetsGeometry.symmetric(vertical: 20.0, horizontal: 30.0),
labelText: 'Password', ),
suffixIcon: IconButton( ),
icon: Icon( child: Row(children: [Icon(icon, size: 25.0), Gutter(), Text(label)]),
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

@ -1,7 +1,11 @@
/// Base home page
const homePage = "/"; const homePage = "/";
/// Authentication path /// Authentication path
const authPage = "/login"; const authPage = "/login";
/// Manual authentication
const manualAuthPage = "/login/manual";
/// Settings path /// Settings path
const settingsPage = "/settings"; const settingsPage = "/settings";