Skip to content

Commit 67e43e9

Browse files
committed
- Implemented TV series feature with main and detail pages
- Added TV series models, entities, repositories, and use cases - Integrated TV series into main tab navigation and routing - Updated movie main page UI
1 parent 4dda77d commit 67e43e9

24 files changed

+1031
-41
lines changed

lib/features/movie/presentation/pages/movie_main_page.dart

Lines changed: 6 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
22
import 'package:flutter_movie_clean_architecture/features/movie/presentation/pages/popular_page.dart';
33
import 'package:flutter_movie_clean_architecture/features/movie/presentation/pages/top_rated_page.dart';
44
import 'package:flutter_movie_clean_architecture/features/movie/presentation/pages/up_coming_page.dart';
5-
import 'package:flutter_movie_clean_architecture/features/movie/presentation/widgets/movie_search.dart';
65
import 'package:flutter_riverpod/flutter_riverpod.dart';
76

87
import 'now_playing_page.dart';
@@ -16,7 +15,6 @@ class MovieMainPage extends ConsumerStatefulWidget {
1615

1716
class _MovieMainPageState extends ConsumerState<MovieMainPage> {
1817
int _selectedIndex = 0;
19-
bool _isSearching = false;
2018

2119
final _pages = [
2220
const NowPlayingPage(),
@@ -25,70 +23,39 @@ class _MovieMainPageState extends ConsumerState<MovieMainPage> {
2523
const UpComingPage(),
2624
];
2725

28-
final _pageLabels = [
29-
'Flutter Movie',
30-
'Popular',
31-
'Top Rated',
32-
'Upcoming',
33-
];
34-
3526
void _onItemTapped(int index) {
3627
setState(() {
3728
_selectedIndex = index;
38-
_isSearching = false;
39-
});
40-
}
41-
42-
void _toggleSearch() {
43-
setState(() {
44-
_isSearching = !_isSearching;
45-
});
46-
}
47-
48-
void _closeSearch() {
49-
setState(() {
50-
_isSearching = false;
5129
});
5230
}
5331

5432
@override
5533
Widget build(BuildContext context) {
5634
return Scaffold(
5735
appBar: AppBar(
58-
title: Text(_pageLabels[_selectedIndex]),
59-
),
60-
body: Stack(
61-
children: [
62-
// Main content
63-
_pages[_selectedIndex],
64-
// Search overlay
65-
if (_isSearching)
66-
MovieSearchWidget(onClose: _closeSearch),
67-
],
68-
),
69-
floatingActionButton: FloatingActionButton(
70-
onPressed: _toggleSearch,
71-
child: const Icon(Icons.search),
36+
title: const Text(''),
37+
automaticallyImplyLeading: false,
7238
),
39+
body: _pages[_selectedIndex],
7340
bottomNavigationBar: BottomNavigationBar(
7441
currentIndex: _selectedIndex,
7542
onTap: _onItemTapped,
7643
type: BottomNavigationBarType.fixed,
7744
items: const [
7845
BottomNavigationBarItem(
79-
icon: Icon(Icons.movie),
46+
icon: Icon(Icons.play_circle_fill),
8047
label: 'Now Playing',
8148
),
8249
BottomNavigationBarItem(
83-
icon: Icon(Icons.trending_up),
50+
icon: Icon(Icons.favorite),
8451
label: 'Popular',
8552
),
8653
BottomNavigationBarItem(
8754
icon: Icon(Icons.star),
8855
label: 'Top Rated',
8956
),
9057
BottomNavigationBarItem(
91-
icon: Icon(Icons.keyboard_arrow_down),
58+
icon: Icon(Icons.upcoming),
9259
label: 'Upcoming',
9360
),
9461
],
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import 'package:dio/dio.dart';
2+
import 'package:flutter_movie_clean_architecture/features/tv_series/data/models/tv_series_detail_model.dart';
3+
import 'package:flutter_movie_clean_architecture/features/tv_series/data/models/tv_series_model.dart';
4+
import 'package:flutter_movie_clean_architecture/features/tv_series/domain/entities/tv_series.dart';
5+
import 'package:flutter_movie_clean_architecture/features/tv_series/domain/entities/tv_series_detail.dart';
6+
7+
class TvSeriesRemoteDataSource {
8+
final Dio dio;
9+
10+
TvSeriesRemoteDataSource(this.dio);
11+
12+
Future<List<TvSeriesModel>> getAiringToday(int page) async {
13+
final response =
14+
await dio.get('tv/airing_today', queryParameters: {'page': page});
15+
return (response.data['results'] as List)
16+
.map((e) => TvSeriesModel.fromJson(e as Map<String, dynamic>))
17+
.toList();
18+
}
19+
20+
Future<List<TvSeriesModel>> getOnTheAir(int page) async {
21+
final response =
22+
await dio.get('tv/on_the_air', queryParameters: {'page': page});
23+
return (response.data['results'] as List)
24+
.map((e) => TvSeriesModel.fromJson(e as Map<String, dynamic>))
25+
.toList();
26+
}
27+
28+
Future<List<TvSeriesModel>> getPopular(int page) async {
29+
final response =
30+
await dio.get('tv/popular', queryParameters: {'page': page});
31+
return (response.data['results'] as List)
32+
.map((e) => TvSeriesModel.fromJson(e as Map<String, dynamic>))
33+
.toList();
34+
}
35+
36+
Future<List<TvSeriesModel>> getUpcoming(int page) async {
37+
final response =
38+
await dio.get('tv/upcoming', queryParameters: {'page': page});
39+
return (response.data['results'] as List)
40+
.map((e) => TvSeriesModel.fromJson(e as Map<String, dynamic>))
41+
.toList();
42+
}
43+
44+
Future<TvSeriesDetailModel> getTvSeriesDetail(int id) async {
45+
final response = await dio.get('/tv/$id');
46+
if (response.statusCode == 200 &&
47+
response.data != null &&
48+
response.data is Map<String, dynamic>) {
49+
return TvSeriesDetailModel.fromJson(response.data as Map<String, dynamic>);
50+
} else {
51+
throw Exception('Failed to load TV series detail: Invalid response');
52+
}
53+
}
54+
55+
Future<List<TvSeriesModel>> searchTvSeries(String query) async {
56+
final response = await dio.get(
57+
'search/tv',
58+
queryParameters: {
59+
'query': query,
60+
},
61+
);
62+
return (response.data['results'] as List)
63+
.map((e) => TvSeriesModel.fromJson(e as Map<String, dynamic>))
64+
.toList();
65+
}
66+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import 'package:freezed_annotation/freezed_annotation.dart';
2+
import 'package:flutter_movie_clean_architecture/features/tv_series/domain/entities/tv_series_detail.dart';
3+
4+
part 'tv_series_detail_model.freezed.dart';
5+
part 'tv_series_detail_model.g.dart';
6+
7+
@freezed
8+
class TvSeriesDetailModel with _$TvSeriesDetailModel {
9+
const factory TvSeriesDetailModel({
10+
required int id,
11+
required String name,
12+
@JsonKey(name: 'poster_path') String? posterPath,
13+
required String overview,
14+
@JsonKey(name: 'first_air_date') required String firstAirDate,
15+
@JsonKey(name: 'vote_average') required double voteAverage,
16+
}) = _TvSeriesDetailModel;
17+
18+
factory TvSeriesDetailModel.fromJson(Map<String, dynamic> json) =>
19+
_$TvSeriesDetailModelFromJson(json);
20+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import 'package:freezed_annotation/freezed_annotation.dart';
2+
import 'package:flutter_movie_clean_architecture/features/tv_series/domain/entities/tv_series.dart';
3+
4+
part 'tv_series_model.freezed.dart';
5+
part 'tv_series_model.g.dart';
6+
7+
@freezed
8+
class TvSeriesModel with _$TvSeriesModel {
9+
const factory TvSeriesModel({
10+
required int id,
11+
required String name,
12+
@JsonKey(name: 'poster_path') String? posterPath,
13+
required String overview,
14+
}) = _TvSeriesModel;
15+
16+
factory TvSeriesModel.fromJson(Map<String, dynamic> json) => _$TvSeriesModelFromJson(json);
17+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import 'package:flutter_movie_clean_architecture/features/tv_series/data/datasources/tv_series_remote_data_source.dart';
2+
import 'package:flutter_movie_clean_architecture/features/tv_series/data/models/tv_series_detail_model.dart';
3+
import 'package:flutter_movie_clean_architecture/features/tv_series/data/models/tv_series_model.dart';
4+
import 'package:flutter_movie_clean_architecture/features/tv_series/domain/entities/tv_series.dart';
5+
import 'package:flutter_movie_clean_architecture/features/tv_series/domain/entities/tv_series_detail.dart';
6+
import 'package:flutter_movie_clean_architecture/features/tv_series/domain/repositories/entities/tv_series_repository.dart';
7+
8+
class TvSeriesRepositoryImpl implements TvSeriesRepository {
9+
final TvSeriesRemoteDataSource remoteDataSource;
10+
11+
TvSeriesRepositoryImpl(this.remoteDataSource);
12+
13+
@override
14+
Future<List<TvSeries>> getAiringToday(int page) async {
15+
final models = await remoteDataSource.getAiringToday(page);
16+
return models
17+
.map((e) => TvSeries(
18+
id: e.id,
19+
name: e.name,
20+
posterPath: e.posterPath,
21+
overview: e.overview,
22+
))
23+
.toList();
24+
}
25+
26+
@override
27+
Future<List<TvSeries>> getOnTheAir(int page) async {
28+
final models = await remoteDataSource.getOnTheAir(page);
29+
return models
30+
.map((e) => TvSeries(
31+
id: e.id,
32+
name: e.name,
33+
posterPath: e.posterPath,
34+
overview: e.overview,
35+
))
36+
.toList();
37+
}
38+
39+
@override
40+
Future<List<TvSeries>> getPopular(int page) async {
41+
final models = await remoteDataSource.getPopular(page);
42+
return models
43+
.map((e) => TvSeries(
44+
id: e.id,
45+
name: e.name,
46+
posterPath: e.posterPath,
47+
overview: e.overview,
48+
))
49+
.toList();
50+
}
51+
52+
@override
53+
Future<List<TvSeries>> getUpcoming(int page) async {
54+
final models = await remoteDataSource.getUpcoming(page);
55+
return models
56+
.map((e) => TvSeries(
57+
id: e.id,
58+
name: e.name,
59+
posterPath: e.posterPath,
60+
overview: e.overview,
61+
))
62+
.toList();
63+
}
64+
65+
@override
66+
Future<TvSeriesDetail> getTvSeriesDetail(int tvSeriesId) async {
67+
final model = await remoteDataSource.getTvSeriesDetail(tvSeriesId);
68+
return TvSeriesDetail(
69+
id: model.id,
70+
name: model.name,
71+
posterPath: model.posterPath,
72+
overview: model.overview,
73+
voteAverage: model.voteAverage,
74+
firstAirDate: model.firstAirDate,
75+
);
76+
}
77+
78+
@override
79+
Future<List<TvSeries>> searchTvSeries(String query) async {
80+
final models = await remoteDataSource.searchTvSeries(query);
81+
return models
82+
.map((e) => TvSeries(
83+
id: e.id,
84+
name: e.name,
85+
posterPath: e.posterPath,
86+
overview: e.overview,
87+
))
88+
.toList();
89+
}
90+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
class TvSeries {
2+
final int id;
3+
final String name;
4+
final String? posterPath;
5+
final String overview;
6+
7+
TvSeries({
8+
required this.id,
9+
required this.name,
10+
required this.posterPath,
11+
required this.overview,
12+
});
13+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
class TvSeriesDetail {
2+
final int id;
3+
final String name;
4+
final String? posterPath;
5+
final String overview;
6+
final double voteAverage;
7+
final String firstAirDate;
8+
9+
TvSeriesDetail({
10+
required this.id,
11+
required this.name,
12+
required this.posterPath,
13+
required this.overview,
14+
required this.voteAverage,
15+
required this.firstAirDate,
16+
});
17+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import 'package:flutter_movie_clean_architecture/features/tv_series/data/models/tv_series_detail_model.dart';
2+
import 'package:flutter_movie_clean_architecture/features/tv_series/domain/entities/tv_series.dart';
3+
import 'package:flutter_movie_clean_architecture/features/tv_series/domain/entities/tv_series_detail.dart';
4+
5+
abstract class TvSeriesRepository {
6+
Future<List<TvSeries>> getAiringToday(int page);
7+
Future<List<TvSeries>> getOnTheAir(int page);
8+
Future<List<TvSeries>> getPopular(int page);
9+
Future<List<TvSeries>> getUpcoming(int page);
10+
Future<TvSeriesDetail> getTvSeriesDetail(int tvSeriesId);
11+
Future<List<TvSeries>> searchTvSeries(String query);
12+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import 'package:flutter_movie_clean_architecture/features/tv_series/domain/entities/tv_series.dart';
2+
import 'package:flutter_movie_clean_architecture/features/tv_series/domain/repositories/entities/tv_series_repository.dart';
3+
4+
class GetAiringToday {
5+
final TvSeriesRepository repository;
6+
7+
GetAiringToday(this.repository);
8+
9+
Future<List<TvSeries>> call(int page) => repository.getAiringToday(page);
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import 'package:flutter_movie_clean_architecture/features/tv_series/domain/entities/tv_series.dart';
2+
import 'package:flutter_movie_clean_architecture/features/tv_series/domain/repositories/entities/tv_series_repository.dart';
3+
4+
class GetOnTheAir {
5+
final TvSeriesRepository repository;
6+
7+
GetOnTheAir(this.repository);
8+
9+
Future<List<TvSeries>> call(int page) => repository.getOnTheAir(page);
10+
}

0 commit comments

Comments
 (0)