fix: Fix routing
This commit is contained in:
106
lib/main.dart
106
lib/main.dart
@@ -14,13 +14,13 @@ import 'package:anitrack/src/ui/pages/anime_search.dart';
|
||||
import 'package:anitrack/src/ui/pages/calendar.dart';
|
||||
import 'package:anitrack/src/ui/pages/details/details.dart';
|
||||
import 'package:anitrack/src/ui/pages/settings.dart';
|
||||
import 'package:anitrack/src/ui/widgets/shell_wrapper.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
void main() async {
|
||||
final navKey = GlobalKey<NavigatorState>();
|
||||
|
||||
// Initialize the widgets binding for sqflite
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
@@ -33,7 +33,7 @@ void main() async {
|
||||
GetIt.I.registerSingleton<AnimeListBloc>(AnimeListBloc());
|
||||
GetIt.I.registerSingleton<AnimeSearchBloc>(AnimeSearchBloc());
|
||||
GetIt.I.registerSingleton<DetailsBloc>(DetailsBloc());
|
||||
GetIt.I.registerSingleton<NavigationBloc>(NavigationBloc(navKey));
|
||||
GetIt.I.registerSingleton<NavigationBloc>(NavigationBloc());
|
||||
GetIt.I.registerSingleton<SettingsBloc>(SettingsBloc());
|
||||
GetIt.I.registerSingleton<CalendarBloc>(CalendarBloc());
|
||||
GetIt.I.registerSingleton<AniListClient>(AniListClient());
|
||||
@@ -68,53 +68,81 @@ void main() async {
|
||||
create: (_) => GetIt.I.get<CalendarBloc>(),
|
||||
),
|
||||
],
|
||||
child: MyApp(navKey),
|
||||
child: MyApp(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
const MyApp(
|
||||
this.navKey, {
|
||||
super.key,
|
||||
});
|
||||
MyApp({super.key});
|
||||
|
||||
final GlobalKey<NavigatorState> navKey;
|
||||
/// The router to attach to the main app.
|
||||
final _router = GoRouter(
|
||||
initialLocation: animeListRoute,
|
||||
debugLogDiagnostics: true,
|
||||
routes: [
|
||||
ShellRoute(
|
||||
builder: (context, state, child) {
|
||||
return ShellWrapper(state: state, child: child);
|
||||
},
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: animeListRoute,
|
||||
builder: (context, state) => const AnimeListPage(),
|
||||
),
|
||||
GoRoute(
|
||||
path: calendarRoute,
|
||||
builder: (context, state) => const CalendarPage(),
|
||||
),
|
||||
],
|
||||
),
|
||||
GoRoute(
|
||||
path: animeSearchRoute,
|
||||
builder: (context, state) => const AnimeSearchPage(),
|
||||
),
|
||||
GoRoute(
|
||||
path: detailsRoute,
|
||||
builder: (context, state) => const DetailsPage(),
|
||||
),
|
||||
GoRoute(
|
||||
path: aboutRoute,
|
||||
builder: (context, state) => const AboutPage(),
|
||||
),
|
||||
GoRoute(
|
||||
path: settingsRoute,
|
||||
builder: (context, state) => const SettingsPage(),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: 'AniTrack',
|
||||
theme: ThemeData(
|
||||
brightness: Brightness.light,
|
||||
primarySwatch: Colors.blue,
|
||||
useMaterial3: true,
|
||||
),
|
||||
darkTheme: ThemeData(
|
||||
brightness: Brightness.dark,
|
||||
primarySwatch: Colors.blue,
|
||||
useMaterial3: true,
|
||||
),
|
||||
navigatorKey: navKey,
|
||||
onGenerateRoute: (settings) {
|
||||
switch (settings.name) {
|
||||
case '/':
|
||||
case animeListRoute:
|
||||
return AnimeListPage.route;
|
||||
case animeSearchRoute:
|
||||
return AnimeSearchPage.route;
|
||||
case calendarRoute:
|
||||
return CalendarPage.route;
|
||||
case detailsRoute:
|
||||
return DetailsPage.route;
|
||||
case aboutRoute:
|
||||
return AboutPage.route;
|
||||
case settingsRoute:
|
||||
return SettingsPage.route;
|
||||
return BlocListener<NavigationBloc, NavigationState>(
|
||||
listener: (context, state) {
|
||||
if (state is NoopNavigationState) {
|
||||
// NOOP
|
||||
} else if (state is PushNavigationState) {
|
||||
_router.go(state.destination);
|
||||
} else if (state is GoNavigationState) {
|
||||
_router.push(state.destination);
|
||||
} else if (state is PoppedNavigationState) {
|
||||
_router.pop();
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
child: MaterialApp.router(
|
||||
title: 'AniTrack',
|
||||
theme: ThemeData(
|
||||
brightness: Brightness.light,
|
||||
primarySwatch: Colors.blue,
|
||||
useMaterial3: true,
|
||||
),
|
||||
darkTheme: ThemeData(
|
||||
brightness: Brightness.dark,
|
||||
primarySwatch: Colors.blue,
|
||||
useMaterial3: true,
|
||||
),
|
||||
routerConfig: _router,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:anitrack/src/ui/bloc/navigation_bloc.dart';
|
||||
import 'package:anitrack/src/ui/constants.dart';
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
|
||||
@@ -41,6 +42,9 @@ class AnimeListBloc extends Bloc<AnimeListEvent, AnimeListState> {
|
||||
growable: true,
|
||||
);
|
||||
|
||||
/// Controller for the bottom navigation bar
|
||||
final pageController = PageController();
|
||||
|
||||
List<AnimeTrackingData> get unfilteredAnime => _animes;
|
||||
|
||||
List<AnimeTrackingData> _getFilteredAnime({
|
||||
@@ -372,10 +376,7 @@ class AnimeListBloc extends Bloc<AnimeListEvent, AnimeListState> {
|
||||
),
|
||||
);
|
||||
GetIt.I.get<NavigationBloc>().add(
|
||||
PushedNamedAndRemoveUntilEvent(
|
||||
const NavigationDestination(animeListRoute),
|
||||
(_) => false,
|
||||
),
|
||||
GoNavigationEvent(animeListRoute),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,14 +5,12 @@ import 'package:anitrack/src/data/search_result.dart';
|
||||
import 'package:anitrack/src/data/source.dart';
|
||||
import 'package:anitrack/src/data/type.dart';
|
||||
import 'package:anitrack/src/service/anilist/anilist_client.dart';
|
||||
import 'package:anitrack/src/service/anilist/model.dart';
|
||||
import 'package:anitrack/src/ui/bloc/anime_list_bloc.dart' as list;
|
||||
import 'package:anitrack/src/ui/bloc/navigation_bloc.dart';
|
||||
import 'package:anitrack/src/ui/constants.dart';
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:jikan_api/jikan_api.dart';
|
||||
|
||||
part 'anime_search_state.dart';
|
||||
part 'anime_search_event.dart';
|
||||
@@ -43,9 +41,7 @@ class AnimeSearchBloc extends Bloc<AnimeSearchEvent, AnimeSearchState> {
|
||||
);
|
||||
|
||||
GetIt.I.get<NavigationBloc>().add(
|
||||
PushedNamedEvent(
|
||||
const NavigationDestination(animeSearchRoute),
|
||||
),
|
||||
PushNavigationEvent(animeSearchRoute),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -166,7 +162,7 @@ class AnimeSearchBloc extends Bloc<AnimeSearchEvent, AnimeSearchState> {
|
||||
);
|
||||
|
||||
GetIt.I.get<NavigationBloc>().add(
|
||||
PoppedRouteEvent(),
|
||||
GoNavigationEvent(animeListRoute),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,9 +44,7 @@ class DetailsBloc extends Bloc<DetailsEvent, DetailsState> {
|
||||
);
|
||||
|
||||
GetIt.I.get<NavigationBloc>().add(
|
||||
PushedNamedEvent(
|
||||
const NavigationDestination(detailsRoute),
|
||||
),
|
||||
GoNavigationEvent(detailsRoute),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -63,9 +61,7 @@ class DetailsBloc extends Bloc<DetailsEvent, DetailsState> {
|
||||
);
|
||||
|
||||
GetIt.I.get<NavigationBloc>().add(
|
||||
PushedNamedEvent(
|
||||
const NavigationDestination(detailsRoute),
|
||||
),
|
||||
PushNavigationEvent(detailsRoute),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -126,7 +122,7 @@ class DetailsBloc extends Bloc<DetailsEvent, DetailsState> {
|
||||
}
|
||||
|
||||
// Navigate back
|
||||
GetIt.I.get<NavigationBloc>().add(PoppedRouteEvent());
|
||||
GetIt.I.get<NavigationBloc>().add(PopNavigationEvent());
|
||||
}
|
||||
|
||||
Future<void> _onAnimeWatcherAdded(
|
||||
|
||||
@@ -1,57 +1,39 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
part 'navigation_event.dart';
|
||||
part 'navigation_state.dart';
|
||||
|
||||
class NavigationBloc extends Bloc<NavigationEvent, NavigationState> {
|
||||
NavigationBloc(this.navigationKey) : super(NavigationState()) {
|
||||
on<PushedNamedEvent>(_onPushedNamed);
|
||||
on<PushedNamedAndRemoveUntilEvent>(_onPushedNamedAndRemoveUntil);
|
||||
on<PushedNamedReplaceEvent>(_onPushedNamedReplaceEvent);
|
||||
on<PoppedRouteEvent>(_onPoppedRoute);
|
||||
NavigationBloc() : super(NoopNavigationState()) {
|
||||
on<GoNavigationEvent>(_onGoEvent);
|
||||
on<PushNavigationEvent>(_onPushEvent);
|
||||
on<PopNavigationEvent>(_onPopEvent);
|
||||
}
|
||||
final GlobalKey<NavigatorState> navigationKey;
|
||||
|
||||
Future<void> _onPushedNamed(
|
||||
PushedNamedEvent event,
|
||||
Future<void> _onGoEvent(
|
||||
GoNavigationEvent event,
|
||||
Emitter<NavigationState> emit,
|
||||
) async {
|
||||
await navigationKey.currentState!.pushNamed(
|
||||
event.destination.path,
|
||||
arguments: event.destination.arguments,
|
||||
emit(
|
||||
GoNavigationState(event.destination),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onPushedNamedAndRemoveUntil(
|
||||
PushedNamedAndRemoveUntilEvent event,
|
||||
Future<void> _onPushEvent(
|
||||
PushNavigationEvent event,
|
||||
Emitter<NavigationState> emit,
|
||||
) async {
|
||||
await navigationKey.currentState!.pushNamedAndRemoveUntil(
|
||||
event.destination.path,
|
||||
event.predicate,
|
||||
arguments: event.destination.arguments,
|
||||
emit(
|
||||
PushNavigationState(event.destination),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onPushedNamedReplaceEvent(
|
||||
PushedNamedReplaceEvent event,
|
||||
Future<void> _onPopEvent(
|
||||
PopNavigationEvent event,
|
||||
Emitter<NavigationState> emit,
|
||||
) async {
|
||||
await navigationKey.currentState!.pushReplacementNamed(
|
||||
event.destination.path,
|
||||
arguments: event.destination.arguments,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onPoppedRoute(
|
||||
PoppedRouteEvent event,
|
||||
Emitter<NavigationState> emit,
|
||||
) async {
|
||||
navigationKey.currentState!.pop();
|
||||
}
|
||||
|
||||
bool canPop() {
|
||||
return navigationKey.currentState!.canPop();
|
||||
emit(PoppedNavigationState());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,20 +11,14 @@ class NavigationDestination {
|
||||
|
||||
abstract class NavigationEvent {}
|
||||
|
||||
class PushedNamedEvent extends NavigationEvent {
|
||||
PushedNamedEvent(this.destination);
|
||||
final NavigationDestination destination;
|
||||
class GoNavigationEvent extends NavigationEvent {
|
||||
GoNavigationEvent(this.destination);
|
||||
final String destination;
|
||||
}
|
||||
|
||||
class PushedNamedAndRemoveUntilEvent extends NavigationEvent {
|
||||
PushedNamedAndRemoveUntilEvent(this.destination, this.predicate);
|
||||
final NavigationDestination destination;
|
||||
final RoutePredicate predicate;
|
||||
class PushNavigationEvent extends NavigationEvent {
|
||||
PushNavigationEvent(this.destination);
|
||||
final String destination;
|
||||
}
|
||||
|
||||
class PushedNamedReplaceEvent extends NavigationEvent {
|
||||
PushedNamedReplaceEvent(this.destination);
|
||||
final NavigationDestination destination;
|
||||
}
|
||||
|
||||
class PoppedRouteEvent extends NavigationEvent {}
|
||||
class PopNavigationEvent extends NavigationEvent {}
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
part of 'navigation_bloc.dart';
|
||||
|
||||
class NavigationState {}
|
||||
abstract class NavigationState {}
|
||||
|
||||
class NoopNavigationState extends NavigationState {}
|
||||
|
||||
class PoppedNavigationState extends NavigationState {}
|
||||
|
||||
class PushNavigationState extends NavigationState {
|
||||
PushNavigationState(this.destination);
|
||||
|
||||
final String destination;
|
||||
}
|
||||
|
||||
class GoNavigationState extends NavigationState {
|
||||
GoNavigationState(this.destination);
|
||||
|
||||
final String destination;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:anitrack/src/ui/bloc/anime_list_bloc.dart';
|
||||
import 'package:anitrack/src/ui/constants.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
Widget getDrawer(BuildContext context) {
|
||||
return Drawer(
|
||||
@@ -24,33 +25,30 @@ Widget getDrawer(BuildContext context) {
|
||||
leading: const Icon(Icons.list),
|
||||
title: Text(t.content.list),
|
||||
onTap: () {
|
||||
GetIt.I.get<AnimeListBloc>().add(
|
||||
AnimeListRequestedEvent(),
|
||||
);
|
||||
GoRouter.of(context).go(animeListRoute);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.calendar_today),
|
||||
title: Text(t.calendar.calendar),
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamedAndRemoveUntil(
|
||||
calendarRoute,
|
||||
(_) => false,
|
||||
);
|
||||
GoRouter.of(context).go(calendarRoute);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.settings),
|
||||
title: Text(t.settings.title),
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamed(settingsRoute);
|
||||
GoRouter.of(context).push(settingsRoute);
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.info),
|
||||
title: Text(t.about.title),
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamed(aboutRoute);
|
||||
GoRouter.of(context).push(aboutRoute);
|
||||
},
|
||||
),
|
||||
],
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:anitrack/licenses.g.dart';
|
||||
import 'package:anitrack/src/ui/constants.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class AboutPage extends StatelessWidget {
|
||||
@@ -22,6 +23,12 @@ class AboutPage extends StatelessWidget {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(t.about.title),
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
onPressed: () {
|
||||
GoRouter.of(context).pop();
|
||||
},
|
||||
),
|
||||
),
|
||||
body: ListView.builder(
|
||||
itemCount: ossLicenses.length + 1,
|
||||
|
||||
@@ -4,13 +4,13 @@ import 'package:anitrack/src/ui/bloc/anime_list_bloc.dart';
|
||||
import 'package:anitrack/src/ui/bloc/anime_search_bloc.dart';
|
||||
import 'package:anitrack/src/ui/bloc/details_bloc.dart';
|
||||
import 'package:anitrack/src/ui/constants.dart';
|
||||
import 'package:anitrack/src/ui/helpers.dart';
|
||||
import 'package:anitrack/src/ui/widgets/grid_item.dart';
|
||||
import 'package:anitrack/src/ui/widgets/image.dart';
|
||||
import 'package:bottom_bar/bottom_bar.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
class AnimeListPage extends StatefulWidget {
|
||||
const AnimeListPage({
|
||||
@@ -24,6 +24,147 @@ class AnimeListPage extends StatefulWidget {
|
||||
),
|
||||
);
|
||||
|
||||
static String getPageTitle(TrackingMediumType type) {
|
||||
switch (type) {
|
||||
case TrackingMediumType.anime:
|
||||
return t.content.anime;
|
||||
case TrackingMediumType.manga:
|
||||
return t.content.manga;
|
||||
}
|
||||
}
|
||||
|
||||
static List<PopupMenuItem<MediumTrackingState>> getPopupButtonItems(
|
||||
TrackingMediumType type,
|
||||
) {
|
||||
return [
|
||||
PopupMenuItem<MediumTrackingState>(
|
||||
value: MediumTrackingState.ongoing,
|
||||
child: Text(MediumTrackingState.ongoing.getName(type)),
|
||||
),
|
||||
PopupMenuItem<MediumTrackingState>(
|
||||
value: MediumTrackingState.completed,
|
||||
child: Text(MediumTrackingState.completed.getName(type)),
|
||||
),
|
||||
PopupMenuItem<MediumTrackingState>(
|
||||
value: MediumTrackingState.planned,
|
||||
child: Text(MediumTrackingState.planned.getName(type)),
|
||||
),
|
||||
PopupMenuItem<MediumTrackingState>(
|
||||
value: MediumTrackingState.dropped,
|
||||
child: Text(MediumTrackingState.dropped.getName(type)),
|
||||
),
|
||||
PopupMenuItem<MediumTrackingState>(
|
||||
value: MediumTrackingState.paused,
|
||||
child: Text(MediumTrackingState.paused.getName(type)),
|
||||
),
|
||||
const PopupMenuItem<MediumTrackingState>(
|
||||
value: MediumTrackingState.all,
|
||||
child: Text('All'),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
static Widget getPopupButton(BuildContext context, AnimeListState state) {
|
||||
switch (state.trackingType) {
|
||||
case TrackingMediumType.anime:
|
||||
return PopupMenuButton(
|
||||
icon: const Icon(
|
||||
Icons.filter_list,
|
||||
),
|
||||
initialValue: state.animeFilterState,
|
||||
onSelected: (filterState) {
|
||||
context.read<AnimeListBloc>().add(
|
||||
AnimeFilterChangedEvent(filterState),
|
||||
);
|
||||
},
|
||||
itemBuilder: (_) =>
|
||||
AnimeListPage.getPopupButtonItems(TrackingMediumType.anime),
|
||||
);
|
||||
case TrackingMediumType.manga:
|
||||
return PopupMenuButton(
|
||||
icon: const Icon(
|
||||
Icons.filter_list,
|
||||
),
|
||||
initialValue: state.mangaFilterState,
|
||||
onSelected: (filterState) {
|
||||
context.read<AnimeListBloc>().add(
|
||||
MangaFilterChangedEvent(filterState),
|
||||
);
|
||||
},
|
||||
itemBuilder: (_) =>
|
||||
AnimeListPage.getPopupButtonItems(TrackingMediumType.manga),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static AppBar buildAppBar(BuildContext context) {
|
||||
return AppBar(
|
||||
title: BlocBuilder<AnimeListBloc, AnimeListState>(
|
||||
builder: (context, state) => Text(
|
||||
AnimeListPage.getPageTitle(state.trackingType),
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
BlocBuilder<AnimeListBloc, AnimeListState>(
|
||||
builder: (context, state) =>
|
||||
AnimeListPage.getPopupButton(context, state),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
static Widget buildBottomNavigationBar(BuildContext context) {
|
||||
final pageController = context.read<AnimeListBloc>().pageController;
|
||||
return BlocBuilder<AnimeListBloc, AnimeListState>(
|
||||
builder: (context, state) => BottomBar(
|
||||
selectedIndex: state.trackingType == TrackingMediumType.anime ? 0 : 1,
|
||||
onTap: (index) {
|
||||
context.read<AnimeListBloc>().add(
|
||||
AnimeTrackingTypeChanged(
|
||||
index == 0 ? TrackingMediumType.anime : TrackingMediumType.manga,
|
||||
),
|
||||
);
|
||||
|
||||
pageController.jumpToPage(index);
|
||||
},
|
||||
items: [
|
||||
BottomBarItem(
|
||||
icon: const Icon(Icons.tv),
|
||||
title: Text(t.content.anime),
|
||||
activeColor: Colors.blue,
|
||||
),
|
||||
BottomBarItem(
|
||||
icon: const Icon(Icons.auto_stories),
|
||||
title: Text(t.content.manga),
|
||||
activeColor: Colors.red,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static Widget buildFab() {
|
||||
return BlocBuilder<AnimeListBloc, AnimeListState>(
|
||||
buildWhen: (prev, next) =>
|
||||
prev.buttonVisibility != next.buttonVisibility ||
|
||||
prev.trackingType != next.trackingType,
|
||||
builder: (context, state) {
|
||||
return AnimatedScale(
|
||||
duration: const Duration(milliseconds: 250),
|
||||
scale: state.buttonVisibility ? 1 : 0,
|
||||
curve: Curves.easeInOutQuint,
|
||||
child: FloatingActionButton(
|
||||
onPressed: () {
|
||||
context.push(animeSearchRoute);
|
||||
},
|
||||
tooltip: t.tooltips.addNewItem,
|
||||
child: const Icon(Icons.add),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AnimeListPageState createState() => AnimeListPageState();
|
||||
}
|
||||
@@ -57,255 +198,124 @@ class AnimeListPageState extends State<AnimeListPage> {
|
||||
}
|
||||
}
|
||||
|
||||
String _getPageTitle(TrackingMediumType type) {
|
||||
switch (type) {
|
||||
case TrackingMediumType.anime:
|
||||
return t.content.anime;
|
||||
case TrackingMediumType.manga:
|
||||
return t.content.manga;
|
||||
}
|
||||
}
|
||||
|
||||
List<PopupMenuItem<MediumTrackingState>> _getPopupButtonItems(
|
||||
TrackingMediumType type,
|
||||
) {
|
||||
return [
|
||||
PopupMenuItem<MediumTrackingState>(
|
||||
value: MediumTrackingState.ongoing,
|
||||
child: Text(MediumTrackingState.ongoing.getName(type)),
|
||||
),
|
||||
PopupMenuItem<MediumTrackingState>(
|
||||
value: MediumTrackingState.completed,
|
||||
child: Text(MediumTrackingState.completed.getName(type)),
|
||||
),
|
||||
PopupMenuItem<MediumTrackingState>(
|
||||
value: MediumTrackingState.planned,
|
||||
child: Text(MediumTrackingState.planned.getName(type)),
|
||||
),
|
||||
PopupMenuItem<MediumTrackingState>(
|
||||
value: MediumTrackingState.dropped,
|
||||
child: Text(MediumTrackingState.dropped.getName(type)),
|
||||
),
|
||||
PopupMenuItem<MediumTrackingState>(
|
||||
value: MediumTrackingState.paused,
|
||||
child: Text(MediumTrackingState.paused.getName(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: const Icon(
|
||||
Icons.filter_list,
|
||||
),
|
||||
initialValue: state.animeFilterState,
|
||||
onSelected: (filterState) {
|
||||
context.read<AnimeListBloc>().add(
|
||||
AnimeFilterChangedEvent(filterState),
|
||||
);
|
||||
},
|
||||
itemBuilder: (_) => _getPopupButtonItems(TrackingMediumType.anime),
|
||||
);
|
||||
case TrackingMediumType.manga:
|
||||
return PopupMenuButton(
|
||||
icon: const Icon(
|
||||
Icons.filter_list,
|
||||
),
|
||||
initialValue: state.mangaFilterState,
|
||||
onSelected: (filterState) {
|
||||
context.read<AnimeListBloc>().add(
|
||||
MangaFilterChangedEvent(filterState),
|
||||
);
|
||||
},
|
||||
itemBuilder: (_) => _getPopupButtonItems(TrackingMediumType.manga),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<AnimeListBloc, AnimeListState>(
|
||||
builder: (context, state) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(
|
||||
_getPageTitle(state.trackingType),
|
||||
return PageView(
|
||||
// Prevent swiping between pages
|
||||
// (https://github.com/flutter/flutter/issues/37510#issuecomment-612663656)
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
controller: _controller,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
child: GridView.builder(
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 3,
|
||||
mainAxisSpacing: 8,
|
||||
crossAxisSpacing: 8,
|
||||
childAspectRatio: 120 / (100 * (16 / 9)),
|
||||
),
|
||||
itemCount: state.animes.length,
|
||||
controller: _animeScrollController,
|
||||
itemBuilder: (context, index) {
|
||||
final anime = state.animes[index];
|
||||
return GridItem(
|
||||
minusCallback: () {
|
||||
context.read<AnimeListBloc>().add(
|
||||
AnimeEpisodeDecrementedEvent(
|
||||
anime.id,
|
||||
),
|
||||
);
|
||||
},
|
||||
plusCallback: () {
|
||||
context.read<AnimeListBloc>().add(
|
||||
AnimeEpisodeIncrementedEvent(
|
||||
anime.id,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: AnimeCoverImage(
|
||||
url: anime.thumbnailUrl,
|
||||
hero: 'grid_${anime.id}',
|
||||
onTap: () {
|
||||
context.read<DetailsBloc>().add(
|
||||
AnimeDetailsRequestedEvent(
|
||||
anime,
|
||||
heroImagePrefix: 'grid_',
|
||||
),
|
||||
);
|
||||
},
|
||||
extra: Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(right: 8),
|
||||
child: Text(
|
||||
'${anime.episodesWatched}/${anime.episodesTotal ?? "???"}',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
_getPopupButton(context, state),
|
||||
],
|
||||
),
|
||||
drawer: getDrawer(context),
|
||||
body: PageView(
|
||||
// Prevent swiping between pages
|
||||
// (https://github.com/flutter/flutter/issues/37510#issuecomment-612663656)
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
controller: _controller,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
child: GridView.builder(
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 3,
|
||||
mainAxisSpacing: 8,
|
||||
crossAxisSpacing: 8,
|
||||
childAspectRatio: 120 / (100 * (16 / 9)),
|
||||
),
|
||||
itemCount: state.animes.length,
|
||||
controller: _animeScrollController,
|
||||
itemBuilder: (context, index) {
|
||||
final anime = state.animes[index];
|
||||
return GridItem(
|
||||
minusCallback: () {
|
||||
context.read<AnimeListBloc>().add(
|
||||
AnimeEpisodeDecrementedEvent(
|
||||
anime.id,
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
child: GridView.builder(
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 3,
|
||||
mainAxisSpacing: 8,
|
||||
crossAxisSpacing: 8,
|
||||
childAspectRatio: 120 / (100 * (16 / 9)),
|
||||
),
|
||||
itemCount: state.mangas.length,
|
||||
itemBuilder: (context, index) {
|
||||
final manga = state.mangas[index];
|
||||
return GridItem(
|
||||
minusCallback: () {
|
||||
context.read<AnimeListBloc>().add(
|
||||
MangaChapterDecrementedEvent(
|
||||
manga.id,
|
||||
),
|
||||
);
|
||||
},
|
||||
plusCallback: () {
|
||||
context.read<AnimeListBloc>().add(
|
||||
MangaChapterIncrementedEvent(
|
||||
manga.id,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: AnimeCoverImage(
|
||||
hero: 'grid_${manga.id}',
|
||||
url: manga.thumbnailUrl,
|
||||
onTap: () {
|
||||
context.read<DetailsBloc>().add(
|
||||
MangaDetailsRequestedEvent(
|
||||
manga,
|
||||
heroImagePrefix: 'grid_',
|
||||
),
|
||||
);
|
||||
},
|
||||
plusCallback: () {
|
||||
context.read<AnimeListBloc>().add(
|
||||
AnimeEpisodeIncrementedEvent(
|
||||
anime.id,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: AnimeCoverImage(
|
||||
url: anime.thumbnailUrl,
|
||||
hero: 'grid_${anime.id}',
|
||||
onTap: () {
|
||||
context.read<DetailsBloc>().add(
|
||||
AnimeDetailsRequestedEvent(
|
||||
anime,
|
||||
heroImagePrefix: 'grid_',
|
||||
),
|
||||
);
|
||||
},
|
||||
extra: Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(right: 8),
|
||||
child: Text(
|
||||
'${anime.episodesWatched}/${anime.episodesTotal ?? "???"}',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
extra: Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(right: 8),
|
||||
child: Text(
|
||||
'${manga.chaptersRead}/${manga.chaptersTotal ?? "???"}',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
child: GridView.builder(
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 3,
|
||||
mainAxisSpacing: 8,
|
||||
crossAxisSpacing: 8,
|
||||
childAspectRatio: 120 / (100 * (16 / 9)),
|
||||
),
|
||||
itemCount: state.mangas.length,
|
||||
itemBuilder: (context, index) {
|
||||
final manga = state.mangas[index];
|
||||
return GridItem(
|
||||
minusCallback: () {
|
||||
context.read<AnimeListBloc>().add(
|
||||
MangaChapterDecrementedEvent(
|
||||
manga.id,
|
||||
),
|
||||
);
|
||||
},
|
||||
plusCallback: () {
|
||||
context.read<AnimeListBloc>().add(
|
||||
MangaChapterIncrementedEvent(
|
||||
manga.id,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: AnimeCoverImage(
|
||||
hero: 'grid_${manga.id}',
|
||||
url: manga.thumbnailUrl,
|
||||
onTap: () {
|
||||
context.read<DetailsBloc>().add(
|
||||
MangaDetailsRequestedEvent(
|
||||
manga,
|
||||
heroImagePrefix: 'grid_',
|
||||
),
|
||||
);
|
||||
},
|
||||
extra: Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(right: 8),
|
||||
child: Text(
|
||||
'${manga.chaptersRead}/${manga.chaptersTotal ?? "???"}',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
floatingActionButton: BlocBuilder<AnimeListBloc, AnimeListState>(
|
||||
buildWhen: (prev, next) =>
|
||||
prev.buttonVisibility != next.buttonVisibility ||
|
||||
prev.trackingType != next.trackingType,
|
||||
builder: (context, state) {
|
||||
return AnimatedScale(
|
||||
duration: const Duration(milliseconds: 250),
|
||||
scale: state.buttonVisibility ? 1 : 0,
|
||||
curve: Curves.easeInOutQuint,
|
||||
child: FloatingActionButton(
|
||||
onPressed: () {
|
||||
context.read<AnimeSearchBloc>().add(
|
||||
AnimeSearchRequestedEvent(state.trackingType),
|
||||
);
|
||||
},
|
||||
tooltip: t.tooltips.addNewItem,
|
||||
child: const Icon(Icons.add),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
bottomNavigationBar: BottomBar(
|
||||
selectedIndex: state.trackingType == TrackingMediumType.anime
|
||||
? 0
|
||||
: 1,
|
||||
onTap: (int index) {
|
||||
context.read<AnimeListBloc>().add(
|
||||
AnimeTrackingTypeChanged(
|
||||
index == 0
|
||||
? TrackingMediumType.anime
|
||||
: TrackingMediumType.manga,
|
||||
),
|
||||
);
|
||||
|
||||
_controller.jumpToPage(index);
|
||||
},
|
||||
items: [
|
||||
BottomBarItem(
|
||||
icon: const Icon(Icons.tv),
|
||||
title: Text(t.content.anime),
|
||||
activeColor: Colors.blue,
|
||||
),
|
||||
BottomBarItem(
|
||||
icon: const Icon(Icons.auto_stories),
|
||||
title: Text(t.content.manga),
|
||||
activeColor: Colors.red,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -20,81 +20,84 @@ class AnimeSearchPage extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<AnimeSearchBloc, AnimeSearchState>(
|
||||
builder: (context, state) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(
|
||||
state.trackingType == TrackingMediumType.anime
|
||||
? t.search.anime
|
||||
: t.search.manga,
|
||||
),
|
||||
),
|
||||
body: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: TextField(
|
||||
decoration: InputDecoration(
|
||||
border: const OutlineInputBorder(),
|
||||
labelText: t.search.query,
|
||||
),
|
||||
onSubmitted: (_) {
|
||||
context.read<AnimeSearchBloc>().add(
|
||||
SearchQuerySubmittedEvent(),
|
||||
);
|
||||
},
|
||||
onChanged: (value) {
|
||||
context.read<AnimeSearchBloc>().add(
|
||||
SearchQueryChangedEvent(value),
|
||||
);
|
||||
},
|
||||
),
|
||||
return BlocListener<AnimeSearchBloc, AnimeSearchState>(
|
||||
listener: (context, state) {},
|
||||
child: BlocBuilder<AnimeSearchBloc, AnimeSearchState>(
|
||||
builder: (context, state) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(
|
||||
state.trackingType == TrackingMediumType.anime
|
||||
? t.search.anime
|
||||
: t.search.manga,
|
||||
),
|
||||
if (state.working)
|
||||
const Expanded(
|
||||
child: Align(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
)
|
||||
else
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
itemCount: state.searchResults.length,
|
||||
itemBuilder: (context, index) {
|
||||
final item = state.searchResults[index];
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
context.read<AnimeSearchBloc>().add(
|
||||
ResultTappedEvent(item),
|
||||
);
|
||||
},
|
||||
child: ListItem(
|
||||
title: item.title,
|
||||
thumbnailUrl: item.thumbnailUrl,
|
||||
cached: false,
|
||||
extra: [
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
item.description,
|
||||
textAlign: TextAlign.justify,
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
maxLines: 4,
|
||||
softWrap: true,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
body: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: TextField(
|
||||
decoration: InputDecoration(
|
||||
border: const OutlineInputBorder(),
|
||||
labelText: t.search.query,
|
||||
),
|
||||
onSubmitted: (_) {
|
||||
context.read<AnimeSearchBloc>().add(
|
||||
SearchQuerySubmittedEvent(),
|
||||
);
|
||||
},
|
||||
onChanged: (value) {
|
||||
context.read<AnimeSearchBloc>().add(
|
||||
SearchQueryChangedEvent(value),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
if (state.working)
|
||||
const Expanded(
|
||||
child: Align(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
)
|
||||
else
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
itemCount: state.searchResults.length,
|
||||
itemBuilder: (context, index) {
|
||||
final item = state.searchResults[index];
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
context.read<AnimeSearchBloc>().add(
|
||||
ResultTappedEvent(item),
|
||||
);
|
||||
},
|
||||
child: ListItem(
|
||||
title: item.title,
|
||||
thumbnailUrl: item.thumbnailUrl,
|
||||
cached: false,
|
||||
extra: [
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
item.description,
|
||||
textAlign: TextAlign.justify,
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
maxLines: 4,
|
||||
softWrap: true,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +63,22 @@ class CalendarPage extends StatefulWidget {
|
||||
),
|
||||
);
|
||||
|
||||
static AppBar buildAppBar(BuildContext context) {
|
||||
return AppBar(
|
||||
title: Text(t.calendar.calendar),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
context.read<CalendarBloc>().add(
|
||||
RefreshPerformedEvent(),
|
||||
);
|
||||
},
|
||||
icon: const Icon(Icons.refresh),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
CalendarPageState createState() => CalendarPageState();
|
||||
}
|
||||
@@ -131,28 +147,20 @@ class CalendarPageState extends State<CalendarPage> {
|
||||
switch (anime.broadcastDay) {
|
||||
case 'Mondays':
|
||||
day = Weekday.monday;
|
||||
break;
|
||||
case 'Tuesdays':
|
||||
day = Weekday.tuesday;
|
||||
break;
|
||||
case 'Wednesdays':
|
||||
day = Weekday.wednesday;
|
||||
break;
|
||||
case 'Thursdays':
|
||||
day = Weekday.thursday;
|
||||
break;
|
||||
case 'Fridays':
|
||||
day = Weekday.friday;
|
||||
break;
|
||||
case 'Saturdays':
|
||||
day = Weekday.saturday;
|
||||
break;
|
||||
case 'Sundays':
|
||||
day = Weekday.sunday;
|
||||
break;
|
||||
default:
|
||||
day = Weekday.unknown;
|
||||
break;
|
||||
}
|
||||
|
||||
airingAnimeMap.addOrSet(day, anime);
|
||||
@@ -184,79 +192,63 @@ class CalendarPageState extends State<CalendarPage> {
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(t.calendar.calendar),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
context.read<CalendarBloc>().add(
|
||||
RefreshPerformedEvent(),
|
||||
);
|
||||
},
|
||||
icon: const Icon(Icons.refresh),
|
||||
child: Padding(
|
||||
padding: const EdgeInsetsGeometry.symmetric(
|
||||
horizontal: 12,
|
||||
),
|
||||
child: CustomScrollView(
|
||||
slivers: [
|
||||
// Render all available weekdays
|
||||
..._renderWeekdayList(
|
||||
context,
|
||||
Weekday.unknown,
|
||||
airingAnimeMap,
|
||||
),
|
||||
..._renderWeekdayList(
|
||||
context,
|
||||
Weekday.monday,
|
||||
airingAnimeMap,
|
||||
),
|
||||
..._renderWeekdayList(
|
||||
context,
|
||||
Weekday.tuesday,
|
||||
airingAnimeMap,
|
||||
),
|
||||
..._renderWeekdayList(
|
||||
context,
|
||||
Weekday.wednesday,
|
||||
airingAnimeMap,
|
||||
),
|
||||
..._renderWeekdayList(
|
||||
context,
|
||||
Weekday.thursday,
|
||||
airingAnimeMap,
|
||||
),
|
||||
..._renderWeekdayList(
|
||||
context,
|
||||
Weekday.friday,
|
||||
airingAnimeMap,
|
||||
),
|
||||
..._renderWeekdayList(
|
||||
context,
|
||||
Weekday.saturday,
|
||||
airingAnimeMap,
|
||||
),
|
||||
..._renderWeekdayList(
|
||||
context,
|
||||
Weekday.sunday,
|
||||
airingAnimeMap,
|
||||
),
|
||||
|
||||
// Provide a nice bottom padding, while keeping the elastic effect attached
|
||||
// to the bottom-most edge.
|
||||
const SliverToBoxAdapter(
|
||||
child: SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
drawer: getDrawer(context),
|
||||
body: Padding(
|
||||
padding: const EdgeInsetsGeometry.symmetric(
|
||||
horizontal: 12,
|
||||
),
|
||||
child: CustomScrollView(
|
||||
slivers: [
|
||||
// Render all available weekdays
|
||||
..._renderWeekdayList(
|
||||
context,
|
||||
Weekday.unknown,
|
||||
airingAnimeMap,
|
||||
),
|
||||
..._renderWeekdayList(
|
||||
context,
|
||||
Weekday.monday,
|
||||
airingAnimeMap,
|
||||
),
|
||||
..._renderWeekdayList(
|
||||
context,
|
||||
Weekday.tuesday,
|
||||
airingAnimeMap,
|
||||
),
|
||||
..._renderWeekdayList(
|
||||
context,
|
||||
Weekday.wednesday,
|
||||
airingAnimeMap,
|
||||
),
|
||||
..._renderWeekdayList(
|
||||
context,
|
||||
Weekday.thursday,
|
||||
airingAnimeMap,
|
||||
),
|
||||
..._renderWeekdayList(
|
||||
context,
|
||||
Weekday.friday,
|
||||
airingAnimeMap,
|
||||
),
|
||||
..._renderWeekdayList(
|
||||
context,
|
||||
Weekday.saturday,
|
||||
airingAnimeMap,
|
||||
),
|
||||
..._renderWeekdayList(
|
||||
context,
|
||||
Weekday.sunday,
|
||||
airingAnimeMap,
|
||||
),
|
||||
|
||||
// Provide a nice bottom padding, while keeping the elastic effect attached
|
||||
// to the bottom-most edge.
|
||||
const SliverToBoxAdapter(
|
||||
child: SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
|
||||
14
lib/src/ui/pages/main_page.dart
Normal file
14
lib/src/ui/pages/main_page.dart
Normal file
@@ -0,0 +1,14 @@
|
||||
import 'package:anitrack/src/ui/helpers.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// A page that renders the anime list and the calendar as nested views.
|
||||
class MainPage extends StatelessWidget {
|
||||
const MainPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
drawer: getDrawer(context),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
|
||||
class SettingsPage extends StatelessWidget {
|
||||
@@ -35,6 +36,12 @@ class SettingsPage extends StatelessWidget {
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(t.settings.title),
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
onPressed: () {
|
||||
GoRouter.of(context).pop();
|
||||
},
|
||||
),
|
||||
),
|
||||
body: ListView(
|
||||
children: [
|
||||
|
||||
49
lib/src/ui/widgets/shell_wrapper.dart
Normal file
49
lib/src/ui/widgets/shell_wrapper.dart
Normal file
@@ -0,0 +1,49 @@
|
||||
import 'package:anitrack/i18n/strings.g.dart';
|
||||
import 'package:anitrack/src/ui/constants.dart';
|
||||
import 'package:anitrack/src/ui/helpers.dart';
|
||||
import 'package:anitrack/src/ui/pages/anime_list.dart';
|
||||
import 'package:anitrack/src/ui/pages/calendar.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
class ShellWrapper extends StatelessWidget {
|
||||
const ShellWrapper({
|
||||
required this.state,
|
||||
required this.child,
|
||||
super.key,
|
||||
});
|
||||
|
||||
/// The current router state.
|
||||
final GoRouterState state;
|
||||
|
||||
/// The child to show.
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final currentPath = GoRouterState.of(context).uri.toString();
|
||||
|
||||
AppBar? appBar;
|
||||
Widget? drawer;
|
||||
Widget? fab;
|
||||
Widget? bottomNavigationBar;
|
||||
switch (currentPath) {
|
||||
case animeListRoute:
|
||||
drawer = getDrawer(context);
|
||||
appBar = AnimeListPage.buildAppBar(context);
|
||||
bottomNavigationBar = AnimeListPage.buildBottomNavigationBar(context);
|
||||
fab = AnimeListPage.buildFab();
|
||||
case calendarRoute:
|
||||
drawer = getDrawer(context);
|
||||
appBar = CalendarPage.buildAppBar(context);
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
appBar: appBar,
|
||||
bottomNavigationBar: bottomNavigationBar,
|
||||
drawer: drawer,
|
||||
floatingActionButton: fab,
|
||||
body: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user