Refactored components

This commit is contained in:
2026-01-07 14:05:19 +01:00
parent 6e45e9435b
commit 21c74b74bc
20 changed files with 429 additions and 159 deletions

View File

@@ -2,6 +2,9 @@ import 'package:flutter/material.dart';
import 'package:game_tracker/l10n/generated/app_localizations.dart'; import 'package:game_tracker/l10n/generated/app_localizations.dart';
/// Button types used for styling the [CustomWidthButton] /// Button types used for styling the [CustomWidthButton]
/// - [ButtonType.primary]: Primary button style.
/// - [ButtonType.secondary]: Secondary button style.
/// - [ButtonType.tertiary]: Tertiary button style.
enum ButtonType { primary, secondary, tertiary } enum ButtonType { primary, secondary, tertiary }
/// Result types for import operations in the [SettingsView] /// Result types for import operations in the [SettingsView]

View File

@@ -1,11 +1,11 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:skeletonizer/skeletonizer.dart'; import 'package:skeletonizer/skeletonizer.dart';
/// A widget that provides a skeleton loading effect to its child widget tree.
/// - [child]: The widget tree to apply the skeleton effect to.
/// - [enabled]: A boolean to enable or disable the skeleton effect.
/// - [fixLayoutBuilder]: A boolean to fix the layout builder for AnimatedSwitcher.
class AppSkeleton extends StatefulWidget { class AppSkeleton extends StatefulWidget {
final Widget child;
final bool enabled;
final bool fixLayoutBuilder;
const AppSkeleton({ const AppSkeleton({
super.key, super.key,
required this.child, required this.child,
@@ -13,6 +13,15 @@ class AppSkeleton extends StatefulWidget {
this.fixLayoutBuilder = false, this.fixLayoutBuilder = false,
}); });
/// The widget tree to apply the skeleton effect to.
final Widget child;
/// A boolean to enable or disable the skeleton effect.
final bool enabled;
/// A boolean to fix the layout builder for AnimatedSwitcher.
final bool fixLayoutBuilder;
@override @override
State<AppSkeleton> createState() => _AppSkeletonState(); State<AppSkeleton> createState() => _AppSkeletonState();
} }

View File

@@ -2,6 +2,12 @@ import 'package:flutter/material.dart';
import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/core/custom_theme.dart';
import 'package:game_tracker/core/enums.dart'; import 'package:game_tracker/core/enums.dart';
/// A custom button widget that is designed to have a width relative to the screen size.
/// It supports three types of buttons: primary, secondary, and text buttons.
/// - [text]: The text to display on the button.
/// - [buttonType]: The type of button to display. Defaults to [ButtonType.primary].
/// - [sizeRelativeToWidth]: The size of the button relative to the width of the screen.
/// - [onPressed]: The callback to be invoked when the button is pressed.
class CustomWidthButton extends StatelessWidget { class CustomWidthButton extends StatelessWidget {
const CustomWidthButton({ const CustomWidthButton({
super.key, super.key,
@@ -11,9 +17,16 @@ class CustomWidthButton extends StatelessWidget {
this.onPressed, this.onPressed,
}); });
/// The text to display on the button.
final String text; final String text;
/// The size of the button relative to the width of the screen.
final double sizeRelativeToWidth; final double sizeRelativeToWidth;
/// The callback to be invoked when the button is pressed.
final VoidCallback? onPressed; final VoidCallback? onPressed;
/// The type of button to display. Depends on the enum [ButtonType].
final ButtonType buttonType; final ButtonType buttonType;
@override @override

View File

@@ -1,15 +1,22 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/core/custom_theme.dart';
/// A button widget designed for quick creating matches in the [HomeView]
/// - [text]: The text to display on the button.
/// - [onPressed]: The callback to be invoked when the button is pressed.
class QuickCreateButton extends StatefulWidget { class QuickCreateButton extends StatefulWidget {
final String text;
final VoidCallback? onPressed;
const QuickCreateButton({ const QuickCreateButton({
super.key, super.key,
required this.text, required this.text,
required this.onPressed, required this.onPressed,
}); });
/// The text to display on the button.
final String text;
/// The callback to be invoked when the button is pressed.
final VoidCallback? onPressed;
@override @override
State<QuickCreateButton> createState() => _QuickCreateButtonState(); State<QuickCreateButton> createState() => _QuickCreateButtonState();
} }

View File

@@ -1,12 +1,12 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
/// A navigation bar item widget that represents a single tab in a navigation bar.
/// - [index]: The index of the tab.
/// - [isSelected]: A boolean indicating whether the tab is currently selected.
/// - [icon]: The icon to display for the tab.
/// - [label]: The label to display for the tab.
/// - [onTabTapped]: The callback to be invoked when the tab is tapped.
class NavbarItem extends StatefulWidget { class NavbarItem extends StatefulWidget {
final int index;
final bool isSelected;
final IconData icon;
final String label;
final Function(int) onTabTapped;
const NavbarItem({ const NavbarItem({
super.key, super.key,
required this.index, required this.index,
@@ -16,6 +16,21 @@ class NavbarItem extends StatefulWidget {
required this.onTabTapped, required this.onTabTapped,
}); });
/// The index of the tab.
final int index;
/// A boolean indicating whether the tab is currently selected.
final bool isSelected;
/// The icon to display for the tab.
final IconData icon;
/// The label to display for the tab.
final String label;
/// The callback to be invoked when the tab is tapped.
final Function(int) onTabTapped;
@override @override
State<NavbarItem> createState() => _NavbarItemState(); State<NavbarItem> createState() => _NavbarItemState();
} }

View File

@@ -11,31 +11,55 @@ import 'package:game_tracker/presentation/widgets/tiles/text_icon_tile.dart';
import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; import 'package:game_tracker/presentation/widgets/top_centered_message.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
/// A widget that allows users to select players from a list,
/// with search functionality and the ability to add new players.
/// - [availablePlayers]: An optional list of players to choose from. If null, all
/// players from the database are used.
/// - [initialSelectedPlayers]: An optional list of players that should be pre-selected.
/// - [onChanged]: A callback function that is invoked whenever the selection changes,
/// providing the updated list of selected players.
class PlayerSelection extends StatefulWidget { class PlayerSelection extends StatefulWidget {
final Function(List<Player> value) onChanged;
final List<Player>? availablePlayers;
final List<Player>? initialSelectedPlayers;
const PlayerSelection({ const PlayerSelection({
super.key, super.key,
required this.onChanged,
this.availablePlayers, this.availablePlayers,
this.initialSelectedPlayers, this.initialSelectedPlayers,
required this.onChanged,
}); });
/// An optional list of players to choose from. If null, all players from the database are used.
final List<Player>? availablePlayers;
/// An optional list of players that should be pre-selected.
final List<Player>? initialSelectedPlayers;
/// A callback function that is invoked whenever the selection changes,
final Function(List<Player> value) onChanged;
@override @override
State<PlayerSelection> createState() => _PlayerSelectionState(); State<PlayerSelection> createState() => _PlayerSelectionState();
} }
class _PlayerSelectionState extends State<PlayerSelection> { class _PlayerSelectionState extends State<PlayerSelection> {
List<Player> selectedPlayers = []; late final AppDatabase db;
List<Player> suggestedPlayers = [];
List<Player> allPlayers = [];
bool isLoading = true; bool isLoading = true;
/// Future that loads all players from the database.
late Future<List<Player>> _allPlayersFuture;
/// The complete list of all available players.
List<Player> allPlayers = [];
/// The list of players suggested based on the search input.
List<Player> suggestedPlayers = [];
/// The list of currently selected players.
List<Player> selectedPlayers = [];
/// Controller for the search bar input.
late final TextEditingController _searchBarController = late final TextEditingController _searchBarController =
TextEditingController(); TextEditingController();
late final AppDatabase db;
late Future<List<Player>> _allPlayersFuture; /// Skeleton data used while loading players.
late final List<Player> skeletonData = List.filled( late final List<Player> skeletonData = List.filled(
7, 7,
Player(name: 'Player 0'), Player(name: 'Player 0'),
@@ -49,42 +73,6 @@ class _PlayerSelectionState extends State<PlayerSelection> {
loadPlayerList(); loadPlayerList();
} }
void loadPlayerList() {
_allPlayersFuture = Future.wait([
db.playerDao.getAllPlayers(),
Future.delayed(Constants.minimumSkeletonDuration),
]).then((results) => results[0] as List<Player>);
if (mounted) {
_allPlayersFuture.then((loadedPlayers) {
setState(() {
// If a list of available players is provided (even if empty), use that list.
if (widget.availablePlayers != null) {
widget.availablePlayers!.sort((a, b) => a.name.compareTo(b.name));
allPlayers = [...widget.availablePlayers!];
suggestedPlayers = [...allPlayers];
if (widget.initialSelectedPlayers != null) {
// Ensures that only players available for selection are pre-selected.
selectedPlayers = widget.initialSelectedPlayers!
.where(
(p) => widget.availablePlayers!.any(
(available) => available.id == p.id,
),
)
.toList();
}
} else {
// Otherwise, use the loaded players from the database.
loadedPlayers.sort((a, b) => a.name.compareTo(b.name));
allPlayers = [...loadedPlayers];
suggestedPlayers = [...loadedPlayers];
}
isLoading = false;
});
});
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final loc = AppLocalizations.of(context); final loc = AppLocalizations.of(context);
@@ -227,53 +215,97 @@ class _PlayerSelectionState extends State<PlayerSelection> {
); );
} }
/// Loads the list of players from the database or uses the provided available players.
/// Sets the loading state and updates the player lists accordingly.
void loadPlayerList() {
_allPlayersFuture = Future.wait([
db.playerDao.getAllPlayers(),
Future.delayed(Constants.minimumSkeletonDuration),
]).then((results) => results[0] as List<Player>);
if (mounted) {
_allPlayersFuture.then((loadedPlayers) {
setState(() {
// If a list of available players is provided (even if empty), use that list.
if (widget.availablePlayers != null) {
widget.availablePlayers!.sort((a, b) => a.name.compareTo(b.name));
allPlayers = [...widget.availablePlayers!];
suggestedPlayers = [...allPlayers];
if (widget.initialSelectedPlayers != null) {
// Ensures that only players available for selection are pre-selected.
selectedPlayers = widget.initialSelectedPlayers!
.where(
(p) => widget.availablePlayers!.any(
(available) => available.id == p.id,
),
)
.toList();
}
} else {
// Otherwise, use the loaded players from the database.
loadedPlayers.sort((a, b) => a.name.compareTo(b.name));
allPlayers = [...loadedPlayers];
suggestedPlayers = [...loadedPlayers];
}
isLoading = false;
});
});
}
}
/// Adds a new player to the database from the search bar input. /// Adds a new player to the database from the search bar input.
/// Shows a snackbar indicating success or failure. /// Shows a snackbar indicating success or failure.
/// [context] - BuildContext to show the snackbar. /// [context] - BuildContext to show the snackbar.
void addNewPlayerFromSearch({required BuildContext context}) async { Future<void> addNewPlayerFromSearch({required BuildContext context}) async {
final loc = AppLocalizations.of(context); final loc = AppLocalizations.of(context);
String playerName = _searchBarController.text.trim(); final playerName = _searchBarController.text.trim();
Player createdPlayer = Player(name: playerName);
bool success = await db.playerDao.addPlayer(player: createdPlayer); final createdPlayer = Player(name: playerName);
final success = await db.playerDao.addPlayer(player: createdPlayer);
if (!context.mounted) return; if (!context.mounted) return;
if (success) { if (success) {
selectedPlayers.insert(0, createdPlayer); _handleSuccessfulPlayerCreation(createdPlayer);
widget.onChanged([...selectedPlayers]); showSnackBarMessage(loc.successfully_added_player(playerName));
allPlayers.add(createdPlayer);
setState(() {
_searchBarController.clear();
suggestedPlayers = allPlayers.where((player) {
return !selectedPlayers.contains(player);
}).toList();
});
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
backgroundColor: CustomTheme.boxColor,
content: Center(
child: Text(
AppLocalizations.of(
context,
).successfully_added_player(playerName),
style: const TextStyle(color: Colors.white),
),
),
),
);
} else { } else {
ScaffoldMessenger.of(context).showSnackBar( showSnackBarMessage(loc.could_not_add_player(playerName));
SnackBar(
backgroundColor: CustomTheme.boxColor,
content: Center(
child: Text(
loc.could_not_add_player(playerName),
style: const TextStyle(color: Colors.white),
),
),
),
);
} }
} }
/// Updates the state after successfully adding a new player.
void _handleSuccessfulPlayerCreation(Player player) {
selectedPlayers.insert(0, player);
widget.onChanged([...selectedPlayers]);
allPlayers.add(player);
setState(() {
_searchBarController.clear();
_updateSuggestedPlayers();
});
}
/// Updates the suggested players list based on current selection.
void _updateSuggestedPlayers() {
suggestedPlayers = allPlayers
.where((player) => !selectedPlayers.contains(player))
.toList();
}
/// Displays a snackbar message at the bottom of the screen.
/// [message] - The message to display in the snackbar.
void showSnackBarMessage(String message) {
if (!context.mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
backgroundColor: CustomTheme.boxColor,
content: Center(
child: Text(message, style: const TextStyle(color: Colors.white)),
),
),
);
}
/// Determines the appropriate info text to display when no players /// Determines the appropriate info text to display when no players
/// are available in the suggested players list. /// are available in the suggested players list.
String _getInfoText(BuildContext context) { String _getInfoText(BuildContext context) {

View File

@@ -1,16 +1,16 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/core/custom_theme.dart';
/// A custom search bar widget that encapsulates a [SearchBar] with additional customization options.
/// - [controller]: The controller for the search bar's text input.
/// - [hintText]: The hint text displayed in the search bar when it is empty.
/// - [trailingButtonShown]: Whether to show the trailing button.
/// - [trailingButtonicon]: The icon for the trailing button.
/// - [trailingButtonEnabled]: Whether the trailing button is in enabled state.
/// - [onTrailingButtonPressed]: The callback invoked when the trailing button is pressed.
/// - [onChanged]: The callback invoked when the text in the search bar changes.
/// - [constraints]: The constraints for the search bar.
class CustomSearchBar extends StatelessWidget { class CustomSearchBar extends StatelessWidget {
final TextEditingController controller;
final String hintText;
final ValueChanged<String>? onChanged;
final BoxConstraints? constraints;
final bool trailingButtonShown;
final bool trailingButtonEnabled;
final VoidCallback? onTrailingButtonPressed;
final IconData trailingButtonicon;
const CustomSearchBar({ const CustomSearchBar({
super.key, super.key,
required this.controller, required this.controller,
@@ -23,6 +23,30 @@ class CustomSearchBar extends StatelessWidget {
this.constraints, this.constraints,
}); });
/// The controller for the search bar's text input.
final TextEditingController controller;
/// The hint text displayed in the search bar when it is empty.
final String hintText;
/// Whether to show the trailing button.
final bool trailingButtonShown;
/// The icon for the trailing button.
final IconData trailingButtonicon;
/// Whether the trailing button is in enabled state.
final bool trailingButtonEnabled;
/// The callback invoked when the trailing button is pressed.
final VoidCallback? onTrailingButtonPressed;
/// The callback invoked when the text in the search bar changes.
final ValueChanged<String>? onChanged;
/// The constraints for the search bar.
final BoxConstraints? constraints;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SearchBar( return SearchBar(

View File

@@ -1,11 +1,11 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/core/custom_theme.dart';
/// A custom text input field widget that encapsulates a [TextField] with specific styling.
/// - [controller]: The controller for the text input field.
/// - [onChanged]: The callback invoked when the text in the field changes.
/// - [hintText]: The hint text displayed in the text input field when it is empty
class TextInputField extends StatelessWidget { class TextInputField extends StatelessWidget {
final TextEditingController controller;
final ValueChanged<String>? onChanged;
final String hintText;
const TextInputField({ const TextInputField({
super.key, super.key,
required this.controller, required this.controller,
@@ -13,6 +13,15 @@ class TextInputField extends StatelessWidget {
this.onChanged, this.onChanged,
}); });
/// The controller for the text input field.
final TextEditingController controller;
/// The callback invoked when the text in the field changes.
final ValueChanged<String>? onChanged;
/// The hint text displayed in the text input field when it is empty.
final String hintText;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return TextField( return TextField(

View File

@@ -1,10 +1,11 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/core/custom_theme.dart';
/// A tile widget that allows users to choose an option by tapping on it.
/// - [title]: The title text displayed on the tile.
/// - [trailingText]: Optional trailing text displayed on the tile.
/// - [onPressed]: The callback invoked when the tile is tapped.
class ChooseTile extends StatefulWidget { class ChooseTile extends StatefulWidget {
final String title;
final VoidCallback? onPressed;
final String? trailingText;
const ChooseTile({ const ChooseTile({
super.key, super.key,
required this.title, required this.title,
@@ -12,6 +13,15 @@ class ChooseTile extends StatefulWidget {
this.onPressed, this.onPressed,
}); });
/// The title text displayed on the tile.
final String title;
/// The callback invoked when the tile is tapped.
final VoidCallback? onPressed;
/// Optional trailing text displayed on the tile.
final String? trailingText;
@override @override
State<ChooseTile> createState() => _ChooseTileState(); State<ChooseTile> createState() => _ChooseTileState();
} }

View File

@@ -1,11 +1,11 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/core/custom_theme.dart';
/// A custom radio list tile widget that encapsulates a [Radio] button with additional styling and functionality.
/// - [text]: The text to display next to the radio button.
/// - [value]: The value associated with the radio button.
/// - [onContainerTap]: The callback invoked when the container is tapped.
class CustomRadioListTile<T> extends StatelessWidget { class CustomRadioListTile<T> extends StatelessWidget {
final String text;
final T value;
final ValueChanged<T> onContainerTap;
const CustomRadioListTile({ const CustomRadioListTile({
super.key, super.key,
required this.text, required this.text,
@@ -13,6 +13,15 @@ class CustomRadioListTile<T> extends StatelessWidget {
required this.onContainerTap, required this.onContainerTap,
}); });
/// The text to display next to the radio button.
final String text;
/// The value associated with the radio button.
final T value;
/// The callback invoked when the container is tapped.
final ValueChanged<T> onContainerTap;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GestureDetector( return GestureDetector(

View File

@@ -3,10 +3,16 @@ import 'package:game_tracker/core/custom_theme.dart';
import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/group.dart';
import 'package:game_tracker/presentation/widgets/tiles/text_icon_tile.dart'; import 'package:game_tracker/presentation/widgets/tiles/text_icon_tile.dart';
/// A tile widget that displays information about a group, including its name and members.
/// - [group]: The group data to be displayed.
/// - [isHighlighted]: Whether the tile should be highlighted.
class GroupTile extends StatelessWidget { class GroupTile extends StatelessWidget {
const GroupTile({super.key, required this.group, this.isHighlighted = false}); const GroupTile({super.key, required this.group, this.isHighlighted = false});
/// The group data to be displayed.
final Group group; final Group group;
/// Whether the tile should be highlighted.
final bool isHighlighted; final bool isHighlighted;
@override @override

View File

@@ -1,13 +1,14 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/core/custom_theme.dart';
/// A tile widget that displays a title with an icon and some content below it.
/// - [title]: The title text displayed on the tile.
/// - [icon]: The icon displayed next to the title.
/// - [content]: The content widget displayed below the title.
/// - [padding]: Optional padding for the tile content.
/// - [height]: Optional height for the tile.
/// - [width]: Optional width for the tile.
class InfoTile extends StatefulWidget { class InfoTile extends StatefulWidget {
final String title;
final IconData icon;
final Widget content;
final EdgeInsets? padding;
final double? height;
final double? width;
const InfoTile({ const InfoTile({
super.key, super.key,
required this.title, required this.title,
@@ -18,6 +19,24 @@ class InfoTile extends StatefulWidget {
this.width, this.width,
}); });
/// The title text displayed on the tile.
final String title;
/// The icon displayed next to the title.
final IconData icon;
/// The content widget displayed below the title.
final Widget content;
/// Optional padding for the tile content.
final EdgeInsets? padding;
/// Optional height for the tile.
final double? height;
/// Optional width for the tile.
final double? width;
@override @override
State<InfoTile> createState() => _InfoTileState(); State<InfoTile> createState() => _InfoTileState();
} }

View File

@@ -1,26 +1,41 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/core/custom_theme.dart';
import 'package:game_tracker/data/dto/match.dart'; import 'package:game_tracker/data/dto/match.dart';
import 'package:game_tracker/data/dto/player.dart';
import 'package:game_tracker/l10n/generated/app_localizations.dart'; import 'package:game_tracker/l10n/generated/app_localizations.dart';
import 'package:game_tracker/presentation/widgets/tiles/text_icon_tile.dart'; import 'package:game_tracker/presentation/widgets/tiles/text_icon_tile.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
/// A tile widget that displays information about a match, including its name,
/// creation date, associated group, winner, and players.
/// - [match]: The match data to be displayed.
/// - [onTap]: The callback invoked when the tile is tapped.
class MatchTile extends StatefulWidget { class MatchTile extends StatefulWidget {
final Match match;
final VoidCallback onTap;
const MatchTile({super.key, required this.match, required this.onTap}); const MatchTile({super.key, required this.match, required this.onTap});
/// The match data to be displayed.
final Match match;
/// The callback invoked when the tile is tapped.
final VoidCallback onTap;
@override @override
State<MatchTile> createState() => _MatchTileState(); State<MatchTile> createState() => _MatchTileState();
} }
class _MatchTileState extends State<MatchTile> { class _MatchTileState extends State<MatchTile> {
late final List<Player> _allPlayers;
@override
void initState() {
super.initState();
_allPlayers = _getCombinedPlayers();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final group = widget.match.group; final group = widget.match.group;
final winner = widget.match.winner; final winner = widget.match.winner;
final allPlayers = _getAllPlayers();
final loc = AppLocalizations.of(context); final loc = AppLocalizations.of(context);
return GestureDetector( return GestureDetector(
@@ -114,7 +129,7 @@ class _MatchTileState extends State<MatchTile> {
const SizedBox(height: 12), const SizedBox(height: 12),
], ],
if (allPlayers.isNotEmpty) ...[ if (_allPlayers.isNotEmpty) ...[
Text( Text(
loc.players, loc.players,
style: const TextStyle( style: const TextStyle(
@@ -127,7 +142,7 @@ class _MatchTileState extends State<MatchTile> {
Wrap( Wrap(
spacing: 6, spacing: 6,
runSpacing: 6, runSpacing: 6,
children: allPlayers.map((player) { children: _allPlayers.map((player) {
return TextIconTile(text: player.name, iconEnabled: false); return TextIconTile(text: player.name, iconEnabled: false);
}).toList(), }).toList(),
), ),
@@ -138,6 +153,8 @@ class _MatchTileState extends State<MatchTile> {
); );
} }
/// Formats the given [dateTime] into a human-readable string based on its
/// difference from the current date.
String _formatDate(DateTime dateTime, BuildContext context) { String _formatDate(DateTime dateTime, BuildContext context) {
final now = DateTime.now(); final now = DateTime.now();
final difference = now.difference(dateTime); final difference = now.difference(dateTime);
@@ -158,8 +175,10 @@ class _MatchTileState extends State<MatchTile> {
} }
} }
List<dynamic> _getAllPlayers() { /// Retrieves all unique players associated with the match,
final allPlayers = <dynamic>[]; /// combining players from both the match and its group.
List<Player> _getCombinedPlayers() {
final allPlayers = <Player>[];
final playerIds = <String>{}; final playerIds = <String>{};
// Add players from game.players // Add players from game.players

View File

@@ -1,13 +1,14 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/core/custom_theme.dart';
/// A tile widget that displays a title with an icon and a numeric value below it.
/// - [title]: The title text displayed on the tile.
/// - [icon]: The icon displayed next to the title.
/// - [value]: The numeric value displayed below the title.
/// - [height]: Optional height for the tile.
/// - [width]: Optional width for the tile.
/// - [padding]: Optional padding for the tile content.
class QuickInfoTile extends StatefulWidget { class QuickInfoTile extends StatefulWidget {
final String title;
final IconData icon;
final int value;
final double? height;
final double? width;
final EdgeInsets? padding;
const QuickInfoTile({ const QuickInfoTile({
super.key, super.key,
required this.title, required this.title,
@@ -18,6 +19,24 @@ class QuickInfoTile extends StatefulWidget {
this.padding, this.padding,
}); });
/// The title text displayed on the tile.
final String title;
/// The icon displayed next to the title.
final IconData icon;
/// The numeric value displayed below the title.
final int value;
/// Optional height for the tile.
final double? height;
/// Optional width for the tile.
final double? width;
/// Optional padding for the tile content.
final EdgeInsets? padding;
@override @override
State<QuickInfoTile> createState() => _QuickInfoTileState(); State<QuickInfoTile> createState() => _QuickInfoTileState();
} }

View File

@@ -1,19 +1,32 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/core/custom_theme.dart';
/// A customizable settings list tile widget that displays an icon, title, and an optional suffix widget.
/// - [icon]: The icon displayed on the left side of the tile.
/// - [title]: The title text displayed next to the icon.
/// - [suffixWidget]: An optional widget displayed on the right side of the tile.
/// - [onPressed]: The callback invoked when the tile is tapped.
class SettingsListTile extends StatelessWidget { class SettingsListTile extends StatelessWidget {
final VoidCallback? onPressed;
final IconData icon;
final String title;
final Widget? suffixWidget;
const SettingsListTile({ const SettingsListTile({
super.key, super.key,
required this.title,
required this.icon, required this.icon,
required this.title,
this.suffixWidget, this.suffixWidget,
this.onPressed, this.onPressed,
}); });
/// The icon displayed on the left side of the tile.
final IconData icon;
/// The title text displayed next to the icon.
final String title;
/// An optional widget displayed on the right side of the tile.
final Widget? suffixWidget;
/// The callback invoked when the tile is tapped.
final VoidCallback? onPressed;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Padding( return Padding(

View File

@@ -4,6 +4,13 @@ import 'package:flutter/material.dart';
import 'package:game_tracker/l10n/generated/app_localizations.dart'; import 'package:game_tracker/l10n/generated/app_localizations.dart';
import 'package:game_tracker/presentation/widgets/tiles/info_tile.dart'; import 'package:game_tracker/presentation/widgets/tiles/info_tile.dart';
/// A tile widget that displays statistical data using horizontal bars.
/// - [icon]: The icon displayed next to the title.
/// - [title]: The title text displayed on the tile.
/// - [width]: The width of the tile.
/// - [values]: A list of tuples containing labels and their corresponding numeric values.
/// - [itemCount]: The maximum number of items to display.
/// - [barColor]: The color of the bars representing the values.
class StatisticsTile extends StatelessWidget { class StatisticsTile extends StatelessWidget {
const StatisticsTile({ const StatisticsTile({
super.key, super.key,
@@ -15,11 +22,22 @@ class StatisticsTile extends StatelessWidget {
required this.barColor, required this.barColor,
}); });
/// The icon displayed next to the title.
final IconData icon; final IconData icon;
/// The title text displayed on the tile.
final String title; final String title;
/// The width of the tile.
final double width; final double width;
/// A list of tuples containing labels and their corresponding numeric values.
final List<(String, num)> values; final List<(String, num)> values;
/// The maximum number of items to display.
final int itemCount; final int itemCount;
/// The color of the bars representing the values.
final Color barColor; final Color barColor;
@override @override

View File

@@ -1,18 +1,27 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/core/custom_theme.dart';
/// A list tile widget that displays text with an optional icon button.
/// - [text]: The text to display in the tile.
/// - [onPressed]: The callback to be invoked when the icon is pressed.
/// - [iconEnabled]: A boolean to determine if the icon should be displayed.
class TextIconListTile extends StatelessWidget { class TextIconListTile extends StatelessWidget {
final String text;
final VoidCallback? onPressed;
final bool iconEnabled;
const TextIconListTile({ const TextIconListTile({
super.key, super.key,
required this.text, required this.text,
this.onPressed,
this.iconEnabled = true, this.iconEnabled = true,
this.onPressed,
}); });
/// The text to display in the tile.
final String text;
/// A boolean to determine if the icon should be displayed.
final bool iconEnabled;
/// The callback to be invoked when the icon is pressed.
final VoidCallback? onPressed;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(

View File

@@ -1,18 +1,27 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/core/custom_theme.dart';
/// A tile widget that displays text with an optional icon that can be tapped.
/// - [text]: The text to display in the tile.
/// - [iconEnabled]: A boolean to determine if the icon should be displayed.
/// - [onIconTap]: The callback to be invoked when the icon is tapped.
class TextIconTile extends StatelessWidget { class TextIconTile extends StatelessWidget {
final String text;
final bool iconEnabled;
final VoidCallback? onIconTap;
const TextIconTile({ const TextIconTile({
super.key, super.key,
required this.text, required this.text,
this.onIconTap,
this.iconEnabled = true, this.iconEnabled = true,
this.onIconTap,
}); });
/// The text to display in the tile.
final String text;
/// A boolean to determine if the icon should be displayed.
final bool iconEnabled;
/// The callback to be invoked when the icon is tapped.
final VoidCallback? onIconTap;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(

View File

@@ -1,14 +1,14 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/core/custom_theme.dart';
/// A list tile widget that displays a title and description, with optional highlighting and badge.
/// - [title]: The title text displayed on the tile.
/// - [description]: The description text displayed below the title.
/// - [onPressed]: The callback invoked when the tile is tapped.
/// - [isHighlighted]: A boolean to determine if the tile should be highlighted.
/// - [badgeText]: Optional text to display in a badge on the right side of the title.
/// - [badgeColor]: Optional color for the badge background.
class TitleDescriptionListTile extends StatelessWidget { class TitleDescriptionListTile extends StatelessWidget {
final String title;
final String description;
final VoidCallback? onPressed;
final bool isHighlighted;
final String? badgeText;
final Color? badgeColor;
const TitleDescriptionListTile({ const TitleDescriptionListTile({
super.key, super.key,
required this.title, required this.title,
@@ -19,6 +19,24 @@ class TitleDescriptionListTile extends StatelessWidget {
this.badgeColor, this.badgeColor,
}); });
/// The title text displayed on the tile.
final String title;
/// The description text displayed below the title.
final String description;
/// The callback invoked when the tile is tapped.
final VoidCallback? onPressed;
/// A boolean to determine if the tile should be highlighted.
final bool isHighlighted;
/// Optional text to display in a badge on the right side of the title.
final String? badgeText;
/// Optional color for the badge background.
final Color? badgeColor;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GestureDetector( return GestureDetector(

View File

@@ -1,5 +1,9 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
/// A widget that displays a message centered at the top of the screen with an icon, title, and message.
/// - [icon]: The icon to display above the title.
/// - [title]: The title text to display.
/// - [message]: The message text to display below the title.
class TopCenteredMessage extends StatelessWidget { class TopCenteredMessage extends StatelessWidget {
const TopCenteredMessage({ const TopCenteredMessage({
super.key, super.key,
@@ -8,10 +12,15 @@ class TopCenteredMessage extends StatelessWidget {
required this.message, required this.message,
}); });
final String title; /// The icon to display above the title.
final String message;
final IconData icon; final IconData icon;
/// The title text to display.
final String title;
/// The message text to display below the title.
final String message;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(