feature: start movie list at current date
refactor: move DatePrecision and related logic into separate filemain
parent
698c785896
commit
dfde4d0aea
|
@ -6,6 +6,7 @@ import 'package:intl/intl.dart';
|
|||
import 'package:release_schedule/api/api_manager.dart';
|
||||
import 'package:release_schedule/api/json_helper.dart';
|
||||
import 'package:release_schedule/api/movie_api.dart';
|
||||
import 'package:release_schedule/model/dates.dart';
|
||||
import 'package:release_schedule/model/movie.dart';
|
||||
|
||||
class WikidataProperties {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:release_schedule/model/dates.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_item.dart';
|
||||
import 'package:release_schedule/view/movie_manager_list.dart';
|
||||
|
@ -65,6 +65,7 @@ class _HomePageState extends State<HomePage>
|
|||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
elevation: 1,
|
||||
title: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
|
@ -159,6 +160,12 @@ class OverviewPage extends StatelessWidget {
|
|||
length: 2,
|
||||
child: Column(
|
||||
children: [
|
||||
const TabBar(
|
||||
tabs: [
|
||||
Tab(icon: Icon(Icons.list), child: Text("Upcoming")),
|
||||
Tab(icon: Icon(Icons.bookmark), child: Text("Bookmarked")),
|
||||
],
|
||||
),
|
||||
Expanded(
|
||||
child: TabBarView(
|
||||
children: [
|
||||
|
@ -195,10 +202,6 @@ class OverviewPage extends StatelessWidget {
|
|||
],
|
||||
),
|
||||
),
|
||||
const TabBar(tabs: [
|
||||
Tab(icon: Icon(Icons.list), child: Text("Upcoming")),
|
||||
Tab(icon: Icon(Icons.bookmark), child: Text("Bookmarked")),
|
||||
]),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
|
|
@ -0,0 +1,124 @@
|
|||
import 'package:intl/intl.dart';
|
||||
|
||||
DateTime getToday() {
|
||||
DateTime now = DateTime.now().toUtc();
|
||||
return DateTime.utc(now.year, now.month, now.day);
|
||||
}
|
||||
|
||||
enum DatePrecision { decade, year, month, day, hour, minute }
|
||||
|
||||
extension DatePrecisionComparison on DatePrecision {
|
||||
bool operator <(DatePrecision other) {
|
||||
return index < other.index;
|
||||
}
|
||||
|
||||
bool operator <=(DatePrecision other) {
|
||||
return index <= other.index;
|
||||
}
|
||||
|
||||
bool operator >(DatePrecision other) {
|
||||
return index > other.index;
|
||||
}
|
||||
|
||||
bool operator >=(DatePrecision other) {
|
||||
return index >= other.index;
|
||||
}
|
||||
}
|
||||
|
||||
DateTime simplifyDateToPrecision(DateTime date, DatePrecision precision) {
|
||||
switch (precision) {
|
||||
case DatePrecision.decade:
|
||||
return DateTime(date.year ~/ 10 * 10);
|
||||
case DatePrecision.year:
|
||||
return DateTime(date.year);
|
||||
case DatePrecision.month:
|
||||
return DateTime(date.year, date.month);
|
||||
case DatePrecision.day:
|
||||
return DateTime(date.year, date.month, date.day);
|
||||
case DatePrecision.hour:
|
||||
return DateTime(date.year, date.month, date.day, date.hour);
|
||||
case DatePrecision.minute:
|
||||
return DateTime(date.year, date.month, date.day, date.hour, date.minute);
|
||||
}
|
||||
}
|
||||
|
||||
class DateWithPrecision implements Comparable<DateWithPrecision> {
|
||||
DateTime date;
|
||||
DatePrecision precision;
|
||||
|
||||
DateWithPrecision(DateTime date, this.precision)
|
||||
: date = simplifyDateToPrecision(date, precision);
|
||||
|
||||
DateWithPrecision.fromJsonEncodable(List<dynamic> json)
|
||||
: date = DateTime.parse(json[0]),
|
||||
precision = DatePrecision.values
|
||||
.firstWhere((element) => element.name == json[1]);
|
||||
|
||||
DateWithPrecision.today() : this(DateTime.now().toUtc(), DatePrecision.day);
|
||||
|
||||
List<dynamic> toJsonEncodable() {
|
||||
return [date.toIso8601String(), precision.name];
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return switch (precision) {
|
||||
DatePrecision.decade =>
|
||||
"${DateFormat("yyyy").format(date).substring(0, 3)}0s",
|
||||
DatePrecision.year => DateFormat.y().format(date),
|
||||
DatePrecision.month => DateFormat.yMMMM().format(date),
|
||||
DatePrecision.day => DateFormat.yMMMMd().format(date),
|
||||
DatePrecision.hour => DateFormat("MMMM d, yyyy, HH").format(date),
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:release_schedule/model/dates.dart';
|
||||
|
||||
class MovieData extends ChangeNotifier {
|
||||
String _title;
|
||||
|
@ -145,106 +145,8 @@ class MovieData extends ChangeNotifier {
|
|||
}
|
||||
}
|
||||
|
||||
enum DatePrecision { decade, year, month, day, hour, minute }
|
||||
|
||||
extension DatePrecisionComparison on DatePrecision {
|
||||
bool operator <(DatePrecision other) {
|
||||
return index < other.index;
|
||||
}
|
||||
|
||||
bool operator <=(DatePrecision other) {
|
||||
return index <= other.index;
|
||||
}
|
||||
|
||||
bool operator >(DatePrecision other) {
|
||||
return index > other.index;
|
||||
}
|
||||
|
||||
bool operator >=(DatePrecision other) {
|
||||
return index >= other.index;
|
||||
}
|
||||
}
|
||||
|
||||
typedef TitleInLanguage = ({String title, String language});
|
||||
|
||||
class DateWithPrecision implements Comparable<DateWithPrecision> {
|
||||
DateTime date;
|
||||
DatePrecision precision;
|
||||
|
||||
DateWithPrecision(this.date, this.precision);
|
||||
|
||||
DateWithPrecision.fromJsonEncodable(List<dynamic> json)
|
||||
: date = DateTime.parse(json[0]),
|
||||
precision = DatePrecision.values
|
||||
.firstWhere((element) => element.name == json[1]);
|
||||
|
||||
List<dynamic> toJsonEncodable() {
|
||||
return [date.toIso8601String(), precision.name];
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return switch (precision) {
|
||||
DatePrecision.decade =>
|
||||
"${DateFormat("yyyy").format(date).substring(0, 3)}0s",
|
||||
DatePrecision.year => DateFormat.y().format(date),
|
||||
DatePrecision.month => DateFormat.yMMMM().format(date),
|
||||
DatePrecision.day => DateFormat.yMMMMd().format(date),
|
||||
DatePrecision.hour => DateFormat("MMMM d, yyyy, HH").format(date),
|
||||
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;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:release_schedule/model/dates.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';
|
||||
|
@ -10,6 +11,24 @@ class MovieList extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (movies.isEmpty) {
|
||||
return Center(
|
||||
child: IntrinsicHeight(
|
||||
child: Column(
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.close,
|
||||
size: 100,
|
||||
),
|
||||
Text(
|
||||
"No movies available",
|
||||
style: Theme.of(context).textTheme.headlineMedium,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
Widget buildGroupSeparator(BuildContext context, DateWithPrecision date) {
|
||||
bool highlight = date.includes(DateTime.now());
|
||||
return SizedBox(
|
||||
|
@ -45,6 +64,22 @@ class MovieList extends StatelessWidget {
|
|||
}
|
||||
index++;
|
||||
}
|
||||
int firstMovieTodayOrAfterIndex = () {
|
||||
DateWithPrecision today = DateWithPrecision.today();
|
||||
int min = 0;
|
||||
int max = indexMap.length;
|
||||
while (min < max) {
|
||||
int center = (min + max) ~/ 2;
|
||||
DateWithPrecision date =
|
||||
movies[indexMap[center]].releaseDate.dateWithPrecision;
|
||||
if (date.compareTo(today) < 0) {
|
||||
min = center + 1;
|
||||
} else {
|
||||
max = center;
|
||||
}
|
||||
}
|
||||
return max;
|
||||
}();
|
||||
return StickyGroupedListView<int, DateWithPrecision>(
|
||||
elements: indexMap,
|
||||
floatingHeader: true,
|
||||
|
@ -54,8 +89,24 @@ class MovieList extends StatelessWidget {
|
|||
itemBuilder: (context, index) {
|
||||
return MovieItem(movies[index]);
|
||||
},
|
||||
initialScrollIndex: firstMovieTodayOrAfterIndex,
|
||||
);
|
||||
}
|
||||
int firstMovieTodayOrAfterIndex = () {
|
||||
DateWithPrecision today = DateWithPrecision.today();
|
||||
int min = 0;
|
||||
int max = movies.length;
|
||||
while (min < max) {
|
||||
int center = (min + max) ~/ 2;
|
||||
DateWithPrecision date = movies[center].releaseDate.dateWithPrecision;
|
||||
if (date.compareTo(today) < 0) {
|
||||
min = center + 1;
|
||||
} else {
|
||||
max = center;
|
||||
}
|
||||
}
|
||||
return max;
|
||||
}();
|
||||
return StickyGroupedListView<MovieData, DateWithPrecision>(
|
||||
elements: movies,
|
||||
floatingHeader: true,
|
||||
|
@ -65,6 +116,7 @@ class MovieList extends StatelessWidget {
|
|||
itemBuilder: (context, movie) {
|
||||
return MovieItem(movie);
|
||||
},
|
||||
initialScrollIndex: firstMovieTodayOrAfterIndex,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:release_schedule/api/movie_api.dart';
|
||||
import 'package:release_schedule/model/dates.dart';
|
||||
import 'package:release_schedule/model/local_movie_storage.dart';
|
||||
import 'package:release_schedule/model/movie.dart';
|
||||
import 'package:release_schedule/model/movie_manager.dart';
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:release_schedule/model/dates.dart';
|
||||
import 'package:release_schedule/model/movie.dart';
|
||||
|
||||
void main() {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:release_schedule/model/dates.dart';
|
||||
import 'package:release_schedule/model/movie.dart';
|
||||
import 'package:release_schedule/view/movie_item.dart';
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:release_schedule/model/dates.dart';
|
||||
import 'package:release_schedule/model/movie.dart';
|
||||
import 'package:release_schedule/view/movie_item.dart';
|
||||
import 'package:release_schedule/view/movie_list.dart';
|
||||
|
@ -28,6 +29,8 @@ void main() {
|
|||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(MovieItem), findsNWidgets(movies.length));
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:release_schedule/api/movie_api.dart';
|
||||
import 'package:release_schedule/model/dates.dart';
|
||||
import 'package:release_schedule/model/local_movie_storage.dart';
|
||||
import 'package:release_schedule/model/movie.dart';
|
||||
import 'package:release_schedule/model/movie_manager.dart';
|
||||
|
|
Loading…
Reference in New Issue