From 4144a9bd120e25f7046c8ad8c56ba1bd3374aa61 Mon Sep 17 00:00:00 2001 From: cp-mayank-v Date: Thu, 5 Sep 2024 12:37:09 +0530 Subject: [PATCH 1/6] Refactor home screen --- data/lib/service/match/match_service.dart | 5 +- khelo/assets/locales/app_en.arb | 22 +- khelo/lib/ui/app_route.dart | 23 ++ .../home/components/home_view_all_screen.dart | 42 +++ khelo/lib/ui/flow/home/home_screen.dart | 246 +++++++++++++----- khelo/lib/ui/flow/home/home_view_model.dart | 53 +++- .../ui/flow/home/home_view_model.freezed.dart | 70 +++-- .../ui/flow/home/search/search_screen.dart | 133 ++++++++++ .../flow/home/search/search_view_model.dart | 49 ++++ .../search/search_view_model.freezed.dart | 179 +++++++++++++ 10 files changed, 735 insertions(+), 87 deletions(-) create mode 100644 khelo/lib/ui/flow/home/components/home_view_all_screen.dart create mode 100644 khelo/lib/ui/flow/home/search/search_screen.dart create mode 100644 khelo/lib/ui/flow/home/search/search_view_model.dart create mode 100644 khelo/lib/ui/flow/home/search/search_view_model.freezed.dart diff --git a/data/lib/service/match/match_service.dart b/data/lib/service/match/match_service.dart index 6e55c4df..2bed0059 100644 --- a/data/lib/service/match/match_service.dart +++ b/data/lib/service/match/match_service.dart @@ -148,14 +148,12 @@ class MatchService { }).handleError((error, stack) => throw AppError.fromError(error, stack)); } - Stream> getRunningMatches() { + Stream> getMatches() { return _matchCollection - .where(FireStoreConst.matchStatus, isEqualTo: MatchStatus.running.value) .snapshots() .asyncMap((snapshot) async { final List matches = []; for (final mainDoc in snapshot.docs) { - if (mainDoc.data().match_status == MatchStatus.running) { final match = mainDoc.data(); final List teams = await getTeamsList(match.teams); @@ -183,7 +181,6 @@ class MatchService { ), ); } - } return matches; }).handleError((error, stack) => throw AppError.fromError(error, stack)); } diff --git a/khelo/assets/locales/app_en.arb b/khelo/assets/locales/app_en.arb index 197a9a5e..a5be1894 100644 --- a/khelo/assets/locales/app_en.arb +++ b/khelo/assets/locales/app_en.arb @@ -105,8 +105,25 @@ "@_HOME": { }, "home_screen_title": "Home", - "home_screen_no_matches_title": "No matches are running currently", - "home_screen_no_matches_description_text": "Running matches will be shown here, Stay tuned for some intense matches.", + "home_screen_live_title": "Live", + "home_screen_upcoming_title": "Upcoming", + "home_screen_winning_title": "Winning", + "home_screen_set_up_match_title": "Set up a match in minutes.", + "home_screen_create_match_btn": "Create match", + "home_screen_set_up_team_title": "Set up a team in minutes.", + "home_screen_create_team_btn": "Create team", + "home_screen_view_all_btn": "View all", + "home_screen_no_matches_title": "No Matches in this area!", + "home_screen_no_matches_description_text": "Enjoy the freedom of creating your own cricket matches or teams.", + + "@_HOME_SEARCH": { + }, + "home_search_hint_title": "Search matches by team name...", + "home_search_empty_title": "Start your search here!", + "home_search_empty_message": "Type your search above to find what you need fast.", + "home_search_results_title": "Results", + "home_match_list_empty_search_title": "No match results", + "home_match_list_empty_search_message": "No results found. Make sure the team name is correct or try again later.", "@_MY_CRICKET": { }, @@ -590,7 +607,6 @@ "score_board_batsman_title": "batsMan", "score_board_bowler_title": "bowler", "score_board_change_striker_title": "Change Striker", - "score_board_add_substitute_title": "Add substitute", "score_board_can_undo_till_running_over_title": "Undo is only allowed till running over", "score_board_undo_last_ball_title": "Undo Last Ball", "score_board_undo_last_ball_description_text": "are you sure you want to undo last ball?", diff --git a/khelo/lib/ui/app_route.dart b/khelo/lib/ui/app_route.dart index 08289152..81236d7d 100644 --- a/khelo/lib/ui/app_route.dart +++ b/khelo/lib/ui/app_route.dart @@ -4,6 +4,7 @@ import 'package:data/api/team/team_model.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; +import 'package:khelo/ui/flow/home/search/search_screen.dart'; import 'package:khelo/ui/flow/intro/intro_screen.dart'; import 'package:khelo/ui/flow/matches/add_match/add_match_screen.dart'; import 'package:khelo/ui/flow/matches/add_match/match_officials/add_match_officials_screen.dart'; @@ -20,6 +21,7 @@ import 'package:khelo/ui/flow/team/add_team_member/add_team_member_screen.dart'; import 'package:khelo/ui/flow/team/detail/make_admin/make_team_admin_screen.dart'; import 'package:khelo/ui/flow/team/detail/team_detail_screen.dart'; import 'package:khelo/ui/flow/team/search_team/search_team_screen.dart'; +import 'flow/home/components/home_view_all_screen.dart'; import 'flow/main/main_screen.dart'; import 'flow/settings/support/contact_support_screen.dart'; import 'flow/sign_in/sign_in_with_phone/sign_in_with_phone_screen.dart'; @@ -40,6 +42,8 @@ class AppRoute { static const pathScoreBoard = '/score-board'; static const pathTeamDetail = '/team-detail'; static const pathMatchDetailTab = '/match-detail-tab'; + static const pathSearchHome = "/search-home"; + static const pathViewAll = "/view-all"; final String path; final String? name; @@ -128,6 +132,17 @@ class AppRoute { ), ); + static AppRoute searchHome({required List matches}) => + AppRoute(pathSearchHome, + builder: (_) => SearchHomeScreen(matches: matches)); + + static AppRoute viewAll({ + required String title, + required List matches, + }) => + AppRoute(pathViewAll, + builder: (_) => HomeViewAllScreen(title: title, matches: matches)); + static AppRoute addTossDetail({required String matchId}) => AppRoute( pathAddTossDetail, builder: (_) => AddTossDetailScreen( @@ -240,6 +255,14 @@ class AppRoute { return state.extra == null ? const MainScreen() : state.widget(context); }, ), + GoRoute( + path: pathSearchHome, + builder: (context, state) => state.widget(context), + ), + GoRoute( + path: pathViewAll, + builder: (context, state) => state.widget(context), + ), GoRoute( path: intro.path, builder: (context, state) { diff --git a/khelo/lib/ui/flow/home/components/home_view_all_screen.dart b/khelo/lib/ui/flow/home/components/home_view_all_screen.dart new file mode 100644 index 00000000..71d1a0f9 --- /dev/null +++ b/khelo/lib/ui/flow/home/components/home_view_all_screen.dart @@ -0,0 +1,42 @@ +import 'package:data/api/match/match_model.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:khelo/components/app_page.dart'; +import 'package:khelo/components/match_detail_cell.dart'; +import 'package:khelo/ui/app_route.dart'; + +class HomeViewAllScreen extends ConsumerWidget { + final String title; + final List matches; + + const HomeViewAllScreen({ + super.key, + required this.title, + required this.matches, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return AppPage( + title: title, + body: Builder(builder: (context) => _body(context)), + ); + } + + Widget _body(BuildContext context) { + return ListView.separated( + padding: const EdgeInsets.all(16), + itemCount: matches.length, + itemBuilder: (context, index) { + final match = matches[index]; + return MatchDetailCell( + match: match, + onTap: () => AppRoute.matchDetailTab( + matchId: match.id ?? 'INVALID ID', + ).push(context), + ); + }, + separatorBuilder: (context, index) => const SizedBox(height: 16), + ); + } +} diff --git a/khelo/lib/ui/flow/home/home_screen.dart b/khelo/lib/ui/flow/home/home_screen.dart index c19439de..4a538b97 100644 --- a/khelo/lib/ui/flow/home/home_screen.dart +++ b/khelo/lib/ui/flow/home/home_screen.dart @@ -1,6 +1,7 @@ import 'package:data/api/match/match_model.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; import 'package:khelo/components/app_page.dart'; import 'package:khelo/components/error_screen.dart'; import 'package:khelo/components/image_avatar.dart'; @@ -8,12 +9,14 @@ import 'package:khelo/domain/extensions/context_extensions.dart'; import 'package:khelo/domain/formatter/date_formatter.dart'; import 'package:khelo/ui/app_route.dart'; import 'package:khelo/ui/flow/home/home_view_model.dart'; -import 'package:smooth_page_indicator/smooth_page_indicator.dart'; import 'package:style/animations/on_tap_scale.dart'; +import 'package:style/button/action_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 '../../../gen/assets.gen.dart'; + class HomeScreen extends ConsumerStatefulWidget { const HomeScreen({super.key}); @@ -22,8 +25,6 @@ class HomeScreen extends ConsumerStatefulWidget { } class _HomeScreenState extends ConsumerState { - final _controller = - PageController(initialPage: 100, keepPage: true, viewportFraction: 0.9); late HomeViewNotifier notifier; @override @@ -37,7 +38,28 @@ class _HomeScreenState extends ConsumerState { final state = ref.watch(homeViewStateProvider); return AppPage( - title: context.l10n.common_matches_title, + titleWidget: Text( + context.l10n.app_title, + style: AppTextStyle.header1.copyWith( + color: context.colorScheme.primary, + ), + ), + actions: (state.matches.isNotEmpty) + ? [ + actionButton( + context, + icon: SvgPicture.asset( + Assets.images.icSearch, + colorFilter: ColorFilter.mode( + context.colorScheme.textPrimary, + BlendMode.srcATop, + ), + ), + onPressed: () => AppRoute.searchHome(matches: state.tempMatches) + .push(context), + ) + ] + : null, body: Builder(builder: (context) { return _body(context, notifier, state); }), @@ -58,49 +80,113 @@ class _HomeScreenState extends ConsumerState { onRetryTap: notifier.onResume, ); } - - if (state.matches.isNotEmpty) { - return _matchCardSlider(context, state); - } else { - return _emptyMatchView(context); - } + return Padding( + padding: const EdgeInsets.symmetric(vertical: 16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: Row( + children: [ + _createActionView( + context, + title: context.l10n.home_screen_set_up_match_title, + btnText: context.l10n.home_screen_create_match_btn, + onTap: () => AppRoute.addMatch().push(context), + ), + _createActionView( + context, + title: context.l10n.home_screen_set_up_team_title, + btnText: context.l10n.home_screen_create_team_btn, + onTap: () => AppRoute.addTeam().push(context), + ), + ], + ), + )), + const SizedBox(height: 16), + if (state.matches.isNotEmpty) ...[ + _content(context, state) + ] else ...[ + _emptyMatchView(context) + ] + ], + ), + ); } - Widget _matchCardSlider( + Widget _content( BuildContext context, HomeViewState state, ) { - return Column( - children: [ - SizedBox( - height: 165, - child: state.matches.length == 1 - ? Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0), - child: _matchCell(context, state.matches.first), + return Expanded( + child: ListView.builder( + padding: context.mediaQueryPadding + + const EdgeInsets.symmetric(horizontal: 8), + itemCount: state.matches.length, + itemBuilder: (context, index) { + final item = state.matches.entries.elementAt(index); + return item.value.isNotEmpty + ? Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _matchHeader( + context, + header: item.key.getString(context), + isViewAllShow: item.value.length > 3, + onViewAll: () => AppRoute.viewAll( + title: item.key.getString(context), + matches: item.value) + .push(context), + ), + SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: item.value + .map((match) => _matchCell(context, match)) + .toList(), + ), + ), + ], ) - : PageView.builder( - controller: _controller, - itemBuilder: (context, index) { - return _matchCell( - context, state.matches[index % state.matches.length]); - }, - ), - ), - const SizedBox(height: 16), - if (state.matches.length >= 2) ...[ - SmoothPageIndicator( - controller: _controller, - count: state.matches.length, - effect: WormEffect( - dotHeight: 8, - dotWidth: 8, - dotColor: context.colorScheme.containerHigh, - activeDotColor: context.colorScheme.primary, - type: WormType.underground), - ) + : const SizedBox(); + }, + ), + ); + } + + Widget _matchHeader( + BuildContext context, { + required String header, + required bool isViewAllShow, + required Function() onViewAll, + }) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: Row( + children: [ + Text( + header, + style: AppTextStyle.header3 + .copyWith(color: context.colorScheme.textPrimary), + ), + const Spacer(), + Opacity( + opacity: isViewAllShow ? 1 : 0, + child: TextButton( + onPressed: onViewAll, + child: Text( + context.l10n.home_screen_view_all_btn, + style: AppTextStyle.button.copyWith( + color: context.colorScheme.primary, + ), + )), + ), ], - ], + ), ); } @@ -110,8 +196,9 @@ class _HomeScreenState extends ConsumerState { .push(context), child: MediaQuery.withNoTextScaling( child: Container( + width: 360, padding: const EdgeInsets.all(16), - margin: const EdgeInsets.symmetric(horizontal: 8), + margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 16), decoration: BoxDecoration( color: context.colorScheme.containerLow, border: Border.all(color: context.colorScheme.outline), @@ -147,6 +234,7 @@ class _HomeScreenState extends ConsumerState { int wicket, ) { return Row( + mainAxisSize: MainAxisSize.min, children: [ ImageAvatar( initial: matchTeam.team.name[0].toUpperCase(), @@ -205,28 +293,64 @@ class _HomeScreenState extends ConsumerState { } Widget _emptyMatchView(BuildContext context) { - return Container( - padding: const EdgeInsets.all(16), - width: double.infinity, - margin: context.mediaQueryPadding + const EdgeInsets.all(16), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12), - border: Border.all(color: context.colorScheme.primary)), - child: IntrinsicHeight( - child: Column( + return Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + context.l10n.home_screen_no_matches_title, + textAlign: TextAlign.center, + style: AppTextStyle.header2 + .copyWith(color: context.colorScheme.textPrimary), + ), + const SizedBox(height: 8), + Text( + context.l10n.home_screen_no_matches_description_text, + textAlign: TextAlign.center, + style: AppTextStyle.subtitle1 + .copyWith(color: context.colorScheme.textSecondary), + ), + ], + ), + ); + } + + Widget _createActionView( + BuildContext context, { + required String title, + required String btnText, + required Function() onTap, + }) { + return OnTapScale( + onTap: onTap, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + margin: const EdgeInsets.symmetric(horizontal: 8), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(30), + border: Border.all(color: context.colorScheme.outline)), + child: Row( + mainAxisSize: MainAxisSize.min, children: [ - Text( - context.l10n.home_screen_no_matches_title, - textAlign: TextAlign.center, - style: AppTextStyle.subtitle1 - .copyWith(color: context.colorScheme.textPrimary), + CircleAvatar( + backgroundColor: context.colorScheme.containerLow, + child: Icon(Icons.add, color: context.colorScheme.primary), ), - const SizedBox(height: 8), - Text( - context.l10n.home_screen_no_matches_description_text, - textAlign: TextAlign.center, - style: AppTextStyle.body1 - .copyWith(color: context.colorScheme.textSecondary), + const SizedBox(width: 16), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: AppTextStyle.body1 + .copyWith(color: context.colorScheme.textPrimary), + ), + Text( + btnText, + style: AppTextStyle.button + .copyWith(color: context.colorScheme.primary), + ) + ], ), ], ), diff --git a/khelo/lib/ui/flow/home/home_view_model.dart b/khelo/lib/ui/flow/home/home_view_model.dart index 3b3ad930..85c09b4b 100644 --- a/khelo/lib/ui/flow/home/home_view_model.dart +++ b/khelo/lib/ui/flow/home/home_view_model.dart @@ -1,10 +1,12 @@ import 'dart:async'; +import 'package:collection/collection.dart'; 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'; +import 'package:khelo/domain/extensions/context_extensions.dart'; part 'home_view_model.freezed.dart'; @@ -35,9 +37,15 @@ class HomeViewNotifier extends StateNotifier { void _loadMatches() async { state = state.copyWith(loading: state.matches.isEmpty); - _streamSubscription = _matchService.getRunningMatches().listen( + _streamSubscription = _matchService.getMatches().listen( (matches) { - state = state.copyWith(matches: matches, loading: false, error: null); + final groupMatches = _groupMatches(matches); + state = state.copyWith( + tempMatches: matches, + matches: groupMatches, + loading: false, + error: null, + ); }, onError: (e) { state = state.copyWith(error: e, loading: false); @@ -46,6 +54,27 @@ class HomeViewNotifier extends StateNotifier { ); } + Map> _groupMatches( + List matches) { + final groupedMatches = groupBy(matches, (match) { + switch (match.match_status) { + case MatchStatus.running: + case MatchStatus.abandoned: + return MatchStatusLabel.live; + case MatchStatus.yetToStart: + return MatchStatusLabel.upcoming; + case MatchStatus.finish: + return MatchStatusLabel.winning; + } + }); + return { + MatchStatusLabel.live: groupedMatches[MatchStatusLabel.live] ?? [], + MatchStatusLabel.upcoming: + groupedMatches[MatchStatusLabel.upcoming] ?? [], + MatchStatusLabel.winning: groupedMatches[MatchStatusLabel.winning] ?? [], + }; + } + onResume() { _streamSubscription.cancel(); _loadMatches(); @@ -63,6 +92,24 @@ class HomeViewState with _$HomeViewState { const factory HomeViewState({ Object? error, @Default(false) bool loading, - @Default([]) List matches, + @Default([]) List tempMatches, + @Default({}) Map> matches, }) = _HomeViewState; } + +enum MatchStatusLabel { + live, + upcoming, + winning; + + String getString(BuildContext context) { + switch (this) { + case MatchStatusLabel.live: + return context.l10n.home_screen_live_title; + case MatchStatusLabel.upcoming: + return context.l10n.home_screen_upcoming_title; + case MatchStatusLabel.winning: + return context.l10n.home_screen_winning_title; + } + } +} diff --git a/khelo/lib/ui/flow/home/home_view_model.freezed.dart b/khelo/lib/ui/flow/home/home_view_model.freezed.dart index 4c659e5a..0424f2fb 100644 --- a/khelo/lib/ui/flow/home/home_view_model.freezed.dart +++ b/khelo/lib/ui/flow/home/home_view_model.freezed.dart @@ -18,7 +18,9 @@ final _privateConstructorUsedError = UnsupportedError( mixin _$HomeViewState { Object? get error => throw _privateConstructorUsedError; bool get loading => throw _privateConstructorUsedError; - List get matches => throw _privateConstructorUsedError; + List get tempMatches => throw _privateConstructorUsedError; + Map> get matches => + throw _privateConstructorUsedError; /// Create a copy of HomeViewState /// with the given fields replaced by the non-null parameter values. @@ -33,7 +35,11 @@ abstract class $HomeViewStateCopyWith<$Res> { HomeViewState value, $Res Function(HomeViewState) then) = _$HomeViewStateCopyWithImpl<$Res, HomeViewState>; @useResult - $Res call({Object? error, bool loading, List matches}); + $Res call( + {Object? error, + bool loading, + List tempMatches, + Map> matches}); } /// @nodoc @@ -53,6 +59,7 @@ class _$HomeViewStateCopyWithImpl<$Res, $Val extends HomeViewState> $Res call({ Object? error = freezed, Object? loading = null, + Object? tempMatches = null, Object? matches = null, }) { return _then(_value.copyWith( @@ -61,10 +68,14 @@ class _$HomeViewStateCopyWithImpl<$Res, $Val extends HomeViewState> ? _value.loading : loading // ignore: cast_nullable_to_non_nullable as bool, + tempMatches: null == tempMatches + ? _value.tempMatches + : tempMatches // ignore: cast_nullable_to_non_nullable + as List, matches: null == matches ? _value.matches : matches // ignore: cast_nullable_to_non_nullable - as List, + as Map>, ) as $Val); } } @@ -77,7 +88,11 @@ abstract class _$$HomeViewStateImplCopyWith<$Res> __$$HomeViewStateImplCopyWithImpl<$Res>; @override @useResult - $Res call({Object? error, bool loading, List matches}); + $Res call( + {Object? error, + bool loading, + List tempMatches, + Map> matches}); } /// @nodoc @@ -95,6 +110,7 @@ class __$$HomeViewStateImplCopyWithImpl<$Res> $Res call({ Object? error = freezed, Object? loading = null, + Object? tempMatches = null, Object? matches = null, }) { return _then(_$HomeViewStateImpl( @@ -103,10 +119,14 @@ class __$$HomeViewStateImplCopyWithImpl<$Res> ? _value.loading : loading // ignore: cast_nullable_to_non_nullable as bool, + tempMatches: null == tempMatches + ? _value._tempMatches + : tempMatches // ignore: cast_nullable_to_non_nullable + as List, matches: null == matches ? _value._matches : matches // ignore: cast_nullable_to_non_nullable - as List, + as Map>, )); } } @@ -117,26 +137,37 @@ class _$HomeViewStateImpl implements _HomeViewState { const _$HomeViewStateImpl( {this.error, this.loading = false, - final List matches = const []}) - : _matches = matches; + final List tempMatches = const [], + final Map> matches = const {}}) + : _tempMatches = tempMatches, + _matches = matches; @override final Object? error; @override @JsonKey() final bool loading; - final List _matches; + final List _tempMatches; @override @JsonKey() - List get matches { - if (_matches is EqualUnmodifiableListView) return _matches; + List get tempMatches { + if (_tempMatches is EqualUnmodifiableListView) return _tempMatches; // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_matches); + return EqualUnmodifiableListView(_tempMatches); + } + + final Map> _matches; + @override + @JsonKey() + Map> get matches { + if (_matches is EqualUnmodifiableMapView) return _matches; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_matches); } @override String toString() { - return 'HomeViewState(error: $error, loading: $loading, matches: $matches)'; + return 'HomeViewState(error: $error, loading: $loading, tempMatches: $tempMatches, matches: $matches)'; } @override @@ -146,6 +177,8 @@ class _$HomeViewStateImpl implements _HomeViewState { other is _$HomeViewStateImpl && const DeepCollectionEquality().equals(other.error, error) && (identical(other.loading, loading) || other.loading == loading) && + const DeepCollectionEquality() + .equals(other._tempMatches, _tempMatches) && const DeepCollectionEquality().equals(other._matches, _matches)); } @@ -154,6 +187,7 @@ class _$HomeViewStateImpl implements _HomeViewState { runtimeType, const DeepCollectionEquality().hash(error), loading, + const DeepCollectionEquality().hash(_tempMatches), const DeepCollectionEquality().hash(_matches)); /// Create a copy of HomeViewState @@ -167,16 +201,20 @@ class _$HomeViewStateImpl implements _HomeViewState { abstract class _HomeViewState implements HomeViewState { const factory _HomeViewState( - {final Object? error, - final bool loading, - final List matches}) = _$HomeViewStateImpl; + {final Object? error, + final bool loading, + final List tempMatches, + final Map> matches}) = + _$HomeViewStateImpl; @override Object? get error; @override bool get loading; @override - List get matches; + List get tempMatches; + @override + Map> get matches; /// Create a copy of HomeViewState /// with the given fields replaced by the non-null parameter values. diff --git a/khelo/lib/ui/flow/home/search/search_screen.dart b/khelo/lib/ui/flow/home/search/search_screen.dart new file mode 100644 index 00000000..1fbe30e0 --- /dev/null +++ b/khelo/lib/ui/flow/home/search/search_screen.dart @@ -0,0 +1,133 @@ +import 'package:data/api/match/match_model.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; +import 'package:khelo/components/app_page.dart'; +import 'package:khelo/components/empty_screen.dart'; +import 'package:khelo/components/match_detail_cell.dart'; +import 'package:khelo/domain/extensions/context_extensions.dart'; +import 'package:khelo/domain/extensions/widget_extension.dart'; +import 'package:khelo/ui/app_route.dart'; +import 'package:khelo/ui/flow/home/search/search_view_model.dart'; +import 'package:style/button/action_button.dart'; +import 'package:style/extensions/context_extensions.dart'; +import 'package:style/text/app_text_style.dart'; +import 'package:style/text/search_text_field.dart'; + +class SearchHomeScreen extends ConsumerStatefulWidget { + final List matches; + + const SearchHomeScreen({ + super.key, + required this.matches, + }); + + @override + ConsumerState createState() => _SearchHomeScreenState(); +} + +class _SearchHomeScreenState extends ConsumerState { + late SearchHomeViewNotifier notifier; + + @override + void initState() { + super.initState(); + notifier = ref.read(searchHomeViewProvider.notifier); + runPostFrame(() => notifier.setData(widget.matches)); + } + + @override + Widget build(BuildContext context) { + final state = ref.watch(searchHomeViewProvider); + return AppPage( + automaticallyImplyLeading: false, + body: Padding( + padding: context.mediaQueryPadding + const EdgeInsets.only(top: 24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _searchBarField(context, state), + Divider( + color: context.colorScheme.outline, + height: 24, + thickness: 1, + ), + if (state.searchedMatches.isEmpty) ...[ + Expanded( + child: EmptyScreen( + title: (state.searchController.text.isNotEmpty) + ? context.l10n.home_match_list_empty_search_title + : context.l10n.home_search_empty_title, + description: (state.searchController.text.isNotEmpty) + ? context.l10n.home_match_list_empty_search_message + : context.l10n.home_search_empty_message, + isShowButton: false, + ), + ), + ] else ...[ + _searchedList(context, state.searchedMatches), + ], + ], + ), + ), + ); + } + + Widget _searchBarField(BuildContext context, SearchHomeViewState state) { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + actionButton(context, + onPressed: context.pop, + icon: Icon( + CupertinoIcons.chevron_back, + size: 24, + color: context.colorScheme.textPrimary, + )), + Expanded( + child: SearchTextField( + controller: state.searchController, + hintText: context.l10n.home_search_hint_title, + suffixIcon: (state.searchController.text.isNotEmpty) + ? actionButton( + context, + icon: Icon( + Icons.close_rounded, + color: context.colorScheme.textDisabled, + ), + onPressed: notifier.onClear, + ) + : null, + onChange: notifier.onChange, + ), + ), + const SizedBox(width: 16), + ], + ); + } + + Widget _searchedList(BuildContext context, List matches) { + return Expanded( + child: ListView.separated( + padding: const EdgeInsets.all(16), + itemCount: 1 + matches.length, + itemBuilder: (context, index) { + if (index == 0) { + return Text( + context.l10n.home_search_results_title, + style: AppTextStyle.header3.copyWith( + color: context.colorScheme.textPrimary, + ), + ); + } + final match = matches[index - 1]; + return MatchDetailCell( + match: match, + onTap: () => AppRoute.matchDetailTab(matchId: match.id ?? ""), + ); + }, + separatorBuilder: (context, index) => const SizedBox(height: 16), + )); + } +} diff --git a/khelo/lib/ui/flow/home/search/search_view_model.dart b/khelo/lib/ui/flow/home/search/search_view_model.dart new file mode 100644 index 00000000..d9d99a76 --- /dev/null +++ b/khelo/lib/ui/flow/home/search/search_view_model.dart @@ -0,0 +1,49 @@ +import 'package:data/api/match/match_model.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'search_view_model.freezed.dart'; + +final searchHomeViewProvider = StateNotifierProvider.autoDispose< + SearchHomeViewNotifier, + SearchHomeViewState>((ref) => SearchHomeViewNotifier()); + +class SearchHomeViewNotifier extends StateNotifier { + SearchHomeViewNotifier() + : super(SearchHomeViewState(searchController: TextEditingController())); + + late List _matches; + + void setData(List matches) { + _matches = matches; + } + + void onChange() { + final searchKey = state.searchController.text.trim(); + if (searchKey.isEmpty) { + state = state.copyWith(searchedMatches: []); + return; + } + + final filteredMatches = _matches.where((match) { + return match.teams.any((team) { + return team.team.name.toLowerCase().contains(searchKey); + }); + }).toList(); + state = state.copyWith(searchedMatches: filteredMatches); + } + + void onClear() { + state.searchController.clear(); + state = state.copyWith(searchedMatches: []); + } +} + +@freezed +class SearchHomeViewState with _$SearchHomeViewState { + const factory SearchHomeViewState({ + required TextEditingController searchController, + @Default([]) List searchedMatches, + }) = _SearchHomeViewState; +} diff --git a/khelo/lib/ui/flow/home/search/search_view_model.freezed.dart b/khelo/lib/ui/flow/home/search/search_view_model.freezed.dart new file mode 100644 index 00000000..5e204827 --- /dev/null +++ b/khelo/lib/ui/flow/home/search/search_view_model.freezed.dart @@ -0,0 +1,179 @@ +// 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 'search_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 _$SearchHomeViewState { + TextEditingController get searchController => + throw _privateConstructorUsedError; + List get searchedMatches => throw _privateConstructorUsedError; + + /// Create a copy of SearchHomeViewState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $SearchHomeViewStateCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $SearchHomeViewStateCopyWith<$Res> { + factory $SearchHomeViewStateCopyWith( + SearchHomeViewState value, $Res Function(SearchHomeViewState) then) = + _$SearchHomeViewStateCopyWithImpl<$Res, SearchHomeViewState>; + @useResult + $Res call( + {TextEditingController searchController, + List searchedMatches}); +} + +/// @nodoc +class _$SearchHomeViewStateCopyWithImpl<$Res, $Val extends SearchHomeViewState> + implements $SearchHomeViewStateCopyWith<$Res> { + _$SearchHomeViewStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of SearchHomeViewState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? searchController = null, + Object? searchedMatches = null, + }) { + return _then(_value.copyWith( + searchController: null == searchController + ? _value.searchController + : searchController // ignore: cast_nullable_to_non_nullable + as TextEditingController, + searchedMatches: null == searchedMatches + ? _value.searchedMatches + : searchedMatches // ignore: cast_nullable_to_non_nullable + as List, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$SearchHomeViewStateImplCopyWith<$Res> + implements $SearchHomeViewStateCopyWith<$Res> { + factory _$$SearchHomeViewStateImplCopyWith(_$SearchHomeViewStateImpl value, + $Res Function(_$SearchHomeViewStateImpl) then) = + __$$SearchHomeViewStateImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {TextEditingController searchController, + List searchedMatches}); +} + +/// @nodoc +class __$$SearchHomeViewStateImplCopyWithImpl<$Res> + extends _$SearchHomeViewStateCopyWithImpl<$Res, _$SearchHomeViewStateImpl> + implements _$$SearchHomeViewStateImplCopyWith<$Res> { + __$$SearchHomeViewStateImplCopyWithImpl(_$SearchHomeViewStateImpl _value, + $Res Function(_$SearchHomeViewStateImpl) _then) + : super(_value, _then); + + /// Create a copy of SearchHomeViewState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? searchController = null, + Object? searchedMatches = null, + }) { + return _then(_$SearchHomeViewStateImpl( + searchController: null == searchController + ? _value.searchController + : searchController // ignore: cast_nullable_to_non_nullable + as TextEditingController, + searchedMatches: null == searchedMatches + ? _value._searchedMatches + : searchedMatches // ignore: cast_nullable_to_non_nullable + as List, + )); + } +} + +/// @nodoc + +class _$SearchHomeViewStateImpl implements _SearchHomeViewState { + const _$SearchHomeViewStateImpl( + {required this.searchController, + final List searchedMatches = const []}) + : _searchedMatches = searchedMatches; + + @override + final TextEditingController searchController; + final List _searchedMatches; + @override + @JsonKey() + List get searchedMatches { + if (_searchedMatches is EqualUnmodifiableListView) return _searchedMatches; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_searchedMatches); + } + + @override + String toString() { + return 'SearchHomeViewState(searchController: $searchController, searchedMatches: $searchedMatches)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$SearchHomeViewStateImpl && + (identical(other.searchController, searchController) || + other.searchController == searchController) && + const DeepCollectionEquality() + .equals(other._searchedMatches, _searchedMatches)); + } + + @override + int get hashCode => Object.hash(runtimeType, searchController, + const DeepCollectionEquality().hash(_searchedMatches)); + + /// Create a copy of SearchHomeViewState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$SearchHomeViewStateImplCopyWith<_$SearchHomeViewStateImpl> get copyWith => + __$$SearchHomeViewStateImplCopyWithImpl<_$SearchHomeViewStateImpl>( + this, _$identity); +} + +abstract class _SearchHomeViewState implements SearchHomeViewState { + const factory _SearchHomeViewState( + {required final TextEditingController searchController, + final List searchedMatches}) = _$SearchHomeViewStateImpl; + + @override + TextEditingController get searchController; + @override + List get searchedMatches; + + /// Create a copy of SearchHomeViewState + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$SearchHomeViewStateImplCopyWith<_$SearchHomeViewStateImpl> get copyWith => + throw _privateConstructorUsedError; +} From a29b255ce41d3aa246533025287853e138c2996e Mon Sep 17 00:00:00 2001 From: cp-mayank-v Date: Thu, 5 Sep 2024 14:24:27 +0530 Subject: [PATCH 2/6] Minor fixes --- khelo/lib/ui/flow/home/home_view_model.dart | 2 +- khelo/lib/ui/flow/home/search/search_view_model.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/khelo/lib/ui/flow/home/home_view_model.dart b/khelo/lib/ui/flow/home/home_view_model.dart index 85c09b4b..510deab1 100644 --- a/khelo/lib/ui/flow/home/home_view_model.dart +++ b/khelo/lib/ui/flow/home/home_view_model.dart @@ -59,10 +59,10 @@ class HomeViewNotifier extends StateNotifier { final groupedMatches = groupBy(matches, (match) { switch (match.match_status) { case MatchStatus.running: - case MatchStatus.abandoned: return MatchStatusLabel.live; case MatchStatus.yetToStart: return MatchStatusLabel.upcoming; + case MatchStatus.abandoned: case MatchStatus.finish: return MatchStatusLabel.winning; } diff --git a/khelo/lib/ui/flow/home/search/search_view_model.dart b/khelo/lib/ui/flow/home/search/search_view_model.dart index d9d99a76..5cca2270 100644 --- a/khelo/lib/ui/flow/home/search/search_view_model.dart +++ b/khelo/lib/ui/flow/home/search/search_view_model.dart @@ -20,7 +20,7 @@ class SearchHomeViewNotifier extends StateNotifier { } void onChange() { - final searchKey = state.searchController.text.trim(); + final searchKey = state.searchController.text..toLowerCase().trim(); if (searchKey.isEmpty) { state = state.copyWith(searchedMatches: []); return; From cc9fe26088e7b3e42f26ca79470e05ead63a5b4e Mon Sep 17 00:00:00 2001 From: cp-mayank-v Date: Fri, 6 Sep 2024 11:44:39 +0530 Subject: [PATCH 3/6] Mr changes --- data/lib/service/match/match_service.dart | 33 ++- khelo/assets/locales/app_en.arb | 2 +- khelo/lib/components/match_detail_cell.dart | 2 +- khelo/lib/ui/app_route.dart | 11 +- .../home/components/home_view_all_screen.dart | 42 ---- khelo/lib/ui/flow/home/home_screen.dart | 48 ++--- khelo/lib/ui/flow/home/home_view_model.dart | 32 ++- .../ui/flow/home/home_view_model.freezed.dart | 74 +++---- .../flow/home/search/search_view_model.dart | 26 ++- .../home/view_all/home_view_all_screen.dart | 80 ++++++++ .../view_all/home_view_all_view_model.dart | 62 ++++++ .../home_view_all_view_model.freezed.dart | 190 ++++++++++++++++++ style/assets/fonts/SuezOne_Regular.ttf | Bin 0 -> 68120 bytes style/lib/callback/on_visible_callback.dart | 28 +++ 14 files changed, 484 insertions(+), 146 deletions(-) delete mode 100644 khelo/lib/ui/flow/home/components/home_view_all_screen.dart create mode 100644 khelo/lib/ui/flow/home/view_all/home_view_all_screen.dart create mode 100644 khelo/lib/ui/flow/home/view_all/home_view_all_view_model.dart create mode 100644 khelo/lib/ui/flow/home/view_all/home_view_all_view_model.freezed.dart create mode 100644 style/assets/fonts/SuezOne_Regular.ttf create mode 100644 style/lib/callback/on_visible_callback.dart diff --git a/data/lib/service/match/match_service.dart b/data/lib/service/match/match_service.dart index 426b2018..6dc65ad0 100644 --- a/data/lib/service/match/match_service.dart +++ b/data/lib/service/match/match_service.dart @@ -173,11 +173,8 @@ class MatchService { }).handleError((error, stack) => throw AppError.fromError(error, stack)); } - Stream> streamRunningMatches() { - return _matchCollection - .where(FireStoreConst.matchStatus, isEqualTo: MatchStatus.running.value) - .snapshots() - .asyncMap((snapshot) async { + Stream> streamMatches() { + return _matchCollection.snapshots().asyncMap((snapshot) async { return await Future.wait( snapshot.docs.map((mainDoc) async { final match = mainDoc.data(); @@ -189,6 +186,32 @@ class MatchService { }).handleError((error, stack) => throw AppError.fromError(error, stack)); } + Future> getMatchesByStatus({ + required List status, + String? lastMatchId, + int limit = 10, + }) async { + final filter = status.map((e) => e.value).toList(); + + var query = _matchCollection + .where(FireStoreConst.matchStatus, whereIn: filter) + .orderBy(FieldPath.documentId); + + if (lastMatchId != null) { + query = query.startAfter([lastMatchId]); + } + + final snapshot = await query.limit(limit).get(); + + return Future.wait( + snapshot.docs.map((doc) async { + final match = doc.data(); + final teams = await getTeamsList(match.teams); + return match.copyWith(teams: teams); + }).toList(), + ); + } + Stream streamMatchById(String id) { return _matchCollection.doc(id).snapshots().asyncMap((snapshot) async { final match = snapshot.data(); diff --git a/khelo/assets/locales/app_en.arb b/khelo/assets/locales/app_en.arb index 12f1bf31..ace1a9af 100644 --- a/khelo/assets/locales/app_en.arb +++ b/khelo/assets/locales/app_en.arb @@ -110,7 +110,7 @@ "home_screen_title": "Home", "home_screen_live_title": "Live", "home_screen_upcoming_title": "Upcoming", - "home_screen_winning_title": "Winning", + "home_screen_finished_title": "Finished", "home_screen_set_up_match_title": "Set up a match in minutes.", "home_screen_create_match_btn": "Create match", "home_screen_set_up_team_title": "Set up a team in minutes.", diff --git a/khelo/lib/components/match_detail_cell.dart b/khelo/lib/components/match_detail_cell.dart index 09f79887..8f25b560 100644 --- a/khelo/lib/components/match_detail_cell.dart +++ b/khelo/lib/components/match_detail_cell.dart @@ -118,7 +118,7 @@ class MatchDetailCell extends StatelessWidget { child: Row( children: [ ImageAvatar( - initial: matchTeam.team.name[0].toUpperCase(), + initial: matchTeam.team.name.characters.first.toUpperCase(), imageUrl: matchTeam.team.profile_img_url, size: 32, ), diff --git a/khelo/lib/ui/app_route.dart b/khelo/lib/ui/app_route.dart index de075663..0839b32b 100644 --- a/khelo/lib/ui/app_route.dart +++ b/khelo/lib/ui/app_route.dart @@ -4,6 +4,7 @@ import 'package:data/api/team/team_model.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; +import 'package:khelo/ui/flow/home/home_view_model.dart'; import 'package:khelo/ui/flow/home/search/search_screen.dart'; import 'package:khelo/ui/flow/intro/intro_screen.dart'; import 'package:khelo/ui/flow/matches/add_match/add_match_screen.dart'; @@ -21,7 +22,7 @@ import 'package:khelo/ui/flow/team/add_team_member/add_team_member_screen.dart'; import 'package:khelo/ui/flow/team/detail/make_admin/make_team_admin_screen.dart'; import 'package:khelo/ui/flow/team/detail/team_detail_screen.dart'; import 'package:khelo/ui/flow/team/search_team/search_team_screen.dart'; -import 'flow/home/components/home_view_all_screen.dart'; +import 'flow/home/view_all/home_view_all_screen.dart'; import 'flow/main/main_screen.dart'; import 'flow/settings/support/contact_support_screen.dart'; import 'flow/sign_in/sign_in_with_phone/sign_in_with_phone_screen.dart'; @@ -138,12 +139,8 @@ class AppRoute { AppRoute(pathSearchHome, builder: (_) => SearchHomeScreen(matches: matches)); - static AppRoute viewAll({ - required String title, - required List matches, - }) => - AppRoute(pathViewAll, - builder: (_) => HomeViewAllScreen(title: title, matches: matches)); + static AppRoute viewAll(MatchStatusLabel status) => + AppRoute(pathViewAll, builder: (_) => HomeViewAllScreen(status: status)); static AppRoute addTossDetail({required String matchId}) => AppRoute( pathAddTossDetail, diff --git a/khelo/lib/ui/flow/home/components/home_view_all_screen.dart b/khelo/lib/ui/flow/home/components/home_view_all_screen.dart deleted file mode 100644 index 71d1a0f9..00000000 --- a/khelo/lib/ui/flow/home/components/home_view_all_screen.dart +++ /dev/null @@ -1,42 +0,0 @@ -import 'package:data/api/match/match_model.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:khelo/components/app_page.dart'; -import 'package:khelo/components/match_detail_cell.dart'; -import 'package:khelo/ui/app_route.dart'; - -class HomeViewAllScreen extends ConsumerWidget { - final String title; - final List matches; - - const HomeViewAllScreen({ - super.key, - required this.title, - required this.matches, - }); - - @override - Widget build(BuildContext context, WidgetRef ref) { - return AppPage( - title: title, - body: Builder(builder: (context) => _body(context)), - ); - } - - Widget _body(BuildContext context) { - return ListView.separated( - padding: const EdgeInsets.all(16), - itemCount: matches.length, - itemBuilder: (context, index) { - final match = matches[index]; - return MatchDetailCell( - match: match, - onTap: () => AppRoute.matchDetailTab( - matchId: match.id ?? 'INVALID ID', - ).push(context), - ); - }, - separatorBuilder: (context, index) => const SizedBox(height: 16), - ); - } -} diff --git a/khelo/lib/ui/flow/home/home_screen.dart b/khelo/lib/ui/flow/home/home_screen.dart index 22d90086..f8473c77 100644 --- a/khelo/lib/ui/flow/home/home_screen.dart +++ b/khelo/lib/ui/flow/home/home_screen.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:khelo/components/app_page.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/domain/extensions/context_extensions.dart'; @@ -55,8 +56,8 @@ class _HomeScreenState extends ConsumerState { BlendMode.srcATop, ), ), - onPressed: () => AppRoute.searchHome(matches: state.tempMatches) - .push(context), + onPressed: () => + AppRoute.searchHome(matches: state.matches).push(context), ) ] : null, @@ -110,7 +111,11 @@ class _HomeScreenState extends ConsumerState { if (state.matches.isNotEmpty) ...[ _content(context, state) ] else ...[ - _emptyMatchView(context) + EmptyScreen( + title: context.l10n.home_screen_no_matches_title, + description: context.l10n.home_screen_no_matches_description_text, + isShowButton: false, + ), ] ], ), @@ -125,9 +130,9 @@ class _HomeScreenState extends ConsumerState { child: ListView.builder( padding: context.mediaQueryPadding + const EdgeInsets.symmetric(horizontal: 8), - itemCount: state.matches.length, + itemCount: state.groupMatches.length, itemBuilder: (context, index) { - final item = state.matches.entries.elementAt(index); + final item = state.groupMatches.entries.elementAt(index); return item.value.isNotEmpty ? Column( mainAxisSize: MainAxisSize.min, @@ -137,10 +142,7 @@ class _HomeScreenState extends ConsumerState { context, header: item.key.getString(context), isViewAllShow: item.value.length > 3, - onViewAll: () => AppRoute.viewAll( - title: item.key.getString(context), - matches: item.value) - .push(context), + onViewAll: () => AppRoute.viewAll(item.key).push(context), ), SingleChildScrollView( scrollDirection: Axis.horizontal, @@ -192,11 +194,10 @@ class _HomeScreenState extends ConsumerState { Widget _matchCell(BuildContext context, MatchModel match) { return OnTapScale( - onTap: () => AppRoute.matchDetailTab(matchId: match.id) - .push(context), + onTap: () => AppRoute.matchDetailTab(matchId: match.id).push(context), child: MediaQuery.withNoTextScaling( child: Container( - width: 360, + width: context.mediaQuerySize.width * 0.83, padding: const EdgeInsets.all(16), margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 16), decoration: BoxDecoration( @@ -292,29 +293,6 @@ class _HomeScreenState extends ConsumerState { ])); } - Widget _emptyMatchView(BuildContext context) { - return Expanded( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - context.l10n.home_screen_no_matches_title, - textAlign: TextAlign.center, - style: AppTextStyle.header2 - .copyWith(color: context.colorScheme.textPrimary), - ), - const SizedBox(height: 8), - Text( - context.l10n.home_screen_no_matches_description_text, - textAlign: TextAlign.center, - style: AppTextStyle.subtitle1 - .copyWith(color: context.colorScheme.textSecondary), - ), - ], - ), - ); - } - Widget _createActionView( BuildContext context, { required String title, diff --git a/khelo/lib/ui/flow/home/home_view_model.dart b/khelo/lib/ui/flow/home/home_view_model.dart index 510deab1..0929b3dc 100644 --- a/khelo/lib/ui/flow/home/home_view_model.dart +++ b/khelo/lib/ui/flow/home/home_view_model.dart @@ -37,12 +37,12 @@ class HomeViewNotifier extends StateNotifier { void _loadMatches() async { state = state.copyWith(loading: state.matches.isEmpty); - _streamSubscription = _matchService.getMatches().listen( + _streamSubscription = _matchService.streamMatches().listen( (matches) { final groupMatches = _groupMatches(matches); state = state.copyWith( - tempMatches: matches, - matches: groupMatches, + matches: matches, + groupMatches: groupMatches, loading: false, error: null, ); @@ -64,14 +64,15 @@ class HomeViewNotifier extends StateNotifier { return MatchStatusLabel.upcoming; case MatchStatus.abandoned: case MatchStatus.finish: - return MatchStatusLabel.winning; + return MatchStatusLabel.finished; } }); return { MatchStatusLabel.live: groupedMatches[MatchStatusLabel.live] ?? [], MatchStatusLabel.upcoming: groupedMatches[MatchStatusLabel.upcoming] ?? [], - MatchStatusLabel.winning: groupedMatches[MatchStatusLabel.winning] ?? [], + MatchStatusLabel.finished: + groupedMatches[MatchStatusLabel.finished] ?? [], }; } @@ -92,15 +93,15 @@ class HomeViewState with _$HomeViewState { const factory HomeViewState({ Object? error, @Default(false) bool loading, - @Default([]) List tempMatches, - @Default({}) Map> matches, + @Default([]) List matches, + @Default({}) Map> groupMatches, }) = _HomeViewState; } enum MatchStatusLabel { live, upcoming, - winning; + finished; String getString(BuildContext context) { switch (this) { @@ -108,8 +109,19 @@ enum MatchStatusLabel { return context.l10n.home_screen_live_title; case MatchStatusLabel.upcoming: return context.l10n.home_screen_upcoming_title; - case MatchStatusLabel.winning: - return context.l10n.home_screen_winning_title; + case MatchStatusLabel.finished: + return context.l10n.home_screen_finished_title; + } + } + + List convertStatus() { + switch (this) { + case MatchStatusLabel.live: + return [MatchStatus.running]; + case MatchStatusLabel.upcoming: + return [MatchStatus.yetToStart]; + case MatchStatusLabel.finished: + return [MatchStatus.finish, MatchStatus.abandoned]; } } } diff --git a/khelo/lib/ui/flow/home/home_view_model.freezed.dart b/khelo/lib/ui/flow/home/home_view_model.freezed.dart index 0424f2fb..e0436606 100644 --- a/khelo/lib/ui/flow/home/home_view_model.freezed.dart +++ b/khelo/lib/ui/flow/home/home_view_model.freezed.dart @@ -18,8 +18,8 @@ final _privateConstructorUsedError = UnsupportedError( mixin _$HomeViewState { Object? get error => throw _privateConstructorUsedError; bool get loading => throw _privateConstructorUsedError; - List get tempMatches => throw _privateConstructorUsedError; - Map> get matches => + List get matches => throw _privateConstructorUsedError; + Map> get groupMatches => throw _privateConstructorUsedError; /// Create a copy of HomeViewState @@ -38,8 +38,8 @@ abstract class $HomeViewStateCopyWith<$Res> { $Res call( {Object? error, bool loading, - List tempMatches, - Map> matches}); + List matches, + Map> groupMatches}); } /// @nodoc @@ -59,8 +59,8 @@ class _$HomeViewStateCopyWithImpl<$Res, $Val extends HomeViewState> $Res call({ Object? error = freezed, Object? loading = null, - Object? tempMatches = null, Object? matches = null, + Object? groupMatches = null, }) { return _then(_value.copyWith( error: freezed == error ? _value.error : error, @@ -68,13 +68,13 @@ class _$HomeViewStateCopyWithImpl<$Res, $Val extends HomeViewState> ? _value.loading : loading // ignore: cast_nullable_to_non_nullable as bool, - tempMatches: null == tempMatches - ? _value.tempMatches - : tempMatches // ignore: cast_nullable_to_non_nullable - as List, matches: null == matches ? _value.matches : matches // ignore: cast_nullable_to_non_nullable + as List, + groupMatches: null == groupMatches + ? _value.groupMatches + : groupMatches // ignore: cast_nullable_to_non_nullable as Map>, ) as $Val); } @@ -91,8 +91,8 @@ abstract class _$$HomeViewStateImplCopyWith<$Res> $Res call( {Object? error, bool loading, - List tempMatches, - Map> matches}); + List matches, + Map> groupMatches}); } /// @nodoc @@ -110,8 +110,8 @@ class __$$HomeViewStateImplCopyWithImpl<$Res> $Res call({ Object? error = freezed, Object? loading = null, - Object? tempMatches = null, Object? matches = null, + Object? groupMatches = null, }) { return _then(_$HomeViewStateImpl( error: freezed == error ? _value.error : error, @@ -119,13 +119,13 @@ class __$$HomeViewStateImplCopyWithImpl<$Res> ? _value.loading : loading // ignore: cast_nullable_to_non_nullable as bool, - tempMatches: null == tempMatches - ? _value._tempMatches - : tempMatches // ignore: cast_nullable_to_non_nullable - as List, matches: null == matches ? _value._matches : matches // ignore: cast_nullable_to_non_nullable + as List, + groupMatches: null == groupMatches + ? _value._groupMatches + : groupMatches // ignore: cast_nullable_to_non_nullable as Map>, )); } @@ -137,37 +137,37 @@ class _$HomeViewStateImpl implements _HomeViewState { const _$HomeViewStateImpl( {this.error, this.loading = false, - final List tempMatches = const [], - final Map> matches = const {}}) - : _tempMatches = tempMatches, - _matches = matches; + final List matches = const [], + final Map> groupMatches = const {}}) + : _matches = matches, + _groupMatches = groupMatches; @override final Object? error; @override @JsonKey() final bool loading; - final List _tempMatches; + final List _matches; @override @JsonKey() - List get tempMatches { - if (_tempMatches is EqualUnmodifiableListView) return _tempMatches; + List get matches { + if (_matches is EqualUnmodifiableListView) return _matches; // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_tempMatches); + return EqualUnmodifiableListView(_matches); } - final Map> _matches; + final Map> _groupMatches; @override @JsonKey() - Map> get matches { - if (_matches is EqualUnmodifiableMapView) return _matches; + Map> get groupMatches { + if (_groupMatches is EqualUnmodifiableMapView) return _groupMatches; // ignore: implicit_dynamic_type - return EqualUnmodifiableMapView(_matches); + return EqualUnmodifiableMapView(_groupMatches); } @override String toString() { - return 'HomeViewState(error: $error, loading: $loading, tempMatches: $tempMatches, matches: $matches)'; + return 'HomeViewState(error: $error, loading: $loading, matches: $matches, groupMatches: $groupMatches)'; } @override @@ -177,9 +177,9 @@ class _$HomeViewStateImpl implements _HomeViewState { other is _$HomeViewStateImpl && const DeepCollectionEquality().equals(other.error, error) && (identical(other.loading, loading) || other.loading == loading) && + const DeepCollectionEquality().equals(other._matches, _matches) && const DeepCollectionEquality() - .equals(other._tempMatches, _tempMatches) && - const DeepCollectionEquality().equals(other._matches, _matches)); + .equals(other._groupMatches, _groupMatches)); } @override @@ -187,8 +187,8 @@ class _$HomeViewStateImpl implements _HomeViewState { runtimeType, const DeepCollectionEquality().hash(error), loading, - const DeepCollectionEquality().hash(_tempMatches), - const DeepCollectionEquality().hash(_matches)); + const DeepCollectionEquality().hash(_matches), + const DeepCollectionEquality().hash(_groupMatches)); /// Create a copy of HomeViewState /// with the given fields replaced by the non-null parameter values. @@ -203,8 +203,8 @@ abstract class _HomeViewState implements HomeViewState { const factory _HomeViewState( {final Object? error, final bool loading, - final List tempMatches, - final Map> matches}) = + final List matches, + final Map> groupMatches}) = _$HomeViewStateImpl; @override @@ -212,9 +212,9 @@ abstract class _HomeViewState implements HomeViewState { @override bool get loading; @override - List get tempMatches; + List get matches; @override - Map> get matches; + Map> get groupMatches; /// Create a copy of HomeViewState /// with the given fields replaced by the non-null parameter values. diff --git a/khelo/lib/ui/flow/home/search/search_view_model.dart b/khelo/lib/ui/flow/home/search/search_view_model.dart index 5cca2270..d554a9c3 100644 --- a/khelo/lib/ui/flow/home/search/search_view_model.dart +++ b/khelo/lib/ui/flow/home/search/search_view_model.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:data/api/match/match_model.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -14,30 +16,38 @@ class SearchHomeViewNotifier extends StateNotifier { : super(SearchHomeViewState(searchController: TextEditingController())); late List _matches; + Timer? _debounce; void setData(List matches) { _matches = matches; } void onChange() { - final searchKey = state.searchController.text..toLowerCase().trim(); + final searchKey = state.searchController.text.toLowerCase().trim(); if (searchKey.isEmpty) { state = state.copyWith(searchedMatches: []); return; } - - final filteredMatches = _matches.where((match) { - return match.teams.any((team) { - return team.team.name.toLowerCase().contains(searchKey); - }); - }).toList(); - state = state.copyWith(searchedMatches: filteredMatches); + _debounce?.cancel(); + _debounce = Timer(const Duration(milliseconds: 300), () { + final filteredMatches = _matches.where((match) { + return match.teams + .any((team) => team.team.name.toLowerCase().contains(searchKey)); + }).toList(); + state = state.copyWith(searchedMatches: filteredMatches); + }); } void onClear() { state.searchController.clear(); state = state.copyWith(searchedMatches: []); } + + @override + void dispose() { + _debounce?.cancel(); + super.dispose(); + } } @freezed diff --git a/khelo/lib/ui/flow/home/view_all/home_view_all_screen.dart b/khelo/lib/ui/flow/home/view_all/home_view_all_screen.dart new file mode 100644 index 00000000..a4582d81 --- /dev/null +++ b/khelo/lib/ui/flow/home/view_all/home_view_all_screen.dart @@ -0,0 +1,80 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:khelo/components/app_page.dart'; +import 'package:khelo/components/match_detail_cell.dart'; +import 'package:khelo/domain/extensions/widget_extension.dart'; +import 'package:khelo/ui/app_route.dart'; +import 'package:khelo/ui/flow/home/home_view_model.dart'; +import 'package:khelo/ui/flow/home/view_all/home_view_all_view_model.dart'; +import 'package:style/callback/on_visible_callback.dart'; +import 'package:style/extensions/context_extensions.dart'; +import 'package:style/indicator/progress_indicator.dart'; + +import '../../../../components/error_screen.dart'; + +class HomeViewAllScreen extends ConsumerStatefulWidget { + final MatchStatusLabel status; + + const HomeViewAllScreen({ + super.key, + required this.status, + }); + + @override + ConsumerState createState() => _HomeViewAllScreenState(); +} + +class _HomeViewAllScreenState extends ConsumerState { + late HomeViewAllViewNotifier notifier; + + @override + void initState() { + super.initState(); + notifier = ref.read(homeViewAllViewProvider.notifier); + runPostFrame(() => notifier.setData(widget.status.convertStatus())); + } + + @override + Widget build(BuildContext context) { + return AppPage( + title: widget.status.getString(context), + body: Builder(builder: (context) { + return _body(context); + }), + ); + } + + Widget _body(BuildContext context) { + final state = ref.watch(homeViewAllViewProvider); + + if (state.loading) { + return const Center(child: AppProgressIndicator()); + } + if (state.error != null) { + return ErrorScreen( + error: state.error, + onRetryTap: notifier.loadMatches, + ); + } + + return ListView.separated( + padding: const EdgeInsets.all(16) + context.mediaQueryPadding, + itemCount: state.matches.length + 1, + itemBuilder: (context, index) { + if (index < state.matches.length) { + final match = state.matches[index]; + return MatchDetailCell( + match: match, + onTap: () => + AppRoute.matchDetailTab(matchId: match.id).push(context), + ); + } + return OnVisibleCallback( + onVisible: () => runPostFrame(() => notifier.loadMatches()), + child: (state.loading && state.matches.isNotEmpty) + ? const Center(child: AppProgressIndicator()) + : const SizedBox()); + }, + separatorBuilder: (context, index) => const SizedBox(height: 16)); + } +} diff --git a/khelo/lib/ui/flow/home/view_all/home_view_all_view_model.dart b/khelo/lib/ui/flow/home/view_all/home_view_all_view_model.dart new file mode 100644 index 00000000..ba6a870b --- /dev/null +++ b/khelo/lib/ui/flow/home/view_all/home_view_all_view_model.dart @@ -0,0 +1,62 @@ +import 'package:data/api/match/match_model.dart'; +import 'package:data/service/match/match_service.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'home_view_all_view_model.freezed.dart'; + +final homeViewAllViewProvider = StateNotifierProvider.autoDispose< + HomeViewAllViewNotifier, HomeViewAllViewState>( + (ref) => HomeViewAllViewNotifier(ref.read(matchServiceProvider))); + +class HomeViewAllViewNotifier extends StateNotifier { + final MatchService _matchService; + + HomeViewAllViewNotifier(this._matchService) + : super(const HomeViewAllViewState()) { + loadMatches(); + } + + List _status = []; + bool _maxLoaded = false; + String? _lastMatchId; + + void setData(List status) { + _status = status; + } + + void loadMatches() async { + if (_status.isEmpty) return; + if (state.loading || _maxLoaded) return; + try { + state = state.copyWith(loading: true); + + final matches = await _matchService.getMatchesByStatus( + status: _status, + lastMatchId: _lastMatchId, + limit: 10, + ); + + _maxLoaded = matches.length < 10; + + if (matches.isNotEmpty) { + _lastMatchId = matches.last.id; + } + state = state.copyWith( + matches: {...state.matches, ...matches}.toList(), loading: false); + } catch (error) { + state = state.copyWith(error: error, loading: false); + debugPrint("HomeViewAllViewNotifier: error while load matches -> $error"); + } + } +} + +@freezed +class HomeViewAllViewState with _$HomeViewAllViewState { + const factory HomeViewAllViewState({ + Object? error, + @Default(false) bool loading, + @Default([]) List matches, + }) = _HomeViewAllViewState; +} diff --git a/khelo/lib/ui/flow/home/view_all/home_view_all_view_model.freezed.dart b/khelo/lib/ui/flow/home/view_all/home_view_all_view_model.freezed.dart new file mode 100644 index 00000000..caa0b2e9 --- /dev/null +++ b/khelo/lib/ui/flow/home/view_all/home_view_all_view_model.freezed.dart @@ -0,0 +1,190 @@ +// 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 'home_view_all_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 _$HomeViewAllViewState { + Object? get error => throw _privateConstructorUsedError; + bool get loading => throw _privateConstructorUsedError; + List get matches => throw _privateConstructorUsedError; + + /// Create a copy of HomeViewAllViewState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $HomeViewAllViewStateCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $HomeViewAllViewStateCopyWith<$Res> { + factory $HomeViewAllViewStateCopyWith(HomeViewAllViewState value, + $Res Function(HomeViewAllViewState) then) = + _$HomeViewAllViewStateCopyWithImpl<$Res, HomeViewAllViewState>; + @useResult + $Res call({Object? error, bool loading, List matches}); +} + +/// @nodoc +class _$HomeViewAllViewStateCopyWithImpl<$Res, + $Val extends HomeViewAllViewState> + implements $HomeViewAllViewStateCopyWith<$Res> { + _$HomeViewAllViewStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of HomeViewAllViewState + /// 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 _$$HomeViewAllViewStateImplCopyWith<$Res> + implements $HomeViewAllViewStateCopyWith<$Res> { + factory _$$HomeViewAllViewStateImplCopyWith(_$HomeViewAllViewStateImpl value, + $Res Function(_$HomeViewAllViewStateImpl) then) = + __$$HomeViewAllViewStateImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({Object? error, bool loading, List matches}); +} + +/// @nodoc +class __$$HomeViewAllViewStateImplCopyWithImpl<$Res> + extends _$HomeViewAllViewStateCopyWithImpl<$Res, _$HomeViewAllViewStateImpl> + implements _$$HomeViewAllViewStateImplCopyWith<$Res> { + __$$HomeViewAllViewStateImplCopyWithImpl(_$HomeViewAllViewStateImpl _value, + $Res Function(_$HomeViewAllViewStateImpl) _then) + : super(_value, _then); + + /// Create a copy of HomeViewAllViewState + /// 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(_$HomeViewAllViewStateImpl( + 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 _$HomeViewAllViewStateImpl implements _HomeViewAllViewState { + const _$HomeViewAllViewStateImpl( + {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 'HomeViewAllViewState(error: $error, loading: $loading, matches: $matches)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$HomeViewAllViewStateImpl && + 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 HomeViewAllViewState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$HomeViewAllViewStateImplCopyWith<_$HomeViewAllViewStateImpl> + get copyWith => + __$$HomeViewAllViewStateImplCopyWithImpl<_$HomeViewAllViewStateImpl>( + this, _$identity); +} + +abstract class _HomeViewAllViewState implements HomeViewAllViewState { + const factory _HomeViewAllViewState( + {final Object? error, + final bool loading, + final List matches}) = _$HomeViewAllViewStateImpl; + + @override + Object? get error; + @override + bool get loading; + @override + List get matches; + + /// Create a copy of HomeViewAllViewState + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$HomeViewAllViewStateImplCopyWith<_$HomeViewAllViewStateImpl> + get copyWith => throw _privateConstructorUsedError; +} diff --git a/style/assets/fonts/SuezOne_Regular.ttf b/style/assets/fonts/SuezOne_Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..d1e477456692bc89e1770377073854abde7638b0 GIT binary patch literal 68120 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%0AA|1UXggYL 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 literal 0 HcmV?d00001 diff --git a/style/lib/callback/on_visible_callback.dart b/style/lib/callback/on_visible_callback.dart new file mode 100644 index 00000000..bb03a3a7 --- /dev/null +++ b/style/lib/callback/on_visible_callback.dart @@ -0,0 +1,28 @@ +import 'package:flutter/cupertino.dart'; + +class OnVisibleCallback extends StatefulWidget { + final void Function() onVisible; + final Widget child; + + const OnVisibleCallback({ + super.key, + required this.onVisible, + required this.child, + }); + + @override + State createState() => _OnCreateState(); +} + +class _OnCreateState extends State { + @override + void initState() { + widget.onVisible(); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return widget.child; + } +} \ No newline at end of file From 39741eca6a1f5308bdf7f2a39ae652e0c8280535 Mon Sep 17 00:00:00 2001 From: cp-mayank-v Date: Fri, 6 Sep 2024 14:29:43 +0530 Subject: [PATCH 4/6] Add new font style --- khelo/lib/ui/flow/home/home_screen.dart | 2 +- .../{SuezOne_Regular.ttf => suezOne_regular.ttf} | Bin style/lib/text/app_text_style.dart | 9 +++++++++ style/pubspec.yaml | 6 +++++- 4 files changed, 15 insertions(+), 2 deletions(-) rename style/assets/fonts/{SuezOne_Regular.ttf => suezOne_regular.ttf} (100%) diff --git a/khelo/lib/ui/flow/home/home_screen.dart b/khelo/lib/ui/flow/home/home_screen.dart index f8473c77..40853191 100644 --- a/khelo/lib/ui/flow/home/home_screen.dart +++ b/khelo/lib/ui/flow/home/home_screen.dart @@ -41,7 +41,7 @@ class _HomeScreenState extends ConsumerState { return AppPage( titleWidget: Text( context.l10n.app_title, - style: AppTextStyle.header1.copyWith( + style: AppTextStyle.appHeader.copyWith( color: context.colorScheme.primary, ), ), diff --git a/style/assets/fonts/SuezOne_Regular.ttf b/style/assets/fonts/suezOne_regular.ttf similarity index 100% rename from style/assets/fonts/SuezOne_Regular.ttf rename to style/assets/fonts/suezOne_regular.ttf diff --git a/style/lib/text/app_text_style.dart b/style/lib/text/app_text_style.dart index 218b6eaa..da0951e1 100644 --- a/style/lib/text/app_text_style.dart +++ b/style/lib/text/app_text_style.dart @@ -2,6 +2,15 @@ import 'package:flutter/material.dart'; class AppTextStyle { static const String poppinsFontFamily = 'Poppins'; + static const String suezOneFontFamily = 'SuezOne'; + + static const TextStyle appHeader = TextStyle( + fontFamily: suezOneFontFamily, + fontWeight: FontWeight.w400, + fontSize: 28, + letterSpacing: -0.64, + package: "style", + ); static const TextStyle header1 = TextStyle( fontFamily: poppinsFontFamily, diff --git a/style/pubspec.yaml b/style/pubspec.yaml index 4b1648d0..a44df68a 100644 --- a/style/pubspec.yaml +++ b/style/pubspec.yaml @@ -41,4 +41,8 @@ flutter: - asset: assets/fonts/poppins_semi_bold.ttf weight: 600 - asset: assets/fonts/poppins_bold.ttf - weight: 700 \ No newline at end of file + weight: 700 + + - family: SuezOne + fonts: + - asset: assets/fonts/suezOne_regular.ttf \ No newline at end of file From e6c97d2d2031448ea76eb67fb92ce2debc02911f Mon Sep 17 00:00:00 2001 From: cp-mayank-v Date: Fri, 6 Sep 2024 14:37:42 +0530 Subject: [PATCH 5/6] Fix lint --- khelo/lib/ui/flow/home/search/search_screen.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/khelo/lib/ui/flow/home/search/search_screen.dart b/khelo/lib/ui/flow/home/search/search_screen.dart index 1fbe30e0..37499cb2 100644 --- a/khelo/lib/ui/flow/home/search/search_screen.dart +++ b/khelo/lib/ui/flow/home/search/search_screen.dart @@ -124,7 +124,7 @@ class _SearchHomeScreenState extends ConsumerState { final match = matches[index - 1]; return MatchDetailCell( match: match, - onTap: () => AppRoute.matchDetailTab(matchId: match.id ?? ""), + onTap: () => AppRoute.matchDetailTab(matchId: match.id), ); }, separatorBuilder: (context, index) => const SizedBox(height: 16), From 5a43b0038f316244cc253ba91cb6ad7b13294e4f Mon Sep 17 00:00:00 2001 From: cp-mayank-v Date: Fri, 6 Sep 2024 18:41:54 +0530 Subject: [PATCH 6/6] Minor fixes --- khelo/lib/ui/flow/home/home_screen.dart | 133 +++++++++++++----------- 1 file changed, 70 insertions(+), 63 deletions(-) diff --git a/khelo/lib/ui/flow/home/home_screen.dart b/khelo/lib/ui/flow/home/home_screen.dart index 40853191..76602eec 100644 --- a/khelo/lib/ui/flow/home/home_screen.dart +++ b/khelo/lib/ui/flow/home/home_screen.dart @@ -81,44 +81,51 @@ class _HomeScreenState extends ConsumerState { onRetryTap: notifier.onResume, ); } - return Padding( - padding: const EdgeInsets.symmetric(vertical: 16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 8), - child: Row( - children: [ - _createActionView( - context, - title: context.l10n.home_screen_set_up_match_title, - btnText: context.l10n.home_screen_create_match_btn, - onTap: () => AppRoute.addMatch().push(context), - ), - _createActionView( - context, - title: context.l10n.home_screen_set_up_team_title, - btnText: context.l10n.home_screen_create_team_btn, - onTap: () => AppRoute.addTeam().push(context), - ), - ], + return ListView( + padding: + context.mediaQueryPadding + const EdgeInsets.symmetric(vertical: 16), + children: [ + _actionRow(context), + const SizedBox(height: 16), + (state.matches.isNotEmpty) + ? _content(context, state) + : SizedBox( + height: context.mediaQuerySize.height / 1.5, + child: EmptyScreen( + title: context.l10n.home_screen_no_matches_title, + description: + context.l10n.home_screen_no_matches_description_text, + isShowButton: false, + ), + ), + ], + ); + } + + Widget _actionRow(BuildContext context) { + return SizedBox( + height: 64, + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: Row( + children: [ + _createActionView( + context, + title: context.l10n.home_screen_set_up_match_title, + btnText: context.l10n.home_screen_create_match_btn, + onTap: () => AppRoute.addMatch().push(context), + ), + _createActionView( + context, + title: context.l10n.home_screen_set_up_team_title, + btnText: context.l10n.home_screen_create_team_btn, + onTap: () => AppRoute.addTeam().push(context), ), - )), - const SizedBox(height: 16), - if (state.matches.isNotEmpty) ...[ - _content(context, state) - ] else ...[ - EmptyScreen( - title: context.l10n.home_screen_no_matches_title, - description: context.l10n.home_screen_no_matches_description_text, - isShowButton: false, + ], ), - ] - ], - ), + )), ); } @@ -126,37 +133,37 @@ class _HomeScreenState extends ConsumerState { BuildContext context, HomeViewState state, ) { - return Expanded( - child: ListView.builder( - padding: context.mediaQueryPadding + - const EdgeInsets.symmetric(horizontal: 8), - itemCount: state.groupMatches.length, - itemBuilder: (context, index) { - final item = state.groupMatches.entries.elementAt(index); - return item.value.isNotEmpty - ? Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _matchHeader( - context, - header: item.key.getString(context), - isViewAllShow: item.value.length > 3, - onViewAll: () => AppRoute.viewAll(item.key).push(context), - ), - SingleChildScrollView( - scrollDirection: Axis.horizontal, + return ListView.builder( + shrinkWrap: true, + itemCount: state.groupMatches.length, + itemBuilder: (context, index) { + final item = state.groupMatches.entries.elementAt(index); + return item.value.isNotEmpty + ? Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _matchHeader( + context, + header: item.key.getString(context), + isViewAllShow: item.value.length > 3, + onViewAll: () => AppRoute.viewAll(item.key).push(context), + ), + SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), child: Row( children: item.value .map((match) => _matchCell(context, match)) .toList(), ), ), - ], - ) - : const SizedBox(); - }, - ), + ), + ], + ) + : const SizedBox(); + }, ); } @@ -167,7 +174,7 @@ class _HomeScreenState extends ConsumerState { required Function() onViewAll, }) { return Padding( - padding: const EdgeInsets.symmetric(horizontal: 8), + padding: const EdgeInsets.symmetric(horizontal: 16), child: Row( children: [ Text( @@ -199,7 +206,7 @@ class _HomeScreenState extends ConsumerState { child: Container( width: context.mediaQuerySize.width * 0.83, padding: const EdgeInsets.all(16), - margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 16), + margin: const EdgeInsets.all(8), decoration: BoxDecoration( color: context.colorScheme.containerLow, border: Border.all(color: context.colorScheme.outline),