feat(meta): Cleanup
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
import 'dart:math';
|
||||
import 'package:anitrack/src/data/anime.dart';
|
||||
import 'package:anitrack/src/data/manga.dart';
|
||||
import 'package:anitrack/src/data/type.dart';
|
||||
|
||||
@@ -18,8 +18,10 @@ final _privateConstructorUsedError = UnsupportedError(
|
||||
mixin _$AnimeListState {
|
||||
List<AnimeTrackingData> get animes => throw _privateConstructorUsedError;
|
||||
List<MangaTrackingData> get mangas => throw _privateConstructorUsedError;
|
||||
AnimeTrackingState get animeFilterState => throw _privateConstructorUsedError;
|
||||
MangaTrackingState get mangaFilterState => throw _privateConstructorUsedError;
|
||||
MediumTrackingState get animeFilterState =>
|
||||
throw _privateConstructorUsedError;
|
||||
MediumTrackingState get mangaFilterState =>
|
||||
throw _privateConstructorUsedError;
|
||||
TrackingMediumType get trackingType => throw _privateConstructorUsedError;
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@@ -35,8 +37,8 @@ abstract class $AnimeListStateCopyWith<$Res> {
|
||||
$Res call(
|
||||
{List<AnimeTrackingData> animes,
|
||||
List<MangaTrackingData> mangas,
|
||||
AnimeTrackingState animeFilterState,
|
||||
MangaTrackingState mangaFilterState,
|
||||
MediumTrackingState animeFilterState,
|
||||
MediumTrackingState mangaFilterState,
|
||||
TrackingMediumType trackingType});
|
||||
}
|
||||
|
||||
@@ -69,11 +71,11 @@ class _$AnimeListStateCopyWithImpl<$Res>
|
||||
animeFilterState: animeFilterState == freezed
|
||||
? _value.animeFilterState
|
||||
: animeFilterState // ignore: cast_nullable_to_non_nullable
|
||||
as AnimeTrackingState,
|
||||
as MediumTrackingState,
|
||||
mangaFilterState: mangaFilterState == freezed
|
||||
? _value.mangaFilterState
|
||||
: mangaFilterState // ignore: cast_nullable_to_non_nullable
|
||||
as MangaTrackingState,
|
||||
as MediumTrackingState,
|
||||
trackingType: trackingType == freezed
|
||||
? _value.trackingType
|
||||
: trackingType // ignore: cast_nullable_to_non_nullable
|
||||
@@ -92,8 +94,8 @@ abstract class _$$_AnimeListStateCopyWith<$Res>
|
||||
$Res call(
|
||||
{List<AnimeTrackingData> animes,
|
||||
List<MangaTrackingData> mangas,
|
||||
AnimeTrackingState animeFilterState,
|
||||
MangaTrackingState mangaFilterState,
|
||||
MediumTrackingState animeFilterState,
|
||||
MediumTrackingState mangaFilterState,
|
||||
TrackingMediumType trackingType});
|
||||
}
|
||||
|
||||
@@ -128,11 +130,11 @@ class __$$_AnimeListStateCopyWithImpl<$Res>
|
||||
animeFilterState: animeFilterState == freezed
|
||||
? _value.animeFilterState
|
||||
: animeFilterState // ignore: cast_nullable_to_non_nullable
|
||||
as AnimeTrackingState,
|
||||
as MediumTrackingState,
|
||||
mangaFilterState: mangaFilterState == freezed
|
||||
? _value.mangaFilterState
|
||||
: mangaFilterState // ignore: cast_nullable_to_non_nullable
|
||||
as MangaTrackingState,
|
||||
as MediumTrackingState,
|
||||
trackingType: trackingType == freezed
|
||||
? _value.trackingType
|
||||
: trackingType // ignore: cast_nullable_to_non_nullable
|
||||
@@ -147,8 +149,8 @@ class _$_AnimeListState implements _AnimeListState {
|
||||
_$_AnimeListState(
|
||||
{final List<AnimeTrackingData> animes = const [],
|
||||
final List<MangaTrackingData> mangas = const [],
|
||||
this.animeFilterState = AnimeTrackingState.watching,
|
||||
this.mangaFilterState = MangaTrackingState.reading,
|
||||
this.animeFilterState = MediumTrackingState.ongoing,
|
||||
this.mangaFilterState = MediumTrackingState.ongoing,
|
||||
this.trackingType = TrackingMediumType.anime})
|
||||
: _animes = animes,
|
||||
_mangas = mangas;
|
||||
@@ -171,10 +173,10 @@ class _$_AnimeListState implements _AnimeListState {
|
||||
|
||||
@override
|
||||
@JsonKey()
|
||||
final AnimeTrackingState animeFilterState;
|
||||
final MediumTrackingState animeFilterState;
|
||||
@override
|
||||
@JsonKey()
|
||||
final MangaTrackingState mangaFilterState;
|
||||
final MediumTrackingState mangaFilterState;
|
||||
@override
|
||||
@JsonKey()
|
||||
final TrackingMediumType trackingType;
|
||||
@@ -218,8 +220,8 @@ abstract class _AnimeListState implements AnimeListState {
|
||||
factory _AnimeListState(
|
||||
{final List<AnimeTrackingData> animes,
|
||||
final List<MangaTrackingData> mangas,
|
||||
final AnimeTrackingState animeFilterState,
|
||||
final MangaTrackingState mangaFilterState,
|
||||
final MediumTrackingState animeFilterState,
|
||||
final MediumTrackingState mangaFilterState,
|
||||
final TrackingMediumType trackingType}) = _$_AnimeListState;
|
||||
|
||||
@override
|
||||
@@ -227,9 +229,9 @@ abstract class _AnimeListState implements AnimeListState {
|
||||
@override
|
||||
List<MangaTrackingData> get mangas;
|
||||
@override
|
||||
AnimeTrackingState get animeFilterState;
|
||||
MediumTrackingState get animeFilterState;
|
||||
@override
|
||||
MangaTrackingState get mangaFilterState;
|
||||
MediumTrackingState get mangaFilterState;
|
||||
@override
|
||||
TrackingMediumType get trackingType;
|
||||
@override
|
||||
|
||||
@@ -31,7 +31,7 @@ class AnimeFilterChangedEvent extends AnimeListEvent {
|
||||
AnimeFilterChangedEvent(this.filterState);
|
||||
|
||||
/// The state to filter
|
||||
final AnimeTrackingState filterState;
|
||||
final MediumTrackingState filterState;
|
||||
}
|
||||
|
||||
/// Triggered when the view is changed from the anime or the manga view
|
||||
@@ -60,7 +60,7 @@ class MangaFilterChangedEvent extends AnimeListEvent {
|
||||
MangaFilterChangedEvent(this.filterState);
|
||||
|
||||
/// The state to filter
|
||||
final MangaTrackingState filterState;
|
||||
final MediumTrackingState filterState;
|
||||
}
|
||||
|
||||
class MangaChapterIncrementedEvent extends AnimeListEvent {
|
||||
|
||||
@@ -5,8 +5,8 @@ class AnimeListState with _$AnimeListState {
|
||||
factory AnimeListState({
|
||||
@Default([]) List<AnimeTrackingData> animes,
|
||||
@Default([]) List<MangaTrackingData> mangas,
|
||||
@Default(AnimeTrackingState.watching) AnimeTrackingState animeFilterState,
|
||||
@Default(MangaTrackingState.reading) MangaTrackingState mangaFilterState,
|
||||
@Default(MediumTrackingState.ongoing) MediumTrackingState animeFilterState,
|
||||
@Default(MediumTrackingState.ongoing) MediumTrackingState mangaFilterState,
|
||||
@Default(TrackingMediumType.anime) TrackingMediumType trackingType,
|
||||
}) = _AnimeListState;
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@ import 'package:anitrack/src/data/anime.dart';
|
||||
import 'package:anitrack/src/data/manga.dart';
|
||||
import 'package:anitrack/src/data/search_result.dart';
|
||||
import 'package:anitrack/src/data/type.dart';
|
||||
import 'package:anitrack/src/ui/constants.dart';
|
||||
import 'package:anitrack/src/ui/bloc/anime_list_bloc.dart' as list;
|
||||
import 'package:anitrack/src/ui/bloc/navigation_bloc.dart';
|
||||
import 'package:anitrack/src/ui/constants.dart';
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
@@ -34,7 +34,7 @@ class AnimeSearchBloc extends Bloc<AnimeSearchEvent, AnimeSearchState> {
|
||||
|
||||
GetIt.I.get<NavigationBloc>().add(
|
||||
PushedNamedEvent(
|
||||
NavigationDestination(animeSearchRoute),
|
||||
const NavigationDestination(animeSearchRoute),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -101,7 +101,7 @@ class AnimeSearchBloc extends Bloc<AnimeSearchEvent, AnimeSearchState> {
|
||||
list.AnimeAddedEvent(
|
||||
AnimeTrackingData(
|
||||
event.result.id,
|
||||
AnimeTrackingState.watching,
|
||||
MediumTrackingState.ongoing,
|
||||
event.result.title,
|
||||
0,
|
||||
event.result.total,
|
||||
@@ -111,14 +111,14 @@ class AnimeSearchBloc extends Bloc<AnimeSearchEvent, AnimeSearchState> {
|
||||
list.MangaAddedEvent(
|
||||
MangaTrackingData(
|
||||
event.result.id,
|
||||
MangaTrackingState.reading,
|
||||
MediumTrackingState.ongoing,
|
||||
event.result.title,
|
||||
0,
|
||||
0,
|
||||
event.result.total,
|
||||
event.result.thumbnailUrl,
|
||||
),
|
||||
)
|
||||
),
|
||||
);
|
||||
|
||||
GetIt.I.get<NavigationBloc>().add(
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import 'package:anitrack/src/data/anime.dart';
|
||||
import 'package:anitrack/src/data/manga.dart';
|
||||
import 'package:anitrack/src/data/type.dart';
|
||||
import 'package:anitrack/src/service/database.dart';
|
||||
import 'package:anitrack/src/ui/bloc/anime_list_bloc.dart';
|
||||
import 'package:anitrack/src/ui/bloc/navigation_bloc.dart';
|
||||
import 'package:anitrack/src/ui/constants.dart';
|
||||
import 'package:anitrack/src/service/database.dart';
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
@@ -30,9 +30,7 @@ class DetailsBloc extends Bloc<DetailsEvent, DetailsState> {
|
||||
|
||||
GetIt.I.get<NavigationBloc>().add(
|
||||
PushedNamedEvent(
|
||||
NavigationDestination(
|
||||
detailsRoute,
|
||||
),
|
||||
const NavigationDestination(detailsRoute),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -47,9 +45,7 @@ class DetailsBloc extends Bloc<DetailsEvent, DetailsState> {
|
||||
|
||||
GetIt.I.get<NavigationBloc>().add(
|
||||
PushedNamedEvent(
|
||||
NavigationDestination(
|
||||
detailsRoute,
|
||||
),
|
||||
const NavigationDestination(detailsRoute),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'navigation_event.dart';
|
||||
part 'navigation_state.dart';
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import 'package:anitrack/src/data/anime.dart';
|
||||
import 'package:anitrack/src/data/manga.dart';
|
||||
import 'package:anitrack/src/data/type.dart';
|
||||
import 'package:anitrack/src/ui/bloc/anime_list_bloc.dart';
|
||||
import 'package:anitrack/src/ui/bloc/anime_search_bloc.dart';
|
||||
@@ -11,6 +9,10 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class AnimeListPage extends StatelessWidget {
|
||||
AnimeListPage({
|
||||
super.key,
|
||||
});
|
||||
|
||||
static MaterialPageRoute<dynamic> get route => MaterialPageRoute<dynamic>(
|
||||
builder: (_) => AnimeListPage(),
|
||||
settings: const RouteSettings(
|
||||
@@ -27,11 +29,36 @@ class AnimeListPage extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
List<PopupMenuItem<MediumTrackingState>> _getPopupButtonItems(TrackingMediumType type) {
|
||||
return [
|
||||
PopupMenuItem<MediumTrackingState>(
|
||||
value: MediumTrackingState.ongoing,
|
||||
child: Text(MediumTrackingState.ongoing.toNameString(type)),
|
||||
),
|
||||
PopupMenuItem<MediumTrackingState>(
|
||||
value: MediumTrackingState.completed,
|
||||
child: Text(MediumTrackingState.completed.toNameString(type)),
|
||||
),
|
||||
PopupMenuItem<MediumTrackingState>(
|
||||
value: MediumTrackingState.planned,
|
||||
child: Text(MediumTrackingState.planned.toNameString(type)),
|
||||
),
|
||||
PopupMenuItem<MediumTrackingState>(
|
||||
value: MediumTrackingState.dropped,
|
||||
child: Text(MediumTrackingState.dropped.toNameString(type)),
|
||||
),
|
||||
const PopupMenuItem<MediumTrackingState>(
|
||||
value: MediumTrackingState.all,
|
||||
child: Text('All'),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
Widget _getPopupButton(BuildContext context, AnimeListState state) {
|
||||
switch (state.trackingType) {
|
||||
case TrackingMediumType.anime:
|
||||
return PopupMenuButton(
|
||||
icon: Icon(
|
||||
icon: const Icon(
|
||||
Icons.filter_list,
|
||||
),
|
||||
initialValue: state.animeFilterState,
|
||||
@@ -40,32 +67,11 @@ class AnimeListPage extends StatelessWidget {
|
||||
AnimeFilterChangedEvent(filterState),
|
||||
);
|
||||
},
|
||||
itemBuilder: (_) => [
|
||||
const PopupMenuItem<AnimeTrackingState>(
|
||||
value: AnimeTrackingState.watching,
|
||||
child: Text('Watching'),
|
||||
),
|
||||
const PopupMenuItem<AnimeTrackingState>(
|
||||
value: AnimeTrackingState.completed,
|
||||
child: Text('Completed'),
|
||||
),
|
||||
const PopupMenuItem<AnimeTrackingState>(
|
||||
value: AnimeTrackingState.planToWatch,
|
||||
child: Text('Plan to watch'),
|
||||
),
|
||||
const PopupMenuItem<AnimeTrackingState>(
|
||||
value: AnimeTrackingState.dropped,
|
||||
child: Text('Dropped'),
|
||||
),
|
||||
const PopupMenuItem<AnimeTrackingState>(
|
||||
value: AnimeTrackingState.all,
|
||||
child: Text('All'),
|
||||
),
|
||||
],
|
||||
itemBuilder: (_) => _getPopupButtonItems(TrackingMediumType.anime),
|
||||
);
|
||||
case TrackingMediumType.manga:
|
||||
return PopupMenuButton(
|
||||
icon: Icon(
|
||||
icon: const Icon(
|
||||
Icons.filter_list,
|
||||
),
|
||||
initialValue: state.mangaFilterState,
|
||||
@@ -74,28 +80,7 @@ class AnimeListPage extends StatelessWidget {
|
||||
MangaFilterChangedEvent(filterState),
|
||||
);
|
||||
},
|
||||
itemBuilder: (_) => [
|
||||
const PopupMenuItem<MangaTrackingState>(
|
||||
value: MangaTrackingState.reading,
|
||||
child: Text('Reading'),
|
||||
),
|
||||
const PopupMenuItem<MangaTrackingState>(
|
||||
value: MangaTrackingState.completed,
|
||||
child: Text('Completed'),
|
||||
),
|
||||
const PopupMenuItem<MangaTrackingState>(
|
||||
value: MangaTrackingState.planToRead,
|
||||
child: Text('Plan to read'),
|
||||
),
|
||||
const PopupMenuItem<MangaTrackingState>(
|
||||
value: MangaTrackingState.dropped,
|
||||
child: Text('Dropped'),
|
||||
),
|
||||
const PopupMenuItem<MangaTrackingState>(
|
||||
value: MangaTrackingState.all,
|
||||
child: Text('All'),
|
||||
),
|
||||
],
|
||||
itemBuilder: (_) => _getPopupButtonItems(TrackingMediumType.manga),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -107,7 +92,7 @@ class AnimeListPage extends StatelessWidget {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(
|
||||
_getPageTitle(state.trackingType)
|
||||
_getPageTitle(state.trackingType),
|
||||
),
|
||||
actions: [
|
||||
_getPopupButton(context, state),
|
||||
@@ -120,7 +105,7 @@ class AnimeListPage extends StatelessWidget {
|
||||
itemCount: state.animes.length,
|
||||
itemBuilder: (context, index) {
|
||||
final anime = state.animes[index];
|
||||
if (state.animeFilterState != AnimeTrackingState.all) {
|
||||
if (state.animeFilterState != MediumTrackingState.all) {
|
||||
if (anime.state != state.animeFilterState) return Container();
|
||||
}
|
||||
|
||||
@@ -157,7 +142,7 @@ class AnimeListPage extends StatelessWidget {
|
||||
itemCount: state.mangas.length,
|
||||
itemBuilder: (context, index) {
|
||||
final manga = state.mangas[index];
|
||||
if (state.mangaFilterState != MangaTrackingState.all) {
|
||||
if (state.mangaFilterState != MediumTrackingState.all) {
|
||||
if (manga.state != state.mangaFilterState) return Container();
|
||||
}
|
||||
|
||||
@@ -216,7 +201,7 @@ class AnimeListPage extends StatelessWidget {
|
||||
|
||||
_controller.jumpToPage(index);
|
||||
},
|
||||
items: <BottomBarItem>[
|
||||
items: const [
|
||||
BottomBarItem(
|
||||
icon: Icon(Icons.tv),
|
||||
title: Text('Anime'),
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:anitrack/src/data/anime.dart';
|
||||
import 'package:anitrack/src/data/type.dart';
|
||||
import 'package:anitrack/src/ui/bloc/anime_search_bloc.dart';
|
||||
import 'package:anitrack/src/ui/constants.dart';
|
||||
import 'package:anitrack/src/ui/widgets/list_item.dart';
|
||||
import 'package:anitrack/src/ui/widgets/image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class AnimeSearchPage extends StatelessWidget {
|
||||
const AnimeSearchPage({
|
||||
super.key,
|
||||
});
|
||||
|
||||
static MaterialPageRoute<dynamic> get route => MaterialPageRoute<dynamic>(
|
||||
builder: (_) => AnimeSearchPage(),
|
||||
builder: (_) => const AnimeSearchPage(),
|
||||
settings: const RouteSettings(
|
||||
name: animeSearchRoute,
|
||||
),
|
||||
@@ -32,7 +34,7 @@ class AnimeSearchPage extends StatelessWidget {
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: TextField(
|
||||
decoration: InputDecoration(
|
||||
decoration: const InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
labelText: 'Search query',
|
||||
),
|
||||
@@ -50,9 +52,8 @@ class AnimeSearchPage extends StatelessWidget {
|
||||
),
|
||||
|
||||
if (state.working)
|
||||
Expanded(
|
||||
const Expanded(
|
||||
child: Align(
|
||||
alignment: Alignment.center,
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
)
|
||||
@@ -86,7 +87,7 @@ class AnimeSearchPage extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -3,44 +3,28 @@ import 'package:anitrack/src/data/manga.dart';
|
||||
import 'package:anitrack/src/data/type.dart';
|
||||
import 'package:anitrack/src/ui/bloc/details_bloc.dart';
|
||||
import 'package:anitrack/src/ui/constants.dart';
|
||||
import 'package:anitrack/src/ui/widgets/dropdown.dart';
|
||||
import 'package:anitrack/src/ui/widgets/image.dart';
|
||||
import 'package:anitrack/src/ui/widgets/list_item.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
Widget _makeListTile(BuildContext context, dynamic value, String text, bool selected) {
|
||||
return ListTile(
|
||||
title: Text(text),
|
||||
trailing: selected ?
|
||||
Icon(Icons.check) :
|
||||
null,
|
||||
onTap: () {
|
||||
Navigator.of(context).pop(value);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
class DetailsPage extends StatelessWidget {
|
||||
const DetailsPage({
|
||||
super.key,
|
||||
});
|
||||
|
||||
static MaterialPageRoute<dynamic> get route => MaterialPageRoute<dynamic>(
|
||||
builder: (_) => DetailsPage(),
|
||||
builder: (_) => const DetailsPage(),
|
||||
settings: const RouteSettings(
|
||||
name: detailsRoute,
|
||||
),
|
||||
);
|
||||
|
||||
String _getTrackingStateText(DetailsState state) {
|
||||
if (state.trackingType == TrackingMediumType.anime) {
|
||||
return (state.data as AnimeTrackingData).state.toNameString();
|
||||
} else {
|
||||
return (state.data as MangaTrackingData).state.toNameString();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Details'),
|
||||
title: const Text('Details'),
|
||||
),
|
||||
body: BlocBuilder<DetailsBloc, DetailsState>(
|
||||
builder: (context, state) {
|
||||
@@ -69,101 +53,63 @@ class DetailsPage extends StatelessWidget {
|
||||
children: [
|
||||
Text(
|
||||
state.trackingType == TrackingMediumType.anime ?
|
||||
(state.data as AnimeTrackingData).title :
|
||||
(state.data as MangaTrackingData).title,
|
||||
(state.data as AnimeTrackingData).title :
|
||||
(state.data as MangaTrackingData).title,
|
||||
textAlign: TextAlign.left,
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
maxLines: 2,
|
||||
softWrap: true,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
|
||||
TextButton(
|
||||
child: Text(
|
||||
_getTrackingStateText(state),
|
||||
),
|
||||
onPressed: () async {
|
||||
final result = await showModalBottomSheet<dynamic>(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return ListView(
|
||||
shrinkWrap: true,
|
||||
children: [
|
||||
_makeListTile(
|
||||
context,
|
||||
state.trackingType == TrackingMediumType.anime ?
|
||||
AnimeTrackingState.watching :
|
||||
MangaTrackingState.reading,
|
||||
state.trackingType == TrackingMediumType.anime ?
|
||||
'Watching' :
|
||||
'Reading',
|
||||
state.trackingType == TrackingMediumType.anime ?
|
||||
(state.data as AnimeTrackingData).state == AnimeTrackingState.watching :
|
||||
(state.data as MangaTrackingData).state == MangaTrackingState.reading,
|
||||
),
|
||||
_makeListTile(
|
||||
context,
|
||||
state.trackingType == TrackingMediumType.anime ?
|
||||
AnimeTrackingState.completed :
|
||||
MangaTrackingState.completed,
|
||||
'Completed',
|
||||
state.trackingType == TrackingMediumType.anime ?
|
||||
(state.data as AnimeTrackingData).state == AnimeTrackingState.completed :
|
||||
(state.data as MangaTrackingData).state == MangaTrackingState.completed,
|
||||
),
|
||||
_makeListTile(
|
||||
context,
|
||||
state.trackingType == TrackingMediumType.anime ?
|
||||
AnimeTrackingState.planToWatch :
|
||||
MangaTrackingState.planToRead,
|
||||
state.trackingType == TrackingMediumType.anime ?
|
||||
'Plan to watch' :
|
||||
'Plan to read',
|
||||
state.trackingType == TrackingMediumType.anime ?
|
||||
(state.data as AnimeTrackingData).state == AnimeTrackingState.planToWatch :
|
||||
(state.data as MangaTrackingData).state == MangaTrackingState.planToRead,
|
||||
),
|
||||
_makeListTile(
|
||||
context,
|
||||
state.trackingType == TrackingMediumType.anime ?
|
||||
AnimeTrackingState.dropped :
|
||||
MangaTrackingState.dropped,
|
||||
'Dropped',
|
||||
state.trackingType == TrackingMediumType.anime ?
|
||||
(state.data as AnimeTrackingData).state == AnimeTrackingState.dropped :
|
||||
(state.data as MangaTrackingData).state == MangaTrackingState.dropped,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
if (result == null) return;
|
||||
|
||||
if (state.trackingType == TrackingMediumType.anime) {
|
||||
context.read<DetailsBloc>().add(
|
||||
DetailsUpdatedEvent(
|
||||
(state.data as AnimeTrackingData).copyWith(
|
||||
state: result as AnimeTrackingState,
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
context.read<DetailsBloc>().add(
|
||||
DetailsUpdatedEvent(
|
||||
(state.data as MangaTrackingData).copyWith(
|
||||
state: result as MangaTrackingState,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
DropdownSelector<MediumTrackingState>(
|
||||
title: 'Watchstate',
|
||||
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.trackingType == TrackingMediumType.anime ?
|
||||
(state.data as AnimeTrackingData).state :
|
||||
(state.data as MangaTrackingData).state,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
129
lib/src/ui/widgets/dropdown.dart
Normal file
129
lib/src/ui/widgets/dropdown.dart
Normal file
@@ -0,0 +1,129 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class SelectorItem<T> {
|
||||
const SelectorItem(this.value, this.text);
|
||||
|
||||
final T value;
|
||||
|
||||
final String text;
|
||||
}
|
||||
|
||||
class DropdownSelector<T> extends StatefulWidget {
|
||||
const DropdownSelector({
|
||||
required this.title,
|
||||
required this.onChanged,
|
||||
required this.values,
|
||||
required this.initialValue,
|
||||
super.key,
|
||||
});
|
||||
|
||||
/// The title of the dropdown
|
||||
final String title;
|
||||
|
||||
final List<SelectorItem<T>> values;
|
||||
|
||||
final T initialValue;
|
||||
|
||||
/// Called when the selection has changed
|
||||
final void Function(T) onChanged;
|
||||
|
||||
@override
|
||||
DropdownSelectorState<T> createState() => DropdownSelectorState<T>();
|
||||
}
|
||||
|
||||
class DropdownSelectorDialog<T> extends StatelessWidget {
|
||||
const DropdownSelectorDialog({
|
||||
required this.values,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final List<SelectorItem<T>> values;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SafeArea(
|
||||
child: Material(
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
child: ListView.builder(
|
||||
itemCount: values.length,
|
||||
itemBuilder: (context, index) => InkWell(
|
||||
onTap: () {
|
||||
Navigator.of(context).pop(values[index].value);
|
||||
},
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Text(
|
||||
values[index].text,
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DropdownSelectorState<T> extends State<DropdownSelector<T>> {
|
||||
late int index;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
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,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ class AnimeCoverImage extends StatelessWidget {
|
||||
/// The URL to the cover image.
|
||||
final String url;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import 'package:anitrack/src/data/anime.dart';
|
||||
import 'package:anitrack/src/ui/widgets/image.dart';
|
||||
import 'package:anitrack/src/ui/widgets/swipe_icon.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -6,7 +5,7 @@ import 'package:swipeable_tile/swipeable_tile.dart';
|
||||
|
||||
/// A widget for displaying simple data about an anime in the listview
|
||||
class ListItem extends StatelessWidget {
|
||||
ListItem({
|
||||
const ListItem({
|
||||
required this.thumbnailUrl,
|
||||
required this.title,
|
||||
this.onLeftSwipe,
|
||||
@@ -42,14 +41,14 @@ class ListItem extends StatelessWidget {
|
||||
key: UniqueKey(),
|
||||
backgroundBuilder: (_, direction, __) {
|
||||
if (direction == SwipeDirection.endToStart) {
|
||||
return Align(
|
||||
return const Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: SwipeIcon(
|
||||
Icons.add,
|
||||
),
|
||||
);
|
||||
} else if (direction == SwipeDirection.startToEnd) {
|
||||
return Align(
|
||||
return const Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: SwipeIcon(
|
||||
Icons.remove,
|
||||
|
||||
Reference in New Issue
Block a user