Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Delete sighting #106

Merged
merged 11 commits into from
May 31, 2024
15 changes: 10 additions & 5 deletions packages/app/lib/locales/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"createSightingError": "Something went wrong: {error}",
"createSightingScreenTitle": "Add Sighting",
"createSightingSuccess": "Yay, you've successfully added a new sighting to the database",
"deleteSighting": "Delete Sighting",
"editCardCancelButton": "Cancel",
"editCardSaveButton": "Save",
"editSpeciesErrorCantRemove": "Species needs to be defined",
Expand All @@ -27,9 +28,9 @@
"imageDeleteConfirmation": "Image deleted.",
"imageLoadingError": "Could not load image",
"imageMissingError": "No image given",
"localNameCardSaveAction": "Save",
"localNameCardTitle": "Local Name",
"localNamesCardTitle": "Local Names",
"localNameCardSaveAction": "Save",
"locationAdd": "Do you want to add a GPS location?",
"locationAddAction": "Add location to image",
"locationErrorPermissionDenied": "Location permission was denied",
Expand Down Expand Up @@ -57,11 +58,13 @@
"settingsSystemInfoError": "Could not retrieve system informations",
"settingsSystemInfoSDK": "Android SDK Version",
"settingsSystemInformation": "System Information",
"sightingDeleteAlertBody": "Are you sure you want to delete this sighting?",
"sightingDeleteAlertCancel": "Cancel",
"sightingDeleteAlertConfirm": "Delete",
"sightingDeleteAlertTitle": "Delete Sighting",
"sightingDeleteConfirmation": "Sighting deleted.",
"sightingScreenTitle": "Sighting",
"sightingUnspecified": "Unknown species",
"speciesScreenTitle": "Species",
"usedForCardTitle": "Used For",
"usedForTextSave": "Save",
"speciesCardTitle": "Species",
"speciesDescription": "Description",
"speciesScreenTitle": "Species",
Expand All @@ -73,5 +76,7 @@
"taxonomyPhylum": "Phylum",
"taxonomySpecies": "Species",
"taxonomySubfamily": "Subfamily",
"taxonomyTribe": "Tribe"
"taxonomyTribe": "Tribe",
"usedForCardTitle": "Used For",
"usedForTextSave": "Save"
}
67 changes: 38 additions & 29 deletions packages/app/lib/ui/screens/sighting.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import 'package:app/ui/widgets/local_name_field.dart';
import 'package:app/ui/widgets/note_field.dart';
import 'package:app/ui/widgets/refresh_provider.dart';
import 'package:app/ui/widgets/scaffold.dart';
import 'package:app/ui/widgets/sighting_popup_menu.dart';
import 'package:app/ui/widgets/species_field.dart';
import 'package:app/ui/widgets/used_for_field.dart';

Expand All @@ -33,35 +34,43 @@ class SightingScreen extends StatefulWidget {
class _SightingScreenState extends State<SightingScreen> {
@override
Widget build(BuildContext context) {
return MeliScaffold(
title: AppLocalizations.of(context)!.sightingScreenTitle,
backgroundColor: MeliColors.electric,
appBarColor: MeliColors.electric,
body: SingleChildScrollView(
child: Query(
options:
QueryOptions(document: gql(sightingQuery(widget.documentId))),
builder: (result, {VoidCallback? refetch, FetchMore? fetchMore}) {
if (result.hasException) {
return ErrorCard(message: result.exception.toString());
}

if (result.isLoading) {
return const Center(
child: SizedBox(
width: 50,
height: 50,
child:
CircularProgressIndicator(color: MeliColors.black)),
);
}

final sighting = Sighting.fromJson(
result.data?['sighting'] as Map<String, dynamic>);

return SightingProfile(sighting);
}),
));
return Query(
options: QueryOptions(document: gql(sightingQuery(widget.documentId))),
builder: (result, {VoidCallback? refetch, FetchMore? fetchMore}) {
Sighting? sighting;

if (!result.hasException && !result.isLoading) {
sighting = Sighting.fromJson(
result.data?['sighting'] as Map<String, dynamic>);
}

return MeliScaffold(
title: AppLocalizations.of(context)!.sightingScreenTitle,
backgroundColor: MeliColors.electric,
appBarColor: MeliColors.electric,
actionRight: sighting != null
? SightingPopupMenu(viewId: sighting.viewId)
: null,
body: SingleChildScrollView(
child: Builder(builder: (BuildContext context) {
if (result.hasException) {
return ErrorCard(message: result.exception.toString());
}

if (result.isLoading) {
return const Center(
child: SizedBox(
width: 50,
height: 50,
child: CircularProgressIndicator(
color: MeliColors.black)),
);
}

return SightingProfile(sighting!);
}),
));
});
}
}

Expand Down
10 changes: 6 additions & 4 deletions packages/app/lib/ui/screens/species.dart
Original file line number Diff line number Diff line change
Expand Up @@ -138,11 +138,13 @@ class _SpeciesProfileState extends State<SpeciesProfile> {
onTap: (DocumentId sightingId) {
router.pushNamed(RoutePaths.sighting.name,
pathParameters: {'documentId': sightingId}).then((value) {
final refreshProvider = RefreshProvider.of(context);
// Force loading the species again after we've returned from
// an updated sighting, to make sure that aggregated data over
// all sightings is up-to-date
if (RefreshProvider.of(context)
.isDirty(RefreshKeys.UpdatedSighting) &&
// an updated or deleted sighting, to make sure that
// aggregated data over all sightings is up-to-date
if ((refreshProvider.isDirty(RefreshKeys.UpdatedSighting) ||
refreshProvider
.isDirty(RefreshKeys.DeletedSighting)) &&
widget.refetch != null) {
widget.refetch!();
}
Expand Down
7 changes: 2 additions & 5 deletions packages/app/lib/ui/widgets/refresh_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,11 @@

import 'package:flutter/widgets.dart';

/// Actions which might affect other views.
enum RefreshKeys {
/// New sighting got created which might affect other views.
CreatedSighting,

/// Sighting got updated with data which might affect other views.
UpdatedSighting,

/// Species got updated with data which might affect other views.
DeletedSighting,
UpdatedSpecies,
}

Expand Down
30 changes: 20 additions & 10 deletions packages/app/lib/ui/widgets/scaffold.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import 'package:app/ui/colors.dart';
class MeliScaffold extends StatefulWidget {
final String? title;
final Widget? body;
final Widget? actionRight;
final Color backgroundColor;
final Color appBarColor;
final List<Widget> floatingActionButtons;
Expand All @@ -16,6 +17,7 @@ class MeliScaffold extends StatefulWidget {
{super.key,
this.body,
this.title,
this.actionRight,
this.floatingActionButtons = const [],
this.fabAlignment = MainAxisAlignment.spaceBetween,
this.appBarColor = MeliColors.flurry,
Expand All @@ -35,16 +37,24 @@ class _MeliScaffoldState extends State<MeliScaffold> {
shadowColor: Colors.black54,
forceMaterialTransparency: false,
backgroundColor: widget.appBarColor,
title: Row(mainAxisAlignment: MainAxisAlignment.center, children: [
IconButton(
icon: const Icon(Icons.arrow_back_rounded),
onPressed: () {
Navigator.of(context).pop();
}),
const SizedBox(width: 7.0),
Text(widget.title!),
const SizedBox(width: 35.0),
]),
title: Stack(
children: [
Row(mainAxisAlignment: MainAxisAlignment.center, children: [
IconButton(
icon: const Icon(Icons.arrow_back_rounded),
onPressed: () {
Navigator.of(context).pop();
}),
const SizedBox(width: 7.0),
Text(widget.title!),
const SizedBox(width: 35.0),
]),
if (widget.actionRight != null)
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [widget.actionRight!]),
],
),
);
}

Expand Down
61 changes: 61 additions & 0 deletions packages/app/lib/ui/widgets/sighting_popup_menu.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// SPDX-License-Identifier: AGPL-3.0-or-later

import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';

import 'package:app/io/p2panda/publish.dart';
import 'package:app/models/sightings.dart';
import 'package:app/router.dart';
import 'package:app/ui/widgets/confirm_dialog.dart';
import 'package:app/ui/widgets/refresh_provider.dart';

class SightingPopupMenu extends StatelessWidget {
final DocumentViewId viewId;

const SightingPopupMenu({super.key, required this.viewId});

void _onDelete(BuildContext context) {
final messenger = ScaffoldMessenger.of(context);
final t = AppLocalizations.of(context)!;
final refreshProvider = RefreshProvider.of(context);

showDialog<String>(
context: context,
builder: (BuildContext context) => ConfirmDialog(
title: t.sightingDeleteAlertTitle,
message: t.sightingDeleteAlertBody,
labelAbort: t.sightingDeleteAlertCancel,
labelConfirm: t.sightingDeleteAlertConfirm,
onConfirm: () async {
// @TODO: also delete all uses documents and hive location documents.
await deleteSighting(viewId);

// Set flag for other widgets to tell them that they might need to
// re-render their data. This will make sure that our updates are
// reflected in the UI
refreshProvider.setDirty(RefreshKeys.DeletedSighting);

// First pop closes this dialog, second goes back to the view we came
// from
router.pop();
router.pop();

messenger.showSnackBar(
SnackBar(content: Text(t.sightingDeleteConfirmation)));
},
),
);
}

@override
Widget build(BuildContext context) {
return PopupMenuButton(
itemBuilder: (BuildContext context) => [
PopupMenuItem<void>(
child: Text(AppLocalizations.of(context)!.deleteSighting),
onTap: () {
_onDelete(context);
}),
]);
}
}
8 changes: 5 additions & 3 deletions packages/app/lib/ui/widgets/sightings_list.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@ class _SightingsListState extends State<SightingsList> {
return SightingCard(
onTap: () => router.pushNamed(RoutePaths.sighting.name,
pathParameters: {'documentId': sighting.id}).then((value) {
// Refresh list when we've returned from updating a sighting
if (RefreshProvider.of(context)
.isDirty(RefreshKeys.UpdatedSighting) &&
final refreshProvider = RefreshProvider.of(context);
// Refresh list when we've returned from updating or deleting a
// sighting
if ((refreshProvider.isDirty(RefreshKeys.UpdatedSighting) ||
refreshProvider.isDirty(RefreshKeys.DeletedSighting)) &&
widget.paginator.refresh != null) {
widget.paginator.refresh!();
}
Expand Down