feat(meta): Implement removing tracking items

This commit is contained in:
PapaTutuWawa 2023-02-06 20:40:18 +01:00
parent b240870ebf
commit 8df287b6eb
10 changed files with 153 additions and 19 deletions

View File

@ -17,6 +17,9 @@ enum MediumTrackingState {
/// Interface for the Anime and Manga data classes /// Interface for the Anime and Manga data classes
abstract class TrackingMedium { abstract class TrackingMedium {
/// The ID of the medium
final String id = '';
/// The title of the medium /// The title of the medium
final String title = ''; final String title = '';

View File

@ -76,6 +76,14 @@ class DatabaseService {
); );
} }
Future<void> deleteAnime(String id) async {
await _db.delete(
animeTable,
where: 'id = ?',
whereArgs: [id],
);
}
Future<void> addManga(MangaTrackingData data) async { Future<void> addManga(MangaTrackingData data) async {
await _db.insert( await _db.insert(
mangaTable, mangaTable,
@ -91,4 +99,12 @@ class DatabaseService {
whereArgs: [data.id], whereArgs: [data.id],
); );
} }
Future<void> deleteManga(String id) async {
await _db.delete(
mangaTable,
where: 'id = ?',
whereArgs: [id],
);
}
} }

View File

@ -24,6 +24,8 @@ class AnimeListBloc extends Bloc<AnimeListEvent, AnimeListState> {
on<MangaChapterDecrementedEvent>(_onMangaDecremented); on<MangaChapterDecrementedEvent>(_onMangaDecremented);
on<AnimeUpdatedEvent>(_onAnimeUpdated); on<AnimeUpdatedEvent>(_onAnimeUpdated);
on<MangaUpdatedEvent>(_onMangaUpdated); on<MangaUpdatedEvent>(_onMangaUpdated);
on<AnimeRemovedEvent>(_onAnimeRemoved);
on<MangaRemovedEvent>(_onMangaRemoved);
} }
Future<void> _onAnimeAdded(AnimeAddedEvent event, Emitter<AnimeListState> emit) async { Future<void> _onAnimeAdded(AnimeAddedEvent event, Emitter<AnimeListState> emit) async {
@ -206,4 +208,30 @@ class AnimeListBloc extends Bloc<AnimeListEvent, AnimeListState> {
), ),
); );
} }
Future<void> _onAnimeRemoved(AnimeRemovedEvent event, Emitter<AnimeListState> emit) async {
emit(
state.copyWith(
animes: List.from(
state.animes.where((anime) => anime.id != event.id),
),
),
);
// Update the database
await GetIt.I.get<DatabaseService>().deleteAnime(event.id);
}
Future<void> _onMangaRemoved(MangaRemovedEvent event, Emitter<AnimeListState> emit) async {
emit(
state.copyWith(
mangas: List.from(
state.mangas.where((manga) => manga.id != event.id),
),
),
);
// Update the database
await GetIt.I.get<DatabaseService>().deleteManga(event.id);
}
} }

View File

@ -48,6 +48,13 @@ class AnimeUpdatedEvent extends AnimeListEvent {
final AnimeTrackingData anime; final AnimeTrackingData anime;
} }
class AnimeRemovedEvent extends AnimeListEvent {
AnimeRemovedEvent(this.id);
/// The ID of the anime to be removed from the list.
final String id;
}
class MangaAddedEvent extends AnimeListEvent { class MangaAddedEvent extends AnimeListEvent {
MangaAddedEvent(this.data); MangaAddedEvent(this.data);
@ -82,3 +89,10 @@ class MangaUpdatedEvent extends AnimeListEvent {
final MangaTrackingData manga; final MangaTrackingData manga;
} }
class MangaRemovedEvent extends AnimeListEvent {
MangaRemovedEvent(this.id);
/// The ID of the manga to be removed from the list.
final String id;
}

View File

@ -18,6 +18,7 @@ class DetailsBloc extends Bloc<DetailsEvent, DetailsState> {
on<AnimeDetailsRequestedEvent>(_onAnimeRequested); on<AnimeDetailsRequestedEvent>(_onAnimeRequested);
on<MangaDetailsRequestedEvent>(_onMangaRequested); on<MangaDetailsRequestedEvent>(_onMangaRequested);
on<DetailsUpdatedEvent>(_onDetailsUpdated); on<DetailsUpdatedEvent>(_onDetailsUpdated);
on<ItemRemovedEvent>(_onItemRemoved);
} }
Future<void> _onAnimeRequested(AnimeDetailsRequestedEvent event, Emitter<DetailsState> emit) async { Future<void> _onAnimeRequested(AnimeDetailsRequestedEvent event, Emitter<DetailsState> emit) async {
@ -77,4 +78,26 @@ class DetailsBloc extends Bloc<DetailsEvent, DetailsState> {
); );
} }
} }
Future<void> _onItemRemoved(ItemRemovedEvent event, Emitter<DetailsState> emit) async {
emit(
state.copyWith(
data: null,
),
);
/// Remove the item from the database
final bloc = GetIt.I.get<AnimeListBloc>();
switch (event.trackingType) {
case TrackingMediumType.anime:
bloc.add(AnimeRemovedEvent(event.id));
break;
case TrackingMediumType.manga:
bloc.add(MangaRemovedEvent(event.id));
break;
}
// Navigate back
GetIt.I.get<NavigationBloc>().add(PoppedRouteEvent());
}
} }

View File

@ -19,5 +19,15 @@ class MangaDetailsRequestedEvent extends DetailsEvent {
class DetailsUpdatedEvent extends DetailsEvent { class DetailsUpdatedEvent extends DetailsEvent {
DetailsUpdatedEvent(this.data); DetailsUpdatedEvent(this.data);
final dynamic data; final TrackingMedium data;
}
class ItemRemovedEvent extends DetailsEvent {
ItemRemovedEvent(this.id, this.trackingType);
/// The ID of the item to be removed
final String id;
/// The type of medium of the item
final TrackingMediumType trackingType;
} }

View File

@ -1,16 +1,15 @@
import 'dart:io';
import 'package:anitrack/licenses.g.dart'; import 'package:anitrack/licenses.g.dart';
import 'package:anitrack/src/ui/constants.dart'; import 'package:anitrack/src/ui/constants.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
class AboutPage extends StatelessWidget { class AboutPage extends StatelessWidget {
AboutPage({ const AboutPage({
super.key, super.key,
}); });
static MaterialPageRoute<dynamic> get route => MaterialPageRoute<dynamic>( static MaterialPageRoute<dynamic> get route => MaterialPageRoute<dynamic>(
builder: (_) => AboutPage(), builder: (_) => const AboutPage(),
settings: const RouteSettings( settings: const RouteSettings(
name: aboutRoute, name: aboutRoute,
), ),
@ -20,7 +19,7 @@ class AboutPage extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text('About'), title: const Text('About'),
), ),
body: ListView.builder( body: ListView.builder(
itemCount: ossLicenses.length + 1, itemCount: ossLicenses.length + 1,
@ -29,7 +28,6 @@ class AboutPage extends StatelessWidget {
return Padding( return Padding(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Text( Text(
@ -44,7 +42,7 @@ class AboutPage extends StatelessWidget {
mode: LaunchMode.externalApplication, mode: LaunchMode.externalApplication,
); );
}, },
child: Text('Source') child: const Text('Source'),
), ),
], ],
), ),

View File

@ -101,7 +101,7 @@ class AnimeListPage extends StatelessWidget {
drawer: Drawer( drawer: Drawer(
child: ListView( child: ListView(
children: [ children: [
DrawerHeader( const DrawerHeader(
decoration: BoxDecoration( decoration: BoxDecoration(
color: Color(0xffcf4aff), color: Color(0xffcf4aff),
), ),
@ -115,8 +115,8 @@ class AnimeListPage extends StatelessWidget {
), ),
ListTile( ListTile(
leading: Icon(Icons.info), leading: const Icon(Icons.info),
title: Text('About'), title: const Text('About'),
onTap: () { onTap: () {
Navigator.of(context).pushNamed(aboutRoute); Navigator.of(context).pushNamed(aboutRoute);
}, },

View File

@ -60,6 +60,48 @@ class DetailsPage extends StatelessWidget {
softWrap: true, softWrap: true,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
ElevatedButton(
onPressed: () async {
final result = await showDialog<bool>(
context: context,
builder: (context) {
return AlertDialog(
title: Text('Remove "${state.data!.title}"?'),
content: Text('Are you sure you want to remove "${state.data!.title}" from the list?'),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop(true);
},
style: TextButton.styleFrom(
foregroundColor: Colors.red,
),
child: const Text('Remove'),
),
TextButton(
onPressed: () {
Navigator.of(context).pop(false);
},
child: const Text('Cancel'),
),
],
);
},
);
if (result != true) return;
// ignore: use_build_context_synchronously
context.read<DetailsBloc>().add(
ItemRemovedEvent(
state.data!.id,
state.trackingType,
),
);
},
child: const Icon(Icons.delete),
),
], ],
), ),
), ),
@ -79,7 +121,7 @@ class DetailsPage extends StatelessWidget {
if (state.trackingType == TrackingMediumType.anime) { if (state.trackingType == TrackingMediumType.anime) {
context.read<DetailsBloc>().add( context.read<DetailsBloc>().add(
DetailsUpdatedEvent( DetailsUpdatedEvent(
(state.data as AnimeTrackingData).copyWith( (state.data! as AnimeTrackingData).copyWith(
state: newState, state: newState,
), ),
), ),
@ -87,7 +129,7 @@ class DetailsPage extends StatelessWidget {
} else if (state.trackingType == TrackingMediumType.manga) { } else if (state.trackingType == TrackingMediumType.manga) {
context.read<DetailsBloc>().add( context.read<DetailsBloc>().add(
DetailsUpdatedEvent( DetailsUpdatedEvent(
(state.data as MangaTrackingData).copyWith( (state.data! as MangaTrackingData).copyWith(
state: newState, state: newState,
), ),
), ),
@ -127,7 +169,7 @@ class DetailsPage extends StatelessWidget {
onChanged: (value) { onChanged: (value) {
switch (state.trackingType) { switch (state.trackingType) {
case TrackingMediumType.anime: case TrackingMediumType.anime:
final data = state.data as AnimeTrackingData; final data = state.data! as AnimeTrackingData;
context.read<DetailsBloc>().add( context.read<DetailsBloc>().add(
DetailsUpdatedEvent( DetailsUpdatedEvent(
data.copyWith( data.copyWith(
@ -137,7 +179,7 @@ class DetailsPage extends StatelessWidget {
); );
break; break;
case TrackingMediumType.manga: case TrackingMediumType.manga:
final data = state.data as MangaTrackingData; final data = state.data! as MangaTrackingData;
context.read<DetailsBloc>().add( context.read<DetailsBloc>().add(
DetailsUpdatedEvent( DetailsUpdatedEvent(
data.copyWith( data.copyWith(
@ -149,8 +191,8 @@ class DetailsPage extends StatelessWidget {
} }
}, },
initialValue: state.trackingType == TrackingMediumType.anime ? initialValue: state.trackingType == TrackingMediumType.anime ?
(state.data as AnimeTrackingData).episodesWatched : (state.data! as AnimeTrackingData).episodesWatched :
(state.data as MangaTrackingData).chaptersRead, (state.data! as MangaTrackingData).chaptersRead,
), ),
), ),
@ -162,7 +204,7 @@ class DetailsPage extends StatelessWidget {
child: IntegerInput( child: IntegerInput(
labelText: 'Volumes owned', labelText: 'Volumes owned',
onChanged: (value) { onChanged: (value) {
final data = state.data as MangaTrackingData; final data = state.data! as MangaTrackingData;
context.read<DetailsBloc>().add( context.read<DetailsBloc>().add(
DetailsUpdatedEvent( DetailsUpdatedEvent(
data.copyWith( data.copyWith(
@ -171,7 +213,7 @@ class DetailsPage extends StatelessWidget {
), ),
); );
}, },
initialValue: (GetIt.I.get<DetailsBloc>().state.data as MangaTrackingData).volumesOwned, initialValue: (GetIt.I.get<DetailsBloc>().state.data! as MangaTrackingData).volumesOwned,
), ),
), ),
], ],

View File

@ -19,9 +19,9 @@ dependencies:
get_it: ^7.2.0 get_it: ^7.2.0
jikan_api: ^2.0.0 jikan_api: ^2.0.0
json_annotation: 4.6.0 json_annotation: 4.6.0
url_launcher: ^6.1.8
sqflite: ^2.2.4+1 sqflite: ^2.2.4+1
swipeable_tile: ^2.0.0+3 swipeable_tile: ^2.0.0+3
url_launcher: ^6.1.8
dev_dependencies: dev_dependencies:
build_runner: ^2.1.11 build_runner: ^2.1.11