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 'dart:math';
import 'package:anitrack/src/data/anime.dart'; import 'package:anitrack/src/data/anime.dart';
import 'package:anitrack/src/data/type.dart';
import 'package:anitrack/src/service/database.dart'; import 'package:anitrack/src/service/database.dart';
import 'package:bloc/bloc.dart'; import 'package:bloc/bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
@ -16,6 +17,7 @@ class AnimeListBloc extends Bloc<AnimeListEvent, AnimeListState> {
on<AnimeEpisodeDecrementedEvent>(_onDecremented); on<AnimeEpisodeDecrementedEvent>(_onDecremented);
on<AnimesLoadedEvent>(_onAnimesLoaded); on<AnimesLoadedEvent>(_onAnimesLoaded);
on<AnimeFilterChangedEvent>(_onAnimesFiltered); on<AnimeFilterChangedEvent>(_onAnimesFiltered);
on<AnimeTrackingTypeChanged>(_onTrackingTypeChanged);
} }
Future<void> _onAnimeAdded(AnimeAddedEvent event, Emitter<AnimeListState> emit) async { 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 { mixin _$AnimeListState {
List<AnimeTrackingData> get animes => throw _privateConstructorUsedError; List<AnimeTrackingData> get animes => throw _privateConstructorUsedError;
AnimeTrackingState get filterState => throw _privateConstructorUsedError; AnimeTrackingState get filterState => throw _privateConstructorUsedError;
TrackingMediumType get trackingType => throw _privateConstructorUsedError;
@JsonKey(ignore: true) @JsonKey(ignore: true)
$AnimeListStateCopyWith<AnimeListState> get copyWith => $AnimeListStateCopyWith<AnimeListState> get copyWith =>
@ -29,7 +30,10 @@ abstract class $AnimeListStateCopyWith<$Res> {
factory $AnimeListStateCopyWith( factory $AnimeListStateCopyWith(
AnimeListState value, $Res Function(AnimeListState) then) = AnimeListState value, $Res Function(AnimeListState) then) =
_$AnimeListStateCopyWithImpl<$Res>; _$AnimeListStateCopyWithImpl<$Res>;
$Res call({List<AnimeTrackingData> animes, AnimeTrackingState filterState}); $Res call(
{List<AnimeTrackingData> animes,
AnimeTrackingState filterState,
TrackingMediumType trackingType});
} }
/// @nodoc /// @nodoc
@ -45,6 +49,7 @@ class _$AnimeListStateCopyWithImpl<$Res>
$Res call({ $Res call({
Object? animes = freezed, Object? animes = freezed,
Object? filterState = freezed, Object? filterState = freezed,
Object? trackingType = freezed,
}) { }) {
return _then(_value.copyWith( return _then(_value.copyWith(
animes: animes == freezed animes: animes == freezed
@ -55,6 +60,10 @@ class _$AnimeListStateCopyWithImpl<$Res>
? _value.filterState ? _value.filterState
: filterState // ignore: cast_nullable_to_non_nullable : filterState // ignore: cast_nullable_to_non_nullable
as AnimeTrackingState, 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) = _$_AnimeListState value, $Res Function(_$_AnimeListState) then) =
__$$_AnimeListStateCopyWithImpl<$Res>; __$$_AnimeListStateCopyWithImpl<$Res>;
@override @override
$Res call({List<AnimeTrackingData> animes, AnimeTrackingState filterState}); $Res call(
{List<AnimeTrackingData> animes,
AnimeTrackingState filterState,
TrackingMediumType trackingType});
} }
/// @nodoc /// @nodoc
@ -84,6 +96,7 @@ class __$$_AnimeListStateCopyWithImpl<$Res>
$Res call({ $Res call({
Object? animes = freezed, Object? animes = freezed,
Object? filterState = freezed, Object? filterState = freezed,
Object? trackingType = freezed,
}) { }) {
return _then(_$_AnimeListState( return _then(_$_AnimeListState(
animes: animes == freezed animes: animes == freezed
@ -94,6 +107,10 @@ class __$$_AnimeListStateCopyWithImpl<$Res>
? _value.filterState ? _value.filterState
: filterState // ignore: cast_nullable_to_non_nullable : filterState // ignore: cast_nullable_to_non_nullable
as AnimeTrackingState, 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 { class _$_AnimeListState implements _AnimeListState {
_$_AnimeListState( _$_AnimeListState(
{final List<AnimeTrackingData> animes = const [], {final List<AnimeTrackingData> animes = const [],
this.filterState = AnimeTrackingState.watching}) this.filterState = AnimeTrackingState.watching,
this.trackingType = TrackingMediumType.anime})
: _animes = animes; : _animes = animes;
final List<AnimeTrackingData> _animes; final List<AnimeTrackingData> _animes;
@ -117,10 +135,13 @@ class _$_AnimeListState implements _AnimeListState {
@override @override
@JsonKey() @JsonKey()
final AnimeTrackingState filterState; final AnimeTrackingState filterState;
@override
@JsonKey()
final TrackingMediumType trackingType;
@override @override
String toString() { String toString() {
return 'AnimeListState(animes: $animes, filterState: $filterState)'; return 'AnimeListState(animes: $animes, filterState: $filterState, trackingType: $trackingType)';
} }
@override @override
@ -130,14 +151,17 @@ class _$_AnimeListState implements _AnimeListState {
other is _$_AnimeListState && other is _$_AnimeListState &&
const DeepCollectionEquality().equals(other._animes, _animes) && const DeepCollectionEquality().equals(other._animes, _animes) &&
const DeepCollectionEquality() const DeepCollectionEquality()
.equals(other.filterState, filterState)); .equals(other.filterState, filterState) &&
const DeepCollectionEquality()
.equals(other.trackingType, trackingType));
} }
@override @override
int get hashCode => Object.hash( int get hashCode => Object.hash(
runtimeType, runtimeType,
const DeepCollectionEquality().hash(_animes), const DeepCollectionEquality().hash(_animes),
const DeepCollectionEquality().hash(filterState)); const DeepCollectionEquality().hash(filterState),
const DeepCollectionEquality().hash(trackingType));
@JsonKey(ignore: true) @JsonKey(ignore: true)
@override @override
@ -148,13 +172,16 @@ class _$_AnimeListState implements _AnimeListState {
abstract class _AnimeListState implements AnimeListState { abstract class _AnimeListState implements AnimeListState {
factory _AnimeListState( factory _AnimeListState(
{final List<AnimeTrackingData> animes, {final List<AnimeTrackingData> animes,
final AnimeTrackingState filterState}) = _$_AnimeListState; final AnimeTrackingState filterState,
final TrackingMediumType trackingType}) = _$_AnimeListState;
@override @override
List<AnimeTrackingData> get animes; List<AnimeTrackingData> get animes;
@override @override
AnimeTrackingState get filterState; AnimeTrackingState get filterState;
@override @override
TrackingMediumType get trackingType;
@override
@JsonKey(ignore: true) @JsonKey(ignore: true)
_$$_AnimeListStateCopyWith<_$_AnimeListState> get copyWith => _$$_AnimeListStateCopyWith<_$_AnimeListState> get copyWith =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;

View File

@ -33,3 +33,11 @@ class AnimeFilterChangedEvent extends AnimeListEvent {
/// The state to filter /// The state to filter
final AnimeTrackingState filterState; 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({ factory AnimeListState({
@Default([]) List<AnimeTrackingData> animes, @Default([]) List<AnimeTrackingData> animes,
@Default(AnimeTrackingState.watching) AnimeTrackingState filterState, @Default(AnimeTrackingState.watching) AnimeTrackingState filterState,
@Default(TrackingMediumType.anime) TrackingMediumType trackingType,
}) = _AnimeListState; }) = _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/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_list_bloc.dart';
import 'package:anitrack/src/ui/bloc/anime_search_bloc.dart'; import 'package:anitrack/src/ui/bloc/anime_search_bloc.dart';
import 'package:anitrack/src/ui/constants.dart'; import 'package:anitrack/src/ui/constants.dart';
import 'package:anitrack/src/ui/widgets/anime.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 { class AnimeListPage extends StatelessWidget {
static MaterialPageRoute<dynamic> get route => MaterialPageRoute<dynamic>( 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocBuilder<AnimeListBloc, AnimeListState>( return BlocBuilder<AnimeListBloc, AnimeListState>(
builder: (context, state) { builder: (context, state) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text('Animes'), title: Text(
_getPageTitle(state.trackingType)
),
actions: [ actions: [
PopupMenuButton( PopupMenuButton(
icon: Icon( icon: Icon(
@ -57,28 +70,34 @@ class AnimeListPage extends StatelessWidget {
), ),
], ],
), ),
body: ListView.builder( body: PageView(
itemCount: state.animes.length, controller: _controller,
itemBuilder: (context, index) { children: [
final anime = state.animes[index]; ListView.builder(
if (state.filterState != AnimeTrackingState.all) { itemCount: state.animes.length,
if (anime.state != state.filterState) return Container(); itemBuilder: (context, index) {
} final anime = state.animes[index];
if (state.filterState != AnimeTrackingState.all) {
if (anime.state != state.filterState) return Container();
}
return AnimeListWidget( return AnimeListWidget(
data: anime, data: anime,
onLeftSwipe: () { onLeftSwipe: () {
context.read<AnimeListBloc>().add( context.read<AnimeListBloc>().add(
AnimeEpisodeDecrementedEvent(state.animes[index].id), AnimeEpisodeDecrementedEvent(state.animes[index].id),
);
},
onRightSwipe: () {
context.read<AnimeListBloc>().add(
AnimeEpisodeIncrementedEvent(state.animes[index].id),
);
},
); );
}, },
onRightSwipe: () { ),
context.read<AnimeListBloc>().add( Placeholder(),
AnimeEpisodeIncrementedEvent(state.animes[index].id), ],
);
},
);
},
), ),
floatingActionButton: FloatingActionButton( floatingActionButton: FloatingActionButton(
onPressed: () { onPressed: () {
@ -89,6 +108,33 @@ class AnimeListPage extends StatelessWidget {
tooltip: 'Increment', tooltip: 'Increment',
child: const Icon(Icons.add), 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" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.0" 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: build:
dependency: transitive dependency: transitive
description: description:

View File

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