Compare commits

...

25 Commits

Author SHA1 Message Date
e489cae228 release: Release 0.2.2 2026-07-26 22:15:25 +02:00
06da4dbb16 chore: Apply dart analyze fixes 2026-07-26 22:14:24 +02:00
18f0d55e00 feat: Improve a bit the padding on about 2026-07-26 22:10:18 +02:00
d7566c9414 fix: Disable go_router debug logging in debug mode 2026-07-26 22:07:47 +02:00
c5c7711611 fix: Fix licenses on the about page 2026-07-26 22:05:40 +02:00
84594b42cf fix: Fix hardcoded text 2026-07-26 22:02:03 +02:00
437d76fddc fix: Fix PageView not working after adding an entry 2026-07-26 21:59:51 +02:00
3a6ab1e825 fix: Navigation back from the details view 2026-07-26 19:00:09 +02:00
39cc9b8d7c feat: Fix framework assertion when adding an item 2026-07-26 18:55:42 +02:00
58ea71e694 feat: Also update total episode count on refresh 2026-07-26 18:42:32 +02:00
04d64c3178 feat: Fix calendar view using a stack 2026-07-26 18:37:03 +02:00
69553707a9 fix: Use ON DELETE CASCADE for the anime watcher join table 2026-07-26 18:14:39 +02:00
d034875e16 fix: Fix routing 2026-07-26 15:53:36 +02:00
34b3817e6d release: Release 0.2.1 2026-07-26 00:06:29 +02:00
a17bebffe4 fix: Fix database due to forgotten new attribute 2026-07-26 00:06:02 +02:00
87fc1566f2 fix: Put the database somewhere more sensible on Linux 2026-07-25 23:48:27 +02:00
85df1e702a chore: Update dependencies 2026-07-25 23:40:42 +02:00
cfe0c0fb94 fix: Fix database entry creation due to missing other titles 2026-07-25 23:38:13 +02:00
2f5ddb6ea4 feat: Use large cover images instead of medium ones 2026-07-25 23:11:24 +02:00
60c09fcffd fix: Fix a weird interaction when switching back to the anime list 2026-07-25 23:05:16 +02:00
0e61f9f757 fix: Fix TextField look when adding a watcher 2026-07-25 22:52:26 +02:00
90a7531d72 fix: Fix hero transition for images 2026-07-25 20:57:14 +02:00
8a31f2b72d chore: Move details.dart into details/ 2026-07-25 20:49:54 +02:00
c47c98e65e fix: Fix weird borders in the calendar view 2026-07-25 20:46:13 +02:00
ca796ab9f9 fix: Fix anime watchers creation 2026-07-25 20:45:16 +02:00
43 changed files with 2616 additions and 7930 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -12,15 +12,16 @@ import 'package:anitrack/src/ui/pages/about.dart';
import 'package:anitrack/src/ui/pages/anime_list.dart';
import 'package:anitrack/src/ui/pages/anime_search.dart';
import 'package:anitrack/src/ui/pages/calendar.dart';
import 'package:anitrack/src/ui/pages/details.dart';
import 'package:anitrack/src/ui/pages/details/details.dart';
import 'package:anitrack/src/ui/pages/settings.dart';
import 'package:anitrack/src/ui/widgets/shell_wrapper.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:get_it/get_it.dart';
import 'package:go_router/go_router.dart';
void main() async {
final navKey = GlobalKey<NavigatorState>();
// Initialize the widgets binding for sqflite
WidgetsFlutterBinding.ensureInitialized();
@@ -33,7 +34,7 @@ void main() async {
GetIt.I.registerSingleton<AnimeListBloc>(AnimeListBloc());
GetIt.I.registerSingleton<AnimeSearchBloc>(AnimeSearchBloc());
GetIt.I.registerSingleton<DetailsBloc>(DetailsBloc());
GetIt.I.registerSingleton<NavigationBloc>(NavigationBloc(navKey));
GetIt.I.registerSingleton<NavigationBloc>(NavigationBloc());
GetIt.I.registerSingleton<SettingsBloc>(SettingsBloc());
GetIt.I.registerSingleton<CalendarBloc>(CalendarBloc());
GetIt.I.registerSingleton<AniListClient>(AniListClient());
@@ -68,53 +69,85 @@ void main() async {
create: (_) => GetIt.I.get<CalendarBloc>(),
),
],
child: MyApp(navKey),
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
const MyApp(
this.navKey, {
super.key,
});
MyApp({super.key});
final GlobalKey<NavigatorState> navKey;
/// The router to attach to the main app.
final _router = GoRouter(
initialLocation: animeListRoute,
debugLogDiagnostics: kDebugMode,
routes: [
ShellRoute(
builder: (context, state, child) {
return ShellWrapper(state: state, child: child);
},
routes: [
GoRoute(
path: animeListRoute,
builder: (context, state) => const AnimeListPage(),
),
GoRoute(
path: calendarRoute,
builder: (context, state) => const CalendarPage(),
),
],
),
GoRoute(
path: animeSearchRoute,
builder: (context, state) => const AnimeSearchPage(),
),
GoRoute(
path: detailsRoute,
builder: (context, state) => const DetailsPage(),
),
GoRoute(
path: aboutRoute,
builder: (context, state) => const AboutPage(),
),
GoRoute(
path: settingsRoute,
builder: (context, state) => const SettingsPage(),
),
],
);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'AniTrack',
theme: ThemeData(
brightness: Brightness.light,
primarySwatch: Colors.blue,
useMaterial3: true,
),
darkTheme: ThemeData(
brightness: Brightness.dark,
primarySwatch: Colors.blue,
useMaterial3: true,
),
navigatorKey: navKey,
onGenerateRoute: (settings) {
switch (settings.name) {
case '/':
case animeListRoute:
return AnimeListPage.route;
case animeSearchRoute:
return AnimeSearchPage.route;
case calendarRoute:
return CalendarPage.route;
case detailsRoute:
return DetailsPage.route;
case aboutRoute:
return AboutPage.route;
case settingsRoute:
return SettingsPage.route;
return BlocListener<NavigationBloc, NavigationState>(
listener: (context, state) {
if (state is NoopNavigationState) {
// NOOP
} else if (state is PushNavigationState) {
_router.push(state.destination);
} else if (state is GoNavigationState) {
if (_router.canPop()) {
_router.replace(state.destination);
} else {
_router.go(state.destination);
}
} else if (state is PoppedNavigationState) {
_router.pop();
}
return null;
},
child: MaterialApp.router(
title: 'AniTrack',
theme: ThemeData(
brightness: Brightness.light,
primarySwatch: Colors.blue,
useMaterial3: true,
),
darkTheme: ThemeData(
brightness: Brightness.dark,
primarySwatch: Colors.blue,
useMaterial3: true,
),
routerConfig: _router,
),
);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,5 @@
import 'package:anitrack/src/data/data_base.dart';
import 'package:anitrack/src/data/other_titles.dart';
import 'package:anitrack/src/data/source.dart';
import 'package:anitrack/src/data/type.dart';
import 'package:anitrack/src/service/database.dart';
@@ -47,6 +48,9 @@ abstract class AnimeTrackingData
/// The day of the week the anime is airing
String? broadcastDay,
/// Other titles
@OtherTitlesConverter() OtherTitles otherTitles,
/// The source where we got the data from.
@TrackingDataSourceConverter() TrackingDataSource source,
) = _AnimeTrackingData;

View File

@@ -23,7 +23,8 @@ mixin _$AnimeTrackingData {
int? get episodesTotal;/// URL to the thumbnail/cover art for the anime.
String get thumbnailUrl;/// Flag whether the anime is airing
@BoolConverter() bool get airing;/// The day of the week the anime is airing
String? get broadcastDay;/// The source where we got the data from.
String? get broadcastDay;/// Other titles
@OtherTitlesConverter() OtherTitles get otherTitles;/// The source where we got the data from.
@TrackingDataSourceConverter() TrackingDataSource get source;
/// Create a copy of AnimeTrackingData
/// with the given fields replaced by the non-null parameter values.
@@ -37,16 +38,16 @@ $AnimeTrackingDataCopyWith<AnimeTrackingData> get copyWith => _$AnimeTrackingDat
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is AnimeTrackingData&&(identical(other.id, id) || other.id == id)&&(identical(other.state, state) || other.state == state)&&(identical(other.title, title) || other.title == title)&&(identical(other.episodesWatched, episodesWatched) || other.episodesWatched == episodesWatched)&&(identical(other.episodesTotal, episodesTotal) || other.episodesTotal == episodesTotal)&&(identical(other.thumbnailUrl, thumbnailUrl) || other.thumbnailUrl == thumbnailUrl)&&(identical(other.airing, airing) || other.airing == airing)&&(identical(other.broadcastDay, broadcastDay) || other.broadcastDay == broadcastDay)&&(identical(other.source, source) || other.source == source));
return identical(this, other) || (other.runtimeType == runtimeType&&other is AnimeTrackingData&&(identical(other.id, id) || other.id == id)&&(identical(other.state, state) || other.state == state)&&(identical(other.title, title) || other.title == title)&&(identical(other.episodesWatched, episodesWatched) || other.episodesWatched == episodesWatched)&&(identical(other.episodesTotal, episodesTotal) || other.episodesTotal == episodesTotal)&&(identical(other.thumbnailUrl, thumbnailUrl) || other.thumbnailUrl == thumbnailUrl)&&(identical(other.airing, airing) || other.airing == airing)&&(identical(other.broadcastDay, broadcastDay) || other.broadcastDay == broadcastDay)&&(identical(other.otherTitles, otherTitles) || other.otherTitles == otherTitles)&&(identical(other.source, source) || other.source == source));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,state,title,episodesWatched,episodesTotal,thumbnailUrl,airing,broadcastDay,source);
int get hashCode => Object.hash(runtimeType,id,state,title,episodesWatched,episodesTotal,thumbnailUrl,airing,broadcastDay,otherTitles,source);
@override
String toString() {
return 'AnimeTrackingData(id: $id, state: $state, title: $title, episodesWatched: $episodesWatched, episodesTotal: $episodesTotal, thumbnailUrl: $thumbnailUrl, airing: $airing, broadcastDay: $broadcastDay, source: $source)';
return 'AnimeTrackingData(id: $id, state: $state, title: $title, episodesWatched: $episodesWatched, episodesTotal: $episodesTotal, thumbnailUrl: $thumbnailUrl, airing: $airing, broadcastDay: $broadcastDay, otherTitles: $otherTitles, source: $source)';
}
@@ -57,11 +58,11 @@ abstract mixin class $AnimeTrackingDataCopyWith<$Res> {
factory $AnimeTrackingDataCopyWith(AnimeTrackingData value, $Res Function(AnimeTrackingData) _then) = _$AnimeTrackingDataCopyWithImpl;
@useResult
$Res call({
String id,@MediumTrackingStateConverter() MediumTrackingState state, String title, int episodesWatched, int? episodesTotal, String thumbnailUrl,@BoolConverter() bool airing, String? broadcastDay,@TrackingDataSourceConverter() TrackingDataSource source
String id,@MediumTrackingStateConverter() MediumTrackingState state, String title, int episodesWatched, int? episodesTotal, String thumbnailUrl,@BoolConverter() bool airing, String? broadcastDay,@OtherTitlesConverter() OtherTitles otherTitles,@TrackingDataSourceConverter() TrackingDataSource source
});
$OtherTitlesCopyWith<$Res> get otherTitles;
}
/// @nodoc
@@ -74,7 +75,7 @@ class _$AnimeTrackingDataCopyWithImpl<$Res>
/// Create a copy of AnimeTrackingData
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? state = null,Object? title = null,Object? episodesWatched = null,Object? episodesTotal = freezed,Object? thumbnailUrl = null,Object? airing = null,Object? broadcastDay = freezed,Object? source = null,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? state = null,Object? title = null,Object? episodesWatched = null,Object? episodesTotal = freezed,Object? thumbnailUrl = null,Object? airing = null,Object? broadcastDay = freezed,Object? otherTitles = null,Object? source = null,}) {
return _then(_self.copyWith(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,state: null == state ? _self.state : state // ignore: cast_nullable_to_non_nullable
@@ -84,11 +85,21 @@ as int,episodesTotal: freezed == episodesTotal ? _self.episodesTotal : episodesT
as int?,thumbnailUrl: null == thumbnailUrl ? _self.thumbnailUrl : thumbnailUrl // ignore: cast_nullable_to_non_nullable
as String,airing: null == airing ? _self.airing : airing // ignore: cast_nullable_to_non_nullable
as bool,broadcastDay: freezed == broadcastDay ? _self.broadcastDay : broadcastDay // ignore: cast_nullable_to_non_nullable
as String?,source: null == source ? _self.source : source // ignore: cast_nullable_to_non_nullable
as String?,otherTitles: null == otherTitles ? _self.otherTitles : otherTitles // ignore: cast_nullable_to_non_nullable
as OtherTitles,source: null == source ? _self.source : source // ignore: cast_nullable_to_non_nullable
as TrackingDataSource,
));
}
/// Create a copy of AnimeTrackingData
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$OtherTitlesCopyWith<$Res> get otherTitles {
return $OtherTitlesCopyWith<$Res>(_self.otherTitles, (value) {
return _then(_self.copyWith(otherTitles: value));
});
}
}
@@ -170,10 +181,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, @MediumTrackingStateConverter() MediumTrackingState state, String title, int episodesWatched, int? episodesTotal, String thumbnailUrl, @BoolConverter() bool airing, String? broadcastDay, @TrackingDataSourceConverter() TrackingDataSource source)? $default,{required TResult orElse(),}) {final _that = this;
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, @MediumTrackingStateConverter() MediumTrackingState state, String title, int episodesWatched, int? episodesTotal, String thumbnailUrl, @BoolConverter() bool airing, String? broadcastDay, @OtherTitlesConverter() OtherTitles otherTitles, @TrackingDataSourceConverter() TrackingDataSource source)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _AnimeTrackingData() when $default != null:
return $default(_that.id,_that.state,_that.title,_that.episodesWatched,_that.episodesTotal,_that.thumbnailUrl,_that.airing,_that.broadcastDay,_that.source);case _:
return $default(_that.id,_that.state,_that.title,_that.episodesWatched,_that.episodesTotal,_that.thumbnailUrl,_that.airing,_that.broadcastDay,_that.otherTitles,_that.source);case _:
return orElse();
}
@@ -191,10 +202,10 @@ return $default(_that.id,_that.state,_that.title,_that.episodesWatched,_that.epi
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, @MediumTrackingStateConverter() MediumTrackingState state, String title, int episodesWatched, int? episodesTotal, String thumbnailUrl, @BoolConverter() bool airing, String? broadcastDay, @TrackingDataSourceConverter() TrackingDataSource source) $default,) {final _that = this;
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, @MediumTrackingStateConverter() MediumTrackingState state, String title, int episodesWatched, int? episodesTotal, String thumbnailUrl, @BoolConverter() bool airing, String? broadcastDay, @OtherTitlesConverter() OtherTitles otherTitles, @TrackingDataSourceConverter() TrackingDataSource source) $default,) {final _that = this;
switch (_that) {
case _AnimeTrackingData():
return $default(_that.id,_that.state,_that.title,_that.episodesWatched,_that.episodesTotal,_that.thumbnailUrl,_that.airing,_that.broadcastDay,_that.source);case _:
return $default(_that.id,_that.state,_that.title,_that.episodesWatched,_that.episodesTotal,_that.thumbnailUrl,_that.airing,_that.broadcastDay,_that.otherTitles,_that.source);case _:
throw StateError('Unexpected subclass');
}
@@ -211,10 +222,10 @@ return $default(_that.id,_that.state,_that.title,_that.episodesWatched,_that.epi
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, @MediumTrackingStateConverter() MediumTrackingState state, String title, int episodesWatched, int? episodesTotal, String thumbnailUrl, @BoolConverter() bool airing, String? broadcastDay, @TrackingDataSourceConverter() TrackingDataSource source)? $default,) {final _that = this;
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, @MediumTrackingStateConverter() MediumTrackingState state, String title, int episodesWatched, int? episodesTotal, String thumbnailUrl, @BoolConverter() bool airing, String? broadcastDay, @OtherTitlesConverter() OtherTitles otherTitles, @TrackingDataSourceConverter() TrackingDataSource source)? $default,) {final _that = this;
switch (_that) {
case _AnimeTrackingData() when $default != null:
return $default(_that.id,_that.state,_that.title,_that.episodesWatched,_that.episodesTotal,_that.thumbnailUrl,_that.airing,_that.broadcastDay,_that.source);case _:
return $default(_that.id,_that.state,_that.title,_that.episodesWatched,_that.episodesTotal,_that.thumbnailUrl,_that.airing,_that.broadcastDay,_that.otherTitles,_that.source);case _:
return null;
}
@@ -226,7 +237,7 @@ return $default(_that.id,_that.state,_that.title,_that.episodesWatched,_that.epi
@JsonSerializable()
class _AnimeTrackingData extends AnimeTrackingData {
_AnimeTrackingData(this.id, @MediumTrackingStateConverter() this.state, this.title, this.episodesWatched, this.episodesTotal, this.thumbnailUrl, @BoolConverter() this.airing, this.broadcastDay, @TrackingDataSourceConverter() this.source): super._();
_AnimeTrackingData(this.id, @MediumTrackingStateConverter() this.state, this.title, this.episodesWatched, this.episodesTotal, this.thumbnailUrl, @BoolConverter() this.airing, this.broadcastDay, @OtherTitlesConverter() this.otherTitles, @TrackingDataSourceConverter() this.source): super._();
factory _AnimeTrackingData.fromJson(Map<String, dynamic> json) => _$AnimeTrackingDataFromJson(json);
/// The ID of the anime
@@ -245,6 +256,8 @@ class _AnimeTrackingData extends AnimeTrackingData {
@override@BoolConverter() final bool airing;
/// The day of the week the anime is airing
@override final String? broadcastDay;
/// Other titles
@override@OtherTitlesConverter() final OtherTitles otherTitles;
/// The source where we got the data from.
@override@TrackingDataSourceConverter() final TrackingDataSource source;
@@ -261,16 +274,16 @@ Map<String, dynamic> toJson() {
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _AnimeTrackingData&&(identical(other.id, id) || other.id == id)&&(identical(other.state, state) || other.state == state)&&(identical(other.title, title) || other.title == title)&&(identical(other.episodesWatched, episodesWatched) || other.episodesWatched == episodesWatched)&&(identical(other.episodesTotal, episodesTotal) || other.episodesTotal == episodesTotal)&&(identical(other.thumbnailUrl, thumbnailUrl) || other.thumbnailUrl == thumbnailUrl)&&(identical(other.airing, airing) || other.airing == airing)&&(identical(other.broadcastDay, broadcastDay) || other.broadcastDay == broadcastDay)&&(identical(other.source, source) || other.source == source));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _AnimeTrackingData&&(identical(other.id, id) || other.id == id)&&(identical(other.state, state) || other.state == state)&&(identical(other.title, title) || other.title == title)&&(identical(other.episodesWatched, episodesWatched) || other.episodesWatched == episodesWatched)&&(identical(other.episodesTotal, episodesTotal) || other.episodesTotal == episodesTotal)&&(identical(other.thumbnailUrl, thumbnailUrl) || other.thumbnailUrl == thumbnailUrl)&&(identical(other.airing, airing) || other.airing == airing)&&(identical(other.broadcastDay, broadcastDay) || other.broadcastDay == broadcastDay)&&(identical(other.otherTitles, otherTitles) || other.otherTitles == otherTitles)&&(identical(other.source, source) || other.source == source));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,state,title,episodesWatched,episodesTotal,thumbnailUrl,airing,broadcastDay,source);
int get hashCode => Object.hash(runtimeType,id,state,title,episodesWatched,episodesTotal,thumbnailUrl,airing,broadcastDay,otherTitles,source);
@override
String toString() {
return 'AnimeTrackingData(id: $id, state: $state, title: $title, episodesWatched: $episodesWatched, episodesTotal: $episodesTotal, thumbnailUrl: $thumbnailUrl, airing: $airing, broadcastDay: $broadcastDay, source: $source)';
return 'AnimeTrackingData(id: $id, state: $state, title: $title, episodesWatched: $episodesWatched, episodesTotal: $episodesTotal, thumbnailUrl: $thumbnailUrl, airing: $airing, broadcastDay: $broadcastDay, otherTitles: $otherTitles, source: $source)';
}
@@ -281,11 +294,11 @@ abstract mixin class _$AnimeTrackingDataCopyWith<$Res> implements $AnimeTracking
factory _$AnimeTrackingDataCopyWith(_AnimeTrackingData value, $Res Function(_AnimeTrackingData) _then) = __$AnimeTrackingDataCopyWithImpl;
@override @useResult
$Res call({
String id,@MediumTrackingStateConverter() MediumTrackingState state, String title, int episodesWatched, int? episodesTotal, String thumbnailUrl,@BoolConverter() bool airing, String? broadcastDay,@TrackingDataSourceConverter() TrackingDataSource source
String id,@MediumTrackingStateConverter() MediumTrackingState state, String title, int episodesWatched, int? episodesTotal, String thumbnailUrl,@BoolConverter() bool airing, String? broadcastDay,@OtherTitlesConverter() OtherTitles otherTitles,@TrackingDataSourceConverter() TrackingDataSource source
});
@override $OtherTitlesCopyWith<$Res> get otherTitles;
}
/// @nodoc
@@ -298,7 +311,7 @@ class __$AnimeTrackingDataCopyWithImpl<$Res>
/// Create a copy of AnimeTrackingData
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? state = null,Object? title = null,Object? episodesWatched = null,Object? episodesTotal = freezed,Object? thumbnailUrl = null,Object? airing = null,Object? broadcastDay = freezed,Object? source = null,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? state = null,Object? title = null,Object? episodesWatched = null,Object? episodesTotal = freezed,Object? thumbnailUrl = null,Object? airing = null,Object? broadcastDay = freezed,Object? otherTitles = null,Object? source = null,}) {
return _then(_AnimeTrackingData(
null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,null == state ? _self.state : state // ignore: cast_nullable_to_non_nullable
@@ -308,12 +321,22 @@ as int,freezed == episodesTotal ? _self.episodesTotal : episodesTotal // ignore:
as int?,null == thumbnailUrl ? _self.thumbnailUrl : thumbnailUrl // ignore: cast_nullable_to_non_nullable
as String,null == airing ? _self.airing : airing // ignore: cast_nullable_to_non_nullable
as bool,freezed == broadcastDay ? _self.broadcastDay : broadcastDay // ignore: cast_nullable_to_non_nullable
as String?,null == source ? _self.source : source // ignore: cast_nullable_to_non_nullable
as String?,null == otherTitles ? _self.otherTitles : otherTitles // ignore: cast_nullable_to_non_nullable
as OtherTitles,null == source ? _self.source : source // ignore: cast_nullable_to_non_nullable
as TrackingDataSource,
));
}
/// Create a copy of AnimeTrackingData
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$OtherTitlesCopyWith<$Res> get otherTitles {
return $OtherTitlesCopyWith<$Res>(_self.otherTitles, (value) {
return _then(_self.copyWith(otherTitles: value));
});
}
}
// dart format on

View File

@@ -18,6 +18,7 @@ _AnimeTrackingData _$AnimeTrackingDataFromJson(Map<String, dynamic> json) =>
json['thumbnailUrl'] as String,
const BoolConverter().fromJson((json['airing'] as num).toInt()),
json['broadcastDay'] as String?,
const OtherTitlesConverter().fromJson(json['otherTitles'] as String),
const TrackingDataSourceConverter().fromJson(json['source'] as String),
);
@@ -31,5 +32,6 @@ Map<String, dynamic> _$AnimeTrackingDataToJson(_AnimeTrackingData instance) =>
'thumbnailUrl': instance.thumbnailUrl,
'airing': const BoolConverter().toJson(instance.airing),
'broadcastDay': instance.broadcastDay,
'otherTitles': const OtherTitlesConverter().toJson(instance.otherTitles),
'source': const TrackingDataSourceConverter().toJson(instance.source),
};

View File

@@ -1,4 +1,5 @@
import 'package:anitrack/src/data/data_base.dart';
import 'package:anitrack/src/data/other_titles.dart';
import 'package:anitrack/src/data/source.dart';
import 'package:anitrack/src/data/type.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
@@ -33,6 +34,9 @@ abstract class MangaTrackingData
/// URL to the thumbnail/cover art for the manga.
String thumbnailUrl,
/// Other titles the manga can have.
@OtherTitlesConverter() OtherTitles otherTitles,
/// The source where we got the data from.
@TrackingDataSourceConverter() TrackingDataSource source,
) = _MangaTrackingData;

View File

@@ -22,7 +22,8 @@ mixin _$MangaTrackingData {
int get chaptersRead;/// Chapters read.
int get volumesOwned;/// Episodes watched.
int? get chaptersTotal;/// URL to the thumbnail/cover art for the manga.
String get thumbnailUrl;/// The source where we got the data from.
String get thumbnailUrl;/// Other titles the manga can have.
@OtherTitlesConverter() OtherTitles get otherTitles;/// The source where we got the data from.
@TrackingDataSourceConverter() TrackingDataSource get source;
/// Create a copy of MangaTrackingData
/// with the given fields replaced by the non-null parameter values.
@@ -36,16 +37,16 @@ $MangaTrackingDataCopyWith<MangaTrackingData> get copyWith => _$MangaTrackingDat
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is MangaTrackingData&&(identical(other.id, id) || other.id == id)&&(identical(other.state, state) || other.state == state)&&(identical(other.title, title) || other.title == title)&&(identical(other.chaptersRead, chaptersRead) || other.chaptersRead == chaptersRead)&&(identical(other.volumesOwned, volumesOwned) || other.volumesOwned == volumesOwned)&&(identical(other.chaptersTotal, chaptersTotal) || other.chaptersTotal == chaptersTotal)&&(identical(other.thumbnailUrl, thumbnailUrl) || other.thumbnailUrl == thumbnailUrl)&&(identical(other.source, source) || other.source == source));
return identical(this, other) || (other.runtimeType == runtimeType&&other is MangaTrackingData&&(identical(other.id, id) || other.id == id)&&(identical(other.state, state) || other.state == state)&&(identical(other.title, title) || other.title == title)&&(identical(other.chaptersRead, chaptersRead) || other.chaptersRead == chaptersRead)&&(identical(other.volumesOwned, volumesOwned) || other.volumesOwned == volumesOwned)&&(identical(other.chaptersTotal, chaptersTotal) || other.chaptersTotal == chaptersTotal)&&(identical(other.thumbnailUrl, thumbnailUrl) || other.thumbnailUrl == thumbnailUrl)&&(identical(other.otherTitles, otherTitles) || other.otherTitles == otherTitles)&&(identical(other.source, source) || other.source == source));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,state,title,chaptersRead,volumesOwned,chaptersTotal,thumbnailUrl,source);
int get hashCode => Object.hash(runtimeType,id,state,title,chaptersRead,volumesOwned,chaptersTotal,thumbnailUrl,otherTitles,source);
@override
String toString() {
return 'MangaTrackingData(id: $id, state: $state, title: $title, chaptersRead: $chaptersRead, volumesOwned: $volumesOwned, chaptersTotal: $chaptersTotal, thumbnailUrl: $thumbnailUrl, source: $source)';
return 'MangaTrackingData(id: $id, state: $state, title: $title, chaptersRead: $chaptersRead, volumesOwned: $volumesOwned, chaptersTotal: $chaptersTotal, thumbnailUrl: $thumbnailUrl, otherTitles: $otherTitles, source: $source)';
}
@@ -56,11 +57,11 @@ abstract mixin class $MangaTrackingDataCopyWith<$Res> {
factory $MangaTrackingDataCopyWith(MangaTrackingData value, $Res Function(MangaTrackingData) _then) = _$MangaTrackingDataCopyWithImpl;
@useResult
$Res call({
String id,@MediumTrackingStateConverter() MediumTrackingState state, String title, int chaptersRead, int volumesOwned, int? chaptersTotal, String thumbnailUrl,@TrackingDataSourceConverter() TrackingDataSource source
String id,@MediumTrackingStateConverter() MediumTrackingState state, String title, int chaptersRead, int volumesOwned, int? chaptersTotal, String thumbnailUrl,@OtherTitlesConverter() OtherTitles otherTitles,@TrackingDataSourceConverter() TrackingDataSource source
});
$OtherTitlesCopyWith<$Res> get otherTitles;
}
/// @nodoc
@@ -73,7 +74,7 @@ class _$MangaTrackingDataCopyWithImpl<$Res>
/// Create a copy of MangaTrackingData
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? state = null,Object? title = null,Object? chaptersRead = null,Object? volumesOwned = null,Object? chaptersTotal = freezed,Object? thumbnailUrl = null,Object? source = null,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? state = null,Object? title = null,Object? chaptersRead = null,Object? volumesOwned = null,Object? chaptersTotal = freezed,Object? thumbnailUrl = null,Object? otherTitles = null,Object? source = null,}) {
return _then(_self.copyWith(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,state: null == state ? _self.state : state // ignore: cast_nullable_to_non_nullable
@@ -82,11 +83,21 @@ as String,chaptersRead: null == chaptersRead ? _self.chaptersRead : chaptersRead
as int,volumesOwned: null == volumesOwned ? _self.volumesOwned : volumesOwned // ignore: cast_nullable_to_non_nullable
as int,chaptersTotal: freezed == chaptersTotal ? _self.chaptersTotal : chaptersTotal // ignore: cast_nullable_to_non_nullable
as int?,thumbnailUrl: null == thumbnailUrl ? _self.thumbnailUrl : thumbnailUrl // ignore: cast_nullable_to_non_nullable
as String,source: null == source ? _self.source : source // ignore: cast_nullable_to_non_nullable
as String,otherTitles: null == otherTitles ? _self.otherTitles : otherTitles // ignore: cast_nullable_to_non_nullable
as OtherTitles,source: null == source ? _self.source : source // ignore: cast_nullable_to_non_nullable
as TrackingDataSource,
));
}
/// Create a copy of MangaTrackingData
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$OtherTitlesCopyWith<$Res> get otherTitles {
return $OtherTitlesCopyWith<$Res>(_self.otherTitles, (value) {
return _then(_self.copyWith(otherTitles: value));
});
}
}
@@ -168,10 +179,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, @MediumTrackingStateConverter() MediumTrackingState state, String title, int chaptersRead, int volumesOwned, int? chaptersTotal, String thumbnailUrl, @TrackingDataSourceConverter() TrackingDataSource source)? $default,{required TResult orElse(),}) {final _that = this;
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, @MediumTrackingStateConverter() MediumTrackingState state, String title, int chaptersRead, int volumesOwned, int? chaptersTotal, String thumbnailUrl, @OtherTitlesConverter() OtherTitles otherTitles, @TrackingDataSourceConverter() TrackingDataSource source)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _MangaTrackingData() when $default != null:
return $default(_that.id,_that.state,_that.title,_that.chaptersRead,_that.volumesOwned,_that.chaptersTotal,_that.thumbnailUrl,_that.source);case _:
return $default(_that.id,_that.state,_that.title,_that.chaptersRead,_that.volumesOwned,_that.chaptersTotal,_that.thumbnailUrl,_that.otherTitles,_that.source);case _:
return orElse();
}
@@ -189,10 +200,10 @@ return $default(_that.id,_that.state,_that.title,_that.chaptersRead,_that.volume
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, @MediumTrackingStateConverter() MediumTrackingState state, String title, int chaptersRead, int volumesOwned, int? chaptersTotal, String thumbnailUrl, @TrackingDataSourceConverter() TrackingDataSource source) $default,) {final _that = this;
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, @MediumTrackingStateConverter() MediumTrackingState state, String title, int chaptersRead, int volumesOwned, int? chaptersTotal, String thumbnailUrl, @OtherTitlesConverter() OtherTitles otherTitles, @TrackingDataSourceConverter() TrackingDataSource source) $default,) {final _that = this;
switch (_that) {
case _MangaTrackingData():
return $default(_that.id,_that.state,_that.title,_that.chaptersRead,_that.volumesOwned,_that.chaptersTotal,_that.thumbnailUrl,_that.source);case _:
return $default(_that.id,_that.state,_that.title,_that.chaptersRead,_that.volumesOwned,_that.chaptersTotal,_that.thumbnailUrl,_that.otherTitles,_that.source);case _:
throw StateError('Unexpected subclass');
}
@@ -209,10 +220,10 @@ return $default(_that.id,_that.state,_that.title,_that.chaptersRead,_that.volume
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, @MediumTrackingStateConverter() MediumTrackingState state, String title, int chaptersRead, int volumesOwned, int? chaptersTotal, String thumbnailUrl, @TrackingDataSourceConverter() TrackingDataSource source)? $default,) {final _that = this;
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, @MediumTrackingStateConverter() MediumTrackingState state, String title, int chaptersRead, int volumesOwned, int? chaptersTotal, String thumbnailUrl, @OtherTitlesConverter() OtherTitles otherTitles, @TrackingDataSourceConverter() TrackingDataSource source)? $default,) {final _that = this;
switch (_that) {
case _MangaTrackingData() when $default != null:
return $default(_that.id,_that.state,_that.title,_that.chaptersRead,_that.volumesOwned,_that.chaptersTotal,_that.thumbnailUrl,_that.source);case _:
return $default(_that.id,_that.state,_that.title,_that.chaptersRead,_that.volumesOwned,_that.chaptersTotal,_that.thumbnailUrl,_that.otherTitles,_that.source);case _:
return null;
}
@@ -224,7 +235,7 @@ return $default(_that.id,_that.state,_that.title,_that.chaptersRead,_that.volume
@JsonSerializable()
class _MangaTrackingData extends MangaTrackingData {
_MangaTrackingData(this.id, @MediumTrackingStateConverter() this.state, this.title, this.chaptersRead, this.volumesOwned, this.chaptersTotal, this.thumbnailUrl, @TrackingDataSourceConverter() this.source): super._();
_MangaTrackingData(this.id, @MediumTrackingStateConverter() this.state, this.title, this.chaptersRead, this.volumesOwned, this.chaptersTotal, this.thumbnailUrl, @OtherTitlesConverter() this.otherTitles, @TrackingDataSourceConverter() this.source): super._();
factory _MangaTrackingData.fromJson(Map<String, dynamic> json) => _$MangaTrackingDataFromJson(json);
/// The ID of the manga
@@ -241,6 +252,8 @@ class _MangaTrackingData extends MangaTrackingData {
@override final int? chaptersTotal;
/// URL to the thumbnail/cover art for the manga.
@override final String thumbnailUrl;
/// Other titles the manga can have.
@override@OtherTitlesConverter() final OtherTitles otherTitles;
/// The source where we got the data from.
@override@TrackingDataSourceConverter() final TrackingDataSource source;
@@ -257,16 +270,16 @@ Map<String, dynamic> toJson() {
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _MangaTrackingData&&(identical(other.id, id) || other.id == id)&&(identical(other.state, state) || other.state == state)&&(identical(other.title, title) || other.title == title)&&(identical(other.chaptersRead, chaptersRead) || other.chaptersRead == chaptersRead)&&(identical(other.volumesOwned, volumesOwned) || other.volumesOwned == volumesOwned)&&(identical(other.chaptersTotal, chaptersTotal) || other.chaptersTotal == chaptersTotal)&&(identical(other.thumbnailUrl, thumbnailUrl) || other.thumbnailUrl == thumbnailUrl)&&(identical(other.source, source) || other.source == source));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _MangaTrackingData&&(identical(other.id, id) || other.id == id)&&(identical(other.state, state) || other.state == state)&&(identical(other.title, title) || other.title == title)&&(identical(other.chaptersRead, chaptersRead) || other.chaptersRead == chaptersRead)&&(identical(other.volumesOwned, volumesOwned) || other.volumesOwned == volumesOwned)&&(identical(other.chaptersTotal, chaptersTotal) || other.chaptersTotal == chaptersTotal)&&(identical(other.thumbnailUrl, thumbnailUrl) || other.thumbnailUrl == thumbnailUrl)&&(identical(other.otherTitles, otherTitles) || other.otherTitles == otherTitles)&&(identical(other.source, source) || other.source == source));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,state,title,chaptersRead,volumesOwned,chaptersTotal,thumbnailUrl,source);
int get hashCode => Object.hash(runtimeType,id,state,title,chaptersRead,volumesOwned,chaptersTotal,thumbnailUrl,otherTitles,source);
@override
String toString() {
return 'MangaTrackingData(id: $id, state: $state, title: $title, chaptersRead: $chaptersRead, volumesOwned: $volumesOwned, chaptersTotal: $chaptersTotal, thumbnailUrl: $thumbnailUrl, source: $source)';
return 'MangaTrackingData(id: $id, state: $state, title: $title, chaptersRead: $chaptersRead, volumesOwned: $volumesOwned, chaptersTotal: $chaptersTotal, thumbnailUrl: $thumbnailUrl, otherTitles: $otherTitles, source: $source)';
}
@@ -277,11 +290,11 @@ abstract mixin class _$MangaTrackingDataCopyWith<$Res> implements $MangaTracking
factory _$MangaTrackingDataCopyWith(_MangaTrackingData value, $Res Function(_MangaTrackingData) _then) = __$MangaTrackingDataCopyWithImpl;
@override @useResult
$Res call({
String id,@MediumTrackingStateConverter() MediumTrackingState state, String title, int chaptersRead, int volumesOwned, int? chaptersTotal, String thumbnailUrl,@TrackingDataSourceConverter() TrackingDataSource source
String id,@MediumTrackingStateConverter() MediumTrackingState state, String title, int chaptersRead, int volumesOwned, int? chaptersTotal, String thumbnailUrl,@OtherTitlesConverter() OtherTitles otherTitles,@TrackingDataSourceConverter() TrackingDataSource source
});
@override $OtherTitlesCopyWith<$Res> get otherTitles;
}
/// @nodoc
@@ -294,7 +307,7 @@ class __$MangaTrackingDataCopyWithImpl<$Res>
/// Create a copy of MangaTrackingData
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? state = null,Object? title = null,Object? chaptersRead = null,Object? volumesOwned = null,Object? chaptersTotal = freezed,Object? thumbnailUrl = null,Object? source = null,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? state = null,Object? title = null,Object? chaptersRead = null,Object? volumesOwned = null,Object? chaptersTotal = freezed,Object? thumbnailUrl = null,Object? otherTitles = null,Object? source = null,}) {
return _then(_MangaTrackingData(
null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,null == state ? _self.state : state // ignore: cast_nullable_to_non_nullable
@@ -303,12 +316,22 @@ as String,null == chaptersRead ? _self.chaptersRead : chaptersRead // ignore: ca
as int,null == volumesOwned ? _self.volumesOwned : volumesOwned // ignore: cast_nullable_to_non_nullable
as int,freezed == chaptersTotal ? _self.chaptersTotal : chaptersTotal // ignore: cast_nullable_to_non_nullable
as int?,null == thumbnailUrl ? _self.thumbnailUrl : thumbnailUrl // ignore: cast_nullable_to_non_nullable
as String,null == source ? _self.source : source // ignore: cast_nullable_to_non_nullable
as String,null == otherTitles ? _self.otherTitles : otherTitles // ignore: cast_nullable_to_non_nullable
as OtherTitles,null == source ? _self.source : source // ignore: cast_nullable_to_non_nullable
as TrackingDataSource,
));
}
/// Create a copy of MangaTrackingData
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$OtherTitlesCopyWith<$Res> get otherTitles {
return $OtherTitlesCopyWith<$Res>(_self.otherTitles, (value) {
return _then(_self.copyWith(otherTitles: value));
});
}
}
// dart format on

View File

@@ -17,6 +17,7 @@ _MangaTrackingData _$MangaTrackingDataFromJson(Map<String, dynamic> json) =>
(json['volumesOwned'] as num).toInt(),
(json['chaptersTotal'] as num?)?.toInt(),
json['thumbnailUrl'] as String,
const OtherTitlesConverter().fromJson(json['otherTitles'] as String),
const TrackingDataSourceConverter().fromJson(json['source'] as String),
);
@@ -29,5 +30,6 @@ Map<String, dynamic> _$MangaTrackingDataToJson(_MangaTrackingData instance) =>
'volumesOwned': instance.volumesOwned,
'chaptersTotal': instance.chaptersTotal,
'thumbnailUrl': instance.thumbnailUrl,
'otherTitles': const OtherTitlesConverter().toJson(instance.otherTitles),
'source': const TrackingDataSourceConverter().toJson(instance.source),
};

View File

@@ -0,0 +1,30 @@
import 'dart:convert';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'other_titles.freezed.dart';
part 'other_titles.g.dart';
class OtherTitlesConverter implements JsonConverter<OtherTitles, String> {
const OtherTitlesConverter();
@override
OtherTitles fromJson(String json) =>
OtherTitles.fromJson(jsonDecode(json) as Map<String, dynamic>);
@override
String toJson(OtherTitles object) => jsonEncode(object.toJson());
}
@freezed
abstract class OtherTitles with _$OtherTitles {
factory OtherTitles({
String? english,
String? japanese,
@Default([]) List<String> others,
}) = _OtherTitles;
/// JSON
factory OtherTitles.fromJson(Map<String, dynamic> json) =>
_$OtherTitlesFromJson(json);
}

View File

@@ -0,0 +1,289 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'other_titles.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$OtherTitles {
String? get english; String? get japanese; List<String> get others;
/// Create a copy of OtherTitles
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$OtherTitlesCopyWith<OtherTitles> get copyWith => _$OtherTitlesCopyWithImpl<OtherTitles>(this as OtherTitles, _$identity);
/// Serializes this OtherTitles to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is OtherTitles&&(identical(other.english, english) || other.english == english)&&(identical(other.japanese, japanese) || other.japanese == japanese)&&const DeepCollectionEquality().equals(other.others, others));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,english,japanese,const DeepCollectionEquality().hash(others));
@override
String toString() {
return 'OtherTitles(english: $english, japanese: $japanese, others: $others)';
}
}
/// @nodoc
abstract mixin class $OtherTitlesCopyWith<$Res> {
factory $OtherTitlesCopyWith(OtherTitles value, $Res Function(OtherTitles) _then) = _$OtherTitlesCopyWithImpl;
@useResult
$Res call({
String? english, String? japanese, List<String> others
});
}
/// @nodoc
class _$OtherTitlesCopyWithImpl<$Res>
implements $OtherTitlesCopyWith<$Res> {
_$OtherTitlesCopyWithImpl(this._self, this._then);
final OtherTitles _self;
final $Res Function(OtherTitles) _then;
/// Create a copy of OtherTitles
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? english = freezed,Object? japanese = freezed,Object? others = null,}) {
return _then(_self.copyWith(
english: freezed == english ? _self.english : english // ignore: cast_nullable_to_non_nullable
as String?,japanese: freezed == japanese ? _self.japanese : japanese // ignore: cast_nullable_to_non_nullable
as String?,others: null == others ? _self.others : others // ignore: cast_nullable_to_non_nullable
as List<String>,
));
}
}
/// Adds pattern-matching-related methods to [OtherTitles].
extension OtherTitlesPatterns on OtherTitles {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _OtherTitles value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _OtherTitles() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _OtherTitles value) $default,){
final _that = this;
switch (_that) {
case _OtherTitles():
return $default(_that);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _OtherTitles value)? $default,){
final _that = this;
switch (_that) {
case _OtherTitles() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String? english, String? japanese, List<String> others)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _OtherTitles() when $default != null:
return $default(_that.english,_that.japanese,_that.others);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String? english, String? japanese, List<String> others) $default,) {final _that = this;
switch (_that) {
case _OtherTitles():
return $default(_that.english,_that.japanese,_that.others);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String? english, String? japanese, List<String> others)? $default,) {final _that = this;
switch (_that) {
case _OtherTitles() when $default != null:
return $default(_that.english,_that.japanese,_that.others);case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable()
class _OtherTitles implements OtherTitles {
_OtherTitles({this.english, this.japanese, final List<String> others = const []}): _others = others;
factory _OtherTitles.fromJson(Map<String, dynamic> json) => _$OtherTitlesFromJson(json);
@override final String? english;
@override final String? japanese;
final List<String> _others;
@override@JsonKey() List<String> get others {
if (_others is EqualUnmodifiableListView) return _others;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_others);
}
/// Create a copy of OtherTitles
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$OtherTitlesCopyWith<_OtherTitles> get copyWith => __$OtherTitlesCopyWithImpl<_OtherTitles>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$OtherTitlesToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _OtherTitles&&(identical(other.english, english) || other.english == english)&&(identical(other.japanese, japanese) || other.japanese == japanese)&&const DeepCollectionEquality().equals(other._others, _others));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,english,japanese,const DeepCollectionEquality().hash(_others));
@override
String toString() {
return 'OtherTitles(english: $english, japanese: $japanese, others: $others)';
}
}
/// @nodoc
abstract mixin class _$OtherTitlesCopyWith<$Res> implements $OtherTitlesCopyWith<$Res> {
factory _$OtherTitlesCopyWith(_OtherTitles value, $Res Function(_OtherTitles) _then) = __$OtherTitlesCopyWithImpl;
@override @useResult
$Res call({
String? english, String? japanese, List<String> others
});
}
/// @nodoc
class __$OtherTitlesCopyWithImpl<$Res>
implements _$OtherTitlesCopyWith<$Res> {
__$OtherTitlesCopyWithImpl(this._self, this._then);
final _OtherTitles _self;
final $Res Function(_OtherTitles) _then;
/// Create a copy of OtherTitles
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? english = freezed,Object? japanese = freezed,Object? others = null,}) {
return _then(_OtherTitles(
english: freezed == english ? _self.english : english // ignore: cast_nullable_to_non_nullable
as String?,japanese: freezed == japanese ? _self.japanese : japanese // ignore: cast_nullable_to_non_nullable
as String?,others: null == others ? _self._others : others // ignore: cast_nullable_to_non_nullable
as List<String>,
));
}
}
// dart format on

View File

@@ -0,0 +1,22 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'other_titles.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_OtherTitles _$OtherTitlesFromJson(Map<String, dynamic> json) => _OtherTitles(
english: json['english'] as String?,
japanese: json['japanese'] as String?,
others:
(json['others'] as List<dynamic>?)?.map((e) => e as String).toList() ??
const [],
);
Map<String, dynamic> _$OtherTitlesToJson(_OtherTitles instance) =>
<String, dynamic>{
'english': instance.english,
'japanese': instance.japanese,
'others': instance.others,
};

View File

@@ -1,6 +1,8 @@
class SearchResult {
const SearchResult(
this.title,
this.titleEnglish,
this.titleJapanese,
this.id,
this.total,
this.thumbnailUrl,
@@ -12,6 +14,12 @@ class SearchResult {
/// The title of the anime.
final String title;
/// The English title.
final String? titleEnglish;
/// The original Japanese title.
final String? titleJapanese;
/// The id of the anime.
final String id;

View File

@@ -4,7 +4,7 @@ import 'package:graphql/client.dart';
class AniListClient {
/// The base GraphQL client for AniList
final _client = GraphQLClient(
link: HttpLink("https://graphql.anilist.co"),
link: HttpLink('https://graphql.anilist.co'),
cache: GraphQLCache(),
);
@@ -16,7 +16,7 @@ class AniListClient {
id
status
coverImage {
medium
large
}
episodes
description
@@ -42,11 +42,11 @@ class AniListClient {
);
if (result.hasException) {
// TODO: Handle this more elegantly
print(result.exception.toString());
print(result.exception);
return [];
}
return (result.data!["Page"]["media"] as List<Object?>)
return (result.data!['Page']['media'] as List<Object?>)
.cast<Map<String, dynamic>>()
.map(AnimeSearchResult.fromJson)
.toList();
@@ -60,7 +60,7 @@ class AniListClient {
id
status
coverImage {
medium
large
}
chapters
description
@@ -83,11 +83,11 @@ class AniListClient {
);
if (result.hasException) {
// TODO: Handle this more elegantly
print(result.exception.toString());
print(result.exception);
return [];
}
return (result.data!["Page"]["media"] as List<Object?>)
return (result.data!['Page']['media'] as List<Object?>)
.cast<Map<String, dynamic>>()
.map(MangaSearchResult.fromJson)
.toList();
@@ -119,7 +119,7 @@ class AniListClient {
),
);
if (result.hasException) {
print(result.exception.toString());
print(result.exception);
}
return Anime.fromJson(result.data!['Media'] as Map<String, dynamic>);

View File

@@ -7,6 +7,8 @@ part 'model.freezed.dart';
abstract class AnimeSearchResult with _$AnimeSearchResult {
const factory AnimeSearchResult({
required String title,
required String? titleEnglish,
required String? titleJapanese,
required String id,
required int? episodes,
required String imageUrl,
@@ -24,10 +26,14 @@ abstract class AnimeSearchResult with _$AnimeSearchResult {
);
return AnimeSearchResult(
title: (json['title']! as Map<String, dynamic>)['romaji']! as String,
titleEnglish:
(json['title']! as Map<String, dynamic>)['english'] as String?,
titleJapanese:
(json['title']! as Map<String, dynamic>)['native'] as String?,
id: (json['id']! as int).toString(),
episodes: json['episodes'] as int?,
imageUrl:
(json['coverImage']! as Map<String, dynamic>)['medium']! as String,
(json['coverImage']! as Map<String, dynamic>)['large']! as String,
description: json['description'] as String?,
isAiring: json['status'] == 'RELEASING',
broadcastDay: airingDayOfTheWeek,
@@ -39,6 +45,8 @@ abstract class AnimeSearchResult with _$AnimeSearchResult {
abstract class MangaSearchResult with _$MangaSearchResult {
const factory MangaSearchResult({
required String title,
required String? titleEnglish,
required String? titleJapanese,
required String id,
required int? chapters,
required String imageUrl,
@@ -48,10 +56,14 @@ abstract class MangaSearchResult with _$MangaSearchResult {
factory MangaSearchResult.fromJson(Map<String, Object?> json) {
return MangaSearchResult(
title: (json['title']! as Map<String, dynamic>)['romaji']! as String,
titleEnglish:
(json['title']! as Map<String, dynamic>)['english'] as String?,
titleJapanese:
(json['title']! as Map<String, dynamic>)['native'] as String?,
id: (json['id']! as int).toString(),
chapters: json['chapters'] as int?,
imageUrl:
(json['coverImage']! as Map<String, dynamic>)['medium']! as String,
(json['coverImage']! as Map<String, dynamic>)['large']! as String,
description: json['description'] as String?,
);
}
@@ -63,6 +75,7 @@ abstract class Anime with _$Anime {
required String title,
required bool isAiring,
required String? broadcastDay,
required int? episodes,
}) = _Anime;
factory Anime.fromJson(Map<String, Object?> json) {
@@ -76,6 +89,7 @@ abstract class Anime with _$Anime {
return Anime(
title: (json['title']! as Map<String, dynamic>)['romaji']! as String,
isAiring: json['status'] == 'RELEASING',
episodes: json['episodes'] as int?,
broadcastDay: airingDayOfTheWeek,
);
}

View File

@@ -14,7 +14,7 @@ T _$identity<T>(T value) => value;
/// @nodoc
mixin _$AnimeSearchResult {
String get title; String get id; int? get episodes; String get imageUrl; String? get description; bool get isAiring; String? get broadcastDay;
String get title; String? get titleEnglish; String? get titleJapanese; String get id; int? get episodes; String get imageUrl; String? get description; bool get isAiring; String? get broadcastDay;
/// Create a copy of AnimeSearchResult
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -25,16 +25,16 @@ $AnimeSearchResultCopyWith<AnimeSearchResult> get copyWith => _$AnimeSearchResul
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is AnimeSearchResult&&(identical(other.title, title) || other.title == title)&&(identical(other.id, id) || other.id == id)&&(identical(other.episodes, episodes) || other.episodes == episodes)&&(identical(other.imageUrl, imageUrl) || other.imageUrl == imageUrl)&&(identical(other.description, description) || other.description == description)&&(identical(other.isAiring, isAiring) || other.isAiring == isAiring)&&(identical(other.broadcastDay, broadcastDay) || other.broadcastDay == broadcastDay));
return identical(this, other) || (other.runtimeType == runtimeType&&other is AnimeSearchResult&&(identical(other.title, title) || other.title == title)&&(identical(other.titleEnglish, titleEnglish) || other.titleEnglish == titleEnglish)&&(identical(other.titleJapanese, titleJapanese) || other.titleJapanese == titleJapanese)&&(identical(other.id, id) || other.id == id)&&(identical(other.episodes, episodes) || other.episodes == episodes)&&(identical(other.imageUrl, imageUrl) || other.imageUrl == imageUrl)&&(identical(other.description, description) || other.description == description)&&(identical(other.isAiring, isAiring) || other.isAiring == isAiring)&&(identical(other.broadcastDay, broadcastDay) || other.broadcastDay == broadcastDay));
}
@override
int get hashCode => Object.hash(runtimeType,title,id,episodes,imageUrl,description,isAiring,broadcastDay);
int get hashCode => Object.hash(runtimeType,title,titleEnglish,titleJapanese,id,episodes,imageUrl,description,isAiring,broadcastDay);
@override
String toString() {
return 'AnimeSearchResult(title: $title, id: $id, episodes: $episodes, imageUrl: $imageUrl, description: $description, isAiring: $isAiring, broadcastDay: $broadcastDay)';
return 'AnimeSearchResult(title: $title, titleEnglish: $titleEnglish, titleJapanese: $titleJapanese, id: $id, episodes: $episodes, imageUrl: $imageUrl, description: $description, isAiring: $isAiring, broadcastDay: $broadcastDay)';
}
@@ -45,7 +45,7 @@ abstract mixin class $AnimeSearchResultCopyWith<$Res> {
factory $AnimeSearchResultCopyWith(AnimeSearchResult value, $Res Function(AnimeSearchResult) _then) = _$AnimeSearchResultCopyWithImpl;
@useResult
$Res call({
String title, String id, int? episodes, String imageUrl, String? description, bool isAiring, String? broadcastDay
String title, String? titleEnglish, String? titleJapanese, String id, int? episodes, String imageUrl, String? description, bool isAiring, String? broadcastDay
});
@@ -62,10 +62,12 @@ class _$AnimeSearchResultCopyWithImpl<$Res>
/// Create a copy of AnimeSearchResult
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? title = null,Object? id = null,Object? episodes = freezed,Object? imageUrl = null,Object? description = freezed,Object? isAiring = null,Object? broadcastDay = freezed,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? title = null,Object? titleEnglish = freezed,Object? titleJapanese = freezed,Object? id = null,Object? episodes = freezed,Object? imageUrl = null,Object? description = freezed,Object? isAiring = null,Object? broadcastDay = freezed,}) {
return _then(_self.copyWith(
title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
as String,id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,titleEnglish: freezed == titleEnglish ? _self.titleEnglish : titleEnglish // ignore: cast_nullable_to_non_nullable
as String?,titleJapanese: freezed == titleJapanese ? _self.titleJapanese : titleJapanese // ignore: cast_nullable_to_non_nullable
as String?,id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,episodes: freezed == episodes ? _self.episodes : episodes // ignore: cast_nullable_to_non_nullable
as int?,imageUrl: null == imageUrl ? _self.imageUrl : imageUrl // ignore: cast_nullable_to_non_nullable
as String,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
@@ -156,10 +158,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String title, String id, int? episodes, String imageUrl, String? description, bool isAiring, String? broadcastDay)? $default,{required TResult orElse(),}) {final _that = this;
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String title, String? titleEnglish, String? titleJapanese, String id, int? episodes, String imageUrl, String? description, bool isAiring, String? broadcastDay)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _AnimeSearchResult() when $default != null:
return $default(_that.title,_that.id,_that.episodes,_that.imageUrl,_that.description,_that.isAiring,_that.broadcastDay);case _:
return $default(_that.title,_that.titleEnglish,_that.titleJapanese,_that.id,_that.episodes,_that.imageUrl,_that.description,_that.isAiring,_that.broadcastDay);case _:
return orElse();
}
@@ -177,10 +179,10 @@ return $default(_that.title,_that.id,_that.episodes,_that.imageUrl,_that.descrip
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String title, String id, int? episodes, String imageUrl, String? description, bool isAiring, String? broadcastDay) $default,) {final _that = this;
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String title, String? titleEnglish, String? titleJapanese, String id, int? episodes, String imageUrl, String? description, bool isAiring, String? broadcastDay) $default,) {final _that = this;
switch (_that) {
case _AnimeSearchResult():
return $default(_that.title,_that.id,_that.episodes,_that.imageUrl,_that.description,_that.isAiring,_that.broadcastDay);case _:
return $default(_that.title,_that.titleEnglish,_that.titleJapanese,_that.id,_that.episodes,_that.imageUrl,_that.description,_that.isAiring,_that.broadcastDay);case _:
throw StateError('Unexpected subclass');
}
@@ -197,10 +199,10 @@ return $default(_that.title,_that.id,_that.episodes,_that.imageUrl,_that.descrip
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String title, String id, int? episodes, String imageUrl, String? description, bool isAiring, String? broadcastDay)? $default,) {final _that = this;
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String title, String? titleEnglish, String? titleJapanese, String id, int? episodes, String imageUrl, String? description, bool isAiring, String? broadcastDay)? $default,) {final _that = this;
switch (_that) {
case _AnimeSearchResult() when $default != null:
return $default(_that.title,_that.id,_that.episodes,_that.imageUrl,_that.description,_that.isAiring,_that.broadcastDay);case _:
return $default(_that.title,_that.titleEnglish,_that.titleJapanese,_that.id,_that.episodes,_that.imageUrl,_that.description,_that.isAiring,_that.broadcastDay);case _:
return null;
}
@@ -212,10 +214,12 @@ return $default(_that.title,_that.id,_that.episodes,_that.imageUrl,_that.descrip
class _AnimeSearchResult implements AnimeSearchResult {
const _AnimeSearchResult({required this.title, required this.id, required this.episodes, required this.imageUrl, required this.description, required this.isAiring, required this.broadcastDay});
const _AnimeSearchResult({required this.title, required this.titleEnglish, required this.titleJapanese, required this.id, required this.episodes, required this.imageUrl, required this.description, required this.isAiring, required this.broadcastDay});
@override final String title;
@override final String? titleEnglish;
@override final String? titleJapanese;
@override final String id;
@override final int? episodes;
@override final String imageUrl;
@@ -233,16 +237,16 @@ _$AnimeSearchResultCopyWith<_AnimeSearchResult> get copyWith => __$AnimeSearchRe
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _AnimeSearchResult&&(identical(other.title, title) || other.title == title)&&(identical(other.id, id) || other.id == id)&&(identical(other.episodes, episodes) || other.episodes == episodes)&&(identical(other.imageUrl, imageUrl) || other.imageUrl == imageUrl)&&(identical(other.description, description) || other.description == description)&&(identical(other.isAiring, isAiring) || other.isAiring == isAiring)&&(identical(other.broadcastDay, broadcastDay) || other.broadcastDay == broadcastDay));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _AnimeSearchResult&&(identical(other.title, title) || other.title == title)&&(identical(other.titleEnglish, titleEnglish) || other.titleEnglish == titleEnglish)&&(identical(other.titleJapanese, titleJapanese) || other.titleJapanese == titleJapanese)&&(identical(other.id, id) || other.id == id)&&(identical(other.episodes, episodes) || other.episodes == episodes)&&(identical(other.imageUrl, imageUrl) || other.imageUrl == imageUrl)&&(identical(other.description, description) || other.description == description)&&(identical(other.isAiring, isAiring) || other.isAiring == isAiring)&&(identical(other.broadcastDay, broadcastDay) || other.broadcastDay == broadcastDay));
}
@override
int get hashCode => Object.hash(runtimeType,title,id,episodes,imageUrl,description,isAiring,broadcastDay);
int get hashCode => Object.hash(runtimeType,title,titleEnglish,titleJapanese,id,episodes,imageUrl,description,isAiring,broadcastDay);
@override
String toString() {
return 'AnimeSearchResult(title: $title, id: $id, episodes: $episodes, imageUrl: $imageUrl, description: $description, isAiring: $isAiring, broadcastDay: $broadcastDay)';
return 'AnimeSearchResult(title: $title, titleEnglish: $titleEnglish, titleJapanese: $titleJapanese, id: $id, episodes: $episodes, imageUrl: $imageUrl, description: $description, isAiring: $isAiring, broadcastDay: $broadcastDay)';
}
@@ -253,7 +257,7 @@ abstract mixin class _$AnimeSearchResultCopyWith<$Res> implements $AnimeSearchRe
factory _$AnimeSearchResultCopyWith(_AnimeSearchResult value, $Res Function(_AnimeSearchResult) _then) = __$AnimeSearchResultCopyWithImpl;
@override @useResult
$Res call({
String title, String id, int? episodes, String imageUrl, String? description, bool isAiring, String? broadcastDay
String title, String? titleEnglish, String? titleJapanese, String id, int? episodes, String imageUrl, String? description, bool isAiring, String? broadcastDay
});
@@ -270,10 +274,12 @@ class __$AnimeSearchResultCopyWithImpl<$Res>
/// Create a copy of AnimeSearchResult
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? title = null,Object? id = null,Object? episodes = freezed,Object? imageUrl = null,Object? description = freezed,Object? isAiring = null,Object? broadcastDay = freezed,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? title = null,Object? titleEnglish = freezed,Object? titleJapanese = freezed,Object? id = null,Object? episodes = freezed,Object? imageUrl = null,Object? description = freezed,Object? isAiring = null,Object? broadcastDay = freezed,}) {
return _then(_AnimeSearchResult(
title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
as String,id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,titleEnglish: freezed == titleEnglish ? _self.titleEnglish : titleEnglish // ignore: cast_nullable_to_non_nullable
as String?,titleJapanese: freezed == titleJapanese ? _self.titleJapanese : titleJapanese // ignore: cast_nullable_to_non_nullable
as String?,id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,episodes: freezed == episodes ? _self.episodes : episodes // ignore: cast_nullable_to_non_nullable
as int?,imageUrl: null == imageUrl ? _self.imageUrl : imageUrl // ignore: cast_nullable_to_non_nullable
as String,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
@@ -289,7 +295,7 @@ as String?,
/// @nodoc
mixin _$MangaSearchResult {
String get title; String get id; int? get chapters; String get imageUrl; String? get description;
String get title; String? get titleEnglish; String? get titleJapanese; String get id; int? get chapters; String get imageUrl; String? get description;
/// Create a copy of MangaSearchResult
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -300,16 +306,16 @@ $MangaSearchResultCopyWith<MangaSearchResult> get copyWith => _$MangaSearchResul
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is MangaSearchResult&&(identical(other.title, title) || other.title == title)&&(identical(other.id, id) || other.id == id)&&(identical(other.chapters, chapters) || other.chapters == chapters)&&(identical(other.imageUrl, imageUrl) || other.imageUrl == imageUrl)&&(identical(other.description, description) || other.description == description));
return identical(this, other) || (other.runtimeType == runtimeType&&other is MangaSearchResult&&(identical(other.title, title) || other.title == title)&&(identical(other.titleEnglish, titleEnglish) || other.titleEnglish == titleEnglish)&&(identical(other.titleJapanese, titleJapanese) || other.titleJapanese == titleJapanese)&&(identical(other.id, id) || other.id == id)&&(identical(other.chapters, chapters) || other.chapters == chapters)&&(identical(other.imageUrl, imageUrl) || other.imageUrl == imageUrl)&&(identical(other.description, description) || other.description == description));
}
@override
int get hashCode => Object.hash(runtimeType,title,id,chapters,imageUrl,description);
int get hashCode => Object.hash(runtimeType,title,titleEnglish,titleJapanese,id,chapters,imageUrl,description);
@override
String toString() {
return 'MangaSearchResult(title: $title, id: $id, chapters: $chapters, imageUrl: $imageUrl, description: $description)';
return 'MangaSearchResult(title: $title, titleEnglish: $titleEnglish, titleJapanese: $titleJapanese, id: $id, chapters: $chapters, imageUrl: $imageUrl, description: $description)';
}
@@ -320,7 +326,7 @@ abstract mixin class $MangaSearchResultCopyWith<$Res> {
factory $MangaSearchResultCopyWith(MangaSearchResult value, $Res Function(MangaSearchResult) _then) = _$MangaSearchResultCopyWithImpl;
@useResult
$Res call({
String title, String id, int? chapters, String imageUrl, String? description
String title, String? titleEnglish, String? titleJapanese, String id, int? chapters, String imageUrl, String? description
});
@@ -337,10 +343,12 @@ class _$MangaSearchResultCopyWithImpl<$Res>
/// Create a copy of MangaSearchResult
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? title = null,Object? id = null,Object? chapters = freezed,Object? imageUrl = null,Object? description = freezed,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? title = null,Object? titleEnglish = freezed,Object? titleJapanese = freezed,Object? id = null,Object? chapters = freezed,Object? imageUrl = null,Object? description = freezed,}) {
return _then(_self.copyWith(
title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
as String,id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,titleEnglish: freezed == titleEnglish ? _self.titleEnglish : titleEnglish // ignore: cast_nullable_to_non_nullable
as String?,titleJapanese: freezed == titleJapanese ? _self.titleJapanese : titleJapanese // ignore: cast_nullable_to_non_nullable
as String?,id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,chapters: freezed == chapters ? _self.chapters : chapters // ignore: cast_nullable_to_non_nullable
as int?,imageUrl: null == imageUrl ? _self.imageUrl : imageUrl // ignore: cast_nullable_to_non_nullable
as String,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
@@ -429,10 +437,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String title, String id, int? chapters, String imageUrl, String? description)? $default,{required TResult orElse(),}) {final _that = this;
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String title, String? titleEnglish, String? titleJapanese, String id, int? chapters, String imageUrl, String? description)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _MangaSearchResult() when $default != null:
return $default(_that.title,_that.id,_that.chapters,_that.imageUrl,_that.description);case _:
return $default(_that.title,_that.titleEnglish,_that.titleJapanese,_that.id,_that.chapters,_that.imageUrl,_that.description);case _:
return orElse();
}
@@ -450,10 +458,10 @@ return $default(_that.title,_that.id,_that.chapters,_that.imageUrl,_that.descrip
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String title, String id, int? chapters, String imageUrl, String? description) $default,) {final _that = this;
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String title, String? titleEnglish, String? titleJapanese, String id, int? chapters, String imageUrl, String? description) $default,) {final _that = this;
switch (_that) {
case _MangaSearchResult():
return $default(_that.title,_that.id,_that.chapters,_that.imageUrl,_that.description);case _:
return $default(_that.title,_that.titleEnglish,_that.titleJapanese,_that.id,_that.chapters,_that.imageUrl,_that.description);case _:
throw StateError('Unexpected subclass');
}
@@ -470,10 +478,10 @@ return $default(_that.title,_that.id,_that.chapters,_that.imageUrl,_that.descrip
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String title, String id, int? chapters, String imageUrl, String? description)? $default,) {final _that = this;
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String title, String? titleEnglish, String? titleJapanese, String id, int? chapters, String imageUrl, String? description)? $default,) {final _that = this;
switch (_that) {
case _MangaSearchResult() when $default != null:
return $default(_that.title,_that.id,_that.chapters,_that.imageUrl,_that.description);case _:
return $default(_that.title,_that.titleEnglish,_that.titleJapanese,_that.id,_that.chapters,_that.imageUrl,_that.description);case _:
return null;
}
@@ -485,10 +493,12 @@ return $default(_that.title,_that.id,_that.chapters,_that.imageUrl,_that.descrip
class _MangaSearchResult implements MangaSearchResult {
const _MangaSearchResult({required this.title, required this.id, required this.chapters, required this.imageUrl, required this.description});
const _MangaSearchResult({required this.title, required this.titleEnglish, required this.titleJapanese, required this.id, required this.chapters, required this.imageUrl, required this.description});
@override final String title;
@override final String? titleEnglish;
@override final String? titleJapanese;
@override final String id;
@override final int? chapters;
@override final String imageUrl;
@@ -504,16 +514,16 @@ _$MangaSearchResultCopyWith<_MangaSearchResult> get copyWith => __$MangaSearchRe
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _MangaSearchResult&&(identical(other.title, title) || other.title == title)&&(identical(other.id, id) || other.id == id)&&(identical(other.chapters, chapters) || other.chapters == chapters)&&(identical(other.imageUrl, imageUrl) || other.imageUrl == imageUrl)&&(identical(other.description, description) || other.description == description));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _MangaSearchResult&&(identical(other.title, title) || other.title == title)&&(identical(other.titleEnglish, titleEnglish) || other.titleEnglish == titleEnglish)&&(identical(other.titleJapanese, titleJapanese) || other.titleJapanese == titleJapanese)&&(identical(other.id, id) || other.id == id)&&(identical(other.chapters, chapters) || other.chapters == chapters)&&(identical(other.imageUrl, imageUrl) || other.imageUrl == imageUrl)&&(identical(other.description, description) || other.description == description));
}
@override
int get hashCode => Object.hash(runtimeType,title,id,chapters,imageUrl,description);
int get hashCode => Object.hash(runtimeType,title,titleEnglish,titleJapanese,id,chapters,imageUrl,description);
@override
String toString() {
return 'MangaSearchResult(title: $title, id: $id, chapters: $chapters, imageUrl: $imageUrl, description: $description)';
return 'MangaSearchResult(title: $title, titleEnglish: $titleEnglish, titleJapanese: $titleJapanese, id: $id, chapters: $chapters, imageUrl: $imageUrl, description: $description)';
}
@@ -524,7 +534,7 @@ abstract mixin class _$MangaSearchResultCopyWith<$Res> implements $MangaSearchRe
factory _$MangaSearchResultCopyWith(_MangaSearchResult value, $Res Function(_MangaSearchResult) _then) = __$MangaSearchResultCopyWithImpl;
@override @useResult
$Res call({
String title, String id, int? chapters, String imageUrl, String? description
String title, String? titleEnglish, String? titleJapanese, String id, int? chapters, String imageUrl, String? description
});
@@ -541,10 +551,12 @@ class __$MangaSearchResultCopyWithImpl<$Res>
/// Create a copy of MangaSearchResult
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? title = null,Object? id = null,Object? chapters = freezed,Object? imageUrl = null,Object? description = freezed,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? title = null,Object? titleEnglish = freezed,Object? titleJapanese = freezed,Object? id = null,Object? chapters = freezed,Object? imageUrl = null,Object? description = freezed,}) {
return _then(_MangaSearchResult(
title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
as String,id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,titleEnglish: freezed == titleEnglish ? _self.titleEnglish : titleEnglish // ignore: cast_nullable_to_non_nullable
as String?,titleJapanese: freezed == titleJapanese ? _self.titleJapanese : titleJapanese // ignore: cast_nullable_to_non_nullable
as String?,id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,chapters: freezed == chapters ? _self.chapters : chapters // ignore: cast_nullable_to_non_nullable
as int?,imageUrl: null == imageUrl ? _self.imageUrl : imageUrl // ignore: cast_nullable_to_non_nullable
as String,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
@@ -558,7 +570,7 @@ as String?,
/// @nodoc
mixin _$Anime {
String get title; bool get isAiring; String? get broadcastDay;
String get title; bool get isAiring; String? get broadcastDay; int? get episodes;
/// Create a copy of Anime
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -569,16 +581,16 @@ $AnimeCopyWith<Anime> get copyWith => _$AnimeCopyWithImpl<Anime>(this as Anime,
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is Anime&&(identical(other.title, title) || other.title == title)&&(identical(other.isAiring, isAiring) || other.isAiring == isAiring)&&(identical(other.broadcastDay, broadcastDay) || other.broadcastDay == broadcastDay));
return identical(this, other) || (other.runtimeType == runtimeType&&other is Anime&&(identical(other.title, title) || other.title == title)&&(identical(other.isAiring, isAiring) || other.isAiring == isAiring)&&(identical(other.broadcastDay, broadcastDay) || other.broadcastDay == broadcastDay)&&(identical(other.episodes, episodes) || other.episodes == episodes));
}
@override
int get hashCode => Object.hash(runtimeType,title,isAiring,broadcastDay);
int get hashCode => Object.hash(runtimeType,title,isAiring,broadcastDay,episodes);
@override
String toString() {
return 'Anime(title: $title, isAiring: $isAiring, broadcastDay: $broadcastDay)';
return 'Anime(title: $title, isAiring: $isAiring, broadcastDay: $broadcastDay, episodes: $episodes)';
}
@@ -589,7 +601,7 @@ abstract mixin class $AnimeCopyWith<$Res> {
factory $AnimeCopyWith(Anime value, $Res Function(Anime) _then) = _$AnimeCopyWithImpl;
@useResult
$Res call({
String title, bool isAiring, String? broadcastDay
String title, bool isAiring, String? broadcastDay, int? episodes
});
@@ -606,12 +618,13 @@ class _$AnimeCopyWithImpl<$Res>
/// Create a copy of Anime
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? title = null,Object? isAiring = null,Object? broadcastDay = freezed,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? title = null,Object? isAiring = null,Object? broadcastDay = freezed,Object? episodes = freezed,}) {
return _then(_self.copyWith(
title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
as String,isAiring: null == isAiring ? _self.isAiring : isAiring // ignore: cast_nullable_to_non_nullable
as bool,broadcastDay: freezed == broadcastDay ? _self.broadcastDay : broadcastDay // ignore: cast_nullable_to_non_nullable
as String?,
as String?,episodes: freezed == episodes ? _self.episodes : episodes // ignore: cast_nullable_to_non_nullable
as int?,
));
}
@@ -696,10 +709,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String title, bool isAiring, String? broadcastDay)? $default,{required TResult orElse(),}) {final _that = this;
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String title, bool isAiring, String? broadcastDay, int? episodes)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _Anime() when $default != null:
return $default(_that.title,_that.isAiring,_that.broadcastDay);case _:
return $default(_that.title,_that.isAiring,_that.broadcastDay,_that.episodes);case _:
return orElse();
}
@@ -717,10 +730,10 @@ return $default(_that.title,_that.isAiring,_that.broadcastDay);case _:
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String title, bool isAiring, String? broadcastDay) $default,) {final _that = this;
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String title, bool isAiring, String? broadcastDay, int? episodes) $default,) {final _that = this;
switch (_that) {
case _Anime():
return $default(_that.title,_that.isAiring,_that.broadcastDay);case _:
return $default(_that.title,_that.isAiring,_that.broadcastDay,_that.episodes);case _:
throw StateError('Unexpected subclass');
}
@@ -737,10 +750,10 @@ return $default(_that.title,_that.isAiring,_that.broadcastDay);case _:
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String title, bool isAiring, String? broadcastDay)? $default,) {final _that = this;
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String title, bool isAiring, String? broadcastDay, int? episodes)? $default,) {final _that = this;
switch (_that) {
case _Anime() when $default != null:
return $default(_that.title,_that.isAiring,_that.broadcastDay);case _:
return $default(_that.title,_that.isAiring,_that.broadcastDay,_that.episodes);case _:
return null;
}
@@ -752,12 +765,13 @@ return $default(_that.title,_that.isAiring,_that.broadcastDay);case _:
class _Anime implements Anime {
const _Anime({required this.title, required this.isAiring, required this.broadcastDay});
const _Anime({required this.title, required this.isAiring, required this.broadcastDay, required this.episodes});
@override final String title;
@override final bool isAiring;
@override final String? broadcastDay;
@override final int? episodes;
/// Create a copy of Anime
/// with the given fields replaced by the non-null parameter values.
@@ -769,16 +783,16 @@ _$AnimeCopyWith<_Anime> get copyWith => __$AnimeCopyWithImpl<_Anime>(this, _$ide
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _Anime&&(identical(other.title, title) || other.title == title)&&(identical(other.isAiring, isAiring) || other.isAiring == isAiring)&&(identical(other.broadcastDay, broadcastDay) || other.broadcastDay == broadcastDay));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _Anime&&(identical(other.title, title) || other.title == title)&&(identical(other.isAiring, isAiring) || other.isAiring == isAiring)&&(identical(other.broadcastDay, broadcastDay) || other.broadcastDay == broadcastDay)&&(identical(other.episodes, episodes) || other.episodes == episodes));
}
@override
int get hashCode => Object.hash(runtimeType,title,isAiring,broadcastDay);
int get hashCode => Object.hash(runtimeType,title,isAiring,broadcastDay,episodes);
@override
String toString() {
return 'Anime(title: $title, isAiring: $isAiring, broadcastDay: $broadcastDay)';
return 'Anime(title: $title, isAiring: $isAiring, broadcastDay: $broadcastDay, episodes: $episodes)';
}
@@ -789,7 +803,7 @@ abstract mixin class _$AnimeCopyWith<$Res> implements $AnimeCopyWith<$Res> {
factory _$AnimeCopyWith(_Anime value, $Res Function(_Anime) _then) = __$AnimeCopyWithImpl;
@override @useResult
$Res call({
String title, bool isAiring, String? broadcastDay
String title, bool isAiring, String? broadcastDay, int? episodes
});
@@ -806,12 +820,13 @@ class __$AnimeCopyWithImpl<$Res>
/// Create a copy of Anime
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? title = null,Object? isAiring = null,Object? broadcastDay = freezed,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? title = null,Object? isAiring = null,Object? broadcastDay = freezed,Object? episodes = freezed,}) {
return _then(_Anime(
title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
as String,isAiring: null == isAiring ? _self.isAiring : isAiring // ignore: cast_nullable_to_non_nullable
as bool,broadcastDay: freezed == broadcastDay ? _self.broadcastDay : broadcastDay // ignore: cast_nullable_to_non_nullable
as String?,
as String?,episodes: freezed == episodes ? _self.episodes : episodes // ignore: cast_nullable_to_non_nullable
as int?,
));
}

View File

@@ -7,6 +7,10 @@ import 'package:anitrack/src/service/migrations/0000_airing.dart';
import 'package:anitrack/src/service/migrations/0000_score.dart';
import 'package:anitrack/src/service/migrations/0001_anime_watcher.dart';
import 'package:anitrack/src/service/migrations/0002_anilist.dart';
import 'package:anitrack/src/service/migrations/0003_other_titles.dart';
import 'package:anitrack/src/service/migrations/0004_fix_constraints.dart';
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
const animeTable = 'Anime';
@@ -61,7 +65,7 @@ Future<void> _createDatabase(Database db, int version) async {
await db.execute(
'''
CREATE TABLE $animeWatcherTable(
name TEXT NOT NULL PRIMARY KEY,
name TEXT NOT NULL PRIMARY KEY
)''',
);
await db.execute(
@@ -70,8 +74,8 @@ Future<void> _createDatabase(Database db, int version) async {
name TEXT NOT NULL,
anime TEXT NOT NULL,
CONSTRAINT pk_watcher_join_table PRIMARY KEY(name, anime),
CONSTRAINT fk_watcher FOREIGN KEY (name) REFERENCES $animeWatcherTable(name),
CONSTRAINT fk_anime FOREIGN KEY (anime) REFERENCES $animeTable(id)
CONSTRAINT fk_watcher FOREIGN KEY (name) REFERENCES $animeWatcherTable(name) ON DELETE CASCADE,
CONSTRAINT fk_anime FOREIGN KEY (anime) REFERENCES $animeTable(id) ON DELETE CASCADE
)''',
);
}
@@ -83,6 +87,15 @@ class DatabaseService {
/// Cached AnimeWatchers.
List<AnimeWatcher>? _watcherCache;
Future<String> _getDatabasePath() async {
if (Platform.isAndroid || Platform.isIOS) {
return 'anitrack.db';
}
final supportDir = await getApplicationSupportDirectory();
return p.join(supportDir.path, 'anitrack.db');
}
Future<void> initialize() async {
// Allow initializing the database on Windows and Linux as well.
if (Platform.isLinux || Platform.isWindows) {
@@ -90,9 +103,11 @@ class DatabaseService {
databaseFactory = databaseFactoryFfi;
}
final databasePath = await _getDatabasePath();
print('Opening database at $databasePath');
_db = await openDatabase(
'anitrack.db',
version: 5,
databasePath,
version: 7,
onConfigure: (db) async {
// In order to do schema changes during database upgrades, we disable foreign
// keys in the onConfigure phase, but re-enable them here.
@@ -117,9 +132,14 @@ class DatabaseService {
if (oldVersion < 5) {
await migrateFromV4ToV5(db);
}
if (oldVersion < 6) {
await migrateFromV5ToV6(db);
}
if (oldVersion < 7) {
await migrateFromV6ToV7(db);
}
},
);
print(_db.path);
}
Future<List<AnimeTrackingData>> loadAnimes() async {
@@ -144,7 +164,6 @@ class DatabaseService {
await _db.insert(
animeTable,
data.toJson(),
conflictAlgorithm: ConflictAlgorithm.ignore,
);
}

View File

@@ -0,0 +1,15 @@
import 'package:anitrack/src/service/database.dart';
import 'package:sqflite/sqflite.dart';
Future<void> migrateFromV5ToV6(Database db) async {
await db.execute(
'''
ALTER TABLE $animeTable ADD COLUMN otherTitles TEXT NOT NULL DEFAULT '{}';
''',
);
await db.execute(
'''
ALTER TABLE $mangaTable ADD COLUMN otherTitles TEXT NOT NULL DEFAULT '{}';
''',
);
}

View File

@@ -0,0 +1,22 @@
import 'package:anitrack/src/service/database.dart';
import 'package:sqflite/sqflite.dart';
Future<void> migrateFromV6ToV7(Database db) async {
await db.execute(
'''
CREATE TABLE ${animeWatcherJoinTable}_2 (
name TEXT NOT NULL,
anime TEXT NOT NULL,
CONSTRAINT pk_watcher_join_table PRIMARY KEY(name, anime),
CONSTRAINT fk_watcher FOREIGN KEY (name) REFERENCES $animeWatcherTable(name) ON DELETE CASCADE,
CONSTRAINT fk_anime FOREIGN KEY (anime) REFERENCES $animeTable(id) ON DELETE CASCADE
);''',
);
await db.execute(
'INSERT INTO ${animeWatcherJoinTable}_2 SELECT * from $animeWatcherJoinTable;',
);
await db.execute('DROP TABLE $animeWatcherJoinTable');
await db.execute(
'ALTER TABLE ${animeWatcherJoinTable}_2 RENAME TO $animeWatcherJoinTable',
);
}

View File

@@ -2,6 +2,8 @@ import 'package:anitrack/src/data/anime.dart';
import 'package:anitrack/src/data/manga.dart';
import 'package:anitrack/src/data/type.dart';
import 'package:anitrack/src/service/database.dart';
import 'package:anitrack/src/ui/bloc/navigation_bloc.dart';
import 'package:anitrack/src/ui/constants.dart';
import 'package:bloc/bloc.dart';
import 'package:collection/collection.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
@@ -28,6 +30,7 @@ class AnimeListBloc extends Bloc<AnimeListEvent, AnimeListState> {
on<AnimeRemovedEvent>(_onAnimeRemoved);
on<MangaRemovedEvent>(_onMangaRemoved);
on<AddButtonVisibilitySetEvent>(_onButtonVisibilityToggled);
on<AnimeListRequestedEvent>(_onAnimeListSummoned);
}
/// Internal anime state
@@ -122,8 +125,9 @@ class AnimeListBloc extends Bloc<AnimeListEvent, AnimeListState> {
final anime = state.animes[index];
if (anime.episodesTotal != null &&
anime.episodesWatched + 1 > anime.episodesTotal!)
anime.episodesWatched + 1 > anime.episodesTotal!) {
return;
}
final newAnime = await GetIt.I
.get<DatabaseService>()
@@ -223,8 +227,9 @@ class AnimeListBloc extends Bloc<AnimeListEvent, AnimeListState> {
final manga = state.mangas[index];
if (manga.chaptersTotal != null &&
manga.chaptersRead + 1 > manga.chaptersTotal!)
manga.chaptersRead + 1 > manga.chaptersTotal!) {
return;
}
final newManga = await GetIt.I
.get<DatabaseService>()
@@ -356,4 +361,20 @@ class AnimeListBloc extends Bloc<AnimeListEvent, AnimeListState> {
),
);
}
Future<void> _onAnimeListSummoned(
AnimeListRequestedEvent event,
Emitter<AnimeListState> emit,
) async {
emit(
state.copyWith(
trackingType: TrackingMediumType.anime,
animeFilterState: MediumTrackingState.ongoing,
mangaFilterState: MediumTrackingState.ongoing,
),
);
GetIt.I.get<NavigationBloc>().add(
GoNavigationEvent(animeListRoute),
);
}
}

View File

@@ -114,3 +114,6 @@ class AddButtonVisibilitySetEvent extends AnimeListEvent {
/// The visibility of the button
final bool state;
}
/// Triggered by the UI when the anime list is supposed to be summoned.
class AnimeListRequestedEvent extends AnimeListEvent {}

View File

@@ -1,17 +1,16 @@
import 'package:anitrack/src/data/anime.dart';
import 'package:anitrack/src/data/manga.dart';
import 'package:anitrack/src/data/other_titles.dart';
import 'package:anitrack/src/data/search_result.dart';
import 'package:anitrack/src/data/source.dart';
import 'package:anitrack/src/data/type.dart';
import 'package:anitrack/src/service/anilist/anilist_client.dart';
import 'package:anitrack/src/service/anilist/model.dart';
import 'package:anitrack/src/ui/bloc/anime_list_bloc.dart' as list;
import 'package:anitrack/src/ui/bloc/navigation_bloc.dart';
import 'package:anitrack/src/ui/constants.dart';
import 'package:bloc/bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:get_it/get_it.dart';
import 'package:jikan_api/jikan_api.dart';
part 'anime_search_state.dart';
part 'anime_search_event.dart';
@@ -42,9 +41,7 @@ class AnimeSearchBloc extends Bloc<AnimeSearchEvent, AnimeSearchState> {
);
GetIt.I.get<NavigationBloc>().add(
PushedNamedEvent(
const NavigationDestination(animeSearchRoute),
),
PushNavigationEvent(animeSearchRoute),
);
}
@@ -82,6 +79,8 @@ class AnimeSearchBloc extends Bloc<AnimeSearchEvent, AnimeSearchState> {
.map(
(anime) => SearchResult(
anime.title,
anime.titleEnglish,
anime.titleJapanese,
anime.id,
anime.episodes,
anime.imageUrl,
@@ -104,6 +103,8 @@ class AnimeSearchBloc extends Bloc<AnimeSearchEvent, AnimeSearchState> {
.map(
(manga) => SearchResult(
manga.title,
null,
null,
manga.id,
manga.chapters,
manga.imageUrl,
@@ -135,6 +136,10 @@ class AnimeSearchBloc extends Bloc<AnimeSearchEvent, AnimeSearchState> {
event.result.thumbnailUrl,
event.result.isAiring,
event.result.broadcastDay,
OtherTitles(
english: event.result.titleEnglish,
japanese: event.result.titleJapanese,
),
TrackingDataSource.anilist,
),
)
@@ -147,13 +152,17 @@ class AnimeSearchBloc extends Bloc<AnimeSearchEvent, AnimeSearchState> {
0,
event.result.total,
event.result.thumbnailUrl,
OtherTitles(
english: event.result.titleEnglish,
japanese: event.result.titleJapanese,
),
TrackingDataSource.anilist,
),
),
);
GetIt.I.get<NavigationBloc>().add(
PoppedRouteEvent(),
GoNavigationEvent(animeListRoute),
);
}
}

View File

@@ -44,29 +44,36 @@ class CalendarBloc extends Bloc<CalendarEvent, CalendarState> {
String? broadcastDay;
bool airing;
int? episodes;
try {
switch (anime.source) {
case TrackingDataSource.mal:
final apiData = await Jikan().getAnime(int.parse(anime.id));
airing = apiData.airing;
broadcastDay = apiData.broadcast?.split(' ').first;
episodes = apiData.episodes;
case TrackingDataSource.anilist:
final apiData = await GetIt.I.get<AniListClient>().getAnimeById(
anime.id,
);
airing = apiData.isAiring;
broadcastDay = apiData.broadcastDay;
episodes = apiData.episodes;
}
} catch (ex) {
print('API request for anime ${anime.id} failed: $ex');
airing = false;
continue;
}
print('Anime "${anime.title}": airing=$airing');
print('Anime "${anime.title}": airing=$airing; episodes=$episodes');
if (!airing) {
al.add(
AnimeUpdatedEvent(
anime.copyWith(airing: false, broadcastDay: null),
anime.copyWith(
airing: false,
broadcastDay: null,
episodesTotal: episodes,
),
commit: true,
),
);
@@ -74,13 +81,17 @@ class CalendarBloc extends Bloc<CalendarEvent, CalendarState> {
print('Updating Anime "${anime.title}": broadcastDay=$broadcastDay');
al.add(
AnimeUpdatedEvent(
anime.copyWith(airing: true, broadcastDay: broadcastDay),
anime.copyWith(
airing: true,
broadcastDay: broadcastDay,
episodesTotal: episodes,
),
commit: true,
),
);
}
// Prevent hammering Jikan
// Prevent hammering Jikan/AniList
await Future<void>.delayed(const Duration(milliseconds: 500));
}

View File

@@ -44,9 +44,7 @@ class DetailsBloc extends Bloc<DetailsEvent, DetailsState> {
);
GetIt.I.get<NavigationBloc>().add(
PushedNamedEvent(
const NavigationDestination(detailsRoute),
),
PushNavigationEvent(detailsRoute),
);
}
@@ -57,14 +55,13 @@ class DetailsBloc extends Bloc<DetailsEvent, DetailsState> {
emit(
state.copyWith(
trackingType: TrackingMediumType.manga,
heroImagePrefix: event.heroImagePrefix,
data: event.manga,
),
);
GetIt.I.get<NavigationBloc>().add(
PushedNamedEvent(
const NavigationDestination(detailsRoute),
),
PushNavigationEvent(detailsRoute),
);
}
@@ -118,14 +115,12 @@ class DetailsBloc extends Bloc<DetailsEvent, DetailsState> {
switch (event.trackingType) {
case TrackingMediumType.anime:
bloc.add(AnimeRemovedEvent(event.id));
break;
case TrackingMediumType.manga:
bloc.add(MangaRemovedEvent(event.id));
break;
}
// Navigate back
GetIt.I.get<NavigationBloc>().add(PoppedRouteEvent());
GetIt.I.get<NavigationBloc>().add(PopNavigationEvent());
}
Future<void> _onAnimeWatcherAdded(

View File

@@ -15,10 +15,15 @@ class AnimeDetailsRequestedEvent extends DetailsEvent {
}
class MangaDetailsRequestedEvent extends DetailsEvent {
MangaDetailsRequestedEvent(this.manga);
MangaDetailsRequestedEvent(
this.manga, {
this.heroImagePrefix,
});
/// The manga to show details about
final MangaTrackingData manga;
final String? heroImagePrefix;
}
class DetailsUpdatedEvent extends DetailsEvent {

View File

@@ -1,57 +1,39 @@
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:flutter/material.dart';
part 'navigation_event.dart';
part 'navigation_state.dart';
class NavigationBloc extends Bloc<NavigationEvent, NavigationState> {
NavigationBloc(this.navigationKey) : super(NavigationState()) {
on<PushedNamedEvent>(_onPushedNamed);
on<PushedNamedAndRemoveUntilEvent>(_onPushedNamedAndRemoveUntil);
on<PushedNamedReplaceEvent>(_onPushedNamedReplaceEvent);
on<PoppedRouteEvent>(_onPoppedRoute);
NavigationBloc() : super(NoopNavigationState()) {
on<GoNavigationEvent>(_onGoEvent);
on<PushNavigationEvent>(_onPushEvent);
on<PopNavigationEvent>(_onPopEvent);
}
final GlobalKey<NavigatorState> navigationKey;
Future<void> _onPushedNamed(
PushedNamedEvent event,
Future<void> _onGoEvent(
GoNavigationEvent event,
Emitter<NavigationState> emit,
) async {
await navigationKey.currentState!.pushNamed(
event.destination.path,
arguments: event.destination.arguments,
emit(
GoNavigationState(event.destination),
);
}
Future<void> _onPushedNamedAndRemoveUntil(
PushedNamedAndRemoveUntilEvent event,
Future<void> _onPushEvent(
PushNavigationEvent event,
Emitter<NavigationState> emit,
) async {
await navigationKey.currentState!.pushNamedAndRemoveUntil(
event.destination.path,
event.predicate,
arguments: event.destination.arguments,
emit(
PushNavigationState(event.destination),
);
}
Future<void> _onPushedNamedReplaceEvent(
PushedNamedReplaceEvent event,
Future<void> _onPopEvent(
PopNavigationEvent event,
Emitter<NavigationState> emit,
) async {
await navigationKey.currentState!.pushReplacementNamed(
event.destination.path,
arguments: event.destination.arguments,
);
}
Future<void> _onPoppedRoute(
PoppedRouteEvent event,
Emitter<NavigationState> emit,
) async {
navigationKey.currentState!.pop();
}
bool canPop() {
return navigationKey.currentState!.canPop();
emit(PoppedNavigationState());
}
}

View File

@@ -11,20 +11,14 @@ class NavigationDestination {
abstract class NavigationEvent {}
class PushedNamedEvent extends NavigationEvent {
PushedNamedEvent(this.destination);
final NavigationDestination destination;
class GoNavigationEvent extends NavigationEvent {
GoNavigationEvent(this.destination);
final String destination;
}
class PushedNamedAndRemoveUntilEvent extends NavigationEvent {
PushedNamedAndRemoveUntilEvent(this.destination, this.predicate);
final NavigationDestination destination;
final RoutePredicate predicate;
class PushNavigationEvent extends NavigationEvent {
PushNavigationEvent(this.destination);
final String destination;
}
class PushedNamedReplaceEvent extends NavigationEvent {
PushedNamedReplaceEvent(this.destination);
final NavigationDestination destination;
}
class PoppedRouteEvent extends NavigationEvent {}
class PopNavigationEvent extends NavigationEvent {}

View File

@@ -1,3 +1,19 @@
part of 'navigation_bloc.dart';
class NavigationState {}
abstract class NavigationState {}
class NoopNavigationState extends NavigationState {}
class PoppedNavigationState extends NavigationState {}
class PushNavigationState extends NavigationState {
PushNavigationState(this.destination);
final String destination;
}
class GoNavigationState extends NavigationState {
GoNavigationState(this.destination);
final String destination;
}

View File

@@ -4,6 +4,7 @@ import 'dart:io';
import 'package:anitrack/i18n/strings.g.dart';
import 'package:anitrack/src/data/anime.dart';
import 'package:anitrack/src/data/manga.dart';
import 'package:anitrack/src/data/other_titles.dart';
import 'package:anitrack/src/data/source.dart';
import 'package:anitrack/src/data/type.dart';
import 'package:anitrack/src/service/database.dart';
@@ -132,6 +133,7 @@ class SettingsBloc extends Bloc<SettingsEvent, SettingsState> {
// NOTE: When the calendar gets refreshed, this should also get cleared
true,
null,
OtherTitles(),
TrackingDataSource.mal,
),
);
@@ -209,6 +211,7 @@ class SettingsBloc extends Bloc<SettingsEvent, SettingsState> {
// 0 means that MAL does not know
totalChapters == 0 ? null : totalChapters,
data.imageUrl,
OtherTitles(),
TrackingDataSource.mal,
),
);
@@ -247,7 +250,7 @@ class SettingsBloc extends Bloc<SettingsEvent, SettingsState> {
Emitter<SettingsState> emit,
) async {
final al = GetIt.I.get<AnimeListBloc>();
final exportArchive = archive.GZipDecoder().decodeBytes(
final exportArchive = const archive.GZipDecoder().decodeBytes(
await File(event.path).readAsBytes(),
);
final json = jsonDecode(utf8.decode(exportArchive)) as Map<String, dynamic>;

View File

@@ -1,6 +1,7 @@
import 'package:anitrack/i18n/strings.g.dart';
import 'package:anitrack/src/ui/constants.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
Widget getDrawer(BuildContext context) {
return Drawer(
@@ -22,34 +23,30 @@ Widget getDrawer(BuildContext context) {
leading: const Icon(Icons.list),
title: Text(t.content.list),
onTap: () {
Navigator.of(context).pushNamedAndRemoveUntil(
animeListRoute,
(_) => false,
);
GoRouter.of(context).go(animeListRoute);
Navigator.of(context).pop();
},
),
ListTile(
leading: const Icon(Icons.calendar_today),
title: Text(t.calendar.calendar),
onTap: () {
Navigator.of(context).pushNamedAndRemoveUntil(
calendarRoute,
(_) => false,
);
GoRouter.of(context).go(calendarRoute);
Navigator.of(context).pop();
},
),
ListTile(
leading: const Icon(Icons.settings),
title: Text(t.settings.title),
onTap: () {
Navigator.of(context).pushNamed(settingsRoute);
GoRouter.of(context).push(settingsRoute);
},
),
ListTile(
leading: const Icon(Icons.info),
title: Text(t.about.title),
onTap: () {
Navigator.of(context).pushNamed(aboutRoute);
GoRouter.of(context).push(aboutRoute);
},
),
],

View File

@@ -1,8 +1,9 @@
import 'package:anitrack/i18n/strings.g.dart';
import 'package:anitrack/licenses.g.dart';
import 'package:anitrack/oss_licenses.dart';
import 'package:anitrack/src/ui/constants.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:go_router/go_router.dart';
import 'package:url_launcher/url_launcher.dart';
class AboutPage extends StatelessWidget {
@@ -22,9 +23,15 @@ class AboutPage extends StatelessWidget {
return Scaffold(
appBar: AppBar(
title: Text(t.about.title),
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () {
GoRouter.of(context).pop();
},
),
),
body: ListView.builder(
itemCount: ossLicenses.length + 1,
itemCount: dependencies.length + 1,
itemBuilder: (context, index) {
if (index == 0) {
return Padding(
@@ -41,7 +48,11 @@ class AboutPage extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
padding: const EdgeInsets.only(
top: 8,
left: 4,
right: 4,
),
child: ElevatedButton(
onPressed: () async {
await launchUrl(
@@ -55,7 +66,11 @@ class AboutPage extends StatelessWidget {
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
padding: const EdgeInsets.only(
top: 8,
left: 4,
right: 4,
),
child: ElevatedButton(
onPressed: () async {
final licenseText = await rootBundle.loadString(
@@ -90,9 +105,7 @@ class AboutPage extends StatelessWidget {
);
}
final dep = ossLicenses[index - 1];
if (!dep.isDirectDependency) return Container();
final dep = dependencies[index - 1];
return ListTile(
title: Text(dep.name),
onTap: () {

View File

@@ -4,7 +4,6 @@ import 'package:anitrack/src/ui/bloc/anime_list_bloc.dart';
import 'package:anitrack/src/ui/bloc/anime_search_bloc.dart';
import 'package:anitrack/src/ui/bloc/details_bloc.dart';
import 'package:anitrack/src/ui/constants.dart';
import 'package:anitrack/src/ui/helpers.dart';
import 'package:anitrack/src/ui/widgets/grid_item.dart';
import 'package:anitrack/src/ui/widgets/image.dart';
import 'package:bottom_bar/bottom_bar.dart';
@@ -24,40 +23,7 @@ class AnimeListPage extends StatefulWidget {
),
);
@override
AnimeListPageState createState() => AnimeListPageState();
}
class AnimeListPageState extends State<AnimeListPage> {
final PageController _controller = PageController();
final ScrollController _animeScrollController = ScrollController();
@override
void initState() {
super.initState();
_animeScrollController.addListener(_onAnimeListScrolled);
}
void _onAnimeListScrolled() {
//print(_animeScrollController.position.maxScrollExtent);
final bloc = GetIt.I.get<AnimeListBloc>();
if (_animeScrollController.offset + 20 >=
_animeScrollController.position.maxScrollExtent) {
if (bloc.state.buttonVisibility) {
bloc.add(
AddButtonVisibilitySetEvent(false),
);
}
} else {
if (!bloc.state.buttonVisibility) {
bloc.add(
AddButtonVisibilitySetEvent(true),
);
}
}
}
String _getPageTitle(TrackingMediumType type) {
static String getPageTitle(TrackingMediumType type) {
switch (type) {
case TrackingMediumType.anime:
return t.content.anime;
@@ -66,7 +32,7 @@ class AnimeListPageState extends State<AnimeListPage> {
}
}
List<PopupMenuItem<MediumTrackingState>> _getPopupButtonItems(
static List<PopupMenuItem<MediumTrackingState>> getPopupButtonItems(
TrackingMediumType type,
) {
return [
@@ -90,14 +56,14 @@ class AnimeListPageState extends State<AnimeListPage> {
value: MediumTrackingState.paused,
child: Text(MediumTrackingState.paused.getName(type)),
),
const PopupMenuItem<MediumTrackingState>(
PopupMenuItem<MediumTrackingState>(
value: MediumTrackingState.all,
child: Text('All'),
child: Text(t.data.all),
),
];
}
Widget _getPopupButton(BuildContext context, AnimeListState state) {
static Widget getPopupButton(BuildContext context, AnimeListState state) {
switch (state.trackingType) {
case TrackingMediumType.anime:
return PopupMenuButton(
@@ -110,7 +76,8 @@ class AnimeListPageState extends State<AnimeListPage> {
AnimeFilterChangedEvent(filterState),
);
},
itemBuilder: (_) => _getPopupButtonItems(TrackingMediumType.anime),
itemBuilder: (_) =>
AnimeListPage.getPopupButtonItems(TrackingMediumType.anime),
);
case TrackingMediumType.manga:
return PopupMenuButton(
@@ -123,183 +90,224 @@ class AnimeListPageState extends State<AnimeListPage> {
MangaFilterChangedEvent(filterState),
);
},
itemBuilder: (_) => _getPopupButtonItems(TrackingMediumType.manga),
itemBuilder: (_) =>
AnimeListPage.getPopupButtonItems(TrackingMediumType.manga),
);
}
}
static AppBar buildAppBar(BuildContext context) {
return AppBar(
title: BlocBuilder<AnimeListBloc, AnimeListState>(
builder: (context, state) => Text(
AnimeListPage.getPageTitle(state.trackingType),
),
),
actions: const [
BlocBuilder<AnimeListBloc, AnimeListState>(
builder: AnimeListPage.getPopupButton,
),
],
);
}
static Widget buildBottomNavigationBar(BuildContext context) {
return BlocBuilder<AnimeListBloc, AnimeListState>(
builder: (context, state) => BottomBar(
selectedIndex: state.trackingType == TrackingMediumType.anime ? 0 : 1,
onTap: (index) {
context.read<AnimeListBloc>().add(
AnimeTrackingTypeChanged(
index == 0 ? TrackingMediumType.anime : TrackingMediumType.manga,
),
);
},
items: [
BottomBarItem(
icon: const Icon(Icons.tv),
title: Text(t.content.anime),
activeColor: Colors.blue,
),
BottomBarItem(
icon: const Icon(Icons.auto_stories),
title: Text(t.content.manga),
activeColor: Colors.red,
),
],
),
);
}
static Widget buildFab() {
return BlocBuilder<AnimeListBloc, AnimeListState>(
buildWhen: (prev, next) =>
prev.buttonVisibility != next.buttonVisibility ||
prev.trackingType != next.trackingType,
builder: (context, state) {
return AnimatedScale(
duration: const Duration(milliseconds: 250),
scale: state.buttonVisibility ? 1 : 0,
curve: Curves.easeInOutQuint,
child: FloatingActionButton(
onPressed: () {
context.read<AnimeSearchBloc>().add(
AnimeSearchRequestedEvent(state.trackingType),
);
},
tooltip: t.tooltips.addNewItem,
child: const Icon(Icons.add),
),
);
},
);
}
@override
AnimeListPageState createState() => AnimeListPageState();
}
class AnimeListPageState extends State<AnimeListPage> {
final ScrollController _animeScrollController = ScrollController();
@override
void initState() {
super.initState();
_animeScrollController.addListener(_onAnimeListScrolled);
}
void _onAnimeListScrolled() {
final bloc = GetIt.I.get<AnimeListBloc>();
if (_animeScrollController.offset + 20 >=
_animeScrollController.position.maxScrollExtent) {
if (bloc.state.buttonVisibility) {
bloc.add(
AddButtonVisibilitySetEvent(false),
);
}
} else {
if (!bloc.state.buttonVisibility) {
bloc.add(
AddButtonVisibilitySetEvent(true),
);
}
}
}
@override
Widget build(BuildContext context) {
return BlocBuilder<AnimeListBloc, AnimeListState>(
builder: (context, state) {
return Scaffold(
appBar: AppBar(
title: Text(
_getPageTitle(state.trackingType),
return IndexedStack(
index: state.trackingType == TrackingMediumType.anime ? 0 : 1,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
mainAxisSpacing: 8,
crossAxisSpacing: 8,
childAspectRatio: 120 / (100 * (16 / 9)),
),
itemCount: state.animes.length,
controller: _animeScrollController,
itemBuilder: (context, index) {
final anime = state.animes[index];
return GridItem(
minusCallback: () {
context.read<AnimeListBloc>().add(
AnimeEpisodeDecrementedEvent(
anime.id,
),
);
},
plusCallback: () {
context.read<AnimeListBloc>().add(
AnimeEpisodeIncrementedEvent(
anime.id,
),
);
},
child: AnimeCoverImage(
url: anime.thumbnailUrl,
hero: 'grid_${anime.id}',
onTap: () {
context.read<DetailsBloc>().add(
AnimeDetailsRequestedEvent(
anime,
heroImagePrefix: 'grid_',
),
);
},
extra: Align(
alignment: Alignment.centerRight,
child: Padding(
padding: const EdgeInsets.only(right: 8),
child: Text(
'${anime.episodesWatched}/${anime.episodesTotal ?? "???"}',
style: Theme.of(context).textTheme.titleMedium,
),
),
),
),
);
},
),
),
actions: [
_getPopupButton(context, state),
],
),
drawer: getDrawer(context),
body: PageView(
// Prevent swiping between pages
// (https://github.com/flutter/flutter/issues/37510#issuecomment-612663656)
physics: const NeverScrollableScrollPhysics(),
controller: _controller,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
mainAxisSpacing: 8,
crossAxisSpacing: 8,
childAspectRatio: 120 / (100 * (16 / 9)),
),
itemCount: state.animes.length,
controller: _animeScrollController,
itemBuilder: (context, index) {
final anime = state.animes[index];
return GridItem(
minusCallback: () {
context.read<AnimeListBloc>().add(
AnimeEpisodeDecrementedEvent(
anime.id,
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
mainAxisSpacing: 8,
crossAxisSpacing: 8,
childAspectRatio: 120 / (100 * (16 / 9)),
),
itemCount: state.mangas.length,
itemBuilder: (context, index) {
final manga = state.mangas[index];
return GridItem(
minusCallback: () {
context.read<AnimeListBloc>().add(
MangaChapterDecrementedEvent(
manga.id,
),
);
},
plusCallback: () {
context.read<AnimeListBloc>().add(
MangaChapterIncrementedEvent(
manga.id,
),
);
},
child: AnimeCoverImage(
hero: 'grid_${manga.id}',
url: manga.thumbnailUrl,
onTap: () {
context.read<DetailsBloc>().add(
MangaDetailsRequestedEvent(
manga,
heroImagePrefix: 'grid_',
),
);
},
plusCallback: () {
context.read<AnimeListBloc>().add(
AnimeEpisodeIncrementedEvent(
anime.id,
),
);
},
child: AnimeCoverImage(
url: anime.thumbnailUrl,
hero: anime.id,
onTap: () {
context.read<DetailsBloc>().add(
AnimeDetailsRequestedEvent(anime),
);
},
extra: Align(
alignment: Alignment.centerRight,
child: Padding(
padding: const EdgeInsets.only(right: 8),
child: Text(
'${anime.episodesWatched}/${anime.episodesTotal ?? "???"}',
style: Theme.of(context).textTheme.titleMedium,
),
extra: Align(
alignment: Alignment.centerRight,
child: Padding(
padding: const EdgeInsets.only(right: 8),
child: Text(
'${manga.chaptersRead}/${manga.chaptersTotal ?? "???"}',
style: Theme.of(context).textTheme.titleMedium,
),
),
),
);
},
),
),
);
},
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
mainAxisSpacing: 8,
crossAxisSpacing: 8,
childAspectRatio: 120 / (100 * (16 / 9)),
),
itemCount: state.mangas.length,
itemBuilder: (context, index) {
final manga = state.mangas[index];
return GridItem(
minusCallback: () {
context.read<AnimeListBloc>().add(
MangaChapterDecrementedEvent(
manga.id,
),
);
},
plusCallback: () {
context.read<AnimeListBloc>().add(
MangaChapterIncrementedEvent(
manga.id,
),
);
},
child: AnimeCoverImage(
hero: manga.id,
url: manga.thumbnailUrl,
onTap: () {
context.read<DetailsBloc>().add(
MangaDetailsRequestedEvent(manga),
);
},
extra: Align(
alignment: Alignment.centerRight,
child: Padding(
padding: const EdgeInsets.only(right: 8),
child: Text(
'${manga.chaptersRead}/${manga.chaptersTotal ?? "???"}',
style: Theme.of(context).textTheme.titleMedium,
),
),
),
),
);
},
),
),
],
),
floatingActionButton: BlocBuilder<AnimeListBloc, AnimeListState>(
buildWhen: (prev, next) =>
prev.buttonVisibility != next.buttonVisibility ||
prev.trackingType != next.trackingType,
builder: (context, state) {
return AnimatedScale(
duration: const Duration(milliseconds: 250),
scale: state.buttonVisibility ? 1 : 0,
curve: Curves.easeInOutQuint,
child: FloatingActionButton(
onPressed: () {
context.read<AnimeSearchBloc>().add(
AnimeSearchRequestedEvent(state.trackingType),
);
},
tooltip: t.tooltips.addNewItem,
child: const Icon(Icons.add),
),
);
},
),
bottomNavigationBar: BottomBar(
selectedIndex: state.trackingType == TrackingMediumType.anime
? 0
: 1,
onTap: (int index) {
context.read<AnimeListBloc>().add(
AnimeTrackingTypeChanged(
index == 0
? TrackingMediumType.anime
: TrackingMediumType.manga,
),
);
_controller.jumpToPage(index);
},
items: [
BottomBarItem(
icon: const Icon(Icons.tv),
title: Text(t.content.anime),
activeColor: Colors.blue,
),
BottomBarItem(
icon: const Icon(Icons.auto_stories),
title: Text(t.content.manga),
activeColor: Colors.red,
),
],
),
),
],
);
},
);

View File

@@ -20,81 +20,84 @@ class AnimeSearchPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<AnimeSearchBloc, AnimeSearchState>(
builder: (context, state) {
return Scaffold(
appBar: AppBar(
title: Text(
state.trackingType == TrackingMediumType.anime
? t.search.anime
: t.search.manga,
),
),
body: Column(
children: [
Padding(
padding: const EdgeInsets.all(8),
child: TextField(
decoration: InputDecoration(
border: const OutlineInputBorder(),
labelText: t.search.query,
),
onSubmitted: (_) {
context.read<AnimeSearchBloc>().add(
SearchQuerySubmittedEvent(),
);
},
onChanged: (value) {
context.read<AnimeSearchBloc>().add(
SearchQueryChangedEvent(value),
);
},
),
return BlocListener<AnimeSearchBloc, AnimeSearchState>(
listener: (context, state) {},
child: BlocBuilder<AnimeSearchBloc, AnimeSearchState>(
builder: (context, state) {
return Scaffold(
appBar: AppBar(
title: Text(
state.trackingType == TrackingMediumType.anime
? t.search.anime
: t.search.manga,
),
if (state.working)
const Expanded(
child: Align(
child: CircularProgressIndicator(),
),
)
else
Expanded(
child: ListView.builder(
itemCount: state.searchResults.length,
itemBuilder: (context, index) {
final item = state.searchResults[index];
return InkWell(
onTap: () {
context.read<AnimeSearchBloc>().add(
ResultTappedEvent(item),
);
},
child: ListItem(
title: item.title,
thumbnailUrl: item.thumbnailUrl,
cached: false,
extra: [
Align(
alignment: Alignment.centerLeft,
child: Text(
item.description,
textAlign: TextAlign.justify,
style: Theme.of(context).textTheme.bodyMedium,
maxLines: 4,
softWrap: true,
overflow: TextOverflow.ellipsis,
),
),
],
),
),
body: Column(
children: [
Padding(
padding: const EdgeInsets.all(8),
child: TextField(
decoration: InputDecoration(
border: const OutlineInputBorder(),
labelText: t.search.query,
),
onSubmitted: (_) {
context.read<AnimeSearchBloc>().add(
SearchQuerySubmittedEvent(),
);
},
onChanged: (value) {
context.read<AnimeSearchBloc>().add(
SearchQueryChangedEvent(value),
);
},
),
),
],
),
);
},
if (state.working)
const Expanded(
child: Align(
child: CircularProgressIndicator(),
),
)
else
Expanded(
child: ListView.builder(
itemCount: state.searchResults.length,
itemBuilder: (context, index) {
final item = state.searchResults[index];
return InkWell(
onTap: () {
context.read<AnimeSearchBloc>().add(
ResultTappedEvent(item),
);
},
child: ListItem(
title: item.title,
thumbnailUrl: item.thumbnailUrl,
cached: false,
extra: [
Align(
alignment: Alignment.centerLeft,
child: Text(
item.description,
textAlign: TextAlign.justify,
style: Theme.of(context).textTheme.bodyMedium,
maxLines: 4,
softWrap: true,
overflow: TextOverflow.ellipsis,
),
),
],
),
);
},
),
),
],
),
);
},
),
);
}
}

View File

@@ -4,7 +4,6 @@ import 'package:anitrack/src/ui/bloc/anime_list_bloc.dart';
import 'package:anitrack/src/ui/bloc/calendar_bloc.dart';
import 'package:anitrack/src/ui/bloc/details_bloc.dart';
import 'package:anitrack/src/ui/constants.dart';
import 'package:anitrack/src/ui/helpers.dart';
import 'package:anitrack/src/ui/widgets/grid_item.dart';
import 'package:anitrack/src/ui/widgets/image.dart';
import 'package:flutter/material.dart';
@@ -63,11 +62,30 @@ class CalendarPage extends StatefulWidget {
),
);
static AppBar buildAppBar(BuildContext context) {
return AppBar(
title: Text(t.calendar.calendar),
actions: [
IconButton(
onPressed: () {
context.read<CalendarBloc>().add(
RefreshPerformedEvent(),
);
},
icon: const Icon(Icons.refresh),
),
],
);
}
@override
CalendarPageState createState() => CalendarPageState();
}
class CalendarPageState extends State<CalendarPage> {
/// State for the "refreshing" overlay.
OverlayEntry? _overlayEntry;
List<Widget> _renderWeekdayList(
BuildContext context,
Weekday day,
@@ -131,28 +149,20 @@ class CalendarPageState extends State<CalendarPage> {
switch (anime.broadcastDay) {
case 'Mondays':
day = Weekday.monday;
break;
case 'Tuesdays':
day = Weekday.tuesday;
break;
case 'Wednesdays':
day = Weekday.wednesday;
break;
case 'Thursdays':
day = Weekday.thursday;
break;
case 'Fridays':
day = Weekday.friday;
break;
case 'Saturdays':
day = Weekday.saturday;
break;
case 'Sundays':
day = Weekday.sunday;
break;
default:
day = Weekday.unknown;
break;
}
airingAnimeMap.addOrSet(day, anime);
@@ -169,128 +179,20 @@ class CalendarPageState extends State<CalendarPage> {
listenWhen: (previous, current) =>
previous.refreshing != current.refreshing,
listener: (context, state) {
// Force an update
if (!state.refreshing) {
setState(() {});
}
},
child: WillPopScope(
onWillPop: () async =>
!context.read<CalendarBloc>().state.refreshing,
child: Stack(
children: [
Positioned(
left: 8,
right: 8,
top: 0,
bottom: 0,
child: Scaffold(
appBar: AppBar(
title: Text(t.calendar.calendar),
actions: [
IconButton(
onPressed: () {
context.read<CalendarBloc>().add(
RefreshPerformedEvent(),
);
},
icon: const Icon(Icons.refresh),
),
],
),
drawer: getDrawer(context),
body: Padding(
padding: const EdgeInsetsGeometry.symmetric(
horizontal: 12,
),
child: CustomScrollView(
slivers: [
// Render all available weekdays
..._renderWeekdayList(
context,
Weekday.unknown,
airingAnimeMap,
),
..._renderWeekdayList(
context,
Weekday.monday,
airingAnimeMap,
),
..._renderWeekdayList(
context,
Weekday.tuesday,
airingAnimeMap,
),
..._renderWeekdayList(
context,
Weekday.wednesday,
airingAnimeMap,
),
..._renderWeekdayList(
context,
Weekday.thursday,
airingAnimeMap,
),
..._renderWeekdayList(
context,
Weekday.friday,
airingAnimeMap,
),
..._renderWeekdayList(
context,
Weekday.saturday,
airingAnimeMap,
),
..._renderWeekdayList(
context,
Weekday.sunday,
airingAnimeMap,
),
// Provide a nice bottom padding, while keeping the elastic effect attached
// to the bottom-most edge.
const SliverToBoxAdapter(
child: SizedBox(
height: 16,
),
),
],
),
),
),
),
Positioned(
left: 0,
right: 0,
top: 0,
bottom: 0,
child: BlocBuilder<CalendarBloc, CalendarState>(
buildWhen: (previous, current) =>
previous.refreshing != current.refreshing,
builder: (context, state) {
if (!state.refreshing) {
return const SizedBox();
}
return const ModalBarrier(
_overlayEntry?.remove();
_overlayEntry?.dispose();
_overlayEntry = null;
} else {
_overlayEntry = OverlayEntry(
builder: (context) => SafeArea(
child: Stack(
children: [
const ModalBarrier(
dismissible: false,
color: Colors.black54,
);
},
),
),
Positioned(
left: 0,
right: 0,
top: 0,
bottom: 0,
child: BlocBuilder<CalendarBloc, CalendarState>(
builder: (context, state) {
if (!state.refreshing) {
return const SizedBox();
}
return Center(
),
Center(
child: SizedBox(
width: 150,
height: 150,
@@ -307,22 +209,89 @@ class CalendarPageState extends State<CalendarPage> {
padding: EdgeInsets.all(25),
child: CircularProgressIndicator(),
),
Text(
t.settings.importIndicator(
current: state.refreshingCount,
total: state.refreshingTotal,
BlocBuilder<CalendarBloc, CalendarState>(
builder: (context, state) => Text(
t.settings.importIndicator(
current: state.refreshingCount,
total: state.refreshingTotal,
),
style: Theme.of(
context,
).textTheme.bodyMedium,
),
style: Theme.of(context).textTheme.bodyMedium,
),
],
),
),
),
);
},
),
],
),
),
],
);
Overlay.of(context).insert(_overlayEntry!);
}
},
child: WillPopScope(
onWillPop: () async =>
!context.read<CalendarBloc>().state.refreshing,
child: Padding(
padding: const EdgeInsetsGeometry.symmetric(
horizontal: 12,
),
child: CustomScrollView(
slivers: [
// Render all available weekdays
..._renderWeekdayList(
context,
Weekday.unknown,
airingAnimeMap,
),
..._renderWeekdayList(
context,
Weekday.monday,
airingAnimeMap,
),
..._renderWeekdayList(
context,
Weekday.tuesday,
airingAnimeMap,
),
..._renderWeekdayList(
context,
Weekday.wednesday,
airingAnimeMap,
),
..._renderWeekdayList(
context,
Weekday.thursday,
airingAnimeMap,
),
..._renderWeekdayList(
context,
Weekday.friday,
airingAnimeMap,
),
..._renderWeekdayList(
context,
Weekday.saturday,
airingAnimeMap,
),
..._renderWeekdayList(
context,
Weekday.sunday,
airingAnimeMap,
),
// Provide a nice bottom padding, while keeping the elastic effect attached
// to the bottom-most edge.
const SliverToBoxAdapter(
child: SizedBox(
height: 16,
),
),
],
),
),
),
);

View File

@@ -16,6 +16,7 @@ import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:get_it/get_it.dart';
import 'package:go_router/go_router.dart';
import 'package:url_launcher/url_launcher.dart';
class DetailsPage extends StatelessWidget {
@@ -60,6 +61,12 @@ class DetailsPage extends StatelessWidget {
return Scaffold(
appBar: AppBar(
title: Text(t.details.title),
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () {
GoRouter.of(context).pop();
},
),
),
body: BlocBuilder<DetailsBloc, DetailsState>(
builder: (context, state) {

View File

@@ -80,6 +80,7 @@ class DetailsWatcherSheetState extends State<DetailsWatcherSheet> {
child: TextField(
controller: _controller,
decoration: InputDecoration(
border: const OutlineInputBorder(),
labelText: t.details.watchingWith.name,
),
),

View File

@@ -5,6 +5,7 @@ import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:get_it/get_it.dart';
import 'package:go_router/go_router.dart';
import 'package:permission_handler/permission_handler.dart';
class SettingsPage extends StatelessWidget {
@@ -35,6 +36,12 @@ class SettingsPage extends StatelessWidget {
child: Scaffold(
appBar: AppBar(
title: Text(t.settings.title),
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () {
GoRouter.of(context).pop();
},
),
),
body: ListView(
children: [
@@ -43,7 +50,7 @@ class SettingsPage extends StatelessWidget {
subtitle: Text(t.settings.importAnimeDesc),
onTap: () async {
// Pick the file
final result = await FilePicker.platform.pickFiles();
final result = await FilePicker.pickFiles();
if (result == null) return;
if (!result.files.first.path!.endsWith('.xml.gz')) {
@@ -70,7 +77,7 @@ class SettingsPage extends StatelessWidget {
subtitle: Text(t.settings.importMangaDesc),
onTap: () async {
// Pick the file
final result = await FilePicker.platform.pickFiles();
final result = await FilePicker.pickFiles();
if (result == null) return;
if (!result.files.first.path!.endsWith('.xml.gz')) {
@@ -96,14 +103,14 @@ class SettingsPage extends StatelessWidget {
title: Text(t.settings.exportData),
onTap: () async {
// Pick the file
final result = await FilePicker.platform
.getDirectoryPath();
final result = await FilePicker.getDirectoryPath();
if (result == null) return;
if (!(await Permission.manageExternalStorage
.request())
.isGranted)
.isGranted) {
return;
}
GetIt.I.get<SettingsBloc>().add(
DataExportedEvent(
@@ -116,7 +123,7 @@ class SettingsPage extends StatelessWidget {
title: Text(t.settings.importData),
onTap: () async {
// Pick the file
final result = await FilePicker.platform.pickFiles();
final result = await FilePicker.pickFiles();
if (result == null) return;
if (!result.files.first.path!.endsWith('.json.gz')) {

View File

@@ -1,4 +1,3 @@
import 'package:anitrack/src/ui/widgets/list_item.dart';
import 'package:flutter/material.dart';
class SelectorItem<T> {

View File

@@ -4,7 +4,7 @@ import 'package:flutter/material.dart';
class AnimeCoverImage extends StatelessWidget {
const AnimeCoverImage({
required this.url,
required this.hero,
this.hero,
this.cached = true,
this.extra,
this.onTap,
@@ -18,7 +18,7 @@ class AnimeCoverImage extends StatelessWidget {
final bool cached;
/// The hero tag of the image.
final String hero;
final String? hero;
/// An extra widget with a translucent backdrop.
final Widget? extra;
@@ -28,53 +28,59 @@ class AnimeCoverImage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Hero(
tag: hero,
child: ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Material(
child: SizedBox(
height: 100 * (16 / 9),
width: 120,
child: InkWell(
onTap: onTap ?? () {},
child: Stack(
children: [
Positioned(
left: 0,
right: 0,
top: 0,
bottom: 0,
child: DecoratedBox(
decoration: BoxDecoration(
image: DecorationImage(
image: cached
? CachedNetworkImageProvider(url) as ImageProvider
: NetworkImage(url),
fit: BoxFit.cover,
),
final image = ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Material(
child: SizedBox(
height: 100 * (16 / 9),
width: 120,
child: InkWell(
onTap: onTap ?? () {},
child: Stack(
children: [
Positioned(
left: 0,
right: 0,
top: 0,
bottom: 0,
child: DecoratedBox(
decoration: BoxDecoration(
image: DecorationImage(
image: cached
? CachedNetworkImageProvider(url) as ImageProvider
: NetworkImage(url),
fit: BoxFit.cover,
),
),
),
if (extra != null)
Positioned(
left: 0,
bottom: 0,
right: 0,
child: SizedBox(
height: 40,
child: ColoredBox(
color: Colors.black54,
child: extra,
),
),
if (extra != null)
Positioned(
left: 0,
bottom: 0,
right: 0,
child: SizedBox(
height: 40,
child: ColoredBox(
color: Colors.black54,
child: extra,
),
),
],
),
),
],
),
),
),
),
);
if (hero != null) {
return Hero(
tag: hero!,
child: image,
);
}
return image;
}
}

View File

@@ -46,7 +46,7 @@ class ListItem extends StatelessWidget {
},
// TODO(PapaTutuWawa): Fix
key: UniqueKey(),
backgroundBuilder: (_, direction, __) {
backgroundBuilder: (_, direction, _) {
if (direction == SwipeDirection.endToStart) {
return const Align(
alignment: Alignment.centerRight,
@@ -76,8 +76,6 @@ class ListItem extends StatelessWidget {
children: [
AnimeCoverImage(
cached: cached,
// TODO(Unknown): Have the ID here
hero: thumbnailUrl,
extra: imageExtra,
url: thumbnailUrl,
),

View File

@@ -0,0 +1,48 @@
import 'package:anitrack/src/ui/constants.dart';
import 'package:anitrack/src/ui/helpers.dart';
import 'package:anitrack/src/ui/pages/anime_list.dart';
import 'package:anitrack/src/ui/pages/calendar.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
class ShellWrapper extends StatelessWidget {
const ShellWrapper({
required this.state,
required this.child,
super.key,
});
/// The current router state.
final GoRouterState state;
/// The child to show.
final Widget child;
@override
Widget build(BuildContext context) {
final currentPath = GoRouterState.of(context).uri.toString();
AppBar? appBar;
Widget? drawer;
Widget? fab;
Widget? bottomNavigationBar;
switch (currentPath) {
case animeListRoute:
drawer = getDrawer(context);
appBar = AnimeListPage.buildAppBar(context);
bottomNavigationBar = AnimeListPage.buildBottomNavigationBar(context);
fab = AnimeListPage.buildFab();
case calendarRoute:
drawer = getDrawer(context);
appBar = CalendarPage.buildAppBar(context);
}
return Scaffold(
appBar: appBar,
bottomNavigationBar: bottomNavigationBar,
drawer: drawer,
floatingActionButton: fab,
body: child,
);
}
}

View File

@@ -5,18 +5,18 @@ packages:
dependency: transitive
description:
name: _fe_analyzer_shared
sha256: "8d7ff3948166b8ec5da0fbb5962000926b8e02f2ed9b3e51d1738905fbd4c98d"
sha256: "3b19a47f6ea7c2632760777c78174f47f6aec1e05f0cd611380d4593b8af1dbc"
url: "https://pub.dev"
source: hosted
version: "93.0.0"
version: "96.0.0"
analyzer:
dependency: transitive
description:
name: analyzer
sha256: de7148ed2fcec579b19f122c1800933dfa028f6d9fd38a152b04b1516cec120b
sha256: "0c516bc4ad36a1a75759e54d5047cb9d15cded4459df01aa35a0b5ec7db2c2a0"
url: "https://pub.dev"
source: hosted
version: "10.0.1"
version: "10.2.0"
archive:
dependency: "direct main"
description:
@@ -242,7 +242,7 @@ packages:
source: hosted
version: "1.0.9"
dart_pubspec_licenses:
dependency: transitive
dependency: "direct dev"
description:
name: dart_pubspec_licenses
sha256: "3d579e1aa3ad3b6519f08fce6980799c0a8375bf41e0b8d58ca21f1be64032c9"
@@ -261,10 +261,10 @@ packages:
dependency: transitive
description:
name: dbus
sha256: "0ce9b0a839e6dee59a37a623d2fc26a35bbbe6404213e419b0d6411023d62645"
sha256: "792974a4007974fbc5c1b5433eb2330a9db3e368c3f906253af4c007d0f49a91"
url: "https://pub.dev"
source: hosted
version: "0.7.14"
version: "0.7.13"
fake_async:
dependency: transitive
description:
@@ -293,10 +293,10 @@ packages:
dependency: "direct main"
description:
name: file_picker
sha256: "57d9a1dd5063f85fa3107fb42d1faffda52fdc948cefd5fe5ea85267a5fc7343"
sha256: f13a03000d942e476bc1ff0a736d2e9de711d2f89a95cd4c1d88f861c3348387
url: "https://pub.dev"
source: hosted
version: "10.3.10"
version: "11.0.2"
fixnum:
dependency: transitive
description:
@@ -347,14 +347,6 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
flutter_oss_licenses:
dependency: "direct dev"
description:
name: flutter_oss_licenses
sha256: "80f5d879f1760f90020692db0d4b58b5a475df23573df567e12757a71ce59628"
url: "https://pub.dev"
source: hosted
version: "3.2.0"
flutter_plugin_android_lifecycle:
dependency: transitive
description:
@@ -421,6 +413,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.3"
go_router:
dependency: "direct main"
description:
name: go_router
sha256: "5922b2861e2235a3504896f0d6fa07d84141b480cf52eecd2f42cd25585a9e8a"
url: "https://pub.dev"
source: hosted
version: "17.3.0"
gql:
dependency: transitive
description:
@@ -545,10 +545,10 @@ packages:
dependency: transitive
description:
name: image
sha256: f9881ff4998044947ec38d098bc7c8316ae1186fa786eddffdb867b9bc94dfce
sha256: "6300175e00616bbc832e2fc91bfa4d776af5402c81c7151bee6905bb08473c52"
url: "https://pub.dev"
source: hosted
version: "4.8.0"
version: "4.9.1"
intl:
dependency: transitive
description:
@@ -766,7 +766,7 @@ packages:
source: hosted
version: "1.9.1"
path_provider:
dependency: transitive
dependency: "direct main"
description:
name: path_provider
sha256: a7f4874f987173da295a61c181b8ee71dab59b332a486b391babf26a1b884825
@@ -1070,50 +1070,50 @@ packages:
dependency: "direct main"
description:
name: sqflite
sha256: "564cfed0746fe53140c23b70b308e045c3b31f17778f2f326ccb7d804ea0250a"
sha256: "58a799e6ac17dd32fbab93813d39ed835a75ccc0f8f85b8955fe318c6712b082"
url: "https://pub.dev"
source: hosted
version: "2.4.2+1"
version: "2.4.3"
sqflite_android:
dependency: transitive
description:
name: sqflite_android
sha256: "881e28efdcc9950fd8e9bb42713dcf1103e62a2e7168f23c9338d82db13dec40"
sha256: d0548f9d7422a2dae99ec6f8b0a3074463b132d216fa5ba0d230eeefc901983b
url: "https://pub.dev"
source: hosted
version: "2.4.2+3"
version: "2.4.3"
sqflite_common:
dependency: transitive
description:
name: sqflite_common
sha256: "1581ffbf7a0e333b380d6a30737d78516b826cb35beb7fb0bf8a3ea0c678b465"
sha256: "5bf6a55c166e73bf651ba7ec3ed486e577620e3dc8f3a9c6a258a8031b624590"
url: "https://pub.dev"
source: hosted
version: "2.5.8"
version: "2.5.11"
sqflite_common_ffi:
dependency: "direct main"
description:
name: sqflite_common_ffi
sha256: cd0c7f7de39a08f2d54ef144d9058c46eca8461879aaa648025643455c1e5a20
sha256: "5ccd38136edb9beb3213f6927775d52db70dfdadcdb28dad1f625ca9f2b9824f"
url: "https://pub.dev"
source: hosted
version: "2.4.0+3"
version: "2.4.2"
sqflite_darwin:
dependency: transitive
description:
name: sqflite_darwin
sha256: "279832e5cde3fe99e8571879498c9211f3ca6391b0d818df4e17d9fff5c6ccb3"
sha256: c86ca18b8f666bbf903924687fe21cc16fc385d086005067e26619ca530bef9f
url: "https://pub.dev"
source: hosted
version: "2.4.2"
version: "2.4.3+1"
sqflite_platform_interface:
dependency: transitive
description:
name: sqflite_platform_interface
sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920"
sha256: f84939f84350d92d04416f8bc4dc52d3896aec7716cc9e80cf0146342139dc50
url: "https://pub.dev"
source: hosted
version: "2.4.0"
version: "2.4.1"
sqlite3:
dependency: transitive
description:
@@ -1166,10 +1166,10 @@ packages:
dependency: transitive
description:
name: synchronized
sha256: "63896c27e81b28f8cb4e69ead0d3e8f03f1d1e5fc531a3e579cabed6a2c7c9e5"
sha256: "61894a1956de6b4fc1aefd0892e109514a1a706cbece3ac59decd90ff5a7a423"
url: "https://pub.dev"
source: hosted
version: "3.4.0+1"
version: "3.4.1+1"
term_glyph:
dependency: transitive
description:
@@ -1222,10 +1222,10 @@ packages:
dependency: transitive
description:
name: url_launcher_android
sha256: "17bc677f0b301615530dd1d67e0a9828cafa2d0b6b6eae4cd3679b7eac4a273c"
sha256: b413d49b73867ac08dd2f9890efd3cc11f2a0e577618d50843440a1fb3776c32
url: "https://pub.dev"
source: hosted
version: "6.3.30"
version: "6.3.32"
url_launcher_ios:
dependency: transitive
description:
@@ -1294,10 +1294,10 @@ packages:
dependency: "direct dev"
description:
name: very_good_analysis
sha256: d1cb1d66a5aae2c702d68caca6c8347306d35e728fd94555fa21fa0448a972e0
sha256: "481af67ab5877af20325251dc215a4ebac7666a1c8cf09198ffd457bc612b33d"
url: "https://pub.dev"
source: hosted
version: "10.2.0"
version: "10.3.0"
vm_service:
dependency: transitive
description:
@@ -1366,10 +1366,10 @@ packages:
dependency: "direct main"
description:
name: xml
sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025"
sha256: "67f0aff7be013d107995e9b75bf4e7f2c3ef2dfdb2c8e68024bba0a7fd5756a4"
url: "https://pub.dev"
source: hosted
version: "6.6.1"
version: "7.0.1"
yaml:
dependency: transitive
description:
@@ -1379,5 +1379,5 @@ packages:
source: hosted
version: "3.1.3"
sdks:
dart: ">=3.11.0 <4.0.0"
flutter: ">=3.41.0"
dart: ">=3.12.0 <4.0.0"
flutter: ">=3.44.0"

View File

@@ -2,7 +2,7 @@ name: anitrack
description: An anime and manga tracker
publish_to: "none"
version: 0.2.0+2016
version: 0.2.2+2019
environment:
sdk: ^3.8.0
@@ -14,17 +14,19 @@ dependencies:
cached_network_image: ^3.4.1
collection: ^1.18.0
cupertino_icons: ^1.0.8
file_picker: ^10.3.10
file_picker: ^11.0.2
flutter:
sdk: flutter
flutter_bloc: ^9.1.1
fluttertoast: ^9.0.0
freezed_annotation: ^3.1.0
get_it: ^9.2.1
go_router: ^17.3.0
graphql: ^5.2.4
jikan_api: ^2.2.1
json_annotation: ^4.11.0
path: ^1.9.0
path: ^1.9.1
path_provider: ^2.1.6
permission_handler: ^12.0.1
slang: ^4.13.0
slang_flutter: ^4.13.0
@@ -32,13 +34,13 @@ dependencies:
sqflite_common_ffi:
swipeable_tile: ^2.0.1
url_launcher: ^6.3.0
xml: ^6.5.0
xml: ^7.0.1
dev_dependencies:
build_runner: ^2.4.12
flutter_launcher_icons: ^0.14.1
flutter_lints: ^6.0.0
flutter_oss_licenses: ^3.0.2
dart_pubspec_licenses: ^3.2.0
flutter_test:
sdk: flutter
freezed: ^3.2.5