Skip to content

Commit 8b2acff

Browse files
committed
Add FavoriteMoviesCubit and repository for managing favorite movies
1 parent 466090d commit 8b2acff

File tree

7 files changed

+410
-135
lines changed

7 files changed

+410
-135
lines changed

lib/app/app.dart

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,38 @@
11
import 'package:flutter/material.dart';
22
import 'package:flutter_bloc/flutter_bloc.dart';
3+
import 'package:tmdb_flutter/app/api/movie_api.dart';
4+
import 'package:tmdb_flutter/app/cubit/favorite_movies_cubit.dart';
5+
import 'package:tmdb_flutter/app/cubit/home_cubit.dart';
6+
import 'package:tmdb_flutter/app/data/local/database_helper.dart';
7+
import 'package:tmdb_flutter/app/data/local/favorite_movies_local_data_source.dart';
8+
import 'package:tmdb_flutter/app/data/repository/favorite_movies_repository.dart';
39
import 'package:tmdb_flutter/app/view/main_page.dart';
4-
import 'api/movie_api.dart';
5-
import 'cubit/home_cubit.dart';
6-
import 'view/home_page.dart';
710

811
class App extends StatelessWidget {
912
const App({super.key});
1013

1114
@override
1215
Widget build(BuildContext context) {
13-
return MaterialApp(
14-
title: 'TMDB Movies',
15-
theme: ThemeData(
16-
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
17-
useMaterial3: true,
18-
),
19-
home: BlocProvider(
20-
create: (context) => HomeCubit(MovieAPI()),
21-
child: const MainPage(),
16+
return MultiBlocProvider(
17+
providers: [
18+
BlocProvider(
19+
create: (context) => HomeCubit(MovieAPI()),
20+
),
21+
BlocProvider(
22+
create: (context) => FavoriteMoviesCubit(
23+
FavoriteMoviesRepository(
24+
FavoriteMoviesLocalDataSource(DatabaseHelper()),
25+
),
26+
),
27+
),
28+
],
29+
child: MaterialApp(
30+
title: 'TMDB Movies',
31+
theme: ThemeData(
32+
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
33+
useMaterial3: true,
34+
),
35+
home: const MainPage(),
2236
),
2337
);
2438
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import 'package:flutter_bloc/flutter_bloc.dart';
2+
import 'package:tmdb_flutter/app/api/models/movie_responses.dart';
3+
import 'package:tmdb_flutter/app/cubit/favorite_movies_state.dart';
4+
import 'package:tmdb_flutter/app/data/repository/favorite_movies_repository.dart';
5+
6+
class FavoriteMoviesCubit extends Cubit<FavoriteMoviesState> {
7+
FavoriteMoviesCubit(this._repository) : super(FavoriteMoviesInitial());
8+
9+
final FavoriteMoviesRepository _repository;
10+
11+
Future<void> loadFavoriteMovies() async {
12+
try {
13+
emit(FavoriteMoviesLoading());
14+
final movies = await _repository.getFavoriteMovies();
15+
final favoriteMovieIds = movies.map((m) => m.id).toSet();
16+
emit(FavoriteMoviesLoaded(
17+
movies: movies,
18+
favoriteMovieIds: favoriteMovieIds,
19+
));
20+
} catch (e) {
21+
emit(FavoriteMoviesError(e.toString()));
22+
}
23+
}
24+
25+
Future<void> toggleFavorite(Movie movie) async {
26+
try {
27+
final isFavorite = await _repository.isMovieFavorite(movie.id);
28+
if (isFavorite) {
29+
await _repository.removeFavoriteMovie(movie.id);
30+
} else {
31+
await _repository.addFavoriteMovie(movie);
32+
}
33+
await loadFavoriteMovies();
34+
} catch (e) {
35+
emit(FavoriteMoviesError(e.toString()));
36+
}
37+
}
38+
39+
Future<bool> isMovieFavorite(int movieId) async {
40+
return _repository.isMovieFavorite(movieId);
41+
}
42+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import 'package:equatable/equatable.dart';
2+
import 'package:tmdb_flutter/app/api/models/movie_responses.dart';
3+
4+
abstract class FavoriteMoviesState extends Equatable {
5+
const FavoriteMoviesState();
6+
7+
@override
8+
List<Object?> get props => [];
9+
}
10+
11+
class FavoriteMoviesInitial extends FavoriteMoviesState {}
12+
13+
class FavoriteMoviesLoading extends FavoriteMoviesState {}
14+
15+
class FavoriteMoviesLoaded extends FavoriteMoviesState {
16+
const FavoriteMoviesLoaded({
17+
required this.movies,
18+
required this.favoriteMovieIds,
19+
});
20+
21+
final List<Movie> movies;
22+
final Set<int> favoriteMovieIds;
23+
24+
@override
25+
List<Object?> get props => [movies, favoriteMovieIds];
26+
}
27+
28+
class FavoriteMoviesError extends FavoriteMoviesState {
29+
const FavoriteMoviesError(this.message);
30+
31+
final String message;
32+
33+
@override
34+
List<Object?> get props => [message];
35+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import 'package:tmdb_flutter/app/api/models/movie_responses.dart';
2+
import 'package:tmdb_flutter/app/data/local/favorite_movies_local_data_source.dart';
3+
4+
class FavoriteMoviesRepository {
5+
FavoriteMoviesRepository(this._localDataSource);
6+
7+
final FavoriteMoviesLocalDataSource _localDataSource;
8+
9+
Future<void> addFavoriteMovie(Movie movie) async {
10+
await _localDataSource.addFavoriteMovie({
11+
'movieId': movie.id,
12+
'title': movie.title,
13+
'posterPath': movie.posterPath,
14+
'overview': movie.overview,
15+
'voteAverage': movie.voteAverage,
16+
'releaseDate': movie.releaseDate,
17+
});
18+
}
19+
20+
Future<void> removeFavoriteMovie(int movieId) async {
21+
await _localDataSource.removeFavoriteMovie(movieId);
22+
}
23+
24+
Future<List<Movie>> getFavoriteMovies() async {
25+
final movies = await _localDataSource.getFavoriteMovies();
26+
return movies.map((movie) => Movie(
27+
id: movie['movieId'] as int,
28+
title: movie['title'] as String,
29+
posterPath: movie['posterPath'] as String?,
30+
overview: movie['overview'] as String,
31+
voteAverage: movie['voteAverage'] as double,
32+
releaseDate: movie['releaseDate'] as String?,
33+
)).toList();
34+
}
35+
36+
Future<bool> isMovieFavorite(int movieId) async {
37+
return _localDataSource.isMovieFavorite(movieId);
38+
}
39+
}

lib/app/view/details_page.dart

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,33 @@
11
import 'package:flutter/material.dart';
2+
import 'package:flutter_bloc/flutter_bloc.dart';
23
import 'package:tmdb_flutter/app/api/models/movie_responses.dart';
4+
import 'package:tmdb_flutter/app/cubit/favorite_movies_cubit.dart';
35

4-
class DetailsPage extends StatelessWidget {
6+
class DetailsPage extends StatefulWidget {
57
const DetailsPage({required this.movie, super.key});
68

79
final Movie movie;
810

11+
@override
12+
State<DetailsPage> createState() => _DetailsPageState();
13+
}
14+
15+
class _DetailsPageState extends State<DetailsPage> {
16+
bool _isFavorite = false;
17+
18+
@override
19+
void initState() {
20+
super.initState();
21+
_checkFavoriteStatus();
22+
}
23+
24+
Future<void> _checkFavoriteStatus() async {
25+
final isFavorite = await context.read<FavoriteMoviesCubit>().isMovieFavorite(widget.movie.id);
26+
setState(() {
27+
_isFavorite = isFavorite;
28+
});
29+
}
30+
931
@override
1032
Widget build(BuildContext context) {
1133
return Scaffold(
@@ -27,7 +49,7 @@ class DetailsPage extends StatelessWidget {
2749
),
2850
image: DecorationImage(
2951
image: NetworkImage(
30-
'https://image.tmdb.org/t/p/w500${movie.backdropPath ?? movie.posterPath}',
52+
'https://image.tmdb.org/t/p/w500${widget.movie.backdropPath ?? widget.movie.posterPath}',
3153
),
3254
fit: BoxFit.cover,
3355
),
@@ -45,14 +67,17 @@ class DetailsPage extends StatelessWidget {
4567
top: 16,
4668
right: 16,
4769
child: _CircleButton(
48-
icon: Icons.favorite_border,
49-
onTap: () {},
70+
icon: _isFavorite ? Icons.favorite : Icons.favorite_border,
71+
onTap: () async {
72+
await context.read<FavoriteMoviesCubit>().toggleFavorite(widget.movie);
73+
_checkFavoriteStatus();
74+
},
5075
),
5176
),
5277
Positioned(
5378
left: 24,
5479
bottom: 0,
55-
child: _RatingIndicator(percent: movie.voteAverage / 10),
80+
child: _RatingIndicator(percent: widget.movie.voteAverage / 10),
5681
),
5782
const Positioned(
5883
left: 100,

0 commit comments

Comments
 (0)