diff --git a/assets/strings/en.json b/assets/strings/en.json index d944fc0..7681e3e 100644 --- a/assets/strings/en.json +++ b/assets/strings/en.json @@ -7,5 +7,6 @@ "trending": "Trending", "information": "Information", "trailers": "Trailers", - "casts": "Casts" + "casts": "Casts", + "delete": "Delete" } \ No newline at end of file diff --git a/assets/strings/ja.json b/assets/strings/ja.json index d944fc0..7681e3e 100644 --- a/assets/strings/ja.json +++ b/assets/strings/ja.json @@ -7,5 +7,6 @@ "trending": "Trending", "information": "Information", "trailers": "Trailers", - "casts": "Casts" + "casts": "Casts", + "delete": "Delete" } \ No newline at end of file diff --git a/lib/src/app.dart b/lib/src/app.dart index acca59c..84463e3 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -31,6 +31,7 @@ class App extends StatelessWidget { MainScreen.routeName: (context) => MainScreen(), GenreScreen.routeName: (context) => GenreScreen(), DetailMovieScreen.routeName: (context) => DetailMovieScreen(), + FavoriteScreen.routeName: (context) => FavoriteScreen(), }, ); } diff --git a/lib/src/blocs/blocs.dart b/lib/src/blocs/blocs.dart index 622d2f8..0d76c9e 100644 --- a/lib/src/blocs/blocs.dart +++ b/lib/src/blocs/blocs.dart @@ -4,3 +4,4 @@ export 'genre_bloc/blocs.dart'; export 'setting_bloc/blocs.dart'; export 'detail_movie_bloc/blocs.dart'; export 'main_bloc/blocs.dart'; +export 'favorite_bloc/blocs.dart'; diff --git a/lib/src/blocs/detail_movie_bloc/detail_movie_bloc.dart b/lib/src/blocs/detail_movie_bloc/detail_movie_bloc.dart index 0b94264..5df1bf6 100644 --- a/lib/src/blocs/detail_movie_bloc/detail_movie_bloc.dart +++ b/lib/src/blocs/detail_movie_bloc/detail_movie_bloc.dart @@ -6,7 +6,7 @@ import 'package:moviesdb/src/locator.dart'; import 'package:moviesdb/src/repositories/repositories.dart'; class DetailMovieBloc extends Bloc { - MoviesRepositories moviesRepositories = locator(); + MoviesRepository moviesRepositories = locator(); @override BaseState get initialState => InitState(); diff --git a/lib/src/blocs/favorite_bloc/blocs.dart b/lib/src/blocs/favorite_bloc/blocs.dart new file mode 100644 index 0000000..88b141e --- /dev/null +++ b/lib/src/blocs/favorite_bloc/blocs.dart @@ -0,0 +1,3 @@ +export 'favorite_event.dart'; +export 'favorite_state.dart'; +export 'favorite_bloc.dart'; diff --git a/lib/src/blocs/favorite_bloc/favorite_bloc.dart b/lib/src/blocs/favorite_bloc/favorite_bloc.dart new file mode 100644 index 0000000..0d89d8a --- /dev/null +++ b/lib/src/blocs/favorite_bloc/favorite_bloc.dart @@ -0,0 +1,53 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:moviesdb/src/blocs/base_bloc/base.dart'; +import 'package:moviesdb/src/blocs/blocs.dart'; +import 'package:moviesdb/src/locator.dart'; +import 'package:moviesdb/src/repositories/repositories.dart'; + +class FavoriteBloc extends Bloc { + final FavoriteRepository repository = locator(); + + @override + BaseState get initialState => InitState(); + + @override + Stream mapEventToState(BaseEvent event) async* { + if (event is ClickedFavorite) { + try { + yield (LoadingState()); + final result = await repository.isFavorite(event.movie); + if (result == true) { + await repository.deleteFavorite(event.movie); + yield (NormalState()); + } else if (result == false) { + await repository.addFavorite(event.movie); + yield (FavoriteState()); + } + } catch (e) { + yield (ErrorState(data: e.toString())); + } + } else if (event is CheckFavorite) { + try { + yield (LoadingState()); + final result = await repository.isFavorite(event.movie); + yield (result == true ? FavoriteState() : NormalState()); + } catch (e) { + yield (ErrorState(data: e.toString())); + } + } else if (event is GetFavorites) { + try { + yield (LoadingState()); + final result = await repository.getFavorites(); + yield (LoadedState(data: result)); + } catch (e) { + yield (ErrorState(data: e.toString())); + } + } else if (event is UnFavorite) { + try { + await repository.deleteFavorite(event.movie); + } catch (e) { + yield (ErrorState(data: e.toString())); + } + } + } +} diff --git a/lib/src/blocs/favorite_bloc/favorite_event.dart b/lib/src/blocs/favorite_bloc/favorite_event.dart new file mode 100644 index 0000000..9694d13 --- /dev/null +++ b/lib/src/blocs/favorite_bloc/favorite_event.dart @@ -0,0 +1,22 @@ +import 'package:moviesdb/src/blocs/base_bloc/base.dart'; +import 'package:moviesdb/src/data/models/models.dart'; + +class GetFavorites extends BaseEvent {} + +class CheckFavorite extends BaseEvent { + final Movie movie; + + CheckFavorite(this.movie); +} + +class ClickedFavorite extends BaseEvent { + final Movie movie; + + ClickedFavorite(this.movie); +} + +class UnFavorite extends BaseEvent { + final Movie movie; + + UnFavorite(this.movie); +} diff --git a/lib/src/blocs/favorite_bloc/favorite_state.dart b/lib/src/blocs/favorite_bloc/favorite_state.dart new file mode 100644 index 0000000..0cafcac --- /dev/null +++ b/lib/src/blocs/favorite_bloc/favorite_state.dart @@ -0,0 +1,5 @@ +import 'package:moviesdb/src/blocs/base_bloc/base.dart'; + +class FavoriteState extends BaseState {} + +class NormalState extends BaseState {} diff --git a/lib/src/blocs/genre_bloc/genre_bloc.dart b/lib/src/blocs/genre_bloc/genre_bloc.dart index e9b4351..784d749 100644 --- a/lib/src/blocs/genre_bloc/genre_bloc.dart +++ b/lib/src/blocs/genre_bloc/genre_bloc.dart @@ -7,7 +7,7 @@ import 'package:moviesdb/src/repositories/repositories.dart'; import 'package:rxdart/rxdart.dart'; class GenreBloc extends Bloc { - MoviesRepositories moviesRepositories = locator(); + MoviesRepository moviesRepositories = locator(); bool loadingMore = false; @override diff --git a/lib/src/blocs/home_bloc/home_bloc.dart b/lib/src/blocs/home_bloc/home_bloc.dart index 7024f09..aa3d952 100644 --- a/lib/src/blocs/home_bloc/home_bloc.dart +++ b/lib/src/blocs/home_bloc/home_bloc.dart @@ -7,7 +7,7 @@ import 'package:moviesdb/src/locator.dart'; import 'package:moviesdb/src/repositories/repositories.dart'; class HomeBloc extends Bloc { - MoviesRepositories moviesRepositories = locator(); + MoviesRepository moviesRepositories = locator(); @override BaseState get initialState => InitState(); diff --git a/lib/src/blocs/setting_bloc/setting_bloc.dart b/lib/src/blocs/setting_bloc/setting_bloc.dart index 52f11a6..eace718 100644 --- a/lib/src/blocs/setting_bloc/setting_bloc.dart +++ b/lib/src/blocs/setting_bloc/setting_bloc.dart @@ -6,7 +6,7 @@ import 'package:moviesdb/src/repositories/repositories.dart'; import 'package:package_info/package_info.dart'; class SettingBloc extends Bloc { - SettingRepositories settingRepositories = locator(); + SettingRepository settingRepositories = locator(); @override BaseState get initialState => InitState(); diff --git a/lib/src/data/models/movie.dart b/lib/src/data/models/movie.dart index 1c09a59..db4ba26 100644 --- a/lib/src/data/models/movie.dart +++ b/lib/src/data/models/movie.dart @@ -44,4 +44,14 @@ class Movie extends Equatable { ? json["credits"]["cast"]?.map((e) => Actor.fromJson(e))?.toList()?.cast() : []; } + + Map toMap() { + return Map() + ..["id"] = id + ..["poster_path"] = porterPath + ..["title"] = title + ..["overview"] = overview + ..["release_date"] = releaseDate + ..["vote_average"] = voteAverage; + } } diff --git a/lib/src/locator.dart b/lib/src/locator.dart index cd6e4e6..e5daa0d 100644 --- a/lib/src/locator.dart +++ b/lib/src/locator.dart @@ -9,8 +9,10 @@ GetIt locator = GetIt.instance; void setupLocator() { locator.registerLazySingleton(() => Network()); - locator.registerLazySingleton(() => MoviesRepositories()); - locator.registerLazySingleton(() => SettingRepositories()); + locator.registerLazySingleton(() => DatabaseProvider.databaseProvider); + locator.registerLazySingleton(() => MoviesRepository()); + locator.registerLazySingleton(() => SettingRepository()); + locator.registerLazySingleton(() => FavoriteRepository()); } void setupLocatorWithContext(BuildContext context) { diff --git a/lib/src/repositories/favorite_repository.dart b/lib/src/repositories/favorite_repository.dart new file mode 100644 index 0000000..ccf133e --- /dev/null +++ b/lib/src/repositories/favorite_repository.dart @@ -0,0 +1,23 @@ +import 'package:moviesdb/src/data/models/models.dart'; +import 'package:moviesdb/src/locator.dart'; +import 'package:moviesdb/src/services/database.dart'; + +class FavoriteRepository { + DatabaseProvider databaseProvider = locator(); + + Future addFavorite(Movie movie) { + return databaseProvider.addMovie(movie); + } + + Future isFavorite(Movie movie) { + return databaseProvider.checkExist(movie); + } + + Future> getFavorites() { + return databaseProvider.getMovies(); + } + + Future deleteFavorite(Movie movie) { + return databaseProvider.deleteMovie(movie); + } +} diff --git a/lib/src/repositories/movies_repositories.dart b/lib/src/repositories/movies_repository.dart similarity index 98% rename from lib/src/repositories/movies_repositories.dart rename to lib/src/repositories/movies_repository.dart index 125fe5c..1be6dfc 100644 --- a/lib/src/repositories/movies_repositories.dart +++ b/lib/src/repositories/movies_repository.dart @@ -6,7 +6,7 @@ import 'package:moviesdb/src/services/services.dart'; import 'package:moviesdb/src/utils/utils.dart'; import 'package:moviesdb/src/extension.dart'; -class MoviesRepositories { +class MoviesRepository { Network _network = locator(); Future getDetailMovie(int movieId) async { diff --git a/lib/src/repositories/repositories.dart b/lib/src/repositories/repositories.dart index 51682df..5dd9148 100644 --- a/lib/src/repositories/repositories.dart +++ b/lib/src/repositories/repositories.dart @@ -1,2 +1,3 @@ -export 'movies_repositories.dart'; -export 'setting_repositories.dart'; \ No newline at end of file +export 'movies_repository.dart'; +export 'setting_repository.dart'; +export 'favorite_repository.dart'; diff --git a/lib/src/repositories/setting_repositories.dart b/lib/src/repositories/setting_repository.dart similarity index 83% rename from lib/src/repositories/setting_repositories.dart rename to lib/src/repositories/setting_repository.dart index ce668fa..fda82c5 100644 --- a/lib/src/repositories/setting_repositories.dart +++ b/lib/src/repositories/setting_repository.dart @@ -1,6 +1,6 @@ import 'package:package_info/package_info.dart'; -class SettingRepositories{ +class SettingRepository{ Future getAppInfo() async{ return await PackageInfo.fromPlatform(); } diff --git a/lib/src/services/database.dart b/lib/src/services/database.dart new file mode 100644 index 0000000..8a20863 --- /dev/null +++ b/lib/src/services/database.dart @@ -0,0 +1,78 @@ +import 'package:moviesdb/src/data/models/movie.dart'; +import 'package:path/path.dart'; +import 'package:sqflite/sqflite.dart'; +import 'package:sqflite/sqlite_api.dart'; + +const String DB_NAME = "movies_database.db"; +const String TABLE_FAVORITE = "favorite"; + +class DatabaseProvider { + static final DatabaseProvider databaseProvider = DatabaseProvider._(); + Database _database; + + DatabaseProvider._(); + + Future get database async { + if (_database != null) return _database; + _database = await _initDatabase(); + return _database; + } + + _initDatabase() async { + return await openDatabase( + join(await getDatabasesPath(), DB_NAME), + version: 1, + onCreate: (db, version) { + return db.execute("""CREATE TABLE $TABLE_FAVORITE ( + id INTEGER PRIMARY KEY, + poster_path TEXT, + title TEXT, + overview TEXT, + release_date TEXT, + vote_average REAL + )"""); + }, + ); + } + + Future close() { + return _database?.close(); + } + + //delete the database + Future deleteDB() async { + return deleteDatabase(join(await getDatabasesPath(), DB_NAME)); + } + + Future addMovie(Movie movie) async { + final db = await database; + return await db.insert(TABLE_FAVORITE, movie.toMap(), + conflictAlgorithm: ConflictAlgorithm.ignore); + } + + Future checkExist(Movie movie) async { + final db = await database; + List> maps = + await db.query(TABLE_FAVORITE, where: "id = ?", whereArgs: [movie.id]); + return maps.isNotEmpty; + } + + Future> getMovies() async { + final db = await database; + List> maps = await db.query(TABLE_FAVORITE); + return maps.map((e) => Movie.formJson(e)).toList(); + } + + Future deleteMovie(Movie movie) async { + final db = await database; + final result = await db.delete(TABLE_FAVORITE, where: "id = ?", whereArgs: [movie.id]); + return result >= 0; + } + + Future updateMovie(Movie movie) async { + final db = await database; + final result = + await db.update(TABLE_FAVORITE, movie.toMap(), where: "id = ?", whereArgs: [movie.id]); + return result >= 0; + } +} diff --git a/lib/src/services/services.dart b/lib/src/services/services.dart index bb25dd8..7f9305c 100644 --- a/lib/src/services/services.dart +++ b/lib/src/services/services.dart @@ -1 +1,2 @@ -export 'network.dart'; \ No newline at end of file +export 'network.dart'; +export 'database.dart'; \ No newline at end of file diff --git a/lib/src/ui/screens/detail_movie_screen.dart b/lib/src/ui/screens/detail_movie_screen.dart index 6197ac9..06d78d6 100644 --- a/lib/src/ui/screens/detail_movie_screen.dart +++ b/lib/src/ui/screens/detail_movie_screen.dart @@ -19,8 +19,15 @@ class DetailMovieScreen extends StatelessWidget { @override Widget build(BuildContext context) { final Movie movie = ModalRoute.of(context).settings.arguments; - return BlocProvider( - create: (context) => DetailMovieBloc()..add(GetDetailMovie(movie.id)), + return MultiBlocProvider( + providers: [ + BlocProvider( + create: (context) => DetailMovieBloc()..add(GetDetailMovie(movie.id)), + ), + BlocProvider( + create: (context) => FavoriteBloc(), + ) + ], child: Scaffold( appBar: AppBar( title: Text( @@ -35,7 +42,12 @@ class DetailMovieScreen extends StatelessWidget { } Widget _body(BuildContext context) { - return BlocBuilder( + return BlocConsumer( + listener: (context, state) { + if (state is LoadedState) { + context.bloc().add(CheckFavorite(state.data)); + } + }, builder: (context, state) { if (state is LoadedState) { return Container( @@ -153,9 +165,24 @@ class DetailMovieScreen extends StatelessWidget { ), ), GestureDetector( - child: Padding( - padding: const EdgeInsets.all(6), - child: Icon(Icons.favorite_border), + onTap: () { + context.bloc().add(ClickedFavorite(movie)); + }, + child: BlocBuilder( + condition: (previous, current) { + return current is FavoriteState || current is NormalState; + }, + builder: (context, state) { + return Padding( + padding: const EdgeInsets.all(6), + child: state is FavoriteState + ? Icon( + Icons.favorite, + color: Colors.redAccent, + ) + : Icon(Icons.favorite_border), + ); + }, ), ), ], diff --git a/lib/src/ui/screens/favorite_screen.dart b/lib/src/ui/screens/favorite_screen.dart new file mode 100644 index 0000000..94cbcc2 --- /dev/null +++ b/lib/src/ui/screens/favorite_screen.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:moviesdb/src/blocs/blocs.dart'; +import 'package:moviesdb/src/data/models/models.dart'; +import 'package:moviesdb/src/ui/widgets/widgets.dart'; + +class FavoriteScreen extends StatelessWidget { + static const routeName = "/FavoriteScreen"; + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => FavoriteBloc()..add(GetFavorites()), + child: Scaffold( + body: BlocBuilder( + builder: (context, state) { + if (state is LoadedState) { + final List movies = state.data ?? []; + return ListView.builder( + physics: ClampingScrollPhysics(), + itemCount: movies.length, + itemBuilder: (context, index) { + return ItemListMovie( + movie: movies[index], + onDismissed: (movie) { + context.bloc().add(UnFavorite(movie)); + }, + ); + }, + ); + } + return Center( + child: ProgressLoading( + size: 24, + ), + ); + }, + ), + ), + ); + } +} diff --git a/lib/src/ui/screens/main_screen.dart b/lib/src/ui/screens/main_screen.dart index 951865b..661e8c3 100644 --- a/lib/src/ui/screens/main_screen.dart +++ b/lib/src/ui/screens/main_screen.dart @@ -12,7 +12,7 @@ class MainScreen extends StatelessWidget { create: (_) => HomeBloc()..add(GetData()), child: HomeScreen(), ), - Container(), + FavoriteScreen(), SettingScreen(), ]; final bloc = MainBloc(); @@ -23,7 +23,6 @@ class MainScreen extends StatelessWidget { bloc: bloc, builder: (context, state) { if (state is MainState) { - print('---------${state.index}'); return Scaffold( body: _children[state.index], bottomNavigationBar: BottomNavigationBar( @@ -44,7 +43,6 @@ class MainScreen extends StatelessWidget { selectedItemColor: Colors.black, currentIndex: state.index, onTap: (index) { - print('adfahsgghdlkfgl'); bloc.add(ChangeIndexBottomNavigation(index)); }, ), diff --git a/lib/src/ui/screens/screens.dart b/lib/src/ui/screens/screens.dart index f7e59b7..4b2eef5 100644 --- a/lib/src/ui/screens/screens.dart +++ b/lib/src/ui/screens/screens.dart @@ -4,3 +4,4 @@ export 'setting_screen.dart'; export 'home_screen.dart'; export 'genre_screen.dart'; export 'detail_movie_screen.dart'; +export 'favorite_screen.dart'; \ No newline at end of file diff --git a/lib/src/ui/widgets/genre/list_movies_by_genre.dart b/lib/src/ui/widgets/genre/list_movies_by_genre.dart index fb1826e..cd00e00 100644 --- a/lib/src/ui/widgets/genre/list_movies_by_genre.dart +++ b/lib/src/ui/widgets/genre/list_movies_by_genre.dart @@ -1,12 +1,8 @@ -import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:moviesdb/src/blocs/blocs.dart'; -import 'package:moviesdb/src/constants.dart'; import 'package:moviesdb/src/data/models/models.dart'; -import 'package:moviesdb/src/resources/resources.dart'; import 'package:moviesdb/src/ui/widgets/widgets.dart'; -import 'package:moviesdb/src/utils/utils.dart'; class ListMoviesByGenre extends StatefulWidget { final Genre genre; @@ -43,9 +39,11 @@ class _Sate extends State { final List movies = state.data.movies ?? []; return ListView.builder( physics: ClampingScrollPhysics(), - itemCount: state.data.page < state.data.totalPages ? movies.length +1: movies.length, + itemCount: state.data.page < state.data.totalPages ? movies.length + 1 : movies.length, itemBuilder: (context, index) { - return index < movies.length ? _item(movies[index]) : Center(child: ProgressLoading(size: 20)); + return index < movies.length + ? ItemListMovie(movie: movies[index]) + : Center(child: ProgressLoading(size: 20)); }, controller: _scrollController, ); @@ -55,144 +53,6 @@ class _Sate extends State { ); } - Widget _item(Movie movie) { - return Container( - height: 160, - padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4), - child: Stack( - children: [ - Center( - child: Container( - height: double.infinity, - width: double.infinity, - margin: EdgeInsets.only(left: 56, top: 16), - padding: EdgeInsets.only(left: 60), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.all(Radius.circular(8)), - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.8), - spreadRadius: 2, - blurRadius: 8, - offset: Offset(2, 8), - ) - ], - ), - child: Stack( - children: [ - Column( - children: [ - Padding( - padding: const EdgeInsets.only(right: 40, top: 8), - child: Text( - movie.title, - style: TextStyle(fontWeight: FontWeight.bold), - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), - ), - Text( - movie.releaseDate, - style: TextStyle( - color: Colors.grey, - height: 1.5, - fontSize: 13, - fontWeight: FontWeight.w600, - ), - ), - Expanded( - child: Container( - alignment: Alignment.bottomLeft, - padding: EdgeInsets.only(bottom: 20, right: 8), - child: Text( - movie.overview, - maxLines: 3, - overflow: TextOverflow.ellipsis, - ), - ), - ) - ], - crossAxisAlignment: CrossAxisAlignment.start, - ), - Positioned( - top: 8, - right: 8, - child: MovieVoteAverage( - voteAverage: movie.voteAverage, - size: 24, - ), - ) - ], - ), - ), - ), - CachedNetworkImage( - imageUrl: getImageUrl(movie.porterPath, imageSize: ImageSize.w300), - imageBuilder: (context, imageProvider) { - return Container( - width: 100, - height: 140, - decoration: BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(10)), - image: DecorationImage( - image: imageProvider, - fit: BoxFit.cover, - ), - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.8), - spreadRadius: 2, - blurRadius: 8, - offset: Offset(2, 8), - ) - ], - ), - ); - }, - placeholder: (context, url) => Container( - width: 100, - height: 140, - alignment: Alignment.center, - decoration: BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(10)), - color: Colors.grey, - ), - child: ProgressLoading( - size: 20, - strokeWidth: 1, - valueColor: Colors.black, - ), - ), - errorWidget: (context, url, error) => _errorImage, - ), - ], - ), - ); - } - - Widget get _errorImage { - return Container( - width: 100, - height: 140, - decoration: BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(10)), - image: DecorationImage( - image: AssetImage(Images.no_image), - fit: BoxFit.cover, - ), - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.8), - spreadRadius: 2, - blurRadius: 8, - offset: Offset(2, 8), - ) - ], - ), - ); - } - _onScroll() { final maxScroll = _scrollController.position.maxScrollExtent; final currentScroll = _scrollController.position.pixels; diff --git a/lib/src/ui/widgets/list_movies_item.dart b/lib/src/ui/widgets/list_movies_item.dart new file mode 100644 index 0000000..7c2805c --- /dev/null +++ b/lib/src/ui/widgets/list_movies_item.dart @@ -0,0 +1,181 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_slidable/flutter_slidable.dart'; +import 'package:moviesdb/src/constants.dart'; +import 'package:moviesdb/src/data/models/models.dart'; +import 'package:moviesdb/src/resources/resources.dart'; +import 'package:moviesdb/src/ui/screens/screens.dart'; +import 'package:moviesdb/src/ui/widgets/widgets.dart'; +import 'package:moviesdb/src/utils/utils.dart'; + +class ItemListMovie extends StatelessWidget { + final Movie movie; + final Function(Movie movie) onDismissed; + + const ItemListMovie({Key key, this.movie, this.onDismissed}) : super(key: key); + + @override + Widget build(BuildContext context) { + if (onDismissed == null) return _item(context); + return Slidable( + actionPane: SlidableDrawerActionPane(), + actionExtentRatio: 0.25, + child: _item(context), + secondaryActions: [ + Padding( + padding: const EdgeInsets.only(top: 20, bottom: 4), + child: IconSlideAction( + onTap: () => onDismissed(movie), + color: Colors.redAccent, + icon: Icons.delete_forever, + caption: Language.of(context).getText("delete"), + ), + ) + ], + ); + } + + Widget _item(BuildContext context) { + return GestureDetector( + onTap: () { + Navigator.pushNamed(context, DetailMovieScreen.routeName, arguments: movie); + }, + child: Container( + height: 160, + padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4), + child: Stack( + children: [ + Center( + child: Container( + height: double.infinity, + width: double.infinity, + margin: EdgeInsets.only(left: 56, top: 16), + padding: EdgeInsets.only(left: 60), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all(Radius.circular(8)), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.8), + spreadRadius: 2, + blurRadius: 8, + offset: Offset(2, 8), + ) + ], + ), + child: Stack( + children: [ + Column( + children: [ + Padding( + padding: const EdgeInsets.only(right: 40, top: 8), + child: Text( + movie.title, + style: TextStyle(fontWeight: FontWeight.bold), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ), + Text( + movie.releaseDate, + style: TextStyle( + color: Colors.grey, + height: 1.5, + fontSize: 13, + fontWeight: FontWeight.w600, + ), + ), + Expanded( + child: Container( + alignment: Alignment.bottomLeft, + padding: EdgeInsets.only(bottom: 20, right: 8), + child: Text( + movie.overview, + maxLines: 3, + overflow: TextOverflow.ellipsis, + ), + ), + ) + ], + crossAxisAlignment: CrossAxisAlignment.start, + ), + Positioned( + top: 8, + right: 8, + child: MovieVoteAverage( + voteAverage: movie.voteAverage, + size: 24, + ), + ) + ], + ), + ), + ), + CachedNetworkImage( + imageUrl: getImageUrl(movie.porterPath, imageSize: ImageSize.w300), + imageBuilder: (context, imageProvider) { + return Container( + width: 100, + height: 140, + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(10)), + image: DecorationImage( + image: imageProvider, + fit: BoxFit.cover, + ), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.8), + spreadRadius: 2, + blurRadius: 8, + offset: Offset(2, 8), + ) + ], + ), + ); + }, + placeholder: (context, url) => Container( + width: 100, + height: 140, + alignment: Alignment.center, + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(10)), + color: Colors.grey, + ), + child: ProgressLoading( + size: 20, + strokeWidth: 1, + valueColor: Colors.black, + ), + ), + errorWidget: (context, url, error) => _errorImage, + ), + ], + ), + ), + ); + } + + Widget get _errorImage { + return Container( + width: 100, + height: 140, + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(10)), + image: DecorationImage( + image: AssetImage(Images.no_image), + fit: BoxFit.cover, + ), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.8), + spreadRadius: 2, + blurRadius: 8, + offset: Offset(2, 8), + ) + ], + ), + ); + } +} diff --git a/lib/src/ui/widgets/widgets.dart b/lib/src/ui/widgets/widgets.dart index 842dfeb..836a14b 100644 --- a/lib/src/ui/widgets/widgets.dart +++ b/lib/src/ui/widgets/widgets.dart @@ -4,4 +4,5 @@ export 'home/home_list_genres.dart'; export 'genre_view.dart'; export 'page_indicator.dart'; export 'movie_vote_average.dart'; -export 'genre/list_movies_by_genre.dart'; \ No newline at end of file +export 'genre/list_movies_by_genre.dart'; +export 'list_movies_item.dart'; \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index 1668949..4005def 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -21,21 +21,21 @@ packages: name: archive url: "https://pub.dartlang.org" source: hosted - version: "2.0.11" + version: "2.0.13" args: dependency: transitive description: name: args url: "https://pub.dartlang.org" source: hosted - version: "1.5.2" + version: "1.6.0" async: dependency: transitive description: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.4.0" + version: "2.4.1" bloc: dependency: transitive description: @@ -56,7 +56,7 @@ packages: name: boolean_selector url: "https://pub.dartlang.org" source: hosted - version: "1.0.5" + version: "2.0.0" build: dependency: transitive description: @@ -91,7 +91,7 @@ packages: name: charcode url: "https://pub.dartlang.org" source: hosted - version: "1.1.2" + version: "1.1.3" checked_yaml: dependency: transitive description: @@ -112,7 +112,7 @@ packages: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.14.11" + version: "1.14.12" convert: dependency: transitive description: @@ -133,7 +133,7 @@ packages: name: crypto url: "https://pub.dartlang.org" source: hosted - version: "2.1.3" + version: "2.1.4" csslib: dependency: transitive description: @@ -214,6 +214,13 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_slidable: + dependency: "direct main" + description: + name: flutter_slidable + url: "https://pub.dartlang.org" + source: hosted + version: "0.5.4" flutter_test: dependency: "direct dev" description: flutter @@ -267,7 +274,7 @@ packages: name: image url: "https://pub.dartlang.org" source: hosted - version: "2.1.4" + version: "2.1.12" intl: dependency: "direct main" description: @@ -402,7 +409,7 @@ packages: source: hosted version: "0.2.2" path: - dependency: transitive + dependency: "direct main" description: name: path url: "https://pub.dartlang.org" @@ -491,7 +498,7 @@ packages: name: quiver url: "https://pub.dartlang.org" source: hosted - version: "2.0.5" + version: "2.1.3" rxdart: dependency: "direct main" description: @@ -552,7 +559,7 @@ packages: name: source_map_stack_trace url: "https://pub.dartlang.org" source: hosted - version: "1.1.5" + version: "2.0.0" source_maps: dependency: transitive description: @@ -566,21 +573,21 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.5.5" + version: "1.7.0" sqflite: - dependency: transitive + dependency: "direct main" description: name: sqflite url: "https://pub.dartlang.org" source: hosted - version: "1.3.0" + version: "1.3.1" sqflite_common: dependency: transitive description: name: sqflite_common url: "https://pub.dartlang.org" source: hosted - version: "1.0.0+1" + version: "1.0.2+1" stack_trace: dependency: transitive description: @@ -622,21 +629,21 @@ packages: name: test url: "https://pub.dartlang.org" source: hosted - version: "1.9.4" + version: "1.14.4" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.11" + version: "0.2.15" test_core: dependency: transitive description: name: test_core url: "https://pub.dartlang.org" source: hosted - version: "0.2.15" + version: "0.3.4" typed_data: dependency: transitive description: @@ -686,6 +693,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.0" + webkit_inspection_protocol: + dependency: transitive + description: + name: webkit_inspection_protocol + url: "https://pub.dartlang.org" + source: hosted + version: "0.7.3" webview_media: dependency: transitive description: @@ -699,7 +713,7 @@ packages: name: xml url: "https://pub.dartlang.org" source: hosted - version: "3.5.0" + version: "3.6.1" yaml: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index ed2bbf8..02e6be9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -19,6 +19,8 @@ environment: dependencies: flutter: sdk: flutter + sqflite: ^1.3.1 + path: 1.6.4 flutter_localizations: sdk: flutter # The following adds the Cupertino Icons font to your application. @@ -39,6 +41,7 @@ dependencies: intl: ^0.16.0 flutter_bloc: ^4.0.0 dio_http_cache: ^0.2.7 + flutter_slidable: ^0.5.4 dev_dependencies: flutter_test: diff --git a/test/blocs/detail_movie_bloc_test.dart b/test/blocs/detail_movie_bloc_test.dart index 38b9cca..20256e2 100644 --- a/test/blocs/detail_movie_bloc_test.dart +++ b/test/blocs/detail_movie_bloc_test.dart @@ -17,9 +17,9 @@ main() { setUp(() { final getIt = GetIt.instance; getIt.reset(); - getIt.registerLazySingleton(() => MockMovieRepositories()); + getIt.registerLazySingleton(() => MockMovieRepositories()); - repository = getIt(); + repository = getIt(); bloc = DetailMovieBloc(); }); tearDown(() { diff --git a/test/blocs/genre_bloc_test.dart b/test/blocs/genre_bloc_test.dart index 908ab0c..5f77b94 100644 --- a/test/blocs/genre_bloc_test.dart +++ b/test/blocs/genre_bloc_test.dart @@ -24,9 +24,9 @@ main() { setUp(() { final getIt = GetIt.instance; getIt.reset(); - getIt.registerLazySingleton(() => MockMovieRepositories()); + getIt.registerLazySingleton(() => MockMovieRepositories()); - repository = getIt(); + repository = getIt(); bloc = GenreBloc(); }); tearDown(() { diff --git a/test/blocs/home_bloc_test.dart b/test/blocs/home_bloc_test.dart index 757c61e..67ded46 100644 --- a/test/blocs/home_bloc_test.dart +++ b/test/blocs/home_bloc_test.dart @@ -7,7 +7,7 @@ import 'package:moviesdb/src/constants.dart'; import 'package:moviesdb/src/data/models/models.dart'; import 'package:moviesdb/src/repositories/repositories.dart'; -class MockMovieRepositories extends Mock implements MoviesRepositories {} +class MockMovieRepositories extends Mock implements MoviesRepository {} main() { final Category popular = Category(); @@ -22,9 +22,9 @@ main() { setUp(() { final getIt = GetIt.instance; getIt.reset(); - getIt.registerLazySingleton(() => MockMovieRepositories()); + getIt.registerLazySingleton(() => MockMovieRepositories()); - repository = getIt(); + repository = getIt(); bloc = HomeBloc(); }); tearDown(() { diff --git a/test/blocs/setting_bloc_test.dart b/test/blocs/setting_bloc_test.dart index 2057a32..793c4fb 100644 --- a/test/blocs/setting_bloc_test.dart +++ b/test/blocs/setting_bloc_test.dart @@ -6,7 +6,7 @@ import 'package:moviesdb/src/blocs/blocs.dart'; import 'package:moviesdb/src/repositories/repositories.dart'; import 'package:package_info/package_info.dart'; -class MockSettingRepository extends Mock implements SettingRepositories {} +class MockSettingRepository extends Mock implements SettingRepository {} main() { final PackageInfo packageInfo = PackageInfo(version: "0"); @@ -16,9 +16,9 @@ main() { setUp(() { final getIt = GetIt.instance; getIt.reset(); - getIt.registerLazySingleton(() => MockSettingRepository()); + getIt.registerLazySingleton(() => MockSettingRepository()); - repository = getIt(); + repository = getIt(); bloc = SettingBloc(); }); tearDown(() {