Implement Login Page, Add App Initialization, Router, and Theme Management
This commit is contained in:
79
finlog_app/app/lib/app/router.dart
Normal file
79
finlog_app/app/lib/app/router.dart
Normal file
@@ -0,0 +1,79 @@
|
||||
import 'package:app/features/login/pages/login_page.dart';
|
||||
import 'package:app/main.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:animations/animations.dart';
|
||||
|
||||
/// Centralized route names and paths.
|
||||
/// Splash is not routable (handled before router creation).
|
||||
enum AppRoute {
|
||||
login('/login'),
|
||||
home('/home'),
|
||||
inventory('/inventory'),
|
||||
inventoryAdd('/inventory/add'),
|
||||
budget('/budget'),
|
||||
expenses('/expenses');
|
||||
|
||||
const AppRoute(this.path);
|
||||
|
||||
final String path;
|
||||
}
|
||||
|
||||
/// Generic animated page using the `animations` package.
|
||||
/// Change [transitionType] to vertical / scaled globally if desired.
|
||||
class AnimatedPage<T> extends CustomTransitionPage<T> {
|
||||
AnimatedPage({
|
||||
required LocalKey super.key,
|
||||
required super.child,
|
||||
Duration duration = const Duration(milliseconds: 400),
|
||||
SharedAxisTransitionType transitionType =
|
||||
SharedAxisTransitionType.horizontal,
|
||||
}) : super(
|
||||
transitionDuration: duration,
|
||||
transitionsBuilder: (context, animation, secondary, child) {
|
||||
return SharedAxisTransition(
|
||||
animation: animation,
|
||||
secondaryAnimation: secondary,
|
||||
transitionType: transitionType,
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Helper to create a GoRoute with the global AnimatedPage transition.
|
||||
/// Use this for EVERY route to keep transitions consistent.
|
||||
GoRoute _r(
|
||||
AppRoute route,
|
||||
Widget Function(BuildContext, GoRouterState) builder, {
|
||||
List<RouteBase> routes = const [],
|
||||
SharedAxisTransitionType transitionType = SharedAxisTransitionType.horizontal,
|
||||
}) {
|
||||
return GoRoute(
|
||||
path: route.path,
|
||||
name: route.name,
|
||||
pageBuilder: (context, state) => AnimatedPage<void>(
|
||||
key: state.pageKey,
|
||||
child: builder(context, state),
|
||||
transitionType: transitionType,
|
||||
),
|
||||
routes: routes,
|
||||
);
|
||||
}
|
||||
|
||||
/// Builds the application-wide router. The initial route is decided before creation.
|
||||
GoRouter buildAppRouter(AppRoute initialRoute) {
|
||||
return GoRouter(
|
||||
initialLocation: initialRoute.path,
|
||||
routes: [
|
||||
_r(AppRoute.login, (ctx, st) => const LoginPage()),
|
||||
_r(AppRoute.home, (ctx, st) => const MyApp()),
|
||||
// Add feature routes here, all getting the same animation:
|
||||
// _r(AppRoute.inventory, (ctx, st) => const InventoryPage(), routes: [
|
||||
// _r(AppRoute.inventoryAdd, (ctx, st) => const AddItemPage()),
|
||||
// ]),
|
||||
// _r(AppRoute.budget, (ctx, st) => const BudgetPage()),
|
||||
// _r(AppRoute.expenses, (ctx, st) => const ExpensesPage()),
|
||||
],
|
||||
);
|
||||
}
|
||||
12
finlog_app/app/lib/app/startup/domain/initialize_app.dart
Normal file
12
finlog_app/app/lib/app/startup/domain/initialize_app.dart
Normal file
@@ -0,0 +1,12 @@
|
||||
import 'package:app/app/router.dart';
|
||||
|
||||
class InitializeAppUseCase {
|
||||
Future<AppRoute> call() async {
|
||||
// Beispiel: ggf. weitere Init-Schritte
|
||||
// await _migrateIfNeeded(prefs);
|
||||
// await _warmupCaches();
|
||||
|
||||
// return auth.isLoggedIn ? AppRoute.home : AppRoute.login;
|
||||
return AppRoute.login;
|
||||
}
|
||||
}
|
||||
58
finlog_app/app/lib/app/theme.dart
Normal file
58
finlog_app/app/lib/app/theme.dart
Normal file
@@ -0,0 +1,58 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:fluttery/fluttery.dart';
|
||||
import 'package:fluttery/preferences.dart';
|
||||
|
||||
/// Controls the current theme of the application.
|
||||
/// Loads the theme from Preferences or falls back to system default.
|
||||
class ThemeController extends ChangeNotifier {
|
||||
final Preferences _prefs;
|
||||
|
||||
// vars
|
||||
ThemeMode _themeMode = ThemeMode.system;
|
||||
|
||||
/// Constructor
|
||||
ThemeController() : _prefs = App.service<Preferences>();
|
||||
|
||||
/// Loads theme from Preferences (or defaults to system).
|
||||
Future<void> init() async {
|
||||
final saved = await _prefs.getString('theme');
|
||||
if (saved == null) {
|
||||
_themeMode = ThemeMode.system;
|
||||
} else {
|
||||
_themeMode = _fromString(saved);
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Sets theme and persists it in Preferences.
|
||||
Future<void> setTheme(ThemeMode mode) async {
|
||||
_themeMode = mode;
|
||||
notifyListeners();
|
||||
await _prefs.setString('theme', _toString(mode));
|
||||
}
|
||||
|
||||
ThemeMode _fromString(String value) {
|
||||
switch (value) {
|
||||
case 'light':
|
||||
return ThemeMode.light;
|
||||
case 'dark':
|
||||
return ThemeMode.dark;
|
||||
case 'system':
|
||||
default:
|
||||
return ThemeMode.system;
|
||||
}
|
||||
}
|
||||
|
||||
String _toString(ThemeMode mode) {
|
||||
switch (mode) {
|
||||
case ThemeMode.light:
|
||||
return 'light';
|
||||
case ThemeMode.dark:
|
||||
return 'dark';
|
||||
case ThemeMode.system:
|
||||
return 'system';
|
||||
}
|
||||
}
|
||||
|
||||
ThemeMode get themeMode => _themeMode;
|
||||
}
|
||||
82
finlog_app/app/lib/features/login/pages/login_page.dart
Normal file
82
finlog_app/app/lib/features/login/pages/login_page.dart
Normal file
@@ -0,0 +1,82 @@
|
||||
import 'package:app/app/router.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
class LoginPage extends StatefulWidget {
|
||||
const LoginPage({super.key});
|
||||
|
||||
@override
|
||||
State<LoginPage> createState() => _LoginPageState();
|
||||
}
|
||||
|
||||
class _LoginPageState extends State<LoginPage> {
|
||||
bool _loading = false;
|
||||
|
||||
Future<void> _simulateLogin() async {
|
||||
if (_loading) return;
|
||||
setState(() => _loading = true);
|
||||
|
||||
// Simulate latency, perform demo login, then go home.
|
||||
await Future<void>.delayed(const Duration(milliseconds: 900));
|
||||
// await App.service<Auth>().login('demo', 'demo');
|
||||
|
||||
if (!mounted) return;
|
||||
context.go(AppRoute.home.path);
|
||||
|
||||
setState(() => _loading = false);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Login')),
|
||||
body: Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 360),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const FlutterLogo(size: 64),
|
||||
const SizedBox(height: 24),
|
||||
Text('Please sign in', style: theme.textTheme.titleMedium),
|
||||
const SizedBox(height: 24),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
onPressed: _loading ? null : _simulateLogin,
|
||||
child: AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 250),
|
||||
transitionBuilder: (child, anim) =>
|
||||
FadeTransition(opacity: anim, child: child),
|
||||
child: _loading
|
||||
? const SizedBox(
|
||||
key: ValueKey('loading'),
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
)
|
||||
: const Text('Login', key: ValueKey('text')),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 250),
|
||||
child: _loading
|
||||
? Padding(
|
||||
padding: const EdgeInsets.only(top: 4),
|
||||
child: Text(
|
||||
'Signing you in…',
|
||||
style: theme.textTheme.bodySmall,
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,6 @@
|
||||
import 'package:app/app/router.dart';
|
||||
import 'package:app/app/startup/domain/initialize_app.dart';
|
||||
import 'package:app/app/theme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:fluttery/fluttery.dart';
|
||||
import 'package:fluttery/logger.dart';
|
||||
@@ -14,7 +17,25 @@ Future<void> main() async {
|
||||
final logger = App.service<Logger>();
|
||||
logger.debug("[MAIN] Registered all default services");
|
||||
|
||||
runApp(const MyApp());
|
||||
// Run initialization before building router
|
||||
final init = InitializeAppUseCase();
|
||||
final startRoute = await init();
|
||||
|
||||
final themeController = ThemeController();
|
||||
await themeController.init();
|
||||
|
||||
runApp(
|
||||
AnimatedBuilder(
|
||||
animation: themeController,
|
||||
builder: (context, _) => MaterialApp.router(
|
||||
title: 'App',
|
||||
theme: ThemeData.light(),
|
||||
darkTheme: ThemeData.dark(),
|
||||
themeMode: themeController.themeMode,
|
||||
routerConfig: buildAppRouter(startRoute),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
@@ -76,11 +97,7 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||
'$_counter',
|
||||
style: Theme.of(context).textTheme.headlineMedium,
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
},
|
||||
child: Text("Print workers"),
|
||||
),
|
||||
TextButton(onPressed: () {}, child: Text("Print workers")),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user