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
abstract class TrackingMedium {
/// The ID of the medium
final String id = '';
/// The title of the medium
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 {
await _db.insert(
mangaTable,
@ -91,4 +99,12 @@ class DatabaseService {
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<AnimeUpdatedEvent>(_onAnimeUpdated);
on<MangaUpdatedEvent>(_onMangaUpdated);
on<AnimeRemovedEvent>(_onAnimeRemoved);
on<MangaRemovedEvent>(_onMangaRemoved);
}
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;
}
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 {
MangaAddedEvent(this.data);
@ -82,3 +89,10 @@ class MangaUpdatedEvent extends AnimeListEvent {
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<MangaDetailsRequestedEvent>(_onMangaRequested);
on<DetailsUpdatedEvent>(_onDetailsUpdated);
on<ItemRemovedEvent>(_onItemRemoved);
}
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 {
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/src/ui/constants.dart';
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
class AboutPage extends StatelessWidget {
AboutPage({
const AboutPage({
super.key,
});
static MaterialPageRoute<dynamic> get route => MaterialPageRoute<dynamic>(
builder: (_) => AboutPage(),
builder: (_) => const AboutPage(),
settings: const RouteSettings(
name: aboutRoute,
),
@ -20,7 +19,7 @@ class AboutPage extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('About'),
title: const Text('About'),
),
body: ListView.builder(
itemCount: ossLicenses.length + 1,
@ -29,7 +28,6 @@ class AboutPage extends StatelessWidget {
return Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Text(
@ -44,7 +42,7 @@ class AboutPage extends StatelessWidget {
mode: LaunchMode.externalApplication,
);
},
child: Text('Source')
child: const Text('Source'),
),
],
),

View File

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

View File

@ -60,6 +60,48 @@ class DetailsPage extends StatelessWidget {
softWrap: true,
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) {
context.read<DetailsBloc>().add(
DetailsUpdatedEvent(
(state.data as AnimeTrackingData).copyWith(
(state.data! as AnimeTrackingData).copyWith(
state: newState,
),
),
@ -87,7 +129,7 @@ class DetailsPage extends StatelessWidget {
} else if (state.trackingType == TrackingMediumType.manga) {
context.read<DetailsBloc>().add(
DetailsUpdatedEvent(
(state.data as MangaTrackingData).copyWith(
(state.data! as MangaTrackingData).copyWith(
state: newState,
),
),
@ -127,7 +169,7 @@ class DetailsPage extends StatelessWidget {
onChanged: (value) {
switch (state.trackingType) {
case TrackingMediumType.anime:
final data = state.data as AnimeTrackingData;
final data = state.data! as AnimeTrackingData;
context.read<DetailsBloc>().add(
DetailsUpdatedEvent(
data.copyWith(
@ -137,7 +179,7 @@ class DetailsPage extends StatelessWidget {
);
break;
case TrackingMediumType.manga:
final data = state.data as MangaTrackingData;
final data = state.data! as MangaTrackingData;
context.read<DetailsBloc>().add(
DetailsUpdatedEvent(
data.copyWith(
@ -149,8 +191,8 @@ class DetailsPage extends StatelessWidget {
}
},
initialValue: state.trackingType == TrackingMediumType.anime ?
(state.data as AnimeTrackingData).episodesWatched :
(state.data as MangaTrackingData).chaptersRead,
(state.data! as AnimeTrackingData).episodesWatched :
(state.data! as MangaTrackingData).chaptersRead,
),
),
@ -162,7 +204,7 @@ class DetailsPage extends StatelessWidget {
child: IntegerInput(
labelText: 'Volumes owned',
onChanged: (value) {
final data = state.data as MangaTrackingData;
final data = state.data! as MangaTrackingData;
context.read<DetailsBloc>().add(
DetailsUpdatedEvent(
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
jikan_api: ^2.0.0
json_annotation: 4.6.0
url_launcher: ^6.1.8
sqflite: ^2.2.4+1
swipeable_tile: ^2.0.0+3
url_launcher: ^6.1.8
dev_dependencies:
build_runner: ^2.1.11