add data service and homepage data retrieval

This commit is contained in:
2025-07-24 22:59:50 +01:00
parent e6ed9d01c2
commit 652e83bf38
8 changed files with 157 additions and 20 deletions

1
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1 @@
{}

Binary file not shown.

View File

@@ -167,7 +167,11 @@ class _LoginPanelContentState extends State<LoginPanelContent> {
} }
class RegisterPanelContent extends StatelessWidget { 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 VoidCallback onBack;
final AuthService authService; final AuthService authService;
void register() {} void register() {}

View File

@@ -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<DataService>();
final auth = context.watch<AuthService>();
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()),
],
),
),
],
),
],
);
}
}

View File

@@ -2,8 +2,10 @@ import 'package:flutter/material.dart';
import 'package:mileograph_flutter/services/apiService.dart'; import 'package:mileograph_flutter/services/apiService.dart';
import 'package:mileograph_flutter/services/authservice.dart'; import 'package:mileograph_flutter/services/authservice.dart';
import 'package:mileograph_flutter/services/dataService.dart'; import 'package:mileograph_flutter/services/dataService.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'components/login/login.dart'; import 'components/login/login.dart';
import 'components/pages/dashboard.dart';
late ApiService api; late ApiService api;
@@ -39,13 +41,13 @@ void main() {
} }
class AppRoot extends StatelessWidget { class AppRoot extends StatelessWidget {
const AppRoot({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Consumer<AuthService>( return Consumer<AuthService>(
builder: (context, auth, child) { builder: (context, auth, child) {
return auth.isLoggedIn return auth.isLoggedIn ? MyHomePage() : LoginScreen();
? MyHomePage(title: "Mileograph")
: LoginScreen();
}, },
); );
} }
@@ -92,7 +94,7 @@ class MyApp extends StatelessWidget {
} }
class MyHomePage extends StatefulWidget { 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 // 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 // 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 // used by the build method of the State. Fields in a Widget subclass are
// always marked "final". // always marked "final".
final String title;
@override @override
State<MyHomePage> createState() => _MyHomePageState(); State<MyHomePage> createState() => _MyHomePageState();
} }
@@ -120,14 +120,23 @@ class _MyHomePageState extends State<MyHomePage> {
]; ];
bool loggedIn = false; bool loggedIn = false;
bool _fetched = false;
@override @override
void didChangeDependencies() { void didChangeDependencies() {
super.didChangeDependencies(); super.didChangeDependencies();
if (!_fetched) {
_fetched = true;
WidgetsBinding.instance.addPostFrameCallback((_) {
final data = context.read<DataService>(); final data = context.read<DataService>();
final auth = context.read<AuthService>();
api.setTokenProvider(() => auth.token);
if (data.homepageStats == null) { if (data.homepageStats == null) {
data.fetchHomepageStats(); data.fetchHomepageStats();
} }
});
}
} }
@override @override
@@ -135,6 +144,7 @@ class _MyHomePageState extends State<MyHomePage> {
Widget currentPage; Widget currentPage;
final data = context.watch<DataService>(); final data = context.watch<DataService>();
final auth = context.read<AuthService>();
if (data.homepageStats != null) { if (data.homepageStats != null) {
currentPage = contentPages[pageIndex]; currentPage = contentPages[pageIndex];
@@ -160,9 +170,26 @@ class _MyHomePageState extends State<MyHomePage> {
backgroundColor: Theme.of(context).colorScheme.inversePrimary, backgroundColor: Theme.of(context).colorScheme.inversePrimary,
// Here we take the value from the MyHomePage object that was created by // 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. // 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: [ actions: [
IconButton(onPressed: null, icon: Icon(Icons.account_circle)), IconButton(onPressed: null, icon: Icon(Icons.account_circle)),
IconButton(onPressed: auth.logout, icon: Icon(Icons.logout)),
], ],
), ),
bottomNavigationBar: NavigationBar( bottomNavigationBar: NavigationBar(
@@ -189,10 +216,3 @@ class _MyHomePageState extends State<MyHomePage> {
); );
} }
} }
class Dashboard extends StatelessWidget {
Dashboard({super.key});
Widget build(BuildContext context) {
return Text("Logged in");
}
}

View File

@@ -1,6 +1,5 @@
import 'dart:convert'; import 'dart:convert';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:mileograph_flutter/services/authservice.dart';
typedef TokenProvider = String? Function(); typedef TokenProvider = String? Function();

View File

@@ -1,4 +1,3 @@
import 'package:provider/provider.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:mileograph_flutter/objects/objects.dart'; import 'package:mileograph_flutter/objects/objects.dart';
import 'package:mileograph_flutter/services/apiService.dart'; import 'package:mileograph_flutter/services/apiService.dart';
@@ -65,4 +64,9 @@ class AuthService extends ChangeNotifier {
email: userResponse['email'], email: userResponse['email'],
); );
} }
void logout() {
_user = null;
notifyListeners();
}
} }

View File

@@ -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<void> 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;
}
}