diff --git a/lib/src/features/movies/data/movies_pagination.dart b/lib/src/features/movies/data/movies_pagination.dart deleted file mode 100644 index 65c6fb7..0000000 --- a/lib/src/features/movies/data/movies_pagination.dart +++ /dev/null @@ -1,18 +0,0 @@ -/// Metadata used when fetching movies with the paginated search API. -class MoviesPagination { - MoviesPagination({required this.page, required this.query}); - final int page; - final String query; - - @override - bool operator ==(Object other) { - if (identical(this, other)) return true; - - return other is MoviesPagination && - other.query == query && - other.page == page; - } - - @override - int get hashCode => query.hashCode ^ page.hashCode; -} diff --git a/lib/src/features/movies/data/movies_repository.dart b/lib/src/features/movies/data/movies_repository.dart index 5f1f0f2..f42d34c 100644 --- a/lib/src/features/movies/data/movies_repository.dart +++ b/lib/src/features/movies/data/movies_repository.dart @@ -2,7 +2,6 @@ import 'dart:async'; import 'package:dio/dio.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:tmdb_movie_app_riverpod/env/env.dart'; -import 'package:tmdb_movie_app_riverpod/src/features/movies/data/movies_pagination.dart'; import 'package:tmdb_movie_app_riverpod/src/features/movies/domain/tmdb_movie.dart'; import 'package:tmdb_movie_app_riverpod/src/features/movies/domain/tmdb_movies_response.dart'; import 'package:tmdb_movie_app_riverpod/src/utils/cancel_token_ref.dart'; @@ -10,13 +9,16 @@ import 'package:tmdb_movie_app_riverpod/src/utils/dio_provider.dart'; part 'movies_repository.g.dart'; +/// Metadata used when fetching movies with the paginated search API. +typedef MoviesPagination = ({String query, int page}); + class MoviesRepository { - MoviesRepository({required this.client, required this.apiKey}); + const MoviesRepository({required this.client, required this.apiKey}); final Dio client; final String apiKey; Future searchMovies( - {required int page, String query = '', CancelToken? cancelToken}) async { + {required MoviesPagination pagination, CancelToken? cancelToken}) async { final url = Uri( scheme: 'https', host: 'api.themoviedb.org', @@ -24,8 +26,8 @@ class MoviesRepository { queryParameters: { 'api_key': apiKey, 'include_adult': 'false', - 'page': '$page', - 'query': query, + 'page': '${pagination.page}', + 'query': pagination.query, }, ).toString(); final response = await client.get(url, cancelToken: cancelToken); @@ -131,8 +133,7 @@ Future fetchMovies( if (cancelToken.isCancelled) throw AbortedException(); // use search endpoint return moviesRepo.searchMovies( - page: pagination.page, - query: pagination.query, + pagination: pagination, cancelToken: cancelToken, ); } diff --git a/lib/src/features/movies/data/movies_repository.g.dart b/lib/src/features/movies/data/movies_repository.g.dart index 85cc9c2..07bf340 100644 --- a/lib/src/features/movies/data/movies_repository.g.dart +++ b/lib/src/features/movies/data/movies_repository.g.dart @@ -181,7 +181,7 @@ class _MovieProviderElement extends AutoDisposeFutureProviderElement int get movieId => (origin as MovieProvider).movieId; } -String _$fetchMoviesHash() => r'9cafe6c21952cfb7d9bb9bfdec887cc3c13a9886'; +String _$fetchMoviesHash() => r'e522dc1c22ffbf0c195b263646b5cb400ca1cd6a'; /// Provider to fetch paginated movies data /// @@ -202,7 +202,7 @@ class FetchMoviesFamily extends Family> { /// /// Copied from [fetchMovies]. FetchMoviesProvider call({ - required MoviesPagination pagination, + required ({int page, String query}) pagination, }) { return FetchMoviesProvider( pagination: pagination, @@ -242,7 +242,7 @@ class FetchMoviesProvider /// /// Copied from [fetchMovies]. FetchMoviesProvider({ - required MoviesPagination pagination, + required ({int page, String query}) pagination, }) : this._internal( (ref) => fetchMovies( ref as FetchMoviesRef, @@ -270,7 +270,7 @@ class FetchMoviesProvider required this.pagination, }) : super.internal(); - final MoviesPagination pagination; + final ({int page, String query}) pagination; @override Override overrideWith( @@ -311,7 +311,7 @@ class FetchMoviesProvider mixin FetchMoviesRef on AutoDisposeFutureProviderRef { /// The parameter `pagination` of this provider. - MoviesPagination get pagination; + ({int page, String query}) get pagination; } class _FetchMoviesProviderElement @@ -320,7 +320,8 @@ class _FetchMoviesProviderElement _FetchMoviesProviderElement(super.provider); @override - MoviesPagination get pagination => (origin as FetchMoviesProvider).pagination; + ({int page, String query}) get pagination => + (origin as FetchMoviesProvider).pagination; } // ignore_for_file: type=lint // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/src/features/movies/domain/tmdb_movie.dart b/lib/src/features/movies/domain/tmdb_movie.dart index a2a3c8e..be1fbef 100644 --- a/lib/src/features/movies/domain/tmdb_movie.dart +++ b/lib/src/features/movies/domain/tmdb_movie.dart @@ -8,19 +8,9 @@ part 'tmdb_movie.g.dart'; @freezed class TMDBMovie with _$TMDBMovie { factory TMDBMovie({ - @JsonKey(name: 'vote_count') int? voteCount, required int id, - @Default(false) bool video, - @JsonKey(name: 'vote_average') double? voteAverage, required String title, - double? popularity, @JsonKey(name: 'poster_path') String? posterPath, - @JsonKey(name: 'original_language') String? originalLanguage, - @JsonKey(name: 'original_title') String? originalTitle, - @JsonKey(name: 'genre_ids') List? genreIds, - @JsonKey(name: 'backdrop_path') String? backdropPath, - bool? adult, - String? overview, @JsonKey(name: 'release_date') String? releaseDate, }) = _TMDBMovieBasic; diff --git a/lib/src/features/movies/domain/tmdb_movie.freezed.dart b/lib/src/features/movies/domain/tmdb_movie.freezed.dart index 70d1514..2c4c28b 100644 --- a/lib/src/features/movies/domain/tmdb_movie.freezed.dart +++ b/lib/src/features/movies/domain/tmdb_movie.freezed.dart @@ -20,26 +20,10 @@ TMDBMovie _$TMDBMovieFromJson(Map json) { /// @nodoc mixin _$TMDBMovie { - @JsonKey(name: 'vote_count') - int? get voteCount => throw _privateConstructorUsedError; int get id => throw _privateConstructorUsedError; - bool get video => throw _privateConstructorUsedError; - @JsonKey(name: 'vote_average') - double? get voteAverage => throw _privateConstructorUsedError; String get title => throw _privateConstructorUsedError; - double? get popularity => throw _privateConstructorUsedError; @JsonKey(name: 'poster_path') String? get posterPath => throw _privateConstructorUsedError; - @JsonKey(name: 'original_language') - String? get originalLanguage => throw _privateConstructorUsedError; - @JsonKey(name: 'original_title') - String? get originalTitle => throw _privateConstructorUsedError; - @JsonKey(name: 'genre_ids') - List? get genreIds => throw _privateConstructorUsedError; - @JsonKey(name: 'backdrop_path') - String? get backdropPath => throw _privateConstructorUsedError; - bool? get adult => throw _privateConstructorUsedError; - String? get overview => throw _privateConstructorUsedError; @JsonKey(name: 'release_date') String? get releaseDate => throw _privateConstructorUsedError; @@ -55,19 +39,9 @@ abstract class $TMDBMovieCopyWith<$Res> { _$TMDBMovieCopyWithImpl<$Res, TMDBMovie>; @useResult $Res call( - {@JsonKey(name: 'vote_count') int? voteCount, - int id, - bool video, - @JsonKey(name: 'vote_average') double? voteAverage, + {int id, String title, - double? popularity, @JsonKey(name: 'poster_path') String? posterPath, - @JsonKey(name: 'original_language') String? originalLanguage, - @JsonKey(name: 'original_title') String? originalTitle, - @JsonKey(name: 'genre_ids') List? genreIds, - @JsonKey(name: 'backdrop_path') String? backdropPath, - bool? adult, - String? overview, @JsonKey(name: 'release_date') String? releaseDate}); } @@ -84,74 +58,24 @@ class _$TMDBMovieCopyWithImpl<$Res, $Val extends TMDBMovie> @pragma('vm:prefer-inline') @override $Res call({ - Object? voteCount = freezed, Object? id = null, - Object? video = null, - Object? voteAverage = freezed, Object? title = null, - Object? popularity = freezed, Object? posterPath = freezed, - Object? originalLanguage = freezed, - Object? originalTitle = freezed, - Object? genreIds = freezed, - Object? backdropPath = freezed, - Object? adult = freezed, - Object? overview = freezed, Object? releaseDate = freezed, }) { return _then(_value.copyWith( - voteCount: freezed == voteCount - ? _value.voteCount - : voteCount // ignore: cast_nullable_to_non_nullable - as int?, id: null == id ? _value.id : id // ignore: cast_nullable_to_non_nullable as int, - video: null == video - ? _value.video - : video // ignore: cast_nullable_to_non_nullable - as bool, - voteAverage: freezed == voteAverage - ? _value.voteAverage - : voteAverage // ignore: cast_nullable_to_non_nullable - as double?, title: null == title ? _value.title : title // ignore: cast_nullable_to_non_nullable as String, - popularity: freezed == popularity - ? _value.popularity - : popularity // ignore: cast_nullable_to_non_nullable - as double?, posterPath: freezed == posterPath ? _value.posterPath : posterPath // ignore: cast_nullable_to_non_nullable as String?, - originalLanguage: freezed == originalLanguage - ? _value.originalLanguage - : originalLanguage // ignore: cast_nullable_to_non_nullable - as String?, - originalTitle: freezed == originalTitle - ? _value.originalTitle - : originalTitle // ignore: cast_nullable_to_non_nullable - as String?, - genreIds: freezed == genreIds - ? _value.genreIds - : genreIds // ignore: cast_nullable_to_non_nullable - as List?, - backdropPath: freezed == backdropPath - ? _value.backdropPath - : backdropPath // ignore: cast_nullable_to_non_nullable - as String?, - adult: freezed == adult - ? _value.adult - : adult // ignore: cast_nullable_to_non_nullable - as bool?, - overview: freezed == overview - ? _value.overview - : overview // ignore: cast_nullable_to_non_nullable - as String?, releaseDate: freezed == releaseDate ? _value.releaseDate : releaseDate // ignore: cast_nullable_to_non_nullable @@ -169,19 +93,9 @@ abstract class _$$TMDBMovieBasicImplCopyWith<$Res> @override @useResult $Res call( - {@JsonKey(name: 'vote_count') int? voteCount, - int id, - bool video, - @JsonKey(name: 'vote_average') double? voteAverage, + {int id, String title, - double? popularity, @JsonKey(name: 'poster_path') String? posterPath, - @JsonKey(name: 'original_language') String? originalLanguage, - @JsonKey(name: 'original_title') String? originalTitle, - @JsonKey(name: 'genre_ids') List? genreIds, - @JsonKey(name: 'backdrop_path') String? backdropPath, - bool? adult, - String? overview, @JsonKey(name: 'release_date') String? releaseDate}); } @@ -196,74 +110,24 @@ class __$$TMDBMovieBasicImplCopyWithImpl<$Res> @pragma('vm:prefer-inline') @override $Res call({ - Object? voteCount = freezed, Object? id = null, - Object? video = null, - Object? voteAverage = freezed, Object? title = null, - Object? popularity = freezed, Object? posterPath = freezed, - Object? originalLanguage = freezed, - Object? originalTitle = freezed, - Object? genreIds = freezed, - Object? backdropPath = freezed, - Object? adult = freezed, - Object? overview = freezed, Object? releaseDate = freezed, }) { return _then(_$TMDBMovieBasicImpl( - voteCount: freezed == voteCount - ? _value.voteCount - : voteCount // ignore: cast_nullable_to_non_nullable - as int?, id: null == id ? _value.id : id // ignore: cast_nullable_to_non_nullable as int, - video: null == video - ? _value.video - : video // ignore: cast_nullable_to_non_nullable - as bool, - voteAverage: freezed == voteAverage - ? _value.voteAverage - : voteAverage // ignore: cast_nullable_to_non_nullable - as double?, title: null == title ? _value.title : title // ignore: cast_nullable_to_non_nullable as String, - popularity: freezed == popularity - ? _value.popularity - : popularity // ignore: cast_nullable_to_non_nullable - as double?, posterPath: freezed == posterPath ? _value.posterPath : posterPath // ignore: cast_nullable_to_non_nullable as String?, - originalLanguage: freezed == originalLanguage - ? _value.originalLanguage - : originalLanguage // ignore: cast_nullable_to_non_nullable - as String?, - originalTitle: freezed == originalTitle - ? _value.originalTitle - : originalTitle // ignore: cast_nullable_to_non_nullable - as String?, - genreIds: freezed == genreIds - ? _value._genreIds - : genreIds // ignore: cast_nullable_to_non_nullable - as List?, - backdropPath: freezed == backdropPath - ? _value.backdropPath - : backdropPath // ignore: cast_nullable_to_non_nullable - as String?, - adult: freezed == adult - ? _value.adult - : adult // ignore: cast_nullable_to_non_nullable - as bool?, - overview: freezed == overview - ? _value.overview - : overview // ignore: cast_nullable_to_non_nullable - as String?, releaseDate: freezed == releaseDate ? _value.releaseDate : releaseDate // ignore: cast_nullable_to_non_nullable @@ -276,74 +140,28 @@ class __$$TMDBMovieBasicImplCopyWithImpl<$Res> @JsonSerializable() class _$TMDBMovieBasicImpl implements _TMDBMovieBasic { _$TMDBMovieBasicImpl( - {@JsonKey(name: 'vote_count') this.voteCount, - required this.id, - this.video = false, - @JsonKey(name: 'vote_average') this.voteAverage, + {required this.id, required this.title, - this.popularity, @JsonKey(name: 'poster_path') this.posterPath, - @JsonKey(name: 'original_language') this.originalLanguage, - @JsonKey(name: 'original_title') this.originalTitle, - @JsonKey(name: 'genre_ids') final List? genreIds, - @JsonKey(name: 'backdrop_path') this.backdropPath, - this.adult, - this.overview, - @JsonKey(name: 'release_date') this.releaseDate}) - : _genreIds = genreIds; + @JsonKey(name: 'release_date') this.releaseDate}); factory _$TMDBMovieBasicImpl.fromJson(Map json) => _$$TMDBMovieBasicImplFromJson(json); - @override - @JsonKey(name: 'vote_count') - final int? voteCount; @override final int id; @override - @JsonKey() - final bool video; - @override - @JsonKey(name: 'vote_average') - final double? voteAverage; - @override final String title; @override - final double? popularity; - @override @JsonKey(name: 'poster_path') final String? posterPath; @override - @JsonKey(name: 'original_language') - final String? originalLanguage; - @override - @JsonKey(name: 'original_title') - final String? originalTitle; - final List? _genreIds; - @override - @JsonKey(name: 'genre_ids') - List? get genreIds { - final value = _genreIds; - if (value == null) return null; - if (_genreIds is EqualUnmodifiableListView) return _genreIds; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(value); - } - - @override - @JsonKey(name: 'backdrop_path') - final String? backdropPath; - @override - final bool? adult; - @override - final String? overview; - @override @JsonKey(name: 'release_date') final String? releaseDate; @override String toString() { - return 'TMDBMovie(voteCount: $voteCount, id: $id, video: $video, voteAverage: $voteAverage, title: $title, popularity: $popularity, posterPath: $posterPath, originalLanguage: $originalLanguage, originalTitle: $originalTitle, genreIds: $genreIds, backdropPath: $backdropPath, adult: $adult, overview: $overview, releaseDate: $releaseDate)'; + return 'TMDBMovie(id: $id, title: $title, posterPath: $posterPath, releaseDate: $releaseDate)'; } @override @@ -351,49 +169,18 @@ class _$TMDBMovieBasicImpl implements _TMDBMovieBasic { return identical(this, other) || (other.runtimeType == runtimeType && other is _$TMDBMovieBasicImpl && - (identical(other.voteCount, voteCount) || - other.voteCount == voteCount) && (identical(other.id, id) || other.id == id) && - (identical(other.video, video) || other.video == video) && - (identical(other.voteAverage, voteAverage) || - other.voteAverage == voteAverage) && (identical(other.title, title) || other.title == title) && - (identical(other.popularity, popularity) || - other.popularity == popularity) && (identical(other.posterPath, posterPath) || other.posterPath == posterPath) && - (identical(other.originalLanguage, originalLanguage) || - other.originalLanguage == originalLanguage) && - (identical(other.originalTitle, originalTitle) || - other.originalTitle == originalTitle) && - const DeepCollectionEquality().equals(other._genreIds, _genreIds) && - (identical(other.backdropPath, backdropPath) || - other.backdropPath == backdropPath) && - (identical(other.adult, adult) || other.adult == adult) && - (identical(other.overview, overview) || - other.overview == overview) && (identical(other.releaseDate, releaseDate) || other.releaseDate == releaseDate)); } @JsonKey(ignore: true) @override - int get hashCode => Object.hash( - runtimeType, - voteCount, - id, - video, - voteAverage, - title, - popularity, - posterPath, - originalLanguage, - originalTitle, - const DeepCollectionEquality().hash(_genreIds), - backdropPath, - adult, - overview, - releaseDate); + int get hashCode => + Object.hash(runtimeType, id, title, posterPath, releaseDate); @JsonKey(ignore: true) @override @@ -412,59 +199,23 @@ class _$TMDBMovieBasicImpl implements _TMDBMovieBasic { abstract class _TMDBMovieBasic implements TMDBMovie { factory _TMDBMovieBasic( - {@JsonKey(name: 'vote_count') final int? voteCount, - required final int id, - final bool video, - @JsonKey(name: 'vote_average') final double? voteAverage, + {required final int id, required final String title, - final double? popularity, @JsonKey(name: 'poster_path') final String? posterPath, - @JsonKey(name: 'original_language') final String? originalLanguage, - @JsonKey(name: 'original_title') final String? originalTitle, - @JsonKey(name: 'genre_ids') final List? genreIds, - @JsonKey(name: 'backdrop_path') final String? backdropPath, - final bool? adult, - final String? overview, @JsonKey(name: 'release_date') final String? releaseDate}) = _$TMDBMovieBasicImpl; factory _TMDBMovieBasic.fromJson(Map json) = _$TMDBMovieBasicImpl.fromJson; - @override - @JsonKey(name: 'vote_count') - int? get voteCount; @override int get id; @override - bool get video; - @override - @JsonKey(name: 'vote_average') - double? get voteAverage; - @override String get title; @override - double? get popularity; - @override @JsonKey(name: 'poster_path') String? get posterPath; @override - @JsonKey(name: 'original_language') - String? get originalLanguage; - @override - @JsonKey(name: 'original_title') - String? get originalTitle; - @override - @JsonKey(name: 'genre_ids') - List? get genreIds; - @override - @JsonKey(name: 'backdrop_path') - String? get backdropPath; - @override - bool? get adult; - @override - String? get overview; - @override @JsonKey(name: 'release_date') String? get releaseDate; @override diff --git a/lib/src/features/movies/domain/tmdb_movie.g.dart b/lib/src/features/movies/domain/tmdb_movie.g.dart index 057a197..6122610 100644 --- a/lib/src/features/movies/domain/tmdb_movie.g.dart +++ b/lib/src/features/movies/domain/tmdb_movie.g.dart @@ -8,38 +8,17 @@ part of 'tmdb_movie.dart'; _$TMDBMovieBasicImpl _$$TMDBMovieBasicImplFromJson(Map json) => _$TMDBMovieBasicImpl( - voteCount: json['vote_count'] as int?, id: json['id'] as int, - video: json['video'] as bool? ?? false, - voteAverage: (json['vote_average'] as num?)?.toDouble(), title: json['title'] as String, - popularity: (json['popularity'] as num?)?.toDouble(), posterPath: json['poster_path'] as String?, - originalLanguage: json['original_language'] as String?, - originalTitle: json['original_title'] as String?, - genreIds: - (json['genre_ids'] as List?)?.map((e) => e as int).toList(), - backdropPath: json['backdrop_path'] as String?, - adult: json['adult'] as bool?, - overview: json['overview'] as String?, releaseDate: json['release_date'] as String?, ); Map _$$TMDBMovieBasicImplToJson( _$TMDBMovieBasicImpl instance) => { - 'vote_count': instance.voteCount, 'id': instance.id, - 'video': instance.video, - 'vote_average': instance.voteAverage, 'title': instance.title, - 'popularity': instance.popularity, 'poster_path': instance.posterPath, - 'original_language': instance.originalLanguage, - 'original_title': instance.originalTitle, - 'genre_ids': instance.genreIds, - 'backdrop_path': instance.backdropPath, - 'adult': instance.adult, - 'overview': instance.overview, 'release_date': instance.releaseDate, }; diff --git a/lib/src/features/movies/presentation/movies/movie_list_tile.dart b/lib/src/features/movies/presentation/movies/movie_list_tile.dart index e30418f..691f296 100644 --- a/lib/src/features/movies/presentation/movies/movie_list_tile.dart +++ b/lib/src/features/movies/presentation/movies/movie_list_tile.dart @@ -53,11 +53,14 @@ class MovieListTile extends StatelessWidget { children: [ Text( movie.title, - style: Theme.of(context).textTheme.titleLarge, + style: Theme.of(context).textTheme.titleMedium, ), if (movie.releaseDate != null) ...[ const SizedBox(height: 8), - Text('Released: ${movie.releaseDate}'), + Text( + 'Released: ${movie.releaseDate}', + style: Theme.of(context).textTheme.bodyMedium, + ), ], ], ), diff --git a/lib/src/features/movies/presentation/movies/movies_search_bar.dart b/lib/src/features/movies/presentation/movies/movies_search_bar.dart index d9688ac..68342d5 100644 --- a/lib/src/features/movies/presentation/movies/movies_search_bar.dart +++ b/lib/src/features/movies/presentation/movies/movies_search_bar.dart @@ -1,11 +1,22 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; -// StateProvider that can be watched by other widgets to obtain the -// current search query. -final moviesSearchTextProvider = StateProvider((ref) { - return ''; -}); +part 'movies_search_bar.g.dart'; + +/// Notifier that can be watched to obtain the current search query. +@riverpod +class MoviesSearchQueryNotifier extends _$MoviesSearchQueryNotifier { + @override + String build() { + // by default, return an empty query + return ''; + } + + void setQuery(String query) { + state = query; + } +} class MoviesSearchBar extends ConsumerStatefulWidget { const MoviesSearchBar({super.key}); @@ -56,8 +67,8 @@ class _SearchBarState extends ConsumerState { FocusManager.instance.primaryFocus?.unfocus(); }, onChanged: (text) => ref - .read(moviesSearchTextProvider.notifier) - .state = text, + .read(moviesSearchQueryNotifierProvider.notifier) + .setQuery(text), ), ), ), diff --git a/lib/src/features/movies/presentation/movies/movies_search_bar.g.dart b/lib/src/features/movies/presentation/movies/movies_search_bar.g.dart new file mode 100644 index 0000000..4611ab8 --- /dev/null +++ b/lib/src/features/movies/presentation/movies/movies_search_bar.g.dart @@ -0,0 +1,29 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'movies_search_bar.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$moviesSearchQueryNotifierHash() => + r'a134a9096c00cf385f45da7a6a16589c76c017ad'; + +/// Notifier that can be watched to obtain the current search query. +/// +/// Copied from [MoviesSearchQueryNotifier]. +@ProviderFor(MoviesSearchQueryNotifier) +final moviesSearchQueryNotifierProvider = + AutoDisposeNotifierProvider.internal( + MoviesSearchQueryNotifier.new, + name: r'moviesSearchQueryNotifierProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$moviesSearchQueryNotifierHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$MoviesSearchQueryNotifier = AutoDisposeNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/src/features/movies/presentation/movies/movies_search_screen.dart b/lib/src/features/movies/presentation/movies/movies_search_screen.dart index e9c597c..574936c 100644 --- a/lib/src/features/movies/presentation/movies/movies_search_screen.dart +++ b/lib/src/features/movies/presentation/movies/movies_search_screen.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; -import 'package:tmdb_movie_app_riverpod/src/features/movies/data/movies_pagination.dart'; import 'package:tmdb_movie_app_riverpod/src/features/movies/data/movies_repository.dart'; import 'package:tmdb_movie_app_riverpod/src/features/movies/presentation/movies/movie_list_tile.dart'; import 'package:tmdb_movie_app_riverpod/src/features/movies/presentation/movies/movie_list_tile_shimmer.dart'; @@ -15,12 +14,12 @@ class MoviesSearchScreen extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final query = ref.watch(moviesSearchTextProvider); + final query = ref.watch(moviesSearchQueryNotifierProvider); // * get the first page so we can retrieve the total number of results - final moviesResponse = ref.watch( - fetchMoviesProvider(pagination: MoviesPagination(page: 1, query: query)), + final responseAsync = ref.watch( + fetchMoviesProvider(pagination: (page: 1, query: query)), ); - final totalResults = moviesResponse.valueOrNull?.totalResults; + final totalResults = responseAsync.valueOrNull?.totalResults; return Scaffold( appBar: AppBar( title: const Text('TMDB Movies'), @@ -36,54 +35,52 @@ class MoviesSearchScreen extends ConsumerWidget { // keep showing the progress indicator until the first page is fetched return ref.read( fetchMoviesProvider( - pagination: MoviesPagination(page: 1, query: query), + pagination: (page: 1, query: query), ).future, ); }, - child: ListView.custom( - childrenDelegate: SliverChildBuilderDelegate( - (context, index) { - final page = index ~/ pageSize + 1; - final indexInPage = index % pageSize; - // use the fact that this is an infinite list to fetch a new page - // as soon as the index exceeds the page size - // Note that ref.watch is called for up to pageSize items - // with the same page and query arguments (but this is ok since data is cached) - final moviesResponse = ref.watch( - fetchMoviesProvider( - pagination: - MoviesPagination(page: page, query: query)), - ); - return moviesResponse.when( - // * Only show error on the first item of the page - error: (err, stack) => indexInPage == 0 - ? Padding( - padding: const EdgeInsets.all(16.0), - child: Text(err.toString()), - ) - : const SizedBox.shrink(), - loading: () => const MovieListTileShimmer(), - data: (movies) { - // * This condition should not happen if a non-null - // * childCount is given - if (indexInPage >= movies.results.length) { - return null; - } - final movie = movies.results[indexInPage]; - return MovieListTile( - movie: movie, - debugIndex: index, - onPressed: () => context.goNamed( - AppRoute.movie.name, - pathParameters: {'id': movie.id.toString()}, - extra: movie, - ), - ); - }, - ); - }, - childCount: totalResults, - ), + child: ListView.builder( + // * pass the itemCount explicitly to prevent unnecessary renders + // * during overscroll + itemCount: totalResults, + itemBuilder: (context, index) { + final page = index ~/ pageSize + 1; + final indexInPage = index % pageSize; + // use the fact that this is an infinite list to fetch a new page + // as soon as the index exceeds the page size + // Note that ref.watch is called for up to pageSize items + // with the same page and query arguments (but this is ok since data is cached) + final responseAsync = ref.watch( + fetchMoviesProvider(pagination: (page: page, query: query)), + ); + return responseAsync.when( + // * Only show error on the first item of the page + error: (err, stack) => indexInPage == 0 + ? Padding( + padding: const EdgeInsets.all(16.0), + child: Text(err.toString()), + ) + : const SizedBox.shrink(), + loading: () => const MovieListTileShimmer(), + data: (response) { + //log('index: $index, page: $page, indexInPage: $indexInPage, len: ${response.results.length}'); + // * This condition only happens if a null itemCount is given + if (indexInPage >= response.results.length) { + return null; + } + final movie = response.results[indexInPage]; + return MovieListTile( + movie: movie, + debugIndex: index, + onPressed: () => context.goNamed( + AppRoute.movie.name, + pathParameters: {'id': movie.id.toString()}, + extra: movie, + ), + ); + }, + ); + }, ), ), ),