chore(all): Format
This commit is contained in:
parent
67c1903bd1
commit
9dcc99ca36
File diff suppressed because it is too large
Load Diff
@ -31,9 +31,9 @@ void main() async {
|
|||||||
|
|
||||||
// Load animes
|
// Load animes
|
||||||
GetIt.I.get<AnimeListBloc>().add(
|
GetIt.I.get<AnimeListBloc>().add(
|
||||||
AnimesLoadedEvent(),
|
AnimesLoadedEvent(),
|
||||||
);
|
);
|
||||||
|
|
||||||
runApp(
|
runApp(
|
||||||
MultiBlocProvider(
|
MultiBlocProvider(
|
||||||
providers: [
|
providers: [
|
||||||
@ -62,7 +62,7 @@ class MyApp extends StatelessWidget {
|
|||||||
});
|
});
|
||||||
|
|
||||||
final GlobalKey<NavigatorState> navKey;
|
final GlobalKey<NavigatorState> navKey;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
@ -81,10 +81,14 @@ class MyApp extends StatelessWidget {
|
|||||||
onGenerateRoute: (settings) {
|
onGenerateRoute: (settings) {
|
||||||
switch (settings.name) {
|
switch (settings.name) {
|
||||||
case '/':
|
case '/':
|
||||||
case animeListRoute: return AnimeListPage.route;
|
case animeListRoute:
|
||||||
case animeSearchRoute: return AnimeSearchPage.route;
|
return AnimeListPage.route;
|
||||||
case detailsRoute: return DetailsPage.route;
|
case animeSearchRoute:
|
||||||
case aboutRoute: return AboutPage.route;
|
return AnimeSearchPage.route;
|
||||||
|
case detailsRoute:
|
||||||
|
return DetailsPage.route;
|
||||||
|
case aboutRoute:
|
||||||
|
return AboutPage.route;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
@ -10,18 +10,24 @@ class AnimeTrackingData with _$AnimeTrackingData, TrackingMedium {
|
|||||||
factory AnimeTrackingData(
|
factory AnimeTrackingData(
|
||||||
/// The ID of the anime
|
/// The ID of the anime
|
||||||
String id,
|
String id,
|
||||||
|
|
||||||
/// The state of the anime
|
/// The state of the anime
|
||||||
@MediumTrackingStateConverter() MediumTrackingState state,
|
@MediumTrackingStateConverter() MediumTrackingState state,
|
||||||
|
|
||||||
/// The title of the anime
|
/// The title of the anime
|
||||||
String title,
|
String title,
|
||||||
|
|
||||||
/// Episodes in total.
|
/// Episodes in total.
|
||||||
int episodesWatched,
|
int episodesWatched,
|
||||||
|
|
||||||
/// Episodes watched.
|
/// Episodes watched.
|
||||||
int? episodesTotal,
|
int? episodesTotal,
|
||||||
|
|
||||||
/// URL to the thumbnail/cover art for the anime.
|
/// URL to the thumbnail/cover art for the anime.
|
||||||
String thumbnailUrl,
|
String thumbnailUrl,
|
||||||
) = _AnimeTrackingData;
|
) = _AnimeTrackingData;
|
||||||
|
|
||||||
/// JSON
|
/// JSON
|
||||||
factory AnimeTrackingData.fromJson(Map<String, dynamic> json) => _$AnimeTrackingDataFromJson(json);
|
factory AnimeTrackingData.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$AnimeTrackingDataFromJson(json);
|
||||||
}
|
}
|
||||||
|
@ -10,20 +10,27 @@ class MangaTrackingData with _$MangaTrackingData, TrackingMedium {
|
|||||||
factory MangaTrackingData(
|
factory MangaTrackingData(
|
||||||
/// The ID of the manga
|
/// The ID of the manga
|
||||||
String id,
|
String id,
|
||||||
|
|
||||||
/// The state of the manga
|
/// The state of the manga
|
||||||
@MediumTrackingStateConverter() MediumTrackingState state,
|
@MediumTrackingStateConverter() MediumTrackingState state,
|
||||||
|
|
||||||
/// The title of the manga
|
/// The title of the manga
|
||||||
String title,
|
String title,
|
||||||
|
|
||||||
/// Chapters read.
|
/// Chapters read.
|
||||||
int chaptersRead,
|
int chaptersRead,
|
||||||
|
|
||||||
/// Chapters read.
|
/// Chapters read.
|
||||||
int volumesOwned,
|
int volumesOwned,
|
||||||
|
|
||||||
/// Episodes watched.
|
/// Episodes watched.
|
||||||
int? chaptersTotal,
|
int? chaptersTotal,
|
||||||
|
|
||||||
/// URL to the thumbnail/cover art for the manga.
|
/// URL to the thumbnail/cover art for the manga.
|
||||||
String thumbnailUrl,
|
String thumbnailUrl,
|
||||||
) = _MangaTrackingData;
|
) = _MangaTrackingData;
|
||||||
|
|
||||||
/// JSON
|
/// JSON
|
||||||
factory MangaTrackingData.fromJson(Map<String, dynamic> json) => _$MangaTrackingDataFromJson(json);
|
factory MangaTrackingData.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$MangaTrackingDataFromJson(json);
|
||||||
}
|
}
|
||||||
|
@ -32,52 +32,75 @@ abstract class TrackingMedium {
|
|||||||
|
|
||||||
extension MediumStateExtension on MediumTrackingState {
|
extension MediumStateExtension on MediumTrackingState {
|
||||||
int toInteger() {
|
int toInteger() {
|
||||||
assert(this != MediumTrackingState.all, 'MediumTrackingState.all must not be serialized');
|
assert(
|
||||||
|
this != MediumTrackingState.all,
|
||||||
|
'MediumTrackingState.all must not be serialized',
|
||||||
|
);
|
||||||
switch (this) {
|
switch (this) {
|
||||||
case MediumTrackingState.ongoing: return 0;
|
case MediumTrackingState.ongoing:
|
||||||
case MediumTrackingState.completed: return 1;
|
return 0;
|
||||||
case MediumTrackingState.planned: return 2;
|
case MediumTrackingState.completed:
|
||||||
case MediumTrackingState.dropped: return 3;
|
return 1;
|
||||||
case MediumTrackingState.all: return -1;
|
case MediumTrackingState.planned:
|
||||||
|
return 2;
|
||||||
|
case MediumTrackingState.dropped:
|
||||||
|
return 3;
|
||||||
|
case MediumTrackingState.all:
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String toNameString(TrackingMediumType type) {
|
String toNameString(TrackingMediumType type) {
|
||||||
assert(this != MediumTrackingState.all, 'MediumTrackingState.all must not be stringified');
|
assert(
|
||||||
|
this != MediumTrackingState.all,
|
||||||
|
'MediumTrackingState.all must not be stringified',
|
||||||
|
);
|
||||||
|
|
||||||
switch (this) {
|
switch (this) {
|
||||||
case MediumTrackingState.ongoing:
|
case MediumTrackingState.ongoing:
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case TrackingMediumType.anime: return 'Watching';
|
case TrackingMediumType.anime:
|
||||||
case TrackingMediumType.manga: return 'Reading';
|
return 'Watching';
|
||||||
|
case TrackingMediumType.manga:
|
||||||
|
return 'Reading';
|
||||||
}
|
}
|
||||||
case MediumTrackingState.completed: return 'Completed';
|
case MediumTrackingState.completed:
|
||||||
|
return 'Completed';
|
||||||
case MediumTrackingState.planned:
|
case MediumTrackingState.planned:
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case TrackingMediumType.anime: return 'Plan to watch';
|
case TrackingMediumType.anime:
|
||||||
case TrackingMediumType.manga: return 'Plan to read';
|
return 'Plan to watch';
|
||||||
|
case TrackingMediumType.manga:
|
||||||
|
return 'Plan to read';
|
||||||
}
|
}
|
||||||
case MediumTrackingState.dropped: return 'Dropped';
|
case MediumTrackingState.dropped:
|
||||||
case MediumTrackingState.all: return 'All';
|
return 'Dropped';
|
||||||
|
case MediumTrackingState.all:
|
||||||
|
return 'All';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class MediumTrackingStateConverter implements JsonConverter<MediumTrackingState, int> {
|
class MediumTrackingStateConverter
|
||||||
|
implements JsonConverter<MediumTrackingState, int> {
|
||||||
const MediumTrackingStateConverter();
|
const MediumTrackingStateConverter();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
MediumTrackingState fromJson(int json) {
|
MediumTrackingState fromJson(int json) {
|
||||||
switch (json) {
|
switch (json) {
|
||||||
case 0: return MediumTrackingState.ongoing;
|
case 0:
|
||||||
case 1: return MediumTrackingState.completed;
|
return MediumTrackingState.ongoing;
|
||||||
case 2: return MediumTrackingState.planned;
|
case 1:
|
||||||
case 3: return MediumTrackingState.dropped;
|
return MediumTrackingState.completed;
|
||||||
|
case 2:
|
||||||
|
return MediumTrackingState.planned;
|
||||||
|
case 3:
|
||||||
|
return MediumTrackingState.dropped;
|
||||||
}
|
}
|
||||||
|
|
||||||
return MediumTrackingState.planned;
|
return MediumTrackingState.planned;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int toJson(MediumTrackingState state) => state.toInteger();
|
int toJson(MediumTrackingState state) => state.toInteger();
|
||||||
}
|
}
|
||||||
|
@ -46,20 +46,20 @@ class DatabaseService {
|
|||||||
final animes = await _db.query(animeTable);
|
final animes = await _db.query(animeTable);
|
||||||
|
|
||||||
return animes
|
return animes
|
||||||
.cast<Map<String, dynamic>>()
|
.cast<Map<String, dynamic>>()
|
||||||
.map(AnimeTrackingData.fromJson)
|
.map(AnimeTrackingData.fromJson)
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<MangaTrackingData>> loadMangas() async {
|
Future<List<MangaTrackingData>> loadMangas() async {
|
||||||
final mangas = await _db.query(mangaTable);
|
final mangas = await _db.query(mangaTable);
|
||||||
|
|
||||||
return mangas
|
return mangas
|
||||||
.cast<Map<String, dynamic>>()
|
.cast<Map<String, dynamic>>()
|
||||||
.map(MangaTrackingData.fromJson)
|
.map(MangaTrackingData.fromJson)
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> addAnime(AnimeTrackingData data) async {
|
Future<void> addAnime(AnimeTrackingData data) async {
|
||||||
await _db.insert(
|
await _db.insert(
|
||||||
animeTable,
|
animeTable,
|
||||||
@ -83,7 +83,7 @@ class DatabaseService {
|
|||||||
whereArgs: [id],
|
whereArgs: [id],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> addManga(MangaTrackingData data) async {
|
Future<void> addManga(MangaTrackingData data) async {
|
||||||
await _db.insert(
|
await _db.insert(
|
||||||
mangaTable,
|
mangaTable,
|
||||||
|
@ -30,30 +30,35 @@ class AnimeListBloc extends Bloc<AnimeListEvent, AnimeListState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Internal anime state
|
/// Internal anime state
|
||||||
final List<AnimeTrackingData> _animes = List<AnimeTrackingData>.empty(growable: true);
|
final List<AnimeTrackingData> _animes =
|
||||||
final List<MangaTrackingData> _mangas = List<MangaTrackingData>.empty(growable: true);
|
List<AnimeTrackingData>.empty(growable: true);
|
||||||
|
final List<MangaTrackingData> _mangas =
|
||||||
|
List<MangaTrackingData>.empty(growable: true);
|
||||||
|
|
||||||
List<AnimeTrackingData> _getFilteredAnime({MediumTrackingState? trackingState}) {
|
List<AnimeTrackingData> _getFilteredAnime({
|
||||||
|
MediumTrackingState? trackingState,
|
||||||
|
}) {
|
||||||
final filterState = trackingState ?? state.animeFilterState;
|
final filterState = trackingState ?? state.animeFilterState;
|
||||||
|
|
||||||
if (filterState == MediumTrackingState.all) return _animes;
|
if (filterState == MediumTrackingState.all) return _animes;
|
||||||
|
|
||||||
return _animes
|
return _animes.where((anime) => anime.state == filterState).toList();
|
||||||
.where((anime) => anime.state == filterState)
|
|
||||||
.toList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
List<MangaTrackingData> _getFilteredManga({MediumTrackingState? trackingState}) {
|
List<MangaTrackingData> _getFilteredManga({
|
||||||
|
MediumTrackingState? trackingState,
|
||||||
|
}) {
|
||||||
final filterState = trackingState ?? state.mangaFilterState;
|
final filterState = trackingState ?? state.mangaFilterState;
|
||||||
|
|
||||||
if (state.mangaFilterState == MediumTrackingState.all) return _mangas;
|
if (state.mangaFilterState == MediumTrackingState.all) return _mangas;
|
||||||
|
|
||||||
return _mangas
|
return _mangas.where((manga) => manga.state == filterState).toList();
|
||||||
.where((manga) => manga.state == filterState)
|
|
||||||
.toList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onAnimeAdded(AnimeAddedEvent event, Emitter<AnimeListState> emit) async {
|
Future<void> _onAnimeAdded(
|
||||||
|
AnimeAddedEvent event,
|
||||||
|
Emitter<AnimeListState> emit,
|
||||||
|
) async {
|
||||||
// Add the anime to the database
|
// Add the anime to the database
|
||||||
await GetIt.I.get<DatabaseService>().addAnime(event.data);
|
await GetIt.I.get<DatabaseService>().addAnime(event.data);
|
||||||
|
|
||||||
@ -67,7 +72,10 @@ class AnimeListBloc extends Bloc<AnimeListEvent, AnimeListState> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onMangaAdded(MangaAddedEvent event, Emitter<AnimeListState> emit) async {
|
Future<void> _onMangaAdded(
|
||||||
|
MangaAddedEvent event,
|
||||||
|
Emitter<AnimeListState> emit,
|
||||||
|
) async {
|
||||||
// Add the manga to the database
|
// Add the manga to the database
|
||||||
await GetIt.I.get<DatabaseService>().addManga(event.data);
|
await GetIt.I.get<DatabaseService>().addManga(event.data);
|
||||||
|
|
||||||
@ -80,13 +88,17 @@ class AnimeListBloc extends Bloc<AnimeListEvent, AnimeListState> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onAnimeIncremented(AnimeEpisodeIncrementedEvent event, Emitter<AnimeListState> emit) async {
|
Future<void> _onAnimeIncremented(
|
||||||
|
AnimeEpisodeIncrementedEvent event,
|
||||||
|
Emitter<AnimeListState> emit,
|
||||||
|
) async {
|
||||||
final index = state.animes.indexWhere((item) => item.id == event.id);
|
final index = state.animes.indexWhere((item) => item.id == event.id);
|
||||||
if (index == -1) return;
|
if (index == -1) return;
|
||||||
|
|
||||||
final anime = state.animes[index];
|
final anime = state.animes[index];
|
||||||
if (anime.episodesTotal != null && anime.episodesWatched + 1 > anime.episodesTotal!) return;
|
if (anime.episodesTotal != null &&
|
||||||
|
anime.episodesWatched + 1 > anime.episodesTotal!) return;
|
||||||
|
|
||||||
final newList = List<AnimeTrackingData>.from(state.animes);
|
final newList = List<AnimeTrackingData>.from(state.animes);
|
||||||
final newAnime = anime.copyWith(
|
final newAnime = anime.copyWith(
|
||||||
@ -103,7 +115,10 @@ class AnimeListBloc extends Bloc<AnimeListEvent, AnimeListState> {
|
|||||||
await GetIt.I.get<DatabaseService>().updateAnime(newAnime);
|
await GetIt.I.get<DatabaseService>().updateAnime(newAnime);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onAnimeDecremented(AnimeEpisodeDecrementedEvent event, Emitter<AnimeListState> emit) async {
|
Future<void> _onAnimeDecremented(
|
||||||
|
AnimeEpisodeDecrementedEvent event,
|
||||||
|
Emitter<AnimeListState> emit,
|
||||||
|
) async {
|
||||||
final index = state.animes.indexWhere((item) => item.id == event.id);
|
final index = state.animes.indexWhere((item) => item.id == event.id);
|
||||||
if (index == -1) return;
|
if (index == -1) return;
|
||||||
|
|
||||||
@ -125,14 +140,17 @@ class AnimeListBloc extends Bloc<AnimeListEvent, AnimeListState> {
|
|||||||
await GetIt.I.get<DatabaseService>().updateAnime(newAnime);
|
await GetIt.I.get<DatabaseService>().updateAnime(newAnime);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onAnimesLoaded(AnimesLoadedEvent event, Emitter<AnimeListState> emit) async {
|
Future<void> _onAnimesLoaded(
|
||||||
|
AnimesLoadedEvent event,
|
||||||
|
Emitter<AnimeListState> emit,
|
||||||
|
) async {
|
||||||
_animes.addAll(
|
_animes.addAll(
|
||||||
await GetIt.I.get<DatabaseService>().loadAnimes(),
|
await GetIt.I.get<DatabaseService>().loadAnimes(),
|
||||||
);
|
);
|
||||||
_mangas.addAll(
|
_mangas.addAll(
|
||||||
await GetIt.I.get<DatabaseService>().loadMangas(),
|
await GetIt.I.get<DatabaseService>().loadMangas(),
|
||||||
);
|
);
|
||||||
|
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
animes: _getFilteredAnime(),
|
animes: _getFilteredAnime(),
|
||||||
@ -141,7 +159,10 @@ class AnimeListBloc extends Bloc<AnimeListEvent, AnimeListState> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onAnimesFiltered(AnimeFilterChangedEvent event, Emitter<AnimeListState> emit) async {
|
Future<void> _onAnimesFiltered(
|
||||||
|
AnimeFilterChangedEvent event,
|
||||||
|
Emitter<AnimeListState> emit,
|
||||||
|
) async {
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
animeFilterState: event.filterState,
|
animeFilterState: event.filterState,
|
||||||
@ -150,7 +171,10 @@ class AnimeListBloc extends Bloc<AnimeListEvent, AnimeListState> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onMangasFiltered(MangaFilterChangedEvent event, Emitter<AnimeListState> emit) async {
|
Future<void> _onMangasFiltered(
|
||||||
|
MangaFilterChangedEvent event,
|
||||||
|
Emitter<AnimeListState> emit,
|
||||||
|
) async {
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
mangaFilterState: event.filterState,
|
mangaFilterState: event.filterState,
|
||||||
@ -158,8 +182,11 @@ class AnimeListBloc extends Bloc<AnimeListEvent, AnimeListState> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onTrackingTypeChanged(AnimeTrackingTypeChanged event, Emitter<AnimeListState> emit) async {
|
Future<void> _onTrackingTypeChanged(
|
||||||
|
AnimeTrackingTypeChanged event,
|
||||||
|
Emitter<AnimeListState> emit,
|
||||||
|
) async {
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
trackingType: event.type,
|
trackingType: event.type,
|
||||||
@ -168,12 +195,16 @@ class AnimeListBloc extends Bloc<AnimeListEvent, AnimeListState> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onMangaIncremented(MangaChapterIncrementedEvent event, Emitter<AnimeListState> emit) async {
|
Future<void> _onMangaIncremented(
|
||||||
|
MangaChapterIncrementedEvent event,
|
||||||
|
Emitter<AnimeListState> emit,
|
||||||
|
) async {
|
||||||
final index = state.mangas.indexWhere((item) => item.id == event.id);
|
final index = state.mangas.indexWhere((item) => item.id == event.id);
|
||||||
assert(index != -1, 'The manga must exist');
|
assert(index != -1, 'The manga must exist');
|
||||||
|
|
||||||
final manga = state.mangas[index];
|
final manga = state.mangas[index];
|
||||||
if (manga.chaptersTotal != null && manga.chaptersRead + 1 > manga.chaptersTotal!) return;
|
if (manga.chaptersTotal != null &&
|
||||||
|
manga.chaptersRead + 1 > manga.chaptersTotal!) return;
|
||||||
|
|
||||||
final newList = List<MangaTrackingData>.from(state.mangas);
|
final newList = List<MangaTrackingData>.from(state.mangas);
|
||||||
final newManga = manga.copyWith(
|
final newManga = manga.copyWith(
|
||||||
@ -185,7 +216,7 @@ class AnimeListBloc extends Bloc<AnimeListEvent, AnimeListState> {
|
|||||||
final cacheIndex = _mangas.indexWhere((m) => m.id == event.id);
|
final cacheIndex = _mangas.indexWhere((m) => m.id == event.id);
|
||||||
assert(cacheIndex != -1, 'The manga must exist');
|
assert(cacheIndex != -1, 'The manga must exist');
|
||||||
_mangas[cacheIndex] = newManga;
|
_mangas[cacheIndex] = newManga;
|
||||||
|
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
mangas: newList,
|
mangas: newList,
|
||||||
@ -195,7 +226,10 @@ class AnimeListBloc extends Bloc<AnimeListEvent, AnimeListState> {
|
|||||||
await GetIt.I.get<DatabaseService>().updateManga(newManga);
|
await GetIt.I.get<DatabaseService>().updateManga(newManga);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onMangaDecremented(MangaChapterDecrementedEvent event, Emitter<AnimeListState> emit) async {
|
Future<void> _onMangaDecremented(
|
||||||
|
MangaChapterDecrementedEvent event,
|
||||||
|
Emitter<AnimeListState> emit,
|
||||||
|
) async {
|
||||||
final index = state.mangas.indexWhere((item) => item.id == event.id);
|
final index = state.mangas.indexWhere((item) => item.id == event.id);
|
||||||
if (index == -1) return;
|
if (index == -1) return;
|
||||||
|
|
||||||
@ -212,7 +246,7 @@ class AnimeListBloc extends Bloc<AnimeListEvent, AnimeListState> {
|
|||||||
final cacheIndex = _mangas.indexWhere((m) => m.id == event.id);
|
final cacheIndex = _mangas.indexWhere((m) => m.id == event.id);
|
||||||
assert(cacheIndex != -1, 'The manga must exist');
|
assert(cacheIndex != -1, 'The manga must exist');
|
||||||
_mangas[cacheIndex] = newManga;
|
_mangas[cacheIndex] = newManga;
|
||||||
|
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
mangas: newList,
|
mangas: newList,
|
||||||
@ -222,7 +256,10 @@ class AnimeListBloc extends Bloc<AnimeListEvent, AnimeListState> {
|
|||||||
await GetIt.I.get<DatabaseService>().updateManga(newManga);
|
await GetIt.I.get<DatabaseService>().updateManga(newManga);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onAnimeUpdated(AnimeUpdatedEvent event, Emitter<AnimeListState> emit) async {
|
Future<void> _onAnimeUpdated(
|
||||||
|
AnimeUpdatedEvent event,
|
||||||
|
Emitter<AnimeListState> emit,
|
||||||
|
) async {
|
||||||
final index = _animes.indexWhere((anime) => anime.id == event.anime.id);
|
final index = _animes.indexWhere((anime) => anime.id == event.anime.id);
|
||||||
assert(index != -1, 'The anime must exist');
|
assert(index != -1, 'The anime must exist');
|
||||||
|
|
||||||
@ -235,7 +272,10 @@ class AnimeListBloc extends Bloc<AnimeListEvent, AnimeListState> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onMangaUpdated(MangaUpdatedEvent event, Emitter<AnimeListState> emit) async {
|
Future<void> _onMangaUpdated(
|
||||||
|
MangaUpdatedEvent event,
|
||||||
|
Emitter<AnimeListState> emit,
|
||||||
|
) async {
|
||||||
final index = _mangas.indexWhere((manga) => manga.id == event.manga.id);
|
final index = _mangas.indexWhere((manga) => manga.id == event.manga.id);
|
||||||
assert(index != -1, 'The manga must exist');
|
assert(index != -1, 'The manga must exist');
|
||||||
|
|
||||||
@ -248,7 +288,10 @@ class AnimeListBloc extends Bloc<AnimeListEvent, AnimeListState> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onAnimeRemoved(AnimeRemovedEvent event, Emitter<AnimeListState> emit) async {
|
Future<void> _onAnimeRemoved(
|
||||||
|
AnimeRemovedEvent event,
|
||||||
|
Emitter<AnimeListState> emit,
|
||||||
|
) async {
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
animes: List.from(
|
animes: List.from(
|
||||||
@ -265,8 +308,11 @@ class AnimeListBloc extends Bloc<AnimeListEvent, AnimeListState> {
|
|||||||
// Update the database
|
// Update the database
|
||||||
await GetIt.I.get<DatabaseService>().deleteAnime(event.id);
|
await GetIt.I.get<DatabaseService>().deleteAnime(event.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onMangaRemoved(MangaRemovedEvent event, Emitter<AnimeListState> emit) async {
|
Future<void> _onMangaRemoved(
|
||||||
|
MangaRemovedEvent event,
|
||||||
|
Emitter<AnimeListState> emit,
|
||||||
|
) async {
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
mangas: List.from(
|
mangas: List.from(
|
||||||
@ -279,12 +325,15 @@ class AnimeListBloc extends Bloc<AnimeListEvent, AnimeListState> {
|
|||||||
final cacheIndex = _animes.indexWhere((a) => a.id == event.id);
|
final cacheIndex = _animes.indexWhere((a) => a.id == event.id);
|
||||||
assert(cacheIndex != -1, 'The manga must exist');
|
assert(cacheIndex != -1, 'The manga must exist');
|
||||||
_animes.removeAt(cacheIndex);
|
_animes.removeAt(cacheIndex);
|
||||||
|
|
||||||
// Update the database
|
// Update the database
|
||||||
await GetIt.I.get<DatabaseService>().deleteManga(event.id);
|
await GetIt.I.get<DatabaseService>().deleteManga(event.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onButtonVisibilityToggled(AddButtonVisibilitySetEvent event, Emitter<AnimeListState> emit) async {
|
Future<void> _onButtonVisibilityToggled(
|
||||||
|
AddButtonVisibilitySetEvent event,
|
||||||
|
Emitter<AnimeListState> emit,
|
||||||
|
) async {
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
buttonVisibility: event.state,
|
buttonVisibility: event.state,
|
||||||
|
@ -18,7 +18,7 @@ class AnimeEpisodeDecrementedEvent extends AnimeListEvent {
|
|||||||
|
|
||||||
class AnimeAddedEvent extends AnimeListEvent {
|
class AnimeAddedEvent extends AnimeListEvent {
|
||||||
AnimeAddedEvent(this.data);
|
AnimeAddedEvent(this.data);
|
||||||
|
|
||||||
/// The anime to add.
|
/// The anime to add.
|
||||||
final AnimeTrackingData data;
|
final AnimeTrackingData data;
|
||||||
}
|
}
|
||||||
@ -57,7 +57,7 @@ class AnimeRemovedEvent extends AnimeListEvent {
|
|||||||
|
|
||||||
class MangaAddedEvent extends AnimeListEvent {
|
class MangaAddedEvent extends AnimeListEvent {
|
||||||
MangaAddedEvent(this.data);
|
MangaAddedEvent(this.data);
|
||||||
|
|
||||||
/// The manga to add.
|
/// The manga to add.
|
||||||
final MangaTrackingData data;
|
final MangaTrackingData data;
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,10 @@ class AnimeSearchBloc extends Bloc<AnimeSearchEvent, AnimeSearchState> {
|
|||||||
on<ResultTappedEvent>(_onResultTapped);
|
on<ResultTappedEvent>(_onResultTapped);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onRequested(AnimeSearchRequestedEvent event, Emitter<AnimeSearchState> emit) async {
|
Future<void> _onRequested(
|
||||||
|
AnimeSearchRequestedEvent event,
|
||||||
|
Emitter<AnimeSearchState> emit,
|
||||||
|
) async {
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
searchQuery: '',
|
searchQuery: '',
|
||||||
@ -33,21 +36,27 @@ class AnimeSearchBloc extends Bloc<AnimeSearchEvent, AnimeSearchState> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
GetIt.I.get<NavigationBloc>().add(
|
GetIt.I.get<NavigationBloc>().add(
|
||||||
PushedNamedEvent(
|
PushedNamedEvent(
|
||||||
const NavigationDestination(animeSearchRoute),
|
const NavigationDestination(animeSearchRoute),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onQueryChanged(SearchQueryChangedEvent event, Emitter<AnimeSearchState> emit) async {
|
Future<void> _onQueryChanged(
|
||||||
|
SearchQueryChangedEvent event,
|
||||||
|
Emitter<AnimeSearchState> emit,
|
||||||
|
) async {
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
searchQuery: event.query,
|
searchQuery: event.query,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onQuerySubmitted(SearchQuerySubmittedEvent event, Emitter<AnimeSearchState> emit) async {
|
Future<void> _onQuerySubmitted(
|
||||||
|
SearchQuerySubmittedEvent event,
|
||||||
|
Emitter<AnimeSearchState> emit,
|
||||||
|
) async {
|
||||||
if (state.searchQuery.isEmpty) return;
|
if (state.searchQuery.isEmpty) return;
|
||||||
|
|
||||||
emit(
|
emit(
|
||||||
@ -65,13 +74,17 @@ class AnimeSearchBloc extends Bloc<AnimeSearchEvent, AnimeSearchState> {
|
|||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
working: false,
|
working: false,
|
||||||
searchResults: result.map((Anime anime) => SearchResult(
|
searchResults: result
|
||||||
anime.title,
|
.map(
|
||||||
anime.malId.toString(),
|
(Anime anime) => SearchResult(
|
||||||
anime.episodes,
|
anime.title,
|
||||||
anime.imageUrl,
|
anime.malId.toString(),
|
||||||
anime.synopsis ?? '',
|
anime.episodes,
|
||||||
),).toList(),
|
anime.imageUrl,
|
||||||
|
anime.synopsis ?? '',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@ -83,46 +96,53 @@ class AnimeSearchBloc extends Bloc<AnimeSearchEvent, AnimeSearchState> {
|
|||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
working: false,
|
working: false,
|
||||||
searchResults: result.map((Manga manga) => SearchResult(
|
searchResults: result
|
||||||
manga.title,
|
.map(
|
||||||
manga.malId.toString(),
|
(Manga manga) => SearchResult(
|
||||||
manga.chapters,
|
manga.title,
|
||||||
manga.imageUrl,
|
manga.malId.toString(),
|
||||||
manga.synopsis ?? '',
|
manga.chapters,
|
||||||
),).toList(),
|
manga.imageUrl,
|
||||||
|
manga.synopsis ?? '',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onResultTapped(ResultTappedEvent event, Emitter<AnimeSearchState> emit) async {
|
Future<void> _onResultTapped(
|
||||||
|
ResultTappedEvent event,
|
||||||
|
Emitter<AnimeSearchState> emit,
|
||||||
|
) async {
|
||||||
GetIt.I.get<list.AnimeListBloc>().add(
|
GetIt.I.get<list.AnimeListBloc>().add(
|
||||||
state.trackingType == TrackingMediumType.anime ?
|
state.trackingType == TrackingMediumType.anime
|
||||||
list.AnimeAddedEvent(
|
? list.AnimeAddedEvent(
|
||||||
AnimeTrackingData(
|
AnimeTrackingData(
|
||||||
event.result.id,
|
event.result.id,
|
||||||
MediumTrackingState.ongoing,
|
MediumTrackingState.ongoing,
|
||||||
event.result.title,
|
event.result.title,
|
||||||
0,
|
0,
|
||||||
event.result.total,
|
event.result.total,
|
||||||
event.result.thumbnailUrl,
|
event.result.thumbnailUrl,
|
||||||
),
|
),
|
||||||
) :
|
)
|
||||||
list.MangaAddedEvent(
|
: list.MangaAddedEvent(
|
||||||
MangaTrackingData(
|
MangaTrackingData(
|
||||||
event.result.id,
|
event.result.id,
|
||||||
MediumTrackingState.ongoing,
|
MediumTrackingState.ongoing,
|
||||||
event.result.title,
|
event.result.title,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
event.result.total,
|
event.result.total,
|
||||||
event.result.thumbnailUrl,
|
event.result.thumbnailUrl,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
GetIt.I.get<NavigationBloc>().add(
|
GetIt.I.get<NavigationBloc>().add(
|
||||||
PoppedRouteEvent(),
|
PoppedRouteEvent(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ class AnimeSearchRequestedEvent extends AnimeSearchEvent {
|
|||||||
/// Triggered when the search query is changed.
|
/// Triggered when the search query is changed.
|
||||||
class SearchQueryChangedEvent extends AnimeSearchEvent {
|
class SearchQueryChangedEvent extends AnimeSearchEvent {
|
||||||
SearchQueryChangedEvent(this.query);
|
SearchQueryChangedEvent(this.query);
|
||||||
|
|
||||||
/// The current value of the query
|
/// The current value of the query
|
||||||
final String query;
|
final String query;
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,10 @@ class DetailsBloc extends Bloc<DetailsEvent, DetailsState> {
|
|||||||
on<ItemRemovedEvent>(_onItemRemoved);
|
on<ItemRemovedEvent>(_onItemRemoved);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onAnimeRequested(AnimeDetailsRequestedEvent event, Emitter<DetailsState> emit) async {
|
Future<void> _onAnimeRequested(
|
||||||
|
AnimeDetailsRequestedEvent event,
|
||||||
|
Emitter<DetailsState> emit,
|
||||||
|
) async {
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
trackingType: TrackingMediumType.anime,
|
trackingType: TrackingMediumType.anime,
|
||||||
@ -30,13 +33,16 @@ class DetailsBloc extends Bloc<DetailsEvent, DetailsState> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
GetIt.I.get<NavigationBloc>().add(
|
GetIt.I.get<NavigationBloc>().add(
|
||||||
PushedNamedEvent(
|
PushedNamedEvent(
|
||||||
const NavigationDestination(detailsRoute),
|
const NavigationDestination(detailsRoute),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onMangaRequested(MangaDetailsRequestedEvent event, Emitter<DetailsState> emit) async {
|
Future<void> _onMangaRequested(
|
||||||
|
MangaDetailsRequestedEvent event,
|
||||||
|
Emitter<DetailsState> emit,
|
||||||
|
) async {
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
trackingType: TrackingMediumType.manga,
|
trackingType: TrackingMediumType.manga,
|
||||||
@ -45,13 +51,16 @@ class DetailsBloc extends Bloc<DetailsEvent, DetailsState> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
GetIt.I.get<NavigationBloc>().add(
|
GetIt.I.get<NavigationBloc>().add(
|
||||||
PushedNamedEvent(
|
PushedNamedEvent(
|
||||||
const NavigationDestination(detailsRoute),
|
const NavigationDestination(detailsRoute),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onDetailsUpdated(DetailsUpdatedEvent event, Emitter<DetailsState> emit) async {
|
Future<void> _onDetailsUpdated(
|
||||||
|
DetailsUpdatedEvent event,
|
||||||
|
Emitter<DetailsState> emit,
|
||||||
|
) async {
|
||||||
if (state.trackingType == TrackingMediumType.anime) {
|
if (state.trackingType == TrackingMediumType.anime) {
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
@ -59,11 +68,13 @@ class DetailsBloc extends Bloc<DetailsEvent, DetailsState> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
await GetIt.I.get<DatabaseService>().updateAnime(event.data as AnimeTrackingData);
|
await GetIt.I
|
||||||
|
.get<DatabaseService>()
|
||||||
|
.updateAnime(event.data as AnimeTrackingData);
|
||||||
|
|
||||||
GetIt.I.get<AnimeListBloc>().add(
|
GetIt.I.get<AnimeListBloc>().add(
|
||||||
AnimeUpdatedEvent(event.data as AnimeTrackingData),
|
AnimeUpdatedEvent(event.data as AnimeTrackingData),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
@ -71,15 +82,20 @@ class DetailsBloc extends Bloc<DetailsEvent, DetailsState> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
await GetIt.I.get<DatabaseService>().updateManga(event.data as MangaTrackingData);
|
await GetIt.I
|
||||||
|
.get<DatabaseService>()
|
||||||
|
.updateManga(event.data as MangaTrackingData);
|
||||||
|
|
||||||
GetIt.I.get<AnimeListBloc>().add(
|
GetIt.I.get<AnimeListBloc>().add(
|
||||||
MangaUpdatedEvent(event.data as MangaTrackingData),
|
MangaUpdatedEvent(event.data as MangaTrackingData),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onItemRemoved(ItemRemovedEvent event, Emitter<DetailsState> emit) async {
|
Future<void> _onItemRemoved(
|
||||||
|
ItemRemovedEvent event,
|
||||||
|
Emitter<DetailsState> emit,
|
||||||
|
) async {
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
data: null,
|
data: null,
|
||||||
|
@ -13,14 +13,20 @@ class NavigationBloc extends Bloc<NavigationEvent, NavigationState> {
|
|||||||
}
|
}
|
||||||
final GlobalKey<NavigatorState> navigationKey;
|
final GlobalKey<NavigatorState> navigationKey;
|
||||||
|
|
||||||
Future<void> _onPushedNamed(PushedNamedEvent event, Emitter<NavigationState> emit) async {
|
Future<void> _onPushedNamed(
|
||||||
|
PushedNamedEvent event,
|
||||||
|
Emitter<NavigationState> emit,
|
||||||
|
) async {
|
||||||
await navigationKey.currentState!.pushNamed(
|
await navigationKey.currentState!.pushNamed(
|
||||||
event.destination.path,
|
event.destination.path,
|
||||||
arguments: event.destination.arguments,
|
arguments: event.destination.arguments,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onPushedNamedAndRemoveUntil(PushedNamedAndRemoveUntilEvent event, Emitter<NavigationState> emit) async {
|
Future<void> _onPushedNamedAndRemoveUntil(
|
||||||
|
PushedNamedAndRemoveUntilEvent event,
|
||||||
|
Emitter<NavigationState> emit,
|
||||||
|
) async {
|
||||||
await navigationKey.currentState!.pushNamedAndRemoveUntil(
|
await navigationKey.currentState!.pushNamedAndRemoveUntil(
|
||||||
event.destination.path,
|
event.destination.path,
|
||||||
event.predicate,
|
event.predicate,
|
||||||
@ -28,14 +34,20 @@ class NavigationBloc extends Bloc<NavigationEvent, NavigationState> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onPushedNamedReplaceEvent(PushedNamedReplaceEvent event, Emitter<NavigationState> emit) async {
|
Future<void> _onPushedNamedReplaceEvent(
|
||||||
|
PushedNamedReplaceEvent event,
|
||||||
|
Emitter<NavigationState> emit,
|
||||||
|
) async {
|
||||||
await navigationKey.currentState!.pushReplacementNamed(
|
await navigationKey.currentState!.pushReplacementNamed(
|
||||||
event.destination.path,
|
event.destination.path,
|
||||||
arguments: event.destination.arguments,
|
arguments: event.destination.arguments,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onPoppedRoute(PoppedRouteEvent event, Emitter<NavigationState> emit) async {
|
Future<void> _onPoppedRoute(
|
||||||
|
PoppedRouteEvent event,
|
||||||
|
Emitter<NavigationState> emit,
|
||||||
|
) async {
|
||||||
navigationKey.currentState!.pop();
|
navigationKey.currentState!.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,11 +2,9 @@ part of 'navigation_bloc.dart';
|
|||||||
|
|
||||||
class NavigationDestination {
|
class NavigationDestination {
|
||||||
const NavigationDestination(
|
const NavigationDestination(
|
||||||
this.path,
|
this.path, {
|
||||||
{
|
this.arguments,
|
||||||
this.arguments,
|
});
|
||||||
}
|
|
||||||
);
|
|
||||||
final String path;
|
final String path;
|
||||||
final Object? arguments;
|
final Object? arguments;
|
||||||
}
|
}
|
||||||
|
@ -9,11 +9,11 @@ class AboutPage extends StatelessWidget {
|
|||||||
});
|
});
|
||||||
|
|
||||||
static MaterialPageRoute<dynamic> get route => MaterialPageRoute<dynamic>(
|
static MaterialPageRoute<dynamic> get route => MaterialPageRoute<dynamic>(
|
||||||
builder: (_) => const AboutPage(),
|
builder: (_) => const AboutPage(),
|
||||||
settings: const RouteSettings(
|
settings: const RouteSettings(
|
||||||
name: aboutRoute,
|
name: aboutRoute,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -34,7 +34,6 @@ class AboutPage extends StatelessWidget {
|
|||||||
'AniTrack',
|
'AniTrack',
|
||||||
style: Theme.of(context).textTheme.titleLarge,
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
),
|
),
|
||||||
|
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await launchUrl(
|
await launchUrl(
|
||||||
|
@ -16,11 +16,11 @@ class AnimeListPage extends StatefulWidget {
|
|||||||
});
|
});
|
||||||
|
|
||||||
static MaterialPageRoute<dynamic> get route => MaterialPageRoute<dynamic>(
|
static MaterialPageRoute<dynamic> get route => MaterialPageRoute<dynamic>(
|
||||||
builder: (_) => AnimeListPage(),
|
builder: (_) => const AnimeListPage(),
|
||||||
settings: const RouteSettings(
|
settings: const RouteSettings(
|
||||||
name: animeListRoute,
|
name: animeListRoute,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
AnimeListPageState createState() => AnimeListPageState();
|
AnimeListPageState createState() => AnimeListPageState();
|
||||||
@ -35,11 +35,12 @@ class AnimeListPageState extends State<AnimeListPage> {
|
|||||||
super.initState();
|
super.initState();
|
||||||
_animeScrollController.addListener(_onAnimeListScrolled);
|
_animeScrollController.addListener(_onAnimeListScrolled);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onAnimeListScrolled() {
|
void _onAnimeListScrolled() {
|
||||||
//print(_animeScrollController.position.maxScrollExtent);
|
//print(_animeScrollController.position.maxScrollExtent);
|
||||||
final bloc = GetIt.I.get<AnimeListBloc>();
|
final bloc = GetIt.I.get<AnimeListBloc>();
|
||||||
if (_animeScrollController.offset + 20 >= _animeScrollController.position.maxScrollExtent) {
|
if (_animeScrollController.offset + 20 >=
|
||||||
|
_animeScrollController.position.maxScrollExtent) {
|
||||||
if (bloc.state.buttonVisibility) {
|
if (bloc.state.buttonVisibility) {
|
||||||
bloc.add(
|
bloc.add(
|
||||||
AddButtonVisibilitySetEvent(false),
|
AddButtonVisibilitySetEvent(false),
|
||||||
@ -53,15 +54,19 @@ class AnimeListPageState extends State<AnimeListPage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String _getPageTitle(TrackingMediumType type) {
|
String _getPageTitle(TrackingMediumType type) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case TrackingMediumType.anime: return 'Anime';
|
case TrackingMediumType.anime:
|
||||||
case TrackingMediumType.manga: return 'Manga';
|
return 'Anime';
|
||||||
|
case TrackingMediumType.manga:
|
||||||
|
return 'Manga';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<PopupMenuItem<MediumTrackingState>> _getPopupButtonItems(TrackingMediumType type) {
|
List<PopupMenuItem<MediumTrackingState>> _getPopupButtonItems(
|
||||||
|
TrackingMediumType type,
|
||||||
|
) {
|
||||||
return [
|
return [
|
||||||
PopupMenuItem<MediumTrackingState>(
|
PopupMenuItem<MediumTrackingState>(
|
||||||
value: MediumTrackingState.ongoing,
|
value: MediumTrackingState.ongoing,
|
||||||
@ -85,7 +90,7 @@ class AnimeListPageState extends State<AnimeListPage> {
|
|||||||
),
|
),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _getPopupButton(BuildContext context, AnimeListState state) {
|
Widget _getPopupButton(BuildContext context, AnimeListState state) {
|
||||||
switch (state.trackingType) {
|
switch (state.trackingType) {
|
||||||
case TrackingMediumType.anime:
|
case TrackingMediumType.anime:
|
||||||
@ -96,8 +101,8 @@ class AnimeListPageState extends State<AnimeListPage> {
|
|||||||
initialValue: state.animeFilterState,
|
initialValue: state.animeFilterState,
|
||||||
onSelected: (filterState) {
|
onSelected: (filterState) {
|
||||||
context.read<AnimeListBloc>().add(
|
context.read<AnimeListBloc>().add(
|
||||||
AnimeFilterChangedEvent(filterState),
|
AnimeFilterChangedEvent(filterState),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
itemBuilder: (_) => _getPopupButtonItems(TrackingMediumType.anime),
|
itemBuilder: (_) => _getPopupButtonItems(TrackingMediumType.anime),
|
||||||
);
|
);
|
||||||
@ -109,14 +114,14 @@ class AnimeListPageState extends State<AnimeListPage> {
|
|||||||
initialValue: state.mangaFilterState,
|
initialValue: state.mangaFilterState,
|
||||||
onSelected: (filterState) {
|
onSelected: (filterState) {
|
||||||
context.read<AnimeListBloc>().add(
|
context.read<AnimeListBloc>().add(
|
||||||
MangaFilterChangedEvent(filterState),
|
MangaFilterChangedEvent(filterState),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
itemBuilder: (_) => _getPopupButtonItems(TrackingMediumType.manga),
|
itemBuilder: (_) => _getPopupButtonItems(TrackingMediumType.manga),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocBuilder<AnimeListBloc, AnimeListState>(
|
return BlocBuilder<AnimeListBloc, AnimeListState>(
|
||||||
@ -145,7 +150,6 @@ class AnimeListPageState extends State<AnimeListPage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.info),
|
leading: const Icon(Icons.info),
|
||||||
title: const Text('About'),
|
title: const Text('About'),
|
||||||
@ -178,37 +182,37 @@ class AnimeListPageState extends State<AnimeListPage> {
|
|||||||
return GridItem(
|
return GridItem(
|
||||||
minusCallback: () {
|
minusCallback: () {
|
||||||
context.read<AnimeListBloc>().add(
|
context.read<AnimeListBloc>().add(
|
||||||
AnimeEpisodeDecrementedEvent(
|
AnimeEpisodeDecrementedEvent(
|
||||||
anime.id,
|
anime.id,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
plusCallback: () {
|
plusCallback: () {
|
||||||
context.read<AnimeListBloc>().add(
|
context.read<AnimeListBloc>().add(
|
||||||
AnimeEpisodeIncrementedEvent(
|
AnimeEpisodeIncrementedEvent(
|
||||||
anime.id,
|
anime.id,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: AnimeCoverImage(
|
child: AnimeCoverImage(
|
||||||
url: anime.thumbnailUrl,
|
url: anime.thumbnailUrl,
|
||||||
hero: anime.id,
|
hero: anime.id,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.read<DetailsBloc>().add(
|
context.read<DetailsBloc>().add(
|
||||||
AnimeDetailsRequestedEvent(anime),
|
AnimeDetailsRequestedEvent(anime),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
extra: Align(
|
extra: Align(
|
||||||
alignment: Alignment.centerRight,
|
alignment: Alignment.centerRight,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.only(right: 8),
|
padding: const EdgeInsets.only(right: 8),
|
||||||
child: Text(
|
child: Text(
|
||||||
'${anime.episodesWatched}/${anime.episodesTotal ?? "???"}',
|
'${anime.episodesWatched}/${anime.episodesTotal ?? "???"}',
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -228,37 +232,37 @@ class AnimeListPageState extends State<AnimeListPage> {
|
|||||||
return GridItem(
|
return GridItem(
|
||||||
minusCallback: () {
|
minusCallback: () {
|
||||||
context.read<AnimeListBloc>().add(
|
context.read<AnimeListBloc>().add(
|
||||||
MangaChapterDecrementedEvent(
|
MangaChapterDecrementedEvent(
|
||||||
manga.id,
|
manga.id,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
plusCallback: () {
|
plusCallback: () {
|
||||||
context.read<AnimeListBloc>().add(
|
context.read<AnimeListBloc>().add(
|
||||||
MangaChapterIncrementedEvent(
|
MangaChapterIncrementedEvent(
|
||||||
manga.id,
|
manga.id,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: AnimeCoverImage(
|
child: AnimeCoverImage(
|
||||||
hero: manga.id,
|
hero: manga.id,
|
||||||
url: manga.thumbnailUrl,
|
url: manga.thumbnailUrl,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.read<DetailsBloc>().add(
|
context.read<DetailsBloc>().add(
|
||||||
MangaDetailsRequestedEvent(manga),
|
MangaDetailsRequestedEvent(manga),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
extra: Align(
|
extra: Align(
|
||||||
alignment: Alignment.centerRight,
|
alignment: Alignment.centerRight,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.only(right: 8),
|
padding: const EdgeInsets.only(right: 8),
|
||||||
child: Text(
|
child: Text(
|
||||||
'${manga.chaptersRead}/${manga.chaptersTotal ?? "???"}',
|
'${manga.chaptersRead}/${manga.chaptersTotal ?? "???"}',
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -266,19 +270,18 @@ class AnimeListPageState extends State<AnimeListPage> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
floatingActionButton: BlocBuilder<AnimeListBloc, AnimeListState>(
|
floatingActionButton: BlocBuilder<AnimeListBloc, AnimeListState>(
|
||||||
buildWhen: (prev, next) => prev.buttonVisibility != next.buttonVisibility,
|
buildWhen: (prev, next) =>
|
||||||
|
prev.buttonVisibility != next.buttonVisibility,
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return AnimatedScale(
|
return AnimatedScale(
|
||||||
duration: const Duration(milliseconds: 250),
|
duration: const Duration(milliseconds: 250),
|
||||||
scale: state.buttonVisibility ?
|
scale: state.buttonVisibility ? 1 : 0,
|
||||||
1 :
|
|
||||||
0,
|
|
||||||
curve: Curves.easeInOutQuint,
|
curve: Curves.easeInOutQuint,
|
||||||
child: FloatingActionButton(
|
child: FloatingActionButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
context.read<AnimeSearchBloc>().add(
|
context.read<AnimeSearchBloc>().add(
|
||||||
AnimeSearchRequestedEvent(state.trackingType),
|
AnimeSearchRequestedEvent(state.trackingType),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
tooltip: 'Add new item',
|
tooltip: 'Add new item',
|
||||||
child: const Icon(Icons.add),
|
child: const Icon(Icons.add),
|
||||||
@ -287,17 +290,16 @@ class AnimeListPageState extends State<AnimeListPage> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
bottomNavigationBar: BottomBar(
|
bottomNavigationBar: BottomBar(
|
||||||
selectedIndex: state.trackingType == TrackingMediumType.anime ?
|
selectedIndex:
|
||||||
0 :
|
state.trackingType == TrackingMediumType.anime ? 0 : 1,
|
||||||
1,
|
|
||||||
onTap: (int index) {
|
onTap: (int index) {
|
||||||
context.read<AnimeListBloc>().add(
|
context.read<AnimeListBloc>().add(
|
||||||
AnimeTrackingTypeChanged(
|
AnimeTrackingTypeChanged(
|
||||||
index == 0 ?
|
index == 0
|
||||||
TrackingMediumType.anime :
|
? TrackingMediumType.anime
|
||||||
TrackingMediumType.manga,
|
: TrackingMediumType.manga,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
_controller.jumpToPage(index);
|
_controller.jumpToPage(index);
|
||||||
},
|
},
|
||||||
|
@ -11,12 +11,12 @@ class AnimeSearchPage extends StatelessWidget {
|
|||||||
});
|
});
|
||||||
|
|
||||||
static MaterialPageRoute<dynamic> get route => MaterialPageRoute<dynamic>(
|
static MaterialPageRoute<dynamic> get route => MaterialPageRoute<dynamic>(
|
||||||
builder: (_) => const AnimeSearchPage(),
|
builder: (_) => const AnimeSearchPage(),
|
||||||
settings: const RouteSettings(
|
settings: const RouteSettings(
|
||||||
name: animeSearchRoute,
|
name: animeSearchRoute,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocBuilder<AnimeSearchBloc, AnimeSearchState>(
|
return BlocBuilder<AnimeSearchBloc, AnimeSearchState>(
|
||||||
@ -24,9 +24,9 @@ class AnimeSearchPage extends StatelessWidget {
|
|||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(
|
title: Text(
|
||||||
state.trackingType == TrackingMediumType.anime ?
|
state.trackingType == TrackingMediumType.anime
|
||||||
'Anime Search' :
|
? 'Anime Search'
|
||||||
'Manga Search',
|
: 'Manga Search',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
body: Column(
|
body: Column(
|
||||||
@ -40,17 +40,16 @@ class AnimeSearchPage extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
onSubmitted: (_) {
|
onSubmitted: (_) {
|
||||||
context.read<AnimeSearchBloc>().add(
|
context.read<AnimeSearchBloc>().add(
|
||||||
SearchQuerySubmittedEvent(),
|
SearchQuerySubmittedEvent(),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
context.read<AnimeSearchBloc>().add(
|
context.read<AnimeSearchBloc>().add(
|
||||||
SearchQueryChangedEvent(value),
|
SearchQueryChangedEvent(value),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
if (state.working)
|
if (state.working)
|
||||||
const Expanded(
|
const Expanded(
|
||||||
child: Align(
|
child: Align(
|
||||||
@ -66,8 +65,8 @@ class AnimeSearchPage extends StatelessWidget {
|
|||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.read<AnimeSearchBloc>().add(
|
context.read<AnimeSearchBloc>().add(
|
||||||
ResultTappedEvent(item),
|
ResultTappedEvent(item),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: ListItem(
|
child: ListItem(
|
||||||
title: item.title,
|
title: item.title,
|
||||||
|
@ -16,11 +16,11 @@ class DetailsPage extends StatelessWidget {
|
|||||||
});
|
});
|
||||||
|
|
||||||
static MaterialPageRoute<dynamic> get route => MaterialPageRoute<dynamic>(
|
static MaterialPageRoute<dynamic> get route => MaterialPageRoute<dynamic>(
|
||||||
builder: (_) => const DetailsPage(),
|
builder: (_) => const DetailsPage(),
|
||||||
settings: const RouteSettings(
|
settings: const RouteSettings(
|
||||||
name: detailsRoute,
|
name: detailsRoute,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -30,195 +30,213 @@ class DetailsPage extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
body: BlocBuilder<DetailsBloc, DetailsState>(
|
body: BlocBuilder<DetailsBloc, DetailsState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return state.data == null ?
|
return state.data == null
|
||||||
Container() :
|
? Container()
|
||||||
Padding(
|
: Padding(
|
||||||
padding: const EdgeInsets.all(8),
|
padding: const EdgeInsets.all(8),
|
||||||
child: ListView(
|
child: ListView(
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [AnimeCoverImage(
|
children: [
|
||||||
url: state.data!.thumbnailUrl,
|
AnimeCoverImage(
|
||||||
hero: state.data!.id,
|
url: state.data!.thumbnailUrl,
|
||||||
),
|
hero: state.data!.id,
|
||||||
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.only(
|
padding: const EdgeInsets.only(
|
||||||
left: 8,
|
left: 8,
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
state.data!.title,
|
state.data!.title,
|
||||||
textAlign: TextAlign.left,
|
textAlign: TextAlign.left,
|
||||||
style: Theme.of(context).textTheme.titleLarge,
|
style:
|
||||||
maxLines: 2,
|
Theme.of(context).textTheme.titleLarge,
|
||||||
softWrap: true,
|
maxLines: 2,
|
||||||
overflow: TextOverflow.ellipsis,
|
softWrap: true,
|
||||||
),
|
overflow: TextOverflow.ellipsis,
|
||||||
|
|
||||||
ElevatedButton(
|
|
||||||
onPressed: () async {
|
|
||||||
final result = await showDialog<bool>(
|
|
||||||
context: context,
|
|
||||||
builder: (context) {
|
|
||||||
return AlertDialog(
|
|
||||||
title: Text('Remove "${state.data!.title}"?'),
|
|
||||||
content: Text('Are you sure you want to remove "${state.data!.title}" from the list?'),
|
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context).pop(true);
|
|
||||||
},
|
|
||||||
style: TextButton.styleFrom(
|
|
||||||
foregroundColor: Colors.red,
|
|
||||||
),
|
|
||||||
child: const Text('Remove'),
|
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context).pop(false);
|
|
||||||
},
|
|
||||||
child: const Text('Cancel'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (result != true) return;
|
|
||||||
|
|
||||||
// ignore: use_build_context_synchronously
|
|
||||||
context.read<DetailsBloc>().add(
|
|
||||||
ItemRemovedEvent(
|
|
||||||
state.data!.id,
|
|
||||||
state.trackingType,
|
|
||||||
),
|
),
|
||||||
);
|
ElevatedButton(
|
||||||
},
|
onPressed: () async {
|
||||||
child: const Icon(Icons.delete),
|
final result = await showDialog<bool>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: Text(
|
||||||
|
'Remove "${state.data!.title}"?',
|
||||||
|
),
|
||||||
|
content: Text(
|
||||||
|
'Are you sure you want to remove "${state.data!.title}" from the list?',
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context)
|
||||||
|
.pop(true);
|
||||||
|
},
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
foregroundColor: Colors.red,
|
||||||
|
),
|
||||||
|
child: const Text('Remove'),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context)
|
||||||
|
.pop(false);
|
||||||
|
},
|
||||||
|
child: const Text('Cancel'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result != true) return;
|
||||||
|
|
||||||
|
// ignore: use_build_context_synchronously
|
||||||
|
context.read<DetailsBloc>().add(
|
||||||
|
ItemRemovedEvent(
|
||||||
|
state.data!.id,
|
||||||
|
state.trackingType,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: const Icon(Icons.delete),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 8,
|
||||||
|
),
|
||||||
|
child: DropdownSelector<MediumTrackingState>(
|
||||||
|
title: state.trackingType == TrackingMediumType.anime
|
||||||
|
? 'Watch state'
|
||||||
|
: 'Read state',
|
||||||
|
onChanged: (MediumTrackingState newState) {
|
||||||
|
if (state.trackingType ==
|
||||||
|
TrackingMediumType.anime) {
|
||||||
|
context.read<DetailsBloc>().add(
|
||||||
|
DetailsUpdatedEvent(
|
||||||
|
(state.data! as AnimeTrackingData)
|
||||||
|
.copyWith(
|
||||||
|
state: newState,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else if (state.trackingType ==
|
||||||
|
TrackingMediumType.manga) {
|
||||||
|
context.read<DetailsBloc>().add(
|
||||||
|
DetailsUpdatedEvent(
|
||||||
|
(state.data! as MangaTrackingData)
|
||||||
|
.copyWith(
|
||||||
|
state: newState,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
values: [
|
||||||
|
SelectorItem(
|
||||||
|
MediumTrackingState.ongoing,
|
||||||
|
MediumTrackingState.ongoing
|
||||||
|
.toNameString(state.trackingType),
|
||||||
|
),
|
||||||
|
SelectorItem(
|
||||||
|
MediumTrackingState.completed,
|
||||||
|
MediumTrackingState.completed
|
||||||
|
.toNameString(state.trackingType),
|
||||||
|
),
|
||||||
|
SelectorItem(
|
||||||
|
MediumTrackingState.planned,
|
||||||
|
MediumTrackingState.planned
|
||||||
|
.toNameString(state.trackingType),
|
||||||
|
),
|
||||||
|
SelectorItem(
|
||||||
|
MediumTrackingState.dropped,
|
||||||
|
MediumTrackingState.dropped
|
||||||
|
.toNameString(state.trackingType),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
initialValue: state.data!.state,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
Padding(
|
||||||
],
|
padding: const EdgeInsets.symmetric(
|
||||||
),
|
vertical: 8,
|
||||||
|
),
|
||||||
Padding(
|
child: IntegerInput(
|
||||||
padding: const EdgeInsets.symmetric(
|
labelText:
|
||||||
vertical: 8,
|
state.trackingType == TrackingMediumType.anime
|
||||||
),
|
? 'Episodes'
|
||||||
child: DropdownSelector<MediumTrackingState>(
|
: 'Chapters',
|
||||||
title: state.trackingType == TrackingMediumType.anime ?
|
onChanged: (value) {
|
||||||
'Watch state' :
|
switch (state.trackingType) {
|
||||||
'Read state',
|
case TrackingMediumType.anime:
|
||||||
onChanged: (MediumTrackingState newState) {
|
final data = state.data! as AnimeTrackingData;
|
||||||
if (state.trackingType == TrackingMediumType.anime) {
|
context.read<DetailsBloc>().add(
|
||||||
context.read<DetailsBloc>().add(
|
DetailsUpdatedEvent(
|
||||||
DetailsUpdatedEvent(
|
data.copyWith(
|
||||||
(state.data! as AnimeTrackingData).copyWith(
|
episodesWatched: value,
|
||||||
state: newState,
|
),
|
||||||
),
|
),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case TrackingMediumType.manga:
|
||||||
|
final data = state.data! as MangaTrackingData;
|
||||||
|
context.read<DetailsBloc>().add(
|
||||||
|
DetailsUpdatedEvent(
|
||||||
|
data.copyWith(
|
||||||
|
chaptersRead: value,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
initialValue: state.trackingType ==
|
||||||
|
TrackingMediumType.anime
|
||||||
|
? (state.data! as AnimeTrackingData)
|
||||||
|
.episodesWatched
|
||||||
|
: (state.data! as MangaTrackingData).chaptersRead,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (state.trackingType == TrackingMediumType.manga)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 8,
|
||||||
),
|
),
|
||||||
);
|
child: IntegerInput(
|
||||||
} else if (state.trackingType == TrackingMediumType.manga) {
|
labelText: 'Volumes owned',
|
||||||
context.read<DetailsBloc>().add(
|
onChanged: (value) {
|
||||||
DetailsUpdatedEvent(
|
final data = state.data! as MangaTrackingData;
|
||||||
(state.data! as MangaTrackingData).copyWith(
|
context.read<DetailsBloc>().add(
|
||||||
state: newState,
|
DetailsUpdatedEvent(
|
||||||
),
|
data.copyWith(
|
||||||
|
volumesOwned: value,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
initialValue: (GetIt.I
|
||||||
|
.get<DetailsBloc>()
|
||||||
|
.state
|
||||||
|
.data! as MangaTrackingData)
|
||||||
|
.volumesOwned,
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
}
|
|
||||||
},
|
|
||||||
values: [
|
|
||||||
SelectorItem(
|
|
||||||
MediumTrackingState.ongoing,
|
|
||||||
MediumTrackingState.ongoing.toNameString(state.trackingType),
|
|
||||||
),
|
|
||||||
SelectorItem(
|
|
||||||
MediumTrackingState.completed,
|
|
||||||
MediumTrackingState.completed.toNameString(state.trackingType),
|
|
||||||
),
|
|
||||||
SelectorItem(
|
|
||||||
MediumTrackingState.planned,
|
|
||||||
MediumTrackingState.planned.toNameString(state.trackingType),
|
|
||||||
),
|
|
||||||
SelectorItem(
|
|
||||||
MediumTrackingState.dropped,
|
|
||||||
MediumTrackingState.dropped.toNameString(state.trackingType),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
initialValue: state.data!.state,
|
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
vertical: 8,
|
|
||||||
),
|
|
||||||
child: IntegerInput(
|
|
||||||
labelText: state.trackingType == TrackingMediumType.anime ?
|
|
||||||
'Episodes' :
|
|
||||||
'Chapters',
|
|
||||||
onChanged: (value) {
|
|
||||||
switch (state.trackingType) {
|
|
||||||
case TrackingMediumType.anime:
|
|
||||||
final data = state.data! as AnimeTrackingData;
|
|
||||||
context.read<DetailsBloc>().add(
|
|
||||||
DetailsUpdatedEvent(
|
|
||||||
data.copyWith(
|
|
||||||
episodesWatched: value,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case TrackingMediumType.manga:
|
|
||||||
final data = state.data! as MangaTrackingData;
|
|
||||||
context.read<DetailsBloc>().add(
|
|
||||||
DetailsUpdatedEvent(
|
|
||||||
data.copyWith(
|
|
||||||
chaptersRead: value,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
initialValue: state.trackingType == TrackingMediumType.anime ?
|
|
||||||
(state.data! as AnimeTrackingData).episodesWatched :
|
|
||||||
(state.data! as MangaTrackingData).chaptersRead,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
if (state.trackingType == TrackingMediumType.manga)
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
vertical: 8,
|
|
||||||
),
|
|
||||||
child: IntegerInput(
|
|
||||||
labelText: 'Volumes owned',
|
|
||||||
onChanged: (value) {
|
|
||||||
final data = state.data! as MangaTrackingData;
|
|
||||||
context.read<DetailsBloc>().add(
|
|
||||||
DetailsUpdatedEvent(
|
|
||||||
data.copyWith(
|
|
||||||
volumesOwned: value,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
initialValue: (GetIt.I.get<DetailsBloc>().state.data! as MangaTrackingData).volumesOwned,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -23,10 +23,10 @@ class DropdownSelector<T> extends StatefulWidget {
|
|||||||
final List<SelectorItem<T>> values;
|
final List<SelectorItem<T>> values;
|
||||||
|
|
||||||
final T initialValue;
|
final T initialValue;
|
||||||
|
|
||||||
/// Called when the selection has changed
|
/// Called when the selection has changed
|
||||||
final void Function(T) onChanged;
|
final void Function(T) onChanged;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
DropdownSelectorState<T> createState() => DropdownSelectorState<T>();
|
DropdownSelectorState<T> createState() => DropdownSelectorState<T>();
|
||||||
}
|
}
|
||||||
@ -76,54 +76,56 @@ class DropdownSelectorState<T> extends State<DropdownSelector<T>> {
|
|||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
|
||||||
index = widget.values.indexWhere((item) => item.value == widget.initialValue);
|
index =
|
||||||
|
widget.values.indexWhere((item) => item.value == widget.initialValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Card(
|
child: Card(
|
||||||
clipBehavior: Clip.hardEdge,
|
clipBehavior: Clip.hardEdge,
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
final result = await showDialog<T>(
|
final result = await showDialog<T>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => DropdownSelectorDialog<T>(
|
builder: (context) => DropdownSelectorDialog<T>(
|
||||||
values: widget.values.cast<SelectorItem<T>>(),
|
values: widget.values.cast<SelectorItem<T>>(),
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (result == null) return;
|
|
||||||
if (result == widget.values[index].value) return;
|
|
||||||
|
|
||||||
setState(() {
|
|
||||||
index = widget.values.indexWhere((item) => item.value == result);
|
|
||||||
});
|
|
||||||
|
|
||||||
widget.onChanged(result);
|
|
||||||
},
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(12),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
widget.title,
|
|
||||||
style: Theme.of(context).textTheme.titleSmall,
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
widget.values[index].text,
|
|
||||||
style: Theme.of(context).textTheme.bodyLarge,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result == null) return;
|
||||||
|
if (result == widget.values[index].value) return;
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
index =
|
||||||
|
widget.values.indexWhere((item) => item.value == result);
|
||||||
|
});
|
||||||
|
|
||||||
|
widget.onChanged(result);
|
||||||
|
},
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
widget.title,
|
||||||
|
style: Theme.of(context).textTheme.titleSmall,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
widget.values[index].text,
|
||||||
|
style: Theme.of(context).textTheme.bodyLarge,
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ class GridItem extends StatefulWidget {
|
|||||||
|
|
||||||
final void Function() plusCallback;
|
final void Function() plusCallback;
|
||||||
final void Function() minusCallback;
|
final void Function() minusCallback;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
GridItemState createState() => GridItemState();
|
GridItemState createState() => GridItemState();
|
||||||
}
|
}
|
||||||
@ -28,7 +28,7 @@ class GridItemState extends State<GridItem> {
|
|||||||
onHorizontalDragUpdate: (details) {
|
onHorizontalDragUpdate: (details) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_offset += details.delta.dx;
|
_offset += details.delta.dx;
|
||||||
_translationX = 160 / (1 + exp(-1 * (1/30) * _offset)) - 80;
|
_translationX = 160 / (1 + exp(-1 * (1 / 30) * _offset)) - 80;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onHorizontalDragEnd: (_) {
|
onHorizontalDragEnd: (_) {
|
||||||
|
@ -19,13 +19,13 @@ class AnimeCoverImage extends StatelessWidget {
|
|||||||
|
|
||||||
/// The hero tag of the image.
|
/// The hero tag of the image.
|
||||||
final String hero;
|
final String hero;
|
||||||
|
|
||||||
/// An extra widget with a translucent backdrop.
|
/// An extra widget with a translucent backdrop.
|
||||||
final Widget? extra;
|
final Widget? extra;
|
||||||
|
|
||||||
/// Callback for when the image is tapped.
|
/// Callback for when the image is tapped.
|
||||||
final void Function()? onTap;
|
final void Function()? onTap;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Hero(
|
return Hero(
|
||||||
@ -48,15 +48,14 @@ class AnimeCoverImage extends StatelessWidget {
|
|||||||
child: DecoratedBox(
|
child: DecoratedBox(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
image: DecorationImage(
|
image: DecorationImage(
|
||||||
image: cached ?
|
image: cached
|
||||||
CachedNetworkImageProvider(url) as ImageProvider :
|
? CachedNetworkImageProvider(url) as ImageProvider
|
||||||
NetworkImage(url),
|
: NetworkImage(url),
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
if (extra != null)
|
if (extra != null)
|
||||||
Positioned(
|
Positioned(
|
||||||
left: 0,
|
left: 0,
|
||||||
|
@ -27,7 +27,7 @@ class IntegerInputState extends State<IntegerInput> {
|
|||||||
|
|
||||||
/// The controller for the TextField.
|
/// The controller for the TextField.
|
||||||
final TextEditingController _controller = TextEditingController();
|
final TextEditingController _controller = TextEditingController();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
@ -44,7 +44,7 @@ class IntegerInputState extends State<IntegerInput> {
|
|||||||
_controller.text = '$_value';
|
_controller.text = '$_value';
|
||||||
widget.onChanged(_value);
|
widget.onChanged(_value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Row(
|
return Row(
|
||||||
@ -59,7 +59,6 @@ class IntegerInputState extends State<IntegerInput> {
|
|||||||
},
|
},
|
||||||
child: const Icon(Icons.remove),
|
child: const Icon(Icons.remove),
|
||||||
),
|
),
|
||||||
|
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
@ -84,7 +83,6 @@ class IntegerInputState extends State<IntegerInput> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
_value++;
|
_value++;
|
||||||
|
@ -15,7 +15,7 @@ class ListItem extends StatelessWidget {
|
|||||||
this.imageExtra,
|
this.imageExtra,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// URL for the cover image.
|
/// URL for the cover image.
|
||||||
final String thumbnailUrl;
|
final String thumbnailUrl;
|
||||||
|
|
||||||
@ -26,14 +26,14 @@ class ListItem extends StatelessWidget {
|
|||||||
final List<Widget> extra;
|
final List<Widget> extra;
|
||||||
|
|
||||||
final Widget? imageExtra;
|
final Widget? imageExtra;
|
||||||
|
|
||||||
/// Callbacks for the swipe functionality.
|
/// Callbacks for the swipe functionality.
|
||||||
final void Function()? onLeftSwipe;
|
final void Function()? onLeftSwipe;
|
||||||
final void Function()? onRightSwipe;
|
final void Function()? onRightSwipe;
|
||||||
|
|
||||||
/// Flag indicating whether the thumbnail image should be cached
|
/// Flag indicating whether the thumbnail image should be cached
|
||||||
final bool cached;
|
final bool cached;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SwipeableTile.swipeToTrigger(
|
return SwipeableTile.swipeToTrigger(
|
||||||
@ -66,9 +66,9 @@ class ListItem extends StatelessWidget {
|
|||||||
return Container();
|
return Container();
|
||||||
},
|
},
|
||||||
color: Theme.of(context).scaffoldBackgroundColor,
|
color: Theme.of(context).scaffoldBackgroundColor,
|
||||||
direction: onRightSwipe != null && onLeftSwipe != null ?
|
direction: onRightSwipe != null && onLeftSwipe != null
|
||||||
SwipeDirection.horizontal :
|
? SwipeDirection.horizontal
|
||||||
SwipeDirection.none,
|
: SwipeDirection.none,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(8),
|
padding: const EdgeInsets.all(8),
|
||||||
child: Row(
|
child: Row(
|
||||||
@ -81,7 +81,6 @@ class ListItem extends StatelessWidget {
|
|||||||
extra: imageExtra,
|
extra: imageExtra,
|
||||||
url: thumbnailUrl,
|
url: thumbnailUrl,
|
||||||
),
|
),
|
||||||
|
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
@ -97,7 +96,6 @@ class ListItem extends StatelessWidget {
|
|||||||
softWrap: true,
|
softWrap: true,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
|
|
||||||
...extra,
|
...extra,
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
Loading…
Reference in New Issue
Block a user