Compare commits
No commits in common. "286f705c4125ddddc3e28ab055010db9a0668285" and "fbe72d123246bf919c305917ea196c43ce226876" have entirely different histories.
286f705c41
...
fbe72d1232
@ -33,5 +33,4 @@
|
||||
</application>
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
|
||||
</manifest>
|
||||
|
@ -9,15 +9,7 @@
|
||||
"importMangaDesc": "Import manga list exported from MyAnimeList.",
|
||||
"invalidMangaListTitle": "Invalid manga list",
|
||||
"invalidMangaListBody": "The selected file is not a MAL manga list. It lacks the \".xml.gz\" suffix.",
|
||||
"importIndicator": "$current of $total",
|
||||
"exportData": "Export data",
|
||||
"importData": "Import data",
|
||||
"dataExportSuccess": "Data successfully exported",
|
||||
"dataImportSuccess": "Data successfully imported",
|
||||
"importInvalidData": {
|
||||
"title": "Invalid AniTrack Data",
|
||||
"content": "The selected file is not an AniTrack data export. It lacks the \".json.gz\" suffix."
|
||||
}
|
||||
"importIndicator": "$current of $total"
|
||||
},
|
||||
"about": {
|
||||
"title": "About",
|
||||
@ -27,7 +19,6 @@
|
||||
"addNewItem": "Add new item"
|
||||
},
|
||||
"content": {
|
||||
"list": "List",
|
||||
"anime": "Anime",
|
||||
"manga": "Manga"
|
||||
},
|
||||
@ -48,19 +39,6 @@
|
||||
"chapters": "Chapters",
|
||||
"volumesOwned": "Volumes owned"
|
||||
},
|
||||
"calendar": {
|
||||
"calendar": "Calendar",
|
||||
"days": {
|
||||
"monday": "Monday",
|
||||
"tuesday": "Tuesday",
|
||||
"wednesday": "Wednesday",
|
||||
"thursday": "Thursday",
|
||||
"friday": "Friday",
|
||||
"saturday": "Saturday",
|
||||
"sunday": "Sunday",
|
||||
"unknown": "Unknown"
|
||||
}
|
||||
},
|
||||
"data": {
|
||||
"ongoing": {
|
||||
"anime": "Watching",
|
||||
|
@ -4,6 +4,4 @@ targets:
|
||||
slang_build_runner:
|
||||
options:
|
||||
input_directory: assets/i18n
|
||||
output_directory: lib/i18n
|
||||
fallback_strategy: base_locale
|
||||
base_locale: en
|
||||
output_directory: lib/i18n
|
File diff suppressed because it is too large
Load Diff
@ -2,7 +2,6 @@ import 'package:anitrack/i18n/strings.g.dart';
|
||||
import 'package:anitrack/src/service/database.dart';
|
||||
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/calendar_bloc.dart';
|
||||
import 'package:anitrack/src/ui/bloc/details_bloc.dart';
|
||||
import 'package:anitrack/src/ui/bloc/navigation_bloc.dart';
|
||||
import 'package:anitrack/src/ui/bloc/settings_bloc.dart';
|
||||
@ -10,7 +9,6 @@ import 'package:anitrack/src/ui/constants.dart';
|
||||
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/settings.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@ -34,7 +32,6 @@ void main() async {
|
||||
GetIt.I.registerSingleton<DetailsBloc>(DetailsBloc());
|
||||
GetIt.I.registerSingleton<NavigationBloc>(NavigationBloc(navKey));
|
||||
GetIt.I.registerSingleton<SettingsBloc>(SettingsBloc());
|
||||
GetIt.I.registerSingleton<CalendarBloc>(CalendarBloc());
|
||||
|
||||
// Load animes
|
||||
GetIt.I.get<AnimeListBloc>().add(
|
||||
@ -62,9 +59,6 @@ void main() async {
|
||||
BlocProvider<SettingsBloc>(
|
||||
create: (_) => GetIt.I.get<SettingsBloc>(),
|
||||
),
|
||||
BlocProvider<CalendarBloc>(
|
||||
create: (_) => GetIt.I.get<CalendarBloc>(),
|
||||
),
|
||||
],
|
||||
child: MyApp(navKey),
|
||||
),
|
||||
@ -101,8 +95,6 @@ class MyApp extends StatelessWidget {
|
||||
return AnimeListPage.route;
|
||||
case animeSearchRoute:
|
||||
return AnimeSearchPage.route;
|
||||
case calendarRoute:
|
||||
return CalendarPage.route;
|
||||
case detailsRoute:
|
||||
return DetailsPage.route;
|
||||
case aboutRoute:
|
||||
|
@ -1,20 +1,9 @@
|
||||
import 'package:anitrack/src/data/type.dart';
|
||||
import 'package:anitrack/src/service/database.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'anime.freezed.dart';
|
||||
part 'anime.g.dart';
|
||||
|
||||
class BoolConverter implements JsonConverter<bool, int> {
|
||||
const BoolConverter();
|
||||
|
||||
@override
|
||||
bool fromJson(int json) => json.toBool();
|
||||
|
||||
@override
|
||||
int toJson(bool object) => object.toInt();
|
||||
}
|
||||
|
||||
/// Data about a tracked anime
|
||||
@freezed
|
||||
class AnimeTrackingData with _$AnimeTrackingData, TrackingMedium {
|
||||
@ -36,12 +25,6 @@ class AnimeTrackingData with _$AnimeTrackingData, TrackingMedium {
|
||||
|
||||
/// URL to the thumbnail/cover art for the anime.
|
||||
String thumbnailUrl,
|
||||
|
||||
/// Flag whether the anime is airing
|
||||
@BoolConverter() bool airing,
|
||||
|
||||
/// The day of the week the anime is airing
|
||||
String? broadcastDay,
|
||||
) = _AnimeTrackingData;
|
||||
|
||||
/// JSON
|
||||
|
@ -39,13 +39,6 @@ mixin _$AnimeTrackingData {
|
||||
/// URL to the thumbnail/cover art for the anime.
|
||||
String get thumbnailUrl => throw _privateConstructorUsedError;
|
||||
|
||||
/// Flag whether the anime is airing
|
||||
@BoolConverter()
|
||||
bool get airing => throw _privateConstructorUsedError;
|
||||
|
||||
/// The day of the week the anime is airing
|
||||
String? get broadcastDay => throw _privateConstructorUsedError;
|
||||
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@JsonKey(ignore: true)
|
||||
$AnimeTrackingDataCopyWith<AnimeTrackingData> get copyWith =>
|
||||
@ -63,9 +56,7 @@ abstract class $AnimeTrackingDataCopyWith<$Res> {
|
||||
String title,
|
||||
int episodesWatched,
|
||||
int? episodesTotal,
|
||||
String thumbnailUrl,
|
||||
@BoolConverter() bool airing,
|
||||
String? broadcastDay});
|
||||
String thumbnailUrl});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@ -85,8 +76,6 @@ class _$AnimeTrackingDataCopyWithImpl<$Res>
|
||||
Object? episodesWatched = freezed,
|
||||
Object? episodesTotal = freezed,
|
||||
Object? thumbnailUrl = freezed,
|
||||
Object? airing = freezed,
|
||||
Object? broadcastDay = freezed,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
id: id == freezed
|
||||
@ -113,14 +102,6 @@ class _$AnimeTrackingDataCopyWithImpl<$Res>
|
||||
? _value.thumbnailUrl
|
||||
: thumbnailUrl // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
airing: airing == freezed
|
||||
? _value.airing
|
||||
: airing // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
broadcastDay: broadcastDay == freezed
|
||||
? _value.broadcastDay
|
||||
: broadcastDay // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
));
|
||||
}
|
||||
}
|
||||
@ -138,9 +119,7 @@ abstract class _$$_AnimeTrackingDataCopyWith<$Res>
|
||||
String title,
|
||||
int episodesWatched,
|
||||
int? episodesTotal,
|
||||
String thumbnailUrl,
|
||||
@BoolConverter() bool airing,
|
||||
String? broadcastDay});
|
||||
String thumbnailUrl});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@ -162,8 +141,6 @@ class __$$_AnimeTrackingDataCopyWithImpl<$Res>
|
||||
Object? episodesWatched = freezed,
|
||||
Object? episodesTotal = freezed,
|
||||
Object? thumbnailUrl = freezed,
|
||||
Object? airing = freezed,
|
||||
Object? broadcastDay = freezed,
|
||||
}) {
|
||||
return _then(_$_AnimeTrackingData(
|
||||
id == freezed
|
||||
@ -190,14 +167,6 @@ class __$$_AnimeTrackingDataCopyWithImpl<$Res>
|
||||
? _value.thumbnailUrl
|
||||
: thumbnailUrl // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
airing == freezed
|
||||
? _value.airing
|
||||
: airing // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
broadcastDay == freezed
|
||||
? _value.broadcastDay
|
||||
: broadcastDay // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
));
|
||||
}
|
||||
}
|
||||
@ -205,15 +174,8 @@ class __$$_AnimeTrackingDataCopyWithImpl<$Res>
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$_AnimeTrackingData implements _AnimeTrackingData {
|
||||
_$_AnimeTrackingData(
|
||||
this.id,
|
||||
@MediumTrackingStateConverter() this.state,
|
||||
this.title,
|
||||
this.episodesWatched,
|
||||
this.episodesTotal,
|
||||
this.thumbnailUrl,
|
||||
@BoolConverter() this.airing,
|
||||
this.broadcastDay);
|
||||
_$_AnimeTrackingData(this.id, @MediumTrackingStateConverter() this.state,
|
||||
this.title, this.episodesWatched, this.episodesTotal, this.thumbnailUrl);
|
||||
|
||||
factory _$_AnimeTrackingData.fromJson(Map<String, dynamic> json) =>
|
||||
_$$_AnimeTrackingDataFromJson(json);
|
||||
@ -243,18 +205,9 @@ class _$_AnimeTrackingData implements _AnimeTrackingData {
|
||||
@override
|
||||
final String thumbnailUrl;
|
||||
|
||||
/// Flag whether the anime is airing
|
||||
@override
|
||||
@BoolConverter()
|
||||
final bool airing;
|
||||
|
||||
/// The day of the week the anime is airing
|
||||
@override
|
||||
final String? broadcastDay;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'AnimeTrackingData(id: $id, state: $state, title: $title, episodesWatched: $episodesWatched, episodesTotal: $episodesTotal, thumbnailUrl: $thumbnailUrl, airing: $airing, broadcastDay: $broadcastDay)';
|
||||
return 'AnimeTrackingData(id: $id, state: $state, title: $title, episodesWatched: $episodesWatched, episodesTotal: $episodesTotal, thumbnailUrl: $thumbnailUrl)';
|
||||
}
|
||||
|
||||
@override
|
||||
@ -270,10 +223,7 @@ class _$_AnimeTrackingData implements _AnimeTrackingData {
|
||||
const DeepCollectionEquality()
|
||||
.equals(other.episodesTotal, episodesTotal) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other.thumbnailUrl, thumbnailUrl) &&
|
||||
const DeepCollectionEquality().equals(other.airing, airing) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other.broadcastDay, broadcastDay));
|
||||
.equals(other.thumbnailUrl, thumbnailUrl));
|
||||
}
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@ -285,9 +235,7 @@ class _$_AnimeTrackingData implements _AnimeTrackingData {
|
||||
const DeepCollectionEquality().hash(title),
|
||||
const DeepCollectionEquality().hash(episodesWatched),
|
||||
const DeepCollectionEquality().hash(episodesTotal),
|
||||
const DeepCollectionEquality().hash(thumbnailUrl),
|
||||
const DeepCollectionEquality().hash(airing),
|
||||
const DeepCollectionEquality().hash(broadcastDay));
|
||||
const DeepCollectionEquality().hash(thumbnailUrl));
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@ -310,9 +258,7 @@ abstract class _AnimeTrackingData implements AnimeTrackingData {
|
||||
final String title,
|
||||
final int episodesWatched,
|
||||
final int? episodesTotal,
|
||||
final String thumbnailUrl,
|
||||
@BoolConverter() final bool airing,
|
||||
final String? broadcastDay) = _$_AnimeTrackingData;
|
||||
final String thumbnailUrl) = _$_AnimeTrackingData;
|
||||
|
||||
factory _AnimeTrackingData.fromJson(Map<String, dynamic> json) =
|
||||
_$_AnimeTrackingData.fromJson;
|
||||
@ -343,15 +289,6 @@ abstract class _AnimeTrackingData implements AnimeTrackingData {
|
||||
/// URL to the thumbnail/cover art for the anime.
|
||||
String get thumbnailUrl;
|
||||
@override
|
||||
|
||||
/// Flag whether the anime is airing
|
||||
@BoolConverter()
|
||||
bool get airing;
|
||||
@override
|
||||
|
||||
/// The day of the week the anime is airing
|
||||
String? get broadcastDay;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$_AnimeTrackingDataCopyWith<_$_AnimeTrackingData> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
|
@ -14,8 +14,6 @@ _$_AnimeTrackingData _$$_AnimeTrackingDataFromJson(Map<String, dynamic> json) =>
|
||||
json['episodesWatched'] as int,
|
||||
json['episodesTotal'] as int?,
|
||||
json['thumbnailUrl'] as String,
|
||||
const BoolConverter().fromJson(json['airing'] as int),
|
||||
json['broadcastDay'] as String?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$_AnimeTrackingDataToJson(
|
||||
@ -27,6 +25,4 @@ Map<String, dynamic> _$$_AnimeTrackingDataToJson(
|
||||
'episodesWatched': instance.episodesWatched,
|
||||
'episodesTotal': instance.episodesTotal,
|
||||
'thumbnailUrl': instance.thumbnailUrl,
|
||||
'airing': const BoolConverter().toJson(instance.airing),
|
||||
'broadcastDay': instance.broadcastDay,
|
||||
};
|
||||
|
@ -5,8 +5,6 @@ class SearchResult {
|
||||
this.total,
|
||||
this.thumbnailUrl,
|
||||
this.description,
|
||||
this.isAiring,
|
||||
this.broadcastDay,
|
||||
);
|
||||
|
||||
/// The title of the anime.
|
||||
@ -24,10 +22,4 @@ class SearchResult {
|
||||
|
||||
/// The description of the anime
|
||||
final String description;
|
||||
|
||||
/// Flag whether the anime is airing.
|
||||
final bool isAiring;
|
||||
|
||||
/// The day of the week the anime is airing.
|
||||
final String? broadcastDay;
|
||||
}
|
||||
|
@ -10,46 +10,62 @@ enum TrackingMediumType {
|
||||
/// The state of the medium we're tracking, i.e. reading/watching, dropped, ...
|
||||
enum MediumTrackingState {
|
||||
/// Currently watching or reading
|
||||
ongoing(0),
|
||||
ongoing,
|
||||
|
||||
/// Done
|
||||
completed(1),
|
||||
completed,
|
||||
|
||||
/// Plan to watch or read
|
||||
planned(2),
|
||||
planned,
|
||||
|
||||
/// Dropped
|
||||
dropped(3),
|
||||
dropped,
|
||||
|
||||
/// Paused
|
||||
paused(4),
|
||||
paused,
|
||||
|
||||
/// Meta state
|
||||
all(-1);
|
||||
all,
|
||||
}
|
||||
|
||||
const MediumTrackingState(this.id);
|
||||
/// Interface for the Anime and Manga data classes
|
||||
abstract class TrackingMedium {
|
||||
/// The ID of the medium
|
||||
final String id = '';
|
||||
|
||||
factory MediumTrackingState.fromInt(int id) {
|
||||
switch (id) {
|
||||
case 0:
|
||||
return MediumTrackingState.ongoing;
|
||||
case 1:
|
||||
return MediumTrackingState.completed;
|
||||
case 2:
|
||||
return MediumTrackingState.planned;
|
||||
case 3:
|
||||
return MediumTrackingState.dropped;
|
||||
case 4:
|
||||
return MediumTrackingState.paused;
|
||||
/// The title of the medium
|
||||
final String title = '';
|
||||
|
||||
/// The URL of the cover image.
|
||||
final String thumbnailUrl = '';
|
||||
|
||||
/// The tracking state
|
||||
final MediumTrackingState state = MediumTrackingState.planned;
|
||||
}
|
||||
|
||||
extension MediumStateExtension on MediumTrackingState {
|
||||
int toInteger() {
|
||||
assert(
|
||||
this != MediumTrackingState.all,
|
||||
'MediumTrackingState.all must not be serialized',
|
||||
);
|
||||
switch (this) {
|
||||
case MediumTrackingState.ongoing:
|
||||
return 0;
|
||||
case MediumTrackingState.completed:
|
||||
return 1;
|
||||
case MediumTrackingState.planned:
|
||||
return 2;
|
||||
case MediumTrackingState.dropped:
|
||||
return 3;
|
||||
case MediumTrackingState.paused:
|
||||
return 4;
|
||||
case MediumTrackingState.all:
|
||||
return -1;
|
||||
}
|
||||
|
||||
return MediumTrackingState.planned;
|
||||
}
|
||||
|
||||
/// The id of the value.
|
||||
final int id;
|
||||
|
||||
String getName(TrackingMediumType type) {
|
||||
String toNameString(TrackingMediumType type) {
|
||||
assert(
|
||||
this != MediumTrackingState.all,
|
||||
'MediumTrackingState.all must not be stringified',
|
||||
@ -82,28 +98,28 @@ enum MediumTrackingState {
|
||||
}
|
||||
}
|
||||
|
||||
/// Interface for the Anime and Manga data classes
|
||||
abstract class TrackingMedium {
|
||||
/// The ID of the medium
|
||||
final String id = '';
|
||||
|
||||
/// The title of the medium
|
||||
final String title = '';
|
||||
|
||||
/// The URL of the cover image.
|
||||
final String thumbnailUrl = '';
|
||||
|
||||
/// The tracking state
|
||||
final MediumTrackingState state = MediumTrackingState.planned;
|
||||
}
|
||||
|
||||
class MediumTrackingStateConverter
|
||||
implements JsonConverter<MediumTrackingState, int> {
|
||||
const MediumTrackingStateConverter();
|
||||
|
||||
@override
|
||||
MediumTrackingState fromJson(int json) => MediumTrackingState.fromInt(json);
|
||||
MediumTrackingState fromJson(int json) {
|
||||
switch (json) {
|
||||
case 0:
|
||||
return MediumTrackingState.ongoing;
|
||||
case 1:
|
||||
return MediumTrackingState.completed;
|
||||
case 2:
|
||||
return MediumTrackingState.planned;
|
||||
case 3:
|
||||
return MediumTrackingState.dropped;
|
||||
case 4:
|
||||
return MediumTrackingState.paused;
|
||||
}
|
||||
|
||||
return MediumTrackingState.planned;
|
||||
}
|
||||
|
||||
@override
|
||||
int toJson(MediumTrackingState state) => state.id;
|
||||
int toJson(MediumTrackingState state) => state.toInteger();
|
||||
}
|
||||
|
@ -1,37 +1,22 @@
|
||||
import 'package:anitrack/src/data/anime.dart';
|
||||
import 'package:anitrack/src/data/manga.dart';
|
||||
import 'package:anitrack/src/service/migrations/0000_airing.dart';
|
||||
import 'package:anitrack/src/service/migrations/0000_score.dart';
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
|
||||
const animeTable = 'Anime';
|
||||
const mangaTable = 'Manga';
|
||||
|
||||
extension BoolToInt on bool {
|
||||
int toInt() {
|
||||
return this ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
extension IntToBool on int {
|
||||
bool toBool() {
|
||||
return this == 1;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _createDatabase(Database db, int version) async {
|
||||
await db.execute(
|
||||
'''
|
||||
CREATE TABLE $animeTable(
|
||||
id TEXT NOT NULL PRIMARY KEY,
|
||||
state INTEGER NOT NULL,
|
||||
episodesTotal INTEGER,
|
||||
id TEXT NOT NULL PRIMARY KEY,
|
||||
state INTEGER NOT NULL,
|
||||
episodesTotal INTEGER,
|
||||
episodesWatched INTEGER NOT NULL,
|
||||
thumbnailUrl TEXT NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
score INTEGER,
|
||||
airing INTEGER NOT NULL,
|
||||
broadcastDay TEXT
|
||||
thumbnailUrl TEXT NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
score INTEGER
|
||||
)''',
|
||||
);
|
||||
await db.execute(
|
||||
@ -55,7 +40,7 @@ class DatabaseService {
|
||||
Future<void> initialize() async {
|
||||
_db = await openDatabase(
|
||||
'anitrack.db',
|
||||
version: 3,
|
||||
version: 2,
|
||||
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.
|
||||
@ -71,9 +56,6 @@ class DatabaseService {
|
||||
if (oldVersion < 2) {
|
||||
await migrateFromV1ToV2(db);
|
||||
}
|
||||
if (oldVersion < 3) {
|
||||
await migrateFromV2ToV3(db);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -1,11 +0,0 @@
|
||||
import 'package:anitrack/src/service/database.dart';
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
|
||||
Future<void> migrateFromV2ToV3(Database db) async {
|
||||
await db.execute(
|
||||
'ALTER TABLE $animeTable ADD COLUMN airing INTEGER NOT NULL DEFAULT ${true.toInt()};',
|
||||
);
|
||||
await db.execute(
|
||||
'ALTER TABLE $animeTable ADD COLUMN broadcastDay TEXT;',
|
||||
);
|
||||
}
|
@ -3,7 +3,6 @@ import 'package:anitrack/src/data/manga.dart';
|
||||
import 'package:anitrack/src/data/type.dart';
|
||||
import 'package:anitrack/src/service/database.dart';
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
|
||||
@ -36,8 +35,6 @@ class AnimeListBloc extends Bloc<AnimeListEvent, AnimeListState> {
|
||||
final List<MangaTrackingData> _mangas =
|
||||
List<MangaTrackingData>.empty(growable: true);
|
||||
|
||||
List<AnimeTrackingData> get unfilteredAnime => _animes;
|
||||
|
||||
List<AnimeTrackingData> _getFilteredAnime({
|
||||
MediumTrackingState? trackingState,
|
||||
}) {
|
||||
@ -66,16 +63,7 @@ class AnimeListBloc extends Bloc<AnimeListEvent, AnimeListState> {
|
||||
await GetIt.I.get<DatabaseService>().addAnime(event.data);
|
||||
|
||||
// Add it to the cache
|
||||
if (event.checkIfExists) {
|
||||
final shouldAdd =
|
||||
_animes.firstWhereOrNull((element) => element.id == event.data.id) ==
|
||||
null;
|
||||
if (shouldAdd) {
|
||||
_animes.add(event.data);
|
||||
}
|
||||
} else {
|
||||
_animes.add(event.data);
|
||||
}
|
||||
_animes.add(event.data);
|
||||
|
||||
emit(
|
||||
state.copyWith(
|
||||
@ -92,17 +80,7 @@ class AnimeListBloc extends Bloc<AnimeListEvent, AnimeListState> {
|
||||
await GetIt.I.get<DatabaseService>().addManga(event.data);
|
||||
|
||||
// Add it to the cache
|
||||
// Add it to the cache
|
||||
if (event.checkIfExists) {
|
||||
final shouldAdd =
|
||||
_mangas.firstWhereOrNull((element) => element.id == event.data.id) ==
|
||||
null;
|
||||
if (shouldAdd) {
|
||||
_mangas.add(event.data);
|
||||
}
|
||||
} else {
|
||||
_mangas.add(event.data);
|
||||
}
|
||||
_mangas.add(event.data);
|
||||
|
||||
emit(
|
||||
state.copyWith(
|
||||
@ -292,8 +270,6 @@ class AnimeListBloc extends Bloc<AnimeListEvent, AnimeListState> {
|
||||
animes: _getFilteredAnime(),
|
||||
),
|
||||
);
|
||||
|
||||
await GetIt.I.get<DatabaseService>().updateAnime(event.anime);
|
||||
}
|
||||
|
||||
Future<void> _onMangaUpdated(
|
||||
|
@ -17,14 +17,10 @@ class AnimeEpisodeDecrementedEvent extends AnimeListEvent {
|
||||
}
|
||||
|
||||
class AnimeAddedEvent extends AnimeListEvent {
|
||||
AnimeAddedEvent(this.data, {this.checkIfExists = false});
|
||||
AnimeAddedEvent(this.data);
|
||||
|
||||
/// The anime to add.
|
||||
final AnimeTrackingData data;
|
||||
|
||||
/// If true, checks if the anime with the id is already in the list.
|
||||
/// If it is, does nothing.
|
||||
final bool checkIfExists;
|
||||
}
|
||||
|
||||
/// Triggered when animes are to be loaded from the database
|
||||
@ -47,12 +43,9 @@ class AnimeTrackingTypeChanged extends AnimeListEvent {
|
||||
}
|
||||
|
||||
class AnimeUpdatedEvent extends AnimeListEvent {
|
||||
AnimeUpdatedEvent(this.anime, {this.commit = false});
|
||||
AnimeUpdatedEvent(this.anime);
|
||||
|
||||
final AnimeTrackingData anime;
|
||||
|
||||
/// Commit the new anime data to the database.
|
||||
final bool commit;
|
||||
}
|
||||
|
||||
class AnimeRemovedEvent extends AnimeListEvent {
|
||||
@ -63,14 +56,10 @@ class AnimeRemovedEvent extends AnimeListEvent {
|
||||
}
|
||||
|
||||
class MangaAddedEvent extends AnimeListEvent {
|
||||
MangaAddedEvent(this.data, {this.checkIfExists = false});
|
||||
MangaAddedEvent(this.data);
|
||||
|
||||
/// The manga to add.
|
||||
final MangaTrackingData data;
|
||||
|
||||
/// If true, checks if the manga with the id is already in the list.
|
||||
/// If it is, does nothing.
|
||||
final bool checkIfExists;
|
||||
}
|
||||
|
||||
/// Triggered when the manga filter is changed
|
||||
|
@ -82,8 +82,6 @@ class AnimeSearchBloc extends Bloc<AnimeSearchEvent, AnimeSearchState> {
|
||||
anime.episodes,
|
||||
anime.imageUrl,
|
||||
anime.synopsis ?? '',
|
||||
anime.airing,
|
||||
anime.broadcast?.split(' ').first,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
@ -106,9 +104,6 @@ class AnimeSearchBloc extends Bloc<AnimeSearchEvent, AnimeSearchState> {
|
||||
manga.chapters,
|
||||
manga.imageUrl,
|
||||
manga.synopsis ?? '',
|
||||
// TODO(Unknown): Implement for Manga
|
||||
false,
|
||||
null,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
@ -131,8 +126,6 @@ class AnimeSearchBloc extends Bloc<AnimeSearchEvent, AnimeSearchState> {
|
||||
0,
|
||||
event.result.total,
|
||||
event.result.thumbnailUrl,
|
||||
event.result.isAiring,
|
||||
event.result.broadcastDay,
|
||||
),
|
||||
)
|
||||
: list.MangaAddedEvent(
|
||||
|
@ -1,78 +0,0 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:anitrack/src/ui/bloc/anime_list_bloc.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 'calendar_state.dart';
|
||||
part 'calendar_bloc.freezed.dart';
|
||||
part 'calendar_event.dart';
|
||||
|
||||
class CalendarBloc extends Bloc<CalendarEvent, CalendarState> {
|
||||
CalendarBloc() : super(CalendarState(false, 0, 0)) {
|
||||
on<RefreshPerformedEvent>(_onRefreshPerformed);
|
||||
}
|
||||
|
||||
Future<void> _onRefreshPerformed(
|
||||
RefreshPerformedEvent event,
|
||||
Emitter<CalendarState> emit,
|
||||
) async {
|
||||
emit(
|
||||
state.copyWith(
|
||||
refreshing: true,
|
||||
refreshingCount: 0,
|
||||
refreshingTotal: 0,
|
||||
),
|
||||
);
|
||||
|
||||
final al = GetIt.I.get<AnimeListBloc>();
|
||||
final animes = al.unfilteredAnime.where((anime) => anime.airing);
|
||||
emit(
|
||||
state.copyWith(
|
||||
refreshing: true,
|
||||
refreshingCount: 0,
|
||||
refreshingTotal: animes.length,
|
||||
),
|
||||
);
|
||||
|
||||
for (final anime in animes) {
|
||||
emit(state.copyWith(refreshingCount: state.refreshingCount + 1));
|
||||
|
||||
String? broadcastDay;
|
||||
bool airing;
|
||||
try {
|
||||
final apiData = await Jikan().getAnime(int.parse(anime.id));
|
||||
airing = apiData.airing;
|
||||
broadcastDay = apiData.broadcast?.split(' ').first;
|
||||
} catch (ex) {
|
||||
print('API request for anime ${anime.id} failed: $ex');
|
||||
airing = false;
|
||||
}
|
||||
|
||||
print('Anime "${anime.title}": airing=${airing}');
|
||||
if (!airing) {
|
||||
al.add(
|
||||
AnimeUpdatedEvent(
|
||||
anime.copyWith(airing: false, broadcastDay: null),
|
||||
commit: true,
|
||||
),
|
||||
);
|
||||
} else if (anime.broadcastDay != broadcastDay) {
|
||||
print('Updating Anime "${anime.title}": broadcastDay=$broadcastDay');
|
||||
al.add(
|
||||
AnimeUpdatedEvent(
|
||||
anime.copyWith(airing: true, broadcastDay: broadcastDay),
|
||||
commit: true,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Prevent hammering Jikan
|
||||
await Future<void>.delayed(const Duration(milliseconds: 500));
|
||||
}
|
||||
|
||||
emit(state.copyWith(refreshing: false));
|
||||
}
|
||||
}
|
@ -1,169 +0,0 @@
|
||||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// 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
|
||||
|
||||
part of 'calendar_bloc.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
final _privateConstructorUsedError = UnsupportedError(
|
||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
|
||||
|
||||
/// @nodoc
|
||||
mixin _$CalendarState {
|
||||
bool get refreshing => throw _privateConstructorUsedError;
|
||||
int get refreshingCount => throw _privateConstructorUsedError;
|
||||
int get refreshingTotal => throw _privateConstructorUsedError;
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
$CalendarStateCopyWith<CalendarState> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $CalendarStateCopyWith<$Res> {
|
||||
factory $CalendarStateCopyWith(
|
||||
CalendarState value, $Res Function(CalendarState) then) =
|
||||
_$CalendarStateCopyWithImpl<$Res>;
|
||||
$Res call({bool refreshing, int refreshingCount, int refreshingTotal});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$CalendarStateCopyWithImpl<$Res>
|
||||
implements $CalendarStateCopyWith<$Res> {
|
||||
_$CalendarStateCopyWithImpl(this._value, this._then);
|
||||
|
||||
final CalendarState _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function(CalendarState) _then;
|
||||
|
||||
@override
|
||||
$Res call({
|
||||
Object? refreshing = freezed,
|
||||
Object? refreshingCount = freezed,
|
||||
Object? refreshingTotal = freezed,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
refreshing: refreshing == freezed
|
||||
? _value.refreshing
|
||||
: refreshing // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
refreshingCount: refreshingCount == freezed
|
||||
? _value.refreshingCount
|
||||
: refreshingCount // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
refreshingTotal: refreshingTotal == freezed
|
||||
? _value.refreshingTotal
|
||||
: refreshingTotal // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$_CalendarStateCopyWith<$Res>
|
||||
implements $CalendarStateCopyWith<$Res> {
|
||||
factory _$$_CalendarStateCopyWith(
|
||||
_$_CalendarState value, $Res Function(_$_CalendarState) then) =
|
||||
__$$_CalendarStateCopyWithImpl<$Res>;
|
||||
@override
|
||||
$Res call({bool refreshing, int refreshingCount, int refreshingTotal});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$_CalendarStateCopyWithImpl<$Res>
|
||||
extends _$CalendarStateCopyWithImpl<$Res>
|
||||
implements _$$_CalendarStateCopyWith<$Res> {
|
||||
__$$_CalendarStateCopyWithImpl(
|
||||
_$_CalendarState _value, $Res Function(_$_CalendarState) _then)
|
||||
: super(_value, (v) => _then(v as _$_CalendarState));
|
||||
|
||||
@override
|
||||
_$_CalendarState get _value => super._value as _$_CalendarState;
|
||||
|
||||
@override
|
||||
$Res call({
|
||||
Object? refreshing = freezed,
|
||||
Object? refreshingCount = freezed,
|
||||
Object? refreshingTotal = freezed,
|
||||
}) {
|
||||
return _then(_$_CalendarState(
|
||||
refreshing == freezed
|
||||
? _value.refreshing
|
||||
: refreshing // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
refreshingCount == freezed
|
||||
? _value.refreshingCount
|
||||
: refreshingCount // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
refreshingTotal == freezed
|
||||
? _value.refreshingTotal
|
||||
: refreshingTotal // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$_CalendarState implements _CalendarState {
|
||||
_$_CalendarState(this.refreshing, this.refreshingCount, this.refreshingTotal);
|
||||
|
||||
@override
|
||||
final bool refreshing;
|
||||
@override
|
||||
final int refreshingCount;
|
||||
@override
|
||||
final int refreshingTotal;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'CalendarState(refreshing: $refreshing, refreshingCount: $refreshingCount, refreshingTotal: $refreshingTotal)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(dynamic other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$_CalendarState &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other.refreshing, refreshing) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other.refreshingCount, refreshingCount) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other.refreshingTotal, refreshingTotal));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
runtimeType,
|
||||
const DeepCollectionEquality().hash(refreshing),
|
||||
const DeepCollectionEquality().hash(refreshingCount),
|
||||
const DeepCollectionEquality().hash(refreshingTotal));
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
_$$_CalendarStateCopyWith<_$_CalendarState> get copyWith =>
|
||||
__$$_CalendarStateCopyWithImpl<_$_CalendarState>(this, _$identity);
|
||||
}
|
||||
|
||||
abstract class _CalendarState implements CalendarState {
|
||||
factory _CalendarState(final bool refreshing, final int refreshingCount,
|
||||
final int refreshingTotal) = _$_CalendarState;
|
||||
|
||||
@override
|
||||
bool get refreshing;
|
||||
@override
|
||||
int get refreshingCount;
|
||||
@override
|
||||
int get refreshingTotal;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$_CalendarStateCopyWith<_$_CalendarState> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
part of 'calendar_bloc.dart';
|
||||
|
||||
abstract class CalendarEvent {}
|
||||
|
||||
/// Triggered by the UI when the user wants to refresh the airing anime list.
|
||||
class RefreshPerformedEvent extends CalendarEvent {}
|
@ -1,10 +0,0 @@
|
||||
part of 'calendar_bloc.dart';
|
||||
|
||||
@freezed
|
||||
class CalendarState with _$CalendarState {
|
||||
factory CalendarState(
|
||||
bool refreshing,
|
||||
int refreshingCount,
|
||||
int refreshingTotal,
|
||||
) = _CalendarState;
|
||||
}
|
@ -28,7 +28,6 @@ class DetailsBloc extends Bloc<DetailsEvent, DetailsState> {
|
||||
emit(
|
||||
state.copyWith(
|
||||
trackingType: TrackingMediumType.anime,
|
||||
heroImagePrefix: event.heroImagePrefix,
|
||||
data: event.anime,
|
||||
),
|
||||
);
|
||||
|
@ -17,7 +17,6 @@ final _privateConstructorUsedError = UnsupportedError(
|
||||
/// @nodoc
|
||||
mixin _$DetailsState {
|
||||
TrackingMedium? get data => throw _privateConstructorUsedError;
|
||||
String? get heroImagePrefix => throw _privateConstructorUsedError;
|
||||
TrackingMediumType get trackingType => throw _privateConstructorUsedError;
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@ -30,10 +29,7 @@ abstract class $DetailsStateCopyWith<$Res> {
|
||||
factory $DetailsStateCopyWith(
|
||||
DetailsState value, $Res Function(DetailsState) then) =
|
||||
_$DetailsStateCopyWithImpl<$Res>;
|
||||
$Res call(
|
||||
{TrackingMedium? data,
|
||||
String? heroImagePrefix,
|
||||
TrackingMediumType trackingType});
|
||||
$Res call({TrackingMedium? data, TrackingMediumType trackingType});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@ -47,7 +43,6 @@ class _$DetailsStateCopyWithImpl<$Res> implements $DetailsStateCopyWith<$Res> {
|
||||
@override
|
||||
$Res call({
|
||||
Object? data = freezed,
|
||||
Object? heroImagePrefix = freezed,
|
||||
Object? trackingType = freezed,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
@ -55,10 +50,6 @@ class _$DetailsStateCopyWithImpl<$Res> implements $DetailsStateCopyWith<$Res> {
|
||||
? _value.data
|
||||
: data // ignore: cast_nullable_to_non_nullable
|
||||
as TrackingMedium?,
|
||||
heroImagePrefix: heroImagePrefix == freezed
|
||||
? _value.heroImagePrefix
|
||||
: heroImagePrefix // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
trackingType: trackingType == freezed
|
||||
? _value.trackingType
|
||||
: trackingType // ignore: cast_nullable_to_non_nullable
|
||||
@ -74,10 +65,7 @@ abstract class _$$_DetailsStateCopyWith<$Res>
|
||||
_$_DetailsState value, $Res Function(_$_DetailsState) then) =
|
||||
__$$_DetailsStateCopyWithImpl<$Res>;
|
||||
@override
|
||||
$Res call(
|
||||
{TrackingMedium? data,
|
||||
String? heroImagePrefix,
|
||||
TrackingMediumType trackingType});
|
||||
$Res call({TrackingMedium? data, TrackingMediumType trackingType});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@ -94,7 +82,6 @@ class __$$_DetailsStateCopyWithImpl<$Res>
|
||||
@override
|
||||
$Res call({
|
||||
Object? data = freezed,
|
||||
Object? heroImagePrefix = freezed,
|
||||
Object? trackingType = freezed,
|
||||
}) {
|
||||
return _then(_$_DetailsState(
|
||||
@ -102,10 +89,6 @@ class __$$_DetailsStateCopyWithImpl<$Res>
|
||||
? _value.data
|
||||
: data // ignore: cast_nullable_to_non_nullable
|
||||
as TrackingMedium?,
|
||||
heroImagePrefix: heroImagePrefix == freezed
|
||||
? _value.heroImagePrefix
|
||||
: heroImagePrefix // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
trackingType: trackingType == freezed
|
||||
? _value.trackingType
|
||||
: trackingType // ignore: cast_nullable_to_non_nullable
|
||||
@ -117,22 +100,17 @@ class __$$_DetailsStateCopyWithImpl<$Res>
|
||||
/// @nodoc
|
||||
|
||||
class _$_DetailsState implements _DetailsState {
|
||||
_$_DetailsState(
|
||||
{this.data,
|
||||
this.heroImagePrefix,
|
||||
this.trackingType = TrackingMediumType.anime});
|
||||
_$_DetailsState({this.data, this.trackingType = TrackingMediumType.anime});
|
||||
|
||||
@override
|
||||
final TrackingMedium? data;
|
||||
@override
|
||||
final String? heroImagePrefix;
|
||||
@override
|
||||
@JsonKey()
|
||||
final TrackingMediumType trackingType;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'DetailsState(data: $data, heroImagePrefix: $heroImagePrefix, trackingType: $trackingType)';
|
||||
return 'DetailsState(data: $data, trackingType: $trackingType)';
|
||||
}
|
||||
|
||||
@override
|
||||
@ -141,8 +119,6 @@ class _$_DetailsState implements _DetailsState {
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$_DetailsState &&
|
||||
const DeepCollectionEquality().equals(other.data, data) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other.heroImagePrefix, heroImagePrefix) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other.trackingType, trackingType));
|
||||
}
|
||||
@ -151,7 +127,6 @@ class _$_DetailsState implements _DetailsState {
|
||||
int get hashCode => Object.hash(
|
||||
runtimeType,
|
||||
const DeepCollectionEquality().hash(data),
|
||||
const DeepCollectionEquality().hash(heroImagePrefix),
|
||||
const DeepCollectionEquality().hash(trackingType));
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@ -163,14 +138,11 @@ class _$_DetailsState implements _DetailsState {
|
||||
abstract class _DetailsState implements DetailsState {
|
||||
factory _DetailsState(
|
||||
{final TrackingMedium? data,
|
||||
final String? heroImagePrefix,
|
||||
final TrackingMediumType trackingType}) = _$_DetailsState;
|
||||
|
||||
@override
|
||||
TrackingMedium? get data;
|
||||
@override
|
||||
String? get heroImagePrefix;
|
||||
@override
|
||||
TrackingMediumType get trackingType;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
|
@ -3,15 +3,10 @@ part of 'details_bloc.dart';
|
||||
abstract class DetailsEvent {}
|
||||
|
||||
class AnimeDetailsRequestedEvent extends DetailsEvent {
|
||||
AnimeDetailsRequestedEvent(
|
||||
this.anime, {
|
||||
this.heroImagePrefix,
|
||||
});
|
||||
AnimeDetailsRequestedEvent(this.anime);
|
||||
|
||||
/// The anime to show details about
|
||||
final AnimeTrackingData anime;
|
||||
|
||||
final String? heroImagePrefix;
|
||||
}
|
||||
|
||||
class MangaDetailsRequestedEvent extends DetailsEvent {
|
||||
|
@ -4,7 +4,6 @@ part of 'details_bloc.dart';
|
||||
class DetailsState with _$DetailsState {
|
||||
factory DetailsState({
|
||||
TrackingMedium? data,
|
||||
String? heroImagePrefix,
|
||||
@Default(TrackingMediumType.anime) TrackingMediumType trackingType,
|
||||
}) = _DetailsState;
|
||||
}
|
||||
|
@ -1,25 +1,19 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
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/type.dart';
|
||||
import 'package:anitrack/src/service/database.dart';
|
||||
import 'package:anitrack/src/ui/bloc/anime_list_bloc.dart';
|
||||
import 'package:archive/archive.dart' as archive;
|
||||
import 'package:archive/archive_io.dart';
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:jikan_api/jikan_api.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:xml/xml.dart';
|
||||
|
||||
part 'settings_bloc.freezed.dart';
|
||||
part 'settings_event.dart';
|
||||
part 'settings_state.dart';
|
||||
part 'settings_event.dart';
|
||||
part 'settings_bloc.freezed.dart';
|
||||
|
||||
MediumTrackingState malStatusToTrackingState(String status) {
|
||||
switch (status) {
|
||||
@ -45,8 +39,6 @@ class SettingsBloc extends Bloc<SettingsEvent, SettingsState> {
|
||||
SettingsBloc() : super(SettingsState()) {
|
||||
on<AnimeListImportedEvent>(_onAnimeListImported);
|
||||
on<MangaListImportedEvent>(_onMangaListImported);
|
||||
on<DataExportedEvent>(_onDataExported);
|
||||
on<DataImportedEvent>(_onDataImported);
|
||||
}
|
||||
|
||||
void _showLoadingSpinner(Emitter<SettingsState> emit) {
|
||||
@ -127,9 +119,6 @@ class SettingsBloc extends Bloc<SettingsEvent, SettingsState> {
|
||||
// 0 means that MAL does not know
|
||||
totalEpisodes == 0 ? null : totalEpisodes,
|
||||
data.imageUrl,
|
||||
// NOTE: When the calendar gets refreshed, this should also get cleared
|
||||
true,
|
||||
null,
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -213,69 +202,4 @@ class SettingsBloc extends Bloc<SettingsEvent, SettingsState> {
|
||||
// Hide the spinner again
|
||||
_hideLoadingSpinner(emit);
|
||||
}
|
||||
|
||||
Future<void> _onDataExported(
|
||||
DataExportedEvent event,
|
||||
Emitter<SettingsState> emit,
|
||||
) async {
|
||||
final al = GetIt.I.get<AnimeListBloc>();
|
||||
final data = {
|
||||
// TODO(Unknown): Track the version here to (maybe) to migrations
|
||||
'animes': al.state.animes.map((anime) => anime.toJson()).toList(),
|
||||
'mangas': al.state.mangas.map((manga) => manga.toJson()).toList(),
|
||||
};
|
||||
final exportData = jsonEncode(data);
|
||||
final date = DateTime.now();
|
||||
final outputPath = path.join(
|
||||
event.path,
|
||||
'anitrack_${date.year}${date.month}${date.day}.json.gz',
|
||||
);
|
||||
archive.GZipEncoder().encode(
|
||||
InputStream(utf8.encode(exportData)),
|
||||
output: OutputFileStream(outputPath),
|
||||
);
|
||||
|
||||
await Fluttertoast.showToast(
|
||||
msg: t.settings.dataExportSuccess,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onDataImported(
|
||||
DataImportedEvent event,
|
||||
Emitter<SettingsState> emit,
|
||||
) async {
|
||||
final al = GetIt.I.get<AnimeListBloc>();
|
||||
final exportArchive = archive.GZipDecoder().decodeBytes(
|
||||
await File(event.path).readAsBytes(),
|
||||
);
|
||||
final json = jsonDecode(utf8.decode(exportArchive)) as Map<String, dynamic>;
|
||||
|
||||
// Process anime
|
||||
for (final animeRaw
|
||||
in (json['animes']! as List<dynamic>).cast<Map<dynamic, dynamic>>()) {
|
||||
final anime = AnimeTrackingData.fromJson(
|
||||
animeRaw.cast<String, dynamic>(),
|
||||
);
|
||||
|
||||
al.add(
|
||||
AnimeAddedEvent(anime, checkIfExists: true),
|
||||
);
|
||||
}
|
||||
|
||||
// Process manga
|
||||
for (final mangaRaw
|
||||
in (json['mangas']! as List<dynamic>).cast<Map<dynamic, dynamic>>()) {
|
||||
final manga = MangaTrackingData.fromJson(
|
||||
mangaRaw.cast<String, dynamic>(),
|
||||
);
|
||||
|
||||
al.add(
|
||||
MangaAddedEvent(manga, checkIfExists: true),
|
||||
);
|
||||
}
|
||||
|
||||
await Fluttertoast.showToast(
|
||||
msg: t.settings.dataImportSuccess,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -34,19 +34,3 @@ class MangaListImportedEvent extends SettingsEvent {
|
||||
/// The type of list we're importing
|
||||
final ImportListType type;
|
||||
}
|
||||
|
||||
/// Triggered when a data export should be produced.
|
||||
class DataExportedEvent extends SettingsEvent {
|
||||
DataExportedEvent(this.path);
|
||||
|
||||
/// The path where the export should be stored.
|
||||
final String path;
|
||||
}
|
||||
|
||||
/// Triggered when a data export has been picked for import.
|
||||
class DataImportedEvent extends SettingsEvent {
|
||||
DataImportedEvent(this.path);
|
||||
|
||||
/// The path of the data export to import.
|
||||
final String path;
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
const animeListRoute = '/anime/list';
|
||||
const animeSearchRoute = '/anime/search';
|
||||
const detailsRoute = '/anime/details';
|
||||
const calendarRoute = '/calendar';
|
||||
const aboutRoute = '/about';
|
||||
const settingsRoute = '/settings';
|
||||
|
@ -1,58 +0,0 @@
|
||||
import 'package:anitrack/i18n/strings.g.dart';
|
||||
import 'package:anitrack/src/ui/constants.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
Widget getDrawer(BuildContext context) {
|
||||
return Drawer(
|
||||
child: ListView(
|
||||
children: [
|
||||
const DrawerHeader(
|
||||
decoration: BoxDecoration(
|
||||
color: Color(0xffcf4aff),
|
||||
),
|
||||
child: Text(
|
||||
'AniTrack',
|
||||
style: TextStyle(
|
||||
color: Color(0xff232323),
|
||||
fontSize: 24,
|
||||
),
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.list),
|
||||
title: Text(t.content.list),
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamedAndRemoveUntil(
|
||||
animeListRoute,
|
||||
(_) => false,
|
||||
);
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.calendar_today),
|
||||
title: Text(t.calendar.calendar),
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamedAndRemoveUntil(
|
||||
calendarRoute,
|
||||
(_) => false,
|
||||
);
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.settings),
|
||||
title: Text(t.settings.title),
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamed(settingsRoute);
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.info),
|
||||
title: Text(t.about.title),
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamed(aboutRoute);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
@ -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';
|
||||
@ -72,23 +71,23 @@ class AnimeListPageState extends State<AnimeListPage> {
|
||||
return [
|
||||
PopupMenuItem<MediumTrackingState>(
|
||||
value: MediumTrackingState.ongoing,
|
||||
child: Text(MediumTrackingState.ongoing.getName(type)),
|
||||
child: Text(MediumTrackingState.ongoing.toNameString(type)),
|
||||
),
|
||||
PopupMenuItem<MediumTrackingState>(
|
||||
value: MediumTrackingState.completed,
|
||||
child: Text(MediumTrackingState.completed.getName(type)),
|
||||
child: Text(MediumTrackingState.completed.toNameString(type)),
|
||||
),
|
||||
PopupMenuItem<MediumTrackingState>(
|
||||
value: MediumTrackingState.planned,
|
||||
child: Text(MediumTrackingState.planned.getName(type)),
|
||||
child: Text(MediumTrackingState.planned.toNameString(type)),
|
||||
),
|
||||
PopupMenuItem<MediumTrackingState>(
|
||||
value: MediumTrackingState.dropped,
|
||||
child: Text(MediumTrackingState.dropped.getName(type)),
|
||||
child: Text(MediumTrackingState.dropped.toNameString(type)),
|
||||
),
|
||||
PopupMenuItem<MediumTrackingState>(
|
||||
value: MediumTrackingState.paused,
|
||||
child: Text(MediumTrackingState.paused.getName(type)),
|
||||
child: Text(MediumTrackingState.paused.toNameString(type)),
|
||||
),
|
||||
const PopupMenuItem<MediumTrackingState>(
|
||||
value: MediumTrackingState.all,
|
||||
@ -141,7 +140,38 @@ class AnimeListPageState extends State<AnimeListPage> {
|
||||
_getPopupButton(context, state),
|
||||
],
|
||||
),
|
||||
drawer: getDrawer(context),
|
||||
drawer: Drawer(
|
||||
child: ListView(
|
||||
children: [
|
||||
const DrawerHeader(
|
||||
decoration: BoxDecoration(
|
||||
color: Color(0xffcf4aff),
|
||||
),
|
||||
child: Text(
|
||||
'AniTrack',
|
||||
style: TextStyle(
|
||||
color: Color(0xff232323),
|
||||
fontSize: 24,
|
||||
),
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.settings),
|
||||
title: Text(t.settings.title),
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamed(settingsRoute);
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.info),
|
||||
title: Text(t.about.title),
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamed(aboutRoute);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
body: PageView(
|
||||
// Prevent swiping between pages
|
||||
// (https://github.com/flutter/flutter/issues/37510#issuecomment-612663656)
|
||||
|
@ -1,317 +0,0 @@
|
||||
import 'package:anitrack/i18n/strings.g.dart';
|
||||
import 'package:anitrack/src/data/anime.dart';
|
||||
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';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
|
||||
enum Weekday {
|
||||
monday,
|
||||
tuesday,
|
||||
wednesday,
|
||||
thursday,
|
||||
friday,
|
||||
saturday,
|
||||
sunday,
|
||||
unknown;
|
||||
|
||||
String toName() {
|
||||
switch (this) {
|
||||
case Weekday.monday:
|
||||
return t.calendar.days.monday;
|
||||
case Weekday.tuesday:
|
||||
return t.calendar.days.tuesday;
|
||||
case Weekday.wednesday:
|
||||
return t.calendar.days.wednesday;
|
||||
case Weekday.thursday:
|
||||
return t.calendar.days.thursday;
|
||||
case Weekday.friday:
|
||||
return t.calendar.days.friday;
|
||||
case Weekday.saturday:
|
||||
return t.calendar.days.saturday;
|
||||
case Weekday.sunday:
|
||||
return t.calendar.days.sunday;
|
||||
case Weekday.unknown:
|
||||
return t.calendar.days.unknown;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension AddIfExists<K, V> on Map<K, List<V>> {
|
||||
void addOrSet(K key, V value) {
|
||||
if (containsKey(key)) {
|
||||
this[key]!.add(value);
|
||||
} else {
|
||||
this[key] = List<V>.from([value]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CalendarPage extends StatefulWidget {
|
||||
const CalendarPage({super.key});
|
||||
|
||||
static MaterialPageRoute<dynamic> get route => MaterialPageRoute<dynamic>(
|
||||
builder: (_) => const CalendarPage(),
|
||||
settings: const RouteSettings(
|
||||
name: calendarRoute,
|
||||
),
|
||||
);
|
||||
|
||||
@override
|
||||
CalendarPageState createState() => CalendarPageState();
|
||||
}
|
||||
|
||||
class CalendarPageState extends State<CalendarPage> {
|
||||
List<Widget> _renderWeekdayList(
|
||||
BuildContext context,
|
||||
Weekday day,
|
||||
Map<Weekday, List<AnimeTrackingData>> data,
|
||||
) {
|
||||
if (!data.containsKey(day)) {
|
||||
return const [];
|
||||
}
|
||||
|
||||
assert(data[day]!.isNotEmpty, 'There should be at least one anime');
|
||||
return [
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
right: 16,
|
||||
top: 20,
|
||||
bottom: 4,
|
||||
),
|
||||
child: Text(
|
||||
day.toName(),
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
),
|
||||
),
|
||||
SliverGrid.builder(
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 3,
|
||||
mainAxisSpacing: 8,
|
||||
crossAxisSpacing: 8,
|
||||
childAspectRatio: 120 / (100 * (16 / 9)),
|
||||
),
|
||||
itemCount: data[day]!.length,
|
||||
itemBuilder: (context, index) {
|
||||
final anime = data[day]![index];
|
||||
return GridItem(
|
||||
child: AnimeCoverImage(
|
||||
url: anime.thumbnailUrl,
|
||||
hero: 'calendar_${anime.id}',
|
||||
onTap: () {
|
||||
context.read<DetailsBloc>().add(
|
||||
AnimeDetailsRequestedEvent(
|
||||
anime,
|
||||
heroImagePrefix: 'calendar_',
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final airingAnimeMap = <Weekday, List<AnimeTrackingData>>{};
|
||||
for (final anime in GetIt.I.get<AnimeListBloc>().unfilteredAnime) {
|
||||
if (!anime.airing) continue;
|
||||
|
||||
final Weekday day;
|
||||
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);
|
||||
}
|
||||
|
||||
return BlocListener<CalendarBloc, CalendarState>(
|
||||
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: 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(
|
||||
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(
|
||||
child: SizedBox(
|
||||
width: 150,
|
||||
height: 150,
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
color: Colors.grey.shade800,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Padding(
|
||||
padding: EdgeInsets.all(25),
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
Text(
|
||||
t.settings.importIndicator(
|
||||
current: state.refreshingCount,
|
||||
total: state.refreshingTotal,
|
||||
),
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -42,7 +42,7 @@ class DetailsPage extends StatelessWidget {
|
||||
children: [
|
||||
AnimeCoverImage(
|
||||
url: state.data!.thumbnailUrl,
|
||||
hero: '${state.heroImagePrefix}${state.data!.id}',
|
||||
hero: state.data!.id,
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
@ -69,14 +69,10 @@ class DetailsPage extends StatelessWidget {
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: Text(
|
||||
t.details.removeTitle(
|
||||
title: state.data!.title,
|
||||
),
|
||||
t.details.removeTitle(title: state.data!.title),
|
||||
),
|
||||
content: Text(
|
||||
t.details.removeBody(
|
||||
title: state.data!.title,
|
||||
),
|
||||
t.details.removeBody(title: state.data!.title),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
@ -87,18 +83,14 @@ class DetailsPage extends StatelessWidget {
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: Colors.red,
|
||||
),
|
||||
child: Text(
|
||||
t.details.removeButton,
|
||||
),
|
||||
child: Text(t.details.removeButton),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context)
|
||||
.pop(false);
|
||||
},
|
||||
child: Text(
|
||||
t.details.cancelButton,
|
||||
),
|
||||
child: Text(t.details.cancelButton),
|
||||
),
|
||||
],
|
||||
);
|
||||
@ -158,27 +150,27 @@ class DetailsPage extends StatelessWidget {
|
||||
SelectorItem(
|
||||
MediumTrackingState.ongoing,
|
||||
MediumTrackingState.ongoing
|
||||
.getName(state.trackingType),
|
||||
.toNameString(state.trackingType),
|
||||
),
|
||||
SelectorItem(
|
||||
MediumTrackingState.completed,
|
||||
MediumTrackingState.completed
|
||||
.getName(state.trackingType),
|
||||
.toNameString(state.trackingType),
|
||||
),
|
||||
SelectorItem(
|
||||
MediumTrackingState.planned,
|
||||
MediumTrackingState.planned
|
||||
.getName(state.trackingType),
|
||||
.toNameString(state.trackingType),
|
||||
),
|
||||
SelectorItem(
|
||||
MediumTrackingState.dropped,
|
||||
MediumTrackingState.dropped
|
||||
.getName(state.trackingType),
|
||||
.toNameString(state.trackingType),
|
||||
),
|
||||
SelectorItem(
|
||||
MediumTrackingState.paused,
|
||||
MediumTrackingState.paused
|
||||
.getName(state.trackingType),
|
||||
.toNameString(state.trackingType),
|
||||
),
|
||||
],
|
||||
initialValue: state.data!.state,
|
||||
|
@ -5,7 +5,6 @@ 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:permission_handler/permission_handler.dart';
|
||||
|
||||
class SettingsPage extends StatelessWidget {
|
||||
const SettingsPage({super.key});
|
||||
@ -92,51 +91,6 @@ class SettingsPage extends StatelessWidget {
|
||||
);
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
title: Text(t.settings.exportData),
|
||||
onTap: () async {
|
||||
// Pick the file
|
||||
final result =
|
||||
await FilePicker.platform.getDirectoryPath();
|
||||
if (result == null) return;
|
||||
|
||||
if (!(await Permission.manageExternalStorage
|
||||
.request())
|
||||
.isGranted) return;
|
||||
|
||||
GetIt.I.get<SettingsBloc>().add(
|
||||
DataExportedEvent(
|
||||
result,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
title: Text(t.settings.importData),
|
||||
onTap: () async {
|
||||
// Pick the file
|
||||
final result = await FilePicker.platform.pickFiles();
|
||||
if (result == null) return;
|
||||
|
||||
if (!result.files.first.path!.endsWith('.json.gz')) {
|
||||
await showDialog<void>(
|
||||
context: context,
|
||||
builder: (_) => AlertDialog(
|
||||
title: Text(t.settings.importInvalidData.title),
|
||||
content:
|
||||
Text(t.settings.importInvalidData.content),
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
GetIt.I.get<SettingsBloc>().add(
|
||||
DataImportedEvent(
|
||||
result.files.first.path!,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -4,18 +4,15 @@ import 'package:flutter/material.dart';
|
||||
class GridItem extends StatefulWidget {
|
||||
const GridItem({
|
||||
required this.child,
|
||||
this.plusCallback,
|
||||
this.minusCallback,
|
||||
this.enableDrag = true,
|
||||
required this.plusCallback,
|
||||
required this.minusCallback,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final Widget child;
|
||||
|
||||
final bool enableDrag;
|
||||
|
||||
final void Function()? plusCallback;
|
||||
final void Function()? minusCallback;
|
||||
final void Function() plusCallback;
|
||||
final void Function() minusCallback;
|
||||
|
||||
@override
|
||||
GridItemState createState() => GridItemState();
|
||||
@ -29,20 +26,16 @@ class GridItemState extends State<GridItem> {
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onHorizontalDragUpdate: (details) {
|
||||
if (!widget.enableDrag) return;
|
||||
|
||||
setState(() {
|
||||
_offset += details.delta.dx;
|
||||
_translationX = 160 / (1 + exp(-1 * (1 / 30) * _offset)) - 80;
|
||||
});
|
||||
},
|
||||
onHorizontalDragEnd: (_) {
|
||||
if (!widget.enableDrag) return;
|
||||
|
||||
if (_translationX <= -40) {
|
||||
widget.plusCallback!();
|
||||
} else if (_translationX >= 40) {
|
||||
widget.minusCallback!();
|
||||
if (_translationX <= -60) {
|
||||
widget.plusCallback();
|
||||
} else if (_translationX >= 60) {
|
||||
widget.minusCallback();
|
||||
}
|
||||
|
||||
// Reset the view
|
||||
|
50
pubspec.lock
50
pubspec.lock
@ -360,14 +360,6 @@ packages:
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
fluttertoast:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: fluttertoast
|
||||
sha256: "474f7d506230897a3cd28c965ec21c5328ae5605fc9c400cd330e9e9d6ac175c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.2.2"
|
||||
freezed:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
@ -569,7 +561,7 @@ packages:
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
path:
|
||||
dependency: "direct main"
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path
|
||||
sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b
|
||||
@ -632,46 +624,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.11.1"
|
||||
permission_handler:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: permission_handler
|
||||
sha256: "63e5216aae014a72fe9579ccd027323395ce7a98271d9defa9d57320d001af81"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.4.3"
|
||||
permission_handler_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_android
|
||||
sha256: c0c9754479a4c4b1c1f3862ddc11930c9b3f03bef2816bb4ea6eed1e13551d6f
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.3.2"
|
||||
permission_handler_apple:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_apple
|
||||
sha256: "99e220bce3f8877c78e4ace901082fb29fa1b4ebde529ad0932d8d664b34f3f5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "9.1.4"
|
||||
permission_handler_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_platform_interface
|
||||
sha256: "7c6b1500385dd1d2ca61bb89e2488ca178e274a69144d26bbd65e33eae7c02a9"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.11.3"
|
||||
permission_handler_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_windows
|
||||
sha256: cc074aace208760f1eee6aa4fae766b45d947df85bc831cde77009cdb4720098
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.3"
|
||||
petitparser:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -2,7 +2,7 @@ name: anitrack
|
||||
description: An anime and manga tracker
|
||||
publish_to: 'none'
|
||||
|
||||
version: 0.1.3+2010
|
||||
version: 0.1.2+8
|
||||
|
||||
environment:
|
||||
sdk: '>=2.18.4 <3.0.0'
|
||||
@ -18,13 +18,10 @@ dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
flutter_bloc: ^8.1.1
|
||||
fluttertoast: ^8.2.2
|
||||
freezed_annotation: 2.1.0
|
||||
get_it: ^7.2.0
|
||||
jikan_api: ^2.0.0
|
||||
json_annotation: 4.6.0
|
||||
path: ^1.8.2
|
||||
permission_handler: ^10.4.3
|
||||
slang: 3.19.0
|
||||
slang_flutter: 3.19.0
|
||||
sqflite: ^2.2.4+1
|
||||
|
Loading…
Reference in New Issue
Block a user