From bcc330eb5ca06028893086dfff189b7f833a8f9f Mon Sep 17 00:00:00 2001 From: Tim Kainz Date: Wed, 4 Mar 2026 23:48:09 +0100 Subject: [PATCH] Improved Tournament Detail View and added Refresh button to Home Page --- .../lib/pages/home_page.dart | 24 +++- .../lib/pages/tournament_detail_page.dart | 113 +++++++++++++++--- .../lib/state_provider.dart | 4 +- .../widgets/available_tournament_list.dart | 105 +++++++++++----- 4 files changed, 193 insertions(+), 53 deletions(-) diff --git a/frontend_splatournament_manager/lib/pages/home_page.dart b/frontend_splatournament_manager/lib/pages/home_page.dart index 07b6fb2..1d10f24 100644 --- a/frontend_splatournament_manager/lib/pages/home_page.dart +++ b/frontend_splatournament_manager/lib/pages/home_page.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; +import 'package:frontend_splatournament_manager/state_provider.dart'; import 'package:frontend_splatournament_manager/widgets/available_tournament_list.dart'; import 'package:go_router/go_router.dart'; +import 'package:provider/provider.dart'; class HomePage extends StatelessWidget { const HomePage({super.key}); @@ -11,21 +13,31 @@ class HomePage extends StatelessWidget { appBar: AppBar( title: Text("Splatournament"), actions: [ + Consumer( + builder: + (BuildContext context, StateProvider value, Widget? child) { + return IconButton( + onPressed: () { + value.notifyState(); + }, + icon: Icon(Icons.refresh), + ); + }, + ), PopupMenuButton( onSelected: (value) { context.go("/settings"); }, itemBuilder: (context) { - return [PopupMenuItem(value: 1,child: Text("Settings"),)]; + return [PopupMenuItem(value: 1, child: Text("Settings"))]; }, ), ], ), - body: Column( - children: [ - AvailableTournamentList(), - ], - ) + body: Container( + padding: EdgeInsets.fromLTRB(0, 12, 0, 24), + child: Column(children: [Spacer(), AvailableTournamentList()]), + ), ); } } diff --git a/frontend_splatournament_manager/lib/pages/tournament_detail_page.dart b/frontend_splatournament_manager/lib/pages/tournament_detail_page.dart index 25ffc6f..bd909cd 100644 --- a/frontend_splatournament_manager/lib/pages/tournament_detail_page.dart +++ b/frontend_splatournament_manager/lib/pages/tournament_detail_page.dart @@ -1,23 +1,108 @@ import 'package:flutter/material.dart'; -import 'package:frontend_splatournament_manager/state_provider.dart'; -import 'package:provider/provider.dart'; +import 'package:frontend_splatournament_manager/models/tournament.dart'; -class TournamentDetailPage extends StatelessWidget { - final int tournamentId; - const TournamentDetailPage({super.key, required this.tournamentId}); +class TournamentDetailPage extends StatefulWidget { + final Tournament tournament; + + const TournamentDetailPage({super.key, required this.tournament}); + + @override + State createState() => _TournamentDetailPageState(); +} + +class _TournamentDetailPageState extends State { + bool isShowingTeams = false; @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar(title: Text("Tournament"),), - body: Consumer(builder: (BuildContext context, StateProvider value, Widget? child) { - var tournament = value.availableTournaments.where((x) => x.id == tournamentId).firstOrNull; - if(tournament == null){ - return Center(child: Text("Tournament not found!")); - } - return Text("${tournament.maxTeamAmount}"); - },) + appBar: AppBar( + title: Text("Tournament"), + backgroundColor: Theme.of(context).colorScheme.surface.withAlpha(148), + elevation: 2, + actions: [ + IconButton( + onPressed: () { + setState(() { + isShowingTeams = !isShowingTeams; + }); + }, + icon: Icon(Icons.group), + ), + ], + ), + extendBodyBehindAppBar: true, + body: Column( + children: [ + DetailHeader( + tournament: widget.tournament, + onTeamsChipClicked: () { + setState(() { + isShowingTeams = !isShowingTeams; + }); + }, + ), + Builder( + builder: (context) { + // Demo Content + if (isShowingTeams) { + return Text("Teams"); + } + return Text("Not Teams"); + }, + ), + ], + ), ); } +} -} \ No newline at end of file +class DetailHeader extends StatelessWidget { + final Tournament tournament; + final Function onTeamsChipClicked; + + const DetailHeader({ + super.key, + required this.tournament, + required this.onTeamsChipClicked, + }); + + @override + Widget build(BuildContext context) { + return SizedBox( + height: 350, + width: double.maxFinite, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadiusDirectional.vertical( + bottom: Radius.circular(8), + ), + color: Colors.red, + image: DecorationImage( + fit: BoxFit.cover, + // Currently a demo image + image: NetworkImage( + "https://flutter.dev/assets/image_1.w635.f71cbb614cd16a40bfb87e128278227c.png", + ), + ), + ), + padding: EdgeInsets.fromLTRB(16, 0, 0, 12), + child: Column( + verticalDirection: VerticalDirection.up, + children: [ + Row( + children: [ + InputChip( + onPressed: () => onTeamsChipClicked(), + label: Text( + "${tournament.currentTeamAmount} out of ${tournament.maxTeamAmount} Teams", + ), + ), + ], + ), + ], + ), + ), + ); + } +} diff --git a/frontend_splatournament_manager/lib/state_provider.dart b/frontend_splatournament_manager/lib/state_provider.dart index fb59066..b92f1fd 100644 --- a/frontend_splatournament_manager/lib/state_provider.dart +++ b/frontend_splatournament_manager/lib/state_provider.dart @@ -14,12 +14,14 @@ class StateProvider extends ChangeNotifier { notifyListeners(); } List? _availableTournaments; + void notifyState(){ + notifyListeners(); + } Future> fetchAvailableTournaments() async { try { var response = await http.get(Uri.parse('http://10.0.2.2:3000/tournaments')); if (response.statusCode == 200) { final List list = json.decode(response.body); - _availableTournaments = list.map((json) => Tournament.fromJson(json)).toList(); return _availableTournaments!; } diff --git a/frontend_splatournament_manager/lib/widgets/available_tournament_list.dart b/frontend_splatournament_manager/lib/widgets/available_tournament_list.dart index 4a0b677..4393e4f 100644 --- a/frontend_splatournament_manager/lib/widgets/available_tournament_list.dart +++ b/frontend_splatournament_manager/lib/widgets/available_tournament_list.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:frontend_splatournament_manager/models/tournament.dart'; import 'package:frontend_splatournament_manager/pages/tournament_detail_page.dart'; import 'package:frontend_splatournament_manager/state_provider.dart'; import 'package:provider/provider.dart'; @@ -8,40 +9,80 @@ class AvailableTournamentList extends StatelessWidget { @override Widget build(BuildContext context) { - return Expanded( - child: Consumer( - builder: (BuildContext context, StateProvider value, Widget? child) => - FutureBuilder( - future: value.fetchAvailableTournaments(), - builder: (context, snapshot) { - if(snapshot.hasError){ - return Center(child: Text('Error: ${snapshot.error}')); - }else if(!snapshot.hasData){ - return Center(child: CircularProgressIndicator()); - } - var list = snapshot.data!; - return ListView.builder( - itemCount: list.length, - itemBuilder: (context, index) { - var tournament = list[index]; - return ListTile( - leading: Icon(Icons.abc), - title: Text(tournament.name), - subtitle: Text(tournament.description), - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => TournamentDetailPage(tournamentId: tournament.id,), - ), - ); - }, - ); - }, - ); - }, + return Container( + padding: EdgeInsets.fromLTRB(24, 0, 24, 0), + child: Column( + children: [ + Row(children: [Text("Available Tournaments")]), + SizedBox( + width: double.infinity, + height: 350, + child: Consumer( + builder: + ( + BuildContext context, + StateProvider provider, + Widget? child, + ) => TournamentListFutureBuilder(provider: provider), ), + ), + ], ), ); } } + +class TournamentListFutureBuilder extends StatelessWidget { + final StateProvider provider; + + const TournamentListFutureBuilder({super.key, required this.provider}); + + @override + Widget build(BuildContext context) { + return FutureBuilder( + future: provider.fetchAvailableTournaments(), + builder: (context, snapshot) { + if (snapshot.hasError) { + return Center(child: Text('Error: ${snapshot.error}')); + } else if (!snapshot.hasData || + snapshot.connectionState == ConnectionState.waiting) { + return Center(child: CircularProgressIndicator()); + } + var list = snapshot.data!; + return ListView.builder( + shrinkWrap: false, + itemCount: list.length, + itemBuilder: (context, index) { + var tournament = list[index]; + return TournamentListItem(tournament: tournament); + }, + ); + }, + ); + } +} + +class TournamentListItem extends StatelessWidget { + final Tournament tournament; + + const TournamentListItem({super.key, required this.tournament}); + + @override + Widget build(BuildContext context) { + return ListTile( + contentPadding: EdgeInsets.all(0), + leading: Icon(Icons.abc), + title: Text(tournament.name), + subtitle: Text(tournament.description), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => TournamentDetailPage(tournament: tournament), + ), + ); + }, + ); + } +} +