From 18f635e6ef0346ab689bd10f6953ece0f8ebab2c Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 24 Nov 2025 20:05:07 +0100 Subject: [PATCH 1/2] Implemented custom skeleton widget --- lib/presentation/widgets/app_skeleton.dart | 51 ++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 lib/presentation/widgets/app_skeleton.dart diff --git a/lib/presentation/widgets/app_skeleton.dart b/lib/presentation/widgets/app_skeleton.dart new file mode 100644 index 0000000..209f1d8 --- /dev/null +++ b/lib/presentation/widgets/app_skeleton.dart @@ -0,0 +1,51 @@ +import 'package:flutter/material.dart'; +import 'package:skeletonizer/skeletonizer.dart'; + +class AppSkeleton extends StatefulWidget { + final Widget child; + final bool enabled; + final bool fixLayoutBuilder; + + const AppSkeleton({ + super.key, + required this.child, + this.enabled = true, + this.fixLayoutBuilder = false, + }); + + @override + State createState() => _AppSkeletonState(); +} + +class _AppSkeletonState extends State { + @override + Widget build(BuildContext context) { + return Skeletonizer( + effect: PulseEffect( + from: Colors.grey[800]!, + to: Colors.grey[600]!, + duration: const Duration(milliseconds: 800), + ), + enabled: widget.enabled, + enableSwitchAnimation: true, + switchAnimationConfig: SwitchAnimationConfig( + duration: const Duration(milliseconds: 200), + switchInCurve: Curves.linear, + switchOutCurve: Curves.linear, + transitionBuilder: AnimatedSwitcher.defaultTransitionBuilder, + layoutBuilder: !widget.fixLayoutBuilder + ? AnimatedSwitcher.defaultLayoutBuilder + : (Widget? currentChild, List previousChildren) { + return Stack( + alignment: Alignment.topCenter, + children: [ + ...previousChildren, + if (currentChild != null) currentChild, + ], + ); + }, + ), + child: widget.child, + ); + } +} From 2d9148788e55e99e9a6711fb7ff36837d257d2e4 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 24 Nov 2025 20:05:18 +0100 Subject: [PATCH 2/2] Refactored skeleton widgets to own --- .../views/main_menu/create_group_view.dart | 20 ++------------ .../views/main_menu/groups_view.dart | 18 ++----------- .../views/main_menu/home_view.dart | 26 ++---------------- .../views/main_menu/statistics_view.dart | 27 +++---------------- 4 files changed, 9 insertions(+), 82 deletions(-) diff --git a/lib/presentation/views/main_menu/create_group_view.dart b/lib/presentation/views/main_menu/create_group_view.dart index c01250b..ef78169 100644 --- a/lib/presentation/views/main_menu/create_group_view.dart +++ b/lib/presentation/views/main_menu/create_group_view.dart @@ -4,6 +4,7 @@ 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/app_skeleton.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'; @@ -11,7 +12,6 @@ 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}); @@ -228,24 +228,8 @@ class _CreateGroupViewState extends State { snapshot.connectionState == ConnectionState.waiting; return Expanded( - child: Skeletonizer( - effect: PulseEffect( - from: Colors.grey[800]!, - to: Colors.grey[600]!, - duration: const Duration(milliseconds: 800), - ), + child: AppSkeleton( enabled: isLoading, - enableSwitchAnimation: true, - switchAnimationConfig: - const SwitchAnimationConfig( - duration: Duration(milliseconds: 200), - switchInCurve: Curves.linear, - switchOutCurve: Curves.linear, - transitionBuilder: AnimatedSwitcher - .defaultTransitionBuilder, - layoutBuilder: - AnimatedSwitcher.defaultLayoutBuilder, - ), child: Visibility( visible: (suggestedPlayers.isEmpty && diff --git a/lib/presentation/views/main_menu/groups_view.dart b/lib/presentation/views/main_menu/groups_view.dart index aaef1a5..29fbac8 100644 --- a/lib/presentation/views/main_menu/groups_view.dart +++ b/lib/presentation/views/main_menu/groups_view.dart @@ -4,11 +4,11 @@ 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/views/main_menu/create_group_view.dart'; +import 'package:game_tracker/presentation/widgets/app_skeleton.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'; -import 'package:skeletonizer/skeletonizer.dart'; class GroupsView extends StatefulWidget { const GroupsView({super.key}); @@ -75,22 +75,8 @@ class _GroupsViewState extends State { final List groups = isLoading ? skeletonData : (snapshot.data ?? []) ..sort((a, b) => b.createdAt.compareTo(a.createdAt)); - return Skeletonizer( - effect: PulseEffect( - from: Colors.grey[800]!, - to: Colors.grey[600]!, - duration: const Duration(milliseconds: 800), - ), + return AppSkeleton( enabled: isLoading, - enableSwitchAnimation: true, - switchAnimationConfig: const SwitchAnimationConfig( - duration: Duration(milliseconds: 200), - switchInCurve: Curves.linear, - switchOutCurve: Curves.linear, - transitionBuilder: - AnimatedSwitcher.defaultTransitionBuilder, - layoutBuilder: AnimatedSwitcher.defaultLayoutBuilder, - ), child: ListView.builder( padding: const EdgeInsets.only(bottom: 85), itemCount: groups.length + 1, diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index f75eb78..1667f2b 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -3,12 +3,12 @@ import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/game.dart'; import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/player.dart'; +import 'package:game_tracker/presentation/widgets/app_skeleton.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'; -import 'package:skeletonizer/skeletonizer.dart'; class HomeView extends StatefulWidget { const HomeView({super.key}); @@ -62,30 +62,8 @@ class _HomeViewState extends State { Widget build(BuildContext context) { return LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { - return Skeletonizer( - effect: PulseEffect( - from: Colors.grey[800]!, - to: Colors.grey[600]!, - duration: const Duration(milliseconds: 800), - ), + return AppSkeleton( enabled: isLoading, - enableSwitchAnimation: true, - switchAnimationConfig: SwitchAnimationConfig( - duration: const Duration(milliseconds: 200), - switchInCurve: Curves.linear, - switchOutCurve: Curves.linear, - transitionBuilder: AnimatedSwitcher.defaultTransitionBuilder, - layoutBuilder: - (Widget? currentChild, List previousChildren) { - return Stack( - alignment: Alignment.topCenter, - children: [ - ...previousChildren, - if (currentChild != null) currentChild, - ], - ); - }, - ), child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.center, diff --git a/lib/presentation/views/main_menu/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view.dart index 6107586..564d0d5 100644 --- a/lib/presentation/views/main_menu/statistics_view.dart +++ b/lib/presentation/views/main_menu/statistics_view.dart @@ -2,9 +2,9 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/game.dart'; import 'package:game_tracker/data/dto/player.dart'; +import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; import 'package:game_tracker/presentation/widgets/tiles/statistics_tile.dart'; import 'package:provider/provider.dart'; -import 'package:skeletonizer/skeletonizer.dart'; class StatisticsView extends StatefulWidget { const StatisticsView({super.key}); @@ -48,30 +48,9 @@ class _StatisticsViewState extends State { return LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { return SingleChildScrollView( - child: Skeletonizer( - effect: PulseEffect( - from: Colors.grey[800]!, - to: Colors.grey[600]!, - duration: const Duration(milliseconds: 800), - ), + child: AppSkeleton( enabled: isLoading, - enableSwitchAnimation: true, - switchAnimationConfig: SwitchAnimationConfig( - duration: const Duration(milliseconds: 200), - switchInCurve: Curves.linear, - switchOutCurve: Curves.linear, - transitionBuilder: AnimatedSwitcher.defaultTransitionBuilder, - layoutBuilder: - (Widget? currentChild, List previousChildren) { - return Stack( - alignment: Alignment.topCenter, - children: [ - ...previousChildren, - if (currentChild != null) currentChild, - ], - ); - }, - ), + fixLayoutBuilder: true, child: ConstrainedBox( constraints: BoxConstraints(minWidth: constraints.maxWidth), child: Column(