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

Check if establishing relationship is possible #451

Open
wants to merge 72 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 53 commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
412251c
chore: can accept relationship
nicole-eb Feb 18, 2025
9a6e4d6
chore: show dialog
nicole-eb Feb 19, 2025
a0023ab
chore: disable button
nicole-eb Feb 19, 2025
c770208
chore: add util
nicole-eb Feb 19, 2025
a987ba4
chore: use util
nicole-eb Feb 19, 2025
9caf3a1
chore: use util
nicole-eb Feb 19, 2025
ed4745d
chore: use translations
nicole-eb Feb 20, 2025
68f7ef3
Merge branch 'refs/heads/main' into check-if-establishing-relationshi…
nicole-eb Feb 20, 2025
a7a9acf
chore: show error message
nicole-eb Feb 20, 2025
191ec92
chore: add error code to route
nicole-eb Feb 20, 2025
0a01251
Merge remote-tracking branch 'refs/remotes/origin/main' into check-if…
nicole-eb Feb 20, 2025
628670e
fix: translation
nicole-eb Feb 20, 2025
031b7a9
fix: comment out code
nicole-eb Feb 20, 2025
4a793fd
refactor: create error details
nicole-eb Feb 20, 2025
a0d0568
Merge remote-tracking branch 'refs/remotes/origin/main' into check-if…
nicole-eb Feb 20, 2025
ae9f013
chore: add missing status
nicole-eb Feb 20, 2025
5d959bd
chore: add expired text
nicole-eb Feb 21, 2025
f9e8218
fix: color of tiles
nicole-eb Feb 21, 2025
6c26bce
refactor: move type to sepperate file
nicole-eb Feb 21, 2025
833eaf3
refactor: naming
nicole-eb Feb 21, 2025
fb0d10a
refactor: check accept inside of other widget
nicole-eb Feb 21, 2025
322941f
Merge branch 'main' into check-if-establishing-relationship-is-possible
mergify[bot] Feb 21, 2025
bfda5d8
Merge remote-tracking branch 'refs/remotes/origin/check-if-establishi…
nicole-eb Feb 21, 2025
c506b76
chore: update translation
nicole-eb Feb 21, 2025
1512018
chore: delete when expired
nicole-eb Feb 21, 2025
2ee33d2
Merge remote-tracking branch 'refs/remotes/origin/main' into check-if…
nicole-eb Feb 24, 2025
61f25a4
fix: add missing error details
nicole-eb Feb 24, 2025
f3ca19c
fix: naming
nicole-eb Feb 24, 2025
41d4af0
Merge remote-tracking branch 'refs/remotes/origin/main' into check-if…
nicole-eb Feb 24, 2025
7275fd7
chore: update func
nicole-eb Feb 24, 2025
00a6f24
Merge remote-tracking branch 'refs/remotes/origin/main' into check-if…
nicole-eb Feb 24, 2025
ed79eff
chore: check if expired
nicole-eb Feb 24, 2025
394b829
chore: translation
nicole-eb Feb 25, 2025
633a5ed
fix: make status nullable
nicole-eb Feb 25, 2025
0042d6f
refactor: return
nicole-eb Feb 25, 2025
1327e90
chore: add new icon
nicole-eb Feb 25, 2025
1a64d55
chore: move logic to separate widget
nicole-eb Feb 25, 2025
b1b8b32
refactor: translation key
nicole-eb Feb 25, 2025
cae4e24
chore: use query
nicole-eb Feb 25, 2025
2b5dab4
chore: pop screen
nicole-eb Feb 25, 2025
4be6322
refactor: wording
nicole-eb Feb 25, 2025
6fa46bd
fix: unawaited
nicole-eb Feb 25, 2025
9418c3c
chore: remove error details usage
nicole-eb Feb 25, 2025
7082fe5
chore: set value of _canAcceptRequest
nicole-eb Feb 25, 2025
59ca19d
chore: use new dialog
nicole-eb Feb 25, 2025
386f716
fix: color of tile
nicole-eb Feb 25, 2025
229399c
chore: show dialog
nicole-eb Feb 25, 2025
345a316
fix: remove unused function
nicole-eb Feb 25, 2025
f28247c
refactor: validateRelationshipCreation
nicole-eb Feb 25, 2025
8acd660
chore: check if request is for relationship
nicole-eb Feb 25, 2025
229883c
chore: revert changes
nicole-eb Feb 26, 2025
4841b2a
chore: make local request source nullable
nicole-eb Feb 26, 2025
fc030eb
chore: show error snackbar
nicole-eb Feb 26, 2025
d60786d
fix: translation key
nicole-eb Feb 26, 2025
fee9eec
fix: check type
nicole-eb Feb 26, 2025
d0c98ea
refactor: check status of request
nicole-eb Feb 26, 2025
15a4073
Merge branch 'main' into check-if-establishing-relationship-is-possible
mergify[bot] Feb 26, 2025
4b95ffa
Merge branch 'main' into check-if-establishing-relationship-is-possible
mergify[bot] Feb 26, 2025
9a594c5
Merge remote-tracking branch 'origin/check-if-establishing-relationsh…
nicole-eb Feb 26, 2025
b103be1
chore: move logic to request screen
nicole-eb Feb 26, 2025
06c25ab
refactor: check
nicole-eb Feb 27, 2025
57702ee
Merge branch 'main' into check-if-establishing-relationship-is-possible
mergify[bot] Feb 27, 2025
2b67052
Merge branch 'main' into check-if-establishing-relationship-is-possible
mergify[bot] Feb 28, 2025
0f1e8ad
Merge branch 'main' into check-if-establishing-relationship-is-possible
mergify[bot] Feb 28, 2025
9e90491
chore: move logic for subtitle
nicole-eb Mar 3, 2025
f3239a9
Merge branch 'main' into check-if-establishing-relationship-is-possible
mergify[bot] Mar 3, 2025
52177dd
Merge branch 'check-if-establishing-relationship-is-possible' of http…
nicole-eb Mar 4, 2025
4adcd9d
chore: check relationship
nicole-eb Mar 4, 2025
b4539d1
refactor: simplify boolean null handling
nicole-eb Mar 4, 2025
5d839ff
Merge branch 'main' into check-if-establishing-relationship-is-possible
mergify[bot] Mar 4, 2025
b26e478
refactor: simplify boolean null handling
nicole-eb Mar 4, 2025
af9f197
Merge branch 'check-if-establishing-relationship-is-possible' of http…
nicole-eb Mar 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 76 additions & 12 deletions apps/enmeshed/lib/account/contacts/contacts_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -359,31 +359,53 @@ class _ContactItem extends StatelessWidget {
Widget build(BuildContext context) {
final contact = item.contact;

final isRequestExpired = item.openContactRequest?.status == LocalRequestStatus.Expired;

return DismissibleContactItem(
contact: contact,
isRequestExpired: isRequestExpired,
onTap: () => _onTap(context),
trailing:
item.openContactRequest != null
? const Padding(padding: EdgeInsets.all(8), child: Icon(Icons.edit))
: IconButton(
icon: isFavoriteContact ? const Icon(Icons.star) : const Icon(Icons.star_border),
color: isFavoriteContact ? Theme.of(context).colorScheme.primary : Theme.of(context).colorScheme.shadow,
onPressed: () => toggleContactFavorite(contact),
),
trailing: _TrailingIcon(
isRequestExpired: isRequestExpired,
isOpenContactRequest: item.openContactRequest != null,
isFavoriteContact: isFavoriteContact,
onToggleFavorite: () => toggleContactFavorite(contact),
onDeletePressed: () => _onDeletePressed(context),
),
onDeletePressed: _onDeletePressed,
subtitle: isRequestExpired ? Text(context.l10n.contacts_requestExpired, style: TextStyle(color: Theme.of(context).colorScheme.error)) : null,
);
}

void _onTap(BuildContext context) {
Future<void> _onTap(BuildContext context) async {
final contact = item.contact;

if (item.openContactRequest == null) {
context.push('/account/$accountId/contacts/${contact.id}');
unawaited(context.push('/account/$accountId/contacts/${contact.id}'));
return;
}

final session = GetIt.I.get<EnmeshedRuntime>().getSession(accountId);
final request = item.openContactRequest!;
context.go('/account/$accountId/contacts/contact-request/${request.id}', extra: request);

final validateRelationshipCreationResponse = await validateRelationshipCreation(
accountId: accountId,
localRequestSource: request.source,
session: session,
);

if (!context.mounted) return;

if (validateRelationshipCreationResponse.success) return context.go('/account/$accountId/contacts/contact-request/${request.id}', extra: request);

final result = await showDialog<bool>(
context: context,
builder: (context) => CreateRelationshipErrorDialog(errorCode: validateRelationshipCreationResponse.errorCode!),
);

if (!context.mounted) return;

if (result != null && result) await _onDeletePressed(context);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (result != null && result) await _onDeletePressed(context);
if (result == true) await _onDeletePressed(context);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

result can be null, but i use now if (result ?? false) instead

}

Future<void> _onDeletePressed(BuildContext context) async {
Expand All @@ -395,6 +417,20 @@ class _ContactItem extends StatelessWidget {
}

final request = item.openContactRequest!;
final session = GetIt.I.get<EnmeshedRuntime>().getSession(accountId);

if (request.status == LocalRequestStatus.Expired) {
final deleteResult = await session.consumptionServices.incomingRequests.delete(requestId: request.id);

if (deleteResult.isError) {
GetIt.I.get<Logger>().e(deleteResult.error);

if (!context.mounted) return;

showErrorSnackbar(context: context, text: context.l10n.error_deleteRequestFailed);
}
return;
}

final rejectItems = List<DecideRequestParametersItem>.from(
request.items.map((e) {
Expand All @@ -409,7 +445,6 @@ class _ContactItem extends StatelessWidget {

final rejectParams = DecideRequestParameters(requestId: request.id, items: rejectItems);

final session = GetIt.I.get<EnmeshedRuntime>().getSession(accountId);
final result = await session.consumptionServices.incomingRequests.reject(params: rejectParams);
if (result.isError) GetIt.I.get<Logger>().e(result.error);
}
Expand All @@ -435,3 +470,32 @@ class _EmptyContactsIndicator extends StatelessWidget {
);
}
}

class _TrailingIcon extends StatelessWidget {
final bool isRequestExpired;
final bool isFavoriteContact;
final bool isOpenContactRequest;
final VoidCallback onDeletePressed;
final VoidCallback onToggleFavorite;

const _TrailingIcon({
required this.isRequestExpired,
required this.isFavoriteContact,
required this.isOpenContactRequest,
required this.onDeletePressed,
required this.onToggleFavorite,
});

@override
Widget build(BuildContext context) {
if (isRequestExpired) return IconButton(icon: const Icon(Icons.cancel_outlined), onPressed: onDeletePressed);

if (isOpenContactRequest) return const Padding(padding: EdgeInsets.all(8), child: Icon(Icons.edit));

return IconButton(
icon: isFavoriteContact ? const Icon(Icons.star) : const Icon(Icons.star_border),
color: isFavoriteContact ? Theme.of(context).colorScheme.primary : Theme.of(context).colorScheme.onSurfaceVariant,
onPressed: onToggleFavorite,
);
}
}
1 change: 1 addition & 0 deletions apps/enmeshed/lib/account/contacts/request_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class RequestScreen extends StatelessWidget {
requestDVO: requestDVO,
acceptRequestText: context.l10n.home_addContact,
validationErrorDescription: context.l10n.contact_request_validationErrorDescription,
validateCreateRelationship: requestDVO?.source?.type == LocalRequestSourceType.RelationshipTemplate,
onAfterAccept: () {
if (context.mounted) context.go('/account/$accountId/contacts');
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ class DismissibleContactItem extends StatefulWidget {
final IdentityDVO contact;
final VoidCallback onTap;
final void Function(BuildContext) onDeletePressed;
final bool isRequestExpired;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you should pass the request instead of this boolean, for this widget the variable name and type is very meaningless

final Widget? trailing;
final Widget? subtitle;
final String? query;
Expand All @@ -17,6 +18,7 @@ class DismissibleContactItem extends StatefulWidget {
required this.contact,
required this.onTap,
required this.onDeletePressed,
required this.isRequestExpired,
this.trailing,
this.subtitle,
this.query,
Expand Down Expand Up @@ -53,8 +55,9 @@ class _DismissibleContactItemState extends State<DismissibleContactItem> with Si
@override
Widget build(BuildContext context) {
final coloringStatus = [RelationshipStatus.Terminated, RelationshipStatus.DeletionProposed];

final tileColor =
widget.contact.relationship == null || coloringStatus.contains(widget.contact.relationship!.status)
(widget.contact.relationship == null && !widget.isRequestExpired) || coloringStatus.contains(widget.contact.relationship?.status)
? Theme.of(context).colorScheme.primaryContainer
: null;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';

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

class CreateRelationshipErrorDialog extends StatelessWidget {
final String errorCode;

const CreateRelationshipErrorDialog({required this.errorCode, super.key});

@override
Widget build(BuildContext context) {
return AlertDialog(
icon: _icon(context),
title: Text(_title(context)),
content: Text(_content(context), textAlign: TextAlign.center, style: Theme.of(context).textTheme.bodyMedium),
actions: [
FilledButton(
onPressed: errorCode == 'error.transport.relationships.relationshipTemplateIsExpired' ? () => context.pop(true) : () => context.pop(false),
child: Text(
errorCode == 'error.transport.relationships.relationshipTemplateIsExpired'
? context.l10n.error_deleteRequest
: context.l10n.error_understood,
),
),
],
actionsAlignment: MainAxisAlignment.center,
);
}

Icon _icon(BuildContext context) => switch (errorCode) {
'error.transport.relationships.activeIdentityDeletionProcessOfOwnerOfRelationshipTemplate' => Icon(
Icons.cancel,
color: Theme.of(context).colorScheme.error,
),
_ => Icon(Icons.error, color: Theme.of(context).colorScheme.error),
};

String _title(BuildContext context) => switch (errorCode) {
'error.transport.relationships.relationshipNotYetDecomposedByPeer' => context.l10n.errorDialog_relationshipNotYetDecomposedByPeer_title,
'error.transport.relationships.activeIdentityDeletionProcessOfOwnerOfRelationshipTemplate' =>
context.l10n.errorDialog_activeIdentityDeletionProcessOfOwnerOfRelationshipTemplate_title,
'error.transport.relationships.relationshipTemplateIsExpired' => context.l10n.errorDialog_relationshipTemplateIsExpired_title,
'error.relationshipTemplateProcessedModule.requestExpired' => context.l10n.errorDialog_requestExpired_title,
_ => context.l10n.errorDialog_title,
};

String _content(BuildContext context) => switch (errorCode) {
'error.transport.relationships.relationshipNotYetDecomposedByPeer' => context.l10n.errorDialog_relationshipNotYetDecomposedByPeer_description,
'error.transport.relationships.activeIdentityDeletionProcessOfOwnerOfRelationshipTemplate' =>
context.l10n.errorDialog_activeIdentityDeletionProcessOfOwnerOfRelationshipTemplate_description,
'error.transport.relationships.relationshipTemplateIsExpired' => context.l10n.errorDialog_relationshipTemplateIsExpired_description,
'error.relationshipTemplateProcessedModule.requestExpired' => context.l10n.errorDialog_requestExpired_description,
_ => context.l10n.errorDialog_description,
};
}
1 change: 1 addition & 0 deletions apps/enmeshed/lib/core/modals/modals.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export 'create_attribute.dart';
export 'create_recovery_kit/create_recovery_kit.dart';
export 'create_relationship_error_dialog.dart';
export 'delete_attribute.dart';
export 'delete_local_data.dart';
export 'enter_password/enter_password.dart';
Expand Down
22 changes: 21 additions & 1 deletion apps/enmeshed/lib/core/utils/contact_utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,11 @@ Future<List<IdentityDVO>> getContacts({required Session session}) async {
Future<List<LocalRequestDVO>> incomingOpenRequestsFromRelationshipTemplate({required Session session}) async {
final incomingRequestResult = await session.consumptionServices.incomingRequests.getRequests(
query: {
'status': QueryValue.stringList([LocalRequestStatus.DecisionRequired.name, LocalRequestStatus.ManualDecisionRequired.name]),
'status': QueryValue.stringList([
LocalRequestStatus.DecisionRequired.name,
LocalRequestStatus.ManualDecisionRequired.name,
LocalRequestStatus.Expired.name,
]),
'source.type': QueryValue.string(LocalRequestSourceType.RelationshipTemplate.name),
},
);
Expand Down Expand Up @@ -147,3 +151,19 @@ Future<void> deleteContact({

onContactDeleted();
}

Future<({bool success, String? errorCode})> validateRelationshipCreation({
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

again - this is not enough. type RelationshipTemplate only means that it originates from a RelationshipTemplate, but it could also originate from onExistingRelationship, then we wouldn't create a relationship and instead answer using a message.

Please talk to the runtime team how to handle this properly, I suggest @Milena-Czierlinski as a sparring partner. Maybe we can make the requestDVO event smarter so that we KNOW if it would be answered using a Relationship or a Message ;)

If you discussed this please tell me the results.

Copy link
Contributor Author

@nicole-eb nicole-eb Feb 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We think it could be enough to check for localRequestDVO.peer.hasRelationship and then do the validation if its false.

required String accountId,
required Session session,
LocalRequestSourceDVO? localRequestSource,
}) async {
if (localRequestSource == null) return (success: true, errorCode: null);

final response = await session.transportServices.relationships.canCreateRelationship(templateId: localRequestSource.reference);

if (response.value.isSuccess) return (success: true, errorCode: null);

final failureResponse = response.value as CanCreateRelationshipFailureResponse;

return (success: false, errorCode: failureResponse.code);
}
55 changes: 54 additions & 1 deletion apps/enmeshed/lib/core/widgets/request_dvo_renderer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import 'package:logger/logger.dart';
import 'package:renderers/renderers.dart';

import '../modals/create_attribute.dart';
import '../modals/create_relationship_error_dialog.dart';
import '../types/types.dart';
import '../utils/utils.dart';
import 'contact_circle_avatar.dart';
Expand All @@ -24,6 +25,7 @@ class RequestDVORenderer extends StatefulWidget {
final String validationErrorDescription;
final VoidCallback onAfterAccept;
final bool showHeader;
final bool validateCreateRelationship;
final LocalRequestDVO? requestDVO;
final String? description;

Expand All @@ -35,6 +37,7 @@ class RequestDVORenderer extends StatefulWidget {
required this.validationErrorDescription,
required this.onAfterAccept,
this.showHeader = true,
this.validateCreateRelationship = false,
this.requestDVO,
this.description,
super.key,
Expand All @@ -55,6 +58,7 @@ class _RequestDVORendererState extends State<RequestDVORenderer> {
GetIdentityInfoResponse? _identityInfo;

bool _loading = false;
late bool _canAcceptRequest;

@override
void initState() {
Expand All @@ -63,13 +67,17 @@ class _RequestDVORendererState extends State<RequestDVORenderer> {
final session = GetIt.I.get<EnmeshedRuntime>().getSession(widget.accountId);
_request = widget.requestDVO;

_canAcceptRequest = !widget.validateCreateRelationship;

_updateIdentityInfo();

if (_request == null) {
_loadRequest(session);
} else {
_setController(session, _request!);
}

_canCreateRelationship();
}

@override
Expand All @@ -95,6 +103,8 @@ class _RequestDVORendererState extends State<RequestDVORenderer> {
} else {
_setController(session, _request!);
}

_canCreateRelationship();
}

super.didUpdateWidget(oldWidget);
Expand Down Expand Up @@ -165,7 +175,7 @@ class _RequestDVORendererState extends State<RequestDVORenderer> {
children: [
OutlinedButton(onPressed: _loading && _request != null ? null : _rejectRequest, child: Text(context.l10n.reject)),
Gaps.w8,
FilledButton(onPressed: _acceptRequest, child: Text(widget.acceptRequestText)),
FilledButton(onPressed: _onAcceptButtonPressed, child: Text(widget.acceptRequestText)),
],
),
),
Expand Down Expand Up @@ -194,6 +204,14 @@ class _RequestDVORendererState extends State<RequestDVORenderer> {

void _setController(Session session, LocalRequestDVO request) => _controller = RequestRendererController(request: request);

Future<void> _onAcceptButtonPressed() async {
await _canCreateRelationship();

if (!_canAcceptRequest) return;

await _acceptRequest();
}

Future<void> _acceptRequest() async {
if (_loading) return;

Expand Down Expand Up @@ -230,6 +248,13 @@ class _RequestDVORendererState extends State<RequestDVORenderer> {
widget.onAfterAccept();
}

Future<void> _deleteRequest() async {
final session = GetIt.I.get<EnmeshedRuntime>().getSession(widget.accountId);
final deleteResult = await session.consumptionServices.incomingRequests.delete(requestId: _request!.id);

if (deleteResult.isError) GetIt.I.get<Logger>().e(deleteResult.error);
}

Future<void> _rejectRequest() async {
setState(() => _loading = true);

Expand Down Expand Up @@ -287,6 +312,34 @@ class _RequestDVORendererState extends State<RequestDVORenderer> {

return choice;
}

Future<void> _canCreateRelationship() async {
if (!widget.validateCreateRelationship) return;

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we discussed a very important part that was just ignored here. This function now runs every time a request is rendered that originates from a template. But I miss the check for the existing relationship.

Also, is this really the correct place to run this check? Can't we move this to the request screen?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In RequestScreen i do "validateCreateRelationship: requestDVO?.source?.type == LocalRequestSourceType.RelationshipTemplate" and pass that to the param validateCreateRelationship, which returns on line 317

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But i guess i could do this whole check in RequestScreen

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, as you see it's not a good idea to split this information


final validateRelationshipCreationResponse = await validateRelationshipCreation(
accountId: widget.accountId,
localRequestSource: _request?.source,
session: session,
);

setState(() => _canAcceptRequest = validateRelationshipCreationResponse.success);

if (_canAcceptRequest || !mounted) return;

final result = await showDialog<bool>(
barrierDismissible: false,
context: context,
builder: (context) => CreateRelationshipErrorDialog(errorCode: validateRelationshipCreationResponse.errorCode!),
);

if (result != null && result) await _deleteRequest();

if (!mounted) return;

context.pop();
}
}

class _AttributeSwitcher extends StatefulWidget {
Expand Down
Loading
Loading