diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/lib/assets/fonts/Tomatoes-O8L8.ttf b/lib/assets/fonts/Tomatoes-O8L8.ttf new file mode 100644 index 0000000..b82f99a Binary files /dev/null and b/lib/assets/fonts/Tomatoes-O8L8.ttf differ diff --git a/lib/components/login/login.dart b/lib/components/login/login.dart index 5e3e691..229e693 100644 --- a/lib/components/login/login.dart +++ b/lib/components/login/login.dart @@ -167,7 +167,11 @@ class _LoginPanelContentState extends State { } class RegisterPanelContent extends StatelessWidget { - const RegisterPanelContent({required this.onBack, required this.authService}); + const RegisterPanelContent({ + super.key, + required this.onBack, + required this.authService, + }); final VoidCallback onBack; final AuthService authService; void register() {} diff --git a/lib/components/pages/dashboard.dart b/lib/components/pages/dashboard.dart new file mode 100644 index 0000000..8924fa4 --- /dev/null +++ b/lib/components/pages/dashboard.dart @@ -0,0 +1,58 @@ +import 'package:flutter/material.dart'; +import 'package:mileograph_flutter/services/authservice.dart'; +import 'package:mileograph_flutter/services/dataService.dart'; + +import 'package:provider/provider.dart'; + +class Dashboard extends StatelessWidget { + const Dashboard({super.key}); + @override + Widget build(BuildContext context) { + final data = context.watch(); + final auth = context.watch(); + return DashboardHeader(auth: auth, data: data); + } +} + +class DashboardHeader extends StatelessWidget { + const DashboardHeader({super.key, required this.auth, required this.data}); + + final AuthService auth; + final DataService data; + + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Column( + children: [ + Text( + auth.fullName ?? "Unknown", + style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), + ), + Text.rich( + TextSpan( + children: [ + TextSpan(text: "Total Mileage: "), + TextSpan( + text: data.homepageStats?.totalMileage.toString() ?? "0", + ), + ], + ), + ), + Text.rich( + TextSpan( + children: [ + TextSpan(text: DateTime.now().year.toString()), + TextSpan(text: " Mileage: "), + TextSpan(text: data.getMileageForCurrentYear().toString()), + ], + ), + ), + ], + ), + ], + ); + } +} diff --git a/lib/main.dart b/lib/main.dart index c497338..911ae18 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,8 +2,10 @@ import 'package:flutter/material.dart'; import 'package:mileograph_flutter/services/apiService.dart'; import 'package:mileograph_flutter/services/authservice.dart'; import 'package:mileograph_flutter/services/dataService.dart'; + import 'package:provider/provider.dart'; import 'components/login/login.dart'; +import 'components/pages/dashboard.dart'; late ApiService api; @@ -39,13 +41,13 @@ void main() { } class AppRoot extends StatelessWidget { + const AppRoot({super.key}); + @override Widget build(BuildContext context) { return Consumer( builder: (context, auth, child) { - return auth.isLoggedIn - ? MyHomePage(title: "Mileograph") - : LoginScreen(); + return auth.isLoggedIn ? MyHomePage() : LoginScreen(); }, ); } @@ -92,7 +94,7 @@ class MyApp extends StatelessWidget { } class MyHomePage extends StatefulWidget { - const MyHomePage({super.key, required this.title}); + const MyHomePage({super.key}); // This widget is the home page of your application. It is stateful, meaning // that it has a State object (defined below) that contains fields that affect @@ -103,8 +105,6 @@ class MyHomePage extends StatefulWidget { // used by the build method of the State. Fields in a Widget subclass are // always marked "final". - final String title; - @override State createState() => _MyHomePageState(); } @@ -120,13 +120,22 @@ class _MyHomePageState extends State { ]; bool loggedIn = false; + bool _fetched = false; @override void didChangeDependencies() { super.didChangeDependencies(); - final data = context.read(); - if (data.homepageStats == null) { - data.fetchHomepageStats(); + + if (!_fetched) { + _fetched = true; + WidgetsBinding.instance.addPostFrameCallback((_) { + final data = context.read(); + final auth = context.read(); + api.setTokenProvider(() => auth.token); + if (data.homepageStats == null) { + data.fetchHomepageStats(); + } + }); } } @@ -135,6 +144,7 @@ class _MyHomePageState extends State { Widget currentPage; final data = context.watch(); + final auth = context.read(); if (data.homepageStats != null) { currentPage = contentPages[pageIndex]; @@ -160,9 +170,26 @@ class _MyHomePageState extends State { backgroundColor: Theme.of(context).colorScheme.inversePrimary, // Here we take the value from the MyHomePage object that was created by // the App.build method, and use it to set our appbar title. - title: Text(widget.title), + title: Text.rich( + TextSpan( + children: [ + TextSpan(text: "Mile"), + TextSpan( + text: "O", + style: TextStyle(color: Colors.red), + ), + TextSpan(text: "graph"), + ], + style: TextStyle( + decoration: TextDecoration.none, + color: Colors.white, + fontFamily: "Tomatoes", + ), + ), + ), actions: [ IconButton(onPressed: null, icon: Icon(Icons.account_circle)), + IconButton(onPressed: auth.logout, icon: Icon(Icons.logout)), ], ), bottomNavigationBar: NavigationBar( @@ -189,10 +216,3 @@ class _MyHomePageState extends State { ); } } - -class Dashboard extends StatelessWidget { - Dashboard({super.key}); - Widget build(BuildContext context) { - return Text("Logged in"); - } -} diff --git a/lib/services/apiService.dart b/lib/services/apiService.dart index 933fabb..5f0baa4 100644 --- a/lib/services/apiService.dart +++ b/lib/services/apiService.dart @@ -1,6 +1,5 @@ import 'dart:convert'; import 'package:http/http.dart' as http; -import 'package:mileograph_flutter/services/authservice.dart'; typedef TokenProvider = String? Function(); diff --git a/lib/services/authservice.dart b/lib/services/authservice.dart index d204009..23e7366 100644 --- a/lib/services/authservice.dart +++ b/lib/services/authservice.dart @@ -1,4 +1,3 @@ -import 'package:provider/provider.dart'; import 'package:flutter/foundation.dart'; import 'package:mileograph_flutter/objects/objects.dart'; import 'package:mileograph_flutter/services/apiService.dart'; @@ -65,4 +64,9 @@ class AuthService extends ChangeNotifier { email: userResponse['email'], ); } + + void logout() { + _user = null; + notifyListeners(); + } } diff --git a/lib/services/dataService.dart b/lib/services/dataService.dart new file mode 100644 index 0000000..6a07cd0 --- /dev/null +++ b/lib/services/dataService.dart @@ -0,0 +1,51 @@ +import 'package:flutter/foundation.dart'; +import 'package:mileograph_flutter/objects/objects.dart'; +import 'package:mileograph_flutter/services/apiService.dart'; // assumes you've moved HomepageStats + submodels to a separate file + +class DataService extends ChangeNotifier { + final ApiService api; + + DataService({required this.api}); + + HomepageStats? _homepageStats; + HomepageStats? get homepageStats => _homepageStats; + + bool _isHomepageLoading = false; + bool get isHomepageLoading => _isHomepageLoading; + + Future fetchHomepageStats() async { + _isHomepageLoading = true; + notifyListeners(); + + try { + final json = await api.get('/stats/homepage'); + _homepageStats = HomepageStats.fromJson(json); + } catch (e) { + debugPrint('Failed to fetch homepage stats: $e'); + _homepageStats = null; + } finally { + _isHomepageLoading = false; + notifyListeners(); + } + } + + void clear() { + _homepageStats = null; + notifyListeners(); + } + + double getMileageForCurrentYear() { + final currentYear = DateTime.now().year; + return getMileageForYear(currentYear) ?? 0; + } + + double? getMileageForYear(int year) { + return _homepageStats?.yearlyMileage + .firstWhere( + (entry) => entry.year == year, + orElse: () => YearlyMileage(year: null, mileage: 0), + ) + .mileage ?? + 0; + } +}