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
 | 
			
		||||
abstract class TrackingMedium {
 | 
			
		||||
  /// The ID of the medium
 | 
			
		||||
  final String id = '';
 | 
			
		||||
 | 
			
		||||
  /// The title of the medium
 | 
			
		||||
  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 {
 | 
			
		||||
    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],
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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());
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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'),
 | 
			
		||||
                  ),
 | 
			
		||||
                ],
 | 
			
		||||
              ),
 | 
			
		||||
 | 
			
		||||
@ -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);
 | 
			
		||||
                  },
 | 
			
		||||
 | 
			
		||||
@ -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,
 | 
			
		||||
                    ),
 | 
			
		||||
                  ),
 | 
			
		||||
              ],
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user