feat(ui): Stub out a manga tracking page

This commit is contained in:
PapaTutuWawa 2023-02-04 11:48:24 +01:00
parent d273a6deb2
commit a60e9e8e81
8 changed files with 134 additions and 29 deletions

5
lib/src/data/type.dart Normal file
View File

@ -0,0 +1,5 @@
/// The type of medium we are tracking. Useful for UI stuff.
enum TrackingMediumType {
anime,
manga,
}

View File

@ -1,5 +1,6 @@
import 'dart:math';
import 'package:anitrack/src/data/anime.dart';
import 'package:anitrack/src/data/type.dart';
import 'package:anitrack/src/service/database.dart';
import 'package:bloc/bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
@ -16,6 +17,7 @@ class AnimeListBloc extends Bloc<AnimeListEvent, AnimeListState> {
on<AnimeEpisodeDecrementedEvent>(_onDecremented);
on<AnimesLoadedEvent>(_onAnimesLoaded);
on<AnimeFilterChangedEvent>(_onAnimesFiltered);
on<AnimeTrackingTypeChanged>(_onTrackingTypeChanged);
}
Future<void> _onAnimeAdded(AnimeAddedEvent event, Emitter<AnimeListState> emit) async {
@ -91,4 +93,12 @@ class AnimeListBloc extends Bloc<AnimeListEvent, AnimeListState> {
),
);
}
Future<void> _onTrackingTypeChanged(AnimeTrackingTypeChanged event, Emitter<AnimeListState> emit) async {
emit(
state.copyWith(
trackingType: event.type,
),
);
}
}

View File

@ -18,6 +18,7 @@ final _privateConstructorUsedError = UnsupportedError(
mixin _$AnimeListState {
List<AnimeTrackingData> get animes => throw _privateConstructorUsedError;
AnimeTrackingState get filterState => throw _privateConstructorUsedError;
TrackingMediumType get trackingType => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$AnimeListStateCopyWith<AnimeListState> get copyWith =>
@ -29,7 +30,10 @@ abstract class $AnimeListStateCopyWith<$Res> {
factory $AnimeListStateCopyWith(
AnimeListState value, $Res Function(AnimeListState) then) =
_$AnimeListStateCopyWithImpl<$Res>;
$Res call({List<AnimeTrackingData> animes, AnimeTrackingState filterState});
$Res call(
{List<AnimeTrackingData> animes,
AnimeTrackingState filterState,
TrackingMediumType trackingType});
}
/// @nodoc
@ -45,6 +49,7 @@ class _$AnimeListStateCopyWithImpl<$Res>
$Res call({
Object? animes = freezed,
Object? filterState = freezed,
Object? trackingType = freezed,
}) {
return _then(_value.copyWith(
animes: animes == freezed
@ -55,6 +60,10 @@ class _$AnimeListStateCopyWithImpl<$Res>
? _value.filterState
: filterState // ignore: cast_nullable_to_non_nullable
as AnimeTrackingState,
trackingType: trackingType == freezed
? _value.trackingType
: trackingType // ignore: cast_nullable_to_non_nullable
as TrackingMediumType,
));
}
}
@ -66,7 +75,10 @@ abstract class _$$_AnimeListStateCopyWith<$Res>
_$_AnimeListState value, $Res Function(_$_AnimeListState) then) =
__$$_AnimeListStateCopyWithImpl<$Res>;
@override
$Res call({List<AnimeTrackingData> animes, AnimeTrackingState filterState});
$Res call(
{List<AnimeTrackingData> animes,
AnimeTrackingState filterState,
TrackingMediumType trackingType});
}
/// @nodoc
@ -84,6 +96,7 @@ class __$$_AnimeListStateCopyWithImpl<$Res>
$Res call({
Object? animes = freezed,
Object? filterState = freezed,
Object? trackingType = freezed,
}) {
return _then(_$_AnimeListState(
animes: animes == freezed
@ -94,6 +107,10 @@ class __$$_AnimeListStateCopyWithImpl<$Res>
? _value.filterState
: filterState // ignore: cast_nullable_to_non_nullable
as AnimeTrackingState,
trackingType: trackingType == freezed
? _value.trackingType
: trackingType // ignore: cast_nullable_to_non_nullable
as TrackingMediumType,
));
}
}
@ -103,7 +120,8 @@ class __$$_AnimeListStateCopyWithImpl<$Res>
class _$_AnimeListState implements _AnimeListState {
_$_AnimeListState(
{final List<AnimeTrackingData> animes = const [],
this.filterState = AnimeTrackingState.watching})
this.filterState = AnimeTrackingState.watching,
this.trackingType = TrackingMediumType.anime})
: _animes = animes;
final List<AnimeTrackingData> _animes;
@ -117,10 +135,13 @@ class _$_AnimeListState implements _AnimeListState {
@override
@JsonKey()
final AnimeTrackingState filterState;
@override
@JsonKey()
final TrackingMediumType trackingType;
@override
String toString() {
return 'AnimeListState(animes: $animes, filterState: $filterState)';
return 'AnimeListState(animes: $animes, filterState: $filterState, trackingType: $trackingType)';
}
@override
@ -130,14 +151,17 @@ class _$_AnimeListState implements _AnimeListState {
other is _$_AnimeListState &&
const DeepCollectionEquality().equals(other._animes, _animes) &&
const DeepCollectionEquality()
.equals(other.filterState, filterState));
.equals(other.filterState, filterState) &&
const DeepCollectionEquality()
.equals(other.trackingType, trackingType));
}
@override
int get hashCode => Object.hash(
runtimeType,
const DeepCollectionEquality().hash(_animes),
const DeepCollectionEquality().hash(filterState));
const DeepCollectionEquality().hash(filterState),
const DeepCollectionEquality().hash(trackingType));
@JsonKey(ignore: true)
@override
@ -148,13 +172,16 @@ class _$_AnimeListState implements _AnimeListState {
abstract class _AnimeListState implements AnimeListState {
factory _AnimeListState(
{final List<AnimeTrackingData> animes,
final AnimeTrackingState filterState}) = _$_AnimeListState;
final AnimeTrackingState filterState,
final TrackingMediumType trackingType}) = _$_AnimeListState;
@override
List<AnimeTrackingData> get animes;
@override
AnimeTrackingState get filterState;
@override
TrackingMediumType get trackingType;
@override
@JsonKey(ignore: true)
_$$_AnimeListStateCopyWith<_$_AnimeListState> get copyWith =>
throw _privateConstructorUsedError;

View File

@ -33,3 +33,11 @@ class AnimeFilterChangedEvent extends AnimeListEvent {
/// The state to filter
final AnimeTrackingState filterState;
}
/// Triggered when the view is changed from the anime or the manga view
class AnimeTrackingTypeChanged extends AnimeListEvent {
AnimeTrackingTypeChanged(this.type);
/// The type we switched to
final TrackingMediumType type;
}

View File

@ -5,5 +5,6 @@ class AnimeListState with _$AnimeListState {
factory AnimeListState({
@Default([]) List<AnimeTrackingData> animes,
@Default(AnimeTrackingState.watching) AnimeTrackingState filterState,
@Default(TrackingMediumType.anime) TrackingMediumType trackingType,
}) = _AnimeListState;
}

View File

@ -1,10 +1,12 @@
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_list_bloc.dart';
import 'package:anitrack/src/ui/bloc/anime_search_bloc.dart';
import 'package:anitrack/src/ui/constants.dart';
import 'package:anitrack/src/ui/widgets/anime.dart';
import 'package:bottom_bar/bottom_bar.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class AnimeListPage extends StatelessWidget {
static MaterialPageRoute<dynamic> get route => MaterialPageRoute<dynamic>(
@ -14,13 +16,24 @@ class AnimeListPage extends StatelessWidget {
),
);
final PageController _controller = PageController();
String _getPageTitle(TrackingMediumType type) {
switch (type) {
case TrackingMediumType.anime: return 'Anime';
case TrackingMediumType.manga: return 'Manga';
}
}
@override
Widget build(BuildContext context) {
return BlocBuilder<AnimeListBloc, AnimeListState>(
builder: (context, state) {
return Scaffold(
appBar: AppBar(
title: Text('Animes'),
title: Text(
_getPageTitle(state.trackingType)
),
actions: [
PopupMenuButton(
icon: Icon(
@ -57,28 +70,34 @@ class AnimeListPage extends StatelessWidget {
),
],
),
body: ListView.builder(
itemCount: state.animes.length,
itemBuilder: (context, index) {
final anime = state.animes[index];
if (state.filterState != AnimeTrackingState.all) {
if (anime.state != state.filterState) return Container();
}
body: PageView(
controller: _controller,
children: [
ListView.builder(
itemCount: state.animes.length,
itemBuilder: (context, index) {
final anime = state.animes[index];
if (state.filterState != AnimeTrackingState.all) {
if (anime.state != state.filterState) return Container();
}
return AnimeListWidget(
data: anime,
onLeftSwipe: () {
context.read<AnimeListBloc>().add(
AnimeEpisodeDecrementedEvent(state.animes[index].id),
return AnimeListWidget(
data: anime,
onLeftSwipe: () {
context.read<AnimeListBloc>().add(
AnimeEpisodeDecrementedEvent(state.animes[index].id),
);
},
onRightSwipe: () {
context.read<AnimeListBloc>().add(
AnimeEpisodeIncrementedEvent(state.animes[index].id),
);
},
);
},
onRightSwipe: () {
context.read<AnimeListBloc>().add(
AnimeEpisodeIncrementedEvent(state.animes[index].id),
);
},
);
},
),
Placeholder(),
],
),
floatingActionButton: FloatingActionButton(
onPressed: () {
@ -89,6 +108,33 @@ class AnimeListPage extends StatelessWidget {
tooltip: 'Increment',
child: const Icon(Icons.add),
),
bottomNavigationBar: BottomBar(
selectedIndex: state.trackingType == TrackingMediumType.anime ?
0 :
1,
onTap: (int index) {
_controller.jumpToPage(index);
context.read<AnimeListBloc>().add(
AnimeTrackingTypeChanged(
index == 0 ?
TrackingMediumType.anime :
TrackingMediumType.manga,
),
);
},
items: <BottomBarItem>[
BottomBarItem(
icon: Icon(Icons.tv),
title: Text('Anime'),
activeColor: Colors.blue,
),
BottomBarItem(
icon: Icon(Icons.auto_stories),
title: Text('Manga'),
activeColor: Colors.red,
),
],
),
);
},
);

View File

@ -43,6 +43,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
bottom_bar:
dependency: "direct main"
description:
name: bottom_bar
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.3"
build:
dependency: transitive
description:

View File

@ -9,6 +9,7 @@ environment:
dependencies:
bloc: ^8.1.0
bottom_bar: ^2.0.3
cupertino_icons: ^1.0.2
flutter:
sdk: flutter