feat(meta): Implement removing tracking items
This commit is contained in:
parent
b240870ebf
commit
8df287b6eb
@ -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 = '';
|
||||||
|
|
||||||
|
@ -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],
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
@ -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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -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);
|
||||||
},
|
},
|
||||||
|
@ -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,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user