chore(all): Format
This commit is contained in:
parent
67c1903bd1
commit
9dcc99ca36
File diff suppressed because it is too large
Load Diff
@ -31,8 +31,8 @@ void main() async {
|
||||
|
||||
// Load animes
|
||||
GetIt.I.get<AnimeListBloc>().add(
|
||||
AnimesLoadedEvent(),
|
||||
);
|
||||
AnimesLoadedEvent(),
|
||||
);
|
||||
|
||||
runApp(
|
||||
MultiBlocProvider(
|
||||
@ -81,10 +81,14 @@ class MyApp extends StatelessWidget {
|
||||
onGenerateRoute: (settings) {
|
||||
switch (settings.name) {
|
||||
case '/':
|
||||
case animeListRoute: return AnimeListPage.route;
|
||||
case animeSearchRoute: return AnimeSearchPage.route;
|
||||
case detailsRoute: return DetailsPage.route;
|
||||
case aboutRoute: return AboutPage.route;
|
||||
case animeListRoute:
|
||||
return AnimeListPage.route;
|
||||
case animeSearchRoute:
|
||||
return AnimeSearchPage.route;
|
||||
case detailsRoute:
|
||||
return DetailsPage.route;
|
||||
case aboutRoute:
|
||||
return AboutPage.route;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
@ -10,18 +10,24 @@ class AnimeTrackingData with _$AnimeTrackingData, TrackingMedium {
|
||||
factory AnimeTrackingData(
|
||||
/// The ID of the anime
|
||||
String id,
|
||||
|
||||
/// The state of the anime
|
||||
@MediumTrackingStateConverter() MediumTrackingState state,
|
||||
|
||||
/// The title of the anime
|
||||
String title,
|
||||
|
||||
/// Episodes in total.
|
||||
int episodesWatched,
|
||||
|
||||
/// Episodes watched.
|
||||
int? episodesTotal,
|
||||
|
||||
/// URL to the thumbnail/cover art for the anime.
|
||||
String thumbnailUrl,
|
||||
) = _AnimeTrackingData;
|
||||
|
||||
/// 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(
|
||||
/// The ID of the manga
|
||||
String id,
|
||||
|
||||
/// The state of the manga
|
||||
@MediumTrackingStateConverter() MediumTrackingState state,
|
||||
|
||||
/// The title of the manga
|
||||
String title,
|
||||
|
||||
/// Chapters read.
|
||||
int chaptersRead,
|
||||
|
||||
/// Chapters read.
|
||||
int volumesOwned,
|
||||
|
||||
/// Episodes watched.
|
||||
int? chaptersTotal,
|
||||
|
||||
/// URL to the thumbnail/cover art for the manga.
|
||||
String thumbnailUrl,
|
||||
) = _MangaTrackingData;
|
||||
|
||||
/// JSON
|
||||
factory MangaTrackingData.fromJson(Map<String, dynamic> json) => _$MangaTrackingDataFromJson(json);
|
||||
factory MangaTrackingData.fromJson(Map<String, dynamic> json) =>
|
||||
_$MangaTrackingDataFromJson(json);
|
||||
}
|
||||
|
@ -32,47 +32,70 @@ abstract class TrackingMedium {
|
||||
|
||||
extension MediumStateExtension on MediumTrackingState {
|
||||
int toInteger() {
|
||||
assert(this != MediumTrackingState.all, 'MediumTrackingState.all must not be serialized');
|
||||
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.all: return -1;
|
||||
case MediumTrackingState.ongoing:
|
||||
return 0;
|
||||
case MediumTrackingState.completed:
|
||||
return 1;
|
||||
case MediumTrackingState.planned:
|
||||
return 2;
|
||||
case MediumTrackingState.dropped:
|
||||
return 3;
|
||||
case MediumTrackingState.all:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
case MediumTrackingState.ongoing:
|
||||
switch (type) {
|
||||
case TrackingMediumType.anime: return 'Watching';
|
||||
case TrackingMediumType.manga: return 'Reading';
|
||||
case TrackingMediumType.anime:
|
||||
return 'Watching';
|
||||
case TrackingMediumType.manga:
|
||||
return 'Reading';
|
||||
}
|
||||
case MediumTrackingState.completed: return 'Completed';
|
||||
case MediumTrackingState.completed:
|
||||
return 'Completed';
|
||||
case MediumTrackingState.planned:
|
||||
switch (type) {
|
||||
case TrackingMediumType.anime: return 'Plan to watch';
|
||||
case TrackingMediumType.manga: return 'Plan to read';
|
||||
case TrackingMediumType.anime:
|
||||
return 'Plan to watch';
|
||||
case TrackingMediumType.manga:
|
||||
return 'Plan to read';
|
||||
}
|
||||
case MediumTrackingState.dropped: return 'Dropped';
|
||||
case MediumTrackingState.all: return 'All';
|
||||
case MediumTrackingState.dropped:
|
||||
return 'Dropped';
|
||||
case MediumTrackingState.all:
|
||||
return 'All';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MediumTrackingStateConverter implements JsonConverter<MediumTrackingState, int> {
|
||||
class MediumTrackingStateConverter
|
||||
implements JsonConverter<MediumTrackingState, int> {
|
||||
const MediumTrackingStateConverter();
|
||||
|
||||
@override
|
||||
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 0:
|
||||
return MediumTrackingState.ongoing;
|
||||
case 1:
|
||||
return MediumTrackingState.completed;
|
||||
case 2:
|
||||
return MediumTrackingState.planned;
|
||||
case 3:
|
||||
return MediumTrackingState.dropped;
|
||||
}
|
||||
|
||||
return MediumTrackingState.planned;
|
||||
|
@ -46,18 +46,18 @@ class DatabaseService {
|
||||
final animes = await _db.query(animeTable);
|
||||
|
||||
return animes
|
||||
.cast<Map<String, dynamic>>()
|
||||
.map(AnimeTrackingData.fromJson)
|
||||
.toList();
|
||||
.cast<Map<String, dynamic>>()
|
||||
.map(AnimeTrackingData.fromJson)
|
||||
.toList();
|
||||
}
|
||||
|
||||
Future<List<MangaTrackingData>> loadMangas() async {
|
||||
final mangas = await _db.query(mangaTable);
|
||||
|
||||
return mangas
|
||||
.cast<Map<String, dynamic>>()
|
||||
.map(MangaTrackingData.fromJson)
|
||||
.toList();
|
||||
.cast<Map<String, dynamic>>()
|
||||
.map(MangaTrackingData.fromJson)
|
||||
.toList();
|
||||
}
|
||||
|
||||
Future<void> addAnime(AnimeTrackingData data) async {
|
||||
|
@ -30,30 +30,35 @@ class AnimeListBloc extends Bloc<AnimeListEvent, AnimeListState> {
|
||||
}
|
||||
|
||||
/// Internal anime state
|
||||
final List<AnimeTrackingData> _animes = List<AnimeTrackingData>.empty(growable: true);
|
||||
final List<MangaTrackingData> _mangas = List<MangaTrackingData>.empty(growable: true);
|
||||
final List<AnimeTrackingData> _animes =
|
||||
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;
|
||||
|
||||
if (filterState == MediumTrackingState.all) return _animes;
|
||||
|
||||
return _animes
|
||||
.where((anime) => anime.state == filterState)
|
||||
.toList();
|
||||
return _animes.where((anime) => anime.state == filterState).toList();
|
||||
}
|
||||
|
||||
List<MangaTrackingData> _getFilteredManga({MediumTrackingState? trackingState}) {
|
||||
List<MangaTrackingData> _getFilteredManga({
|
||||
MediumTrackingState? trackingState,
|
||||
}) {
|
||||
final filterState = trackingState ?? state.mangaFilterState;
|
||||
|
||||
if (state.mangaFilterState == MediumTrackingState.all) return _mangas;
|
||||
|
||||
return _mangas
|
||||
.where((manga) => manga.state == filterState)
|
||||
.toList();
|
||||
return _mangas.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
|
||||
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
|
||||
await GetIt.I.get<DatabaseService>().addManga(event.data);
|
||||
|
||||
@ -81,12 +89,16 @@ 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);
|
||||
if (index == -1) return;
|
||||
|
||||
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 newAnime = anime.copyWith(
|
||||
@ -103,7 +115,10 @@ class AnimeListBloc extends Bloc<AnimeListEvent, AnimeListState> {
|
||||
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);
|
||||
if (index == -1) return;
|
||||
|
||||
@ -125,7 +140,10 @@ class AnimeListBloc extends Bloc<AnimeListEvent, AnimeListState> {
|
||||
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(
|
||||
await GetIt.I.get<DatabaseService>().loadAnimes(),
|
||||
);
|
||||
@ -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(
|
||||
state.copyWith(
|
||||
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(
|
||||
state.copyWith(
|
||||
mangaFilterState: event.filterState,
|
||||
@ -159,7 +183,10 @@ 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(
|
||||
state.copyWith(
|
||||
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);
|
||||
assert(index != -1, 'The manga must exist');
|
||||
|
||||
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 newManga = manga.copyWith(
|
||||
@ -195,7 +226,10 @@ class AnimeListBloc extends Bloc<AnimeListEvent, AnimeListState> {
|
||||
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);
|
||||
if (index == -1) return;
|
||||
|
||||
@ -222,7 +256,10 @@ class AnimeListBloc extends Bloc<AnimeListEvent, AnimeListState> {
|
||||
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);
|
||||
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);
|
||||
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(
|
||||
state.copyWith(
|
||||
animes: List.from(
|
||||
@ -266,7 +309,10 @@ class AnimeListBloc extends Bloc<AnimeListEvent, AnimeListState> {
|
||||
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(
|
||||
state.copyWith(
|
||||
mangas: List.from(
|
||||
@ -284,7 +330,10 @@ class AnimeListBloc extends Bloc<AnimeListEvent, AnimeListState> {
|
||||
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(
|
||||
state.copyWith(
|
||||
buttonVisibility: event.state,
|
||||
|
@ -22,7 +22,10 @@ class AnimeSearchBloc extends Bloc<AnimeSearchEvent, AnimeSearchState> {
|
||||
on<ResultTappedEvent>(_onResultTapped);
|
||||
}
|
||||
|
||||
Future<void> _onRequested(AnimeSearchRequestedEvent event, Emitter<AnimeSearchState> emit) async {
|
||||
Future<void> _onRequested(
|
||||
AnimeSearchRequestedEvent event,
|
||||
Emitter<AnimeSearchState> emit,
|
||||
) async {
|
||||
emit(
|
||||
state.copyWith(
|
||||
searchQuery: '',
|
||||
@ -33,13 +36,16 @@ class AnimeSearchBloc extends Bloc<AnimeSearchEvent, AnimeSearchState> {
|
||||
);
|
||||
|
||||
GetIt.I.get<NavigationBloc>().add(
|
||||
PushedNamedEvent(
|
||||
const NavigationDestination(animeSearchRoute),
|
||||
),
|
||||
);
|
||||
PushedNamedEvent(
|
||||
const NavigationDestination(animeSearchRoute),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onQueryChanged(SearchQueryChangedEvent event, Emitter<AnimeSearchState> emit) async {
|
||||
Future<void> _onQueryChanged(
|
||||
SearchQueryChangedEvent event,
|
||||
Emitter<AnimeSearchState> emit,
|
||||
) async {
|
||||
emit(
|
||||
state.copyWith(
|
||||
searchQuery: event.query,
|
||||
@ -47,7 +53,10 @@ class AnimeSearchBloc extends Bloc<AnimeSearchEvent, AnimeSearchState> {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onQuerySubmitted(SearchQuerySubmittedEvent event, Emitter<AnimeSearchState> emit) async {
|
||||
Future<void> _onQuerySubmitted(
|
||||
SearchQuerySubmittedEvent event,
|
||||
Emitter<AnimeSearchState> emit,
|
||||
) async {
|
||||
if (state.searchQuery.isEmpty) return;
|
||||
|
||||
emit(
|
||||
@ -65,13 +74,17 @@ class AnimeSearchBloc extends Bloc<AnimeSearchEvent, AnimeSearchState> {
|
||||
emit(
|
||||
state.copyWith(
|
||||
working: false,
|
||||
searchResults: result.map((Anime anime) => SearchResult(
|
||||
anime.title,
|
||||
anime.malId.toString(),
|
||||
anime.episodes,
|
||||
anime.imageUrl,
|
||||
anime.synopsis ?? '',
|
||||
),).toList(),
|
||||
searchResults: result
|
||||
.map(
|
||||
(Anime anime) => SearchResult(
|
||||
anime.title,
|
||||
anime.malId.toString(),
|
||||
anime.episodes,
|
||||
anime.imageUrl,
|
||||
anime.synopsis ?? '',
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
@ -83,46 +96,53 @@ class AnimeSearchBloc extends Bloc<AnimeSearchEvent, AnimeSearchState> {
|
||||
emit(
|
||||
state.copyWith(
|
||||
working: false,
|
||||
searchResults: result.map((Manga manga) => SearchResult(
|
||||
manga.title,
|
||||
manga.malId.toString(),
|
||||
manga.chapters,
|
||||
manga.imageUrl,
|
||||
manga.synopsis ?? '',
|
||||
),).toList(),
|
||||
searchResults: result
|
||||
.map(
|
||||
(Manga manga) => SearchResult(
|
||||
manga.title,
|
||||
manga.malId.toString(),
|
||||
manga.chapters,
|
||||
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(
|
||||
state.trackingType == TrackingMediumType.anime ?
|
||||
list.AnimeAddedEvent(
|
||||
AnimeTrackingData(
|
||||
event.result.id,
|
||||
MediumTrackingState.ongoing,
|
||||
event.result.title,
|
||||
0,
|
||||
event.result.total,
|
||||
event.result.thumbnailUrl,
|
||||
),
|
||||
) :
|
||||
list.MangaAddedEvent(
|
||||
MangaTrackingData(
|
||||
event.result.id,
|
||||
MediumTrackingState.ongoing,
|
||||
event.result.title,
|
||||
0,
|
||||
0,
|
||||
event.result.total,
|
||||
event.result.thumbnailUrl,
|
||||
),
|
||||
),
|
||||
);
|
||||
state.trackingType == TrackingMediumType.anime
|
||||
? list.AnimeAddedEvent(
|
||||
AnimeTrackingData(
|
||||
event.result.id,
|
||||
MediumTrackingState.ongoing,
|
||||
event.result.title,
|
||||
0,
|
||||
event.result.total,
|
||||
event.result.thumbnailUrl,
|
||||
),
|
||||
)
|
||||
: list.MangaAddedEvent(
|
||||
MangaTrackingData(
|
||||
event.result.id,
|
||||
MediumTrackingState.ongoing,
|
||||
event.result.title,
|
||||
0,
|
||||
0,
|
||||
event.result.total,
|
||||
event.result.thumbnailUrl,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
GetIt.I.get<NavigationBloc>().add(
|
||||
PoppedRouteEvent(),
|
||||
);
|
||||
PoppedRouteEvent(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,10 @@ class DetailsBloc extends Bloc<DetailsEvent, DetailsState> {
|
||||
on<ItemRemovedEvent>(_onItemRemoved);
|
||||
}
|
||||
|
||||
Future<void> _onAnimeRequested(AnimeDetailsRequestedEvent event, Emitter<DetailsState> emit) async {
|
||||
Future<void> _onAnimeRequested(
|
||||
AnimeDetailsRequestedEvent event,
|
||||
Emitter<DetailsState> emit,
|
||||
) async {
|
||||
emit(
|
||||
state.copyWith(
|
||||
trackingType: TrackingMediumType.anime,
|
||||
@ -30,13 +33,16 @@ class DetailsBloc extends Bloc<DetailsEvent, DetailsState> {
|
||||
);
|
||||
|
||||
GetIt.I.get<NavigationBloc>().add(
|
||||
PushedNamedEvent(
|
||||
const NavigationDestination(detailsRoute),
|
||||
),
|
||||
);
|
||||
PushedNamedEvent(
|
||||
const NavigationDestination(detailsRoute),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onMangaRequested(MangaDetailsRequestedEvent event, Emitter<DetailsState> emit) async {
|
||||
Future<void> _onMangaRequested(
|
||||
MangaDetailsRequestedEvent event,
|
||||
Emitter<DetailsState> emit,
|
||||
) async {
|
||||
emit(
|
||||
state.copyWith(
|
||||
trackingType: TrackingMediumType.manga,
|
||||
@ -45,13 +51,16 @@ class DetailsBloc extends Bloc<DetailsEvent, DetailsState> {
|
||||
);
|
||||
|
||||
GetIt.I.get<NavigationBloc>().add(
|
||||
PushedNamedEvent(
|
||||
const NavigationDestination(detailsRoute),
|
||||
),
|
||||
);
|
||||
PushedNamedEvent(
|
||||
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) {
|
||||
emit(
|
||||
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(
|
||||
AnimeUpdatedEvent(event.data as AnimeTrackingData),
|
||||
);
|
||||
AnimeUpdatedEvent(event.data as AnimeTrackingData),
|
||||
);
|
||||
} else {
|
||||
emit(
|
||||
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(
|
||||
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(
|
||||
state.copyWith(
|
||||
data: null,
|
||||
|
@ -13,14 +13,20 @@ class NavigationBloc extends Bloc<NavigationEvent, NavigationState> {
|
||||
}
|
||||
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(
|
||||
event.destination.path,
|
||||
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(
|
||||
event.destination.path,
|
||||
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(
|
||||
event.destination.path,
|
||||
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();
|
||||
}
|
||||
|
||||
|
@ -2,11 +2,9 @@ part of 'navigation_bloc.dart';
|
||||
|
||||
class NavigationDestination {
|
||||
const NavigationDestination(
|
||||
this.path,
|
||||
{
|
||||
this.arguments,
|
||||
}
|
||||
);
|
||||
this.path, {
|
||||
this.arguments,
|
||||
});
|
||||
final String path;
|
||||
final Object? arguments;
|
||||
}
|
||||
|
@ -9,11 +9,11 @@ class AboutPage extends StatelessWidget {
|
||||
});
|
||||
|
||||
static MaterialPageRoute<dynamic> get route => MaterialPageRoute<dynamic>(
|
||||
builder: (_) => const AboutPage(),
|
||||
settings: const RouteSettings(
|
||||
name: aboutRoute,
|
||||
),
|
||||
);
|
||||
builder: (_) => const AboutPage(),
|
||||
settings: const RouteSettings(
|
||||
name: aboutRoute,
|
||||
),
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -34,7 +34,6 @@ class AboutPage extends StatelessWidget {
|
||||
'AniTrack',
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
|
||||
ElevatedButton(
|
||||
onPressed: () async {
|
||||
await launchUrl(
|
||||
|
@ -16,11 +16,11 @@ class AnimeListPage extends StatefulWidget {
|
||||
});
|
||||
|
||||
static MaterialPageRoute<dynamic> get route => MaterialPageRoute<dynamic>(
|
||||
builder: (_) => AnimeListPage(),
|
||||
settings: const RouteSettings(
|
||||
name: animeListRoute,
|
||||
),
|
||||
);
|
||||
builder: (_) => const AnimeListPage(),
|
||||
settings: const RouteSettings(
|
||||
name: animeListRoute,
|
||||
),
|
||||
);
|
||||
|
||||
@override
|
||||
AnimeListPageState createState() => AnimeListPageState();
|
||||
@ -39,7 +39,8 @@ class AnimeListPageState extends State<AnimeListPage> {
|
||||
void _onAnimeListScrolled() {
|
||||
//print(_animeScrollController.position.maxScrollExtent);
|
||||
final bloc = GetIt.I.get<AnimeListBloc>();
|
||||
if (_animeScrollController.offset + 20 >= _animeScrollController.position.maxScrollExtent) {
|
||||
if (_animeScrollController.offset + 20 >=
|
||||
_animeScrollController.position.maxScrollExtent) {
|
||||
if (bloc.state.buttonVisibility) {
|
||||
bloc.add(
|
||||
AddButtonVisibilitySetEvent(false),
|
||||
@ -56,12 +57,16 @@ class AnimeListPageState extends State<AnimeListPage> {
|
||||
|
||||
String _getPageTitle(TrackingMediumType type) {
|
||||
switch (type) {
|
||||
case TrackingMediumType.anime: return 'Anime';
|
||||
case TrackingMediumType.manga: return 'Manga';
|
||||
case TrackingMediumType.anime:
|
||||
return 'Anime';
|
||||
case TrackingMediumType.manga:
|
||||
return 'Manga';
|
||||
}
|
||||
}
|
||||
|
||||
List<PopupMenuItem<MediumTrackingState>> _getPopupButtonItems(TrackingMediumType type) {
|
||||
List<PopupMenuItem<MediumTrackingState>> _getPopupButtonItems(
|
||||
TrackingMediumType type,
|
||||
) {
|
||||
return [
|
||||
PopupMenuItem<MediumTrackingState>(
|
||||
value: MediumTrackingState.ongoing,
|
||||
@ -96,8 +101,8 @@ class AnimeListPageState extends State<AnimeListPage> {
|
||||
initialValue: state.animeFilterState,
|
||||
onSelected: (filterState) {
|
||||
context.read<AnimeListBloc>().add(
|
||||
AnimeFilterChangedEvent(filterState),
|
||||
);
|
||||
AnimeFilterChangedEvent(filterState),
|
||||
);
|
||||
},
|
||||
itemBuilder: (_) => _getPopupButtonItems(TrackingMediumType.anime),
|
||||
);
|
||||
@ -109,8 +114,8 @@ class AnimeListPageState extends State<AnimeListPage> {
|
||||
initialValue: state.mangaFilterState,
|
||||
onSelected: (filterState) {
|
||||
context.read<AnimeListBloc>().add(
|
||||
MangaFilterChangedEvent(filterState),
|
||||
);
|
||||
MangaFilterChangedEvent(filterState),
|
||||
);
|
||||
},
|
||||
itemBuilder: (_) => _getPopupButtonItems(TrackingMediumType.manga),
|
||||
);
|
||||
@ -145,7 +150,6 @@ class AnimeListPageState extends State<AnimeListPage> {
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
ListTile(
|
||||
leading: const Icon(Icons.info),
|
||||
title: const Text('About'),
|
||||
@ -178,37 +182,37 @@ class AnimeListPageState extends State<AnimeListPage> {
|
||||
return GridItem(
|
||||
minusCallback: () {
|
||||
context.read<AnimeListBloc>().add(
|
||||
AnimeEpisodeDecrementedEvent(
|
||||
anime.id,
|
||||
),
|
||||
);
|
||||
AnimeEpisodeDecrementedEvent(
|
||||
anime.id,
|
||||
),
|
||||
);
|
||||
},
|
||||
plusCallback: () {
|
||||
context.read<AnimeListBloc>().add(
|
||||
AnimeEpisodeIncrementedEvent(
|
||||
anime.id,
|
||||
),
|
||||
);
|
||||
AnimeEpisodeIncrementedEvent(
|
||||
anime.id,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: AnimeCoverImage(
|
||||
url: anime.thumbnailUrl,
|
||||
hero: anime.id,
|
||||
onTap: () {
|
||||
context.read<DetailsBloc>().add(
|
||||
AnimeDetailsRequestedEvent(anime),
|
||||
);
|
||||
},
|
||||
extra: Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(right: 8),
|
||||
child: Text(
|
||||
'${anime.episodesWatched}/${anime.episodesTotal ?? "???"}',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
url: anime.thumbnailUrl,
|
||||
hero: anime.id,
|
||||
onTap: () {
|
||||
context.read<DetailsBloc>().add(
|
||||
AnimeDetailsRequestedEvent(anime),
|
||||
);
|
||||
},
|
||||
extra: Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(right: 8),
|
||||
child: Text(
|
||||
'${anime.episodesWatched}/${anime.episodesTotal ?? "???"}',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
@ -228,37 +232,37 @@ class AnimeListPageState extends State<AnimeListPage> {
|
||||
return GridItem(
|
||||
minusCallback: () {
|
||||
context.read<AnimeListBloc>().add(
|
||||
MangaChapterDecrementedEvent(
|
||||
manga.id,
|
||||
),
|
||||
);
|
||||
MangaChapterDecrementedEvent(
|
||||
manga.id,
|
||||
),
|
||||
);
|
||||
},
|
||||
plusCallback: () {
|
||||
context.read<AnimeListBloc>().add(
|
||||
MangaChapterIncrementedEvent(
|
||||
manga.id,
|
||||
),
|
||||
);
|
||||
MangaChapterIncrementedEvent(
|
||||
manga.id,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: AnimeCoverImage(
|
||||
hero: manga.id,
|
||||
url: manga.thumbnailUrl,
|
||||
onTap: () {
|
||||
context.read<DetailsBloc>().add(
|
||||
MangaDetailsRequestedEvent(manga),
|
||||
);
|
||||
},
|
||||
extra: Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(right: 8),
|
||||
child: Text(
|
||||
'${manga.chaptersRead}/${manga.chaptersTotal ?? "???"}',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
url: manga.thumbnailUrl,
|
||||
onTap: () {
|
||||
context.read<DetailsBloc>().add(
|
||||
MangaDetailsRequestedEvent(manga),
|
||||
);
|
||||
},
|
||||
extra: Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(right: 8),
|
||||
child: Text(
|
||||
'${manga.chaptersRead}/${manga.chaptersTotal ?? "???"}',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
@ -266,19 +270,18 @@ class AnimeListPageState extends State<AnimeListPage> {
|
||||
],
|
||||
),
|
||||
floatingActionButton: BlocBuilder<AnimeListBloc, AnimeListState>(
|
||||
buildWhen: (prev, next) => prev.buttonVisibility != next.buttonVisibility,
|
||||
buildWhen: (prev, next) =>
|
||||
prev.buttonVisibility != next.buttonVisibility,
|
||||
builder: (context, state) {
|
||||
return AnimatedScale(
|
||||
duration: const Duration(milliseconds: 250),
|
||||
scale: state.buttonVisibility ?
|
||||
1 :
|
||||
0,
|
||||
scale: state.buttonVisibility ? 1 : 0,
|
||||
curve: Curves.easeInOutQuint,
|
||||
child: FloatingActionButton(
|
||||
onPressed: () {
|
||||
context.read<AnimeSearchBloc>().add(
|
||||
AnimeSearchRequestedEvent(state.trackingType),
|
||||
);
|
||||
AnimeSearchRequestedEvent(state.trackingType),
|
||||
);
|
||||
},
|
||||
tooltip: 'Add new item',
|
||||
child: const Icon(Icons.add),
|
||||
@ -287,17 +290,16 @@ class AnimeListPageState extends State<AnimeListPage> {
|
||||
},
|
||||
),
|
||||
bottomNavigationBar: BottomBar(
|
||||
selectedIndex: state.trackingType == TrackingMediumType.anime ?
|
||||
0 :
|
||||
1,
|
||||
selectedIndex:
|
||||
state.trackingType == TrackingMediumType.anime ? 0 : 1,
|
||||
onTap: (int index) {
|
||||
context.read<AnimeListBloc>().add(
|
||||
AnimeTrackingTypeChanged(
|
||||
index == 0 ?
|
||||
TrackingMediumType.anime :
|
||||
TrackingMediumType.manga,
|
||||
),
|
||||
);
|
||||
AnimeTrackingTypeChanged(
|
||||
index == 0
|
||||
? TrackingMediumType.anime
|
||||
: TrackingMediumType.manga,
|
||||
),
|
||||
);
|
||||
|
||||
_controller.jumpToPage(index);
|
||||
},
|
||||
|
@ -11,11 +11,11 @@ class AnimeSearchPage extends StatelessWidget {
|
||||
});
|
||||
|
||||
static MaterialPageRoute<dynamic> get route => MaterialPageRoute<dynamic>(
|
||||
builder: (_) => const AnimeSearchPage(),
|
||||
settings: const RouteSettings(
|
||||
name: animeSearchRoute,
|
||||
),
|
||||
);
|
||||
builder: (_) => const AnimeSearchPage(),
|
||||
settings: const RouteSettings(
|
||||
name: animeSearchRoute,
|
||||
),
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -24,9 +24,9 @@ class AnimeSearchPage extends StatelessWidget {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(
|
||||
state.trackingType == TrackingMediumType.anime ?
|
||||
'Anime Search' :
|
||||
'Manga Search',
|
||||
state.trackingType == TrackingMediumType.anime
|
||||
? 'Anime Search'
|
||||
: 'Manga Search',
|
||||
),
|
||||
),
|
||||
body: Column(
|
||||
@ -40,17 +40,16 @@ class AnimeSearchPage extends StatelessWidget {
|
||||
),
|
||||
onSubmitted: (_) {
|
||||
context.read<AnimeSearchBloc>().add(
|
||||
SearchQuerySubmittedEvent(),
|
||||
);
|
||||
SearchQuerySubmittedEvent(),
|
||||
);
|
||||
},
|
||||
onChanged: (value) {
|
||||
context.read<AnimeSearchBloc>().add(
|
||||
SearchQueryChangedEvent(value),
|
||||
);
|
||||
SearchQueryChangedEvent(value),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
if (state.working)
|
||||
const Expanded(
|
||||
child: Align(
|
||||
@ -66,8 +65,8 @@ class AnimeSearchPage extends StatelessWidget {
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
context.read<AnimeSearchBloc>().add(
|
||||
ResultTappedEvent(item),
|
||||
);
|
||||
ResultTappedEvent(item),
|
||||
);
|
||||
},
|
||||
child: ListItem(
|
||||
title: item.title,
|
||||
|
@ -16,11 +16,11 @@ class DetailsPage extends StatelessWidget {
|
||||
});
|
||||
|
||||
static MaterialPageRoute<dynamic> get route => MaterialPageRoute<dynamic>(
|
||||
builder: (_) => const DetailsPage(),
|
||||
settings: const RouteSettings(
|
||||
name: detailsRoute,
|
||||
),
|
||||
);
|
||||
builder: (_) => const DetailsPage(),
|
||||
settings: const RouteSettings(
|
||||
name: detailsRoute,
|
||||
),
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -30,195 +30,213 @@ class DetailsPage extends StatelessWidget {
|
||||
),
|
||||
body: BlocBuilder<DetailsBloc, DetailsState>(
|
||||
builder: (context, state) {
|
||||
return state.data == null ?
|
||||
Container() :
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: ListView(
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [AnimeCoverImage(
|
||||
url: state.data!.thumbnailUrl,
|
||||
hero: state.data!.id,
|
||||
),
|
||||
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 8,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
state.data!.title,
|
||||
textAlign: TextAlign.left,
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
maxLines: 2,
|
||||
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,
|
||||
return state.data == null
|
||||
? Container()
|
||||
: Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: ListView(
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
AnimeCoverImage(
|
||||
url: state.data!.thumbnailUrl,
|
||||
hero: state.data!.id,
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 8,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
state.data!.title,
|
||||
textAlign: TextAlign.left,
|
||||
style:
|
||||
Theme.of(context).textTheme.titleLarge,
|
||||
maxLines: 2,
|
||||
softWrap: true,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: const Icon(Icons.delete),
|
||||
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,
|
||||
),
|
||||
);
|
||||
},
|
||||
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,
|
||||
),
|
||||
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,
|
||||
),
|
||||
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,
|
||||
),
|
||||
);
|
||||
} else if (state.trackingType == TrackingMediumType.manga) {
|
||||
context.read<DetailsBloc>().add(
|
||||
DetailsUpdatedEvent(
|
||||
(state.data! as MangaTrackingData).copyWith(
|
||||
state: newState,
|
||||
),
|
||||
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,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
@ -76,54 +76,56 @@ class DropdownSelectorState<T> extends State<DropdownSelector<T>> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
index = widget.values.indexWhere((item) => item.value == widget.initialValue);
|
||||
index =
|
||||
widget.values.indexWhere((item) => item.value == widget.initialValue);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Card(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
child: InkWell(
|
||||
onTap: () async {
|
||||
final result = await showDialog<T>(
|
||||
context: context,
|
||||
builder: (context) => DropdownSelectorDialog<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,
|
||||
),
|
||||
],
|
||||
children: [
|
||||
Expanded(
|
||||
child: Card(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
child: InkWell(
|
||||
onTap: () async {
|
||||
final result = await showDialog<T>(
|
||||
context: context,
|
||||
builder: (context) => DropdownSelectorDialog<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,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ class GridItemState extends State<GridItem> {
|
||||
onHorizontalDragUpdate: (details) {
|
||||
setState(() {
|
||||
_offset += details.delta.dx;
|
||||
_translationX = 160 / (1 + exp(-1 * (1/30) * _offset)) - 80;
|
||||
_translationX = 160 / (1 + exp(-1 * (1 / 30) * _offset)) - 80;
|
||||
});
|
||||
},
|
||||
onHorizontalDragEnd: (_) {
|
||||
|
@ -48,15 +48,14 @@ class AnimeCoverImage extends StatelessWidget {
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
image: DecorationImage(
|
||||
image: cached ?
|
||||
CachedNetworkImageProvider(url) as ImageProvider :
|
||||
NetworkImage(url),
|
||||
image: cached
|
||||
? CachedNetworkImageProvider(url) as ImageProvider
|
||||
: NetworkImage(url),
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
if (extra != null)
|
||||
Positioned(
|
||||
left: 0,
|
||||
|
@ -59,7 +59,6 @@ class IntegerInputState extends State<IntegerInput> {
|
||||
},
|
||||
child: const Icon(Icons.remove),
|
||||
),
|
||||
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
@ -84,7 +83,6 @@ class IntegerInputState extends State<IntegerInput> {
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
_value++;
|
||||
|
@ -66,9 +66,9 @@ class ListItem extends StatelessWidget {
|
||||
return Container();
|
||||
},
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
direction: onRightSwipe != null && onLeftSwipe != null ?
|
||||
SwipeDirection.horizontal :
|
||||
SwipeDirection.none,
|
||||
direction: onRightSwipe != null && onLeftSwipe != null
|
||||
? SwipeDirection.horizontal
|
||||
: SwipeDirection.none,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Row(
|
||||
@ -81,7 +81,6 @@ class ListItem extends StatelessWidget {
|
||||
extra: imageExtra,
|
||||
url: thumbnailUrl,
|
||||
),
|
||||
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
@ -97,7 +96,6 @@ class ListItem extends StatelessWidget {
|
||||
softWrap: true,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
|
||||
...extra,
|
||||
],
|
||||
),
|
||||
|
Loading…
Reference in New Issue
Block a user