add: group movies by release date
parent
497c2e6d2e
commit
698c785896
|
@ -168,8 +168,8 @@ class WikidataMovieData extends MovieData {
|
|||
_precisionFromWikidata(value["precision"]), country);
|
||||
}).toList();
|
||||
// Sort release dates with higher precision to the beginning
|
||||
releaseDates
|
||||
.sort((a, b) => -a.precision.index.compareTo(b.precision.index));
|
||||
releaseDates.sort((a, b) => -a.dateWithPrecision.precision.index
|
||||
.compareTo(b.dateWithPrecision.precision.index));
|
||||
List<String>? genres = selectInJson<String>(
|
||||
claims, "${WikidataProperties.genre}.*.mainsnak.datavalue.value.id")
|
||||
.map(_getCachedLabelForEntity)
|
||||
|
|
|
@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
|||
import 'package:release_schedule/model/live_search.dart';
|
||||
import 'package:release_schedule/model/movie.dart';
|
||||
import 'package:release_schedule/model/movie_manager.dart';
|
||||
import 'package:release_schedule/view/movie_list.dart';
|
||||
import 'package:release_schedule/view/movie_item.dart';
|
||||
import 'package:release_schedule/view/movie_manager_list.dart';
|
||||
import 'package:release_schedule/view/swipe_transition.dart';
|
||||
|
||||
|
@ -126,10 +126,20 @@ class SearchResultPage extends StatelessWidget {
|
|||
return AnimatedBuilder(
|
||||
animation: liveSearch,
|
||||
builder: (context, child) {
|
||||
return Column(children: [
|
||||
liveSearch.loading ? const LinearProgressIndicator() : Container(),
|
||||
Expanded(child: MovieList(liveSearch.searchResults)),
|
||||
]);
|
||||
return Column(
|
||||
children: [
|
||||
liveSearch.loading ? const LinearProgressIndicator() : Container(),
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
itemCount: liveSearch.searchResults.length,
|
||||
itemBuilder: (context, index) => MovieItem(
|
||||
liveSearch.searchResults[index],
|
||||
showReleaseDate: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -158,7 +168,8 @@ class OverviewPage extends StatelessWidget {
|
|||
// Only show movies that are bookmarked or have a release date with at least month precision and at least one title
|
||||
filter: (movie) =>
|
||||
movie.bookmarked ||
|
||||
(movie.releaseDate.precision >= DatePrecision.month &&
|
||||
(movie.releaseDate.dateWithPrecision.precision >=
|
||||
DatePrecision.month &&
|
||||
(movie.titles?.length ?? 0) >= 1),
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
|
@ -209,8 +220,9 @@ class HamburgerMenu extends StatelessWidget {
|
|||
onTap: () => manager.removeMoviesWhere((movie) =>
|
||||
!movie.bookmarked &&
|
||||
!(movie.releaseDates?.any((date) =>
|
||||
date.precision >= DatePrecision.month &&
|
||||
date.date.isAfter(DateTime.now()
|
||||
date.dateWithPrecision.precision >=
|
||||
DatePrecision.month &&
|
||||
date.dateWithPrecision.date.isAfter(DateTime.now()
|
||||
.subtract(const Duration(days: 30)))) ??
|
||||
false)),
|
||||
),
|
||||
|
|
|
@ -97,7 +97,8 @@ class MovieData extends ChangeNotifier {
|
|||
}
|
||||
|
||||
bool same(MovieData other) {
|
||||
return title == other.title && releaseDate.date == other.releaseDate.date;
|
||||
return title == other.title &&
|
||||
releaseDate.dateWithPrecision == other.releaseDate.dateWithPrecision;
|
||||
}
|
||||
|
||||
Map toJsonEncodable() {
|
||||
|
@ -166,29 +167,23 @@ extension DatePrecisionComparison on DatePrecision {
|
|||
|
||||
typedef TitleInLanguage = ({String title, String language});
|
||||
|
||||
class DateWithPrecisionAndCountry {
|
||||
class DateWithPrecision implements Comparable<DateWithPrecision> {
|
||||
DateTime date;
|
||||
DatePrecision precision;
|
||||
String country;
|
||||
|
||||
DateWithPrecisionAndCountry(this.date, this.precision, this.country);
|
||||
DateWithPrecision(this.date, this.precision);
|
||||
|
||||
DateWithPrecisionAndCountry.fromJsonEncodable(List<dynamic> json)
|
||||
DateWithPrecision.fromJsonEncodable(List<dynamic> json)
|
||||
: date = DateTime.parse(json[0]),
|
||||
precision = DatePrecision.values
|
||||
.firstWhere((element) => element.name == json[1]),
|
||||
country = json[2];
|
||||
.firstWhere((element) => element.name == json[1]);
|
||||
|
||||
toJsonEncodable() {
|
||||
return [date.toIso8601String(), precision.name, country];
|
||||
List<dynamic> toJsonEncodable() {
|
||||
return [date.toIso8601String(), precision.name];
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return "${toDateString()} ($country)";
|
||||
}
|
||||
|
||||
String toDateString() {
|
||||
return switch (precision) {
|
||||
DatePrecision.decade =>
|
||||
"${DateFormat("yyyy").format(date).substring(0, 3)}0s",
|
||||
|
@ -199,6 +194,77 @@ class DateWithPrecisionAndCountry {
|
|||
DatePrecision.minute => DateFormat("MMMM d, yyyy, HH:mm").format(date)
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
int compareTo(DateWithPrecision other) {
|
||||
if (date.isBefore(other.date)) {
|
||||
return -1;
|
||||
} else if (date.isAfter(other.date)) {
|
||||
return 1;
|
||||
} else {
|
||||
return precision.index - other.precision.index;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is DateWithPrecision &&
|
||||
date == other.date &&
|
||||
precision == other.precision;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return date.hashCode ^ precision.hashCode;
|
||||
}
|
||||
|
||||
bool includes(DateTime date) {
|
||||
switch (precision) {
|
||||
case DatePrecision.decade:
|
||||
return this.date.year ~/ 10 == date.year ~/ 10;
|
||||
case DatePrecision.year:
|
||||
return this.date.year == date.year;
|
||||
case DatePrecision.month:
|
||||
return this.date.year == date.year && this.date.month == date.month;
|
||||
case DatePrecision.day:
|
||||
return this.date.year == date.year &&
|
||||
this.date.month == date.month &&
|
||||
this.date.day == date.day;
|
||||
case DatePrecision.hour:
|
||||
return this.date.year == date.year &&
|
||||
this.date.month == date.month &&
|
||||
this.date.day == date.day &&
|
||||
this.date.hour == date.hour;
|
||||
case DatePrecision.minute:
|
||||
return this.date.year == date.year &&
|
||||
this.date.month == date.month &&
|
||||
this.date.day == date.day &&
|
||||
this.date.hour == date.hour &&
|
||||
this.date.minute == date.minute;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class DateWithPrecisionAndCountry {
|
||||
final DateWithPrecision dateWithPrecision;
|
||||
final String country;
|
||||
|
||||
DateWithPrecisionAndCountry(
|
||||
DateTime date, DatePrecision precision, this.country)
|
||||
: dateWithPrecision = DateWithPrecision(date, precision);
|
||||
|
||||
DateWithPrecisionAndCountry.fromJsonEncodable(List<dynamic> json)
|
||||
: dateWithPrecision = DateWithPrecision.fromJsonEncodable(json),
|
||||
country = json[2];
|
||||
|
||||
toJsonEncodable() {
|
||||
return dateWithPrecision.toJsonEncodable() + [country];
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return "${dateWithPrecision.toString()} ($country)";
|
||||
}
|
||||
}
|
||||
|
||||
class Review {
|
||||
|
|
|
@ -70,8 +70,8 @@ class MovieManager extends ChangeNotifier {
|
|||
int max = movies.length - 1;
|
||||
while (min <= max) {
|
||||
int center = (min + max) ~/ 2;
|
||||
int diff =
|
||||
movie.releaseDate.date.compareTo(movies[center].releaseDate.date);
|
||||
int diff = movie.releaseDate.dateWithPrecision
|
||||
.compareTo(movies[center].releaseDate.dateWithPrecision);
|
||||
if (diff < 0) {
|
||||
max = center - 1;
|
||||
} else {
|
||||
|
@ -86,7 +86,12 @@ class MovieManager extends ChangeNotifier {
|
|||
var temp = movies[i];
|
||||
int j = i - 1;
|
||||
for (;
|
||||
j >= 0 && movies[j].releaseDate.date.isAfter(temp.releaseDate.date);
|
||||
j >= 0 &&
|
||||
movies[j]
|
||||
.releaseDate
|
||||
.dateWithPrecision
|
||||
.compareTo(temp.releaseDate.dateWithPrecision) >
|
||||
0;
|
||||
j--) {
|
||||
movies[j + 1] = movies[j];
|
||||
}
|
||||
|
|
|
@ -4,7 +4,8 @@ import 'package:release_schedule/view/movie_page.dart';
|
|||
|
||||
class MovieItem extends StatelessWidget {
|
||||
final MovieData movie;
|
||||
const MovieItem(this.movie, {super.key});
|
||||
final bool showReleaseDate;
|
||||
const MovieItem(this.movie, {this.showReleaseDate = false, super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -13,7 +14,10 @@ class MovieItem extends StatelessWidget {
|
|||
builder: (context, widget) {
|
||||
return ListTile(
|
||||
title: Text(movie.title),
|
||||
subtitle: Text(movie.genres?.join(", ") ?? ""),
|
||||
subtitle: Text(
|
||||
(showReleaseDate ? "${movie.releaseDate} " : "") +
|
||||
(movie.genres?.join(", ") ?? ""),
|
||||
),
|
||||
trailing: IconButton(
|
||||
icon: Icon(movie.bookmarked
|
||||
? Icons.bookmark_added
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:release_schedule/model/movie.dart';
|
||||
import 'package:release_schedule/view/movie_item.dart';
|
||||
import 'package:sticky_grouped_list/sticky_grouped_list.dart';
|
||||
|
||||
class MovieList extends StatelessWidget {
|
||||
final List<MovieData> movies;
|
||||
|
@ -8,7 +9,32 @@ class MovieList extends StatelessWidget {
|
|||
const MovieList(this.movies, {this.filter, super.key});
|
||||
|
||||
@override
|
||||
Widget build(Object context) {
|
||||
Widget build(BuildContext context) {
|
||||
Widget buildGroupSeparator(BuildContext context, DateWithPrecision date) {
|
||||
bool highlight = date.includes(DateTime.now());
|
||||
return SizedBox(
|
||||
height: 50,
|
||||
child: Align(
|
||||
alignment: Alignment.center,
|
||||
child: Card(
|
||||
elevation: 5,
|
||||
color: highlight
|
||||
? Theme.of(context).colorScheme.primaryContainer
|
||||
: null,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 8,
|
||||
),
|
||||
child: Text(
|
||||
date.toString(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final localFilter = filter;
|
||||
if (localFilter != null) {
|
||||
List<int> indexMap = [];
|
||||
|
@ -19,17 +45,25 @@ class MovieList extends StatelessWidget {
|
|||
}
|
||||
index++;
|
||||
}
|
||||
return ListView.builder(
|
||||
itemCount: indexMap.length,
|
||||
return StickyGroupedListView<int, DateWithPrecision>(
|
||||
elements: indexMap,
|
||||
floatingHeader: true,
|
||||
groupBy: (index) => movies[index].releaseDate.dateWithPrecision,
|
||||
groupSeparatorBuilder: (index) => buildGroupSeparator(
|
||||
context, movies[index].releaseDate.dateWithPrecision),
|
||||
itemBuilder: (context, index) {
|
||||
return MovieItem(movies[indexMap[index]]);
|
||||
return MovieItem(movies[index]);
|
||||
},
|
||||
);
|
||||
}
|
||||
return ListView.builder(
|
||||
itemCount: movies.length,
|
||||
itemBuilder: (context, index) {
|
||||
return MovieItem(movies[index]);
|
||||
return StickyGroupedListView<MovieData, DateWithPrecision>(
|
||||
elements: movies,
|
||||
floatingHeader: true,
|
||||
groupBy: (movie) => movie.releaseDate.dateWithPrecision,
|
||||
groupSeparatorBuilder: (movie) =>
|
||||
buildGroupSeparator(context, movie.releaseDate.dateWithPrecision),
|
||||
itemBuilder: (context, movie) {
|
||||
return MovieItem(movie);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
@ -95,7 +95,9 @@ class MoviePage extends StatelessWidget {
|
|||
TableCell(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(releaseDate.toDateString()),
|
||||
child: Text(
|
||||
releaseDate.dateWithPrecision.toString(),
|
||||
),
|
||||
))
|
||||
],
|
||||
);
|
||||
|
|
16
pubspec.lock
16
pubspec.lock
|
@ -219,6 +219,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.6"
|
||||
scrollable_positioned_list:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: scrollable_positioned_list
|
||||
sha256: "1b54d5f1329a1e263269abc9e2543d90806131aa14fe7c6062a8054d57249287"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.8"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
|
@ -240,6 +248,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.11.0"
|
||||
sticky_grouped_list:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: sticky_grouped_list
|
||||
sha256: "40398cb90321f07cbdbdd3049c27a5f048bd75a13abb19453b07a956f53a0eda"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
stream_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
@ -14,6 +14,7 @@ dependencies:
|
|||
http: ^1.1.0
|
||||
intl: ^0.18.1
|
||||
get_storage: ^2.1.1
|
||||
sticky_grouped_list: ^3.1.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
|
|
@ -136,8 +136,9 @@ void main() {
|
|||
DateTime(2023, 1, 1), DatePrecision.day, 'US');
|
||||
final json = date.toJsonEncodable();
|
||||
final date2 = DateWithPrecisionAndCountry.fromJsonEncodable(json);
|
||||
expect(date2.date, equals(date.date));
|
||||
expect(date2.precision, equals(date.precision));
|
||||
expect(date2.dateWithPrecision, equals(date.dateWithPrecision));
|
||||
expect(date2.dateWithPrecision.precision,
|
||||
equals(date.dateWithPrecision.precision));
|
||||
expect(date2.country, equals(date.country));
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in New Issue