reorganize the profile page, move My Teams from Homepage to profile add sign out button to popup menu in homepage

This commit is contained in:
2026-03-13 14:56:39 +01:00
parent 780afb0c56
commit cc7faf477b
5 changed files with 126 additions and 132 deletions

View File

@@ -148,3 +148,24 @@ Folgende Dateien wurden in diesem Prompt verändert:
- frontend_splatournament_manager/linux/runner/my_application.cc - frontend_splatournament_manager/linux/runner/my_application.cc
- frontend_splatournament_manager/windows/runner/main.cpp - frontend_splatournament_manager/windows/runner/main.cpp
- frontend_splatournament_manager/windows/runner/Runner.rc - frontend_splatournament_manager/windows/runner/Runner.rc
- Move the sign out button to the popup menu in the homescreen.
Folgende Dateien wurden in diesem Prompt verändert:
- frontend_splatournament_manager/lib/pages/home_page.dart
- frontend_splatournament_manager/lib/pages/settings_page.dart
- Move only the "Meine Teams" list to the settings menu, keep the "Alle Teams" list in the homescreen but get rid of the top tabbar.
Folgende Dateien wurden in diesem Prompt verändert:
- frontend_splatournament_manager/lib/pages/home_page.dart
- frontend_splatournament_manager/lib/pages/settings_page.dart
- frontend_splatournament_manager/lib/widgets/my_teams_widget.dart
- The member count isn't updated in both lists when joining and leaving a team.
Folgende Dateien wurden in diesem Prompt verändert:
- frontend_splatournament_manager/lib/providers/team_provider.dart
- frontend_splatournament_manager/lib/widgets/my_teams_widget.dart
- Rename the settings page to Profile
Folgende Dateien wurden in diesem Prompt verändert:
- frontend_splatournament_manager/lib/pages/settings_page.dart
- frontend_splatournament_manager/lib/pages/home_page.dart

View File

@@ -3,10 +3,10 @@ import 'package:frontend_splatournament_manager/providers/tournament_provider.da
import 'package:frontend_splatournament_manager/providers/team_provider.dart'; import 'package:frontend_splatournament_manager/providers/team_provider.dart';
import 'package:frontend_splatournament_manager/widgets/available_tournament_list.dart'; import 'package:frontend_splatournament_manager/widgets/available_tournament_list.dart';
import 'package:frontend_splatournament_manager/widgets/teams_list_widget.dart'; import 'package:frontend_splatournament_manager/widgets/teams_list_widget.dart';
import 'package:frontend_splatournament_manager/widgets/my_teams_widget.dart';
import 'package:frontend_splatournament_manager/widgets/my_tournaments_carousel.dart'; import 'package:frontend_splatournament_manager/widgets/my_tournaments_carousel.dart';
import 'package:frontend_splatournament_manager/pages/create_tournament_page.dart'; import 'package:frontend_splatournament_manager/pages/create_tournament_page.dart';
import 'package:frontend_splatournament_manager/pages/create_team_page.dart'; import 'package:frontend_splatournament_manager/pages/create_team_page.dart';
import 'package:frontend_splatournament_manager/providers/auth_provider.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@@ -17,48 +17,14 @@ class HomePage extends StatefulWidget {
State<HomePage> createState() => _HomePageState(); State<HomePage> createState() => _HomePageState();
} }
class _HomePageState extends State<HomePage> class _HomePageState extends State<HomePage> {
with SingleTickerProviderStateMixin {
int _selectedIndex = 0; int _selectedIndex = 0;
late TabController _tabController;
@override
void initState() {
super.initState();
_tabController = TabController(length: 2, vsync: this);
}
@override
void dispose() {
_tabController.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final appBarForeground =
Theme.of(context).appBarTheme.foregroundColor ??
Theme.of(context).colorScheme.onSurface;
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text(_selectedIndex == 0 ? 'Turniere' : 'Teams'), title: Text(_selectedIndex == 0 ? 'Turniere' : 'Teams'),
bottom: _selectedIndex == 1
? TabBar(
controller: _tabController,
labelColor: appBarForeground,
unselectedLabelColor: appBarForeground.withValues(alpha: 0.82),
indicatorColor: appBarForeground,
labelStyle: const TextStyle(fontWeight: FontWeight.w700),
unselectedLabelStyle: const TextStyle(
fontWeight: FontWeight.w500,
),
tabs: const [
Tab(text: 'Alle Teams'),
Tab(text: 'Meine Teams'),
],
)
: null,
actions: [ actions: [
IconButton( IconButton(
onPressed: () async { onPressed: () async {
@@ -91,12 +57,24 @@ class _HomePageState extends State<HomePage>
), ),
PopupMenuButton( PopupMenuButton(
onSelected: (value) { onSelected: (value) {
if (value == 1) {
context.go("/settings"); context.go("/settings");
} else if (value == 2) {
Provider.of<AuthProvider>(context, listen: false).logout();
context.go('/login');
}
}, },
offset: const Offset(0, 48), offset: const Offset(0, 48),
itemBuilder: (context) { itemBuilder: (context) {
return [ return [
const PopupMenuItem(value: 1, child: Text('Einstellungen')), const PopupMenuItem(value: 1, child: Text('Profil')),
const PopupMenuItem(
value: 2,
child: Text(
'Abmelden',
style: TextStyle(color: Colors.red),
),
),
]; ];
}, },
), ),
@@ -112,11 +90,8 @@ class _HomePageState extends State<HomePage>
const Expanded(child: AvailableTournamentList()), const Expanded(child: AvailableTournamentList()),
], ],
), ),
// Teams View with tabs // Teams View
TabBarView( const TeamsListWidget(),
controller: _tabController,
children: const [TeamsListWidget(), MyTeamsWidget()],
),
], ],
), ),
bottomNavigationBar: BottomNavigationBar( bottomNavigationBar: BottomNavigationBar(

View File

@@ -1,8 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:frontend_splatournament_manager/widgets/my_teams_widget.dart';
import 'package:frontend_splatournament_manager/widgets/theme_selector_widget.dart'; import 'package:frontend_splatournament_manager/widgets/theme_selector_widget.dart';
import 'package:provider/provider.dart';
import 'package:frontend_splatournament_manager/providers/auth_provider.dart';
import 'package:go_router/go_router.dart';
import '../widgets/profile_widget.dart'; import '../widgets/profile_widget.dart';
@@ -17,28 +15,23 @@ class _SettingsPageState extends State<SettingsPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar(title: const Text('Einstellungen')), appBar: AppBar(title: const Text('Profil')),
body: Column( body: Column(
children: [ children: [
const SizedBox(height: 24), const SizedBox(height: 24),
const ProfileWidget(), const ProfileWidget(),
Column(
mainAxisSize: MainAxisSize.min,
children: [
ThemeSelectorWidget(), ThemeSelectorWidget(),
ListTile( const Padding(
leading: const Icon(Icons.logout, color: Colors.red), padding: EdgeInsets.fromLTRB(16, 16, 16, 4),
title: const Text( child: Align(
'Abmelden', alignment: Alignment.centerLeft,
style: TextStyle(color: Colors.red), child: Text(
'Meine Teams',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
), ),
onTap: () {
Provider.of<AuthProvider>(context, listen: false).logout();
context.go('/login');
},
), ),
],
), ),
const Expanded(child: MyTeamsWidget()),
], ],
), ),
); );

View File

@@ -6,9 +6,12 @@ class TeamProvider extends ChangeNotifier {
final TeamService _teamService = TeamService(); final TeamService _teamService = TeamService();
List<Team> _teams = []; List<Team> _teams = [];
List<Team> _userTeams = [];
Future<List<Team>>? _initialLoadFuture; Future<List<Team>>? _initialLoadFuture;
Future<List<Team>>? _userTeamsFuture;
List<Team> get teams => _teams; List<Team> get teams => _teams;
List<Team> get userTeams => _userTeams;
Future<List<Team>> fetchAllTeams() async { Future<List<Team>> fetchAllTeams() async {
_teams = await _teamService.getAllTeams(); _teams = await _teamService.getAllTeams();
@@ -26,6 +29,17 @@ class TeamProvider extends ChangeNotifier {
return _initialLoadFuture!; return _initialLoadFuture!;
} }
Future<List<Team>> fetchUserTeams() async {
_userTeams = await _teamService.getUserTeams();
notifyListeners();
return _userTeams;
}
Future<List<Team>> ensureUserTeamsLoaded() {
_userTeamsFuture ??= fetchUserTeams();
return _userTeamsFuture!;
}
Future<Team> createTeam({ Future<Team> createTeam({
required String name, required String name,
required String tag, required String tag,
@@ -77,11 +91,19 @@ class TeamProvider extends ChangeNotifier {
Future<void> joinTeam(int teamId) async { Future<void> joinTeam(int teamId) async {
await _teamService.joinTeam(teamId); await _teamService.joinTeam(teamId);
notifyListeners(); await _refreshMembership();
} }
Future<void> leaveTeam(int teamId) async { Future<void> leaveTeam(int teamId) async {
await _teamService.leaveTeam(teamId); await _teamService.leaveTeam(teamId);
await _refreshMembership();
}
Future<void> _refreshMembership() async {
await Future.wait([
_teamService.getAllTeams().then((t) => _teams = t),
_teamService.getUserTeams().then((t) => _userTeams = t),
]);
notifyListeners(); notifyListeners();
} }

View File

@@ -3,51 +3,35 @@ import 'package:frontend_splatournament_manager/models/team.dart';
import 'package:frontend_splatournament_manager/providers/team_provider.dart'; import 'package:frontend_splatournament_manager/providers/team_provider.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
class MyTeamsWidget extends StatefulWidget { class MyTeamsWidget extends StatelessWidget {
const MyTeamsWidget({super.key}); const MyTeamsWidget({super.key});
@override
State<MyTeamsWidget> createState() => _MyTeamsWidgetState();
}
class _MyTeamsWidgetState extends State<MyTeamsWidget> {
late Future<List<Team>> _myTeamsFuture;
@override
void initState() {
super.initState();
_loadMyTeams();
}
void _loadMyTeams() {
_myTeamsFuture = Provider.of<TeamProvider>(
context,
listen: false,
).getUserTeams();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Consumer<TeamProvider>(
builder: (context, provider, _) {
return FutureBuilder<List<Team>>( return FutureBuilder<List<Team>>(
future: _myTeamsFuture, future: provider.ensureUserTeamsLoaded(),
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) { final teams = provider.userTeams;
if (snapshot.connectionState == ConnectionState.waiting &&
teams.isEmpty) {
return const Center(child: CircularProgressIndicator()); return const Center(child: CircularProgressIndicator());
} }
if (snapshot.hasError) { if (snapshot.hasError && teams.isEmpty) {
return Center( return Center(
child: Text( child: Text(
'Fehler: ${snapshot.error.toString().replaceFirst('Exception: ', '')}', "Fehler: ${snapshot.error.toString().replaceFirst("Exception: ", "")}",
), ),
); );
} }
final teams = snapshot.data ?? [];
if (teams.isEmpty) { if (teams.isEmpty) {
return const Center( return const Center(
child: Text( child: Text(
'Du bist noch in keinem Team.\nTritt einem Team im Tab Alle Teams bei.', 'Du bist noch in keinem Team.\nTritt einem Team unter Teams bei.',
), ),
); );
} }
@@ -55,19 +39,21 @@ class _MyTeamsWidgetState extends State<MyTeamsWidget> {
return ListView.builder( return ListView.builder(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
itemCount: teams.length, itemCount: teams.length,
itemBuilder: (context, index) => _buildTeamCard(teams[index]), itemBuilder: (context, index) =>
_buildTeamCard(context, teams[index]),
);
},
); );
}, },
); );
} }
Widget _buildTeamCard(Team team) { Widget _buildTeamCard(BuildContext context, Team team) {
final memberCountText = team.memberCount != null final memberCountText = team.memberCount != null
? '${team.memberCount}/4 Mitglieder' ? "${team.memberCount}/4 Mitglieder"
: 'Keine Mitglieder'; : "Keine Mitglieder";
final description = team.description.isEmpty final description =
? 'Keine Beschreibung' team.description.isEmpty ? "Keine Beschreibung" : team.description;
: team.description;
return Card( return Card(
margin: const EdgeInsets.only(bottom: 12), margin: const EdgeInsets.only(bottom: 12),
@@ -80,50 +66,47 @@ class _MyTeamsWidgetState extends State<MyTeamsWidget> {
), ),
trailing: IconButton( trailing: IconButton(
icon: const Icon(Icons.logout, color: Colors.red), icon: const Icon(Icons.logout, color: Colors.red),
onPressed: () => _leaveTeam(team), onPressed: () => _leaveTeam(context, team),
), ),
), ),
); );
} }
Future<void> _leaveTeam(Team team) async { Future<void> _leaveTeam(BuildContext context, Team team) async {
final confirmed = await showDialog<bool>( final confirmed = await showDialog<bool>(
context: context, context: context,
builder: (context) => AlertDialog( builder: (context) => AlertDialog(
title: const Text('Team verlassen?'), title: const Text("Team verlassen?"),
content: Text('Soll "${team.name}" verlassen werden?'), content: Text('Soll "${team.name}" verlassen werden?'),
actions: [ actions: [
TextButton( TextButton(
onPressed: () => Navigator.pop(context, false), onPressed: () => Navigator.pop(context, false),
child: const Text('Abbrechen'), child: const Text("Abbrechen"),
), ),
TextButton( TextButton(
onPressed: () => Navigator.pop(context, true), onPressed: () => Navigator.pop(context, true),
child: const Text('Verlassen', style: TextStyle(color: Colors.red)), child: const Text("Verlassen", style: TextStyle(color: Colors.red)),
), ),
], ],
), ),
); );
if (confirmed == true && mounted) { if (confirmed == true && context.mounted) {
try { try {
await Provider.of<TeamProvider>( await Provider.of<TeamProvider>(context, listen: false).leaveTeam(
context, team.id,
listen: false, );
).leaveTeam(team.id); if (context.mounted) {
if (mounted) {
ScaffoldMessenger.of( ScaffoldMessenger.of(
context, context,
).showSnackBar(const SnackBar(content: Text('Team verlassen'))); ).showSnackBar(const SnackBar(content: Text("Team verlassen")));
_loadMyTeams();
setState(() {});
} }
} catch (e) { } catch (e) {
if (mounted) { if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: Text( content: Text(
'Fehler: ${e.toString().replaceFirst('Exception: ', '')}', "Fehler: ${e.toString().replaceFirst("Exception: ", "")}",
), ),
), ),
); );