diff --git a/data/lib/service/ball_score/ball_score_service.dart b/data/lib/service/ball_score/ball_score_service.dart index 93127b34..144920ea 100644 --- a/data/lib/service/ball_score/ball_score_service.dart +++ b/data/lib/service/ball_score/ball_score_service.dart @@ -5,7 +5,6 @@ import '../../errors/app_error.dart'; import '../../extensions/double_extensions.dart'; import '../innings/inning_service.dart'; import '../match/match_service.dart'; -import '../../storage/app_preferences.dart'; import '../../utils/constant/firestore_constant.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -16,16 +15,12 @@ final ballScoreServiceProvider = Provider((ref) { FirebaseFirestore.instance, ref.read(matchServiceProvider), ref.read(inningServiceProvider), - ref.read(currentUserPod)?.id, ); - ref.listen(currentUserPod, (_, next) => service._currentUserId = next?.id); return service; }); class BallScoreService { - String? _currentUserId; - final FirebaseFirestore _firestore; final MatchService _matchService; final InningsService _inningsService; @@ -34,7 +29,6 @@ class BallScoreService { this._firestore, this._matchService, this._inningsService, - this._currentUserId, ); CollectionReference get _ballScoreCollection => @@ -135,25 +129,6 @@ class BallScoreService { .handleError((error, stack) => throw AppError.fromError(error, stack)); } - Stream> streamCurrentUserRelatedBalls() { - if (_currentUserId == null) { - return Stream.value([]); - } - - return _ballScoreCollection - .where( - Filter.or( - Filter(FireStoreConst.bowlerId, isEqualTo: _currentUserId), - Filter(FireStoreConst.batsmanId, isEqualTo: _currentUserId), - Filter(FireStoreConst.wicketTakerId, isEqualTo: _currentUserId), - Filter(FireStoreConst.playerOutId, isEqualTo: _currentUserId), - ), - ) - .snapshots() - .map((event) => event.docs.map((score) => score.data()).toList()) - .handleError((error, stack) => throw AppError.fromError(error, stack)); - } - Future deleteBallAndUpdateTeamDetails({ required String ballId, required String matchId, diff --git a/data/lib/service/match/match_service.dart b/data/lib/service/match/match_service.dart index 057b744a..730080d0 100644 --- a/data/lib/service/match/match_service.dart +++ b/data/lib/service/match/match_service.dart @@ -94,30 +94,6 @@ class MatchService { } } - Stream> streamCurrentUserPlayedMatches() { - if (_currentUserId == null) { - return Stream.value([]); - } - - final filter = Filter.and( - Filter(FireStoreConst.matchStatus, isEqualTo: MatchStatus.finish.value), - Filter(FireStoreConst.players, arrayContains: _currentUserId), - ); - - return _matchCollection - .where(filter) - .snapshots() - .asyncMap((snapshot) async { - return await Future.wait( - snapshot.docs.map((mainDoc) async { - final match = mainDoc.data(); - final List teams = await getTeamsList(match.teams); - return match.copyWith(teams: teams); - }).toList(), - ); - }).handleError((error, stack) => throw AppError.fromError(error, stack)); - } - Stream> streamCurrentUserRelatedMatches() { if (_currentUserId == null) { return Stream.value([]); diff --git a/khelo/assets/locales/app_en.arb b/khelo/assets/locales/app_en.arb index f46f3391..72ae31a7 100644 --- a/khelo/assets/locales/app_en.arb +++ b/khelo/assets/locales/app_en.arb @@ -301,6 +301,7 @@ "team_detail_add_members_title": "Add members", "team_detail_add_match_title": "Add match", "team_detail_make_admin": "Make admin", + "make_admin_selection_error": "Can't select deactivated user as admin", "team_detail_make_admin_screen_title": "Make Admin", "team_detail_admin": "{count, plural, =0{{count} admins} =1{{count} admin} other{{count} admins}}", "@team_detail_admin": { diff --git a/khelo/lib/ui/flow/main/main_screen.dart b/khelo/lib/ui/flow/main/main_screen.dart index fa76f6d1..6c25bb6b 100644 --- a/khelo/lib/ui/flow/main/main_screen.dart +++ b/khelo/lib/ui/flow/main/main_screen.dart @@ -8,7 +8,7 @@ import 'package:khelo/domain/extensions/context_extensions.dart'; import 'package:khelo/gen/assets.gen.dart'; import 'package:khelo/ui/flow/my_game/my_game_tab_screen.dart'; import 'package:khelo/ui/flow/profile/profile_screen.dart'; -import 'package:khelo/ui/flow/stats/my_stats_tab_screen.dart'; +import 'package:khelo/ui/flow/stats/user_stat/user_stat_screen.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:style/extensions/context_extensions.dart'; import 'package:style/navigation/bottom_navigation_bar.dart'; @@ -31,7 +31,7 @@ class _MainScreenState extends ConsumerState static final List _widgets = [ const HomeScreen(), const MyGameTabScreen(), - const MyStatsTabScreen(), + const UserStatScreen(), const ProfileScreen(), ]; diff --git a/khelo/lib/ui/flow/matches/add_match/match_officials/add_match_officials_screen.dart b/khelo/lib/ui/flow/matches/add_match/match_officials/add_match_officials_screen.dart index 0666377a..742f8030 100644 --- a/khelo/lib/ui/flow/matches/add_match/match_officials/add_match_officials_screen.dart +++ b/khelo/lib/ui/flow/matches/add_match/match_officials/add_match_officials_screen.dart @@ -49,13 +49,14 @@ class _AddMatchOfficialsScreenState return Stack( children: [ ListView( - padding: context.mediaQueryPadding + BottomStickyOverlay.padding, + padding: context.mediaQueryPadding + + BottomStickyOverlay.padding + + const EdgeInsets.only(bottom: 40), children: [ for (final type in MatchOfficials.values) ...[ _sectionTitle(context, type.getTitle(context)), _officialList(context, notifier, state, type), ], - const SizedBox(height: 40) ], ), _stickyButton(context, state) diff --git a/khelo/lib/ui/flow/matches/add_match/match_officials/search_user/search_user_screen.dart b/khelo/lib/ui/flow/matches/add_match/match_officials/search_user/search_user_screen.dart index a9991cc2..ae99f10e 100644 --- a/khelo/lib/ui/flow/matches/add_match/match_officials/search_user/search_user_screen.dart +++ b/khelo/lib/ui/flow/matches/add_match/match_officials/search_user/search_user_screen.dart @@ -36,10 +36,11 @@ class SearchUserBottomSheet extends ConsumerWidget { final notifier = ref.watch(searchUserStateProvider.notifier); final state = ref.watch(searchUserStateProvider); - return Padding( - padding: const EdgeInsets.symmetric(vertical: 8.0), - child: SizedBox( - height: context.mediaQuerySize.height * 0.8, + return SizedBox( + height: context.mediaQuerySize.height * 0.8, + child: Padding( + padding: + EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom), child: Column( children: [ _searchTextField(context, notifier, state), @@ -73,6 +74,7 @@ class SearchUserBottomSheet extends ConsumerWidget { isShowButton: false, ) : ListView.separated( + padding: const EdgeInsets.only(top: 16, bottom: 40), separatorBuilder: (context, index) { return Divider( color: context.colorScheme.outline, diff --git a/khelo/lib/ui/flow/score_board/add_substitute_sheet/add_substitute_sheet.dart b/khelo/lib/ui/flow/score_board/add_substitute_sheet/add_substitute_sheet.dart index c3ca3ac6..69f3231e 100644 --- a/khelo/lib/ui/flow/score_board/add_substitute_sheet/add_substitute_sheet.dart +++ b/khelo/lib/ui/flow/score_board/add_substitute_sheet/add_substitute_sheet.dart @@ -3,16 +3,16 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; -import 'package:khelo/components/empty_screen.dart'; import 'package:khelo/components/error_screen.dart'; +import 'package:khelo/components/image_avatar.dart'; import 'package:khelo/components/user_detail_cell.dart'; import 'package:khelo/domain/extensions/context_extensions.dart'; import 'package:khelo/domain/extensions/widget_extension.dart'; import 'package:khelo/ui/flow/matches/add_match/select_squad/components/user_detail_sheet.dart'; import 'package:khelo/ui/flow/score_board/add_substitute_sheet/add_substitute_view_model.dart'; -import 'package:khelo/ui/flow/score_board/components/user_cell_view.dart'; +import 'package:khelo/ui/flow/score_board/components/bottom_sheet_wrapper.dart'; import 'package:khelo/ui/flow/team/add_team_member/components/verify_team_member_sheet.dart'; -import 'package:style/button/bottom_sticky_overlay.dart'; +import 'package:style/animations/on_tap_scale.dart'; import 'package:style/button/primary_button.dart'; import 'package:style/button/secondary_button.dart'; import 'package:style/extensions/context_extensions.dart'; @@ -80,89 +80,42 @@ class _AddSubstituteSheetState extends ConsumerState { Widget build(BuildContext context) { final state = ref.watch(addSubstituteStateProvider); - return Container( - height: context.mediaQuerySize.height * 0.8, - decoration: BoxDecoration( - color: context.colorScheme.surface, - borderRadius: const BorderRadius.vertical(top: Radius.circular(16)), - ), - child: Stack( - children: [ - Padding( - padding: const EdgeInsets.only(top: 44) + - const EdgeInsets.symmetric(horizontal: 16.0) + - context.mediaQueryPadding, - child: CustomScrollView( - slivers: _content(context, state), - ), - ), - _dragHandle(context), - if (state.error == null) _stickyButton(context), - ], - ), - ); - } - - Widget _dragHandle(BuildContext context) { - return Align( - alignment: Alignment.topCenter, - child: Container( - height: 4, - width: 32, - margin: const EdgeInsets.symmetric(vertical: 20), - decoration: BoxDecoration( - color: context.colorScheme.outline, - borderRadius: BorderRadius.circular(10)), - ), + return BottomSheetWrapper( + contentBottomSpacing: 16, + content: _content(context, state), + action: [ + PrimaryButton( + context.l10n.common_select_title, + enabled: selectedPlayer != null, + onPressed: () => context.pop(selectedPlayer), + ), + ], ); } - Widget _stickyButton(BuildContext context) { - return BottomStickyOverlay( - child: PrimaryButton( - context.l10n.common_select_title, - enabled: selectedPlayer != null, - onPressed: () => context.pop(selectedPlayer), - ), - ); - } - - List _content(BuildContext context, AddSubstituteViewState state) { + Widget _content(BuildContext context, AddSubstituteViewState state) { if (state.error != null) { - return [ - SliverToBoxAdapter( - child: Padding( - padding: BottomStickyOverlay.padding, - child: ErrorScreen( - error: state.error, - onRetryTap: notifier.onSearchChanged, - ), - ), - ) - ]; + return ErrorScreen( + error: state.error, + onRetryTap: notifier.onSearchChanged, + ); } - return [ - SliverPadding( - padding: BottomStickyOverlay.padding, - sliver: SliverMainAxisGroup(slivers: [ - SliverToBoxAdapter( - child: Text( - context.l10n.score_board_add_substitute_title, - style: AppTextStyle.header3 - .copyWith(color: context.colorScheme.textPrimary), - ), - ), - const SliverToBoxAdapter(child: SizedBox(height: 16)), - SliverToBoxAdapter( - child: _benchPlayerView(context, state.nonPlayingPlayers)), - SliverToBoxAdapter(child: _searchTextField(context, notifier, state)), - const SliverToBoxAdapter(child: SizedBox(height: 24)), - SliverFillRemaining( - hasScrollBody: false, - child: _searchResultView(context, notifier, state)), - ]), - ) - ]; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + context.l10n.score_board_add_substitute_title, + style: AppTextStyle.header3 + .copyWith(color: context.colorScheme.textPrimary), + ), + const SizedBox(height: 16), + _benchPlayerView(context, state.nonPlayingPlayers), + _searchTextField(context, notifier, state), + const SizedBox(height: 24), + _searchResultView(context, notifier, state), + ], + ); } Widget _benchPlayerView(BuildContext context, List bench) { @@ -172,12 +125,35 @@ class _AddSubstituteSheetState extends ConsumerState { children: bench.map((player) { return Padding( padding: const EdgeInsets.only(right: 16, bottom: 16), - child: UserCellView( - title: player.name ?? context.l10n.common_anonymous_title, - imageUrl: player.profile_img_url, - initial: player.nameInitial, - isSelected: selectedPlayer?.id == player.id, + child: OnTapScale( onTap: () => setState(() => selectedPlayer = player), + child: Container( + padding: + const EdgeInsets.symmetric(vertical: 12, horizontal: 16), + decoration: BoxDecoration( + border: Border.all( + color: selectedPlayer?.id == player.id + ? context.colorScheme.primary + : Colors.transparent), + borderRadius: BorderRadius.circular(16), + color: context.colorScheme.containerLow), + child: Column( + children: [ + ImageAvatar( + initial: player.nameInitial, + imageUrl: player.profile_img_url, + size: 40, + ), + const SizedBox(height: 4), + Text( + player.name ?? context.l10n.common_anonymous_title, + style: AppTextStyle.caption.copyWith( + color: context.colorScheme.textPrimary, + ), + ) + ], + ), + ), ), ); }).toList(), @@ -190,46 +166,50 @@ class _AddSubstituteSheetState extends ConsumerState { AddSubstituteViewNotifier notifier, AddSubstituteViewState state, ) { - return state.searchedUsers.isEmpty - ? EmptyScreen( - title: (state.searchController.text.isNotEmpty) - ? context.l10n.add_team_member_search_no_result_title - : context.l10n.add_substitute_search_substitute_title, - description: (state.searchController.text.isNotEmpty) - ? context.l10n.add_team_member_search_description_text - : context.l10n.add_substitute_search_substitute_description, - isShowButton: false, - ) - : Column( - children: state.searchedUsers.map( - (user) { - final enableAction = !state.playingSquadIds.contains(user.id); - return Padding( - padding: const EdgeInsets.only(bottom: 16), - child: UserDetailCell( - user: user, - trailing: _addButton(context, user, enableAction), - onTap: () => UserDetailSheet.show( - context, - user, - actionButtonTitle: enableAction - ? context.l10n.common_select_title - : null, - onButtonTap: () async { - if (user.phone != null) { - final res = await VerifyTeamMemberSheet.show(context, - phoneNumber: user.phone!); - if (res != null && res && context.mounted) { - context.pop(user); - } - } - }, - ), - ), - ); - }, - ).toList(), + return AnimatedCrossFade( + duration: const Duration(milliseconds: 300), + sizeCurve: Curves.decelerate, + crossFadeState: state.searchedUsers.isEmpty + ? CrossFadeState.showFirst + : CrossFadeState.showSecond, + firstChild: _emptyScreen( + context, + title: (state.searchController.text.isNotEmpty) + ? context.l10n.add_team_member_search_no_result_title + : context.l10n.add_substitute_search_substitute_title, + description: (state.searchController.text.isNotEmpty) + ? context.l10n.add_team_member_search_description_text + : context.l10n.add_substitute_search_substitute_description, + ), + secondChild: Column( + children: state.searchedUsers.map( + (user) { + final enableAction = !state.playingSquadIds.contains(user.id); + return Padding( + padding: const EdgeInsets.only(bottom: 16), + child: UserDetailCell( + user: user, + trailing: _addButton(context, user, enableAction), + onTap: () => UserDetailSheet.show( + context, + user, + actionButtonTitle: + enableAction ? context.l10n.common_select_title : null, + onButtonTap: () async { + if (user.phone != null) { + final res = await VerifyTeamMemberSheet.show(context, + phoneNumber: user.phone!); + if (res != null && res && context.mounted) { + context.pop(user); + } + } + }, + ), + ), ); + }, + ).toList()), + ); } Widget _searchTextField( @@ -262,4 +242,27 @@ class _AddSubstituteSheetState extends ConsumerState { }, ); } + + Widget _emptyScreen( + BuildContext context, { + required String title, + required String description, + }) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: AppTextStyle.header4 + .copyWith(color: context.colorScheme.textPrimary), + ), + const SizedBox(height: 16), + Text( + description, + style: AppTextStyle.subtitle2 + .copyWith(color: context.colorScheme.textSecondary), + ), + ], + ); + } } diff --git a/khelo/lib/ui/flow/score_board/components/bottom_sheet_wrapper.dart b/khelo/lib/ui/flow/score_board/components/bottom_sheet_wrapper.dart index e3a6ffcd..109bc42b 100644 --- a/khelo/lib/ui/flow/score_board/components/bottom_sheet_wrapper.dart +++ b/khelo/lib/ui/flow/score_board/components/bottom_sheet_wrapper.dart @@ -21,54 +21,60 @@ class BottomSheetWrapper extends StatelessWidget { @override Widget build(BuildContext context) { return Container( - constraints: - BoxConstraints(maxHeight: context.mediaQuerySize.height * 0.8), + constraints: BoxConstraints( + maxHeight: (context.mediaQuerySize.height - + MediaQuery.of(context).viewInsets.bottom) * + 0.8), decoration: BoxDecoration( color: context.colorScheme.surface, borderRadius: const BorderRadius.vertical(top: Radius.circular(16)), ), - child: IntrinsicHeight( - child: Stack( - children: [ - Padding( - padding: EdgeInsets.only(top: showDragHandle ? 44 : 0), - child: SingleChildScrollView( - padding: const EdgeInsets.symmetric(horizontal: 16.0) + - EdgeInsets.only(bottom: contentBottomSpacing) + - context.mediaQueryPadding + - BottomStickyOverlay.padding, - child: content), - ), - if (showDragHandle) ...[ - Align( - alignment: Alignment.topCenter, - child: Container( - height: 4, - width: 32, - margin: const EdgeInsets.symmetric(vertical: 20), - decoration: BoxDecoration( - color: context.colorScheme.outline, - borderRadius: BorderRadius.circular(10)), - ), + child: AnimatedSize( + duration: const Duration(milliseconds: 300), + curve: Curves.ease, + child: IntrinsicHeight( + child: Stack( + children: [ + Padding( + padding: EdgeInsets.only(top: showDragHandle ? 44 : 0), + child: SingleChildScrollView( + padding: const EdgeInsets.symmetric(horizontal: 16.0) + + EdgeInsets.only(bottom: contentBottomSpacing) + + context.mediaQueryPadding + + BottomStickyOverlay.padding, + child: content), ), - ], - BottomStickyOverlay( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - ...?options, - Row( - children: [ - for (int i = 0; i < action.length; i++) ...[ - Expanded(child: action[i]), - if (i < action.length - 1) const SizedBox(width: 16), - ] - ], + if (showDragHandle) ...[ + Align( + alignment: Alignment.topCenter, + child: Container( + height: 4, + width: 32, + margin: const EdgeInsets.symmetric(vertical: 20), + decoration: BoxDecoration( + color: context.colorScheme.outline, + borderRadius: BorderRadius.circular(10)), ), - ], + ), + ], + BottomStickyOverlay( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ...?options, + Row( + children: [ + for (int i = 0; i < action.length; i++) ...[ + Expanded(child: action[i]), + if (i < action.length - 1) const SizedBox(width: 16), + ] + ], + ), + ], + ), ), - ), - ], + ], + ), ), ), ); diff --git a/khelo/lib/ui/flow/score_board/score_board_view_model.dart b/khelo/lib/ui/flow/score_board/score_board_view_model.dart index c3aaf772..1ac64bee 100644 --- a/khelo/lib/ui/flow/score_board/score_board_view_model.dart +++ b/khelo/lib/ui/flow/score_board/score_board_view_model.dart @@ -1377,9 +1377,7 @@ class ScoreBoardViewNotifier extends StateNotifier { "INVALID ID"; final teamPlayers = state.match?.teams .firstWhere((element) => element.team.id == teamId) - .squad - .where((element) => element.player.isActive) - .toList(); + .squad; if (type == PlayerSelectionType.bowler) { // remove substitute to select from bowlers @@ -1434,8 +1432,7 @@ class ScoreBoardViewNotifier extends StateNotifier { .firstOrNull; final teamSquadIds = team?.squad.map((e) => e.player.id).toList() ?? []; final teamPlayers = team?.team.players - .where((element) => - element.user.isActive && !teamSquadIds.contains(element.id)) + .where((element) => !teamSquadIds.contains(element.id)) .map((e) => e.user) .toList(); return teamPlayers ?? []; @@ -1496,9 +1493,7 @@ class ScoreBoardViewNotifier extends StateNotifier { final teamId = state.otherInning?.team_id ?? "INVALID ID"; final teamPlayers = state.match?.teams .firstWhere((element) => element.team.id == teamId) - .squad - .where((element) => element.player.isActive) - .toList(); + .squad; return teamPlayers ?? []; } diff --git a/khelo/lib/ui/flow/stats/my_stats_tab_screen.dart b/khelo/lib/ui/flow/stats/my_stats_tab_screen.dart deleted file mode 100644 index 003fabca..00000000 --- a/khelo/lib/ui/flow/stats/my_stats_tab_screen.dart +++ /dev/null @@ -1,113 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:khelo/components/app_page.dart'; -import 'package:khelo/domain/extensions/context_extensions.dart'; -import 'package:khelo/ui/flow/stats/my_stats_tab_view_model.dart'; -import 'package:khelo/ui/flow/stats/user_match/user_match_list_screen.dart'; -import 'package:khelo/ui/flow/stats/user_stat/user_stat_screen.dart'; -import 'package:style/button/action_button.dart'; -import 'package:style/button/tab_button.dart'; -import 'package:style/extensions/context_extensions.dart'; - -class MyStatsTabScreen extends ConsumerStatefulWidget { - const MyStatsTabScreen({super.key}); - - @override - ConsumerState createState() => _MyStatsTabScreenState(); -} - -class _MyStatsTabScreenState extends ConsumerState - with WidgetsBindingObserver { - final List _tabs = [ - const UserMatchListScreen(), - const UserStatScreen(), - ]; - - late PageController _controller; - - int get _selectedTab => _controller.hasClients - ? _controller.page?.round() ?? 0 - : _controller.initialPage; - - @override - void initState() { - super.initState(); - WidgetsBinding.instance.addObserver(this); - _controller = PageController( - initialPage: ref.read(myStatsTabStateProvider).selectedTab, - ); - } - - @override - void didChangeAppLifecycleState(AppLifecycleState state) { - if (state == AppLifecycleState.detached) { - // deallocate resources - _controller.dispose(); - WidgetsBinding.instance.removeObserver(this); - } - } - - @override - Widget build(BuildContext context) { - final notifier = ref.watch(myStatsTabStateProvider.notifier); - return AppPage( - body: Builder( - builder: (context) { - return _content(context, notifier); - }, - ), - ); - } - - Widget _content(BuildContext context, MyStatsTabViewNotifier notifier) { - return SafeArea( - child: Column( - children: [ - _tabView(context), - Expanded( - child: PageView( - controller: _controller, - children: _tabs, - onPageChanged: (index) { - notifier.onTabChange(index); - setState(() {}); - }, - ), - ), - ], - ), - ); - } - - Widget _tabView(BuildContext context) { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: Row( - children: [ - TabButton( - context.l10n.common_matches_title, - selected: _selectedTab == 0, - onTap: () => _controller.jumpToPage(0), - ), - const SizedBox(width: 8), - TabButton( - context.l10n.tab_stats_title, - selected: _selectedTab == 1, - onTap: () => _controller.jumpToPage(1), - ), - - // Dummy add-icon to maintain consistency in tab placement for myCricket & Stats tab. - Visibility( - visible: false, - maintainState: true, - maintainAnimation: true, - maintainSize: true, - child: actionButton(context, - onPressed: null, - icon: Icon(Icons.add, color: context.colorScheme.primary)), - ), - ], - ), - ); - } -} diff --git a/khelo/lib/ui/flow/stats/my_stats_tab_view_model.dart b/khelo/lib/ui/flow/stats/my_stats_tab_view_model.dart deleted file mode 100644 index 35628d0b..00000000 --- a/khelo/lib/ui/flow/stats/my_stats_tab_view_model.dart +++ /dev/null @@ -1,25 +0,0 @@ -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; - -part 'my_stats_tab_view_model.freezed.dart'; - -final myStatsTabStateProvider = - StateNotifierProvider.autoDispose( - (ref) => MyStatsTabViewNotifier()); - -class MyStatsTabViewNotifier extends StateNotifier { - MyStatsTabViewNotifier() : super(const MyStatsTabState()); - - void onTabChange(int tab) { - if (state.selectedTab != tab) { - state = state.copyWith(selectedTab: tab); - } - } -} - -@freezed -class MyStatsTabState with _$MyStatsTabState { - const factory MyStatsTabState({ - @Default(0) int selectedTab, - }) = _MyStatsTabState; -} diff --git a/khelo/lib/ui/flow/stats/my_stats_tab_view_model.freezed.dart b/khelo/lib/ui/flow/stats/my_stats_tab_view_model.freezed.dart deleted file mode 100644 index af98bfc3..00000000 --- a/khelo/lib/ui/flow/stats/my_stats_tab_view_model.freezed.dart +++ /dev/null @@ -1,147 +0,0 @@ -// coverage:ignore-file -// GENERATED CODE - DO NOT MODIFY BY HAND -// ignore_for_file: type=lint -// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark - -part of 'my_stats_tab_view_model.dart'; - -// ************************************************************************** -// FreezedGenerator -// ************************************************************************** - -T _$identity(T value) => value; - -final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); - -/// @nodoc -mixin _$MyStatsTabState { - int get selectedTab => throw _privateConstructorUsedError; - - /// Create a copy of MyStatsTabState - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - $MyStatsTabStateCopyWith get copyWith => - throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $MyStatsTabStateCopyWith<$Res> { - factory $MyStatsTabStateCopyWith( - MyStatsTabState value, $Res Function(MyStatsTabState) then) = - _$MyStatsTabStateCopyWithImpl<$Res, MyStatsTabState>; - @useResult - $Res call({int selectedTab}); -} - -/// @nodoc -class _$MyStatsTabStateCopyWithImpl<$Res, $Val extends MyStatsTabState> - implements $MyStatsTabStateCopyWith<$Res> { - _$MyStatsTabStateCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - /// Create a copy of MyStatsTabState - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? selectedTab = null, - }) { - return _then(_value.copyWith( - selectedTab: null == selectedTab - ? _value.selectedTab - : selectedTab // ignore: cast_nullable_to_non_nullable - as int, - ) as $Val); - } -} - -/// @nodoc -abstract class _$$MyStatsTabStateImplCopyWith<$Res> - implements $MyStatsTabStateCopyWith<$Res> { - factory _$$MyStatsTabStateImplCopyWith(_$MyStatsTabStateImpl value, - $Res Function(_$MyStatsTabStateImpl) then) = - __$$MyStatsTabStateImplCopyWithImpl<$Res>; - @override - @useResult - $Res call({int selectedTab}); -} - -/// @nodoc -class __$$MyStatsTabStateImplCopyWithImpl<$Res> - extends _$MyStatsTabStateCopyWithImpl<$Res, _$MyStatsTabStateImpl> - implements _$$MyStatsTabStateImplCopyWith<$Res> { - __$$MyStatsTabStateImplCopyWithImpl( - _$MyStatsTabStateImpl _value, $Res Function(_$MyStatsTabStateImpl) _then) - : super(_value, _then); - - /// Create a copy of MyStatsTabState - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? selectedTab = null, - }) { - return _then(_$MyStatsTabStateImpl( - selectedTab: null == selectedTab - ? _value.selectedTab - : selectedTab // ignore: cast_nullable_to_non_nullable - as int, - )); - } -} - -/// @nodoc - -class _$MyStatsTabStateImpl implements _MyStatsTabState { - const _$MyStatsTabStateImpl({this.selectedTab = 0}); - - @override - @JsonKey() - final int selectedTab; - - @override - String toString() { - return 'MyStatsTabState(selectedTab: $selectedTab)'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$MyStatsTabStateImpl && - (identical(other.selectedTab, selectedTab) || - other.selectedTab == selectedTab)); - } - - @override - int get hashCode => Object.hash(runtimeType, selectedTab); - - /// Create a copy of MyStatsTabState - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - @override - @pragma('vm:prefer-inline') - _$$MyStatsTabStateImplCopyWith<_$MyStatsTabStateImpl> get copyWith => - __$$MyStatsTabStateImplCopyWithImpl<_$MyStatsTabStateImpl>( - this, _$identity); -} - -abstract class _MyStatsTabState implements MyStatsTabState { - const factory _MyStatsTabState({final int selectedTab}) = - _$MyStatsTabStateImpl; - - @override - int get selectedTab; - - /// Create a copy of MyStatsTabState - /// with the given fields replaced by the non-null parameter values. - @override - @JsonKey(includeFromJson: false, includeToJson: false) - _$$MyStatsTabStateImplCopyWith<_$MyStatsTabStateImpl> get copyWith => - throw _privateConstructorUsedError; -} diff --git a/khelo/lib/ui/flow/stats/user_match/user_match_list_screen.dart b/khelo/lib/ui/flow/stats/user_match/user_match_list_screen.dart deleted file mode 100644 index 4644c3f9..00000000 --- a/khelo/lib/ui/flow/stats/user_match/user_match_list_screen.dart +++ /dev/null @@ -1,82 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:khelo/components/empty_screen.dart'; -import 'package:khelo/components/error_screen.dart'; -import 'package:khelo/components/match_detail_cell.dart'; -import 'package:khelo/domain/extensions/context_extensions.dart'; -import 'package:khelo/ui/app_route.dart'; - -import 'package:khelo/ui/flow/stats/user_match/user_match_list_view_model.dart'; -import 'package:style/extensions/context_extensions.dart'; -import 'package:style/indicator/progress_indicator.dart'; - -class UserMatchListScreen extends ConsumerStatefulWidget { - const UserMatchListScreen({super.key}); - - @override - ConsumerState createState() => _UserMatchListScreenState(); -} - -class _UserMatchListScreenState extends ConsumerState - with WidgetsBindingObserver { - late UserMatchListViewNotifier notifier; - - @override - void initState() { - WidgetsBinding.instance.addObserver(this); - notifier = ref.read(userMatchListStateProvider.notifier); - super.initState(); - } - - @override - void didChangeAppLifecycleState(AppLifecycleState state) { - if (state == AppLifecycleState.detached) { - // deallocate resources - notifier.dispose(); - WidgetsBinding.instance.removeObserver(this); - } - } - - @override - Widget build(BuildContext context) { - return Builder(builder: (context) { - return _body(context); - }); - } - - Widget _body(BuildContext context) { - final state = ref.watch(userMatchListStateProvider); - if (state.loading) { - return const Center(child: AppProgressIndicator()); - } - - if (state.error != null) { - return ErrorScreen( - error: state.error, - onRetryTap: notifier.onResume, - ); - } - - return (state.matches.isNotEmpty) - ? ListView.separated( - padding: const EdgeInsets.all(16) + context.mediaQueryPadding, - itemCount: state.matches.length, - itemBuilder: (context, index) => MatchDetailCell( - match: state.matches.elementAt(index), - showStatusTag: false, - onTap: () => AppRoute.matchDetailTab( - matchId: - state.matches.elementAt(index).id) - .push(context), - ), - separatorBuilder: (context, index) => const SizedBox(height: 16), - ) - : EmptyScreen( - title: context.l10n.match_list_no_match_here_title, - description: - context.l10n.team_detail_empty_matches_description_text, - buttonTitle: context.l10n.add_match_screen_title, - onTap: () => AppRoute.addMatch().push(context), - ); - } -} diff --git a/khelo/lib/ui/flow/stats/user_match/user_match_list_view_model.dart b/khelo/lib/ui/flow/stats/user_match/user_match_list_view_model.dart deleted file mode 100644 index 37a43ea6..00000000 --- a/khelo/lib/ui/flow/stats/user_match/user_match_list_view_model.dart +++ /dev/null @@ -1,74 +0,0 @@ -import 'dart:async'; -import 'package:data/api/match/match_model.dart'; -import 'package:data/service/match/match_service.dart'; -import 'package:data/storage/app_preferences.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; - -part 'user_match_list_view_model.freezed.dart'; - -final userMatchListStateProvider = - StateNotifierProvider((ref) { - final notifier = UserMatchListViewNotifier(ref.read(matchServiceProvider)); - ref.listen(hasUserSession, (_, next) => notifier._onUserSessionUpdate(next)); - return notifier; -}); - -class UserMatchListViewNotifier extends StateNotifier { - final MatchService _matchService; - late StreamSubscription _matchesStreamSubscription; - - UserMatchListViewNotifier(this._matchService) - : super(const UserMatchListState()) { - _loadUserMatches(); - } - - void _onUserSessionUpdate(bool hasSession) { - if (!hasSession) { - _cancelStreamSubscription(); - } - } - - Future _loadUserMatches() async { - state = state.copyWith(loading: true); - try { - _matchesStreamSubscription = - _matchService.streamCurrentUserPlayedMatches().listen((matches) { - state = state.copyWith(matches: matches, loading: false, error: null); - }, onError: (e) { - state = state.copyWith(error: e, loading: false); - debugPrint( - "UserMatchListViewNotifier: error while loading user matches -> $e"); - }); - } catch (e) { - state = state.copyWith(error: e, loading: false); - debugPrint( - "UserMatchListViewNotifier: error while loading user matches -> $e"); - } - } - - _cancelStreamSubscription() async { - await _matchesStreamSubscription.cancel(); - } - - onResume() { - _cancelStreamSubscription(); - _loadUserMatches(); - } - - @override - Future dispose() async { - _cancelStreamSubscription(); - super.dispose(); - } -} - -@freezed -class UserMatchListState with _$UserMatchListState { - const factory UserMatchListState({ - Object? error, - @Default(false) bool loading, - @Default([]) List matches, - }) = _UserMatchListState; -} diff --git a/khelo/lib/ui/flow/stats/user_match/user_match_list_view_model.freezed.dart b/khelo/lib/ui/flow/stats/user_match/user_match_list_view_model.freezed.dart deleted file mode 100644 index 02ddf3d6..00000000 --- a/khelo/lib/ui/flow/stats/user_match/user_match_list_view_model.freezed.dart +++ /dev/null @@ -1,188 +0,0 @@ -// coverage:ignore-file -// GENERATED CODE - DO NOT MODIFY BY HAND -// ignore_for_file: type=lint -// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark - -part of 'user_match_list_view_model.dart'; - -// ************************************************************************** -// FreezedGenerator -// ************************************************************************** - -T _$identity(T value) => value; - -final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); - -/// @nodoc -mixin _$UserMatchListState { - Object? get error => throw _privateConstructorUsedError; - bool get loading => throw _privateConstructorUsedError; - List get matches => throw _privateConstructorUsedError; - - /// Create a copy of UserMatchListState - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - $UserMatchListStateCopyWith get copyWith => - throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $UserMatchListStateCopyWith<$Res> { - factory $UserMatchListStateCopyWith( - UserMatchListState value, $Res Function(UserMatchListState) then) = - _$UserMatchListStateCopyWithImpl<$Res, UserMatchListState>; - @useResult - $Res call({Object? error, bool loading, List matches}); -} - -/// @nodoc -class _$UserMatchListStateCopyWithImpl<$Res, $Val extends UserMatchListState> - implements $UserMatchListStateCopyWith<$Res> { - _$UserMatchListStateCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - /// Create a copy of UserMatchListState - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? error = freezed, - Object? loading = null, - Object? matches = null, - }) { - return _then(_value.copyWith( - error: freezed == error ? _value.error : error, - loading: null == loading - ? _value.loading - : loading // ignore: cast_nullable_to_non_nullable - as bool, - matches: null == matches - ? _value.matches - : matches // ignore: cast_nullable_to_non_nullable - as List, - ) as $Val); - } -} - -/// @nodoc -abstract class _$$UserMatchListStateImplCopyWith<$Res> - implements $UserMatchListStateCopyWith<$Res> { - factory _$$UserMatchListStateImplCopyWith(_$UserMatchListStateImpl value, - $Res Function(_$UserMatchListStateImpl) then) = - __$$UserMatchListStateImplCopyWithImpl<$Res>; - @override - @useResult - $Res call({Object? error, bool loading, List matches}); -} - -/// @nodoc -class __$$UserMatchListStateImplCopyWithImpl<$Res> - extends _$UserMatchListStateCopyWithImpl<$Res, _$UserMatchListStateImpl> - implements _$$UserMatchListStateImplCopyWith<$Res> { - __$$UserMatchListStateImplCopyWithImpl(_$UserMatchListStateImpl _value, - $Res Function(_$UserMatchListStateImpl) _then) - : super(_value, _then); - - /// Create a copy of UserMatchListState - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? error = freezed, - Object? loading = null, - Object? matches = null, - }) { - return _then(_$UserMatchListStateImpl( - error: freezed == error ? _value.error : error, - loading: null == loading - ? _value.loading - : loading // ignore: cast_nullable_to_non_nullable - as bool, - matches: null == matches - ? _value._matches - : matches // ignore: cast_nullable_to_non_nullable - as List, - )); - } -} - -/// @nodoc - -class _$UserMatchListStateImpl implements _UserMatchListState { - const _$UserMatchListStateImpl( - {this.error, - this.loading = false, - final List matches = const []}) - : _matches = matches; - - @override - final Object? error; - @override - @JsonKey() - final bool loading; - final List _matches; - @override - @JsonKey() - List get matches { - if (_matches is EqualUnmodifiableListView) return _matches; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_matches); - } - - @override - String toString() { - return 'UserMatchListState(error: $error, loading: $loading, matches: $matches)'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$UserMatchListStateImpl && - const DeepCollectionEquality().equals(other.error, error) && - (identical(other.loading, loading) || other.loading == loading) && - const DeepCollectionEquality().equals(other._matches, _matches)); - } - - @override - int get hashCode => Object.hash( - runtimeType, - const DeepCollectionEquality().hash(error), - loading, - const DeepCollectionEquality().hash(_matches)); - - /// Create a copy of UserMatchListState - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - @override - @pragma('vm:prefer-inline') - _$$UserMatchListStateImplCopyWith<_$UserMatchListStateImpl> get copyWith => - __$$UserMatchListStateImplCopyWithImpl<_$UserMatchListStateImpl>( - this, _$identity); -} - -abstract class _UserMatchListState implements UserMatchListState { - const factory _UserMatchListState( - {final Object? error, - final bool loading, - final List matches}) = _$UserMatchListStateImpl; - - @override - Object? get error; - @override - bool get loading; - @override - List get matches; - - /// Create a copy of UserMatchListState - /// with the given fields replaced by the non-null parameter values. - @override - @JsonKey(includeFromJson: false, includeToJson: false) - _$$UserMatchListStateImplCopyWith<_$UserMatchListStateImpl> get copyWith => - throw _privateConstructorUsedError; -} diff --git a/khelo/lib/ui/flow/stats/user_stat/user_stat_screen.dart b/khelo/lib/ui/flow/stats/user_stat/user_stat_screen.dart index 85fad617..fd240419 100644 --- a/khelo/lib/ui/flow/stats/user_stat/user_stat_screen.dart +++ b/khelo/lib/ui/flow/stats/user_stat/user_stat_screen.dart @@ -1,14 +1,17 @@ -import 'package:data/api/ball_score/ball_score_model.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:khelo/components/app_page.dart'; import 'package:khelo/components/error_screen.dart'; import 'package:khelo/domain/extensions/context_extensions.dart'; import 'package:khelo/ui/flow/stats/user_stat/user_stat_view_model.dart'; +import 'package:style/button/tab_button.dart'; import 'package:style/extensions/context_extensions.dart'; import 'package:style/indicator/progress_indicator.dart'; -import 'package:style/text/app_text_style.dart'; + +import '../../team/user_detail/component/user_detail_batting_content.dart'; +import '../../team/user_detail/component/user_detail_bowling_content.dart'; class UserStatScreen extends ConsumerStatefulWidget { const UserStatScreen({super.key}); @@ -19,12 +22,21 @@ class UserStatScreen extends ConsumerStatefulWidget { class _UserStatScreenState extends ConsumerState with WidgetsBindingObserver { + late PageController _controller; + + int get _selectedTab => _controller.hasClients + ? _controller.page?.round() ?? 0 + : _controller.initialPage; + late UserStatViewNotifier notifier; @override void initState() { WidgetsBinding.instance.addObserver(this); notifier = ref.read(userStatViewStateProvider.notifier); + _controller = PageController( + initialPage: ref.read(userStatViewStateProvider).selectedTab, + ); super.initState(); } @@ -39,215 +51,86 @@ class _UserStatScreenState extends ConsumerState @override Widget build(BuildContext context) { - return Builder(builder: (context) => _body(context)); + return AppPage( + body: Builder(builder: (context) => _body(context)), + ); } Widget _body(BuildContext context) { final state = ref.watch(userStatViewStateProvider); - - if (state.loading) return const Center(child: AppProgressIndicator()); - + if (state.loading) { + return const Center(child: AppProgressIndicator()); + } if (state.error != null) { return ErrorScreen( error: state.error, - onRetryTap: notifier.onResume, + onRetryTap: notifier.loadData, ); } - if (state.userStat == null) return const SizedBox(); - - return ListView( - padding: const EdgeInsets.all(16) + context.mediaQueryPadding, - children: [ - if (state.userStat?.battingStat != null) ...[ - _sectionTitle( - context, context.l10n.my_stat_stats_batting_statics_title), - _battingStats(context, state.userStat?.battingStat), - ], - const SizedBox(height: 40), - if (state.userStat?.bowlingStat != null) ...[ - _sectionTitle( - context, context.l10n.my_stat_stats_bowling_statics_title), - _bowlingStats(context, state.userStat?.bowlingStat), - ], - const SizedBox(height: 40), - if (state.userStat?.fieldingStat != null) ...[ - _sectionTitle( - context, context.l10n.my_stat_stats_fielding_statics_title), - _fieldingStats(context, state.userStat?.fieldingStat), - ], - ], - ); - } - - Widget _battingStats(BuildContext context, BattingStat? battingStat) { - if (battingStat == null) return const SizedBox(); - return Container( - padding: const EdgeInsets.all(16), - margin: const EdgeInsets.only(top: 16), - decoration: BoxDecoration( - border: Border.all(color: context.colorScheme.outline), - borderRadius: BorderRadius.circular(16)), + return Padding( + padding: context.mediaQueryPadding, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - _mainStatisticView( - context, - title: context.l10n.my_stat_stats_run_scored_title, - count: battingStat.runScored.toString(), - ), - const SizedBox(height: 16), - IntrinsicHeight( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - _subStatisticView(context, - title: context.l10n.common_batting_average_title, - count: battingStat.average.toStringAsFixed(2)), - _subStatisticView(context, - title: context.l10n.my_stat_stats_strike_rate_title, - count: '${battingStat.strikeRate.toStringAsFixed(2)}%'), - _subStatisticView(context, - title: context.l10n.my_stat_stats_ball_faced_title, - count: battingStat.ballFaced.toString()), - ], - ), - ), + _tabView(context), + _content(context, state), ], ), ); } - Widget _bowlingStats(BuildContext context, BowlingStat? bowlingStat) { - if (bowlingStat == null) return const SizedBox(); - - return Container( - padding: const EdgeInsets.all(16), - margin: const EdgeInsets.only(top: 16), - decoration: BoxDecoration( - border: Border.all(color: context.colorScheme.outline), - borderRadius: BorderRadius.circular(16)), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _mainStatisticView( - context, - title: context.l10n.common_wicket_taken_title, - count: bowlingStat.wicketTaken.toString(), - ), - const SizedBox(height: 16), - IntrinsicHeight( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - _subStatisticView(context, - title: context.l10n.common_bowling_average_title, - count: bowlingStat.average.toStringAsFixed(2)), - _subStatisticView(context, - title: context.l10n.my_stat_stats_strike_rate_title, - count: '${bowlingStat.strikeRate.toStringAsFixed(2)}%'), - _subStatisticView(context, - title: context.l10n.my_stat_stats_economy_rate_title, - count: bowlingStat.economyRate.toStringAsFixed(2)), - ], + Widget _tabView(BuildContext context) { + final tabs = [ + context.l10n.user_detail_batting_title, + context.l10n.user_detail_bowling_title + ]; + + return SingleChildScrollView( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 16), + scrollDirection: Axis.horizontal, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: List.generate( + tabs.length, + (index) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 4), + child: TabButton( + tabs[index], + onTap: () { + _controller.jumpToPage(index); + }, + selected: index == _selectedTab, ), ), - ], - ), - ); - } - - Widget _fieldingStats(BuildContext context, FieldingStat? fieldingStat) { - if (fieldingStat == null) return const SizedBox(); - - return Container( - padding: const EdgeInsets.all(16), - margin: const EdgeInsets.only(top: 16), - decoration: BoxDecoration( - border: Border.all(color: context.colorScheme.outline), - borderRadius: BorderRadius.circular(16)), - child: IntrinsicHeight( - child: Row( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - _subStatisticView(context, - title: context.l10n.my_stat_stats_catches_title, - count: fieldingStat.catches.toString()), - _subStatisticView(context, - title: context.l10n.common_run_out_title, - count: fieldingStat.runOut.toString()), - _subStatisticView(context, - title: context.l10n.my_stat_stats_stumping_title, - count: fieldingStat.stumping.toString()), - ], ), ), ); } - Widget _sectionTitle(BuildContext context, String title) => Text(title, - style: AppTextStyle.header4 - .copyWith(color: context.colorScheme.textPrimary)); - - Widget _mainStatisticView( - BuildContext context, { - required String title, - required String count, - }) { - return Container( - width: context.mediaQuerySize.width, - padding: const EdgeInsets.symmetric(vertical: 16), - decoration: BoxDecoration( - color: context.colorScheme.containerLow, - borderRadius: BorderRadius.circular(16)), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - count, - style: AppTextStyle.subtitle1 - .copyWith(color: context.colorScheme.textPrimary), - ), - Text( - title, - style: AppTextStyle.body2 - .copyWith(color: context.colorScheme.textSecondary), - ), - ], - )); - } - - Widget _subStatisticView( - BuildContext context, { - required String title, - required String count, - }) { + Widget _content(BuildContext context, UserStatViewState state) { return Expanded( - child: Container( - padding: const EdgeInsets.all(12), - margin: const EdgeInsets.symmetric(horizontal: 8), - decoration: BoxDecoration( - color: context.colorScheme.containerLow, - borderRadius: BorderRadius.circular(16)), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - count, - style: AppTextStyle.subtitle1 - .copyWith(color: context.colorScheme.textPrimary), - ), - const SizedBox(height: 4), - Text( - title, - textAlign: TextAlign.center, - style: AppTextStyle.body2 - .copyWith(color: context.colorScheme.textSecondary), - ), - ], - )), + child: PageView( + controller: _controller, + children: [ + UserDetailBattingContent( + testMatchesCount: state.testMatchesCount, + otherMatchesCount: state.otherMatchesCount, + testStats: state.testStats.battingStat, + otherStats: state.otherStats.battingStat, + ), + UserDetailBowlingContent( + testMatchesCount: state.testMatchesCount, + otherMatchesCount: state.otherMatchesCount, + testStats: state.testStats.bowlingStat, + otherStats: state.otherStats.bowlingStat, + ), + ], + onPageChanged: (index) { + notifier.onTabChange(index); + setState(() {}); + }, + ), ); } } diff --git a/khelo/lib/ui/flow/stats/user_stat/user_stat_view_model.dart b/khelo/lib/ui/flow/stats/user_stat/user_stat_view_model.dart index 1396577a..f4d8323a 100644 --- a/khelo/lib/ui/flow/stats/user_stat/user_stat_view_model.dart +++ b/khelo/lib/ui/flow/stats/user_stat/user_stat_view_model.dart @@ -1,6 +1,8 @@ import 'dart:async'; import 'package:data/api/ball_score/ball_score_model.dart'; +import 'package:data/api/match/match_model.dart'; import 'package:data/service/ball_score/ball_score_service.dart'; +import 'package:data/service/match/match_service.dart'; import 'package:data/storage/app_preferences.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -11,6 +13,7 @@ part 'user_stat_view_model.freezed.dart'; final userStatViewStateProvider = StateNotifierProvider((ref) { final notifier = UserStatViewNotifier( + ref.read(matchServiceProvider), ref.read(ballScoreServiceProvider), ref.read(currentUserPod)?.id, ); @@ -21,180 +24,86 @@ final userStatViewStateProvider = }); class UserStatViewNotifier extends StateNotifier { + final MatchService _matchService; final BallScoreService _ballScoreService; - late StreamSubscription _ballScoreStreamSubscription; + StreamSubscription? _subscription; - UserStatViewNotifier(this._ballScoreService, String? userId) + UserStatViewNotifier( + this._matchService, this._ballScoreService, String? userId) : super(UserStatViewState(currentUserId: userId)) { - _getUserRelatedBalls(); + loadData(); } void setUserId(String? userId) { if (userId == null) { - _cancelStreamSubscription(); + _subscription?.cancel(); } state = state.copyWith(currentUserId: userId); } - Future _getUserRelatedBalls() async { + void loadData() { + if (state.currentUserId == null) { + return; + } + _subscription?.cancel(); state = state.copyWith(loading: true); + + _subscription = _matchService + .streamUserMatches(state.currentUserId!) + .listen((matches) async { + final (testMatchCount, testStat, otherMatchCount, otherStats) = + await loadMatchData(matches); + state = state.copyWith( + testStats: testStat, + otherStats: otherStats, + testMatchesCount: testMatchCount, + otherMatchesCount: otherMatchCount, + loading: false, + ); + }, onError: (e) { + state = state.copyWith(loading: false, error: e); + debugPrint("UserDetailViewNotifier: error while loading data -> $e"); + }); + } + + Future<(int, UserStat, int, UserStat)> loadMatchData( + List matches) async { try { - _ballScoreStreamSubscription = - _ballScoreService.streamCurrentUserRelatedBalls().listen((ballScores) { - final userStat = _calculateUserStats(ballScores); - state = state.copyWith(userStat: userStat, loading: false, error: null); - }, onError: (e) { - state = state.copyWith(error: e, loading: false); - debugPrint( - "UserStatViewNotifier: error while getting user related balls -> $e"); - }); + final testMatches = matches + .where((element) => element.match_type == MatchType.testMatch) + .map((e) => e.id); + final otherMatches = matches + .where((element) => element.match_type != MatchType.testMatch) + .map((e) => e.id); + final ballScore = await _ballScoreService + .getBallScoresByMatchIds(matches.map((e) => e.id).toList()); + + final testStats = ballScore + .where((element) => testMatches.contains(element.match_id)) + .toList() + .calculateUserStats(state.currentUserId ?? ''); + final otherStats = ballScore + .where((element) => otherMatches.contains(element.match_id)) + .toList() + .calculateUserStats(state.currentUserId ?? ''); + + return (testMatches.length, testStats, otherMatches.length, otherStats); } catch (e) { - state = state.copyWith(error: e, loading: false); debugPrint( - "UserStatViewNotifier: error while getting user related balls -> $e"); + "UserDetailViewNotifier: error while loading match data -> $e"); + rethrow; } } - UserStat _calculateUserStats(List ballList) { - final runScored = ballList - .where((element) => element.batsman_id == state.currentUserId) - .fold(0, (sum, element) => sum + element.runs_scored); - final batingStat = - _calculateBatingStats(ballList: ballList, runScored: runScored); - - final bowlingStat = _calculateBowlingStats(ballList); - - final fieldingStat = _calculateFieldingStats(ballList); - - return UserStat( - battingStat: batingStat, - bowlingStat: bowlingStat, - fieldingStat: fieldingStat, - ); - } - - BattingStat _calculateBatingStats({ - required List ballList, - required int runScored, - }) { - final dismissal = ballList - .where((element) => element.player_out_id == state.currentUserId) - .length; - - final ballFaced = ballList - .where((element) => - element.batsman_id == state.currentUserId && - (element.extras_type == null || - element.extras_type == ExtrasType.legBye || - element.extras_type == ExtrasType.bye)) - .length; - - final average = dismissal == 0 ? 0.0 : runScored / dismissal; - - final strikeRate = ballFaced == 0 ? 0.0 : (runScored / ballFaced) * 100.0; - - return BattingStat( - average: average, - strikeRate: strikeRate, - ballFaced: ballFaced, - runScored: runScored, - ); - } - - BowlingStat _calculateBowlingStats(List ballList) { - final deliveries = - ballList.where((element) => element.bowler_id == state.currentUserId); - - final wicketTaken = deliveries - .where((element) => - element.wicket_type != null && - (element.wicket_type == WicketType.retired || - element.wicket_type == WicketType.retiredHurt || - element.wicket_type == WicketType.timedOut)) - .length; - - final bowledBallCount = deliveries - .where((element) => - element.wicket_type != WicketType.retired && - element.wicket_type != WicketType.retiredHurt && - element.wicket_type != WicketType.timedOut && - element.extras_type != ExtrasType.penaltyRun) - .length; - - final bowledBallCountForEconomyRate = deliveries - .where((element) => - (element.extras_type == null || - element.extras_type == ExtrasType.legBye || - element.extras_type == ExtrasType.bye) && - element.wicket_type != WicketType.retired && - element.wicket_type != WicketType.retiredHurt && - element.wicket_type != WicketType.timedOut && - element.extras_type != ExtrasType.penaltyRun) - .length; - - final runsConceded = deliveries - .where((element) => element.extras_type != ExtrasType.penaltyRun) - .fold( - 0, - (sum, element) => - sum + element.runs_scored + (element.extras_awarded ?? 0)); - - final average = wicketTaken == 0 ? 0.0 : runsConceded / wicketTaken; - - final strikeRate = wicketTaken == 0 ? 0.0 : bowledBallCount / wicketTaken; - - final economyRate = bowledBallCountForEconomyRate == 0 - ? 0.0 - : (runsConceded / bowledBallCountForEconomyRate) * 6; - - return BowlingStat( - average: average, - strikeRate: strikeRate, - wicketTaken: wicketTaken, - economyRate: economyRate, - ); - } - - FieldingStat _calculateFieldingStats(List ballList) { - final catches = ballList - .where((element) => - element.wicket_taker_id == state.currentUserId && - (element.wicket_type == WicketType.caught || - element.wicket_type == WicketType.caughtBehind || - element.wicket_type == WicketType.caughtAndBowled)) - .length; - - final runOut = ballList - .where((element) => - element.wicket_taker_id == state.currentUserId && - element.wicket_type == WicketType.runOut) - .length; - - final stumping = ballList - .where((element) => - element.wicket_taker_id == state.currentUserId && - element.wicket_type == WicketType.stumped) - .length; - - return FieldingStat( - catches: catches, - runOut: runOut, - stumping: stumping, - ); - } - - _cancelStreamSubscription() { - _ballScoreStreamSubscription.cancel(); - } - - onResume() { - _cancelStreamSubscription(); - _getUserRelatedBalls(); + void onTabChange(int tab) { + if (state.selectedTab != tab) { + state = state.copyWith(selectedTab: tab); + } } @override void dispose() { - _cancelStreamSubscription(); + _subscription?.cancel(); super.dispose(); } } @@ -204,7 +113,11 @@ class UserStatViewState with _$UserStatViewState { const factory UserStatViewState({ Object? error, String? currentUserId, - UserStat? userStat, + @Default(0) int selectedTab, + @Default(0) int testMatchesCount, + @Default(0) int otherMatchesCount, + @Default(UserStat()) UserStat testStats, + @Default(UserStat()) UserStat otherStats, @Default(false) bool loading, }) = _UserStatViewState; } diff --git a/khelo/lib/ui/flow/stats/user_stat/user_stat_view_model.freezed.dart b/khelo/lib/ui/flow/stats/user_stat/user_stat_view_model.freezed.dart index 4ce77f05..ac0ea110 100644 --- a/khelo/lib/ui/flow/stats/user_stat/user_stat_view_model.freezed.dart +++ b/khelo/lib/ui/flow/stats/user_stat/user_stat_view_model.freezed.dart @@ -18,7 +18,11 @@ final _privateConstructorUsedError = UnsupportedError( mixin _$UserStatViewState { Object? get error => throw _privateConstructorUsedError; String? get currentUserId => throw _privateConstructorUsedError; - UserStat? get userStat => throw _privateConstructorUsedError; + int get selectedTab => throw _privateConstructorUsedError; + int get testMatchesCount => throw _privateConstructorUsedError; + int get otherMatchesCount => throw _privateConstructorUsedError; + UserStat get testStats => throw _privateConstructorUsedError; + UserStat get otherStats => throw _privateConstructorUsedError; bool get loading => throw _privateConstructorUsedError; /// Create a copy of UserStatViewState @@ -35,9 +39,17 @@ abstract class $UserStatViewStateCopyWith<$Res> { _$UserStatViewStateCopyWithImpl<$Res, UserStatViewState>; @useResult $Res call( - {Object? error, String? currentUserId, UserStat? userStat, bool loading}); + {Object? error, + String? currentUserId, + int selectedTab, + int testMatchesCount, + int otherMatchesCount, + UserStat testStats, + UserStat otherStats, + bool loading}); - $UserStatCopyWith<$Res>? get userStat; + $UserStatCopyWith<$Res> get testStats; + $UserStatCopyWith<$Res> get otherStats; } /// @nodoc @@ -57,7 +69,11 @@ class _$UserStatViewStateCopyWithImpl<$Res, $Val extends UserStatViewState> $Res call({ Object? error = freezed, Object? currentUserId = freezed, - Object? userStat = freezed, + Object? selectedTab = null, + Object? testMatchesCount = null, + Object? otherMatchesCount = null, + Object? testStats = null, + Object? otherStats = null, Object? loading = null, }) { return _then(_value.copyWith( @@ -66,10 +82,26 @@ class _$UserStatViewStateCopyWithImpl<$Res, $Val extends UserStatViewState> ? _value.currentUserId : currentUserId // ignore: cast_nullable_to_non_nullable as String?, - userStat: freezed == userStat - ? _value.userStat - : userStat // ignore: cast_nullable_to_non_nullable - as UserStat?, + selectedTab: null == selectedTab + ? _value.selectedTab + : selectedTab // ignore: cast_nullable_to_non_nullable + as int, + testMatchesCount: null == testMatchesCount + ? _value.testMatchesCount + : testMatchesCount // ignore: cast_nullable_to_non_nullable + as int, + otherMatchesCount: null == otherMatchesCount + ? _value.otherMatchesCount + : otherMatchesCount // ignore: cast_nullable_to_non_nullable + as int, + testStats: null == testStats + ? _value.testStats + : testStats // ignore: cast_nullable_to_non_nullable + as UserStat, + otherStats: null == otherStats + ? _value.otherStats + : otherStats // ignore: cast_nullable_to_non_nullable + as UserStat, loading: null == loading ? _value.loading : loading // ignore: cast_nullable_to_non_nullable @@ -81,13 +113,19 @@ class _$UserStatViewStateCopyWithImpl<$Res, $Val extends UserStatViewState> /// with the given fields replaced by the non-null parameter values. @override @pragma('vm:prefer-inline') - $UserStatCopyWith<$Res>? get userStat { - if (_value.userStat == null) { - return null; - } + $UserStatCopyWith<$Res> get testStats { + return $UserStatCopyWith<$Res>(_value.testStats, (value) { + return _then(_value.copyWith(testStats: value) as $Val); + }); + } - return $UserStatCopyWith<$Res>(_value.userStat!, (value) { - return _then(_value.copyWith(userStat: value) as $Val); + /// Create a copy of UserStatViewState + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $UserStatCopyWith<$Res> get otherStats { + return $UserStatCopyWith<$Res>(_value.otherStats, (value) { + return _then(_value.copyWith(otherStats: value) as $Val); }); } } @@ -101,10 +139,19 @@ abstract class _$$UserStatViewStateImplCopyWith<$Res> @override @useResult $Res call( - {Object? error, String? currentUserId, UserStat? userStat, bool loading}); + {Object? error, + String? currentUserId, + int selectedTab, + int testMatchesCount, + int otherMatchesCount, + UserStat testStats, + UserStat otherStats, + bool loading}); @override - $UserStatCopyWith<$Res>? get userStat; + $UserStatCopyWith<$Res> get testStats; + @override + $UserStatCopyWith<$Res> get otherStats; } /// @nodoc @@ -122,7 +169,11 @@ class __$$UserStatViewStateImplCopyWithImpl<$Res> $Res call({ Object? error = freezed, Object? currentUserId = freezed, - Object? userStat = freezed, + Object? selectedTab = null, + Object? testMatchesCount = null, + Object? otherMatchesCount = null, + Object? testStats = null, + Object? otherStats = null, Object? loading = null, }) { return _then(_$UserStatViewStateImpl( @@ -131,10 +182,26 @@ class __$$UserStatViewStateImplCopyWithImpl<$Res> ? _value.currentUserId : currentUserId // ignore: cast_nullable_to_non_nullable as String?, - userStat: freezed == userStat - ? _value.userStat - : userStat // ignore: cast_nullable_to_non_nullable - as UserStat?, + selectedTab: null == selectedTab + ? _value.selectedTab + : selectedTab // ignore: cast_nullable_to_non_nullable + as int, + testMatchesCount: null == testMatchesCount + ? _value.testMatchesCount + : testMatchesCount // ignore: cast_nullable_to_non_nullable + as int, + otherMatchesCount: null == otherMatchesCount + ? _value.otherMatchesCount + : otherMatchesCount // ignore: cast_nullable_to_non_nullable + as int, + testStats: null == testStats + ? _value.testStats + : testStats // ignore: cast_nullable_to_non_nullable + as UserStat, + otherStats: null == otherStats + ? _value.otherStats + : otherStats // ignore: cast_nullable_to_non_nullable + as UserStat, loading: null == loading ? _value.loading : loading // ignore: cast_nullable_to_non_nullable @@ -147,21 +214,41 @@ class __$$UserStatViewStateImplCopyWithImpl<$Res> class _$UserStatViewStateImpl implements _UserStatViewState { const _$UserStatViewStateImpl( - {this.error, this.currentUserId, this.userStat, this.loading = false}); + {this.error, + this.currentUserId, + this.selectedTab = 0, + this.testMatchesCount = 0, + this.otherMatchesCount = 0, + this.testStats = const UserStat(), + this.otherStats = const UserStat(), + this.loading = false}); @override final Object? error; @override final String? currentUserId; @override - final UserStat? userStat; + @JsonKey() + final int selectedTab; + @override + @JsonKey() + final int testMatchesCount; + @override + @JsonKey() + final int otherMatchesCount; + @override + @JsonKey() + final UserStat testStats; + @override + @JsonKey() + final UserStat otherStats; @override @JsonKey() final bool loading; @override String toString() { - return 'UserStatViewState(error: $error, currentUserId: $currentUserId, userStat: $userStat, loading: $loading)'; + return 'UserStatViewState(error: $error, currentUserId: $currentUserId, selectedTab: $selectedTab, testMatchesCount: $testMatchesCount, otherMatchesCount: $otherMatchesCount, testStats: $testStats, otherStats: $otherStats, loading: $loading)'; } @override @@ -172,8 +259,16 @@ class _$UserStatViewStateImpl implements _UserStatViewState { const DeepCollectionEquality().equals(other.error, error) && (identical(other.currentUserId, currentUserId) || other.currentUserId == currentUserId) && - (identical(other.userStat, userStat) || - other.userStat == userStat) && + (identical(other.selectedTab, selectedTab) || + other.selectedTab == selectedTab) && + (identical(other.testMatchesCount, testMatchesCount) || + other.testMatchesCount == testMatchesCount) && + (identical(other.otherMatchesCount, otherMatchesCount) || + other.otherMatchesCount == otherMatchesCount) && + (identical(other.testStats, testStats) || + other.testStats == testStats) && + (identical(other.otherStats, otherStats) || + other.otherStats == otherStats) && (identical(other.loading, loading) || other.loading == loading)); } @@ -182,7 +277,11 @@ class _$UserStatViewStateImpl implements _UserStatViewState { runtimeType, const DeepCollectionEquality().hash(error), currentUserId, - userStat, + selectedTab, + testMatchesCount, + otherMatchesCount, + testStats, + otherStats, loading); /// Create a copy of UserStatViewState @@ -199,7 +298,11 @@ abstract class _UserStatViewState implements UserStatViewState { const factory _UserStatViewState( {final Object? error, final String? currentUserId, - final UserStat? userStat, + final int selectedTab, + final int testMatchesCount, + final int otherMatchesCount, + final UserStat testStats, + final UserStat otherStats, final bool loading}) = _$UserStatViewStateImpl; @override @@ -207,7 +310,15 @@ abstract class _UserStatViewState implements UserStatViewState { @override String? get currentUserId; @override - UserStat? get userStat; + int get selectedTab; + @override + int get testMatchesCount; + @override + int get otherMatchesCount; + @override + UserStat get testStats; + @override + UserStat get otherStats; @override bool get loading; diff --git a/khelo/lib/ui/flow/team/add_team/add_team_screen.dart b/khelo/lib/ui/flow/team/add_team/add_team_screen.dart index b95eab92..5134d898 100644 --- a/khelo/lib/ui/flow/team/add_team/add_team_screen.dart +++ b/khelo/lib/ui/flow/team/add_team/add_team_screen.dart @@ -95,7 +95,7 @@ class _AddTeamScreenState extends ConsumerState { return Stack( children: [ ListView( - padding: const EdgeInsets.all(16) + BottomStickyOverlay.padding, + padding: context.mediaQueryPadding + const EdgeInsets.all(16) + BottomStickyOverlay.padding, children: [ ProfileImageAvatar( size: profileViewHeight, diff --git a/khelo/lib/ui/flow/team/detail/make_admin/make_team_admin_screen.dart b/khelo/lib/ui/flow/team/detail/make_admin/make_team_admin_screen.dart index 05e6e50a..7a2871c6 100644 --- a/khelo/lib/ui/flow/team/detail/make_admin/make_team_admin_screen.dart +++ b/khelo/lib/ui/flow/team/detail/make_admin/make_team_admin_screen.dart @@ -42,6 +42,7 @@ class _MakeAdminScreenState extends ConsumerState { @override Widget build(BuildContext context) { _observePop(); + _observeShowSelectionError(); _observeActionError(); final state = ref.watch(makeTeamAdminStateProvider); return AppPage( @@ -75,6 +76,17 @@ class _MakeAdminScreenState extends ConsumerState { ); } + void _observeShowSelectionError() { + ref.listen( + makeTeamAdminStateProvider.select((value) => value.showSelectionError), + (previous, next) { + if (next) { + showErrorSnackBar( + context: context, error: context.l10n.make_admin_selection_error); + } + }); + } + void _observeActionError() { ref.listen(makeTeamAdminStateProvider.select((value) => value.actionError), (previous, next) { diff --git a/khelo/lib/ui/flow/team/detail/make_admin/make_team_admin_view_model.dart b/khelo/lib/ui/flow/team/detail/make_admin/make_team_admin_view_model.dart index 8f2c1a6d..6d6fe3d2 100644 --- a/khelo/lib/ui/flow/team/detail/make_admin/make_team_admin_view_model.dart +++ b/khelo/lib/ui/flow/team/detail/make_admin/make_team_admin_view_model.dart @@ -27,7 +27,15 @@ class MakeTeamAdminViewNotifier extends StateNotifier { } void selectAdmin(TeamPlayer player) { + state = state.copyWith(showSelectionError: false); final admins = state.selectedPlayers.toList(); + + // Do not allow to select Deactivated user but allow to deselect them. + if (!player.user.isActive && !admins.contains(player)) { + state = state.copyWith(showSelectionError: true); + return; + } + (admins.contains(player)) ? admins.remove(player) : admins.add(player); state = state.copyWith(selectedPlayers: admins, isButtonEnabled: true); } @@ -57,6 +65,7 @@ class MakeTeamAdminState with _$MakeTeamAdminState { Object? actionError, @Default(false) bool pop, @Default(false) bool isButtonEnabled, + @Default(false) bool showSelectionError, @Default([]) List selectedPlayers, }) = _MakeTeamAdminState; } diff --git a/khelo/lib/ui/flow/team/detail/make_admin/make_team_admin_view_model.freezed.dart b/khelo/lib/ui/flow/team/detail/make_admin/make_team_admin_view_model.freezed.dart index c32ea1a8..d47a1a9d 100644 --- a/khelo/lib/ui/flow/team/detail/make_admin/make_team_admin_view_model.freezed.dart +++ b/khelo/lib/ui/flow/team/detail/make_admin/make_team_admin_view_model.freezed.dart @@ -19,6 +19,7 @@ mixin _$MakeTeamAdminState { Object? get actionError => throw _privateConstructorUsedError; bool get pop => throw _privateConstructorUsedError; bool get isButtonEnabled => throw _privateConstructorUsedError; + bool get showSelectionError => throw _privateConstructorUsedError; List get selectedPlayers => throw _privateConstructorUsedError; /// Create a copy of MakeTeamAdminState @@ -38,6 +39,7 @@ abstract class $MakeTeamAdminStateCopyWith<$Res> { {Object? actionError, bool pop, bool isButtonEnabled, + bool showSelectionError, List selectedPlayers}); } @@ -59,6 +61,7 @@ class _$MakeTeamAdminStateCopyWithImpl<$Res, $Val extends MakeTeamAdminState> Object? actionError = freezed, Object? pop = null, Object? isButtonEnabled = null, + Object? showSelectionError = null, Object? selectedPlayers = null, }) { return _then(_value.copyWith( @@ -71,6 +74,10 @@ class _$MakeTeamAdminStateCopyWithImpl<$Res, $Val extends MakeTeamAdminState> ? _value.isButtonEnabled : isButtonEnabled // ignore: cast_nullable_to_non_nullable as bool, + showSelectionError: null == showSelectionError + ? _value.showSelectionError + : showSelectionError // ignore: cast_nullable_to_non_nullable + as bool, selectedPlayers: null == selectedPlayers ? _value.selectedPlayers : selectedPlayers // ignore: cast_nullable_to_non_nullable @@ -91,6 +98,7 @@ abstract class _$$MakeTeamAdminStateImplCopyWith<$Res> {Object? actionError, bool pop, bool isButtonEnabled, + bool showSelectionError, List selectedPlayers}); } @@ -110,6 +118,7 @@ class __$$MakeTeamAdminStateImplCopyWithImpl<$Res> Object? actionError = freezed, Object? pop = null, Object? isButtonEnabled = null, + Object? showSelectionError = null, Object? selectedPlayers = null, }) { return _then(_$MakeTeamAdminStateImpl( @@ -122,6 +131,10 @@ class __$$MakeTeamAdminStateImplCopyWithImpl<$Res> ? _value.isButtonEnabled : isButtonEnabled // ignore: cast_nullable_to_non_nullable as bool, + showSelectionError: null == showSelectionError + ? _value.showSelectionError + : showSelectionError // ignore: cast_nullable_to_non_nullable + as bool, selectedPlayers: null == selectedPlayers ? _value._selectedPlayers : selectedPlayers // ignore: cast_nullable_to_non_nullable @@ -137,6 +150,7 @@ class _$MakeTeamAdminStateImpl implements _MakeTeamAdminState { {this.actionError, this.pop = false, this.isButtonEnabled = false, + this.showSelectionError = false, final List selectedPlayers = const []}) : _selectedPlayers = selectedPlayers; @@ -148,6 +162,9 @@ class _$MakeTeamAdminStateImpl implements _MakeTeamAdminState { @override @JsonKey() final bool isButtonEnabled; + @override + @JsonKey() + final bool showSelectionError; final List _selectedPlayers; @override @JsonKey() @@ -159,7 +176,7 @@ class _$MakeTeamAdminStateImpl implements _MakeTeamAdminState { @override String toString() { - return 'MakeTeamAdminState(actionError: $actionError, pop: $pop, isButtonEnabled: $isButtonEnabled, selectedPlayers: $selectedPlayers)'; + return 'MakeTeamAdminState(actionError: $actionError, pop: $pop, isButtonEnabled: $isButtonEnabled, showSelectionError: $showSelectionError, selectedPlayers: $selectedPlayers)'; } @override @@ -172,6 +189,8 @@ class _$MakeTeamAdminStateImpl implements _MakeTeamAdminState { (identical(other.pop, pop) || other.pop == pop) && (identical(other.isButtonEnabled, isButtonEnabled) || other.isButtonEnabled == isButtonEnabled) && + (identical(other.showSelectionError, showSelectionError) || + other.showSelectionError == showSelectionError) && const DeepCollectionEquality() .equals(other._selectedPlayers, _selectedPlayers)); } @@ -182,6 +201,7 @@ class _$MakeTeamAdminStateImpl implements _MakeTeamAdminState { const DeepCollectionEquality().hash(actionError), pop, isButtonEnabled, + showSelectionError, const DeepCollectionEquality().hash(_selectedPlayers)); /// Create a copy of MakeTeamAdminState @@ -199,6 +219,7 @@ abstract class _MakeTeamAdminState implements MakeTeamAdminState { {final Object? actionError, final bool pop, final bool isButtonEnabled, + final bool showSelectionError, final List selectedPlayers}) = _$MakeTeamAdminStateImpl; @override @@ -208,6 +229,8 @@ abstract class _MakeTeamAdminState implements MakeTeamAdminState { @override bool get isButtonEnabled; @override + bool get showSelectionError; + @override List get selectedPlayers; /// Create a copy of MakeTeamAdminState diff --git a/khelo/lib/ui/flow/team/user_detail/component/user_detail_batting_content.dart b/khelo/lib/ui/flow/team/user_detail/component/user_detail_batting_content.dart index 8642abdd..9d30e8fc 100644 --- a/khelo/lib/ui/flow/team/user_detail/component/user_detail_batting_content.dart +++ b/khelo/lib/ui/flow/team/user_detail/component/user_detail_batting_content.dart @@ -1,18 +1,27 @@ +import 'package:data/api/ball_score/ball_score_model.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:khelo/domain/extensions/context_extensions.dart'; import 'package:khelo/ui/flow/team/user_detail/component/user_detail_bowling_content.dart'; -import 'package:khelo/ui/flow/team/user_detail/user_detail_view_model.dart'; class UserDetailBattingContent extends ConsumerWidget { - const UserDetailBattingContent({super.key}); + final int testMatchesCount; + final int otherMatchesCount; + final BattingStat? testStats; + final BattingStat? otherStats; + + const UserDetailBattingContent({ + super.key, + this.testMatchesCount = 0, + this.otherMatchesCount = 0, + this.testStats, + this.otherStats, + }); @override Widget build(BuildContext context, WidgetRef ref) { - final state = ref.read(userDetailStateProvider); - final testStats = state.testStats.battingStat; - final otherStats = state.otherStats.battingStat; return ListView( + padding: const EdgeInsets.only(bottom: 40), children: [ statsDataRow( context, @@ -27,8 +36,8 @@ class UserDetailBattingContent extends ConsumerWidget { context, showDivider: false, title: context.l10n.user_detail_matches_title, - subtitle1: state.testMatchesCount.toString(), - subtitle2: state.otherMatchesCount.toString(), + subtitle1: testMatchesCount.toString(), + subtitle2: otherMatchesCount.toString(), ), statsDataRow( context, diff --git a/khelo/lib/ui/flow/team/user_detail/component/user_detail_bowling_content.dart b/khelo/lib/ui/flow/team/user_detail/component/user_detail_bowling_content.dart index 30d057f8..598e7178 100644 --- a/khelo/lib/ui/flow/team/user_detail/component/user_detail_bowling_content.dart +++ b/khelo/lib/ui/flow/team/user_detail/component/user_detail_bowling_content.dart @@ -1,20 +1,28 @@ +import 'package:data/api/ball_score/ball_score_model.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:khelo/domain/extensions/context_extensions.dart'; -import 'package:khelo/ui/flow/team/user_detail/user_detail_view_model.dart'; import 'package:style/extensions/context_extensions.dart'; import 'package:style/text/app_text_style.dart'; class UserDetailBowlingContent extends ConsumerWidget { - const UserDetailBowlingContent({super.key}); + final int testMatchesCount; + final int otherMatchesCount; + final BowlingStat? testStats; + final BowlingStat? otherStats; + + const UserDetailBowlingContent({ + super.key, + this.testMatchesCount = 0, + this.otherMatchesCount = 0, + this.testStats, + this.otherStats, + }); @override Widget build(BuildContext context, WidgetRef ref) { - final state = ref.read(userDetailStateProvider); - final testStats = state.testStats.bowlingStat; - final otherStats = state.otherStats.bowlingStat; - return ListView( + padding: const EdgeInsets.only(bottom: 40), children: [ statsDataRow( context, @@ -29,8 +37,8 @@ class UserDetailBowlingContent extends ConsumerWidget { context, showDivider: false, title: context.l10n.user_detail_matches_title, - subtitle1: state.testMatchesCount.toString(), - subtitle2: state.otherMatchesCount.toString(), + subtitle1: testMatchesCount.toString(), + subtitle2: otherMatchesCount.toString(), ), statsDataRow( context, diff --git a/khelo/lib/ui/flow/team/user_detail/component/user_detail_info_content.dart b/khelo/lib/ui/flow/team/user_detail/component/user_detail_info_content.dart index 994d4b36..17cf6544 100644 --- a/khelo/lib/ui/flow/team/user_detail/component/user_detail_info_content.dart +++ b/khelo/lib/ui/flow/team/user_detail/component/user_detail_info_content.dart @@ -1,39 +1,38 @@ +import 'package:data/api/user/user_models.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:khelo/domain/extensions/context_extensions.dart'; import 'package:khelo/domain/extensions/enum_extensions.dart'; import 'package:khelo/domain/formatter/date_formatter.dart'; -import 'package:khelo/ui/flow/team/user_detail/user_detail_view_model.dart'; import 'package:style/extensions/context_extensions.dart'; import 'package:style/text/app_text_style.dart'; class UserDetailInfoContent extends ConsumerWidget { - const UserDetailInfoContent({super.key}); + final UserModel? user; + final String teams; + + const UserDetailInfoContent({super.key, this.user, this.teams = ''}); @override Widget build(BuildContext context, WidgetRef ref) { - final state = ref.read(userDetailStateProvider); - return ListView( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), children: [ _title(context, context.l10n.user_detail_personal_information_title), const SizedBox(height: 8), - _infoRowView( - context, - context.l10n.user_detail_joining_date_title, - state.user?.created_at?.format(context, DateFormatType.shortDate)), + _infoRowView(context, context.l10n.user_detail_joining_date_title, + user?.created_at?.format(context, DateFormatType.shortDate)), _infoRowView(context, context.l10n.common_gender_title, - state.user?.gender?.getString(context)), + user?.gender?.getString(context)), _infoRowView( - context, context.l10n.common_location_title, state.user?.location), + context, context.l10n.common_location_title, user?.location), _infoRowView(context, context.l10n.user_detail_role_title, - state.user?.player_role?.getString(context)), + user?.player_role?.getString(context)), _infoRowView(context, context.l10n.common_batting_style_title, - state.user?.batting_style?.getString(context)), + user?.batting_style?.getString(context)), _infoRowView(context, context.l10n.common_bowling_style_title, - state.user?.bowling_style?.getString(context)), - _teamParticipationView(context, state), + user?.bowling_style?.getString(context)), + _teamParticipationView(context), ], ); } @@ -74,11 +73,7 @@ class UserDetailInfoContent extends ConsumerWidget { ); } - Widget _teamParticipationView( - BuildContext context, - UserDetailViewState state, - ) { - final teams = state.teams.map((e) => e.name).join(", "); + Widget _teamParticipationView(BuildContext context) { if (teams.isEmpty) { return const SizedBox(); } diff --git a/khelo/lib/ui/flow/team/user_detail/user_detail_screen.dart b/khelo/lib/ui/flow/team/user_detail/user_detail_screen.dart index 5b4a98b1..e4de2582 100644 --- a/khelo/lib/ui/flow/team/user_detail/user_detail_screen.dart +++ b/khelo/lib/ui/flow/team/user_detail/user_detail_screen.dart @@ -35,12 +35,6 @@ class UserDetailScreen extends ConsumerStatefulWidget { } class _UserDetailScreenState extends ConsumerState { - final List _tabs = [ - const UserDetailInfoContent(), - const UserDetailBattingContent(), - const UserDetailBowlingContent(), - ]; - late PageController _controller; int get _selectedTab => _controller.hasClients @@ -134,46 +128,66 @@ class _UserDetailScreenState extends ConsumerState { padding: context.mediaQueryPadding, child: Column( mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, children: [ _tabView(context), - _content(context), + _content(context, state), ], ), ); } Widget _tabView(BuildContext context) { - return Padding( - padding: const EdgeInsets.all(16), + final tabs = [ + context.l10n.user_detail_info_title, + context.l10n.user_detail_batting_title, + context.l10n.user_detail_bowling_title + ]; + + return SingleChildScrollView( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 16), + scrollDirection: Axis.horizontal, child: Row( - children: [ - TabButton( - context.l10n.user_detail_info_title, - selected: _selectedTab == 0, - onTap: () => _controller.jumpToPage(0), + mainAxisAlignment: MainAxisAlignment.start, + children: List.generate( + tabs.length, + (index) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 4), + child: TabButton( + tabs[index], + onTap: () { + _controller.jumpToPage(index); + }, + selected: index == _selectedTab, + ), ), - const SizedBox(width: 8), - TabButton( - context.l10n.user_detail_batting_title, - selected: _selectedTab == 1, - onTap: () => _controller.jumpToPage(1), - ), - const SizedBox(width: 8), - TabButton( - context.l10n.user_detail_bowling_title, - selected: _selectedTab == 2, - onTap: () => _controller.jumpToPage(2), - ), - ], + ), ), ); } - Widget _content(BuildContext context) { + Widget _content(BuildContext context, UserDetailViewState state) { return Expanded( child: PageView( controller: _controller, - children: _tabs, + children: [ + UserDetailInfoContent( + teams: state.teams.map((e) => e.name).join(", "), + user: state.user, + ), + UserDetailBattingContent( + testMatchesCount: state.testMatchesCount, + otherMatchesCount: state.otherMatchesCount, + testStats: state.testStats.battingStat, + otherStats: state.otherStats.battingStat, + ), + UserDetailBowlingContent( + testMatchesCount: state.testMatchesCount, + otherMatchesCount: state.otherMatchesCount, + testStats: state.testStats.bowlingStat, + otherStats: state.otherStats.bowlingStat, + ), + ], onPageChanged: (index) { notifier.onTabChange(index); setState(() {});