diff --git a/data/lib/api/tournament/tournament_model.dart b/data/lib/api/tournament/tournament_model.dart index 3a3e2282..5c2c0878 100644 --- a/data/lib/api/tournament/tournament_model.dart +++ b/data/lib/api/tournament/tournament_model.dart @@ -34,15 +34,6 @@ class TournamentModel with _$TournamentModel { @JsonKey(includeFromJson: false, includeToJson: false) @Default([]) List teams, - @JsonKey(includeFromJson: false, includeToJson: false) - @Default([]) - List matches, - @JsonKey(includeFromJson: false, includeToJson: false) - @Default([]) - List keyStats, - @JsonKey(includeFromJson: false, includeToJson: false) - @Default([]) - List teamStats, }) = _TournamentModel; factory TournamentModel.fromJson(Map json) => @@ -55,6 +46,41 @@ class TournamentModel with _$TournamentModel { TournamentModel.fromJson(snapshot.data()!); } +extension TournamentModelExtensions on TournamentModel { + TournamentStatus getTournamentStatus(List matches) { + if (matches.isEmpty) return TournamentStatus.upcoming; + + final bool isUpcoming = (start_date.isAfter(DateTime.now()) && + end_date.isAfter(DateTime.now())) && + matches.every((match) => match.match_status == MatchStatus.yetToStart); + + final bool isRunning = start_date.isBefore(DateTime.now()) && + end_date.isAfter(DateTime.now()) && + matches.any((match) => match.match_status == MatchStatus.running); + + final bool isFinished = end_date.isBefore(DateTime.now()) && + matches.every( + (match) => + match.match_status == MatchStatus.finish || + match.match_status == MatchStatus.abandoned, + ); + + final bool isInProgress = start_date.isBefore(DateTime.now()) && + end_date.isAfter(DateTime.now()) && + matches.any( + (match) => + match.match_status == MatchStatus.running || + match.match_status == MatchStatus.yetToStart, + ); + + if (isUpcoming) return TournamentStatus.upcoming; + if (isRunning || isInProgress) return TournamentStatus.running; + if (isFinished) return TournamentStatus.finish; + + return TournamentStatus.finish; + } +} + @freezed class TournamentMember with _$TournamentMember { @JsonSerializable(explicitToJson: true) diff --git a/data/lib/api/tournament/tournament_model.freezed.dart b/data/lib/api/tournament/tournament_model.freezed.dart index e3806d79..6026be69 100644 --- a/data/lib/api/tournament/tournament_model.freezed.dart +++ b/data/lib/api/tournament/tournament_model.freezed.dart @@ -39,12 +39,6 @@ mixin _$TournamentModel { List get match_ids => throw _privateConstructorUsedError; @JsonKey(includeFromJson: false, includeToJson: false) List get teams => throw _privateConstructorUsedError; - @JsonKey(includeFromJson: false, includeToJson: false) - List get matches => throw _privateConstructorUsedError; - @JsonKey(includeFromJson: false, includeToJson: false) - List get keyStats => throw _privateConstructorUsedError; - @JsonKey(includeFromJson: false, includeToJson: false) - List get teamStats => throw _privateConstructorUsedError; /// Serializes this TournamentModel to a JSON map. Map toJson() => throw _privateConstructorUsedError; @@ -78,13 +72,7 @@ abstract class $TournamentModelCopyWith<$Res> { List team_ids, List match_ids, @JsonKey(includeFromJson: false, includeToJson: false) - List teams, - @JsonKey(includeFromJson: false, includeToJson: false) - List matches, - @JsonKey(includeFromJson: false, includeToJson: false) - List keyStats, - @JsonKey(includeFromJson: false, includeToJson: false) - List teamStats}); + List teams}); } /// @nodoc @@ -116,9 +104,6 @@ class _$TournamentModelCopyWithImpl<$Res, $Val extends TournamentModel> Object? team_ids = null, Object? match_ids = null, Object? teams = null, - Object? matches = null, - Object? keyStats = null, - Object? teamStats = null, }) { return _then(_value.copyWith( id: null == id @@ -177,18 +162,6 @@ class _$TournamentModelCopyWithImpl<$Res, $Val extends TournamentModel> ? _value.teams : teams // ignore: cast_nullable_to_non_nullable as List, - matches: null == matches - ? _value.matches - : matches // ignore: cast_nullable_to_non_nullable - as List, - keyStats: null == keyStats - ? _value.keyStats - : keyStats // ignore: cast_nullable_to_non_nullable - as List, - teamStats: null == teamStats - ? _value.teamStats - : teamStats // ignore: cast_nullable_to_non_nullable - as List, ) as $Val); } } @@ -217,13 +190,7 @@ abstract class _$$TournamentModelImplCopyWith<$Res> List team_ids, List match_ids, @JsonKey(includeFromJson: false, includeToJson: false) - List teams, - @JsonKey(includeFromJson: false, includeToJson: false) - List matches, - @JsonKey(includeFromJson: false, includeToJson: false) - List keyStats, - @JsonKey(includeFromJson: false, includeToJson: false) - List teamStats}); + List teams}); } /// @nodoc @@ -253,9 +220,6 @@ class __$$TournamentModelImplCopyWithImpl<$Res> Object? team_ids = null, Object? match_ids = null, Object? teams = null, - Object? matches = null, - Object? keyStats = null, - Object? teamStats = null, }) { return _then(_$TournamentModelImpl( id: null == id @@ -314,18 +278,6 @@ class __$$TournamentModelImplCopyWithImpl<$Res> ? _value._teams : teams // ignore: cast_nullable_to_non_nullable as List, - matches: null == matches - ? _value._matches - : matches // ignore: cast_nullable_to_non_nullable - as List, - keyStats: null == keyStats - ? _value._keyStats - : keyStats // ignore: cast_nullable_to_non_nullable - as List, - teamStats: null == teamStats - ? _value._teamStats - : teamStats // ignore: cast_nullable_to_non_nullable - as List, )); } } @@ -350,20 +302,11 @@ class _$TournamentModelImpl implements _TournamentModel { final List team_ids = const [], final List match_ids = const [], @JsonKey(includeFromJson: false, includeToJson: false) - final List teams = const [], - @JsonKey(includeFromJson: false, includeToJson: false) - final List matches = const [], - @JsonKey(includeFromJson: false, includeToJson: false) - final List keyStats = const [], - @JsonKey(includeFromJson: false, includeToJson: false) - final List teamStats = const []}) + final List teams = const []}) : _members = members, _team_ids = team_ids, _match_ids = match_ids, - _teams = teams, - _matches = matches, - _keyStats = keyStats, - _teamStats = teamStats; + _teams = teams; factory _$TournamentModelImpl.fromJson(Map json) => _$$TournamentModelImplFromJson(json); @@ -428,36 +371,9 @@ class _$TournamentModelImpl implements _TournamentModel { return EqualUnmodifiableListView(_teams); } - final List _matches; - @override - @JsonKey(includeFromJson: false, includeToJson: false) - List get matches { - if (_matches is EqualUnmodifiableListView) return _matches; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_matches); - } - - final List _keyStats; - @override - @JsonKey(includeFromJson: false, includeToJson: false) - List get keyStats { - if (_keyStats is EqualUnmodifiableListView) return _keyStats; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_keyStats); - } - - final List _teamStats; - @override - @JsonKey(includeFromJson: false, includeToJson: false) - List get teamStats { - if (_teamStats is EqualUnmodifiableListView) return _teamStats; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_teamStats); - } - @override String toString() { - return 'TournamentModel(id: $id, name: $name, profile_img_url: $profile_img_url, banner_img_url: $banner_img_url, type: $type, status: $status, members: $members, created_by: $created_by, created_at: $created_at, start_date: $start_date, end_date: $end_date, team_ids: $team_ids, match_ids: $match_ids, teams: $teams, matches: $matches, keyStats: $keyStats, teamStats: $teamStats)'; + return 'TournamentModel(id: $id, name: $name, profile_img_url: $profile_img_url, banner_img_url: $banner_img_url, type: $type, status: $status, members: $members, created_by: $created_by, created_at: $created_at, start_date: $start_date, end_date: $end_date, team_ids: $team_ids, match_ids: $match_ids, teams: $teams)'; } @override @@ -485,11 +401,7 @@ class _$TournamentModelImpl implements _TournamentModel { const DeepCollectionEquality().equals(other._team_ids, _team_ids) && const DeepCollectionEquality() .equals(other._match_ids, _match_ids) && - const DeepCollectionEquality().equals(other._teams, _teams) && - const DeepCollectionEquality().equals(other._matches, _matches) && - const DeepCollectionEquality().equals(other._keyStats, _keyStats) && - const DeepCollectionEquality() - .equals(other._teamStats, _teamStats)); + const DeepCollectionEquality().equals(other._teams, _teams)); } @JsonKey(includeFromJson: false, includeToJson: false) @@ -509,10 +421,7 @@ class _$TournamentModelImpl implements _TournamentModel { end_date, const DeepCollectionEquality().hash(_team_ids), const DeepCollectionEquality().hash(_match_ids), - const DeepCollectionEquality().hash(_teams), - const DeepCollectionEquality().hash(_matches), - const DeepCollectionEquality().hash(_keyStats), - const DeepCollectionEquality().hash(_teamStats)); + const DeepCollectionEquality().hash(_teams)); /// Create a copy of TournamentModel /// with the given fields replaced by the non-null parameter values. @@ -548,13 +457,7 @@ abstract class _TournamentModel implements TournamentModel { final List team_ids, final List match_ids, @JsonKey(includeFromJson: false, includeToJson: false) - final List teams, - @JsonKey(includeFromJson: false, includeToJson: false) - final List matches, - @JsonKey(includeFromJson: false, includeToJson: false) - final List keyStats, - @JsonKey(includeFromJson: false, includeToJson: false) - final List teamStats}) = _$TournamentModelImpl; + final List teams}) = _$TournamentModelImpl; factory _TournamentModel.fromJson(Map json) = _$TournamentModelImpl.fromJson; @@ -592,15 +495,6 @@ abstract class _TournamentModel implements TournamentModel { @override @JsonKey(includeFromJson: false, includeToJson: false) List get teams; - @override - @JsonKey(includeFromJson: false, includeToJson: false) - List get matches; - @override - @JsonKey(includeFromJson: false, includeToJson: false) - List get keyStats; - @override - @JsonKey(includeFromJson: false, includeToJson: false) - List get teamStats; /// Create a copy of TournamentModel /// with the given fields replaced by the non-null parameter values. diff --git a/data/lib/service/match/match_service.dart b/data/lib/service/match/match_service.dart index 97e1e80e..86c7e650 100644 --- a/data/lib/service/match/match_service.dart +++ b/data/lib/service/match/match_service.dart @@ -625,4 +625,24 @@ class MatchService { throw AppError.fromError(error, stack); } } + + Stream> streamMatchesByIds(List matchIds) { + try { + if (matchIds.isEmpty) return Stream.empty(); + return _matchCollection + .where(FieldPath.documentId, whereIn: matchIds) + .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(), + ); + }); + } catch (error, stack) { + throw AppError.fromError(error, stack); + } + } } diff --git a/data/lib/service/tournament/tournament_service.dart b/data/lib/service/tournament/tournament_service.dart index 39b2a78d..ed2083c7 100644 --- a/data/lib/service/tournament/tournament_service.dart +++ b/data/lib/service/tournament/tournament_service.dart @@ -1,5 +1,4 @@ import 'package:cloud_firestore/cloud_firestore.dart'; -import 'package:collection/collection.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../api/ball_score/ball_score_model.dart'; @@ -122,31 +121,28 @@ class TournamentService { final matchIds = tournament.match_ids; if (matchIds.isNotEmpty) { final matches = await _matchService.getMatchesByIds(matchIds); - tournament = tournament.copyWith(matches: matches); + final status = tournament.getTournamentStatus(matches); + tournament = tournament.copyWith(status: status); } - final status = getTournamentStatus(tournament); - return tournament.copyWith(status: status); + + return tournament; }, ), ); } - Future> getTeamsStats( - String tournamentId, - List teams, - ) async { - try { - final snapshot = await _tournamentTeamStatCollection(tournamentId).get(); - return snapshot.docs - .map( - (e) => e.data().copyWith( - team: teams.firstWhere((element) => element.id == e.id), - ), - ) - .toList(); - } catch (error, stack) { - throw AppError.fromError(error, stack); - } + Stream> streamTeamStats(String tournamentId) { + return _tournamentTeamStatCollection(tournamentId) + .snapshots() + .asyncMap((snapshot) async { + return await Future.wait( + snapshot.docs.map((doc) async { + final data = doc.data(); + final team = await _teamService.getTeamById(data.team_id); + return data.copyWith(team: team); + }), + ); + }).handleError((error, stack) => throw AppError.fromError(error, stack)); } Future getTeamStatByTeamId( @@ -163,26 +159,18 @@ class TournamentService { } } - Future> getPlayerKeyStats( - String tournamentId, - List players, - ) async { - try { - final snapshot = - await _tournamentPlayerKeyStatCollection(tournamentId).get(); - - if (snapshot.docs.isEmpty && players.isEmpty) return []; - - return snapshot.docs.map((doc) { - final data = doc.data(); - - final player = players.firstWhereOrNull((p) => p.id == data.player_id); - - return player == null ? data : data.copyWith(player: player); - }).toList(); - } catch (error, stack) { - throw AppError.fromError(error, stack); - } + Stream> streamPlayerKeyStats(String tournamentId) { + return _tournamentPlayerKeyStatCollection(tournamentId) + .snapshots() + .asyncMap((snapshot) async { + return await Future.wait( + snapshot.docs.map((doc) async { + final data = doc.data(); + final player = await _userService.getUser(data.player_id); + return player == null ? data : data.copyWith(player: player); + }), + ); + }).handleError((error, stack) => throw AppError.fromError(error, stack)); } Future getPlayerKeyStatByPlayerId( @@ -233,10 +221,11 @@ class TournamentService { final matchIds = tournament.match_ids; if (matchIds.isNotEmpty) { final matches = await _matchService.getMatchesByIds(matchIds); - tournament = tournament.copyWith(matches: matches); + final status = tournament.getTournamentStatus(matches); + tournament = tournament.copyWith(status: status); } - final status = getTournamentStatus(tournament); - return tournament.copyWith(status: status); + + return tournament; }, ), ); @@ -280,30 +269,10 @@ class TournamentService { } else { var tournament = snapshot.data()!; final teamIds = tournament.team_ids; - final matchIds = tournament.match_ids; if (teamIds.isNotEmpty) { final teams = await _teamService.getTeamsByIds(teamIds); - final teamStats = await getTeamsStats(tournamentId, teams); - tournament = tournament.copyWith(teams: teams, teamStats: teamStats); - } - - if (matchIds.isNotEmpty) { - final matches = await _matchService.getMatchesByIds(matchIds); - - final players = matches - .expand((match) => match.teams) - .expand((team) => team.squad) - .map((member) => member.player) - .where((player) => player.isActive) - .toSet() - .toList(); - - final keyStats = - (await getPlayerKeyStats(tournamentId, players)).getTopKeyStats(); - - tournament = - tournament.copyWith(matches: matches, keyStats: keyStats); + tournament = tournament.copyWith(teams: teams); } if (tournament.members.isNotEmpty) { @@ -317,9 +286,8 @@ class TournamentService { tournament = tournament.copyWith(members: members); } - final status = getTournamentStatus(tournament); - return tournament.copyWith(status: status); + return tournament; } }).handleError((error, stack) => throw AppError.fromError(error, stack)); } @@ -493,40 +461,4 @@ class TournamentService { throw AppError.fromError(error, stack); } } - - // Helper - TournamentStatus getTournamentStatus(TournamentModel tournament) { - if (tournament.matches.isEmpty) return TournamentStatus.upcoming; - - final bool isUpcoming = (tournament.start_date.isAfter(DateTime.now()) && - tournament.end_date.isAfter(DateTime.now())) && - tournament.matches - .every((match) => match.match_status == MatchStatus.yetToStart); - - final bool isRunning = tournament.start_date.isBefore(DateTime.now()) && - tournament.end_date.isAfter(DateTime.now()) && - tournament.matches - .any((match) => match.match_status == MatchStatus.running); - - final bool isFinished = tournament.end_date.isBefore(DateTime.now()) && - tournament.matches.every( - (match) => - match.match_status == MatchStatus.finish || - match.match_status == MatchStatus.abandoned, - ); - - final bool isInProgress = tournament.start_date.isBefore(DateTime.now()) && - tournament.end_date.isAfter(DateTime.now()) && - tournament.matches.any( - (match) => - match.match_status == MatchStatus.running || - match.match_status == MatchStatus.yetToStart, - ); - - if (isUpcoming) return TournamentStatus.upcoming; - if (isRunning || isInProgress) return TournamentStatus.running; - if (isFinished) return TournamentStatus.finish; - - return TournamentStatus.finish; - } } diff --git a/khelo/lib/ui/flow/tournament/add/add_tournament_view_model.dart b/khelo/lib/ui/flow/tournament/add/add_tournament_view_model.dart index f0dab3ce..52121f91 100644 --- a/khelo/lib/ui/flow/tournament/add/add_tournament_view_model.dart +++ b/khelo/lib/ui/flow/tournament/add/add_tournament_view_model.dart @@ -1,6 +1,5 @@ import 'dart:io'; -import 'package:data/api/match/match_model.dart'; import 'package:data/api/team/team_model.dart'; import 'package:data/api/tournament/tournament_model.dart'; import 'package:data/errors/app_error.dart'; @@ -60,7 +59,6 @@ class AddTournamentViewNotifier extends StateNotifier { DateTime.now().add(const Duration(days: 1)), selectedType: editedTournament?.type ?? TournamentType.knockOut, teams: editedTournament?.teams ?? [], - matches: editedTournament?.matches ?? [], ); } @@ -175,7 +173,7 @@ class AddTournamentViewNotifier extends StateNotifier { created_at: _editedTournament?.created_at ?? DateTime.now(), created_by: _editedTournament?.created_by ?? state.currentUserId!, team_ids: state.teams.map((e) => e.id).toList(), - match_ids: state.matches.map((e) => e.id).toList(), + match_ids: _editedTournament?.match_ids ?? [], members: _editedTournament?.members ?? [ TournamentMember( @@ -231,7 +229,6 @@ class AddTournamentState with _$AddTournamentState { required DateTime endDate, required DateTime startDate, @Default([]) List teams, - @Default([]) List matches, @Default(false) bool pop, @Default(false) bool loading, @Default(false) bool showDateError, diff --git a/khelo/lib/ui/flow/tournament/add/add_tournament_view_model.freezed.dart b/khelo/lib/ui/flow/tournament/add/add_tournament_view_model.freezed.dart index 649047c0..61d55984 100644 --- a/khelo/lib/ui/flow/tournament/add/add_tournament_view_model.freezed.dart +++ b/khelo/lib/ui/flow/tournament/add/add_tournament_view_model.freezed.dart @@ -24,7 +24,6 @@ mixin _$AddTournamentState { DateTime get endDate => throw _privateConstructorUsedError; DateTime get startDate => throw _privateConstructorUsedError; List get teams => throw _privateConstructorUsedError; - List get matches => throw _privateConstructorUsedError; bool get pop => throw _privateConstructorUsedError; bool get loading => throw _privateConstructorUsedError; bool get showDateError => throw _privateConstructorUsedError; @@ -58,7 +57,6 @@ abstract class $AddTournamentStateCopyWith<$Res> { DateTime endDate, DateTime startDate, List teams, - List matches, bool pop, bool loading, bool showDateError, @@ -93,7 +91,6 @@ class _$AddTournamentStateCopyWithImpl<$Res, $Val extends AddTournamentState> Object? endDate = null, Object? startDate = null, Object? teams = null, - Object? matches = null, Object? pop = null, Object? loading = null, Object? showDateError = null, @@ -131,10 +128,6 @@ class _$AddTournamentStateCopyWithImpl<$Res, $Val extends AddTournamentState> ? _value.teams : teams // ignore: cast_nullable_to_non_nullable as List, - matches: null == matches - ? _value.matches - : matches // ignore: cast_nullable_to_non_nullable - as List, pop: null == pop ? _value.pop : pop // ignore: cast_nullable_to_non_nullable @@ -192,7 +185,6 @@ abstract class _$$AddTournamentStateImplCopyWith<$Res> DateTime endDate, DateTime startDate, List teams, - List matches, bool pop, bool loading, bool showDateError, @@ -225,7 +217,6 @@ class __$$AddTournamentStateImplCopyWithImpl<$Res> Object? endDate = null, Object? startDate = null, Object? teams = null, - Object? matches = null, Object? pop = null, Object? loading = null, Object? showDateError = null, @@ -263,10 +254,6 @@ class __$$AddTournamentStateImplCopyWithImpl<$Res> ? _value._teams : teams // ignore: cast_nullable_to_non_nullable as List, - matches: null == matches - ? _value._matches - : matches // ignore: cast_nullable_to_non_nullable - as List, pop: null == pop ? _value.pop : pop // ignore: cast_nullable_to_non_nullable @@ -319,7 +306,6 @@ class _$AddTournamentStateImpl implements _AddTournamentState { required this.endDate, required this.startDate, final List teams = const [], - final List matches = const [], this.pop = false, this.loading = false, this.showDateError = false, @@ -329,8 +315,7 @@ class _$AddTournamentStateImpl implements _AddTournamentState { this.bannerImgUrl = null, required this.nameController, this.selectedType = TournamentType.knockOut}) - : _teams = teams, - _matches = matches; + : _teams = teams; @override final Object? error; @@ -355,15 +340,6 @@ class _$AddTournamentStateImpl implements _AddTournamentState { return EqualUnmodifiableListView(_teams); } - final List _matches; - @override - @JsonKey() - List get matches { - if (_matches is EqualUnmodifiableListView) return _matches; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_matches); - } - @override @JsonKey() final bool pop; @@ -393,7 +369,7 @@ class _$AddTournamentStateImpl implements _AddTournamentState { @override String toString() { - return 'AddTournamentState(error: $error, actionError: $actionError, profilePath: $profilePath, bannerPath: $bannerPath, currentUserId: $currentUserId, endDate: $endDate, startDate: $startDate, teams: $teams, matches: $matches, pop: $pop, loading: $loading, showDateError: $showDateError, enableButton: $enableButton, imageUploading: $imageUploading, profileImgUrl: $profileImgUrl, bannerImgUrl: $bannerImgUrl, nameController: $nameController, selectedType: $selectedType)'; + return 'AddTournamentState(error: $error, actionError: $actionError, profilePath: $profilePath, bannerPath: $bannerPath, currentUserId: $currentUserId, endDate: $endDate, startDate: $startDate, teams: $teams, pop: $pop, loading: $loading, showDateError: $showDateError, enableButton: $enableButton, imageUploading: $imageUploading, profileImgUrl: $profileImgUrl, bannerImgUrl: $bannerImgUrl, nameController: $nameController, selectedType: $selectedType)'; } @override @@ -414,7 +390,6 @@ class _$AddTournamentStateImpl implements _AddTournamentState { (identical(other.startDate, startDate) || other.startDate == startDate) && const DeepCollectionEquality().equals(other._teams, _teams) && - const DeepCollectionEquality().equals(other._matches, _matches) && (identical(other.pop, pop) || other.pop == pop) && (identical(other.loading, loading) || other.loading == loading) && (identical(other.showDateError, showDateError) || @@ -444,7 +419,6 @@ class _$AddTournamentStateImpl implements _AddTournamentState { endDate, startDate, const DeepCollectionEquality().hash(_teams), - const DeepCollectionEquality().hash(_matches), pop, loading, showDateError, @@ -475,7 +449,6 @@ abstract class _AddTournamentState implements AddTournamentState { required final DateTime endDate, required final DateTime startDate, final List teams, - final List matches, final bool pop, final bool loading, final bool showDateError, @@ -503,8 +476,6 @@ abstract class _AddTournamentState implements AddTournamentState { @override List get teams; @override - List get matches; - @override bool get pop; @override bool get loading; diff --git a/khelo/lib/ui/flow/tournament/detail/tabs/tournament_detail_matches_tab.dart b/khelo/lib/ui/flow/tournament/detail/tabs/tournament_detail_matches_tab.dart index 1b4837f1..413f439e 100644 --- a/khelo/lib/ui/flow/tournament/detail/tabs/tournament_detail_matches_tab.dart +++ b/khelo/lib/ui/flow/tournament/detail/tabs/tournament_detail_matches_tab.dart @@ -26,7 +26,7 @@ class TournamentDetailMatchesTab extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final state = ref.watch(tournamentDetailStateProvider); - if (state.filteredMatches.isEmpty && state.tournament!.matches.isEmpty) { + if (state.filteredMatches.isEmpty && state.matches.isEmpty) { return EmptyScreen( title: context.l10n.tournament_detail_matches_empty_title, description: context.l10n.tournament_detail_matches_empty_description, diff --git a/khelo/lib/ui/flow/tournament/detail/tabs/tournament_detail_overview_tab.dart b/khelo/lib/ui/flow/tournament/detail/tabs/tournament_detail_overview_tab.dart index fd04685a..5400569c 100644 --- a/khelo/lib/ui/flow/tournament/detail/tabs/tournament_detail_overview_tab.dart +++ b/khelo/lib/ui/flow/tournament/detail/tabs/tournament_detail_overview_tab.dart @@ -15,27 +15,27 @@ import 'package:style/extensions/context_extensions.dart'; import 'package:style/text/app_text_style.dart'; import '../../../../../components/image_avatar.dart'; +import '../tournament_detail_view_model.dart'; class TournamentDetailOverviewTab extends ConsumerWidget { - final TournamentModel tournament; final PageController controller; const TournamentDetailOverviewTab({ super.key, - required this.tournament, required this.controller, }); @override Widget build(BuildContext context, WidgetRef ref) { + final state = ref.watch(tournamentDetailStateProvider); return ListView( padding: context.mediaQueryPadding.copyWith(top: 0) + EdgeInsets.symmetric(horizontal: 16).copyWith(bottom: 40), children: [ - _featuredMatchesView(context, tournament.matches), - _keyStatsView(context, tournament.keyStats), - _teamsSquadsView(context, tournament.teams), - _infoView(context, tournament), + _featuredMatchesView(context, state.matches), + _keyStatsView(context, state.keyStats), + _teamsSquadsView(context, state.tournament!.teams), + _infoView(context, state.tournament!), ], ); } diff --git a/khelo/lib/ui/flow/tournament/detail/tabs/tournament_detail_stats_tab.dart b/khelo/lib/ui/flow/tournament/detail/tabs/tournament_detail_stats_tab.dart index 2502a2ed..15a853fe 100644 --- a/khelo/lib/ui/flow/tournament/detail/tabs/tournament_detail_stats_tab.dart +++ b/khelo/lib/ui/flow/tournament/detail/tabs/tournament_detail_stats_tab.dart @@ -30,7 +30,7 @@ class TournamentDetailStatsTab extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final state = ref.watch(tournamentDetailStateProvider); - if (state.tournament!.keyStats.isEmpty && state.filteredStats.isEmpty) { + if (state.keyStats.isEmpty && state.filteredStats.isEmpty) { return EmptyScreen( title: context.l10n.tournament_detail_stats_empty_title, description: context.l10n.tournament_detail_stats_empty_description, @@ -96,11 +96,7 @@ class TournamentDetailStatsTab extends ConsumerWidget { )) .toList(), rows: state.filteredStats - .map((e) => _dataRow( - context, - e, - state.tournament!.matches, - )) + .map((e) => _dataRow(context, e, state.matches)) .toList()), ); } diff --git a/khelo/lib/ui/flow/tournament/detail/tournament_detail_screen.dart b/khelo/lib/ui/flow/tournament/detail/tournament_detail_screen.dart index dddb3bff..a1edfa43 100644 --- a/khelo/lib/ui/flow/tournament/detail/tournament_detail_screen.dart +++ b/khelo/lib/ui/flow/tournament/detail/tournament_detail_screen.dart @@ -146,7 +146,6 @@ class _TournamentDetailScreenState onPageChanged: notifier.onTabChange, children: [ TournamentDetailOverviewTab( - tournament: state.tournament!, controller: _controller, ), TournamentDetailTeamsTab( @@ -157,7 +156,7 @@ class _TournamentDetailScreenState onAddMatchTap: () => _handleAddMatchTap(context, state), ), TournamentDetailPointsTableTab( - teamStats: state.tournament!.teamStats, + teamStats: state.teamStats, ), TournamentDetailStatsTab( onFiltered: notifier.onStatFilter, diff --git a/khelo/lib/ui/flow/tournament/detail/tournament_detail_view_model.dart b/khelo/lib/ui/flow/tournament/detail/tournament_detail_view_model.dart index 75260b42..abf15851 100644 --- a/khelo/lib/ui/flow/tournament/detail/tournament_detail_view_model.dart +++ b/khelo/lib/ui/flow/tournament/detail/tournament_detail_view_model.dart @@ -3,8 +3,10 @@ import 'dart:async'; import 'package:data/api/match/match_model.dart'; import 'package:data/api/team/team_model.dart'; import 'package:data/api/tournament/tournament_model.dart'; +import 'package:data/service/match/match_service.dart'; import 'package:data/service/tournament/tournament_service.dart'; import 'package:data/storage/app_preferences.dart'; +import 'package:data/utils/combine_latest.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; @@ -16,6 +18,7 @@ final tournamentDetailStateProvider = StateNotifierProvider.autoDispose< TournamentDetailStateViewNotifier, TournamentDetailState>( (ref) => TournamentDetailStateViewNotifier( ref.read(tournamentServiceProvider), + ref.read(matchServiceProvider), ref.read(currentUserPod)?.id, ), ); @@ -23,10 +26,13 @@ final tournamentDetailStateProvider = StateNotifierProvider.autoDispose< class TournamentDetailStateViewNotifier extends StateNotifier { final TournamentService _tournamentService; + final MatchService _matchService; StreamSubscription? _tournamentSubscription; + StreamSubscription? _matchSubscription; TournamentDetailStateViewNotifier( this._tournamentService, + this._matchService, String? userId, ) : super(TournamentDetailState(currentUserId: userId)); @@ -43,19 +49,34 @@ class TournamentDetailStateViewNotifier state = state.copyWith(loading: true); - _tournamentSubscription = _tournamentService - .streamTournamentById(_tournamentId!) - .listen((tournament) { - state = state.copyWith( - tournament: tournament, - loading: false, - ); - onMatchFilter(null); - onStatFilter(state.selectedFilterTag); + final tournamentData = combineLatest3( + _tournamentService.streamTournamentById(_tournamentId!), + _tournamentService.streamTeamStats(_tournamentId!), + _tournamentService.streamPlayerKeyStats(_tournamentId!)); + + _tournamentSubscription = tournamentData.listen((data) async { + final tournament = data.$1; + final teamStats = data.$2; + final keyStats = data.$3.getTopKeyStats(); + + _matchSubscription?.cancel(); + _matchSubscription = _matchService + .streamMatchesByIds(tournament.match_ids) + .listen((matches) { + state = state.copyWith( + tournament: tournament, + teamStats: teamStats, + keyStats: keyStats, + matches: matches, + loading: false, + ); + onMatchFilter(null); + onStatFilter(state.selectedFilterTag); + }); }, onError: (e) { state = state.copyWith(error: e, loading: false); debugPrint( - "TournamentDetailStateViewNotifier: error while loading tournament list -> $e"); + "TournamentDetailStateViewNotifier: error while loading tournament data -> $e"); }); } @@ -78,9 +99,9 @@ class TournamentDetailStateViewNotifier } void onMatchFilter(String? filter) { - if (state.tournament == null) return; + if (state.matches.isEmpty || state.tournament == null) return; - final matches = state.tournament!.matches; + final matches = state.matches.toList(); if (filter == null) { state = state.copyWith(filteredMatches: matches); @@ -105,7 +126,7 @@ class TournamentDetailStateViewNotifier void onStatFilter(KeyStatFilterTag tag) { if (state.tournament == null) return; - var filteredStats = state.tournament!.keyStats; + var filteredStats = state.keyStats.toList(); filteredStats = filteredStats.where((e) { switch (tag) { @@ -163,6 +184,7 @@ class TournamentDetailStateViewNotifier @override void dispose() { _tournamentSubscription?.cancel(); + _matchSubscription?.cancel(); super.dispose(); } } @@ -177,6 +199,9 @@ class TournamentDetailState with _$TournamentDetailState { Object? actionError, String? currentUserId, @Default(null) String? matchFilter, + @Default([]) List matches, + @Default([]) List teamStats, + @Default([]) List keyStats, @Default([]) List filteredMatches, @Default(KeyStatFilterTag.all) KeyStatFilterTag selectedFilterTag, @Default([]) List filteredStats, diff --git a/khelo/lib/ui/flow/tournament/detail/tournament_detail_view_model.freezed.dart b/khelo/lib/ui/flow/tournament/detail/tournament_detail_view_model.freezed.dart index f990f253..b42ad39a 100644 --- a/khelo/lib/ui/flow/tournament/detail/tournament_detail_view_model.freezed.dart +++ b/khelo/lib/ui/flow/tournament/detail/tournament_detail_view_model.freezed.dart @@ -23,6 +23,9 @@ mixin _$TournamentDetailState { Object? get actionError => throw _privateConstructorUsedError; String? get currentUserId => throw _privateConstructorUsedError; String? get matchFilter => throw _privateConstructorUsedError; + List get matches => throw _privateConstructorUsedError; + List get teamStats => throw _privateConstructorUsedError; + List get keyStats => throw _privateConstructorUsedError; List get filteredMatches => throw _privateConstructorUsedError; KeyStatFilterTag get selectedFilterTag => throw _privateConstructorUsedError; List get filteredStats => throw _privateConstructorUsedError; @@ -48,6 +51,9 @@ abstract class $TournamentDetailStateCopyWith<$Res> { Object? actionError, String? currentUserId, String? matchFilter, + List matches, + List teamStats, + List keyStats, List filteredMatches, KeyStatFilterTag selectedFilterTag, List filteredStats}); @@ -78,6 +84,9 @@ class _$TournamentDetailStateCopyWithImpl<$Res, Object? actionError = freezed, Object? currentUserId = freezed, Object? matchFilter = freezed, + Object? matches = null, + Object? teamStats = null, + Object? keyStats = null, Object? filteredMatches = null, Object? selectedFilterTag = null, Object? filteredStats = null, @@ -105,6 +114,18 @@ class _$TournamentDetailStateCopyWithImpl<$Res, ? _value.matchFilter : matchFilter // ignore: cast_nullable_to_non_nullable as String?, + matches: null == matches + ? _value.matches + : matches // ignore: cast_nullable_to_non_nullable + as List, + teamStats: null == teamStats + ? _value.teamStats + : teamStats // ignore: cast_nullable_to_non_nullable + as List, + keyStats: null == keyStats + ? _value.keyStats + : keyStats // ignore: cast_nullable_to_non_nullable + as List, filteredMatches: null == filteredMatches ? _value.filteredMatches : filteredMatches // ignore: cast_nullable_to_non_nullable @@ -152,6 +173,9 @@ abstract class _$$TournamentDetailStateImplCopyWith<$Res> Object? actionError, String? currentUserId, String? matchFilter, + List matches, + List teamStats, + List keyStats, List filteredMatches, KeyStatFilterTag selectedFilterTag, List filteredStats}); @@ -181,6 +205,9 @@ class __$$TournamentDetailStateImplCopyWithImpl<$Res> Object? actionError = freezed, Object? currentUserId = freezed, Object? matchFilter = freezed, + Object? matches = null, + Object? teamStats = null, + Object? keyStats = null, Object? filteredMatches = null, Object? selectedFilterTag = null, Object? filteredStats = null, @@ -208,6 +235,18 @@ class __$$TournamentDetailStateImplCopyWithImpl<$Res> ? _value.matchFilter : matchFilter // ignore: cast_nullable_to_non_nullable as String?, + matches: null == matches + ? _value._matches + : matches // ignore: cast_nullable_to_non_nullable + as List, + teamStats: null == teamStats + ? _value._teamStats + : teamStats // ignore: cast_nullable_to_non_nullable + as List, + keyStats: null == keyStats + ? _value._keyStats + : keyStats // ignore: cast_nullable_to_non_nullable + as List, filteredMatches: null == filteredMatches ? _value._filteredMatches : filteredMatches // ignore: cast_nullable_to_non_nullable @@ -235,10 +274,16 @@ class _$TournamentDetailStateImpl implements _TournamentDetailState { this.actionError, this.currentUserId, this.matchFilter = null, + final List matches = const [], + final List teamStats = const [], + final List keyStats = const [], final List filteredMatches = const [], this.selectedFilterTag = KeyStatFilterTag.all, final List filteredStats = const []}) - : _filteredMatches = filteredMatches, + : _matches = matches, + _teamStats = teamStats, + _keyStats = keyStats, + _filteredMatches = filteredMatches, _filteredStats = filteredStats; @override @@ -259,6 +304,33 @@ class _$TournamentDetailStateImpl implements _TournamentDetailState { @override @JsonKey() final String? matchFilter; + final List _matches; + @override + @JsonKey() + List get matches { + if (_matches is EqualUnmodifiableListView) return _matches; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_matches); + } + + final List _teamStats; + @override + @JsonKey() + List get teamStats { + if (_teamStats is EqualUnmodifiableListView) return _teamStats; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_teamStats); + } + + final List _keyStats; + @override + @JsonKey() + List get keyStats { + if (_keyStats is EqualUnmodifiableListView) return _keyStats; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_keyStats); + } + final List _filteredMatches; @override @JsonKey() @@ -282,7 +354,7 @@ class _$TournamentDetailStateImpl implements _TournamentDetailState { @override String toString() { - return 'TournamentDetailState(tournament: $tournament, loading: $loading, selectedTab: $selectedTab, error: $error, actionError: $actionError, currentUserId: $currentUserId, matchFilter: $matchFilter, filteredMatches: $filteredMatches, selectedFilterTag: $selectedFilterTag, filteredStats: $filteredStats)'; + return 'TournamentDetailState(tournament: $tournament, loading: $loading, selectedTab: $selectedTab, error: $error, actionError: $actionError, currentUserId: $currentUserId, matchFilter: $matchFilter, matches: $matches, teamStats: $teamStats, keyStats: $keyStats, filteredMatches: $filteredMatches, selectedFilterTag: $selectedFilterTag, filteredStats: $filteredStats)'; } @override @@ -302,6 +374,10 @@ class _$TournamentDetailStateImpl implements _TournamentDetailState { other.currentUserId == currentUserId) && (identical(other.matchFilter, matchFilter) || other.matchFilter == matchFilter) && + const DeepCollectionEquality().equals(other._matches, _matches) && + const DeepCollectionEquality() + .equals(other._teamStats, _teamStats) && + const DeepCollectionEquality().equals(other._keyStats, _keyStats) && const DeepCollectionEquality() .equals(other._filteredMatches, _filteredMatches) && (identical(other.selectedFilterTag, selectedFilterTag) || @@ -320,6 +396,9 @@ class _$TournamentDetailStateImpl implements _TournamentDetailState { const DeepCollectionEquality().hash(actionError), currentUserId, matchFilter, + const DeepCollectionEquality().hash(_matches), + const DeepCollectionEquality().hash(_teamStats), + const DeepCollectionEquality().hash(_keyStats), const DeepCollectionEquality().hash(_filteredMatches), selectedFilterTag, const DeepCollectionEquality().hash(_filteredStats)); @@ -343,6 +422,9 @@ abstract class _TournamentDetailState implements TournamentDetailState { final Object? actionError, final String? currentUserId, final String? matchFilter, + final List matches, + final List teamStats, + final List keyStats, final List filteredMatches, final KeyStatFilterTag selectedFilterTag, final List filteredStats}) = _$TournamentDetailStateImpl; @@ -362,6 +444,12 @@ abstract class _TournamentDetailState implements TournamentDetailState { @override String? get matchFilter; @override + List get matches; + @override + List get teamStats; + @override + List get keyStats; + @override List get filteredMatches; @override KeyStatFilterTag get selectedFilterTag; diff --git a/khelo/lib/ui/flow/tournament/match_selection/match_selection_view_model.dart b/khelo/lib/ui/flow/tournament/match_selection/match_selection_view_model.dart index 416bb7c5..0848560a 100644 --- a/khelo/lib/ui/flow/tournament/match_selection/match_selection_view_model.dart +++ b/khelo/lib/ui/flow/tournament/match_selection/match_selection_view_model.dart @@ -4,6 +4,7 @@ import 'dart:collection'; import 'package:data/api/match/match_model.dart'; import 'package:data/api/tournament/tournament_model.dart'; import 'package:data/extensions/string_extensions.dart'; +import 'package:data/service/match/match_service.dart'; import 'package:data/service/tournament/tournament_service.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -16,17 +17,20 @@ typedef GroupedMatchMap = Map>>; final matchSelectionStateProvider = StateNotifierProvider.autoDispose< MatchSelectionViewNotifier, MatchSelectionState>( - (ref) => MatchSelectionViewNotifier(ref.read(tournamentServiceProvider))); + (ref) => MatchSelectionViewNotifier( + ref.read(tournamentServiceProvider), ref.read(matchServiceProvider))); class MatchSelectionViewNotifier extends StateNotifier { final TournamentService _tournamentService; + final MatchService _matchService; late MatchScheduler _scheduler; StreamSubscription? _tournamentSubscription; + StreamSubscription? _matchSubscription; String? _tournamentId; Timer? _debounce; - MatchSelectionViewNotifier(this._tournamentService) + MatchSelectionViewNotifier(this._tournamentService, this._matchService) : super(MatchSelectionState(searchController: TextEditingController())); void setData(String tournamentId) { @@ -42,18 +46,24 @@ class MatchSelectionViewNotifier extends StateNotifier { _tournamentSubscription = _tournamentService .streamTournamentById(_tournamentId!) - .listen((tournament) { - _scheduler = - MatchScheduler(tournament.teams, tournament.matches, tournament.type); - final scheduledMatches = _scheduler.scheduleMatchesByType(); - final sorted = SplayTreeMap>>.from( - scheduledMatches, (a, b) => a.index.compareTo(b.index)); - - state = state.copyWith( - tournament: tournament, - matches: sorted, - loading: false, - ); + .listen((tournament) async { + _matchSubscription?.cancel(); + _matchSubscription = _matchService + .streamMatchesByIds(tournament.match_ids) + .listen((matches) { + _scheduler = MatchScheduler(tournament.teams, matches, tournament.type); + final scheduledMatches = _scheduler.scheduleMatchesByType(); + final sorted = + SplayTreeMap>>.from( + scheduledMatches, (a, b) => a.index.compareTo(b.index)); + if (mounted) { + state = state.copyWith( + tournament: tournament, + matches: sorted, + loading: false, + ); + } + }); }, onError: (e) { state = state.copyWith(error: e, loading: false); debugPrint( @@ -179,6 +189,7 @@ class MatchSelectionViewNotifier extends StateNotifier { @override void dispose() { _tournamentSubscription?.cancel(); + _matchSubscription?.cancel(); state.searchController.dispose(); _debounce?.cancel(); super.dispose();