Skip to content

Commit

Permalink
Delete attribute modal is not as the other modals (#418)
Browse files Browse the repository at this point in the history
* feat: add BoltStyledText widget

* feat: start bottom sheet kit

* chore: update pubspec

* fix: completely overhaul delete_attribute

* fix: actually delete shared to peer attributes

* fix: jumpy
  • Loading branch information
jkoenig134 authored Jan 31, 2025
1 parent 581cf06 commit 262bca5
Show file tree
Hide file tree
Showing 11 changed files with 215 additions and 154 deletions.
255 changes: 107 additions & 148 deletions apps/enmeshed/lib/core/modals/delete_attribute.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import 'package:intl/intl.dart';
import 'package:logger/logger.dart';
import 'package:renderers/renderers.dart';
import 'package:vector_graphics/vector_graphics.dart';
import 'package:wolt_modal_sheet/wolt_modal_sheet.dart';

import '../utils/extensions.dart';

Expand All @@ -19,166 +18,83 @@ Future<void> showDeleteAttributeModal({
required LocalAttributeDVO attribute,
required VoidCallback onAttributeDeleted,
}) async {
final deleteEnabledNotifier = ValueNotifier<bool>(true);

final closeButton = Padding(
padding: const EdgeInsets.only(right: 8),
child: IconButton(icon: const Icon(Icons.close), onPressed: () => context.pop()),
);

Future<void> deleteAttributeAndNotifyPeers() async {
if (attribute is! RepositoryAttributeDVO) {
if (!context.mounted) return;

return showDialog<void>(
context: context,
barrierDismissible: false,
builder: (_) {
return AlertDialog(
title: Text(context.l10n.error, style: Theme.of(context).textTheme.titleLarge, textAlign: TextAlign.center),
content: Text(context.l10n.errorDialog_description, textAlign: TextAlign.center),
actions: [
FilledButton(
onPressed: () => context
..pop()
..pop(),
child: Text(context.l10n.back),
),
],
);
},
);
}

deleteEnabledNotifier.value = false;

final session = GetIt.I.get<EnmeshedRuntime>().getSession(accountId);

final deleteAttributeResult = await session.consumptionServices.attributes.deleteRepositoryAttribute(attributeId: attribute.id);

if (deleteAttributeResult.isError) {
GetIt.I.get<Logger>().e('Deleting attribute failed caused by: ${deleteAttributeResult.error}');

if (context.mounted) {
await showDialog<void>(
context: context,
builder: (context) {
return AlertDialog(
title: Text(context.l10n.error, style: Theme.of(context).textTheme.titleLarge),
content: Text(context.l10n.error_deleteAttribute),
);
},
);
}

deleteEnabledNotifier.value = true;

return;
}

for (final sharedToPeerAttribute in attribute.sharedWith) {
final content = Request(items: [DeleteAttributeRequestItem(mustBeAccepted: true, attributeId: sharedToPeerAttribute.id)]);

final canCreateRequestResult = await session.consumptionServices.outgoingRequests.canCreate(content: content, peer: sharedToPeerAttribute.peer);
if (canCreateRequestResult.isError) {
// TODO(scoen): error handling
return;
}

final createRequestResult = await session.consumptionServices.outgoingRequests.create(content: content, peer: sharedToPeerAttribute.peer);
if (createRequestResult.isError) {
// TODO(scoen): error handling
return;
}

final sendMessageResult = await session.transportServices.messages.sendMessage(
content: MessageContentRequest(request: createRequestResult.value.content),
recipients: [sharedToPeerAttribute.peer],
);

if (sendMessageResult.isError) {
GetIt.I.get<Logger>().e('The request to the peer to delete the attribute has failed caused by: ${sendMessageResult.error}');
}
}

onAttributeDeleted();

if (context.mounted) context.pop();
}

await WoltModalSheet.show<void>(
useSafeArea: false,
await showModalBottomSheet<void>(
context: context,
onModalDismissedWithDrag: () => context.pop(),
onModalDismissedWithBarrierTap: () => context.pop(),
showDragHandle: false,
pageListBuilder: (context) => [
WoltModalSheetPage(
trailingNavBarWidget: closeButton,
topBarTitle: Text(context.l10n.personalData_details_deleteEntry, style: Theme.of(context).textTheme.titleMedium),
isTopBarLayerAlwaysVisible: true,
stickyActionBar: ValueListenableBuilder<bool>(
valueListenable: deleteEnabledNotifier,
builder: (context, enabled, child) {
return Padding(
padding: EdgeInsets.only(right: 24, bottom: MediaQuery.viewPaddingOf(context).bottom),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
OutlinedButton(
onPressed: () => context.pop(),
child: Text(context.l10n.personalData_details_cancelDeletion),
),
Gaps.w8,
FilledButton(
style: OutlinedButton.styleFrom(minimumSize: const Size(100, 36)),
onPressed: !enabled ? null : deleteAttributeAndNotifyPeers,
child: Text(context.l10n.personalData_details_confirmAttributeDeletion),
),
],
),
);
},
),
child: _DeleteConfirmation(attribute: attribute as RepositoryAttributeDVO),
),
],
builder: (context) => _DeleteConfirmation(
accountId: accountId,
onAttributeDeleted: onAttributeDeleted,
attribute: attribute as RepositoryAttributeDVO,
),
);

deleteEnabledNotifier.dispose();
}

class _DeleteConfirmation extends StatelessWidget {
class _DeleteConfirmation extends StatefulWidget {
final String accountId;
final VoidCallback onAttributeDeleted;
final RepositoryAttributeDVO attribute;

const _DeleteConfirmation({required this.attribute});
const _DeleteConfirmation({
required this.accountId,
required this.onAttributeDeleted,
required this.attribute,
});

@override
State<_DeleteConfirmation> createState() => _DeleteConfirmationState();
}

class _DeleteConfirmationState extends State<_DeleteConfirmation> {
bool _deleting = false;

@override
Widget build(BuildContext context) {
final isShared = attribute.sharedWith.isNotEmpty;
final isShared = widget.attribute.sharedWith.isNotEmpty;

return Padding(
padding: EdgeInsets.only(left: 24, right: 24, bottom: MediaQuery.viewPaddingOf(context).bottom + 72),
return ConditionalCloseable(
canClose: !_deleting,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
if (isShared)
Container(
width: double.infinity,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(color: Theme.of(context).colorScheme.primaryContainer, borderRadius: BorderRadius.circular(4)),
child: Text(_getDisplayValue(context, attribute.value), style: Theme.of(context).textTheme.bodyLarge, textAlign: TextAlign.center),
BottomSheetHeader(
title: context.l10n.personalData_details_deleteEntry,
canClose: !_deleting,
),
const Padding(
padding: EdgeInsets.symmetric(horizontal: 24, vertical: 16),
child: Align(
alignment: Alignment.topCenter,
child: VectorGraphic(loader: AssetBytesLoader('assets/svg/attribute_deletion.svg'), height: 136),
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
child: isShared
? BoltStyledText(
context.l10n.personalData_details_deleteDescriptionShared(
widget.attribute.sharedWith.length,
_getDisplayValue(context, widget.attribute.value),
),
)
: BoltStyledText(context.l10n.personalData_details_deleteDescription(_getDisplayValue(context, widget.attribute.value))),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 24).add(EdgeInsets.only(bottom: MediaQuery.viewPaddingOf(context).bottom)),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
OutlinedButton(
onPressed: _deleting ? null : context.pop,
child: Text(context.l10n.personalData_details_cancelDeletion),
),
Gaps.w8,
FilledButton(
onPressed: _deleting ? null : _deleteAttributeAndNotifyPeers,
child: Text(context.l10n.personalData_details_confirmAttributeDeletion),
),
],
),
Gaps.h16,
const Align(
alignment: Alignment.topCenter,
child: VectorGraphic(loader: AssetBytesLoader('assets/svg/attribute_deletion.svg'), height: 136),
),
Gaps.h16,
if (isShared)
Text(context.l10n.personalData_details_deleteDescriptionShared(attribute.sharedWith.length))
else
Text(context.l10n.personalData_details_deleteDescription('"${_getDisplayValue(context, attribute.value)}"')),
],
),
);
Expand All @@ -204,10 +120,53 @@ class _DeleteConfirmation extends StatelessWidget {
}

String _getTranslatedEntry(BuildContext context) {
final value = attribute.value.toJson()['value'].toString();
final translation = attribute.valueHints.getTranslation(value);
final value = widget.attribute.value.toJson()['value'].toString();
final translation = widget.attribute.valueHints.getTranslation(value);
if (translation.startsWith('i18n://')) return FlutterI18n.translate(context, translation.substring(7));

return value;
}

Future<void> _deleteAttributeAndNotifyPeers() async {
if (_deleting) return;

setState(() => _deleting = true);

final session = GetIt.I.get<EnmeshedRuntime>().getSession(widget.accountId);

for (final sharedToPeerAttribute in widget.attribute.sharedWith) {
final deleteAttributeResult = await session.consumptionServices.attributes.deleteOwnSharedAttributeAndNotifyPeer(
attributeId: sharedToPeerAttribute.id,
);
if (deleteAttributeResult.isError) {
GetIt.I.get<Logger>().e('Deleting shared attribute failed caused by: ${deleteAttributeResult.error}');
}
}

final deleteAttributeResult = await session.consumptionServices.attributes.deleteRepositoryAttribute(attributeId: widget.attribute.id);

if (deleteAttributeResult.isError) {
GetIt.I.get<Logger>().e('Deleting attribute failed caused by: ${deleteAttributeResult.error}');

if (mounted) {
await showDialog<void>(
context: context,
builder: (context) {
return AlertDialog(
title: Text(context.l10n.error, style: Theme.of(context).textTheme.titleLarge),
content: Text(context.l10n.error_deleteAttribute),
);
},
);
}

setState(() => _deleting = false);

return;
}

widget.onAttributeDeleted();

if (mounted) context.pop();
}
}
7 changes: 5 additions & 2 deletions apps/enmeshed/lib/l10n/app_de.arb
Original file line number Diff line number Diff line change
Expand Up @@ -527,19 +527,22 @@
}
}
},
"personalData_details_deleteDescription": "Möchten Sie den Eintrag {entry} aus Ihren Daten löschen?",
"personalData_details_deleteDescription": "Möchten Sie den Eintrag <bold>{entry}</bold> aus Ihren Daten löschen?",
"@personalData_details_deleteDescription": {
"placeholders": {
"entry": {
"type": "String"
}
}
},
"personalData_details_deleteDescriptionShared": "{count, plural, =1{Dieser Eintrag wird aktuell mit einem Kontakt geteilt. Wenn Sie den Eintrag löschen wird dieser auch bei dem Kontakt gelöscht, mit dem Sie ihn teilen.} other{Dieser Eintrag wird aktuell mit {count} Kontakten geteilt. Wenn Sie den Eintrag löschen wird dieser auch bei den Kontakten gelöscht, mit denen Sie ihn teilen.}}",
"personalData_details_deleteDescriptionShared": "{count, plural, =1{Der Eintrag <bold>{entry}</bold> wird aktuell mit einem Kontakt geteilt. Wenn Sie den Eintrag löschen wird dieser auch bei dem Kontakt gelöscht, mit dem Sie ihn teilen.} other{Der Eintrag <bold>{entry}</bold> wird aktuell mit {count} Kontakten geteilt. Wenn Sie den Eintrag löschen wird dieser auch bei den Kontakten gelöscht, mit denen Sie ihn teilen.}}",
"@personalData_details_deleteDescriptionShared": {
"placeholders": {
"count": {
"type": "num"
},
"entry": {
"type": "String"
}
}
},
Expand Down
7 changes: 5 additions & 2 deletions apps/enmeshed/lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -527,19 +527,22 @@
}
}
},
"personalData_details_deleteDescription": "Do you want to delete the entry {entry} from your data?",
"personalData_details_deleteDescription": "Do you want to delete the entry <bold>{entry}</bold> from your data?",
"@personalData_details_deleteDescription": {
"placeholders": {
"entry": {
"type": "String"
}
}
},
"personalData_details_deleteDescriptionShared": "{count, plural, =1{This entry is currently shared with a contact. If you delete the entry, it will also be deleted for the contact with whom you shared it.} other{This entry is currently shared with {count} contacts. If you delete the entry, it will also be deleted from the contacts with whom you share it.}}",
"personalData_details_deleteDescriptionShared": "{count, plural, =1{The entry <bold>{entry}</bold> is currently shared with a contact. If you delete the entry, it will also be deleted for the contact with whom you shared it.} other{The entry <bold>{entry}</bold> is currently shared with {count} contacts. If you delete the entry, it will also be deleted from the contacts with whom you share it.}}",
"@personalData_details_deleteDescriptionShared": {
"placeholders": {
"count": {
"type": "num"
},
"entry": {
"type": "String"
}
}
},
Expand Down
20 changes: 18 additions & 2 deletions apps/enmeshed/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -535,10 +535,10 @@ packages:
dependency: "direct main"
description:
name: go_router
sha256: "7c2d40b59890a929824f30d442e810116caf5088482629c894b9e4478c67472d"
sha256: "9b736a9fa879d8ad6df7932cbdcc58237c173ab004ef90d8377923d7ad731eaa"
url: "https://pub.dev"
source: hosted
version: "14.6.3"
version: "14.7.2"
gtk:
dependency: transitive
description:
Expand Down Expand Up @@ -1207,6 +1207,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.3.0"
styled_text:
dependency: transitive
description:
name: styled_text
sha256: fd624172cf629751b4f171dd0ecf9acf02a06df3f8a81bb56c0caa4f1df706c3
url: "https://pub.dev"
source: hosted
version: "8.1.0"
term_glyph:
dependency: transitive
description:
Expand Down Expand Up @@ -1454,6 +1462,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "6.2.6"
xmlstream:
dependency: transitive
description:
name: xmlstream
sha256: cfc14e3f256997897df9481ae630d94c2d85ada5187ebeb868bb1aabc2c977b4
url: "https://pub.dev"
source: hosted
version: "1.1.1"
yaml:
dependency: transitive
description:
Expand Down
1 change: 1 addition & 0 deletions packages/enmeshed_ui_kit/lib/enmeshed_ui_kit.dart
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export 'src/bottom_sheets/bottom_sheets.dart';
export 'src/utils/utils.dart';
export 'src/widgets/widgets.dart';
Loading

0 comments on commit 262bca5

Please sign in to comment.