Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
NewContractViewModel.cs
1using CommunityToolkit.Mvvm.ComponentModel;
2using CommunityToolkit.Mvvm.Input;
18using NeuroFeatures;
19using System.Collections.ObjectModel;
20using System.ComponentModel;
21using System.Text;
22using Waher.Content;
27using Waher.Script;
28
30{
34 public partial class NewContractViewModel : BaseViewModel, ILinkableView, IDisposable
35 {
36 private readonly SortedDictionary<CaseInsensitiveString, ParameterInfo> parametersByName = [];
37 private readonly LinkedList<ParameterInfo> parametersInOrder = new();
38 private readonly Dictionary<CaseInsensitiveString, object> presetParameterValues = [];
39 private readonly CaseInsensitiveString[]? suppressedProposalIds;
40 private readonly string? templateId;
41 private Contract? template;
42 private bool saveStateWhileScanning;
43 private Contract? stateTemplateWhileScanning;
44 private readonly Dictionary<CaseInsensitiveString, string> parts = [];
45 private readonly NewContractPage page;
46 private readonly ContractVisibility? initialVisibility = null;
47 private Timer? populateTimer = null;
48
54 public NewContractViewModel(NewContractPage Page, NewContractNavigationArgs? Args)
55 {
56 this.page = Page;
57
58 if (Args is not null)
59 {
60 this.template = Args.Template;
61 this.suppressedProposalIds = Args.SuppressedProposalLegalIds;
62
63 if (Args.ParameterValues is not null)
64 this.presetParameterValues = Args.ParameterValues;
65
66 if (Args.SetVisibility)
67 this.initialVisibility = Args.Template?.Visibility;
68
69 if (this.template is not null)
70 this.template.FormatParameterDisplay += this.Template_FormatParameterDisplay;
71 }
72 else if (this.stateTemplateWhileScanning is not null)
73 {
74 this.template = this.stateTemplateWhileScanning;
75 this.stateTemplateWhileScanning = null;
76
77 this.template.FormatParameterDisplay += this.Template_FormatParameterDisplay;
78 }
79
80 this.templateId = this.template?.ContractId ?? string.Empty;
81 this.IsTemplate = this.template?.CanActAsTemplate ?? false;
82
83 this.ContractVisibilityItems.Add(new ContractVisibilityModel(ContractVisibility.CreatorAndParts, ServiceRef.Localizer[nameof(AppResources.ContractVisibility_CreatorAndParts)]));
84 this.ContractVisibilityItems.Add(new ContractVisibilityModel(ContractVisibility.DomainAndParts, ServiceRef.Localizer[nameof(AppResources.ContractVisibility_DomainAndParts)]));
85 this.ContractVisibilityItems.Add(new ContractVisibilityModel(ContractVisibility.Public, ServiceRef.Localizer[nameof(AppResources.ContractVisibility_Public)]));
86 this.ContractVisibilityItems.Add(new ContractVisibilityModel(ContractVisibility.PublicSearchable, ServiceRef.Localizer[nameof(AppResources.ContractVisibility_PublicSearchable)]));
87 }
88
92 public void Dispose()
93 {
94 this.Dispose(true);
95 GC.SuppressFinalize(this);
96 }
97
101 protected virtual void Dispose(bool Disposing)
102 {
103 if (this.populateTimer is not null)
104 {
105 try
106 {
107 this.populateTimer.Dispose();
108 }
109 catch (Exception)
110 {
111 //Normal operation
112 }
113 finally
114 {
115 this.populateTimer = null;
116 }
117 }
118 }
119
121 protected override async Task OnInitialize()
122 {
123 await base.OnInitialize();
124 await Task.Delay(1);
125 await this.PopulateTemplateForm(this.initialVisibility);
126 }
127
128
130 protected override async Task OnDispose()
131 {
132 if (this.template is not null)
133 this.template.FormatParameterDisplay -= this.Template_FormatParameterDisplay;
134
135 this.ContractVisibilityItems.Clear();
136
137 this.ClearTemplate(false);
138
139 if (!this.saveStateWhileScanning)
140 {
141 await ServiceRef.SettingsService.RemoveState(this.GetSettingsKey(nameof(this.SelectedContractVisibilityItem)));
142 await ServiceRef.SettingsService.RemoveState(this.GetSettingsKey(nameof(this.SelectedRole)));
143 await ServiceRef.SettingsService.RemoveState(this.GetSettingsKey(nameof(this.Parts)));
144 }
145
146 await base.OnDispose();
147 }
148
149 private void Template_FormatParameterDisplay(object? Sender, ParameterValueFormattingEventArgs e)
150 {
151 if (e.Value is Duration Duration)
153 }
154
156 protected override async Task DoSaveState()
157 {
158 await base.DoSaveState();
159
160 if (this.SelectedContractVisibilityItem is not null)
161 await ServiceRef.SettingsService.SaveState(this.GetSettingsKey(nameof(this.SelectedContractVisibilityItem)), this.SelectedContractVisibilityItem.Visibility);
162 else
163 await ServiceRef.SettingsService.RemoveState(this.GetSettingsKey(nameof(this.SelectedContractVisibilityItem)));
164
165 if (this.SelectedRole is not null)
166 await ServiceRef.SettingsService.SaveState(this.GetSettingsKey(nameof(this.SelectedRole)), this.SelectedRole);
167 else
168 await ServiceRef.SettingsService.RemoveState(this.GetSettingsKey(nameof(this.SelectedRole)));
169
170 if (this.HasParts)
171 await ServiceRef.SettingsService.SaveState(this.GetSettingsKey(nameof(this.Parts)), this.Parts);
172 else
173 await ServiceRef.SettingsService.RemoveState(this.GetSettingsKey(nameof(this.Parts)));
174
175 this.parts.Clear();
176 }
177
178 private bool HasParts => this.parts.Count > 0;
179
181 protected override async Task DoRestoreState()
182 {
183 if (this.saveStateWhileScanning)
184 {
185 Enum? LastVisibility = await ServiceRef.SettingsService.RestoreEnumState(this.GetSettingsKey(nameof(this.SelectedContractVisibilityItem)));
186 if (LastVisibility is ContractVisibility ContractVisibility)
187 this.SelectedContractVisibilityItem = this.ContractVisibilityItems.FirstOrDefault(x => x.Visibility == ContractVisibility);
188
189 string? LastRole = await ServiceRef.SettingsService.RestoreStringState(this.GetSettingsKey(nameof(this.SelectedRole)));
190 string? SelectedRole = this.AvailableRoles.FirstOrDefault(x => x.Equals(LastRole, StringComparison.Ordinal));
191
192 if (!string.IsNullOrWhiteSpace(SelectedRole))
193 this.SelectedRole = SelectedRole;
194
195 Dictionary<string, object>? LastParts = await ServiceRef.SettingsService.RestoreState<Dictionary<string, object>>(this.GetSettingsKey(nameof(this.Parts)));
196 if (LastParts is not null)
197 this.Parts = LastParts;
198
199 if (this.HasParts)
200 {
201 foreach (KeyValuePair<CaseInsensitiveString, string> Part in this.parts)
202 await this.AddRole(Part.Key, Part.Value);
203 }
204
205 await this.DeleteState();
206 }
207
208 this.saveStateWhileScanning = false;
209 await base.DoRestoreState();
210 }
211
212 private async Task DeleteState()
213 {
214 await ServiceRef.SettingsService.RemoveState(this.GetSettingsKey(nameof(this.SelectedContractVisibilityItem)));
215 await ServiceRef.SettingsService.RemoveState(this.GetSettingsKey(nameof(this.SelectedRole)));
216 await ServiceRef.SettingsService.RemoveState(this.GetSettingsKey(nameof(this.Parts)));
217 }
218
219 #region Properties
220
224 [ObservableProperty]
225 [NotifyCanExecuteChangedFor(nameof(ProposeCommand))]
226 private bool isProposing;
227
231 [ObservableProperty]
232 private bool isTemplate;
233
237 public ObservableCollection<ContractVisibilityModel> ContractVisibilityItems { get; } = [];
238
242 [ObservableProperty]
243 private ContractVisibilityModel? selectedContractVisibilityItem;
244
246 protected override void OnPropertyChanging(System.ComponentModel.PropertyChangingEventArgs e)
247 {
248 base.OnPropertyChanging(e);
249
250 switch (e.PropertyName)
251 {
252 case nameof(this.SelectedRole):
253 if (this.SelectedRole is not null)
254 this.RemoveRole(this.SelectedRole, ServiceRef.TagProfile.LegalIdentity!.Id);
255 break;
256 }
257 }
258
260 protected override async void OnPropertyChanged(PropertyChangedEventArgs e)
261 {
262 try
263 {
264 base.OnPropertyChanged(e);
265
266 switch (e.PropertyName)
267 {
268 case nameof(this.SelectedContractVisibilityItem):
269 if (this.template is not null && this.SelectedContractVisibilityItem is not null)
270 this.template.Visibility = this.SelectedContractVisibilityItem.Visibility;
271 break;
272
273 case nameof(this.SelectedRole):
274 if (this.template is not null && !string.IsNullOrWhiteSpace(this.SelectedRole))
275 await this.AddRole(this.SelectedRole, ServiceRef.TagProfile.LegalIdentity!.Id);
276 break;
277 }
278 }
279 catch (Exception ex)
280 {
281 ServiceRef.LogService.LogException(ex);
282 }
283 }
284
288 [ObservableProperty]
289 private bool visibilityIsEnabled;
290
294 public ObservableCollection<string> AvailableRoles { get; } = [];
295
299 public ObservableCollection<ContractOption> ParameterOptions { get; } = [];
300
304 [ObservableProperty]
305 private string? selectedRole;
306
310 [ObservableProperty]
311 private VerticalStackLayout? roles;
312
316 [ObservableProperty]
317 private VerticalStackLayout? parameters;
318
322 [ObservableProperty]
323 private VerticalStackLayout? humanReadableText;
324
328 [ObservableProperty]
329 private bool hasRoles;
330
334 [ObservableProperty]
335 private bool hasParameters;
336
340 [ObservableProperty]
341 [NotifyCanExecuteChangedFor(nameof(ProposeCommand))]
342 private bool parametersOk;
343
347 [ObservableProperty]
348 private bool hasHumanReadableText;
349
353 [ObservableProperty]
354 private bool canAddParts;
355
359 public Dictionary<string, object> Parts
360 {
361 get
362 {
363 Dictionary<string, object> Result = [];
364
365 foreach (KeyValuePair<CaseInsensitiveString, string> Part in this.parts)
366 Result[Part.Key.Value] = Part.Value;
367
368 return Result;
369 }
370
371 set
372 {
373 this.parts.Clear();
374
375 foreach (KeyValuePair<string, object> P in value)
376 this.parts[P.Key] = P.Value?.ToString() ?? string.Empty;
377 }
378 }
379
380 #endregion
381
382 private void ClearTemplate(bool propertiesOnly)
383 {
384 if (!propertiesOnly)
385 this.template = null;
386
387 this.SelectedRole = null;
388 this.AvailableRoles.Clear();
389
390 this.Roles = null;
391 this.HasRoles = false;
392
393 this.Parameters = null;
394 this.HasParameters = false;
395
396 this.HumanReadableText = null;
397 this.HasHumanReadableText = false;
398
399 this.CanAddParts = false;
400 this.VisibilityIsEnabled = false;
401 }
402
403 private void RemoveRole(string Role, string LegalId)
404 {
405 Label? ToRemove = null;
406
407 if (this.Roles is null)
408 return;
409
410 if (this.template?.Parts is not null)
411 {
412 List<Part> Parts = [];
413
414 foreach (Part Part in this.template.Parts)
415 {
416 if (Part.LegalId != LegalId || Part.Role != Role)
417 Parts.Add(Part);
418 }
419
420 this.template.Parts = [.. Parts];
421 }
422
423 if (this.Roles is not null)
424 {
425 foreach (IView FrameView in this.Roles.Children)
426 {
427 if (FrameView is Frame RoleFrame && RoleFrame.Content is VerticalStackLayout RoleLayout)
428 {
429 int State = 0;
430
431 foreach (IView View in RoleLayout.Children)
432 {
433 switch (State)
434 {
435 case 0:
436 if (View is Label Label && Label.StyleId == Role)
437 State++;
438 break;
439
440 case 1:
441 if (View is TextButton Button)
442 {
443 if (ToRemove is not null)
444 {
445 RoleLayout.Children.Remove(ToRemove);
446 Button.IsEnabled = true;
447 }
448 return;
449 }
450 else if (View is Label Label2 && Label2.StyleId == LegalId)
451 ToRemove = Label2;
452 break;
453 }
454 }
455 }
456 }
457 }
458 }
459
460 private async Task AddRole(string Role, string LegalId)
461 {
462 Contract? contractToUse = this.template ?? this.stateTemplateWhileScanning;
463
464 if ((contractToUse is null) || (this.Roles is null))
465 return;
466
467 Role? RoleObj = null;
468
469 foreach (Role R in contractToUse.Roles)
470 {
471 if (R.Name == Role)
472 {
473 RoleObj = R;
474 break;
475 }
476 }
477
478 if (RoleObj is null)
479 return;
480
481 if (this.template is not null)
482 {
483 List<Part> Parts = [];
484
485 if (this.template.Parts is not null)
486 {
487 foreach (Part Part in this.template.Parts)
488 {
489 if (Part.LegalId != LegalId || Part.Role != Role)
490 Parts.Add(Part);
491 }
492 }
493
494 Parts.Add(new Part()
495 {
496 LegalId = LegalId,
497 Role = Role
498 });
499
500 this.template.Parts = [.. Parts];
501 }
502
503 if (this.Roles is not null)
504 {
505 int NrParts = 0;
506 int i;
507 bool CurrentRole;
508 bool LegalIdAdded = false;
509
510 foreach (IView FrameView in this.Roles.Children)
511 {
512 if (FrameView is Frame RoleFrame && RoleFrame.Content is VerticalStackLayout RoleLayout)
513 {
514 CurrentRole = false;
515 i = 0;
516
517 foreach (IView View in RoleLayout.Children)
518 {
519 if (View is Label Label)
520 {
521 if (Label.StyleId == Role)
522 {
523 CurrentRole = true;
524 NrParts = 0;
525 }
526 else
527 {
528 if (Label.StyleId == LegalId)
529 LegalIdAdded = true;
530
531 NrParts++;
532 }
533 }
534 else if (View is TextButton Button)
535 {
536 if (CurrentRole)
537 {
538 if (!LegalIdAdded)
539 {
540 string FriendlyName = await ContactInfo.GetFriendlyName(LegalId);
541 Label = new Label
542 {
543 Text = FriendlyName,
544 StyleId = LegalId,
545 HorizontalTextAlignment = TextAlignment.Center,
546 FontAttributes = FontAttributes.Bold,
548 Style = AppStyles.ClickableValueLabel
549 };
550
551 TapGestureRecognizer OpenLegalId = new();
552 OpenLegalId.Tapped += this.LegalId_Tapped;
553
554 Label.GestureRecognizers.Add(OpenLegalId);
555
556 RoleLayout.Insert(i, Label);
557 NrParts++;
558
559 if (NrParts >= RoleObj.MaxCount)
560 Button.IsEnabled = false;
561
562 if (FriendlyName == LegalId)
563 {
564 await Task.Run(async () =>
565 {
566 LegalIdentity? Identity = await ServiceRef.ContractOrchestratorService.TryGetLegalIdentity(LegalId,
567 ServiceRef.Localizer[nameof(AppResources.ForInclusionInContract)]);
568
569 if (Identity is not null)
570 {
571 MainThread.BeginInvokeOnMainThread(() =>
572 {
573 FriendlyName = ContactInfo.GetFriendlyName(Identity);
574 Label.Text = FriendlyName;
575 });
576 }
577 });
578 }
579 }
580
581 return;
582 }
583 else
584 {
585 CurrentRole = false;
586 LegalIdAdded = false;
587 NrParts = 0;
588 }
589 }
590
591 i++;
592 }
593 }
594 }
595 }
596 }
597
598 private async void LegalId_Tapped(object? Sender, EventArgs e)
599 {
600 try
601 {
602 if (Sender is Label label && !string.IsNullOrEmpty(label.StyleId))
603 {
604 await ServiceRef.ContractOrchestratorService.OpenLegalIdentity(label.StyleId,
605 ServiceRef.Localizer[nameof(AppResources.ForInclusionInContract)]);
606 }
607 }
608 catch (Exception ex)
609 {
610 ServiceRef.LogService.LogException(ex);
612 }
613 }
614
615 private async void AddPartButton_Clicked(object? Sender, EventArgs e)
616 {
617 try
618 {
619 if (Sender is TextButton Button)
620 {
621 this.saveStateWhileScanning = true;
622 this.stateTemplateWhileScanning = this.template;
623
624 IEnumerable<ContactInfo> Contacts = await Database.Find<ContactInfo>();
625 string LegalId;
626 bool HasContacts = false;
627
628 foreach (ContactInfo Contact2 in Contacts)
629 {
630 HasContacts = true;
631 break;
632 }
633
634 if (HasContacts)
635 {
636 TaskCompletionSource<ContactInfoModel?> Selected = new();
637 ContactListNavigationArgs Args = new(ServiceRef.Localizer[nameof(AppResources.AddContactToContract)], Selected)
638 {
639 CanScanQrCode = true,
640 Contacts = Contacts
641 };
642
643 await ServiceRef.UiService.GoToAsync(nameof(MyContactsPage), Args, BackMethod.Pop);
644
645 ContactInfoModel? Contact = await Selected.Task;
646 if (Contact is null)
647 return;
648
649 LegalId = Contact.LegalId;
650 }
651 else
652 {
653 string? Code = await QrCode.ScanQrCode(ServiceRef.Localizer[nameof(AppResources.ScanQRCode)], [Constants.UriSchemes.IotId]);
654 if (string.IsNullOrEmpty(Code))
655 return;
656
657 LegalId = Constants.UriSchemes.RemoveScheme(Code) ?? string.Empty;
658 }
659
660 if (string.IsNullOrEmpty(LegalId))
661 await ServiceRef.UiService.DisplayAlert(ServiceRef.Localizer[nameof(AppResources.ErrorTitle)], ServiceRef.Localizer[nameof(AppResources.SelectedContactCannotBeAdded)]);
662 else
663 {
664 this.parts[Button.StyleId] = LegalId;
665 await ServiceRef.SettingsService.SaveState(this.GetSettingsKey(nameof(this.Parts)), this.Parts);
666
667 foreach (KeyValuePair<CaseInsensitiveString, string> part in this.parts)
668 await this.AddRole(part.Key, part.Value);
669 }
670 }
671 }
672 catch (Exception ex)
673 {
674 ServiceRef.LogService.LogException(ex);
676 }
677 }
678
679 private async void Parameter_DateChanged(object? Sender, NullableDateChangedEventArgs e)
680 {
681 try
682 {
683 if (Sender is not ExtendedDatePicker Picker || !this.parametersByName.TryGetValue(Picker.StyleId, out ParameterInfo? ParameterInfo))
684 return;
685
686 if (ParameterInfo?.Parameter is DateParameter DP)
687 {
688 if (e.NewDate is not null)
689 DP.Value = e.NewDate;
690 else
691 {
692 Picker.BackgroundColor = ControlBgColor.ToColor(false);
693 return;
694 }
695 }
696
697 await this.ValidateParameters();
698 await this.PopulateHumanReadableText();
699 }
700 catch (Exception ex)
701 {
702 ServiceRef.LogService.LogException(ex);
703 }
704 }
705
706 private async void Parameter_TextChanged(object? Sender, TextChangedEventArgs e)
707 {
708 try
709 {
710 if (Sender is not Entry Entry ||
711 !this.parametersByName.TryGetValue(Entry.StyleId, out ParameterInfo? ParameterInfo))
712 {
713 return;
714 }
715
716 bool Ok;
717
718 if (ParameterInfo.Parameter is StringParameter SP)
719 {
720 SP.Value = e.NewTextValue;
721 Ok = true;
722 }
723 else if (ParameterInfo.Parameter is NumericalParameter NP)
724 {
725 if (Ok = decimal.TryParse(e.NewTextValue, out decimal d))
726 NP.Value = d;
727 }
728 else if (ParameterInfo.Parameter is BooleanParameter BP)
729 {
730 if (Ok = CommonTypes.TryParse(e.NewTextValue, out bool b))
731 BP.Value = b;
732 }
733 else if (ParameterInfo.Parameter is DateTimeParameter DTP)
734 {
735 if (Ok = DateTime.TryParse(e.NewTextValue, out DateTime TP))
736 DTP.Value = TP;
737 }
738 else if (ParameterInfo.Parameter is TimeParameter TSP)
739 {
740 if (Ok = TimeSpan.TryParse(e.NewTextValue, out TimeSpan TS))
741 TSP.Value = TS;
742 }
743 else if (ParameterInfo.Parameter is DurationParameter DP)
744 {
745 if (Ok = (ParameterInfo.DurationValue != Duration.Zero))
746 DP.Value = ParameterInfo.DurationValue;
747
748 /*
749 if (Ok = Duration.TryParse(e.NewTextValue, out Duration D))
750 DP.Value = D;
751 */
752 }
753 else
754 Ok = false;
755
756 if (!Ok)
757 {
758 Color? BgColor = ControlBgColor.ToColor(Ok);
759
760 Entry.BackgroundColor = BgColor;
761 CompositeEntry? compositeEntry = this.parametersByName[Entry.StyleId].Control as CompositeEntry;
762 if (compositeEntry is not null)
763 {
764 compositeEntry.BackgroundColor = BgColor;
765 compositeEntry.Border.BackgroundColor = BgColor;
766 }
767 //Border.BackgroundColor = BgColor;
768 //CompositeEntry.BackgroundColor = BgColor;
769
770 return;
771 }
772
773 await this.ValidateParameters();
774 if (this.populateTimer is not null)
775 {
776 try
777 {
778 this.populateTimer.Dispose();
779 }
780 catch (Exception)
781 {
782 //Normal operation
783 }
784 finally
785 {
786 this.populateTimer = null;
787 }
788 }
789 this.populateTimer = new Timer(this.PopulateTimer_Callback, null, 3000, Timeout.Infinite);
790 }
791 catch (Exception ex)
792 {
793 ServiceRef.LogService.LogException(ex);
794 }
795 }
796
797 private async void PopulateTimer_Callback(object? obj)
798 {
799 this.populateTimer?.Dispose();
800
801 this.populateTimer = null;
802 await this.PopulateHumanReadableText();
803 }
804
805 private async void Parameter_CheckedChanged(object? Sender, CheckedChangedEventArgs e)
806 {
807 try
808 {
809 if (Sender is not CheckBox CheckBox || !this.parametersByName.TryGetValue(CheckBox.StyleId, out ParameterInfo? ParameterInfo))
810 return;
811
812 if (ParameterInfo.Parameter is BooleanParameter BP)
813 BP.Value = e.Value;
814
815 await this.ValidateParameters();
816 await this.PopulateHumanReadableText();
817 }
818 catch (Exception ex)
819 {
820 ServiceRef.LogService.LogException(ex);
821 }
822 }
823
824 private async Task ValidateParameters()
825 {
827 Variables Variables = [];
828 bool Ok = true;
829
830 if (this.template is not null)
831 Variables["Duration"] = this.template.Duration;
832
833 foreach (ParameterInfo P in this.parametersInOrder)
834 P.Parameter.Populate(Variables);
835
836 foreach (ParameterInfo P in this.parametersInOrder)
837 {
838 bool Valid;
839
840 try
841 {
842 // Calculation parameters might only execute on the server. So, if they fail in the client, allow user to propose contract anyway.
843
844 Valid = await P.Parameter.IsParameterValid(Variables, ContractsClient) || P.Control is null;
845 }
846 catch (Exception)
847 {
848 Valid = false;
849 }
850
851 Ok &= Valid;
852
853 Color? Color = ControlBgColor.ToColor(Valid);
854
855 if (P.Control is not null)
856 P.Control.BackgroundColor = Color;
857
858 if (P.AltColorElement is View Label)
859 Label.BackgroundColor = Color;
860 }
861
862 this.ParametersOk = Ok;
863 }
864
865 [RelayCommand(CanExecute = nameof(CanPropose))]
866 private async Task Propose()
867 {
868 if (this.template is null)
869 return;
870
871 List<Part> Parts = [];
872 Contract? Created = null;
873 string Role = string.Empty;
874 int State = 0;
875 int Nr = 0;
876 int Min = 0;
877 int Max = 0;
878
879 this.IsProposing = true;
880 try
881 {
882 if (this.Roles is not null)
883 {
884 foreach (IView FrameView in this.Roles.Children)
885 {
886 if (FrameView is Frame RoleFrame && RoleFrame.Content is VerticalStackLayout RoleLayout)
887 {
888 State = 0;
889
890 foreach (IView View in RoleLayout.Children)
891 {
892 switch (State)
893 {
894 case 0:
895 if (View is Label Label && !string.IsNullOrEmpty(Label.StyleId))
896 {
897 Role = Label.StyleId;
898 State++;
899 Nr = Min = Max = 0;
900
901 foreach (Role R in this.template.Roles)
902 {
903 if (R.Name == Role)
904 {
905 Min = R.MinCount;
906 Max = R.MaxCount;
907 break;
908 }
909 }
910 }
911 break;
912
913 case 1:
914 if (View is TextButton)
915 {
916 if (Nr < Min)
917 {
918 await ServiceRef.UiService.DisplayAlert(ServiceRef.Localizer[nameof(AppResources.ErrorTitle)],
919 ServiceRef.Localizer[nameof(AppResources.TheContractRequiresAtLeast_AddMoreParts), Min, Role]);
920 return;
921 }
922
923 if (Nr > Min)
924 {
925 await ServiceRef.UiService.DisplayAlert(ServiceRef.Localizer[nameof(AppResources.ErrorTitle)],
926 ServiceRef.Localizer[nameof(AppResources.TheContractRequiresAtMost_RemoveParts), Max, Role]);
927 return;
928 }
929
930 State--;
931 Role = string.Empty;
932 }
933 else if (View is Label Label2 && !string.IsNullOrEmpty(Role))
934 {
935 Parts.Add(new Part
936 {
937 Role = Role,
938 LegalId = Label2.StyleId
939 });
940
941 Nr++;
942 }
943 break;
944 }
945 }
946 }
947 }
948 }
949
950 if (this.Parameters is not null)
951 {
952 foreach (IView View in this.Parameters.Children)
953 {
954 if (View is Entry Entry)
955 {
956 if (Entry.BackgroundColor == AppColors.ErrorBackground)
957 {
958 await ServiceRef.UiService.DisplayAlert(ServiceRef.Localizer[nameof(AppResources.ErrorTitle)],
959 ServiceRef.Localizer[nameof(AppResources.YourContractContainsErrors)]);
960
961 Entry.Focus();
962 return;
963 }
964 }
965 }
966 }
967
968 this.template.PartsMode = ContractParts.Open;
969
970 int i = this.SelectedContractVisibilityItem is null ? -1 : this.ContractVisibilityItems.IndexOf(this.SelectedContractVisibilityItem);
971 switch (i)
972 {
973 case 0:
974 this.template.Visibility = ContractVisibility.CreatorAndParts;
975 break;
976
977 case 1:
978 this.template.Visibility = ContractVisibility.DomainAndParts;
979 break;
980
981 case 2:
982 this.template.Visibility = ContractVisibility.Public;
983 break;
984
985 case 3:
986 this.template.Visibility = ContractVisibility.PublicSearchable;
987 break;
988
989 default:
990 await ServiceRef.UiService.DisplayAlert(ServiceRef.Localizer[nameof(AppResources.ErrorTitle)],
991 ServiceRef.Localizer[nameof(AppResources.ContractVisibilityMustBeSelected)]);
992 return;
993 }
994
995 if (this.SelectedRole is null)
996 {
997 await ServiceRef.UiService.DisplayAlert(ServiceRef.Localizer[nameof(AppResources.ErrorTitle)],
998 ServiceRef.Localizer[nameof(AppResources.ContractRoleMustBeSelected)]);
999 return;
1000 }
1001
1002 if (!await App.AuthenticateUser(AuthenticationPurpose.ProposeContract, true))
1003 return;
1004
1005 Created = await ServiceRef.XmppService.CreateContract(this.templateId, [.. Parts], this.template.Parameters,
1006 this.template.Visibility, ContractParts.ExplicitlyDefined, this.template.Duration ?? Duration.FromYears(1),
1007 this.template.ArchiveRequired ?? Duration.FromYears(1), this.template.ArchiveOptional ?? Duration.FromYears(1),
1008 null, null, false);
1009
1010 Created = await ServiceRef.XmppService.SignContract(Created, this.SelectedRole, false);
1011
1012 if (Created.Parts is not null)
1013 {
1014 foreach (Part Part in Created.Parts)
1015 {
1016 if (this.suppressedProposalIds is not null && Array.IndexOf<CaseInsensitiveString>(this.suppressedProposalIds, Part.LegalId) >= 0)
1017 continue;
1018
1020 if (Info is null || string.IsNullOrEmpty(Info.BareJid))
1021 continue;
1022
1023 await ServiceRef.XmppService.ContractsClient.AuthorizeAccessToContractAsync(Created.ContractId, Info.BareJid, true);
1024
1025 string? Proposal = await ServiceRef.UiService.DisplayPrompt(ServiceRef.Localizer[nameof(AppResources.Proposal)],
1026 ServiceRef.Localizer[nameof(AppResources.EnterProposal), Info.FriendlyName],
1027 ServiceRef.Localizer[nameof(AppResources.Send)],
1028 ServiceRef.Localizer[nameof(AppResources.Cancel)]);
1029
1030 if (!string.IsNullOrEmpty(Proposal))
1031 await ServiceRef.XmppService.SendContractProposal(Created, Part.Role, Info.BareJid, Proposal);
1032 }
1033 }
1034 }
1035 catch (Exception ex)
1036 {
1037 ServiceRef.LogService.LogException(ex);
1039 }
1040 finally
1041 {
1042 this.IsProposing = false;
1043
1044 if (Created is not null)
1045 {
1046 ViewContractNavigationArgs Args = new(Created, false);
1047
1048 // Inherit the back method here. It will vary if created or viewed.
1049 await ServiceRef.UiService.GoToAsync(nameof(ViewContractPage), Args, BackMethod.Pop2);
1050 }
1051 }
1052 }
1053
1054 internal static IView ParseXaml(string Xaml)
1055 {
1056 VerticalStackLayout VerticalLayout = new VerticalStackLayout().LoadFromXaml(Xaml);
1057
1058 IView? First = null;
1059
1060 foreach (IView Element in VerticalLayout.Children)
1061 {
1062 if (First is null)
1063 First = Element;
1064 else
1065 return VerticalLayout;
1066 }
1067
1068 return First ?? VerticalLayout;
1069 }
1070
1071 internal static void Populate(VerticalStackLayout Layout, string Xaml)
1072 {
1073 VerticalStackLayout VerticalLayout = new VerticalStackLayout().LoadFromXaml(Xaml);
1074
1075 foreach (IView Element in VerticalLayout.Children)
1076 Layout.Children.Add(Element);
1077 }
1078
1079 internal static void Populate(HorizontalStackLayout Layout, string Xaml)
1080 {
1081 VerticalStackLayout VerticalLayout = new VerticalStackLayout().LoadFromXaml(Xaml);
1082
1083 foreach (IView Element in VerticalLayout.Children)
1084 Layout.Children.Add(Element);
1085 }
1086
1087 private async Task PopulateTemplateForm(ContractVisibility? Visibility)
1088 {
1089 this.ClearTemplate(true);
1090
1091 if (this.template is null)
1092 return;
1093
1094 await this.PopulateHumanReadableText();
1095
1096 this.HasRoles = (this.template.Roles?.Length ?? 0) > 0;
1097 this.VisibilityIsEnabled = true;
1098
1099 VerticalStackLayout RolesLayout = [];
1100 if (this.template.Roles is not null)
1101 {
1102 foreach (Role Role in this.template.Roles)
1103 {
1104
1105 this.AvailableRoles.Add(Role.Name);
1106
1107 VerticalStackLayout RoleLayout =
1108 [
1109 new Label()
1110 {
1111 Text = Role.Name,
1113 StyleId = Role.Name
1114 }
1115 ];
1116
1117 Populate(RoleLayout, await Role.ToMauiXaml(this.template.DeviceLanguage(), this.template));
1118
1119 if (Role.MinCount > 0)
1120 {
1121 TextButton Button = new()
1122 {
1123 LabelData = ServiceRef.Localizer[nameof(AppResources.AddPart)],
1124 StyleId = Role.Name,
1126 Margin = AppStyles.SmallBottomMargins
1127 };
1128 Button.Clicked += this.AddPartButton_Clicked;
1129
1130 RoleLayout.Children.Add(Button);
1131 }
1132
1133 Frame RoleFrame = new()
1134 {
1135 Style = AppStyles.FrameSubSet,
1136 Content = RoleLayout
1137 };
1138
1139 RolesLayout.Children.Add(RoleFrame);
1140 }
1141 }
1142
1143 this.Roles = RolesLayout;
1144
1145 VerticalStackLayout ParametersLayout = [];
1146
1147 this.parametersByName.Clear();
1148 this.parametersInOrder.Clear();
1149
1150 foreach (Parameter Parameter in this.template!.Parameters)
1151 {
1152 if (Parameter is BooleanParameter BP)
1153 {
1154 CheckBox CheckBox = new()
1155 {
1156 StyleId = Parameter.Name,
1157 IsChecked = BP.Value.HasValue && BP.Value.Value,
1158 VerticalOptions = LayoutOptions.Center
1159 };
1160
1161 Grid Layout = new()
1162 {
1163 ColumnDefinitions =
1164 [
1165 new ColumnDefinition(GridLength.Auto)
1166 ],
1167 RowDefinitions =
1168 [
1169 new RowDefinition(GridLength.Auto),
1170 new RowDefinition(GridLength.Star)
1171 ],
1172 Margin = AppStyles.SmallBottomMargins
1173 };
1174
1175 IView Label = ParseXaml(await Parameter.ToMauiXaml(this.template.DeviceLanguage(), this.template));
1176
1177 Layout.Add(CheckBox, 0, 0);
1178 Layout.Add(Label, 1, 0);
1179 ParametersLayout.Children.Add(Layout);
1180
1181 CheckBox.CheckedChanged += this.Parameter_CheckedChanged;
1182
1183 ParameterInfo PI = new(Parameter, CheckBox, Layout);
1184 this.parametersByName[Parameter.Name] = PI;
1185 this.parametersInOrder.AddLast(PI);
1186
1187 if (this.presetParameterValues.TryGetValue(Parameter.Name, out object? PresetValue))
1188 {
1189 this.presetParameterValues.Remove(Parameter.Name);
1190
1191 if (PresetValue is bool b || CommonTypes.TryParse(PresetValue?.ToString() ?? string.Empty, out b))
1192 CheckBox.IsChecked = b;
1193 }
1194 }
1196 {
1197 ParameterInfo PI = new(Parameter, null);
1198 this.parametersByName[Parameter.Name] = PI;
1199 this.parametersInOrder.AddLast(PI);
1200
1201 this.presetParameterValues.Remove(Parameter.Name);
1202 }
1203 else if (Parameter is DateParameter DP)
1204 {
1205 Populate(ParametersLayout, await Parameter.ToMauiXaml(this.template.DeviceLanguage(), this.template));
1206
1207 Border Border = new()
1208 {
1209 StrokeThickness = 2,
1211 Margin = AppStyles.SmallBottomMargins
1212 };
1213
1214 ExtendedDatePicker Picker = new()
1215 {
1216 StyleId = Parameter.Name,
1217 NullableDate = Parameter.ObjectValue as DateTime?,
1218 Placeholder = Parameter.Guide
1219 };
1220
1221 Border.Content = Picker;
1222 ParametersLayout.Children.Add(Border);
1223
1224 Picker.NullableDateSelected += this.Parameter_DateChanged;
1225
1226 ParameterInfo PI = new(Parameter, Picker, Border);
1227 this.parametersByName[Parameter.Name] = PI;
1228 this.parametersInOrder.AddLast(PI);
1229
1230 if (this.presetParameterValues.TryGetValue(Parameter.Name, out object? PresetValue))
1231 {
1232 this.presetParameterValues.Remove(Parameter.Name);
1233
1234 if (PresetValue is DateTime TP || XML.TryParse(PresetValue?.ToString() ?? string.Empty, out TP))
1235 Picker.Date = TP;
1236 }
1237 }
1238 else
1239 {
1240 Populate(ParametersLayout, await Parameter.ToMauiXaml(this.template.DeviceLanguage(), this.template));
1241
1242 CompositeEntry Entry = new()
1243 {
1244 StyleId = Parameter.Name,
1245 EntryData = Parameter.ObjectValue?.ToString() ?? string.Empty,
1246 Placeholder = Parameter.Guide,
1248 Margin = AppStyles.SmallBottomMargins
1249 };
1250 Entry.Entry.StyleId = Parameter.Name;
1251
1253 {
1254 Grid Grid = new()
1255 {
1256 RowDefinitions =
1257 [
1258 new RowDefinition()
1259 {
1260 Height = GridLength.Auto
1261 }
1262 ],
1263 ColumnDefinitions =
1264 [
1265 new ColumnDefinition()
1266 {
1267 Width = GridLength.Star
1268 },
1269 new ColumnDefinition()
1270 {
1271 Width = GridLength.Auto
1272 }
1273 ],
1274 RowSpacing = 0,
1275 ColumnSpacing = 8,
1276 Padding = new Thickness(0),
1277 Margin = new Thickness(0),
1278 VerticalOptions = LayoutOptions.Center
1279 };
1280
1281 Entry.VerticalOptions = LayoutOptions.Center;
1282 Grid.Add(Entry, 0, 0);
1283
1284 Controls.ImageButton CalcButton = new()
1285 {
1286 StyleId = Parameter.Name,
1287 Style = AppStyles.ImageOnlyButton,
1288 PathData = Parameter is NumericalParameter ? Geometries.CalculatorPath : Geometries.DurationPath,
1289 HorizontalOptions = LayoutOptions.Center,
1290 VerticalOptions = LayoutOptions.Center
1291 };
1292
1294 CalcButton.Clicked += this.CalcButton_Clicked;
1295 else
1296 CalcButton.Clicked += this.DurationButton_Clicked;
1297
1298 Grid.Add(CalcButton, 1, 0);
1299
1300 ParametersLayout.Children.Add(Grid);
1301 }
1302 else
1303 {
1304 ParametersLayout.Children.Add(Entry);
1305 }
1306
1307 Entry.Entry.TextChanged += this.Parameter_TextChanged;
1308
1309 ParameterInfo ParameterInfo = new(Parameter, Entry);
1310
1312 {
1313 Entry.IsReadOnly = true;
1314 Entry.SetBinding(CompositeEntry.EntryDataProperty, new Binding("DurationValue", BindingMode.OneWay, new DurationToString()));
1315 Entry.BindingContext = ParameterInfo;
1316 }
1317
1318 this.parametersByName[Parameter.Name] = ParameterInfo;
1319 this.parametersInOrder.AddLast(ParameterInfo);
1320
1321 if (this.presetParameterValues.TryGetValue(Parameter.Name, out object? PresetValue))
1322 {
1323 this.presetParameterValues.Remove(Parameter.Name);
1324 Entry.EntryData = PresetValue?.ToString() ?? string.Empty;
1325 }
1326 }
1327 }
1328
1329 this.Parameters = ParametersLayout;
1330 this.HasParameters = this.Parameters.Children.Count > 0;
1331
1332 if (this.template.Parts is not null)
1333 {
1334 foreach (Part Part in this.template.Parts)
1335 {
1336 if (ServiceRef.TagProfile.LegalIdentity?.Id == Part.LegalId)
1337 this.SelectedRole = Part.Role;
1338 else
1339 await this.AddRole(Part.Role, Part.LegalId);
1340 }
1341 }
1342
1343 if (this.presetParameterValues.TryGetValue("Visibility", out object? Obj) &&
1344 (Obj is ContractVisibility Visibility2 || Enum.TryParse(Obj?.ToString() ?? string.Empty, out Visibility2)))
1345 {
1346 Visibility = Visibility2;
1347 this.presetParameterValues.Remove("Visibility");
1348 }
1349
1350 if (Visibility.HasValue)
1351 this.SelectedContractVisibilityItem = this.ContractVisibilityItems.FirstOrDefault(x => x.Visibility == Visibility.Value);
1352
1353 if (this.HasRoles)
1354 {
1355 foreach (string Role in this.AvailableRoles)
1356 {
1357 if (this.presetParameterValues.TryGetValue(Role, out Obj) && Obj is string LegalId)
1358 {
1359 int i = LegalId.IndexOf('@');
1360 if (i < 0 || !Guid.TryParse(LegalId[..i], out _))
1361 continue;
1362
1363 await this.AddRole(Role, LegalId);
1364 this.presetParameterValues.Remove(Role);
1365 }
1366 else if (this.template.Parts is not null)
1367 {
1368 foreach (Part Part in this.template.Parts)
1369 {
1370 if (Part.Role == Role)
1371 await this.AddRole(Part.Role, Part.LegalId);
1372 }
1373 }
1374 }
1375 }
1376
1377 if (this.presetParameterValues.TryGetValue("Role", out Obj) && Obj is string SelectedRole)
1378 {
1379 this.SelectedRole = SelectedRole;
1380 this.presetParameterValues.Remove("Role");
1381 }
1382
1383 await this.ValidateParameters();
1384 }
1385
1386 private async Task PopulateHumanReadableText()
1387 {
1388 VerticalStackLayout humanReadableTextLayout = [];
1389
1390 if (this.template is not null)
1391 Populate(humanReadableTextLayout, await this.template.ToMauiXaml(this.template.DeviceLanguage()));
1392
1393
1394 this.HumanReadableText = humanReadableTextLayout;
1395 this.HasHumanReadableText = humanReadableTextLayout.Children.Count > 0;
1396
1397
1398 }
1399
1400 private bool CanPropose()
1401 {
1402 return
1403 this.template is not null &&
1404 this.ParametersOk &&
1405 !this.IsProposing;
1406 }
1407
1408 private async void CalcButton_Clicked(object? Sender, EventArgs e)
1409 {
1410 try
1411 {
1412 if (Sender is not Controls.ImageButton CalcButton)
1413 return;
1414
1415 if (!this.parametersByName.TryGetValue(CalcButton.StyleId, out ParameterInfo? ParameterInfo))
1416 return;
1417
1418 if (ParameterInfo.Control is not CompositeEntry Entry)
1419 return;
1420
1421 CalculatorNavigationArgs Args = new(Entry.Entry);
1422 await ServiceRef.UiService.GoToAsync(nameof(CalculatorPage), Args, BackMethod.Pop);
1423 }
1424 catch (Exception ex)
1425 {
1426 ServiceRef.LogService.LogException(ex);
1427 }
1428 }
1429
1430 private async void DurationButton_Clicked(object? Sender, EventArgs e)
1431 {
1432 try
1433 {
1434 if (Sender is not Controls.ImageButton CalcButton)
1435 return;
1436
1437 if (!this.parametersByName.TryGetValue(CalcButton.StyleId, out ParameterInfo? ParameterInfo))
1438 return;
1439
1440 if (ParameterInfo.Control is not CompositeEntry Entry)
1441 return;
1442
1443 DurationNavigationArgs Args = new(Entry.Entry);
1444 await ServiceRef.UiService.GoToAsync(nameof(DurationPage), Args, BackMethod.Pop);
1445 }
1446 catch (Exception ex)
1447 {
1448 ServiceRef.LogService.LogException(ex);
1449 }
1450 }
1451
1452 #region ILinkableView
1453
1457 public bool IsLinkable => true;
1458
1462 public bool EncodeAppLinks => true;
1463
1467 public string Link
1468 {
1469 get
1470 {
1471 StringBuilder Url = new();
1472 bool First = true;
1473
1474 Url.Append(Constants.UriSchemes.IotSc);
1475 Url.Append(':');
1476 Url.Append(this.template?.ContractId);
1477
1478 foreach (KeyValuePair<CaseInsensitiveString, ParameterInfo> P in this.parametersByName)
1479 {
1480 if (First)
1481 {
1482 First = false;
1483 Url.Append('&');
1484 }
1485 else
1486 Url.Append('?');
1487
1488 Url.Append(P.Key);
1489 Url.Append('=');
1490
1491 if (P.Value.Control is Entry Entry)
1492 Url.Append(Entry.Text);
1493 else if (P.Value.Control is CheckBox CheckBox)
1494 Url.Append(CheckBox.IsChecked ? '1' : '0');
1495 else if (P.Value.Control is ExtendedDatePicker Picker)
1496 {
1497 if (P.Value.Parameter is DateParameter)
1498 Url.Append(XML.Encode(Picker.Date, true));
1499 else
1500 Url.Append(XML.Encode(Picker.Date, false));
1501 }
1502 else
1503 P.Value.Parameter.ObjectValue?.ToString();
1504 }
1505
1506 return Url.ToString();
1507 }
1508 }
1509
1513 public Task<string> Title => ContractModel.GetName(this.template);
1514
1518 public bool HasMedia => false;
1519
1523 public byte[]? Media => null;
1524
1528 public string? MediaContentType => null;
1529
1530 #endregion
1531
1532 #region Contract Options
1533
1538 public async Task ShowContractOptions(IDictionary<CaseInsensitiveString, object>[] Options)
1539 {
1540 if (Options.Length == 0)
1541 return;
1542
1543 if (Options.Length == 1)
1544 this.ShowSingleContractOptions(Options[0]);
1545 else
1546 this.ShowMultipleContractOptions(Options);
1547
1548 await this.ValidateParameters();
1549 }
1550
1551 private void ShowSingleContractOptions(IDictionary<CaseInsensitiveString, object> Option)
1552 {
1553 foreach (KeyValuePair<CaseInsensitiveString, object> Parameter in Option)
1554 {
1555 string ParameterName = Parameter.Key;
1556
1557 try
1558 {
1559 if (ParameterName.StartsWith("Max(", StringComparison.CurrentCultureIgnoreCase) && ParameterName.EndsWith(')'))
1560 {
1561 if (!this.parametersByName.TryGetValue(ParameterName[4..^1].Trim(), out ParameterInfo? Info))
1562 continue;
1563
1564 Info.Parameter.SetMaxValue(Parameter.Value, true);
1565 }
1566 else if (ParameterName.StartsWith("Min(", StringComparison.CurrentCultureIgnoreCase) && ParameterName.EndsWith(')'))
1567 {
1568 if (!this.parametersByName.TryGetValue(ParameterName[4..^1].Trim(), out ParameterInfo? Info))
1569 continue;
1570
1571 Info.Parameter.SetMinValue(Parameter.Value, true);
1572 }
1573 else
1574 {
1575 if (!this.parametersByName.TryGetValue(ParameterName, out ParameterInfo? Info))
1576 continue;
1577
1578 Info.Parameter.SetValue(Parameter.Value);
1579
1580 if (Info.Control is Entry Entry)
1581 Entry.Text = Parameter.Value?.ToString() ?? string.Empty;
1582 else if (Info.Control is CheckBox CheckBox)
1583 {
1584 if (Parameter.Value is bool b)
1585 CheckBox.IsChecked = b;
1586 else if (Parameter.Value is int i)
1587 CheckBox.IsChecked = i != 0;
1588 else if (Parameter.Value is double d)
1589 CheckBox.IsChecked = d != 0;
1590 else if (Parameter.Value is decimal d2)
1591 CheckBox.IsChecked = d2 != 0;
1592 else if (Parameter.Value is string s && CommonTypes.TryParse(s, out b))
1593 CheckBox.IsChecked = b;
1594 else
1595 {
1596 ServiceRef.LogService.LogWarning("Invalid option value.",
1597 new KeyValuePair<string, object?>("Parameter", ParameterName),
1598 new KeyValuePair<string, object?>("Value", Parameter.Value),
1599 new KeyValuePair<string, object?>("Type", Parameter.Value?.GetType().FullName ?? string.Empty));
1600 }
1601 }
1602 else if (Info.Control is ExtendedDatePicker Picker)
1603 {
1604 if (Parameter.Value is DateTime TP)
1605 Picker.NullableDate = TP;
1606 else if (Parameter.Value is string s && (DateTime.TryParse(s, out TP) || XML.TryParse(s, out TP)))
1607 Picker.NullableDate = TP;
1608 else
1609 {
1610 ServiceRef.LogService.LogWarning("Invalid option value.",
1611 new KeyValuePair<string, object?>("Parameter", ParameterName),
1612 new KeyValuePair<string, object?>("Value", Parameter.Value),
1613 new KeyValuePair<string, object?>("Type", Parameter.Value?.GetType().FullName ?? string.Empty));
1614 }
1615 }
1616 }
1617 }
1618 catch (Exception ex)
1619 {
1620 ServiceRef.LogService.LogWarning("Invalid option value. Exception: " + ex.Message,
1621 new KeyValuePair<string, object?>("Parameter", ParameterName),
1622 new KeyValuePair<string, object?>("Value", Parameter.Value),
1623 new KeyValuePair<string, object?>("Type", Parameter.Value?.GetType().FullName ?? string.Empty));
1624
1625 continue;
1626 }
1627 }
1628 }
1629
1630 private void ShowMultipleContractOptions(IDictionary<CaseInsensitiveString, object>[] Options)
1631 {
1632 CaseInsensitiveString PrimaryKey = this.GetPrimaryKey(Options);
1633
1634 if (CaseInsensitiveString.IsNullOrEmpty(PrimaryKey))
1635 {
1636 ServiceRef.LogService.LogWarning("Options not displayed. No primary key could be established. Using only first option.");
1637
1638 foreach (IDictionary<CaseInsensitiveString, object> Option in Options)
1639 {
1640 this.ShowSingleContractOptions(Option);
1641 break;
1642 }
1643
1644 return;
1645 }
1646
1647 if (!this.parametersByName.TryGetValue(PrimaryKey, out ParameterInfo? Info))
1648 {
1649 ServiceRef.LogService.LogWarning("Options not displayed. Primary key not available in contract.");
1650 return;
1651 }
1652
1653 if (Info.Control is not Entry Entry)
1654 {
1655 ServiceRef.LogService.LogWarning("Options not displayed. Parameter control not of a type that allows a selection control to be created.");
1656 return;
1657 }
1658
1659 int EntryIndex = this.Parameters?.Children.IndexOf(Entry) ?? -1;
1660 if (EntryIndex < 0)
1661 {
1662 ServiceRef.LogService.LogWarning("Options not displayed. Primary Key Entry not found.");
1663 return;
1664 }
1665
1666 this.ParameterOptions.Clear();
1667
1668 ContractOption? SelectedOption = null;
1669
1670 foreach (IDictionary<CaseInsensitiveString, object> Option in Options)
1671 {
1672 string Name = Option[PrimaryKey]?.ToString() ?? string.Empty;
1673 ContractOption ContractOption = new(Name, Option);
1674
1675 this.ParameterOptions.Add(ContractOption);
1676
1677 if (Name == Entry.Text)
1678 SelectedOption = ContractOption;
1679 }
1680
1681 Picker Picker = new()
1682 {
1683 StyleId = Info.Parameter.Name,
1684 ItemsSource = this.ParameterOptions,
1685 Title = Info.Parameter.Guide
1686 };
1687
1688 this.Parameters?.Children.RemoveAt(EntryIndex);
1689 this.Parameters?.Children.Insert(EntryIndex, Picker);
1690
1691 Picker.SelectedIndexChanged += this.Parameter_OptionSelectionChanged;
1692 Info.Control = Picker;
1693
1694 if (SelectedOption is not null)
1695 Picker.SelectedItem = SelectedOption;
1696 }
1697
1698 private async void Parameter_OptionSelectionChanged(object? Sender, EventArgs e)
1699 {
1700 if (Sender is not Picker Picker)
1701 return;
1702
1703 if (Picker.SelectedItem is not ContractOption Option)
1704 return;
1705
1706 try
1707 {
1708 foreach (KeyValuePair<CaseInsensitiveString, object> P in Option.Option)
1709 {
1710 string ParameterName = P.Key;
1711
1712 try
1713 {
1714 if (ParameterName.StartsWith("Max(", StringComparison.CurrentCultureIgnoreCase) && ParameterName.EndsWith(')'))
1715 {
1716 if (!this.parametersByName.TryGetValue(ParameterName[4..^1].Trim(), out ParameterInfo? Info))
1717 continue;
1718
1719 Info.Parameter.SetMaxValue(P.Value, true);
1720 }
1721 else if (ParameterName.StartsWith("Min(", StringComparison.CurrentCultureIgnoreCase) && ParameterName.EndsWith(')'))
1722 {
1723 if (!this.parametersByName.TryGetValue(ParameterName[4..^1].Trim(), out ParameterInfo? Info))
1724 continue;
1725
1726 Info.Parameter.SetMinValue(P.Value, true);
1727 }
1728 else
1729 {
1730 if (!this.parametersByName.TryGetValue(ParameterName, out ParameterInfo? Info))
1731 continue;
1732
1733 Entry? Entry = Info.Control as Entry;
1734
1735 if (Info.Parameter is StringParameter SP)
1736 {
1737 string s = P.Value?.ToString() ?? string.Empty;
1738
1739 SP.Value = s;
1740
1741 if (Entry is not null)
1742 {
1743 Entry.Text = s;
1744 Entry.BackgroundColor = ControlBgColor.ToColor(true);
1745 }
1746 }
1747 else if (Info.Parameter is NumericalParameter NP)
1748 {
1749 try
1750 {
1751 NP.Value = Expression.ToDecimal(P.Value);
1752
1753 if (Entry is not null)
1754 Entry.BackgroundColor = ControlBgColor.ToColor(true);
1755 }
1756 catch (Exception)
1757 {
1758 if (Entry is not null)
1759 Entry.BackgroundColor = ControlBgColor.ToColor(false);
1760 }
1761 }
1762 else if (Info.Parameter is BooleanParameter BP)
1763 {
1764 CheckBox? CheckBox = Info.Control as CheckBox;
1765
1766 try
1767 {
1768 if (P.Value is bool b2)
1769 BP.Value = b2;
1770 else if (P.Value is string s && CommonTypes.TryParse(s, out b2))
1771 BP.Value = b2;
1772 else
1773 {
1774 if (CheckBox is not null)
1775 CheckBox.BackgroundColor = ControlBgColor.ToColor(false);
1776
1777 continue;
1778 }
1779
1780 if (CheckBox is not null)
1781 CheckBox.BackgroundColor = ControlBgColor.ToColor(true);
1782 }
1783 catch (Exception)
1784 {
1785 if (CheckBox is not null)
1786 CheckBox.BackgroundColor = ControlBgColor.ToColor(false);
1787 }
1788 }
1789 else if (Info.Parameter is DateTimeParameter DTP)
1790 {
1791 Picker? Picker2 = Info.Control as Picker;
1792
1793 if (P.Value is DateTime TP ||
1794 (P.Value is string s && (DateTime.TryParse(s, out TP) || XML.TryParse(s, out TP))))
1795 {
1796 DTP.Value = TP;
1797
1798 if (Picker2 is not null)
1799 Picker2.BackgroundColor = ControlBgColor.ToColor(true);
1800 }
1801 else
1802 {
1803 if (Picker2 is not null)
1804 Picker2.BackgroundColor = ControlBgColor.ToColor(false);
1805 }
1806 }
1807 else if (Info.Parameter is TimeParameter TSP)
1808 {
1809 if (P.Value is TimeSpan TS ||
1810 (P.Value is string s && TimeSpan.TryParse(s, out TS)))
1811 {
1812 TSP.Value = TS;
1813
1814 if (Entry is not null)
1815 Entry.BackgroundColor = ControlBgColor.ToColor(true);
1816 }
1817 else
1818 {
1819 if (Entry is not null)
1820 Entry.BackgroundColor = ControlBgColor.ToColor(false);
1821 }
1822 }
1823 else if (Info.Parameter is DurationParameter DP)
1824 {
1825 if (P.Value is Duration D ||
1826 (P.Value is string s && Duration.TryParse(s, out D)))
1827 {
1828 DP.Value = D;
1829
1830 if (Entry is not null)
1831 Entry.BackgroundColor = ControlBgColor.ToColor(true);
1832 }
1833 else
1834 {
1835 if (Entry is not null)
1836 Entry.BackgroundColor = ControlBgColor.ToColor(false);
1837
1838 return;
1839 }
1840 }
1841 }
1842 }
1843 catch (Exception ex)
1844 {
1845 ServiceRef.LogService.LogException(ex);
1846 }
1847 }
1848
1849 await this.ValidateParameters();
1850 await this.PopulateHumanReadableText();
1851 }
1852 catch (Exception ex)
1853 {
1854 ServiceRef.LogService.LogException(ex);
1855 }
1856 }
1857
1858 private CaseInsensitiveString GetPrimaryKey(IDictionary<CaseInsensitiveString, object>[] Options)
1859 {
1860 Dictionary<CaseInsensitiveString, Dictionary<string, bool>> ByKeyAndValue = [];
1861 LinkedList<CaseInsensitiveString> Keys = new();
1862 int c = Options.Length;
1863
1864 foreach (IDictionary<CaseInsensitiveString, object> Option in Options)
1865 {
1866 foreach (KeyValuePair<CaseInsensitiveString, object> P in Option)
1867 {
1868 if (!ByKeyAndValue.TryGetValue(P.Key, out Dictionary<string, bool>? Values))
1869 {
1870 Values = [];
1871 ByKeyAndValue[P.Key] = Values;
1872 Keys.AddLast(P.Key);
1873 }
1874
1875 Values[P.Value?.ToString() ?? string.Empty] = true;
1876 }
1877 }
1878
1879 foreach (CaseInsensitiveString Key in Keys)
1880 {
1881 if (ByKeyAndValue[Key].Count == c &&
1882 this.parametersByName.TryGetValue(Key, out ParameterInfo? Info) &&
1883 Info.Control is Entry)
1884 {
1885 return Key;
1886 }
1887 }
1888
1890 }
1891
1892 #endregion
1893
1894 }
1895}
The Application class, representing an instance of the Neuro-Access app.
Definition: App.xaml.cs:69
static Task< bool > AuthenticateUser(AuthenticationPurpose Purpose, bool Force=false)
Authenticates the user using the configured authentication method.
Definition: App.xaml.cs:981
const string IotSc
The IoT Smart Contract URI Scheme (iotsc)
Definition: Constants.cs:109
static ? string RemoveScheme(string Url)
Removes the URI Schema from an URL.
Definition: Constants.cs:218
const string IotId
The IoT ID URI Scheme (iotid)
Definition: Constants.cs:99
A set of never changing property constants and helpful values.
Definition: Constants.cs:7
Contains information about a contact.
Definition: ContactInfo.cs:21
static async Task< string > GetFriendlyName(CaseInsensitiveString RemoteId)
Gets the friendly name of a remote identity (Legal ID or Bare JID).
Definition: ContactInfo.cs:257
static Task< ContactInfo > FindByLegalId(string LegalId)
Finds information about a contact, given its Legal ID.
Definition: ContactInfo.cs:247
CaseInsensitiveString BareJid
Bare JID of contact.
Definition: ContactInfo.cs:62
Base class that references services in the app.
Definition: ServiceRef.cs:31
static ILogService LogService
Log service.
Definition: ServiceRef.cs:91
static IUiService UiService
Service serializing and managing UI-related tasks.
Definition: ServiceRef.cs:55
static ITagProfile TagProfile
TAG Profile service.
Definition: ServiceRef.cs:79
static IStringLocalizer Localizer
Localization service
Definition: ServiceRef.cs:235
static IContractOrchestratorService ContractOrchestratorService
Contract orchestrator service.
Definition: ServiceRef.cs:115
static IXmppService XmppService
The XMPP service for XMPP communication.
Definition: ServiceRef.cs:67
static ISettingsService SettingsService
Settings service.
Definition: ServiceRef.cs:175
Helper class to perform scanning of QR Codes by displaying the UI and handling async results.
Definition: QrCode.cs:17
static async Task< string?> ScanQrCode(string? QrTitle, string[] AllowedSchemas)
Navigates to the Scan QR Code Page, waits for scan to complete, and returns the result....
Definition: QrCode.cs:159
Static class that gives access to app-specific themed colors
Definition: AppColors.cs:7
static Color ErrorBackground
Error Background Color.
Definition: AppColors.cs:462
Static class that gives access to app-specific styles
Definition: AppStyles.cs:11
static Style RegularCompositeEntry
Style for borders in a regular composte entry control.
Definition: AppStyles.cs:280
static Style ImageOnlyButton
Style for buttons containing only an image.
Definition: AppStyles.cs:316
static Style FrameSubSet
Style for frame subsets.
Definition: AppStyles.cs:256
static Style SectionTitleLabel
Style of section title labels
Definition: AppStyles.cs:160
static Thickness SmallBottomMargins
Bottom-only small margins
Definition: AppStyles.cs:112
static Style RegularCompositeEntryBorder
Style for borders in a regular composte entry control.
Definition: AppStyles.cs:292
static Style FilledTextButton
Style for filled text buttons.
Definition: AppStyles.cs:232
static readonly BindableProperty EntryDataProperty
Bindable property for the Entry data.
Extended DatePicker for nullable values with text placeholder
Converts an input control OK state to a background color.
static ? Color ToColor(bool Ok)
Converts a control state to a representative color.
Converts a Duration value to a String value.
static string ToString(Duration Duration)
Converts a Duration to a human-readable text.
Static class containing SVG Paths for symbols used in the app.
Definition: Geometries.cs:11
static readonly Geometry DurationPath
Duration button glyph
Definition: Geometries.cs:439
A base class for all view models, inheriting from the BindableObject. NOTE: using this class requir...
string GetSettingsKey(string PropertyName)
Helper method for getting a unique settings key for a given property.
Contact Information model, including related notification information.
CaseInsensitiveString? LegalId
Legal ID of contact.
Holds navigation parameters specific to views displaying a list of contacts.
A page that displays a list of the current user's contacts.
static async Task< string > GetName(Contract? Contract)
Gets a displayable name for a contract.
A page that allows the user to create a new contract.
The view model to bind to when displaying a new contract view or page.
override async Task DoSaveState()
Override this method to do view model specific saving of state when it's parent page/view disappears ...
override async void OnPropertyChanged(PropertyChangedEventArgs e)
override async Task DoRestoreState()
Override this method to do view model specific restoring of state when it's parent page/view appears ...
override async Task OnDispose()
Method called when the view is disposed, and will not be used more. Use this method to unregister eve...
bool EncodeAppLinks
If App links should be encoded with the link.
ObservableCollection< string > AvailableRoles
The different roles available to choose from when creating a contract.
NewContractViewModel(NewContractPage Page, NewContractNavigationArgs? Args)
The view model to bind to when displaying a new contract view or page.
bool HasMedia
If linkable view has media associated with link.
Dictionary< string, object > Parts
Parts dictionary that can be persisted in the object database.
ObservableCollection< ContractVisibilityModel > ContractVisibilityItems
A list of valid visibility items to choose from for this contract.
ObservableCollection< ContractOption > ParameterOptions
The different parameter options available to choose from when creating a contract.
async Task ShowContractOptions(IDictionary< CaseInsensitiveString, object >[] Options)
Method called (from main thread) when contract options are made available.
override async Task OnInitialize()
Method called when view is initialized for the first time. Use this method to implement registration ...
override void OnPropertyChanging(System.ComponentModel.PropertyChangingEventArgs e)
override string ToString()
Returns the string representation, i.e. name, of this contract option.
A page that allows the user to calculate the value of a numerical input field.
A page that allows the user to duration the value of a numerical input field.
Helps with parsing of commong data types.
Definition: CommonTypes.cs:13
static bool TryParse(string s, out double Value)
Tries to decode a string encoded double.
Definition: CommonTypes.cs:46
Helps with common XML-related tasks.
Definition: XML.cs:19
static string Encode(string s)
Encodes a string for use in XML.
Definition: XML.cs:27
static bool TryParse(string s, out DateTime Value)
Tries to decode a string encoded DateTime.
Definition: XML.cs:744
Calculation contractual parameter
Contains the definition of a contract
Definition: Contract.cs:22
Role[] Roles
Roles defined in the smart contract.
Definition: Contract.cs:240
string ContractId
Contract identity
Definition: Contract.cs:65
Part[] Parts
Defined parts for the smart contract.
Definition: Contract.cs:258
Adds support for legal identities, smart contracts and signatures to an XMPP client.
object Value
Value of parameter. Can be set by event handler, to format the value before it is being displayed.
Abstract base class for contractual parameters
Definition: Parameter.cs:17
void SetMaxValue(object Value)
Sets the maximum value allowed by the parameter.
Definition: Parameter.cs:248
abstract object ObjectValue
Parameter value.
Definition: Parameter.cs:104
string Guide
Parameter guide text
Definition: Parameter.cs:40
Class defining a part in a contract
Definition: Part.cs:30
string LegalId
Legal identity of part
Definition: Part.cs:38
string Role
Role of the part in the contract
Definition: Part.cs:57
Class defining a role
Definition: Role.cs:7
int MaxCount
Largest amount of signatures of this role required for a legally binding contract.
Definition: Role.cs:35
int MinCount
Smallest amount of signatures of this role required for a legally binding contract.
Definition: Role.cs:26
string Name
Name of the role.
Definition: Role.cs:17
Role-reference contractual parameter
String-valued contractual parameter
Represents a case-insensitive string.
static readonly CaseInsensitiveString Empty
Empty case-insensitive string
int Length
Gets the number of characters in the current CaseInsensitiveString object.
static bool IsNullOrEmpty(CaseInsensitiveString value)
Indicates whether the specified string is null or an CaseInsensitiveString.Empty string.
Static interface for database persistence. In order to work, a database provider has to be assigned t...
Definition: Database.cs:19
static Task< IEnumerable< object > > Find(string Collection, params string[] SortOrder)
Finds objects in a given collection.
Definition: Database.cs:247
Class managing a script expression.
Definition: Expression.cs:39
static decimal ToDecimal(object Object)
Converts an object to a double value.
Definition: Expression.cs:4883
Collection of variables.
Definition: Variables.cs:25
Task< string?> DisplayPrompt(string Title, string Message, string? Accept, string? Cancel)
Prompts the user for some input.
Task DisplayException(Exception Exception, string? Title=null)
Displays an alert/message box to the user.
Task GoToAsync(string Route, BackMethod BackMethod=BackMethod.Inherited, string? UniqueId=null)
Navigates the AppShell to the specified route, with page arguments to match.
Task< bool > DisplayAlert(string Title, string Message, string? Accept=null, string? Cancel=null)
Displays an alert/message box to the user.
Interface for linkable views.
Definition: ILinkableView.cs:7
BackMethod
Navigation Back Method
Definition: BackMethod.cs:7
AuthenticationPurpose
Purpose for requesting the user to authenticate itself.
TextAlignment
Text alignment of contents.
ContractParts
How the parts of the contract are defined.
Definition: Part.cs:9
ContractVisibility
Visibility types for contracts.
Definition: Enumerations.cs:58
Represents a duration value, as defined by the xsd:duration data type: http://www....
Definition: Duration.cs:13
static Duration FromYears(int Years)
Creates a Duration object from a given number of years.
Definition: Duration.cs:539
static bool TryParse(string s, out Duration Result)
Tries to parse a duration value.
Definition: Duration.cs:85
static readonly Duration Zero
Zero value
Definition: Duration.cs:532