diff --git a/lib/core/enums.dart b/lib/core/enums.dart new file mode 100644 index 0000000..320eaf7 --- /dev/null +++ b/lib/core/enums.dart @@ -0,0 +1,2 @@ +/// Button types used for styling the [CustomWidthButton] +enum ButtonType { primary, secondary, tertiary } diff --git a/lib/data/dao/group_dao.dart b/lib/data/dao/group_dao.dart index 8eb3a1a..cc680a3 100644 --- a/lib/data/dao/group_dao.dart +++ b/lib/data/dao/group_dao.dart @@ -60,8 +60,8 @@ class GroupDao extends DatabaseAccessor with _$GroupDaoMixin { await Future.wait( group.members.map((player) => db.playerDao.addPlayer(player: player)), ); - return true; }); + return true; } return false; } diff --git a/lib/presentation/views/main_menu/create_group_view.dart b/lib/presentation/views/main_menu/create_group_view.dart new file mode 100644 index 0000000..db8890f --- /dev/null +++ b/lib/presentation/views/main_menu/create_group_view.dart @@ -0,0 +1,309 @@ +import 'package:flutter/material.dart' hide ButtonStyle; +import 'package:game_tracker/core/custom_theme.dart'; +import 'package:game_tracker/core/enums.dart'; +import 'package:game_tracker/data/db/database.dart'; +import 'package:game_tracker/data/dto/group.dart'; +import 'package:game_tracker/data/dto/player.dart'; +import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; +import 'package:game_tracker/presentation/widgets/custom_search_bar.dart'; +import 'package:game_tracker/presentation/widgets/text_input_field.dart'; +import 'package:game_tracker/presentation/widgets/tiles/text_icon_list_tile.dart'; +import 'package:game_tracker/presentation/widgets/tiles/text_icon_tile.dart'; +import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; +import 'package:provider/provider.dart'; +import 'package:skeletonizer/skeletonizer.dart'; + +class CreateGroupView extends StatefulWidget { + const CreateGroupView({super.key}); + + @override + State createState() => _CreateGroupViewState(); +} + +class _CreateGroupViewState extends State { + List selectedPlayers = []; + List suggestedPlayers = []; + List allPlayers = []; + late final AppDatabase db; + late Future> _allPlayersFuture; + late final List skeletonData = List.filled( + 7, + Player(name: 'Player 0'), + ); + final _groupNameController = TextEditingController(); + final _searchBarController = TextEditingController(); + + @override + void initState() { + super.initState(); + db = Provider.of(context, listen: false); + _allPlayersFuture = db.playerDao.getAllPlayers(); + _allPlayersFuture.then((loadedPlayers) { + setState(() { + loadedPlayers.sort((a, b) => a.name.compareTo(b.name)); + allPlayers = [...loadedPlayers]; + suggestedPlayers = [...loadedPlayers]; + }); + }); + } + + @override + Widget build(BuildContext context) { + return SafeArea( + child: Scaffold( + backgroundColor: CustomTheme.backgroundColor, + appBar: AppBar( + backgroundColor: CustomTheme.backgroundColor, + scrolledUnderElevation: 0, + title: const Text( + 'Create new group', + style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + ), + centerTitle: true, + ), + body: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), + child: TextInputField( + controller: _groupNameController, + hintText: 'Group name', + onChanged: (value) { + setState(() {}); + }, + ), + ), + Expanded( + child: Container( + margin: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 10, + ), + padding: const EdgeInsets.symmetric( + vertical: 10, + horizontal: 10, + ), + decoration: BoxDecoration( + color: CustomTheme.boxColor, + border: Border.all(color: CustomTheme.boxBorder), + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CustomSearchBar( + controller: _searchBarController, + constraints: const BoxConstraints( + maxHeight: 45, + minHeight: 45, + ), + hintText: 'Search for players', + onChanged: (value) { + setState(() { + if (value.isEmpty) { + suggestedPlayers = allPlayers.where((player) { + return !selectedPlayers.contains(player); + }).toList(); + } else { + suggestedPlayers = allPlayers.where((player) { + final bool nameMatches = player.name + .toLowerCase() + .contains(value.toLowerCase()); + final bool isNotSelected = !selectedPlayers + .contains(player); + return nameMatches && isNotSelected; + }).toList(); + } + }); + }, + ), + const SizedBox(height: 10), + Text( + 'Ausgewählte Spieler: (${selectedPlayers.length})', + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 10), + Wrap( + alignment: WrapAlignment.start, + crossAxisAlignment: WrapCrossAlignment.start, + spacing: 8.0, + runSpacing: 8.0, + children: [ + for (var player in selectedPlayers) + TextIconTile( + text: player.name, + onIconTap: () { + setState(() { + final currentSearch = _searchBarController.text + .toLowerCase(); + selectedPlayers.remove(player); + if (currentSearch.isEmpty || + player.name.toLowerCase().contains( + currentSearch, + )) { + suggestedPlayers.add(player); + suggestedPlayers.sort( + (a, b) => a.name.compareTo(b.name), + ); + } + }); + }, + ), + ], + ), + const SizedBox(height: 10), + const Text( + 'Alle Spieler:', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 10), + FutureBuilder( + future: _allPlayersFuture, + builder: + ( + BuildContext context, + AsyncSnapshot> snapshot, + ) { + if (snapshot.hasError) { + return const Center( + child: TopCenteredMessage( + icon: Icons.report, + title: 'Error', + message: 'Player data couldn\'t\nbe loaded.', + ), + ); + } + if (snapshot.connectionState == + ConnectionState.done && + (!snapshot.hasData || + snapshot.data!.isEmpty || + (selectedPlayers.isEmpty && + allPlayers.isEmpty))) { + return const Center( + child: TopCenteredMessage( + icon: Icons.info, + title: 'Info', + message: 'No players created yet.', + ), + ); + } + final bool isLoading = + snapshot.connectionState == + ConnectionState.waiting; + return Expanded( + child: Skeletonizer( + effect: PulseEffect( + from: Colors.grey[800]!, + to: Colors.grey[600]!, + duration: const Duration(milliseconds: 800), + ), + enabled: isLoading, + enableSwitchAnimation: true, + switchAnimationConfig: + const SwitchAnimationConfig( + duration: Duration(milliseconds: 200), + switchInCurve: Curves.linear, + switchOutCurve: Curves.linear, + transitionBuilder: AnimatedSwitcher + .defaultTransitionBuilder, + layoutBuilder: + AnimatedSwitcher.defaultLayoutBuilder, + ), + child: + (suggestedPlayers.isEmpty && + allPlayers.isNotEmpty) + ? TopCenteredMessage( + icon: Icons.info, + title: 'Info', + message: + (selectedPlayers.length == + allPlayers.length) + ? 'No more players to add.' + : 'No players found with that name.', + ) + : ListView.builder( + itemCount: suggestedPlayers.length, + itemBuilder: + (BuildContext context, int index) { + return TextIconListTile( + text: suggestedPlayers[index] + .name, + onPressed: () { + setState(() { + if (!selectedPlayers.contains( + suggestedPlayers[index], + )) { + selectedPlayers.add( + suggestedPlayers[index], + ); + selectedPlayers.sort( + (a, b) => a.name + .compareTo(b.name), + ); + suggestedPlayers.remove( + suggestedPlayers[index], + ); + } + }); + }, + ); + }, + ), + ), + ); + }, + ), + ], + ), + ), + ), + CustomWidthButton( + text: 'Create group', + sizeRelativeToWidth: 0.95, + buttonType: ButtonType.primary, + onPressed: + (_groupNameController.text.isEmpty || selectedPlayers.isEmpty) + ? null + : () async { + bool success = await db.groupDao.addGroup( + group: Group( + name: _groupNameController.text, + members: selectedPlayers, + ), + ); + if (!context.mounted) return; + if (success) { + _groupNameController.clear(); + _searchBarController.clear(); + selectedPlayers.clear(); + Navigator.pop(context); + } else { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + backgroundColor: CustomTheme.boxColor, + content: const Center( + child: Text( + 'Error while creating group, please try again', + style: TextStyle(color: Colors.white), + ), + ), + ), + ); + } + setState(() {}); + }, + ), + const SizedBox(height: 20), + ], + ), + ), + ); + } +} diff --git a/lib/presentation/views/main_menu/game_history_view.dart b/lib/presentation/views/main_menu/game_history_view.dart index 3642a88..7c19bbf 100644 --- a/lib/presentation/views/main_menu/game_history_view.dart +++ b/lib/presentation/views/main_menu/game_history_view.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; -import 'package:game_tracker/presentation/widgets/double_row_info_tile.dart'; +import 'package:game_tracker/presentation/widgets/tiles/double_row_info_tile.dart'; import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; class GameHistoryView extends StatefulWidget { diff --git a/lib/presentation/views/main_menu/groups_view.dart b/lib/presentation/views/main_menu/groups_view.dart index 8edf20f..c45cf21 100644 --- a/lib/presentation/views/main_menu/groups_view.dart +++ b/lib/presentation/views/main_menu/groups_view.dart @@ -3,7 +3,8 @@ import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/player.dart'; -import 'package:game_tracker/presentation/widgets/full_width_button.dart'; +import 'package:game_tracker/presentation/views/main_menu/create_group_view.dart'; +import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; import 'package:game_tracker/presentation/widgets/tiles/group_tile.dart'; import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; import 'package:provider/provider.dart'; @@ -18,6 +19,7 @@ class GroupsView extends StatefulWidget { class _GroupsViewState extends State { late Future> _allGroupsFuture; + late final AppDatabase db; final player = Player(name: 'Skeleton Player'); late final List skeletonData = List.filled( @@ -31,7 +33,7 @@ class _GroupsViewState extends State { @override void initState() { super.initState(); - final db = Provider.of(context, listen: false); + db = Provider.of(context, listen: false); _allGroupsFuture = db.groupDao.getAllGroups(); } @@ -102,7 +104,23 @@ class _GroupsViewState extends State { Positioned( bottom: 80, - child: FullWidthButton(text: 'Create Group', onPressed: () {}), + child: CustomWidthButton( + text: 'Create Group', + sizeRelativeToWidth: 0.90, + onPressed: () async { + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) { + return const CreateGroupView(); + }, + ), + ); + setState(() { + _allGroupsFuture = db.groupDao.getAllGroups(); + }); + }, + ), ), ], ), diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index cf6288a..34e4be3 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/data/db/database.dart'; -import 'package:game_tracker/presentation/widgets/game_tile.dart'; -import 'package:game_tracker/presentation/widgets/quick_create_button.dart'; +import 'package:game_tracker/presentation/widgets/buttons/quick_create_button.dart'; +import 'package:game_tracker/presentation/widgets/tiles/game_tile.dart'; import 'package:game_tracker/presentation/widgets/tiles/info_tile.dart'; import 'package:game_tracker/presentation/widgets/tiles/quick_info_tile.dart'; import 'package:provider/provider.dart'; diff --git a/lib/presentation/widgets/buttons/custom_width_button.dart b/lib/presentation/widgets/buttons/custom_width_button.dart new file mode 100644 index 0000000..bce78ed --- /dev/null +++ b/lib/presentation/widgets/buttons/custom_width_button.dart @@ -0,0 +1,114 @@ +import 'package:flutter/material.dart'; +import 'package:game_tracker/core/custom_theme.dart'; +import 'package:game_tracker/core/enums.dart'; + +class CustomWidthButton extends StatelessWidget { + const CustomWidthButton({ + super.key, + required this.text, + this.buttonType = ButtonType.primary, + required this.sizeRelativeToWidth, + this.onPressed, + }); + + final String text; + final double sizeRelativeToWidth; + final VoidCallback? onPressed; + final ButtonType buttonType; + + @override + Widget build(BuildContext context) { + final Color buttonBackgroundColor; + final Color disabledBackgroundColor; + final Color borderSideColor; + final Color textcolor; + final Color disabledTextColor; + + if (buttonType == ButtonType.primary) { + textcolor = Colors.white; + disabledTextColor = Colors.white.withValues(alpha: 0.24); + buttonBackgroundColor = CustomTheme.primaryColor; + disabledBackgroundColor = CustomTheme.primaryColor.withValues( + alpha: 0.24, + ); + + return ElevatedButton( + onPressed: onPressed, + style: ElevatedButton.styleFrom( + foregroundColor: textcolor, + disabledForegroundColor: disabledTextColor, + backgroundColor: buttonBackgroundColor, + disabledBackgroundColor: disabledBackgroundColor, + animationDuration: const Duration(), + minimumSize: Size( + MediaQuery.sizeOf(context).width * sizeRelativeToWidth, + 60, + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + child: Text( + text, + style: const TextStyle(fontWeight: FontWeight.w500, fontSize: 22), + ), + ); + } else if (buttonType == ButtonType.secondary) { + textcolor = CustomTheme.primaryColor; + disabledTextColor = CustomTheme.primaryColor.withValues(alpha: 0.5); + buttonBackgroundColor = Colors.transparent; + disabledBackgroundColor = Colors.transparent; + borderSideColor = onPressed != null + ? CustomTheme.primaryColor + : CustomTheme.primaryColor.withValues(alpha: 0.5); + + return OutlinedButton( + onPressed: onPressed, + style: OutlinedButton.styleFrom( + foregroundColor: textcolor, + disabledForegroundColor: disabledTextColor, + backgroundColor: buttonBackgroundColor, + disabledBackgroundColor: disabledBackgroundColor, + animationDuration: const Duration(), + minimumSize: Size( + MediaQuery.sizeOf(context).width * sizeRelativeToWidth, + 60, + ), + side: BorderSide(color: borderSideColor, width: 2), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + child: Text( + text, + style: const TextStyle(fontWeight: FontWeight.w500, fontSize: 22), + ), + ); + } else { + textcolor = CustomTheme.primaryColor; + disabledTextColor = CustomTheme.primaryColor.withValues(alpha: 0.3); + buttonBackgroundColor = Colors.transparent; + disabledBackgroundColor = Colors.transparent; + + return TextButton( + onPressed: onPressed, + style: TextButton.styleFrom( + foregroundColor: textcolor, + disabledForegroundColor: disabledTextColor, + backgroundColor: buttonBackgroundColor, + disabledBackgroundColor: disabledBackgroundColor, + animationDuration: const Duration(), + minimumSize: Size( + MediaQuery.sizeOf(context).width * sizeRelativeToWidth, + 60, + ), + side: const BorderSide(style: BorderStyle.none), + ), + child: Text( + text, + style: const TextStyle(fontWeight: FontWeight.w500, fontSize: 22), + ), + ); + } + } +} diff --git a/lib/presentation/widgets/quick_create_button.dart b/lib/presentation/widgets/buttons/quick_create_button.dart similarity index 100% rename from lib/presentation/widgets/quick_create_button.dart rename to lib/presentation/widgets/buttons/quick_create_button.dart diff --git a/lib/presentation/widgets/custom_search_bar.dart b/lib/presentation/widgets/custom_search_bar.dart new file mode 100644 index 0000000..b482efb --- /dev/null +++ b/lib/presentation/widgets/custom_search_bar.dart @@ -0,0 +1,36 @@ +import 'package:flutter/material.dart'; +import 'package:game_tracker/core/custom_theme.dart'; + +class CustomSearchBar extends StatelessWidget { + final TextEditingController controller; + final String hintText; + final ValueChanged? onChanged; + final BoxConstraints? constraints; + + const CustomSearchBar({ + super.key, + required this.controller, + required this.hintText, + this.onChanged, + this.constraints, + }); + + @override + Widget build(BuildContext context) { + return SearchBar( + controller: controller, + constraints: + constraints ?? const BoxConstraints(maxHeight: 45, minHeight: 45), + hintText: hintText, + onChanged: onChanged, + hintStyle: WidgetStateProperty.all(const TextStyle(fontSize: 16)), + leading: const Icon(Icons.search), + backgroundColor: WidgetStateProperty.all(CustomTheme.boxColor), + side: WidgetStateProperty.all(BorderSide(color: CustomTheme.boxBorder)), + shape: WidgetStateProperty.all( + RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + ), + elevation: WidgetStateProperty.all(0), + ); + } +} diff --git a/lib/presentation/widgets/full_width_button.dart b/lib/presentation/widgets/full_width_button.dart deleted file mode 100644 index bd18c64..0000000 --- a/lib/presentation/widgets/full_width_button.dart +++ /dev/null @@ -1,29 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:game_tracker/core/custom_theme.dart'; - -class FullWidthButton extends StatelessWidget { - const FullWidthButton({super.key, required this.text, this.onPressed}); - - final String text; - final VoidCallback? onPressed; - - @override - Widget build(BuildContext context) { - return ElevatedButton( - onPressed: onPressed, - style: ElevatedButton.styleFrom( - minimumSize: Size(MediaQuery.sizeOf(context).width * 0.9, 60), - backgroundColor: CustomTheme.primaryColor, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), - ), - child: Text( - text, - style: const TextStyle( - fontWeight: FontWeight.w500, - fontSize: 22, - color: Colors.white, - ), - ), - ); - } -} diff --git a/lib/presentation/widgets/text_input_field.dart b/lib/presentation/widgets/text_input_field.dart new file mode 100644 index 0000000..6cd9d75 --- /dev/null +++ b/lib/presentation/widgets/text_input_field.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; +import 'package:game_tracker/core/custom_theme.dart'; + +class TextInputField extends StatelessWidget { + final TextEditingController controller; + final ValueChanged? onChanged; + final String hintText; + + const TextInputField({ + super.key, + required this.controller, + required this.hintText, + this.onChanged, + }); + + @override + Widget build(BuildContext context) { + return TextField( + controller: controller, + onChanged: onChanged, + decoration: InputDecoration( + filled: true, + fillColor: CustomTheme.boxColor, + hintText: hintText, + hintStyle: const TextStyle(fontSize: 18), + enabledBorder: OutlineInputBorder( + borderRadius: const BorderRadius.all(Radius.circular(12)), + borderSide: BorderSide(color: CustomTheme.boxBorder), + ), + focusedBorder: OutlineInputBorder( + borderRadius: const BorderRadius.all(Radius.circular(12)), + borderSide: BorderSide(color: CustomTheme.boxBorder), + ), + floatingLabelBehavior: FloatingLabelBehavior.never, + ), + ); + } +} diff --git a/lib/presentation/widgets/double_row_info_tile.dart b/lib/presentation/widgets/tiles/double_row_info_tile.dart similarity index 100% rename from lib/presentation/widgets/double_row_info_tile.dart rename to lib/presentation/widgets/tiles/double_row_info_tile.dart diff --git a/lib/presentation/widgets/game_tile.dart b/lib/presentation/widgets/tiles/game_tile.dart similarity index 100% rename from lib/presentation/widgets/game_tile.dart rename to lib/presentation/widgets/tiles/game_tile.dart diff --git a/lib/presentation/widgets/tiles/group_tile.dart b/lib/presentation/widgets/tiles/group_tile.dart index 448c68c..fa91477 100644 --- a/lib/presentation/widgets/tiles/group_tile.dart +++ b/lib/presentation/widgets/tiles/group_tile.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/dto/group.dart'; -import 'package:skeletonizer/skeletonizer.dart'; +import 'package:game_tracker/presentation/widgets/tiles/text_icon_tile.dart'; class GroupTile extends StatelessWidget { const GroupTile({super.key, required this.group}); @@ -24,24 +24,29 @@ class GroupTile extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text( - group.name, - overflow: TextOverflow.ellipsis, - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 18, + Flexible( + child: Text( + group.name, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 18, + ), ), ), - const Spacer(), - Text( - '${group.members.length}', - style: const TextStyle( - fontWeight: FontWeight.w900, - fontSize: 18, - ), + Row( + children: [ + Text( + '${group.members.length}', + style: const TextStyle( + fontWeight: FontWeight.w900, + fontSize: 18, + ), + ), + const SizedBox(width: 3), + const Icon(Icons.group, size: 22), + ], ), - const SizedBox(width: 3), - const Icon(Icons.group, size: 22), ], ), const SizedBox(height: 5), @@ -52,25 +57,7 @@ class GroupTile extends StatelessWidget { runSpacing: 8.0, children: [ for (var member in group.members) - Container( - padding: const EdgeInsets.symmetric( - vertical: 5, - horizontal: 10, - ), - decoration: BoxDecoration( - color: CustomTheme.onBoxColor, - borderRadius: BorderRadius.circular(12), - ), - child: Skeleton.ignore( - child: Text( - member.name, - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.bold, - ), - ), - ), - ), + TextIconTile(text: member.name, iconEnabled: false), ], ), const SizedBox(height: 2.5), diff --git a/lib/presentation/widgets/tiles/text_icon_list_tile.dart b/lib/presentation/widgets/tiles/text_icon_list_tile.dart new file mode 100644 index 0000000..5e272c9 --- /dev/null +++ b/lib/presentation/widgets/tiles/text_icon_list_tile.dart @@ -0,0 +1,52 @@ +import 'package:flutter/material.dart'; +import 'package:game_tracker/core/custom_theme.dart'; + +class TextIconListTile extends StatelessWidget { + final String text; + final VoidCallback? onPressed; + final bool iconEnabled; + + const TextIconListTile({ + super.key, + required this.text, + this.onPressed, + this.iconEnabled = true, + }); + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.symmetric(horizontal: 5, vertical: 5), + padding: const EdgeInsets.symmetric(horizontal: 15), + decoration: BoxDecoration( + color: CustomTheme.boxColor, + border: Border.all(color: CustomTheme.boxBorder), + borderRadius: BorderRadius.circular(12), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisSize: MainAxisSize.max, + children: [ + Flexible( + child: Container( + padding: const EdgeInsets.symmetric(vertical: 12.5), + child: Text( + text, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + if (iconEnabled) + GestureDetector( + onTap: onPressed, + child: const Icon(Icons.add, size: 20), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/widgets/tiles/text_icon_tile.dart b/lib/presentation/widgets/tiles/text_icon_tile.dart new file mode 100644 index 0000000..2544837 --- /dev/null +++ b/lib/presentation/widgets/tiles/text_icon_tile.dart @@ -0,0 +1,47 @@ +import 'package:flutter/material.dart'; +import 'package:game_tracker/core/custom_theme.dart'; + +class TextIconTile extends StatelessWidget { + final String text; + final bool iconEnabled; + final VoidCallback? onIconTap; + + const TextIconTile({ + super.key, + required this.text, + this.onIconTap, + this.iconEnabled = true, + }); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.all(5), + decoration: BoxDecoration( + color: CustomTheme.onBoxColor, + borderRadius: BorderRadius.circular(12), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisSize: MainAxisSize.min, + children: [ + if (iconEnabled) const SizedBox(width: 3), + Flexible( + child: Text( + text, + overflow: TextOverflow.ellipsis, + style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500), + ), + ), + if (iconEnabled) ...[ + const SizedBox(width: 3), + GestureDetector( + onTap: onIconTap, + child: const Icon(Icons.close, size: 20), + ), + ], + ], + ), + ); + } +}