diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml new file mode 100644 index 0000000..bbf778f --- /dev/null +++ b/.github/dependabot.yaml @@ -0,0 +1,12 @@ +version: 2 +updates: + - package-ecosystem: "pub" + directory: "/" + schedule: + interval: "weekly" + open-pull-requests-limit: 10 + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" \ No newline at end of file diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..f07b66c --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,40 @@ +name: pull_request_template.md +about: Template for pull requests +title: '[FEATURE] ' +labels: '' +assignees: '' + +--- + +## Description + +Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. + +Fixes # (issue) + +## Type of change + +Please delete options that are not relevant. + +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) +- [ ] This change requires a documentation update + +## How Has This Been Tested? + +Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. + +- [ ] Test A +- [ ] Test B + +## Checklist: + +- [ ] My code follows the style guidelines of this project +- [ ] I have performed a self-review of my own code +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] I have made corresponding changes to the documentation +- [ ] My changes generate no new warnings +- [ ] I have added tests that prove my fix is effective or that my feature works +- [ ] New and existing unit tests pass locally with my changes +- [ ] Any dependent changes have been merged and published in downstream modules \ No newline at end of file diff --git a/.github/workflows/code_quality.yaml b/.github/workflows/code_quality.yaml new file mode 100644 index 0000000..38939a8 --- /dev/null +++ b/.github/workflows/code_quality.yaml @@ -0,0 +1,37 @@ +name: Code Quality + +on: + pull_request: + branches: + - main + - develop + +jobs: + code_quality: + name: Code Quality Checks + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Flutter + uses: subosito/flutter-action@v2 + with: + flutter-version: '3.35.4' + channel: 'stable' + cache: true + + - name: Get dependencies + run: flutter pub get + + - name: Check Dart code formatting + run: dart format --output=none --set-exit-if-changed . + + - name: Analyze project + run: flutter analyze + + - name: Run tests with coverage + run: flutter test --coverage + + - name: Check for build warnings + run: flutter build apk --debug --quiet \ No newline at end of file diff --git a/.github/workflows/flutter_ci.yaml b/.github/workflows/flutter_ci.yaml new file mode 100644 index 0000000..bae65cd --- /dev/null +++ b/.github/workflows/flutter_ci.yaml @@ -0,0 +1,126 @@ +name: Flutter CI + +on: + push: + branches: + - main + - develop + pull_request: + branches: + - main + - develop + +jobs: + flutter_test: + name: Flutter Test and Analyze + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Java + uses: actions/setup-java@v4 + with: + distribution: 'zulu' + java-version: '17' + + - name: Setup Flutter + uses: subosito/flutter-action@v2 + with: + flutter-version: '3.35.4' + channel: 'stable' + cache: true + + - name: Get dependencies + run: flutter pub get + + - name: Check format + run: dart format --output=none --set-exit-if-changed . + + - name: Analyze + run: flutter analyze + + - name: Run tests + run: flutter test --coverage + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} + file: coverage/lcov.info + + build_android: + name: Build Android App + runs-on: ubuntu-latest + needs: flutter_test + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Java + uses: actions/setup-java@v4 + with: + distribution: 'zulu' + java-version: '17' + + - name: Setup Flutter + uses: subosito/flutter-action@v2 + with: + flutter-version: '3.35.4' + channel: 'stable' + cache: true + + - name: Get dependencies + run: flutter pub get + + - name: Build APK + run: flutter build apk --release + + - name: Build App Bundle + run: flutter build appbundle --release + + - name: Upload APK as artifact + uses: actions/upload-artifact@v4 + with: + name: release-apk + path: build/app/outputs/flutter-apk/app-release.apk + + - name: Upload App Bundle as artifact + uses: actions/upload-artifact@v4 + with: + name: release-appbundle + path: build/app/outputs/bundle/release/app-release.aab + + build_ios: + name: Build iOS App + runs-on: macos-latest + needs: flutter_test + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Flutter + uses: subosito/flutter-action@v2 + with: + flutter-version: '3.35.4' + channel: 'stable' + cache: true + + - name: Get dependencies + run: flutter pub get + + - name: Build iOS + run: flutter build ios --release --no-codesign + + - name: Create IPA + run: | + cd build/ios/iphoneos + mkdir Payload + cp -r Runner.app Payload/ + zip -r app-release.ipa Payload/ + + - name: Upload IPA as artifact + uses: actions/upload-artifact@v4 + with: + name: release-ipa + path: build/ios/iphoneos/app-release.ipa \ No newline at end of file diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..d7cf021 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,73 @@ +name: Release + +on: + push: + tags: + - 'v*' + +jobs: + create_release: + name: Create Release + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Create Release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ github.ref }} + release_name: Release ${{ github.ref }} + draft: false + prerelease: false + + build_and_upload_assets: + name: Build and Upload Release Assets + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Java + uses: actions/setup-java@v4 + with: + distribution: 'zulu' + java-version: '17' + + - name: Setup Flutter + uses: subosito/flutter-action@v2 + with: + flutter-version: '3.35.4' + channel: 'stable' + cache: true + + - name: Get dependencies + run: flutter pub get + + - name: Build APK + run: flutter build apk --release + + - name: Build App Bundle + run: flutter build appbundle --release + + - name: Upload APK to Release + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: build/app/outputs/flutter-apk/app-release.apk + asset_name: app-release.apk + asset_content_type: application/vnd.android.package-archive + + - name: Upload App Bundle to Release + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: build/app/outputs/bundle/release/app-release.aab + asset_name: app-release.aab + asset_content_type: application/x-authorware-bin \ No newline at end of file diff --git a/analysis_options.yaml b/analysis_options.yaml index 0d29021..2bbfd7b 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -24,5 +24,9 @@ linter: # avoid_print: false # Uncomment to disable the `avoid_print` rule # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule +analyzer: + errors: + invalid_annotation_target: ignore + # Additional information about this file can be found at # https://dart.dev/guides/language/analysis-options diff --git a/lib/core/config/app_constant.dart b/lib/core/config/app_constant.dart index d246936..8a0973e 100644 --- a/lib/core/config/app_constant.dart +++ b/lib/core/config/app_constant.dart @@ -1,3 +1,3 @@ -const BASE_URL= "https://api.themoviedb.org/3/"; -const API_KEY ="59cd6896d8432f9c69aed9b86b9c2931"; -const IMAGE_URL = "https://image.tmdb.org/t/p/w342/"; \ No newline at end of file +const baseUrl = "https://api.themoviedb.org/3/"; +const apiKey = "59cd6896d8432f9c69aed9b86b9c2931"; +const imageUrl = "https://image.tmdb.org/t/p/w342/"; diff --git a/lib/core/hive/favorite_model.dart b/lib/core/hive/favorite_model.dart index d8320e3..ad9cecf 100644 --- a/lib/core/hive/favorite_model.dart +++ b/lib/core/hive/favorite_model.dart @@ -34,4 +34,4 @@ class Favorite extends HiveObject { this.overview, this.releaseDate, }); -} \ No newline at end of file +} diff --git a/lib/core/hive/hive_helper.dart b/lib/core/hive/hive_helper.dart index 26dbd63..fb5d6a9 100644 --- a/lib/core/hive/hive_helper.dart +++ b/lib/core/hive/hive_helper.dart @@ -1,4 +1,3 @@ -import 'package:flutter/foundation.dart'; import 'package:hive_flutter/hive_flutter.dart'; import 'package:flutter_movie_clean_architecture/core/hive/favorite_model.dart'; @@ -9,10 +8,10 @@ class HiveHelper { static Future init() async { // Initialize Hive await Hive.initFlutter(); - + // Register adapter Hive.registerAdapter(FavoriteAdapter()); - + // Open box _box = await Hive.openBox(_boxName); } @@ -58,4 +57,4 @@ class HiveHelper { static Future close() async { await _box?.close(); } -} \ No newline at end of file +} diff --git a/lib/core/network/dio_provider.dart b/lib/core/network/dio_provider.dart index edbcd90..fae01a4 100644 --- a/lib/core/network/dio_provider.dart +++ b/lib/core/network/dio_provider.dart @@ -4,20 +4,19 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:pretty_dio_logger/pretty_dio_logger.dart'; final dioProvider = Provider((ref) { - final dio = Dio(BaseOptions( - baseUrl: BASE_URL, - queryParameters: { - 'api_key': API_KEY, - }, - )); - dio.interceptors.add(PrettyDioLogger( - requestHeader: true, - requestBody: true, - responseBody: true, - responseHeader: false, - error: true, - compact: true, - maxWidth: 90, - )); + final dio = Dio( + BaseOptions(baseUrl: baseUrl, queryParameters: {'api_key': apiKey}), + ); + dio.interceptors.add( + PrettyDioLogger( + requestHeader: true, + requestBody: true, + responseBody: true, + responseHeader: false, + error: true, + compact: true, + maxWidth: 90, + ), + ); return dio; }); diff --git a/lib/core/utils/pagination_consumer_state.dart b/lib/core/utils/pagination_consumer_state.dart index a8f871e..3a1d2d8 100644 --- a/lib/core/utils/pagination_consumer_state.dart +++ b/lib/core/utils/pagination_consumer_state.dart @@ -186,4 +186,4 @@ abstract class PaginationConsumerState ], ); } -} \ No newline at end of file +} diff --git a/lib/core/utils/utils.dart b/lib/core/utils/utils.dart index a811835..51f5e9b 100644 --- a/lib/core/utils/utils.dart +++ b/lib/core/utils/utils.dart @@ -8,8 +8,9 @@ String formatTvDuration(List? episodeRunTime) { if (episodeRunTime == null || episodeRunTime.isEmpty) { return 'N/A'; } - + // For TV series, we'll show the average episode runtime - final avgRuntime = episodeRunTime.reduce((a, b) => a + b) ~/ episodeRunTime.length; + final avgRuntime = + episodeRunTime.reduce((a, b) => a + b) ~/ episodeRunTime.length; return '${avgRuntime}m'; -} \ No newline at end of file +} diff --git a/lib/features/celebrity/data/datasources/celebrity_remote_data_source.dart b/lib/features/celebrity/data/datasources/celebrity_remote_data_source.dart index 0b5de9f..a978066 100644 --- a/lib/features/celebrity/data/datasources/celebrity_remote_data_source.dart +++ b/lib/features/celebrity/data/datasources/celebrity_remote_data_source.dart @@ -36,16 +36,15 @@ class CelebrityRemoteDataSourceImpl implements CelebrityRemoteDataSource { Future searchPersons(String query, int page) async { final response = await dio.get( 'search/person', - queryParameters: { - 'query': query, - 'page': page, - }, + queryParameters: {'query': query, 'page': page}, ); return PersonListResponse.fromJson(response.data as Map); } } -final celebrityRemoteDataSourceProvider = Provider((ref) { +final celebrityRemoteDataSourceProvider = Provider(( + ref, +) { final dio = ref.watch(dioProvider); return CelebrityRemoteDataSourceImpl(dio); -}); \ No newline at end of file +}); diff --git a/lib/features/celebrity/data/models/person_list_response.dart b/lib/features/celebrity/data/models/person_list_response.dart index 4ff34be..8e29e2c 100644 --- a/lib/features/celebrity/data/models/person_list_response.dart +++ b/lib/features/celebrity/data/models/person_list_response.dart @@ -15,4 +15,4 @@ class PersonListResponse with _$PersonListResponse { factory PersonListResponse.fromJson(Map json) => _$PersonListResponseFromJson(json); -} \ No newline at end of file +} diff --git a/lib/features/celebrity/data/models/person_model.dart b/lib/features/celebrity/data/models/person_model.dart index 179dc79..3c73389 100644 --- a/lib/features/celebrity/data/models/person_model.dart +++ b/lib/features/celebrity/data/models/person_model.dart @@ -17,4 +17,4 @@ class PersonModel with _$PersonModel { factory PersonModel.fromJson(Map json) => _$PersonModelFromJson(json); -} \ No newline at end of file +} diff --git a/lib/features/celebrity/data/repositories/celebrity_repository_impl.dart b/lib/features/celebrity/data/repositories/celebrity_repository_impl.dart index d2715e9..ba13ab8 100644 --- a/lib/features/celebrity/data/repositories/celebrity_repository_impl.dart +++ b/lib/features/celebrity/data/repositories/celebrity_repository_impl.dart @@ -28,13 +28,15 @@ class CelebrityRepositoryImpl implements CelebrityRepository { List _mapToEntities(List models) { return models - .map((model) => Person( - id: model.id, - name: model.name, - profilePath: model.profilePath, - knownForDepartment: model.knownForDepartment, - popularity: model.popularity, - )) + .map( + (model) => Person( + id: model.id, + name: model.name, + profilePath: model.profilePath, + knownForDepartment: model.knownForDepartment, + popularity: model.popularity, + ), + ) .toList(); } -} \ No newline at end of file +} diff --git a/lib/features/celebrity/domain/entities/person.dart b/lib/features/celebrity/domain/entities/person.dart index 5bde86e..30b1279 100644 --- a/lib/features/celebrity/domain/entities/person.dart +++ b/lib/features/celebrity/domain/entities/person.dart @@ -12,4 +12,4 @@ class Person { required this.knownForDepartment, required this.popularity, }); -} \ No newline at end of file +} diff --git a/lib/features/celebrity/domain/repositories/celebrity_repository.dart b/lib/features/celebrity/domain/repositories/celebrity_repository.dart index 22d06b3..8d37c9f 100644 --- a/lib/features/celebrity/domain/repositories/celebrity_repository.dart +++ b/lib/features/celebrity/domain/repositories/celebrity_repository.dart @@ -4,4 +4,4 @@ abstract class CelebrityRepository { Future> getPopularPersons(int page); Future> getTrendingPersons(int page); Future> searchPersons(String query, int page); -} \ No newline at end of file +} diff --git a/lib/features/celebrity/domain/usecases/get_popular_persons.dart b/lib/features/celebrity/domain/usecases/get_popular_persons.dart index e3a4062..8415627 100644 --- a/lib/features/celebrity/domain/usecases/get_popular_persons.dart +++ b/lib/features/celebrity/domain/usecases/get_popular_persons.dart @@ -9,4 +9,4 @@ class GetPopularPersons { Future> call(int page) async { return await repository.getPopularPersons(page); } -} \ No newline at end of file +} diff --git a/lib/features/celebrity/domain/usecases/get_trending_persons.dart b/lib/features/celebrity/domain/usecases/get_trending_persons.dart index 6461d08..5d24563 100644 --- a/lib/features/celebrity/domain/usecases/get_trending_persons.dart +++ b/lib/features/celebrity/domain/usecases/get_trending_persons.dart @@ -9,4 +9,4 @@ class GetTrendingPersons { Future> call(int page) async { return await repository.getTrendingPersons(page); } -} \ No newline at end of file +} diff --git a/lib/features/celebrity/domain/usecases/search_persons.dart b/lib/features/celebrity/domain/usecases/search_persons.dart index a6b5888..30a15b4 100644 --- a/lib/features/celebrity/domain/usecases/search_persons.dart +++ b/lib/features/celebrity/domain/usecases/search_persons.dart @@ -9,4 +9,4 @@ class SearchPersons { Future> call(String query, int page) async { return await repository.searchPersons(query, page); } -} \ No newline at end of file +} diff --git a/lib/features/celebrity/presentation/pages/celebrity_main_page.dart b/lib/features/celebrity/presentation/pages/celebrity_main_page.dart index bcd8ca0..1479bae 100644 --- a/lib/features/celebrity/presentation/pages/celebrity_main_page.dart +++ b/lib/features/celebrity/presentation/pages/celebrity_main_page.dart @@ -13,10 +13,7 @@ class CelebrityMainPage extends ConsumerStatefulWidget { class _CelebrityMainPageState extends ConsumerState { int _selectedIndex = 0; - final _pages = [ - const PopularPersonsPage(), - const TrendingPersonsPage(), - ]; + final _pages = [const PopularPersonsPage(), const TrendingPersonsPage()]; void _onItemTapped(int index) { setState(() { @@ -33,10 +30,7 @@ class _CelebrityMainPageState extends ConsumerState { onTap: _onItemTapped, type: BottomNavigationBarType.fixed, items: const [ - BottomNavigationBarItem( - icon: Icon(Icons.favorite), - label: 'Popular', - ), + BottomNavigationBarItem(icon: Icon(Icons.favorite), label: 'Popular'), BottomNavigationBarItem( icon: Icon(Icons.trending_up), label: 'Trending', @@ -45,4 +39,4 @@ class _CelebrityMainPageState extends ConsumerState { ), ); } -} \ No newline at end of file +} diff --git a/lib/features/celebrity/presentation/pages/popular_persons_page.dart b/lib/features/celebrity/presentation/pages/popular_persons_page.dart index 76f4916..10ad18c 100644 --- a/lib/features/celebrity/presentation/pages/popular_persons_page.dart +++ b/lib/features/celebrity/presentation/pages/popular_persons_page.dart @@ -12,7 +12,8 @@ class PopularPersonsPage extends ConsumerStatefulWidget { ConsumerState createState() => _PopularPersonsPageState(); } -class _PopularPersonsPageState extends PaginationConsumerState { +class _PopularPersonsPageState + extends PaginationConsumerState { @override Future> fetchData(int page) async { return ref.read(popularPersonsProvider(page).future); @@ -25,4 +26,4 @@ class _PopularPersonsPageState extends PaginationConsumerState PersonCardWidget(person: person), ); } -} \ No newline at end of file +} diff --git a/lib/features/celebrity/presentation/pages/trending_persons_page.dart b/lib/features/celebrity/presentation/pages/trending_persons_page.dart index c0745ec..5fe19e1 100644 --- a/lib/features/celebrity/presentation/pages/trending_persons_page.dart +++ b/lib/features/celebrity/presentation/pages/trending_persons_page.dart @@ -9,10 +9,12 @@ class TrendingPersonsPage extends ConsumerStatefulWidget { const TrendingPersonsPage({super.key}); @override - ConsumerState createState() => _TrendingPersonsPageState(); + ConsumerState createState() => + _TrendingPersonsPageState(); } -class _TrendingPersonsPageState extends PaginationConsumerState { +class _TrendingPersonsPageState + extends PaginationConsumerState { @override Future> fetchData(int page) async { return ref.read(trendingPersonsProvider(page).future); @@ -25,4 +27,4 @@ class _TrendingPersonsPageState extends PaginationConsumerState PersonCardWidget(person: person), ); } -} \ No newline at end of file +} diff --git a/lib/features/celebrity/presentation/providers/celebrity_provider.dart b/lib/features/celebrity/presentation/providers/celebrity_provider.dart index fb68e98..3bf6a80 100644 --- a/lib/features/celebrity/presentation/providers/celebrity_provider.dart +++ b/lib/features/celebrity/presentation/providers/celebrity_provider.dart @@ -12,15 +12,18 @@ final celebrityRemoteDataSourceProvider = Provider( ); final celebrityRepositoryProvider = Provider( - (ref) => CelebrityRepositoryImpl(ref.watch(celebrityRemoteDataSourceProvider)), + (ref) => + CelebrityRepositoryImpl(ref.watch(celebrityRemoteDataSourceProvider)), ); final getPopularPersonsProvider = Provider( (ref) => GetPopularPersons(ref.watch(celebrityRepositoryProvider)), ); -final popularPersonsProvider = - FutureProvider.family, int>((ref, page) async { +final popularPersonsProvider = FutureProvider.family, int>(( + ref, + page, +) async { return ref.watch(getPopularPersonsProvider).call(page); }); @@ -28,8 +31,10 @@ final getTrendingPersonsProvider = Provider( (ref) => GetTrendingPersons(ref.watch(celebrityRepositoryProvider)), ); -final trendingPersonsProvider = - FutureProvider.family, int>((ref, page) async { +final trendingPersonsProvider = FutureProvider.family, int>(( + ref, + page, +) async { return ref.watch(getTrendingPersonsProvider).call(page); }); @@ -37,13 +42,14 @@ final searchPersonsProvider = Provider( (ref) => SearchPersons(ref.watch(celebrityRepositoryProvider)), ); -final searchPersonsResultProvider = - FutureProvider.family, String>((ref, query) async { - return ref.watch(searchPersonsProvider).call(query, 1); -}); +final searchPersonsResultProvider = FutureProvider.family, String>( + (ref, query) async { + return ref.watch(searchPersonsProvider).call(query, 1); + }, +); final searchPersonsPaginatedProvider = FutureProvider.family, (String, int)>((ref, params) async { - final (query, page) = params; - return ref.watch(searchPersonsProvider).call(query, page); -}); \ No newline at end of file + final (query, page) = params; + return ref.watch(searchPersonsProvider).call(query, page); + }); diff --git a/lib/features/celebrity/presentation/widgets/person_card.dart b/lib/features/celebrity/presentation/widgets/person_card.dart index 56a806d..2dbade8 100644 --- a/lib/features/celebrity/presentation/widgets/person_card.dart +++ b/lib/features/celebrity/presentation/widgets/person_card.dart @@ -22,7 +22,7 @@ class PersonCardWidget extends StatelessWidget { borderRadius: BorderRadius.circular(12), child: person.profilePath != null ? CachedNetworkImage( - imageUrl: '$IMAGE_URL${person.profilePath}', + imageUrl: '$imageUrl${person.profilePath}', fit: BoxFit.cover, errorWidget: (context, url, error) => Container( color: Colors.grey[300], @@ -49,13 +49,10 @@ class PersonCardWidget extends StatelessWidget { textAlign: TextAlign.center, maxLines: 2, overflow: TextOverflow.ellipsis, - style: const TextStyle( - fontSize: 12, - fontWeight: FontWeight.bold, - ), + style: const TextStyle(fontSize: 12, fontWeight: FontWeight.bold), ), ], ), ); } -} \ No newline at end of file +} diff --git a/lib/features/favorites/favorites_page.dart b/lib/features/favorites/favorites_page.dart index 79e5a82..df44c92 100644 --- a/lib/features/favorites/favorites_page.dart +++ b/lib/features/favorites/favorites_page.dart @@ -127,10 +127,10 @@ class _FavoritesPageState extends State { ), child: favorite.posterPath.isNotEmpty ? Image.network( - '$IMAGE_URL${favorite.posterPath}', + '$imageUrl${favorite.posterPath}', fit: BoxFit.cover, width: double.infinity, - errorBuilder: (_, __, ___) => + errorBuilder: (context, error, stackTrace) => _buildPlaceholderImage(), ) : _buildPlaceholderImage(), @@ -171,7 +171,9 @@ class _FavoritesPageState extends State { padding: EdgeInsets.zero, onPressed: () async { await HiveHelper.deleteFavorite( - favorite.itemId, favorite.type); + favorite.itemId, + favorite.type, + ); _refreshFavorites(); }, ), @@ -198,14 +200,8 @@ class _FavoritesPageState extends State { }); }, items: const [ - BottomNavigationBarItem( - icon: Icon(Icons.movie), - label: 'Movies', - ), - BottomNavigationBarItem( - icon: Icon(Icons.tv), - label: 'TV Series', - ), + BottomNavigationBarItem(icon: Icon(Icons.movie), label: 'Movies'), + BottomNavigationBarItem(icon: Icon(Icons.tv), label: 'TV Series'), BottomNavigationBarItem( icon: Icon(Icons.people), label: 'Celebrities', @@ -223,4 +219,4 @@ class _FavoritesPageState extends State { child: const Icon(Icons.image, color: Colors.grey), ); } -} \ No newline at end of file +} diff --git a/lib/features/movie/data/datasources/movie_remote_data_source.dart b/lib/features/movie/data/datasources/movie_remote_data_source.dart index 1aee068..4d305b4 100644 --- a/lib/features/movie/data/datasources/movie_remote_data_source.dart +++ b/lib/features/movie/data/datasources/movie_remote_data_source.dart @@ -10,32 +10,40 @@ class MovieRemoteDataSource { MovieRemoteDataSource(this.dio); Future> getNowPlaying(int page) async { - final response = - await dio.get('movie/now_playing', queryParameters: {'page': page}); + final response = await dio.get( + 'movie/now_playing', + queryParameters: {'page': page}, + ); return (response.data['results'] as List) .map((e) => MovieModel.fromJson(e)) .toList(); } Future> getPopular(int page) async { - final response = - await dio.get('movie/popular', queryParameters: {'page': page}); + final response = await dio.get( + 'movie/popular', + queryParameters: {'page': page}, + ); return (response.data['results'] as List) .map((e) => MovieModel.fromJson(e)) .toList(); } Future> getUpcoming(int page) async { - final response = - await dio.get('movie/upcoming', queryParameters: {'page': page}); + final response = await dio.get( + 'movie/upcoming', + queryParameters: {'page': page}, + ); return (response.data['results'] as List) .map((e) => MovieModel.fromJson(e)) .toList(); } Future> getTopRated(int page) async { - final response = - await dio.get('movie/top_rated', queryParameters: {'page': page}); + final response = await dio.get( + 'movie/top_rated', + queryParameters: {'page': page}, + ); return (response.data['results'] as List) .map((e) => MovieModel.fromJson(e)) .toList(); @@ -53,8 +61,10 @@ class MovieRemoteDataSource { } Future> getMovieSearch(String query) async { - final response = - await dio.get('/search/movie', queryParameters: {'query': query}); + final response = await dio.get( + '/search/movie', + queryParameters: {'query': query}, + ); return (response.data['results'] as List) .map((e) => MovieModel.fromJson(e)) .toList(); @@ -76,8 +86,9 @@ class MovieRemoteDataSource { final response = await dio.get('person/$artistId'); return ArtistDetailModel.fromJson(response.data as Map); } + Future getArtistAllMovies(int artistId) async { - final response = await dio.get('person/$artistId/combined_credits'); - return CreditModel.fromJson(response.data as Map); + final response = await dio.get('person/$artistId/combined_credits'); + return CreditModel.fromJson(response.data as Map); } } diff --git a/lib/features/movie/data/models/artist_detail_model.dart b/lib/features/movie/data/models/artist_detail_model.dart index 6a6e545..43a237e 100644 --- a/lib/features/movie/data/models/artist_detail_model.dart +++ b/lib/features/movie/data/models/artist_detail_model.dart @@ -4,7 +4,7 @@ part 'artist_detail_model.freezed.dart'; part 'artist_detail_model.g.dart'; @freezed -class ArtistDetailModel with _$ArtistDetailModel{ +class ArtistDetailModel with _$ArtistDetailModel { const factory ArtistDetailModel({ required bool adult, @JsonKey(name: 'also_known_as') required List alsoKnownAs, @@ -24,4 +24,4 @@ class ArtistDetailModel with _$ArtistDetailModel{ factory ArtistDetailModel.fromJson(Map json) => _$ArtistDetailModelFromJson(json); -} \ No newline at end of file +} diff --git a/lib/features/movie/data/models/credit_model.dart b/lib/features/movie/data/models/credit_model.dart index 4501b01..a853d08 100644 --- a/lib/features/movie/data/models/credit_model.dart +++ b/lib/features/movie/data/models/credit_model.dart @@ -11,7 +11,8 @@ class CreditModel with _$CreditModel { List? crew, }) = _CreditModel; - factory CreditModel.fromJson(Map json) => _$CreditModelFromJson(json); + factory CreditModel.fromJson(Map json) => + _$CreditModelFromJson(json); } @freezed @@ -54,4 +55,4 @@ class Crew with _$Crew { }) = _Crew; factory Crew.fromJson(Map json) => _$CrewFromJson(json); -} \ No newline at end of file +} diff --git a/lib/features/movie/data/models/movie_detail_model.dart b/lib/features/movie/data/models/movie_detail_model.dart index 65e57fc..2ebefcc 100644 --- a/lib/features/movie/data/models/movie_detail_model.dart +++ b/lib/features/movie/data/models/movie_detail_model.dart @@ -9,13 +9,14 @@ class MovieDetailModel with _$MovieDetailModel { required int id, required String title, required String overview, - @JsonKey(name: 'poster_path') required String posterPath, - @JsonKey(name: 'release_date') required String releaseDate, - @JsonKey(name: 'vote_average') required double voteAverage, - required int runtime, + @JsonKey(name: 'poster_path') String? posterPath, + @JsonKey(name: 'release_date') String? releaseDate, + @JsonKey(name: 'vote_average') double? voteAverage, + int? runtime, @JsonKey(name: 'original_language') String? originalLanguage, List? genres, - @JsonKey(name: 'production_companies') List? productionCompanies, + @JsonKey(name: 'production_companies') + List? productionCompanies, }) = _MovieDetailModel; factory MovieDetailModel.fromJson(Map json) => @@ -24,10 +25,7 @@ class MovieDetailModel with _$MovieDetailModel { @freezed class Genre with _$Genre { - const factory Genre({ - required int id, - required String? name, - }) = _Genre; + const factory Genre({required int id, required String? name}) = _Genre; factory Genre.fromJson(Map json) => _$GenreFromJson(json); } @@ -42,4 +40,4 @@ class ProductionCompany with _$ProductionCompany { factory ProductionCompany.fromJson(Map json) => _$ProductionCompanyFromJson(json); -} \ No newline at end of file +} diff --git a/lib/features/movie/data/models/movie_model.dart b/lib/features/movie/data/models/movie_model.dart index b56a1f2..a500ad3 100644 --- a/lib/features/movie/data/models/movie_model.dart +++ b/lib/features/movie/data/models/movie_model.dart @@ -12,5 +12,6 @@ class MovieModel with _$MovieModel { @JsonKey(name: 'overview') String? overview, }) = _MovieModel; - factory MovieModel.fromJson(Map json) => _$MovieModelFromJson(json); -} \ No newline at end of file + factory MovieModel.fromJson(Map json) => + _$MovieModelFromJson(json); +} diff --git a/lib/features/movie/data/repositories/movie_repository_impl.dart b/lib/features/movie/data/repositories/movie_repository_impl.dart index c78e1d6..25ee668 100644 --- a/lib/features/movie/data/repositories/movie_repository_impl.dart +++ b/lib/features/movie/data/repositories/movie_repository_impl.dart @@ -14,12 +14,14 @@ class MovieRepositoryImpl implements MovieRepository { Future> getNowPlaying(int page) async { final models = await remoteDataSource.getNowPlaying(page); return models - .map((e) => Movie( - id: e.id, - title: e.title, - posterPath: e.posterPath, - overview: e.overview ?? '', - )) + .map( + (e) => Movie( + id: e.id, + title: e.title, + posterPath: e.posterPath, + overview: e.overview ?? '', + ), + ) .toList(); } @@ -27,12 +29,14 @@ class MovieRepositoryImpl implements MovieRepository { Future> getPopular(int page) async { final models = await remoteDataSource.getPopular(page); return models - .map((e) => Movie( - id: e.id, - title: e.title, - posterPath: e.posterPath, - overview: e.overview ?? '', - )) + .map( + (e) => Movie( + id: e.id, + title: e.title, + posterPath: e.posterPath, + overview: e.overview ?? '', + ), + ) .toList(); } @@ -40,12 +44,14 @@ class MovieRepositoryImpl implements MovieRepository { Future> getTopRated(int page) async { final models = await remoteDataSource.getTopRated(page); return models - .map((e) => Movie( - id: e.id, - title: e.title, - posterPath: e.posterPath, - overview: e.overview ?? '', - )) + .map( + (e) => Movie( + id: e.id, + title: e.title, + posterPath: e.posterPath, + overview: e.overview ?? '', + ), + ) .toList(); } @@ -53,12 +59,14 @@ class MovieRepositoryImpl implements MovieRepository { Future> getUpComing(int page) async { final models = await remoteDataSource.getUpcoming(page); return models - .map((e) => Movie( - id: e.id, - title: e.title, - posterPath: e.posterPath, - overview: e.overview ?? '', - )) + .map( + (e) => Movie( + id: e.id, + title: e.title, + posterPath: e.posterPath, + overview: e.overview ?? '', + ), + ) .toList(); } @@ -76,7 +84,8 @@ class MovieRepositoryImpl implements MovieRepository { runtime: model.runtime ?? 0, originalLanguage: model.originalLanguage ?? "N/A", genres: model.genres?.map((genre) => genre.name ?? "").toList() ?? [], - productionCompanies: model.productionCompanies + productionCompanies: + model.productionCompanies ?.map((company) => company.name ?? "") .toList() ?? [], @@ -87,12 +96,14 @@ class MovieRepositoryImpl implements MovieRepository { Future> getMovieSearch(String query) async { final models = await remoteDataSource.getMovieSearch(query); return models - .map((e) => Movie( - id: e.id, - title: e.title, - posterPath: e.posterPath, - overview: e.overview ?? '', - )) + .map( + (e) => Movie( + id: e.id, + title: e.title, + posterPath: e.posterPath, + overview: e.overview ?? '', + ), + ) .toList(); } @@ -100,23 +111,21 @@ class MovieRepositoryImpl implements MovieRepository { Future> getRecommendedMovie(int movieId) async { final models = await remoteDataSource.getRecommendedMovie(movieId); return models - .map((e) => Movie( - id: e.id, - title: e.title, - posterPath: e.posterPath, - overview: e.overview ?? '', - )) + .map( + (e) => Movie( + id: e.id, + title: e.title, + posterPath: e.posterPath, + overview: e.overview ?? '', + ), + ) .toList(); } @override Future getMovieCredits(int movieId) async { final model = await remoteDataSource.getMovieCredits(movieId); - return CreditModel( - id: model.id, - cast: model.cast, - crew: model.crew, - ); + return CreditModel(id: model.id, cast: model.cast, crew: model.crew); } @override @@ -133,12 +142,9 @@ class MovieRepositoryImpl implements MovieRepository { ); } + @override Future getArtistAllMovies(int artistId) async { final model = await remoteDataSource.getArtistAllMovies(artistId); - return CreditModel( - id: model.id, - cast: model.cast, - crew: model.crew, - ); + return CreditModel(id: model.id, cast: model.cast, crew: model.crew); } } diff --git a/lib/features/movie/domain/entities/artist_all_movies.dart b/lib/features/movie/domain/entities/artist_all_movies.dart index a69540f..afbce54 100644 --- a/lib/features/movie/domain/entities/artist_all_movies.dart +++ b/lib/features/movie/domain/entities/artist_all_movies.dart @@ -4,8 +4,5 @@ class Artistallmovies { final int id; final List cast; - Artistallmovies({ - required this.id, - required this.cast, - }); + Artistallmovies({required this.id, required this.cast}); } diff --git a/lib/features/movie/domain/entities/credit.dart b/lib/features/movie/domain/entities/credit.dart index 430a4d4..8ce2602 100644 --- a/lib/features/movie/domain/entities/credit.dart +++ b/lib/features/movie/domain/entities/credit.dart @@ -3,11 +3,7 @@ class Credit { final List? cast; final List? crew; - Credit({ - required this.id, - required this.cast, - required this.crew, - }); + Credit({required this.id, required this.cast, required this.crew}); } class Cast { @@ -15,11 +11,7 @@ class Cast { final String name; final String? profilePath; - Cast({ - required this.id, - required this.name, - required this.profilePath, - }); + Cast({required this.id, required this.name, required this.profilePath}); } class Crew { @@ -27,9 +19,5 @@ class Crew { final String name; final String? profilePath; - Crew({ - required this.id, - required this.name, - required this.profilePath, - }); + Crew({required this.id, required this.name, required this.profilePath}); } diff --git a/lib/features/movie/domain/entities/movie.dart b/lib/features/movie/domain/entities/movie.dart index 1bd0415..27c3891 100644 --- a/lib/features/movie/domain/entities/movie.dart +++ b/lib/features/movie/domain/entities/movie.dart @@ -10,4 +10,4 @@ class Movie { required this.posterPath, required this.overview, }); -} \ No newline at end of file +} diff --git a/lib/features/movie/domain/entities/movie_detail.dart b/lib/features/movie/domain/entities/movie_detail.dart index efef235..204a9b4 100644 --- a/lib/features/movie/domain/entities/movie_detail.dart +++ b/lib/features/movie/domain/entities/movie_detail.dart @@ -22,4 +22,4 @@ class MovieDetail { required this.genres, required this.productionCompanies, }); -} \ No newline at end of file +} diff --git a/lib/features/movie/domain/repositories/entities/movie_repository.dart b/lib/features/movie/domain/repositories/entities/movie_repository.dart index 9afe45d..f8d4b03 100644 --- a/lib/features/movie/domain/repositories/entities/movie_repository.dart +++ b/lib/features/movie/domain/repositories/entities/movie_repository.dart @@ -3,7 +3,7 @@ import 'package:flutter_movie_clean_architecture/features/movie/domain/entities/ import 'package:flutter_movie_clean_architecture/features/movie/domain/entities/movie.dart'; import 'package:flutter_movie_clean_architecture/features/movie/domain/entities/movie_detail.dart'; -abstract class MovieRepository { +abstract class MovieRepository { Future> getNowPlaying(int page); Future> getPopular(int page); Future> getTopRated(int page); @@ -14,4 +14,4 @@ abstract class MovieRepository { Future getMovieCredits(int movieId); Future getArtistDetail(int artistId); Future getArtistAllMovies(int artistId); -} \ No newline at end of file +} diff --git a/lib/features/movie/domain/repositories/usecases/get_all_artist_movies.dart b/lib/features/movie/domain/repositories/usecases/get_all_artist_movies.dart index 87cd476..c354b4c 100644 --- a/lib/features/movie/domain/repositories/usecases/get_all_artist_movies.dart +++ b/lib/features/movie/domain/repositories/usecases/get_all_artist_movies.dart @@ -6,5 +6,6 @@ class GetAllArtistMovies { GetAllArtistMovies(this.repository); - Future call(int artistId) => repository.getArtistAllMovies(artistId); -} \ No newline at end of file + Future call(int artistId) => + repository.getArtistAllMovies(artistId); +} diff --git a/lib/features/movie/domain/repositories/usecases/get_movie_credits.dart b/lib/features/movie/domain/repositories/usecases/get_movie_credits.dart index a946440..4c708fc 100644 --- a/lib/features/movie/domain/repositories/usecases/get_movie_credits.dart +++ b/lib/features/movie/domain/repositories/usecases/get_movie_credits.dart @@ -7,4 +7,4 @@ class GetMovieCredits { GetMovieCredits(this.repository); Future call(int movieId) => repository.getMovieCredits(movieId); -} \ No newline at end of file +} diff --git a/lib/features/movie/domain/repositories/usecases/get_movie_detail.dart b/lib/features/movie/domain/repositories/usecases/get_movie_detail.dart index 6076534..d375fe8 100644 --- a/lib/features/movie/domain/repositories/usecases/get_movie_detail.dart +++ b/lib/features/movie/domain/repositories/usecases/get_movie_detail.dart @@ -1,4 +1,3 @@ -import 'package:flutter_movie_clean_architecture/features/movie/domain/entities/movie.dart'; import 'package:flutter_movie_clean_architecture/features/movie/domain/entities/movie_detail.dart'; import 'package:flutter_movie_clean_architecture/features/movie/domain/repositories/entities/movie_repository.dart'; diff --git a/lib/features/movie/domain/repositories/usecases/get_movie_search.dart b/lib/features/movie/domain/repositories/usecases/get_movie_search.dart index b9b4a96..1a2c2b8 100644 --- a/lib/features/movie/domain/repositories/usecases/get_movie_search.dart +++ b/lib/features/movie/domain/repositories/usecases/get_movie_search.dart @@ -6,5 +6,5 @@ class GetMovieSearch { GetMovieSearch(this.repository); - Future> call(String query) => repository.getMovieSearch( query); -} \ No newline at end of file + Future> call(String query) => repository.getMovieSearch(query); +} diff --git a/lib/features/movie/domain/repositories/usecases/get_now_playing_movie.dart b/lib/features/movie/domain/repositories/usecases/get_now_playing_movie.dart index 71f4534..65e28e3 100644 --- a/lib/features/movie/domain/repositories/usecases/get_now_playing_movie.dart +++ b/lib/features/movie/domain/repositories/usecases/get_now_playing_movie.dart @@ -6,5 +6,5 @@ class GetNowPlaying { GetNowPlaying(this.repository); - Future> call(int page) => repository.getNowPlaying( page); -} \ No newline at end of file + Future> call(int page) => repository.getNowPlaying(page); +} diff --git a/lib/features/movie/domain/repositories/usecases/get_popular_movie.dart b/lib/features/movie/domain/repositories/usecases/get_popular_movie.dart index 9f7f7ef..bae689e 100644 --- a/lib/features/movie/domain/repositories/usecases/get_popular_movie.dart +++ b/lib/features/movie/domain/repositories/usecases/get_popular_movie.dart @@ -6,5 +6,5 @@ class GetPopular { GetPopular(this.repository); - Future> call(int page) => repository.getPopular( page); -} \ No newline at end of file + Future> call(int page) => repository.getPopular(page); +} diff --git a/lib/features/movie/domain/repositories/usecases/get_top_rated_movie.dart b/lib/features/movie/domain/repositories/usecases/get_top_rated_movie.dart index 05ad594..b69fd33 100644 --- a/lib/features/movie/domain/repositories/usecases/get_top_rated_movie.dart +++ b/lib/features/movie/domain/repositories/usecases/get_top_rated_movie.dart @@ -6,5 +6,5 @@ class GetTopRated { GetTopRated(this.repository); - Future> call(int page) => repository.getTopRated( page); -} \ No newline at end of file + Future> call(int page) => repository.getTopRated(page); +} diff --git a/lib/features/movie/domain/repositories/usecases/get_up_coming_movie.dart b/lib/features/movie/domain/repositories/usecases/get_up_coming_movie.dart index 5330f16..22725d5 100644 --- a/lib/features/movie/domain/repositories/usecases/get_up_coming_movie.dart +++ b/lib/features/movie/domain/repositories/usecases/get_up_coming_movie.dart @@ -6,5 +6,5 @@ class GetUpComing { GetUpComing(this.repository); - Future> call(int page) => repository.getUpComing( page); -} \ No newline at end of file + Future> call(int page) => repository.getUpComing(page); +} diff --git a/lib/features/movie/presentation/pages/artist_detail_page.dart b/lib/features/movie/presentation/pages/artist_detail_page.dart index ee9673c..3c2286b 100644 --- a/lib/features/movie/presentation/pages/artist_detail_page.dart +++ b/lib/features/movie/presentation/pages/artist_detail_page.dart @@ -26,7 +26,10 @@ class _ArtistDetailPageState extends ConsumerState { } Future _checkIfFavorite() async { - final isFavorite = await HiveHelper.isFavorite(widget.artistId, 'celebrity'); + final isFavorite = await HiveHelper.isFavorite( + widget.artistId, + 'celebrity', + ); if (mounted) { setState(() { _isFavorite = isFavorite; @@ -60,7 +63,9 @@ class _ArtistDetailPageState extends ConsumerState { @override Widget build(BuildContext context) { final artistDetailAsync = ref.watch(artistDetailProvider(widget.artistId)); - final artistAllMoviesAsync = ref.watch(artistDetailAllMoviesProvider(widget.artistId)); + final artistAllMoviesAsync = ref.watch( + artistDetailAllMoviesProvider(widget.artistId), + ); return Scaffold( backgroundColor: Colors.white, @@ -114,14 +119,22 @@ class _ArtistDetailPageState extends ConsumerState { borderRadius: BorderRadius.circular(8), image: artist.profilePath != null ? DecorationImage( - image: NetworkImage("$IMAGE_URL${artist.profilePath}"), - fit: BoxFit.cover, - ) + image: NetworkImage( + "$imageUrl${artist.profilePath}", + ), + fit: BoxFit.cover, + ) + : null, + color: artist.profilePath == null + ? Colors.grey[300] : null, - color: artist.profilePath == null ? Colors.grey[300] : null, ), child: artist.profilePath == null - ? const Icon(Icons.person, color: Colors.grey, size: 60) + ? const Icon( + Icons.person, + color: Colors.grey, + size: 60, + ) : null, ), const SizedBox(width: 18), @@ -140,29 +153,47 @@ class _ArtistDetailPageState extends ConsumerState { const SizedBox(height: 8), Text( 'Artist Detail', - style: TextStyle(fontSize: 12, color: Colors.grey[600]), + style: TextStyle( + fontSize: 12, + color: Colors.grey[600], + ), ), Text( - artist.knownForDepartment ?? 'Acting', - style: const TextStyle(fontSize: 16, color: Colors.black87), + artist.knownForDepartment, + style: const TextStyle( + fontSize: 16, + color: Colors.black87, + ), ), const SizedBox(height: 8), Text( 'Birthday', - style: TextStyle(fontSize: 12, color: Colors.grey[600]), + style: TextStyle( + fontSize: 12, + color: Colors.grey[600], + ), ), Text( - artist.birthday ?? 'N/A', - style: const TextStyle(fontSize: 16, color: Colors.black87), + artist.birthday, + style: const TextStyle( + fontSize: 16, + color: Colors.black87, + ), ), const SizedBox(height: 8), Text( 'Place of Birth', - style: TextStyle(fontSize: 12, color: Colors.grey[600]), + style: TextStyle( + fontSize: 12, + color: Colors.grey[600], + ), ), Text( - artist.placeOfBirth ?? 'N/A', - style: const TextStyle(fontSize: 16, color: Colors.black87), + artist.placeOfBirth, + style: const TextStyle( + fontSize: 16, + color: Colors.black87, + ), ), ], ), @@ -174,14 +205,23 @@ class _ArtistDetailPageState extends ConsumerState { // Biography Section const Text( 'Biography', - style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + ), ), const SizedBox(height: 12), Text( - artist.biography ?? 'No biography available.', + artist.biography, maxLines: _isExpanded ? null : 4, - overflow: _isExpanded ? TextOverflow.visible : TextOverflow.ellipsis, - style: const TextStyle(fontSize: 16, height: 1.5, color: Colors.black87), + overflow: _isExpanded + ? TextOverflow.visible + : TextOverflow.ellipsis, + style: const TextStyle( + fontSize: 16, + height: 1.5, + color: Colors.black87, + ), ), const SizedBox(height: 8), GestureDetector( @@ -202,7 +242,9 @@ class _ArtistDetailPageState extends ConsumerState { const SizedBox(height: 20), // Artist Movies Section - ArtistMoviesSection(artistAllMoviesAsync: artistAllMoviesAsync), + ArtistMoviesSection( + artistAllMoviesAsync: artistAllMoviesAsync, + ), ], ), ), @@ -218,12 +260,16 @@ class _ArtistDetailPageState extends ConsumerState { children: [ const Icon(Icons.error_outline, size: 64, color: Colors.red), const SizedBox(height: 16), - Text('Error loading artist details', - style: Theme.of(context).textTheme.headlineSmall), + Text( + 'Error loading artist details', + style: Theme.of(context).textTheme.headlineSmall, + ), const SizedBox(height: 8), - Text(error.toString(), - textAlign: TextAlign.center, - style: const TextStyle(color: Colors.grey)), + Text( + error.toString(), + textAlign: TextAlign.center, + style: const TextStyle(color: Colors.grey), + ), const SizedBox(height: 16), ElevatedButton( onPressed: () => Navigator.pop(context), @@ -269,7 +315,7 @@ class ArtistMoviesSection extends StatelessWidget { child: ListView.separated( scrollDirection: Axis.horizontal, itemCount: movies.length, - separatorBuilder: (_, __) => const SizedBox(width: 12), + separatorBuilder: (context, index) => const SizedBox(width: 12), itemBuilder: (context, index) { final item = movies[index]; return GestureDetector( @@ -286,12 +332,12 @@ class ArtistMoviesSection extends StatelessWidget { borderRadius: BorderRadius.circular(12), child: item.posterPath != null ? Image.network( - '$IMAGE_URL${item.posterPath}', - width: 110, - height: 160, - fit: BoxFit.cover, - errorBuilder: (_, __, ___) => _errorPlaceholder(), - ) + '$imageUrl${item.posterPath}', + width: 110, + height: 160, + fit: BoxFit.cover, + errorBuilder: (context, error, stackTrace) => _errorPlaceholder(), + ) : _errorPlaceholder(), ), ); @@ -324,4 +370,4 @@ class ArtistMoviesSection extends StatelessWidget { child: const Icon(Icons.movie, size: 48, color: Colors.grey), ); } -} \ No newline at end of file +} diff --git a/lib/features/movie/presentation/pages/movie_detail_page.dart b/lib/features/movie/presentation/pages/movie_detail_page.dart index cb7a086..11aaac4 100644 --- a/lib/features/movie/presentation/pages/movie_detail_page.dart +++ b/lib/features/movie/presentation/pages/movie_detail_page.dart @@ -33,7 +33,8 @@ class MovieDetailPage extends ConsumerWidget { MovieDetailInfoSection(movie: movie), MovieDescriptionSection(movie: movie), RecommendedMoviesSection( - recommendMovieAsync: recommendMovieAsync), + recommendMovieAsync: recommendMovieAsync, + ), MovieCreditsSection(movieCreditAsync: movieCreditAsync), ], ), @@ -49,12 +50,16 @@ class MovieDetailPage extends ConsumerWidget { children: [ const Icon(Icons.error_outline, size: 64, color: Colors.red), const SizedBox(height: 16), - Text('Error loading movie details', - style: Theme.of(context).textTheme.headlineSmall), + Text( + 'Error loading movie details', + style: Theme.of(context).textTheme.headlineSmall, + ), const SizedBox(height: 8), - Text(error.toString(), - textAlign: TextAlign.center, - style: const TextStyle(color: Colors.grey)), + Text( + error.toString(), + textAlign: TextAlign.center, + style: const TextStyle(color: Colors.grey), + ), const SizedBox(height: 16), ElevatedButton( onPressed: () => Navigator.pop(context), @@ -147,28 +152,31 @@ class _MovieDetailHeaderState extends ConsumerState { children: [ widget.movie.posterPath != null ? Image.network( - '$IMAGE_URL${widget.movie.posterPath}', + '$imageUrl${widget.movie.posterPath}', fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) => Container( color: Colors.grey[300], - child: - const Icon(Icons.movie, size: 64, color: Colors.grey), + child: const Icon( + Icons.movie, + size: 64, + color: Colors.grey, + ), ), ) : Container( color: Colors.grey[300], - child: - const Icon(Icons.movie, size: 64, color: Colors.grey), + child: const Icon( + Icons.movie, + size: 64, + color: Colors.grey, + ), ), Container( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, - colors: [ - Colors.transparent, - Colors.black.withOpacity(0.7), - ], + colors: [Colors.transparent, Colors.black.withValues(alpha: 0.7)], ), ), ), @@ -199,7 +207,7 @@ class MovieDetailInfoSection extends StatelessWidget { decoration: BoxDecoration( boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.3), + color: Colors.black.withValues(alpha: 0.3), blurRadius: 8, offset: const Offset(0, 4), ), @@ -207,9 +215,9 @@ class MovieDetailInfoSection extends StatelessWidget { ), child: movie.posterPath != null ? Image.network( - '$IMAGE_URL${movie.posterPath}', + '$imageUrl${movie.posterPath}', fit: BoxFit.cover, - errorBuilder: (_, __, ___) => Container( + errorBuilder: (context, error, stackTrace) => Container( color: Colors.grey[300], child: const Icon(Icons.movie, color: Colors.grey), ), @@ -237,20 +245,25 @@ class MovieDetailInfoSection extends StatelessWidget { Row( children: [ _buildInfoItem( - 'Duration', - movie.runtime != null - ? formatDuration(movie.runtime!) - : 'N/A'), + 'Duration', + movie.runtime != null + ? formatDuration(movie.runtime!) + : 'N/A', + ), _buildInfoItem('Release Date', movie.releaseDate ?? 'N/A'), ], ), const SizedBox(height: 16), Row( children: [ - _buildInfoItem('Language', - movie.originalLanguage?.toUpperCase() ?? 'N/A'), - _buildInfoItem('Rating', - movie.voteAverage?.toStringAsFixed(1) ?? 'N/A'), + _buildInfoItem( + 'Language', + movie.originalLanguage?.toUpperCase() ?? 'N/A', + ), + _buildInfoItem( + 'Rating', + movie.voteAverage?.toStringAsFixed(1) ?? 'N/A', + ), ], ), ], @@ -268,10 +281,7 @@ class MovieDetailInfoSection extends StatelessWidget { children: [ Text(label), const SizedBox(height: 4), - Text( - value, - style: const TextStyle(color: Colors.grey), - ), + Text(value, style: const TextStyle(color: Colors.grey)), ], ), ); @@ -344,8 +354,10 @@ class MovieDescriptionSection extends ConsumerWidget { class RecommendedMoviesSection extends StatelessWidget { final AsyncValue> recommendMovieAsync; - const RecommendedMoviesSection( - {super.key, required this.recommendMovieAsync}); + const RecommendedMoviesSection({ + super.key, + required this.recommendMovieAsync, + }); @override Widget build(BuildContext context) { @@ -374,7 +386,7 @@ class RecommendedMoviesSection extends StatelessWidget { scrollDirection: Axis.horizontal, padding: const EdgeInsets.symmetric(horizontal: 16), itemCount: recommendedMovies.length, - separatorBuilder: (_, __) => const SizedBox(width: 12), + separatorBuilder: (context, index) => const SizedBox(width: 12), itemBuilder: (context, index) { final movie = recommendedMovies[index]; return GestureDetector( @@ -383,11 +395,11 @@ class RecommendedMoviesSection extends StatelessWidget { borderRadius: BorderRadius.circular(12), child: movie.posterPath != null ? Image.network( - '$IMAGE_URL${movie.posterPath}', + '$imageUrl${movie.posterPath}', width: 110, height: 160, fit: BoxFit.cover, - errorBuilder: (_, __, ___) => _errorPlaceholder(), + errorBuilder: (context, error, stackTrace) => _errorPlaceholder(), ) : _errorPlaceholder(), ), @@ -455,11 +467,11 @@ class MovieCreditsSection extends StatelessWidget { padding: const EdgeInsets.symmetric(horizontal: 16), scrollDirection: Axis.horizontal, itemCount: castList.length, - separatorBuilder: (_, __) => const SizedBox(width: 4), + separatorBuilder: (context, index) => const SizedBox(width: 4), itemBuilder: (context, index) { final cast = castList[index]; - final imageUrl = cast.profilePath != null - ? '$IMAGE_URL${cast.profilePath}' + final profileImageUrl = cast.profilePath != null + ? '$imageUrl${cast.profilePath}' : null; return InkWell( @@ -473,13 +485,13 @@ class MovieCreditsSection extends StatelessWidget { child: Column( children: [ ClipOval( - child: imageUrl != null + child: profileImageUrl != null ? Image.network( - imageUrl, + profileImageUrl, width: 70, height: 70, fit: BoxFit.cover, - errorBuilder: (_, __, ___) => + errorBuilder: (context, error, stackTrace) => _placeholder(), ) : _placeholder(), @@ -510,9 +522,7 @@ class MovieCreditsSection extends StatelessWidget { loading: () => const Padding( padding: EdgeInsets.symmetric(vertical: 24), child: Center( - child: CircularProgressIndicator( - color: Color(0xFF7B2CBF), - ), + child: CircularProgressIndicator(color: Color(0xFF7B2CBF)), ), ), error: (error, _) => Padding( diff --git a/lib/features/movie/presentation/pages/movie_main_page.dart b/lib/features/movie/presentation/pages/movie_main_page.dart index 6c4f0bb..f4f83f3 100644 --- a/lib/features/movie/presentation/pages/movie_main_page.dart +++ b/lib/features/movie/presentation/pages/movie_main_page.dart @@ -42,14 +42,8 @@ class _MovieMainPageState extends ConsumerState { icon: Icon(Icons.play_circle_fill), label: 'Now Playing', ), - BottomNavigationBarItem( - icon: Icon(Icons.favorite), - label: 'Popular', - ), - BottomNavigationBarItem( - icon: Icon(Icons.star), - label: 'Top Rated', - ), + BottomNavigationBarItem(icon: Icon(Icons.favorite), label: 'Popular'), + BottomNavigationBarItem(icon: Icon(Icons.star), label: 'Top Rated'), BottomNavigationBarItem( icon: Icon(Icons.upcoming), label: 'Upcoming', @@ -58,4 +52,4 @@ class _MovieMainPageState extends ConsumerState { ), ); } -} \ No newline at end of file +} diff --git a/lib/features/movie/presentation/pages/now_playing_page.dart b/lib/features/movie/presentation/pages/now_playing_page.dart index 32b5ee3..465c039 100644 --- a/lib/features/movie/presentation/pages/now_playing_page.dart +++ b/lib/features/movie/presentation/pages/now_playing_page.dart @@ -12,7 +12,8 @@ class NowPlayingPage extends ConsumerStatefulWidget { ConsumerState createState() => _MovieHomePageState(); } -class _MovieHomePageState extends PaginationConsumerState { +class _MovieHomePageState + extends PaginationConsumerState { @override Future> fetchData(int page) async { return ref.read(nowPlayingMoviesProvider(page).future); @@ -25,4 +26,4 @@ class _MovieHomePageState extends PaginationConsumerState itemBuilder: (movie) => MovieCardWidget(movie: movie), ); } -} \ No newline at end of file +} diff --git a/lib/features/movie/presentation/pages/popular_page.dart b/lib/features/movie/presentation/pages/popular_page.dart index b784c14..669bbcf 100644 --- a/lib/features/movie/presentation/pages/popular_page.dart +++ b/lib/features/movie/presentation/pages/popular_page.dart @@ -12,7 +12,8 @@ class PopularPage extends ConsumerStatefulWidget { ConsumerState createState() => _PopularMoviePageState(); } -class _PopularMoviePageState extends PaginationConsumerState { +class _PopularMoviePageState + extends PaginationConsumerState { @override Future> fetchData(int page) async { return ref.read(popularMoviesProvider(page).future); @@ -25,4 +26,4 @@ class _PopularMoviePageState extends PaginationConsumerState itemBuilder: (movie) => MovieCardWidget(movie: movie), ); } -} \ No newline at end of file +} diff --git a/lib/features/movie/presentation/pages/top_rated_page.dart b/lib/features/movie/presentation/pages/top_rated_page.dart index 68d7832..b32bc02 100644 --- a/lib/features/movie/presentation/pages/top_rated_page.dart +++ b/lib/features/movie/presentation/pages/top_rated_page.dart @@ -12,7 +12,8 @@ class TopRatedPage extends ConsumerStatefulWidget { ConsumerState createState() => _TopRatedMoviePageState(); } -class _TopRatedMoviePageState extends PaginationConsumerState { +class _TopRatedMoviePageState + extends PaginationConsumerState { @override Future> fetchData(int page) async { return ref.read(topRatedMoviesProvider(page).future); @@ -25,4 +26,4 @@ class _TopRatedMoviePageState extends PaginationConsumerState MovieCardWidget(movie: movie), ); } -} \ No newline at end of file +} diff --git a/lib/features/movie/presentation/pages/up_coming_page.dart b/lib/features/movie/presentation/pages/up_coming_page.dart index 3e144e8..311e926 100644 --- a/lib/features/movie/presentation/pages/up_coming_page.dart +++ b/lib/features/movie/presentation/pages/up_coming_page.dart @@ -12,7 +12,8 @@ class UpComingPage extends ConsumerStatefulWidget { ConsumerState createState() => _UpComingMoviePageState(); } -class _UpComingMoviePageState extends PaginationConsumerState { +class _UpComingMoviePageState + extends PaginationConsumerState { @override Future> fetchData(int page) async { return ref.read(upComingMoviesProvider(page).future); @@ -25,4 +26,4 @@ class _UpComingMoviePageState extends PaginationConsumerState MovieCardWidget(movie: movie), ); } -} \ No newline at end of file +} diff --git a/lib/features/movie/presentation/providers/movie_provider.dart b/lib/features/movie/presentation/providers/movie_provider.dart index 298098b..2b8a0ce 100644 --- a/lib/features/movie/presentation/providers/movie_provider.dart +++ b/lib/features/movie/presentation/providers/movie_provider.dart @@ -30,8 +30,10 @@ final getNowPlayingProvider = Provider( (ref) => GetNowPlaying(ref.watch(movieRepositoryProvider)), ); -final nowPlayingMoviesProvider = - FutureProvider.family, int>((ref, page) async { +final nowPlayingMoviesProvider = FutureProvider.family, int>(( + ref, + page, +) async { return ref.watch(getNowPlayingProvider).call(page); }); @@ -39,8 +41,10 @@ final getPopularProvider = Provider( (ref) => GetPopular(ref.watch(movieRepositoryProvider)), ); -final popularMoviesProvider = - FutureProvider.family, int>((ref, page) async { +final popularMoviesProvider = FutureProvider.family, int>(( + ref, + page, +) async { return ref.watch(getPopularProvider).call(page); }); @@ -48,8 +52,10 @@ final getTopRatedProvider = Provider( (ref) => GetTopRated(ref.watch(movieRepositoryProvider)), ); -final topRatedMoviesProvider = - FutureProvider.family, int>((ref, page) async { +final topRatedMoviesProvider = FutureProvider.family, int>(( + ref, + page, +) async { return ref.watch(getTopRatedProvider).call(page); }); @@ -57,8 +63,10 @@ final getUpcomingProvider = Provider( (ref) => GetUpComing(ref.watch(movieRepositoryProvider)), ); -final upComingMoviesProvider = - FutureProvider.family, int>((ref, page) async { +final upComingMoviesProvider = FutureProvider.family, int>(( + ref, + page, +) async { return ref.watch(getUpcomingProvider).call(page); }); @@ -66,8 +74,10 @@ final getMovieDetailProvider = Provider( (ref) => GetMovieDetail(ref.watch(movieRepositoryProvider)), ); -final movieDetailProvider = - FutureProvider.family((ref, movieId) async { +final movieDetailProvider = FutureProvider.family(( + ref, + movieId, +) async { return ref.watch(getMovieDetailProvider).call(movieId); }); @@ -75,8 +85,10 @@ final getMovieSearchProvider = Provider( (ref) => GetMovieSearch(ref.watch(movieRepositoryProvider)), ); -final movieSearchProvider = - FutureProvider.family, String>((ref, query) async { +final movieSearchProvider = FutureProvider.family, String>(( + ref, + query, +) async { return ref.watch(getMovieSearchProvider).call(query); }); @@ -84,8 +96,10 @@ final getRecommendMovieProvider = Provider( (ref) => GetRecommendedMovie(ref.watch(movieRepositoryProvider)), ); -final recommendMovieProvider = - FutureProvider.family, int>((ref, movieId) async { +final recommendMovieProvider = FutureProvider.family, int>(( + ref, + movieId, +) async { return ref.watch(getRecommendMovieProvider).call(movieId); }); @@ -93,8 +107,10 @@ final getMovieCreditProvider = Provider( (ref) => GetMovieCredits(ref.watch(movieRepositoryProvider)), ); -final movieCreditsProvider = - FutureProvider.family((ref, movieId) async { +final movieCreditsProvider = FutureProvider.family(( + ref, + movieId, +) async { return ref.watch(getMovieCreditProvider).call(movieId); }); @@ -102,8 +118,10 @@ final getArtistDetailProvider = Provider( (ref) => GetArtistDetail(ref.watch(movieRepositoryProvider)), ); -final artistDetailProvider = - FutureProvider.family((ref, movieId) async { +final artistDetailProvider = FutureProvider.family(( + ref, + movieId, +) async { return ref.watch(getArtistDetailProvider).call(movieId); }); @@ -111,7 +129,9 @@ final getArtistAllMoviesProvider = Provider( (ref) => GetAllArtistMovies(ref.watch(movieRepositoryProvider)), ); -final artistDetailAllMoviesProvider = -FutureProvider.family((ref, artistId) async { +final artistDetailAllMoviesProvider = FutureProvider.family(( + ref, + artistId, +) async { return ref.watch(getArtistAllMoviesProvider).call(artistId); -}); \ No newline at end of file +}); diff --git a/lib/features/movie/presentation/widgets/movie_card.dart b/lib/features/movie/presentation/widgets/movie_card.dart index c412295..bbf5ab3 100644 --- a/lib/features/movie/presentation/widgets/movie_card.dart +++ b/lib/features/movie/presentation/widgets/movie_card.dart @@ -19,9 +19,11 @@ class MovieCardWidget extends StatelessWidget { children: [ Expanded( child: ClipRRect( - borderRadius: BorderRadius.circular(12), // Adjust radius as needed + borderRadius: BorderRadius.circular( + 12, + ), // Adjust radius as needed child: CachedNetworkImage( - imageUrl: '$IMAGE_URL${movie.posterPath}', + imageUrl: '$imageUrl${movie.posterPath}', fit: BoxFit.cover, ), ), @@ -31,4 +33,4 @@ class MovieCardWidget extends StatelessWidget { ), ); } -} \ No newline at end of file +} diff --git a/lib/features/tv_series/data/datasources/tv_series_remote_data_source.dart b/lib/features/tv_series/data/datasources/tv_series_remote_data_source.dart index 475f6db..11d2102 100644 --- a/lib/features/tv_series/data/datasources/tv_series_remote_data_source.dart +++ b/lib/features/tv_series/data/datasources/tv_series_remote_data_source.dart @@ -9,32 +9,40 @@ class TvSeriesRemoteDataSource { TvSeriesRemoteDataSource(this.dio); Future> getAiringToday(int page) async { - final response = - await dio.get('tv/airing_today', queryParameters: {'page': page}); + final response = await dio.get( + 'tv/airing_today', + queryParameters: {'page': page}, + ); return (response.data['results'] as List) .map((e) => TvSeriesModel.fromJson(e as Map)) .toList(); } Future> getOnTheAir(int page) async { - final response = - await dio.get('tv/on_the_air', queryParameters: {'page': page}); + final response = await dio.get( + 'tv/on_the_air', + queryParameters: {'page': page}, + ); return (response.data['results'] as List) .map((e) => TvSeriesModel.fromJson(e as Map)) .toList(); } Future> getPopular(int page) async { - final response = - await dio.get('tv/popular', queryParameters: {'page': page}); + final response = await dio.get( + 'tv/popular', + queryParameters: {'page': page}, + ); return (response.data['results'] as List) .map((e) => TvSeriesModel.fromJson(e as Map)) .toList(); } Future> getUpcoming(int page) async { - final response = - await dio.get('tv/upcoming', queryParameters: {'page': page}); + final response = await dio.get( + 'tv/upcoming', + queryParameters: {'page': page}, + ); return (response.data['results'] as List) .map((e) => TvSeriesModel.fromJson(e as Map)) .toList(); @@ -45,7 +53,9 @@ class TvSeriesRemoteDataSource { if (response.statusCode == 200 && response.data != null && response.data is Map) { - return TvSeriesDetailModel.fromJson(response.data as Map); + return TvSeriesDetailModel.fromJson( + response.data as Map, + ); } else { throw Exception('Failed to load TV series detail: Invalid response'); } @@ -54,9 +64,7 @@ class TvSeriesRemoteDataSource { Future> searchTvSeries(String query) async { final response = await dio.get( 'search/tv', - queryParameters: { - 'query': query, - }, + queryParameters: {'query': query}, ); return (response.data['results'] as List) .map((e) => TvSeriesModel.fromJson(e as Map)) @@ -74,4 +82,4 @@ class TvSeriesRemoteDataSource { final response = await dio.get('tv/$tvSeriesId/credits'); return TvSeriesCreditModel.fromJson(response.data as Map); } -} \ No newline at end of file +} diff --git a/lib/features/tv_series/data/models/tv_series_credit_model.dart b/lib/features/tv_series/data/models/tv_series_credit_model.dart index 11e6364..0a473d9 100644 --- a/lib/features/tv_series/data/models/tv_series_credit_model.dart +++ b/lib/features/tv_series/data/models/tv_series_credit_model.dart @@ -11,7 +11,8 @@ class TvSeriesCreditModel with _$TvSeriesCreditModel { List? crew, }) = _TvSeriesCreditModel; - factory TvSeriesCreditModel.fromJson(Map json) => _$TvSeriesCreditModelFromJson(json); + factory TvSeriesCreditModel.fromJson(Map json) => + _$TvSeriesCreditModelFromJson(json); } @freezed @@ -52,4 +53,4 @@ class Crew with _$Crew { }) = _Crew; factory Crew.fromJson(Map json) => _$CrewFromJson(json); -} \ No newline at end of file +} diff --git a/lib/features/tv_series/data/models/tv_series_detail_model.dart b/lib/features/tv_series/data/models/tv_series_detail_model.dart index 0e1c4f7..0780b0a 100644 --- a/lib/features/tv_series/data/models/tv_series_detail_model.dart +++ b/lib/features/tv_series/data/models/tv_series_detail_model.dart @@ -21,4 +21,4 @@ class TvSeriesDetailModel with _$TvSeriesDetailModel { factory TvSeriesDetailModel.fromJson(Map json) => _$TvSeriesDetailModelFromJson(json); -} \ No newline at end of file +} diff --git a/lib/features/tv_series/data/models/tv_series_model.dart b/lib/features/tv_series/data/models/tv_series_model.dart index 08ab740..714f330 100644 --- a/lib/features/tv_series/data/models/tv_series_model.dart +++ b/lib/features/tv_series/data/models/tv_series_model.dart @@ -14,4 +14,4 @@ class TvSeriesModel with _$TvSeriesModel { factory TvSeriesModel.fromJson(Map json) => _$TvSeriesModelFromJson(json); -} \ No newline at end of file +} diff --git a/lib/features/tv_series/data/repositories/tv_series_repository_impl.dart b/lib/features/tv_series/data/repositories/tv_series_repository_impl.dart index 5406b12..a58d318 100644 --- a/lib/features/tv_series/data/repositories/tv_series_repository_impl.dart +++ b/lib/features/tv_series/data/repositories/tv_series_repository_impl.dart @@ -4,7 +4,6 @@ import 'package:flutter_movie_clean_architecture/features/tv_series/domain/entit import 'package:flutter_movie_clean_architecture/features/tv_series/domain/entities/tv_series_detail.dart'; import 'package:flutter_movie_clean_architecture/features/tv_series/domain/repositories/entities/tv_series_repository.dart'; - class TvSeriesRepositoryImpl implements TvSeriesRepository { final TvSeriesRemoteDataSource remoteDataSource; @@ -14,12 +13,14 @@ class TvSeriesRepositoryImpl implements TvSeriesRepository { Future> getAiringToday(int page) async { final models = await remoteDataSource.getAiringToday(page); return models - .map((e) => TvSeries( - id: e.id, - name: e.name, - posterPath: e.posterPath, - overview: e.overview, - )) + .map( + (e) => TvSeries( + id: e.id, + name: e.name, + posterPath: e.posterPath, + overview: e.overview, + ), + ) .toList(); } @@ -27,12 +28,14 @@ class TvSeriesRepositoryImpl implements TvSeriesRepository { Future> getOnTheAir(int page) async { final models = await remoteDataSource.getOnTheAir(page); return models - .map((e) => TvSeries( - id: e.id, - name: e.name, - posterPath: e.posterPath, - overview: e.overview, - )) + .map( + (e) => TvSeries( + id: e.id, + name: e.name, + posterPath: e.posterPath, + overview: e.overview, + ), + ) .toList(); } @@ -40,12 +43,14 @@ class TvSeriesRepositoryImpl implements TvSeriesRepository { Future> getPopular(int page) async { final models = await remoteDataSource.getPopular(page); return models - .map((e) => TvSeries( - id: e.id, - name: e.name, - posterPath: e.posterPath, - overview: e.overview, - )) + .map( + (e) => TvSeries( + id: e.id, + name: e.name, + posterPath: e.posterPath, + overview: e.overview, + ), + ) .toList(); } @@ -53,12 +58,14 @@ class TvSeriesRepositoryImpl implements TvSeriesRepository { Future> getUpcoming(int page) async { final models = await remoteDataSource.getUpcoming(page); return models - .map((e) => TvSeries( - id: e.id, - name: e.name, - posterPath: e.posterPath, - overview: e.overview, - )) + .map( + (e) => TvSeries( + id: e.id, + name: e.name, + posterPath: e.posterPath, + overview: e.overview, + ), + ) .toList(); } @@ -84,12 +91,14 @@ class TvSeriesRepositoryImpl implements TvSeriesRepository { Future> searchTvSeries(String query) async { final models = await remoteDataSource.searchTvSeries(query); return models - .map((e) => TvSeries( - id: e.id, - name: e.name, - posterPath: e.posterPath, - overview: e.overview, - )) + .map( + (e) => TvSeries( + id: e.id, + name: e.name, + posterPath: e.posterPath, + overview: e.overview, + ), + ) .toList(); } @@ -97,12 +106,14 @@ class TvSeriesRepositoryImpl implements TvSeriesRepository { Future> getRecommendedTvSeries(int tvSeriesId) async { final models = await remoteDataSource.getRecommendedTvSeries(tvSeriesId); return models - .map((e) => TvSeries( - id: e.id, - name: e.name, - posterPath: e.posterPath, - overview: e.overview, - )) + .map( + (e) => TvSeries( + id: e.id, + name: e.name, + posterPath: e.posterPath, + overview: e.overview, + ), + ) .toList(); } @@ -110,4 +121,4 @@ class TvSeriesRepositoryImpl implements TvSeriesRepository { Future getTvSeriesCredits(int tvSeriesId) async { return await remoteDataSource.getTvSeriesCredits(tvSeriesId); } -} \ No newline at end of file +} diff --git a/lib/features/tv_series/domain/entities/tv_series.dart b/lib/features/tv_series/domain/entities/tv_series.dart index eddb779..575a1be 100644 --- a/lib/features/tv_series/domain/entities/tv_series.dart +++ b/lib/features/tv_series/domain/entities/tv_series.dart @@ -10,4 +10,4 @@ class TvSeries { required this.posterPath, required this.overview, }); -} \ No newline at end of file +} diff --git a/lib/features/tv_series/domain/entities/tv_series_detail.dart b/lib/features/tv_series/domain/entities/tv_series_detail.dart index 0fa59a8..f104171 100644 --- a/lib/features/tv_series/domain/entities/tv_series_detail.dart +++ b/lib/features/tv_series/domain/entities/tv_series_detail.dart @@ -24,4 +24,4 @@ class TvSeriesDetail { this.numberOfEpisodes, this.numberOfSeasons, }); -} \ No newline at end of file +} diff --git a/lib/features/tv_series/domain/repositories/entities/tv_series_repository.dart b/lib/features/tv_series/domain/repositories/entities/tv_series_repository.dart index 89acbac..a9a61e9 100644 --- a/lib/features/tv_series/domain/repositories/entities/tv_series_repository.dart +++ b/lib/features/tv_series/domain/repositories/entities/tv_series_repository.dart @@ -11,4 +11,4 @@ abstract class TvSeriesRepository { Future> searchTvSeries(String query); Future> getRecommendedTvSeries(int tvSeriesId); Future getTvSeriesCredits(int tvSeriesId); -} \ No newline at end of file +} diff --git a/lib/features/tv_series/domain/repositories/usecases/get_airing_today.dart b/lib/features/tv_series/domain/repositories/usecases/get_airing_today.dart index ff40635..24209dc 100644 --- a/lib/features/tv_series/domain/repositories/usecases/get_airing_today.dart +++ b/lib/features/tv_series/domain/repositories/usecases/get_airing_today.dart @@ -7,4 +7,4 @@ class GetAiringToday { GetAiringToday(this.repository); Future> call(int page) => repository.getAiringToday(page); -} \ No newline at end of file +} diff --git a/lib/features/tv_series/domain/repositories/usecases/get_on_the_air.dart b/lib/features/tv_series/domain/repositories/usecases/get_on_the_air.dart index 0026a62..be68f39 100644 --- a/lib/features/tv_series/domain/repositories/usecases/get_on_the_air.dart +++ b/lib/features/tv_series/domain/repositories/usecases/get_on_the_air.dart @@ -7,4 +7,4 @@ class GetOnTheAir { GetOnTheAir(this.repository); Future> call(int page) => repository.getOnTheAir(page); -} \ No newline at end of file +} diff --git a/lib/features/tv_series/domain/repositories/usecases/get_popular_tv_series.dart b/lib/features/tv_series/domain/repositories/usecases/get_popular_tv_series.dart index 3125e1a..846a777 100644 --- a/lib/features/tv_series/domain/repositories/usecases/get_popular_tv_series.dart +++ b/lib/features/tv_series/domain/repositories/usecases/get_popular_tv_series.dart @@ -7,4 +7,4 @@ class GetPopularTvSeries { GetPopularTvSeries(this.repository); Future> call(int page) => repository.getPopular(page); -} \ No newline at end of file +} diff --git a/lib/features/tv_series/domain/repositories/usecases/get_recommended_tv_series.dart b/lib/features/tv_series/domain/repositories/usecases/get_recommended_tv_series.dart index 635175f..395eaa8 100644 --- a/lib/features/tv_series/domain/repositories/usecases/get_recommended_tv_series.dart +++ b/lib/features/tv_series/domain/repositories/usecases/get_recommended_tv_series.dart @@ -8,4 +8,4 @@ class GetRecommendedTvSeries { Future> call(int tvSeriesId) => repository.getRecommendedTvSeries(tvSeriesId); -} \ No newline at end of file +} diff --git a/lib/features/tv_series/domain/repositories/usecases/get_tv_series_credits.dart b/lib/features/tv_series/domain/repositories/usecases/get_tv_series_credits.dart index 73a234f..d98b0b3 100644 --- a/lib/features/tv_series/domain/repositories/usecases/get_tv_series_credits.dart +++ b/lib/features/tv_series/domain/repositories/usecases/get_tv_series_credits.dart @@ -6,5 +6,6 @@ class GetTvSeriesCredits { GetTvSeriesCredits(this.repository); - Future call(int tvSeriesId) => repository.getTvSeriesCredits(tvSeriesId); -} \ No newline at end of file + Future call(int tvSeriesId) => + repository.getTvSeriesCredits(tvSeriesId); +} diff --git a/lib/features/tv_series/domain/repositories/usecases/get_tv_series_detail.dart b/lib/features/tv_series/domain/repositories/usecases/get_tv_series_detail.dart index 4b559d7..7caf584 100644 --- a/lib/features/tv_series/domain/repositories/usecases/get_tv_series_detail.dart +++ b/lib/features/tv_series/domain/repositories/usecases/get_tv_series_detail.dart @@ -6,5 +6,6 @@ class GetTvSeriesDetail { GetTvSeriesDetail(this.repository); - Future call(int tvSeriesId) => repository.getTvSeriesDetail(tvSeriesId); -} \ No newline at end of file + Future call(int tvSeriesId) => + repository.getTvSeriesDetail(tvSeriesId); +} diff --git a/lib/features/tv_series/domain/repositories/usecases/get_tv_series_search.dart b/lib/features/tv_series/domain/repositories/usecases/get_tv_series_search.dart index 44d9577..95ec306 100644 --- a/lib/features/tv_series/domain/repositories/usecases/get_tv_series_search.dart +++ b/lib/features/tv_series/domain/repositories/usecases/get_tv_series_search.dart @@ -9,4 +9,4 @@ class GetTvSeriesSearch { Future> call(String query) async { return await repository.searchTvSeries(query); } -} \ No newline at end of file +} diff --git a/lib/features/tv_series/domain/repositories/usecases/get_upcoming_tv_series.dart b/lib/features/tv_series/domain/repositories/usecases/get_upcoming_tv_series.dart index 429fb01..d9873eb 100644 --- a/lib/features/tv_series/domain/repositories/usecases/get_upcoming_tv_series.dart +++ b/lib/features/tv_series/domain/repositories/usecases/get_upcoming_tv_series.dart @@ -7,4 +7,4 @@ class GetUpcomingTvSeries { GetUpcomingTvSeries(this.repository); Future> call(int page) => repository.getUpcoming(page); -} \ No newline at end of file +} diff --git a/lib/features/tv_series/presentation/pages/airing_today_page.dart b/lib/features/tv_series/presentation/pages/airing_today_page.dart index 32c2bfe..12cfbd7 100644 --- a/lib/features/tv_series/presentation/pages/airing_today_page.dart +++ b/lib/features/tv_series/presentation/pages/airing_today_page.dart @@ -12,7 +12,8 @@ class AiringTodayPage extends ConsumerStatefulWidget { ConsumerState createState() => _AiringTodayPageState(); } -class _AiringTodayPageState extends PaginationConsumerState { +class _AiringTodayPageState + extends PaginationConsumerState { @override Future> fetchData(int page) async { return ref.read(airingTodayTvSeriesProvider(page).future); @@ -25,4 +26,4 @@ class _AiringTodayPageState extends PaginationConsumerState TvSeriesCardWidget(tvSeries: tvSeries), ); } -} \ No newline at end of file +} diff --git a/lib/features/tv_series/presentation/pages/on_the_air_page.dart b/lib/features/tv_series/presentation/pages/on_the_air_page.dart index 567e52c..293fa6d 100644 --- a/lib/features/tv_series/presentation/pages/on_the_air_page.dart +++ b/lib/features/tv_series/presentation/pages/on_the_air_page.dart @@ -12,7 +12,8 @@ class OnTheAirPage extends ConsumerStatefulWidget { ConsumerState createState() => _OnTheAirPageState(); } -class _OnTheAirPageState extends PaginationConsumerState { +class _OnTheAirPageState + extends PaginationConsumerState { @override Future> fetchData(int page) async { return ref.read(onTheAirTvSeriesProvider(page).future); @@ -25,4 +26,4 @@ class _OnTheAirPageState extends PaginationConsumerState itemBuilder: (tvSeries) => TvSeriesCardWidget(tvSeries: tvSeries), ); } -} \ No newline at end of file +} diff --git a/lib/features/tv_series/presentation/pages/popular_tv_series_page.dart b/lib/features/tv_series/presentation/pages/popular_tv_series_page.dart index 1572771..806789d 100644 --- a/lib/features/tv_series/presentation/pages/popular_tv_series_page.dart +++ b/lib/features/tv_series/presentation/pages/popular_tv_series_page.dart @@ -9,10 +9,12 @@ class PopularTvSeriesPage extends ConsumerStatefulWidget { const PopularTvSeriesPage({super.key}); @override - ConsumerState createState() => _PopularTvSeriesPageState(); + ConsumerState createState() => + _PopularTvSeriesPageState(); } -class _PopularTvSeriesPageState extends PaginationConsumerState { +class _PopularTvSeriesPageState + extends PaginationConsumerState { @override Future> fetchData(int page) async { return ref.read(popularTvSeriesProvider(page).future); @@ -25,4 +27,4 @@ class _PopularTvSeriesPageState extends PaginationConsumerState TvSeriesCardWidget(tvSeries: tvSeries), ); } -} \ No newline at end of file +} diff --git a/lib/features/tv_series/presentation/pages/tv_series_detail_page.dart b/lib/features/tv_series/presentation/pages/tv_series_detail_page.dart index e1d63a2..d7078a1 100644 --- a/lib/features/tv_series/presentation/pages/tv_series_detail_page.dart +++ b/lib/features/tv_series/presentation/pages/tv_series_detail_page.dart @@ -18,7 +18,9 @@ class TvSeriesDetailPage extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final tvSeriesDetailAsync = ref.watch(tvSeriesDetailProvider(tvSeriesId)); - final recommendedTvSeriesAsync = ref.watch(recommendedTvSeriesProvider(tvSeriesId)); + final recommendedTvSeriesAsync = ref.watch( + recommendedTvSeriesProvider(tvSeriesId), + ); final tvSeriesCreditsAsync = ref.watch(tvSeriesCreditsProvider(tvSeriesId)); return Scaffold( @@ -32,8 +34,12 @@ class TvSeriesDetailPage extends ConsumerWidget { children: [ TvSeriesDetailInfoSection(tvSeries: tvSeries), TvSeriesDescriptionSection(tvSeries: tvSeries), - RecommendedTvSeriesSection(recommendedTvSeriesAsync: recommendedTvSeriesAsync), - TvSeriesCastSection(tvSeriesCreditsAsync: tvSeriesCreditsAsync), + RecommendedTvSeriesSection( + recommendedTvSeriesAsync: recommendedTvSeriesAsync, + ), + TvSeriesCastSection( + tvSeriesCreditsAsync: tvSeriesCreditsAsync, + ), ], ), ), @@ -48,12 +54,16 @@ class TvSeriesDetailPage extends ConsumerWidget { children: [ const Icon(Icons.error_outline, size: 64, color: Colors.red), const SizedBox(height: 16), - Text('Error loading TV series details', - style: Theme.of(context).textTheme.headlineSmall), + Text( + 'Error loading TV series details', + style: Theme.of(context).textTheme.headlineSmall, + ), const SizedBox(height: 8), - Text(error.toString(), - textAlign: TextAlign.center, - style: const TextStyle(color: Colors.grey)), + Text( + error.toString(), + textAlign: TextAlign.center, + style: const TextStyle(color: Colors.grey), + ), const SizedBox(height: 16), ElevatedButton( onPressed: () => Navigator.pop(context), @@ -73,7 +83,8 @@ class TvSeriesDetailHeader extends ConsumerStatefulWidget { const TvSeriesDetailHeader({super.key, required this.tvSeries}); @override - ConsumerState createState() => _TvSeriesDetailHeaderState(); + ConsumerState createState() => + _TvSeriesDetailHeaderState(); } class _TvSeriesDetailHeaderState extends ConsumerState { @@ -146,28 +157,31 @@ class _TvSeriesDetailHeaderState extends ConsumerState { children: [ widget.tvSeries.posterPath != null ? Image.network( - '$IMAGE_URL${widget.tvSeries.posterPath}', + '$imageUrl${widget.tvSeries.posterPath}', fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) => Container( color: Colors.grey[300], - child: - const Icon(Icons.movie, size: 64, color: Colors.grey), + child: const Icon( + Icons.movie, + size: 64, + color: Colors.grey, + ), ), ) : Container( color: Colors.grey[300], - child: - const Icon(Icons.movie, size: 64, color: Colors.grey), + child: const Icon( + Icons.movie, + size: 64, + color: Colors.grey, + ), ), Container( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, - colors: [ - Colors.transparent, - Colors.black.withOpacity(0.7), - ], + colors: [Colors.transparent, Colors.black.withValues(alpha: 0.7)], ), ), ), @@ -198,7 +212,7 @@ class TvSeriesDetailInfoSection extends StatelessWidget { decoration: BoxDecoration( boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.3), + color: Colors.black.withValues(alpha: 0.3), blurRadius: 8, offset: const Offset(0, 4), ), @@ -206,9 +220,9 @@ class TvSeriesDetailInfoSection extends StatelessWidget { ), child: tvSeries.posterPath != null ? Image.network( - '$IMAGE_URL${tvSeries.posterPath}', + '$imageUrl${tvSeries.posterPath}', fit: BoxFit.cover, - errorBuilder: (_, __, ___) => Container( + errorBuilder: (context, error, stackTrace) => Container( color: Colors.grey[300], child: const Icon(Icons.movie, color: Colors.grey), ), @@ -236,25 +250,39 @@ class TvSeriesDetailInfoSection extends StatelessWidget { Row( children: [ _buildInfoItem( - 'Duration', - formatTvDuration(tvSeries.episodeRunTime)), - _buildInfoItem('First Air Date', tvSeries.firstAirDate ?? 'N/A'), + 'Duration', + formatTvDuration(tvSeries.episodeRunTime), + ), + _buildInfoItem( + 'First Air Date', + tvSeries.firstAirDate ?? 'N/A', + ), ], ), const SizedBox(height: 16), Row( children: [ - _buildInfoItem('Language', - tvSeries.originalLanguage?.toUpperCase() ?? 'N/A'), - _buildInfoItem('Rating', - tvSeries.voteAverage?.toStringAsFixed(1) ?? 'N/A'), + _buildInfoItem( + 'Language', + tvSeries.originalLanguage?.toUpperCase() ?? 'N/A', + ), + _buildInfoItem( + 'Rating', + tvSeries.voteAverage?.toStringAsFixed(1) ?? 'N/A', + ), ], ), const SizedBox(height: 16), Row( children: [ - _buildInfoItem('Episodes', tvSeries.numberOfEpisodes?.toString() ?? 'N/A'), - _buildInfoItem('Seasons', tvSeries.numberOfSeasons?.toString() ?? 'N/A'), + _buildInfoItem( + 'Episodes', + tvSeries.numberOfEpisodes?.toString() ?? 'N/A', + ), + _buildInfoItem( + 'Seasons', + tvSeries.numberOfSeasons?.toString() ?? 'N/A', + ), ], ), ], @@ -272,10 +300,7 @@ class TvSeriesDetailInfoSection extends StatelessWidget { children: [ Text(label), const SizedBox(height: 4), - Text( - value, - style: const TextStyle(color: Colors.grey), - ), + Text(value, style: const TextStyle(color: Colors.grey)), ], ), ); @@ -314,8 +339,9 @@ class TvSeriesDescriptionSection extends ConsumerWidget { ), const SizedBox(height: 12), GestureDetector( - onTap: () => ref.read(tvDescriptionExpandedProvider.notifier).state = - !isExpanded, + onTap: () => + ref.read(tvDescriptionExpandedProvider.notifier).state = + !isExpanded, child: RichText( text: TextSpan( children: [ @@ -348,7 +374,10 @@ class TvSeriesDescriptionSection extends ConsumerWidget { class RecommendedTvSeriesSection extends StatelessWidget { final AsyncValue> recommendedTvSeriesAsync; - const RecommendedTvSeriesSection({super.key, required this.recommendedTvSeriesAsync}); + const RecommendedTvSeriesSection({ + super.key, + required this.recommendedTvSeriesAsync, + }); @override Widget build(BuildContext context) { @@ -377,7 +406,7 @@ class RecommendedTvSeriesSection extends StatelessWidget { scrollDirection: Axis.horizontal, padding: const EdgeInsets.symmetric(horizontal: 16), itemCount: recommendedTvSeries.length, - separatorBuilder: (_, __) => const SizedBox(width: 12), + separatorBuilder: (context, index) => const SizedBox(width: 12), itemBuilder: (context, index) { final tvSeries = recommendedTvSeries[index]; return GestureDetector( @@ -386,11 +415,11 @@ class RecommendedTvSeriesSection extends StatelessWidget { borderRadius: BorderRadius.circular(12), child: tvSeries.posterPath != null ? Image.network( - '$IMAGE_URL${tvSeries.posterPath}', + '$imageUrl${tvSeries.posterPath}', width: 110, height: 160, fit: BoxFit.cover, - errorBuilder: (_, __, ___) => _errorPlaceholder(), + errorBuilder: (context, error, stackTrace) => _errorPlaceholder(), ) : _errorPlaceholder(), ), @@ -458,11 +487,11 @@ class TvSeriesCastSection extends StatelessWidget { padding: const EdgeInsets.symmetric(horizontal: 16), scrollDirection: Axis.horizontal, itemCount: castList.length, - separatorBuilder: (_, __) => const SizedBox(width: 4), + separatorBuilder: (context, index) => const SizedBox(width: 4), itemBuilder: (context, index) { final cast = castList[index]; - final imageUrl = cast.profilePath != null - ? '$IMAGE_URL${cast.profilePath}' + final profileImageUrl = cast.profilePath != null + ? '$imageUrl${cast.profilePath}' : null; return GestureDetector( @@ -474,13 +503,13 @@ class TvSeriesCastSection extends StatelessWidget { child: Column( children: [ ClipOval( - child: imageUrl != null + child: profileImageUrl != null ? Image.network( - imageUrl, + profileImageUrl, width: 70, height: 70, fit: BoxFit.cover, - errorBuilder: (_, __, ___) => + errorBuilder: (context, error, stackTrace) => _placeholder(), ) : _placeholder(), @@ -511,9 +540,7 @@ class TvSeriesCastSection extends StatelessWidget { loading: () => const Padding( padding: EdgeInsets.symmetric(vertical: 24), child: Center( - child: CircularProgressIndicator( - color: Color(0xFF7B2CBF), - ), + child: CircularProgressIndicator(color: Color(0xFF7B2CBF)), ), ), error: (error, _) => Padding( @@ -537,4 +564,4 @@ class TvSeriesCastSection extends StatelessWidget { child: const Icon(Icons.person, color: Colors.grey, size: 35), ); } -} \ No newline at end of file +} diff --git a/lib/features/tv_series/presentation/pages/tv_series_main_page.dart b/lib/features/tv_series/presentation/pages/tv_series_main_page.dart index f98d895..7de5c6b 100644 --- a/lib/features/tv_series/presentation/pages/tv_series_main_page.dart +++ b/lib/features/tv_series/presentation/pages/tv_series_main_page.dart @@ -45,10 +45,7 @@ class _TvSeriesMainPageState extends ConsumerState { icon: Icon(Icons.live_tv), label: 'On The Air', ), - BottomNavigationBarItem( - icon: Icon(Icons.favorite), - label: 'Popular', - ), + BottomNavigationBarItem(icon: Icon(Icons.favorite), label: 'Popular'), BottomNavigationBarItem( icon: Icon(Icons.upcoming), label: 'Upcoming', @@ -57,4 +54,4 @@ class _TvSeriesMainPageState extends ConsumerState { ), ); } -} \ No newline at end of file +} diff --git a/lib/features/tv_series/presentation/pages/upcoming_tv_series_page.dart b/lib/features/tv_series/presentation/pages/upcoming_tv_series_page.dart index bf945ae..96f5fdf 100644 --- a/lib/features/tv_series/presentation/pages/upcoming_tv_series_page.dart +++ b/lib/features/tv_series/presentation/pages/upcoming_tv_series_page.dart @@ -9,10 +9,12 @@ class UpcomingTvSeriesPage extends ConsumerStatefulWidget { const UpcomingTvSeriesPage({super.key}); @override - ConsumerState createState() => _UpcomingTvSeriesPageState(); + ConsumerState createState() => + _UpcomingTvSeriesPageState(); } -class _UpcomingTvSeriesPageState extends PaginationConsumerState { +class _UpcomingTvSeriesPageState + extends PaginationConsumerState { @override Future> fetchData(int page) async { return ref.read(upcomingTvSeriesProvider(page).future); @@ -25,4 +27,4 @@ class _UpcomingTvSeriesPageState extends PaginationConsumerState TvSeriesCardWidget(tvSeries: tvSeries), ); } -} \ No newline at end of file +} diff --git a/lib/features/tv_series/presentation/providers/tv_series_provider.dart b/lib/features/tv_series/presentation/providers/tv_series_provider.dart index b1626e5..60573e1 100644 --- a/lib/features/tv_series/presentation/providers/tv_series_provider.dart +++ b/lib/features/tv_series/presentation/providers/tv_series_provider.dart @@ -26,8 +26,10 @@ final getAiringTodayProvider = Provider( (ref) => GetAiringToday(ref.watch(tvSeriesRepositoryProvider)), ); -final airingTodayTvSeriesProvider = - FutureProvider.family, int>((ref, page) async { +final airingTodayTvSeriesProvider = FutureProvider.family, int>(( + ref, + page, +) async { return ref.watch(getAiringTodayProvider).call(page); }); @@ -35,8 +37,10 @@ final getOnTheAirProvider = Provider( (ref) => GetOnTheAir(ref.watch(tvSeriesRepositoryProvider)), ); -final onTheAirTvSeriesProvider = - FutureProvider.family, int>((ref, page) async { +final onTheAirTvSeriesProvider = FutureProvider.family, int>(( + ref, + page, +) async { return ref.watch(getOnTheAirProvider).call(page); }); @@ -44,8 +48,10 @@ final getPopularTvSeriesProvider = Provider( (ref) => GetPopularTvSeries(ref.watch(tvSeriesRepositoryProvider)), ); -final popularTvSeriesProvider = - FutureProvider.family, int>((ref, page) async { +final popularTvSeriesProvider = FutureProvider.family, int>(( + ref, + page, +) async { return ref.watch(getPopularTvSeriesProvider).call(page); }); @@ -53,8 +59,10 @@ final getUpcomingTvSeriesProvider = Provider( (ref) => GetUpcomingTvSeries(ref.watch(tvSeriesRepositoryProvider)), ); -final upcomingTvSeriesProvider = - FutureProvider.family, int>((ref, page) async { +final upcomingTvSeriesProvider = FutureProvider.family, int>(( + ref, + page, +) async { return ref.watch(getUpcomingTvSeriesProvider).call(page); }); @@ -62,8 +70,10 @@ final getTvSeriesDetailProvider = Provider( (ref) => GetTvSeriesDetail(ref.watch(tvSeriesRepositoryProvider)), ); -final tvSeriesDetailProvider = - FutureProvider.family((ref, tvSeriesId) async { +final tvSeriesDetailProvider = FutureProvider.family(( + ref, + tvSeriesId, +) async { return ref.watch(getTvSeriesDetailProvider).call(tvSeriesId); }); @@ -71,8 +81,10 @@ final getTvSeriesSearchProvider = Provider( (ref) => GetTvSeriesSearch(ref.watch(tvSeriesRepositoryProvider)), ); -final tvSeriesSearchProvider = - FutureProvider.family, String>((ref, query) async { +final tvSeriesSearchProvider = FutureProvider.family, String>(( + ref, + query, +) async { return ref.watch(getTvSeriesSearchProvider).call(query); }); @@ -80,8 +92,10 @@ final getRecommendedTvSeriesProvider = Provider( (ref) => GetRecommendedTvSeries(ref.watch(tvSeriesRepositoryProvider)), ); -final recommendedTvSeriesProvider = - FutureProvider.family, int>((ref, tvSeriesId) async { +final recommendedTvSeriesProvider = FutureProvider.family, int>(( + ref, + tvSeriesId, +) async { return ref.watch(getRecommendedTvSeriesProvider).call(tvSeriesId); }); @@ -89,7 +103,8 @@ final getTvSeriesCreditsProvider = Provider( (ref) => GetTvSeriesCredits(ref.watch(tvSeriesRepositoryProvider)), ); -final tvSeriesCreditsProvider = - FutureProvider.family((ref, tvSeriesId) async { - return ref.watch(getTvSeriesCreditsProvider).call(tvSeriesId); -}); \ No newline at end of file +final tvSeriesCreditsProvider = FutureProvider.family( + (ref, tvSeriesId) async { + return ref.watch(getTvSeriesCreditsProvider).call(tvSeriesId); + }, +); diff --git a/lib/features/tv_series/presentation/widgets/tv_series_card.dart b/lib/features/tv_series/presentation/widgets/tv_series_card.dart index bc6f5c2..6051d3d 100644 --- a/lib/features/tv_series/presentation/widgets/tv_series_card.dart +++ b/lib/features/tv_series/presentation/widgets/tv_series_card.dart @@ -19,9 +19,11 @@ class TvSeriesCardWidget extends StatelessWidget { children: [ Expanded( child: ClipRRect( - borderRadius: BorderRadius.circular(12), // Match movie card radius + borderRadius: BorderRadius.circular( + 12, + ), // Match movie card radius child: CachedNetworkImage( - imageUrl: '$IMAGE_URL${tvSeries.posterPath}', + imageUrl: '$imageUrl${tvSeries.posterPath}', fit: BoxFit.cover, ), ), @@ -31,4 +33,4 @@ class TvSeriesCardWidget extends StatelessWidget { ), ); } -} \ No newline at end of file +} diff --git a/lib/main.dart b/lib/main.dart index 9d406b0..6174f3a 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -22,4 +22,4 @@ class MyApp extends StatelessWidget { ), ); } -} \ No newline at end of file +} diff --git a/lib/presentation/pages/main_tab_page.dart b/lib/presentation/pages/main_tab_page.dart index 267417f..a513ac7 100644 --- a/lib/presentation/pages/main_tab_page.dart +++ b/lib/presentation/pages/main_tab_page.dart @@ -5,7 +5,6 @@ import 'package:flutter_movie_clean_architecture/features/movie/presentation/pag import 'package:flutter_movie_clean_architecture/features/tv_series/presentation/pages/tv_series_main_page.dart'; import 'package:flutter_movie_clean_architecture/presentation/widgets/universal_search.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:go_router/go_router.dart'; class MainTabPage extends ConsumerStatefulWidget { const MainTabPage({super.key}); @@ -14,7 +13,8 @@ class MainTabPage extends ConsumerStatefulWidget { ConsumerState createState() => _MainTabPageState(); } -class _MainTabPageState extends ConsumerState with SingleTickerProviderStateMixin { +class _MainTabPageState extends ConsumerState + with SingleTickerProviderStateMixin { late TabController _tabController; bool _isSearching = false; @@ -68,8 +68,7 @@ class _MainTabPageState extends ConsumerState with SingleTickerProv FavoritesPage(), ], ), - if (_isSearching) - UniversalSearchWidget(onClose: _closeSearch), + if (_isSearching) UniversalSearchWidget(onClose: _closeSearch), ], ), floatingActionButton: Padding( @@ -83,4 +82,4 @@ class _MainTabPageState extends ConsumerState with SingleTickerProv extendBody: true, // allows FAB to float over content ); } -} \ No newline at end of file +} diff --git a/lib/presentation/widgets/universal_search.dart b/lib/presentation/widgets/universal_search.dart index 568d6c3..3a33fe1 100644 --- a/lib/presentation/widgets/universal_search.dart +++ b/lib/presentation/widgets/universal_search.dart @@ -9,13 +9,11 @@ import 'package:go_router/go_router.dart'; class UniversalSearchWidget extends ConsumerStatefulWidget { final VoidCallback onClose; - const UniversalSearchWidget({ - super.key, - required this.onClose, - }); + const UniversalSearchWidget({super.key, required this.onClose}); @override - ConsumerState createState() => _UniversalSearchWidgetState(); + ConsumerState createState() => + _UniversalSearchWidgetState(); } class _UniversalSearchWidgetState extends ConsumerState { @@ -34,9 +32,9 @@ class _UniversalSearchWidgetState extends ConsumerState { setState(() { _searchResults = result.map>((movie) { return { - 'id': movie.id.toString() ?? '', - 'title': movie.title ?? '', - 'image': '$IMAGE_URL${movie.posterPath ?? ''}', + 'id': movie.id.toString(), + 'title': movie.title, + 'image': '$imageUrl${movie.posterPath ?? ''}', 'type': 'movie', }; }).toList(); @@ -63,9 +61,10 @@ class _UniversalSearchWidgetState extends ConsumerState { setState(() { _searchResults = result.map>((tvSeries) { return { - 'id': tvSeries.id.toString() ?? '', - 'title': tvSeries.name ?? '', // TV series use 'name' instead of 'title' - 'image': '$IMAGE_URL${tvSeries.posterPath ?? ''}', + 'id': tvSeries.id.toString(), + 'title': + tvSeries.name, // TV series use 'name' instead of 'title' + 'image': '$imageUrl${tvSeries.posterPath ?? ''}', 'type': 'tv', }; }).toList(); @@ -92,9 +91,9 @@ class _UniversalSearchWidgetState extends ConsumerState { setState(() { _searchResults = result.map>((person) { return { - 'id': person.id.toString() ?? '', - 'title': person.name ?? '', - 'image': '$IMAGE_URL${person.profilePath ?? ''}', + 'id': person.id.toString(), + 'title': person.name, + 'image': '$imageUrl${person.profilePath ?? ''}', 'type': 'celebrity', }; }).toList(); @@ -157,9 +156,7 @@ class _UniversalSearchWidgetState extends ConsumerState { final limitedResults = _searchResults.take(4).toList(); return Container( - constraints: const BoxConstraints( - maxHeight: 400, - ), + constraints: const BoxConstraints(maxHeight: 400), child: ListView.builder( shrinkWrap: true, itemCount: limitedResults.length, @@ -180,7 +177,10 @@ class _UniversalSearchWidgetState extends ConsumerState { } }, child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), + padding: const EdgeInsets.symmetric( + horizontal: 16.0, + vertical: 8.0, + ), child: Row( children: [ ClipRRect( @@ -191,7 +191,7 @@ class _UniversalSearchWidgetState extends ConsumerState { height: 100, fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) => - const Icon(Icons.broken_image, size: 70), + const Icon(Icons.broken_image, size: 70), ), ), const SizedBox(width: 16), @@ -222,7 +222,7 @@ class _UniversalSearchWidgetState extends ConsumerState { @override Widget build(BuildContext context) { return Scaffold( - backgroundColor: Colors.black.withOpacity(0.5), + backgroundColor: Colors.black.withValues(alpha: 0.5), body: SafeArea( child: Column( children: [ @@ -233,7 +233,10 @@ class _UniversalSearchWidgetState extends ConsumerState { children: [ // AppBar-like header Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0), + padding: const EdgeInsets.symmetric( + horizontal: 8.0, + vertical: 4.0, + ), child: Row( children: [ Expanded( @@ -241,7 +244,8 @@ class _UniversalSearchWidgetState extends ConsumerState { controller: _searchController, autofocus: true, decoration: const InputDecoration( - hintText: 'Search movies, TV series, and celebrities...', + hintText: + 'Search movies, TV series, and celebrities...', border: InputBorder.none, ), onChanged: _performSearch, @@ -249,17 +253,21 @@ class _UniversalSearchWidgetState extends ConsumerState { ), _isLoading ? const Padding( - padding: EdgeInsets.all(12.0), - child: SizedBox( - width: 20, - height: 20, - child: CircularProgressIndicator(strokeWidth: 2), - ), - ) + padding: EdgeInsets.all(12.0), + child: SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator( + strokeWidth: 2, + ), + ), + ) : IconButton( - icon: const Icon(Icons.close), - onPressed: _searchController.text.isNotEmpty ? _clearSearch : widget.onClose, - ), + icon: const Icon(Icons.close), + onPressed: _searchController.text.isNotEmpty + ? _clearSearch + : widget.onClose, + ), ], ), ), @@ -290,8 +298,7 @@ class _UniversalSearchWidgetState extends ConsumerState { ), const SizedBox(height: 8), // Search Results - if (_searchController.text.isNotEmpty) - _buildSearchResults(), + if (_searchController.text.isNotEmpty) _buildSearchResults(), ], ), ), @@ -299,9 +306,7 @@ class _UniversalSearchWidgetState extends ConsumerState { Expanded( child: GestureDetector( onTap: widget.onClose, - child: Container( - color: Colors.transparent, - ), + child: Container(color: Colors.transparent), ), ), ], @@ -309,4 +314,4 @@ class _UniversalSearchWidgetState extends ConsumerState { ), ); } -} \ No newline at end of file +} diff --git a/lib/routing/app_router.dart b/lib/routing/app_router.dart index 7c4411f..3f331d2 100644 --- a/lib/routing/app_router.dart +++ b/lib/routing/app_router.dart @@ -6,10 +6,7 @@ import 'package:go_router/go_router.dart'; final router = GoRouter( routes: [ - GoRoute( - path: '/', - builder: (_, __) => const MainTabPage(), - ), + GoRoute(path: '/', builder: (context, state) => const MainTabPage()), GoRoute( path: '/movie/:id', builder: (context, state) { diff --git a/pubspec.lock b/pubspec.lock index c6929ac..5ae506b 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,18 +5,18 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: da0d9209ca76bde579f2da330aeb9df62b6319c834fa7baae052021b0462401f + sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7" url: "https://pub.dev" source: hosted - version: "85.0.0" + version: "67.0.0" analyzer: - dependency: transitive + dependency: "direct dev" description: name: analyzer - sha256: "974859dc0ff5f37bc4313244b3218c791810d03ab3470a579580279ba971a48d" + sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d" url: "https://pub.dev" source: hosted - version: "7.7.1" + version: "6.4.1" args: dependency: transitive description: @@ -45,10 +45,10 @@ packages: dependency: transitive description: name: build - sha256: "51dc711996cbf609b90cbe5b335bbce83143875a9d58e4b5c6d3c4f684d3dda7" + sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" url: "https://pub.dev" source: hosted - version: "2.5.4" + version: "2.4.1" build_config: dependency: transitive description: @@ -69,26 +69,26 @@ packages: dependency: transitive description: name: build_resolvers - sha256: ee4257b3f20c0c90e72ed2b57ad637f694ccba48839a821e87db762548c22a62 + sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a" url: "https://pub.dev" source: hosted - version: "2.5.4" + version: "2.4.2" build_runner: dependency: "direct dev" description: name: build_runner - sha256: "382a4d649addbfb7ba71a3631df0ec6a45d5ab9b098638144faf27f02778eb53" + sha256: "028819cfb90051c6b5440c7e574d1896f8037e3c96cf17aaeb054c9311cfbf4d" url: "https://pub.dev" source: hosted - version: "2.5.4" + version: "2.4.13" build_runner_core: dependency: transitive description: name: build_runner_core - sha256: "85fbbb1036d576d966332a3f5ce83f2ce66a40bea1a94ad2d5fc29a19a0d3792" + sha256: f8126682b87a7282a339b871298cc12009cb67109cfa1614d6436fb0289193e0 url: "https://pub.dev" source: hosted - version: "9.1.2" + version: "7.3.2" built_collection: dependency: transitive description: @@ -157,10 +157,10 @@ packages: dependency: transitive description: name: code_builder - sha256: "0ec10bf4a89e4c613960bf1e8b42c64127021740fb21640c29c909826a5eea3e" + sha256: "11654819532ba94c34de52ff5feb52bd81cba1de00ef2ed622fd50295f9d4243" url: "https://pub.dev" source: hosted - version: "4.10.1" + version: "4.11.0" collection: dependency: transitive description: @@ -197,10 +197,10 @@ packages: dependency: transitive description: name: dart_style - sha256: "8a0e5fba27e8ee025d2ffb4ee820b4e6e2cf5e4246a6b1a477eb66866947e0bb" + sha256: "99e066ce75c89d6b29903d788a7bb9369cf754f7b24bf70bf4b6d6d6b26853b9" url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "2.3.6" dio: dependency: "direct main" description: @@ -266,10 +266,10 @@ packages: dependency: "direct dev" description: name: flutter_lints - sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" + sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1" url: "https://pub.dev" source: hosted - version: "5.0.0" + version: "6.0.0" flutter_riverpod: dependency: "direct main" description: @@ -292,10 +292,10 @@ packages: dependency: "direct dev" description: name: freezed - sha256: "59a584c24b3acdc5250bb856d0d3e9c0b798ed14a4af1ddb7dc1c7b41df91c9c" + sha256: a434911f643466d78462625df76fd9eb13e57348ff43fe1f77bbe909522c67a1 url: "https://pub.dev" source: hosted - version: "2.5.8" + version: "2.5.2" freezed_annotation: dependency: "direct main" description: @@ -352,6 +352,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + hive_generator: + dependency: "direct dev" + description: + name: hive_generator + sha256: "06cb8f58ace74de61f63500564931f9505368f45f98958bd7a6c35ba24159db4" + url: "https://pub.dev" + source: hosted + version: "2.0.1" http: dependency: transitive description: @@ -404,10 +412,10 @@ packages: dependency: "direct dev" description: name: json_serializable - sha256: c50ef5fc083d5b5e12eef489503ba3bf5ccc899e487d691584699b4bdefeea8c + sha256: ea1432d167339ea9b5bb153f0571d0039607a873d6e04e0117af043f14a1fd4b url: "https://pub.dev" source: hosted - version: "6.9.5" + version: "6.8.0" leak_tracker: dependency: transitive description: @@ -436,10 +444,10 @@ packages: dependency: transitive description: name: lints - sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 + sha256: a5e2b223cb7c9c8efdc663ef484fdd95bb243bff242ef5b13e26883547fce9a0 url: "https://pub.dev" source: hosted - version: "5.1.1" + version: "6.0.0" logging: dependency: transitive description: @@ -572,10 +580,10 @@ packages: dependency: transitive description: name: pool - sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" + sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d" url: "https://pub.dev" source: hosted - version: "1.5.1" + version: "1.5.2" pretty_dio_logger: dependency: "direct main" description: @@ -628,10 +636,10 @@ packages: dependency: transitive description: name: shelf_web_socket - sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" + sha256: cc36c297b52866d203dbf9332263c94becc2fe0ceaa9681d07b6ef9807023b67 url: "https://pub.dev" source: hosted - version: "3.0.0" + version: "2.0.1" sky_engine: dependency: transitive description: flutter @@ -641,18 +649,18 @@ packages: dependency: transitive description: name: source_gen - sha256: "35c8150ece9e8c8d263337a265153c3329667640850b9304861faea59fc98f6b" + sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "1.5.0" source_helper: dependency: transitive description: name: source_helper - sha256: a447acb083d3a5ef17f983dd36201aeea33fedadb3228fa831f2f0c92f0f3aca + sha256: "86d247119aedce8e63f4751bd9626fc9613255935558447569ad42f9f5b48b3c" url: "https://pub.dev" source: hosted - version: "1.3.7" + version: "1.3.5" source_span: dependency: transitive description: @@ -681,10 +689,10 @@ packages: dependency: transitive description: name: sqflite_android - sha256: "2b3070c5fa881839f8b402ee4a39c1b4d561704d4ebbbcfb808a119bc2a1701b" + sha256: ecd684501ebc2ae9a83536e8b15731642b9570dc8623e0073d227d0ee2bfea88 url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.2+2" sqflite_common: dependency: transitive description: @@ -809,10 +817,10 @@ packages: dependency: transitive description: name: vm_service - sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 + sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60" url: "https://pub.dev" source: hosted - version: "15.0.0" + version: "15.0.2" watcher: dependency: transitive description: @@ -862,5 +870,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.8.0 <4.0.0" + dart: ">=3.9.0 <4.0.0" flutter: ">=3.29.0" diff --git a/pubspec.yaml b/pubspec.yaml index 576fab6..0cd631a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -19,7 +19,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev version: 1.0.0+1 environment: - sdk: ^3.6.0 + sdk: ^3.9.0 # Dependencies specify other packages that your package needs in order to work. # To automatically upgrade your package dependencies to the latest versions @@ -55,10 +55,12 @@ dev_dependencies: # activated in the `analysis_options.yaml` file located at the root of your # package. See that file for information about deactivating specific lint # rules and activating additional ones. - flutter_lints: ^5.0.0 - build_runner: ^2.4.15 + flutter_lints: ^6.0.0 + build_runner: ^2.4.0 freezed: ^2.4.7 - json_serializable: ^6.9.5 + json_serializable: ^6.8.0 + hive_generator: ^2.0.1 + analyzer: ^6.4.1 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec