feature: add bookmarking functionality

main
daniel-michel 2024-01-08 12:57:36 +01:00
parent 688fa63da2
commit 618f5d135b
7 changed files with 77 additions and 17 deletions

View File

@ -43,10 +43,30 @@ class HomePage extends StatelessWidget {
child: const Icon(Icons.delete)) child: const Icon(Icons.delete))
], ],
), ),
body: MovieManagerList(manager), body: DefaultTabController(
floatingActionButton: FloatingActionButton( length: 2,
child: const Icon(Icons.refresh), child: Column(
onPressed: () => manager.loadUpcomingMovies(), children: [
Expanded(
child: TabBarView(
children: [
Scaffold(
body: MovieManagerList(manager),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.refresh),
onPressed: () => manager.loadUpcomingMovies(),
),
),
MovieManagerList(manager, filter: (movie) => movie.bookmarked)
],
),
),
const TabBar(tabs: [
Tab(icon: Icon(Icons.list), child: Text("Upcoming")),
Tab(icon: Icon(Icons.bookmark), child: Text("Bookmarked")),
]),
],
),
), ),
); );
} }

View File

@ -4,6 +4,7 @@ import 'package:intl/intl.dart';
class MovieData extends ChangeNotifier { class MovieData extends ChangeNotifier {
String _title; String _title;
DateWithPrecisionAndCountry _releaseDate; DateWithPrecisionAndCountry _releaseDate;
bool _bookmarked = false;
bool _hasDetails = false; bool _hasDetails = false;
List<DateWithPrecisionAndCountry>? _releaseDates; List<DateWithPrecisionAndCountry>? _releaseDates;
@ -21,6 +22,10 @@ class MovieData extends ChangeNotifier {
return _releaseDate; return _releaseDate;
} }
bool get bookmarked {
return _bookmarked;
}
List<DateWithPrecisionAndCountry>? get releaseDates { List<DateWithPrecisionAndCountry>? get releaseDates {
return _releaseDates; return _releaseDates;
} }
@ -41,7 +46,9 @@ class MovieData extends ChangeNotifier {
return _hasDetails; return _hasDetails;
} }
void updateWithNew(MovieData movie) { /// Updates the information with that of a new version of the movie
/// but ignores fields that are user controlled, like whether the movie was bookmarked.
void updateWithNewIgnoringUserControlled(MovieData movie) {
setDetails( setDetails(
title: movie.title, title: movie.title,
releaseDate: movie.releaseDate, releaseDate: movie.releaseDate,
@ -54,6 +61,7 @@ class MovieData extends ChangeNotifier {
void setDetails( void setDetails(
{String? title, {String? title,
DateWithPrecisionAndCountry? releaseDate, DateWithPrecisionAndCountry? releaseDate,
bool? bookmarked,
List<DateWithPrecisionAndCountry>? releaseDates, List<DateWithPrecisionAndCountry>? releaseDates,
List<String>? genres, List<String>? genres,
List<TitleInLanguage>? titles, List<TitleInLanguage>? titles,
@ -64,6 +72,9 @@ class MovieData extends ChangeNotifier {
if (releaseDate != null) { if (releaseDate != null) {
_releaseDate = releaseDate; _releaseDate = releaseDate;
} }
if (bookmarked != null) {
_bookmarked = bookmarked;
}
if (releaseDates != null) { if (releaseDates != null) {
_releaseDates = releaseDates; _releaseDates = releaseDates;
} }
@ -85,6 +96,10 @@ class MovieData extends ChangeNotifier {
return "$title (${_releaseDate.toString()}${_genres?.isNotEmpty ?? true ? "; ${_genres?.join(", ")}" : ""})"; return "$title (${_releaseDate.toString()}${_genres?.isNotEmpty ?? true ? "; ${_genres?.join(", ")}" : ""})";
} }
bool same(MovieData other) {
return title == other.title && releaseDate.date == other.releaseDate.date;
}
Map toJsonEncodable() { Map toJsonEncodable() {
List? releaseDatesByCountry = List? releaseDatesByCountry =
_releaseDates?.map((e) => e.toJsonEncodable()).toList(); _releaseDates?.map((e) => e.toJsonEncodable()).toList();
@ -92,6 +107,7 @@ class MovieData extends ChangeNotifier {
return { return {
"title": title, "title": title,
"releaseDate": _releaseDate.toJsonEncodable(), "releaseDate": _releaseDate.toJsonEncodable(),
"bookmarked": _bookmarked,
"releaseDates": releaseDatesByCountry, "releaseDates": releaseDatesByCountry,
"genres": genres, "genres": genres,
"titles": titlesByCountry, "titles": titlesByCountry,
@ -99,15 +115,12 @@ class MovieData extends ChangeNotifier {
}; };
} }
bool same(MovieData other) {
return title == other.title && releaseDate.date == other.releaseDate.date;
}
MovieData.fromJsonEncodable(Map json) MovieData.fromJsonEncodable(Map json)
: _title = json["title"], : _title = json["title"],
_releaseDate = _releaseDate =
DateWithPrecisionAndCountry.fromJsonEncodable(json["releaseDate"]) { DateWithPrecisionAndCountry.fromJsonEncodable(json["releaseDate"]) {
setDetails( setDetails(
bookmarked: json["bookmarked"] as bool,
genres: (json["genres"] as List<dynamic>?) genres: (json["genres"] as List<dynamic>?)
?.map((genre) => genre as String) ?.map((genre) => genre as String)
.toList(), .toList(),

View File

@ -54,7 +54,7 @@ class MovieManager extends ChangeNotifier {
added = true; added = true;
actualMovies.add(movie); actualMovies.add(movie);
} else { } else {
existing.updateWithNew(movie); existing.updateWithNewIgnoringUserControlled(movie);
actualMovies.add(existing); actualMovies.add(existing);
} }
} }

View File

@ -12,9 +12,16 @@ class MovieItem extends StatelessWidget {
animation: movie, animation: movie,
builder: (context, widget) { builder: (context, widget) {
return ListTile( return ListTile(
title: Text(movie.title), title: Text(movie.title),
subtitle: Text( subtitle: Text(
"${dateRelativeToNow(movie.releaseDate.date)}, ${movie.releaseDate.toString()}, ${movie.genres?.join(", ") ?? ""}")); "${dateRelativeToNow(movie.releaseDate.date)}, ${movie.releaseDate.toString()}, ${movie.genres?.join(", ") ?? ""}"),
trailing: TextButton(
child: Icon(movie.bookmarked
? Icons.bookmark_added
: Icons.bookmark_border),
onPressed: () => movie.setDetails(bookmarked: !movie.bookmarked),
),
);
}, },
); );
} }

View File

@ -4,10 +4,28 @@ import 'package:release_schedule/view/movie_item.dart';
class MovieList extends StatelessWidget { class MovieList extends StatelessWidget {
final List<MovieData> movies; final List<MovieData> movies;
const MovieList(this.movies, {super.key}); final bool Function(MovieData)? filter;
const MovieList(this.movies, {this.filter, super.key});
@override @override
Widget build(Object context) { Widget build(Object context) {
final localFilter = filter;
if (localFilter != null) {
List<int> indexMap = [];
int index = 0;
for (var movie in movies) {
if (localFilter(movie)) {
indexMap.add(index);
}
index++;
}
return ListView.builder(
itemCount: indexMap.length,
itemBuilder: (context, index) {
return MovieItem(movies[indexMap[index]]);
},
);
}
return ListView.builder( return ListView.builder(
itemCount: movies.length, itemCount: movies.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {

View File

@ -1,10 +1,12 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:release_schedule/model/movie.dart';
import 'package:release_schedule/model/movie_manager.dart'; import 'package:release_schedule/model/movie_manager.dart';
import 'package:release_schedule/view/movie_list.dart'; import 'package:release_schedule/view/movie_list.dart';
class MovieManagerList extends StatelessWidget { class MovieManagerList extends StatelessWidget {
final MovieManager manager; final MovieManager manager;
const MovieManagerList(this.manager, {super.key}); final bool Function(MovieData)? filter;
const MovieManagerList(this.manager, {this.filter, super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -14,7 +16,7 @@ class MovieManagerList extends StatelessWidget {
return Column( return Column(
children: [ children: [
manager.loading ? const LinearProgressIndicator() : Container(), manager.loading ? const LinearProgressIndicator() : Container(),
Expanded(child: MovieList(manager.movies)) Expanded(child: MovieList(manager.movies, filter: filter))
], ],
); );
}, },

View File

@ -23,7 +23,7 @@ void main() {
], reviews: [ ], reviews: [
Review('8.5', 'John Doe', DateTime(2023, 1, 1), 100) Review('8.5', 'John Doe', DateTime(2023, 1, 1), 100)
]); ]);
movie1.updateWithNew(movie2); movie1.updateWithNewIgnoringUserControlled(movie2);
expect(movie1.title, equals('Title 2')); expect(movie1.title, equals('Title 2'));
expect(movie1.releaseDate.country, equals('UK')); expect(movie1.releaseDate.country, equals('UK'));
expect(movie1.releaseDates!.length, equals(1)); expect(movie1.releaseDates!.length, equals(1));