Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
CompositeDurationPicker.cs
1using System.Collections.ObjectModel;
2using System.Globalization;
3using System.Threading.Tasks;
4using CommunityToolkit.Maui.Core.Extensions;
5using CommunityToolkit.Mvvm.Input;
9using Duration = Waher.Content.Duration;
10using Path = Microsoft.Maui.Controls.Shapes.Path;
11
13{
14 public partial class CompositeDurationPicker : ContentView
15 {
16 #region Fields
17 private readonly VerticalStackLayout durationsContainer;
18 private readonly Label titleLabel;
19 private readonly Label descriptionLabel;
20 private readonly CheckBox negateCheckBox;
21 private ObservableCollection<DurationUnits> durationUnits = [
22 DurationUnits.Years,
23 DurationUnits.Months,
24 DurationUnits.Days,
25 DurationUnits.Hours,
26 DurationUnits.Minutes,
27 DurationUnits.Seconds
28 ];
29 #endregion
30
31 #region Constructors
36 {
37 Grid MainGrid = new()
38 {
39 RowDefinitions =
40 {
41 new RowDefinition { Height = GridLength.Auto }, // Top Label
42 new RowDefinition { Height = GridLength.Auto }, // Date Pickers Vertical Stack Layout
43 new RowDefinition { Height = GridLength.Auto }, // Add new Duration Button
44 new RowDefinition { Height = GridLength.Auto } // Negate Field
45 },
46 RowSpacing = AppStyles.SmallSpacing
47 };
48
49 // Top Label
50 Grid TopTextGrid = new()
51 {
52 RowDefinitions =
53 {
54 new RowDefinition { Height = GridLength.Auto },
55 new RowDefinition { Height = GridLength.Auto }
56 },
57 RowSpacing = AppStyles.SmallSpacing
58 };
59
60 this.titleLabel = new();
61 this.titleLabel.SetBinding(Label.StyleProperty, new Binding(nameof(this.TitleLabelStyle), source: this));
62 this.titleLabel.SetBinding(Label.TextProperty, new Binding(nameof(this.TitleLabelText), source: this));
63 TopTextGrid.Add(this.titleLabel, 0, 0);
64
65 this.descriptionLabel = new();
66 this.descriptionLabel.SetBinding(Label.StyleProperty, new Binding(nameof(this.DescriptionLabelStyle), source: this));
67 this.descriptionLabel.SetBinding(Label.TextProperty, new Binding(nameof(this.DescriptionLabelText), source: this));
68 this.descriptionLabel.Margin = 0;
69 TopTextGrid.Add(this.descriptionLabel, 0, 1);
70
71 MainGrid.Add(TopTextGrid, 0, 0);
72
73 // Date Pickers Vertical Stack Layout
74 this.durationsContainer = [];
75 this.durationsContainer.Spacing = AppStyles.SmallSpacing;
76 MainGrid.Add(this.durationsContainer, 0, 1);
77
79 //TextButton AddDurationButton = new()
80 //{
81 // LabelData = "Add Duration",
82 // Style = AppStyles.TertiaryButton,
83 // HorizontalOptions = LayoutOptions.Fill,
84 // VerticalOptions = LayoutOptions.Center
85 //};
86
87 TemplatedButton AddDurationButton = new()
88 {
89 Content = new Border
90 {
92 HorizontalOptions = LayoutOptions.Fill,
93 VerticalOptions = LayoutOptions.Fill,
94 Margin = 0,
95 Content = new Grid
96 {
97 ColumnDefinitions =
98 {
99 new ColumnDefinition { Width = GridLength.Star },
100 new ColumnDefinition { Width = GridLength.Auto }
101 },
102 Children =
103 {
104 new Label
105 {
108 HorizontalOptions = LayoutOptions.Start
109 },
110 new Path
111 {
114 VerticalOptions = LayoutOptions.Center,
115 HorizontalOptions = LayoutOptions.End,
116 WidthRequest = 18,
117 HeightRequest = 18,
118 Aspect = Stretch.Uniform
119 }
120 }
121 }
122 }
123 };
124
125 AddDurationButton.Clicked += (sender, args) => this.AddDurationButton_Clicked();
126 MainGrid.Add(AddDurationButton, 0, 2);
127
128 // Button Grid
129 Grid NegateGrid = new()
130 {
131 ColumnDefinitions =
132 {
133 new ColumnDefinition { Width = GridLength.Auto },
134 new ColumnDefinition { Width = GridLength.Auto },
135 },
136 HorizontalOptions = LayoutOptions.Center
137 };
138
139 // Negate Label
140 Label NegateLabel = new()
141 {
143 HorizontalOptions = LayoutOptions.Center,
144 VerticalOptions = LayoutOptions.Center,
145 Style = AppStyles.InfoLabel
146 };
147 NegateGrid.Add(NegateLabel, 0, 0);
148
149 // Negate CheckBox
150 this.negateCheckBox = new()
151 {
152 IsChecked = false,
153 HorizontalOptions = LayoutOptions.Center,
154 VerticalOptions = LayoutOptions.Center,
155 Color = AppColors.ButtonAccessPrimarybg
156 };
157
158 this.negateCheckBox.CheckedChanged += (sender, args) => this.UpdateDuration();
159
160 NegateGrid.Add(this.negateCheckBox, 1, 0);
161
162 MainGrid.Add(NegateGrid, 0, 3);
163
164 this.Content = MainGrid;
165 }
166
167 #endregion
168
169 #region Methods
170
174 void PopulateDurationsContainer(List<DurationUnits?> PreviousUnits)
175 {
176 List<DurationUnits> AvaliableUnits = [
177 DurationUnits.Years,
178 DurationUnits.Months,
179 DurationUnits.Days,
180 DurationUnits.Hours,
181 DurationUnits.Minutes,
182 DurationUnits.Seconds
183 ];
184
185 if (this.DurationValue.Years != 0)
186 {
187 if (!PreviousUnits.Contains(DurationUnits.Years))
188 this.AddUnit(DurationUnits.Years, this.DurationValue.Years.ToString(CultureInfo.InvariantCulture));
189
190 AvaliableUnits.Remove(DurationUnits.Years);
191 }
192
193 if (this.DurationValue.Months != 0)
194 {
195 if (!PreviousUnits.Contains(DurationUnits.Months))
196 this.AddUnit(DurationUnits.Months, this.DurationValue.Months.ToString(CultureInfo.InvariantCulture));
197
198 AvaliableUnits.Remove(DurationUnits.Months);
199 }
200
201 if (this.DurationValue.Days != 0)
202 {
203 if (!PreviousUnits.Contains(DurationUnits.Days))
204 this.AddUnit(DurationUnits.Days, this.DurationValue.Days.ToString(CultureInfo.InvariantCulture));
205
206 AvaliableUnits.Remove(DurationUnits.Days);
207 }
208
209 if (this.DurationValue.Hours != 0)
210 {
211 if (!PreviousUnits.Contains(DurationUnits.Hours))
212 this.AddUnit(DurationUnits.Hours, this.DurationValue.Hours.ToString(CultureInfo.InvariantCulture));
213
214 AvaliableUnits.Remove(DurationUnits.Hours);
215 }
216
217 if (this.DurationValue.Minutes != 0)
218 {
219 if (!PreviousUnits.Contains(DurationUnits.Minutes))
220 this.AddUnit(DurationUnits.Minutes, this.DurationValue.Minutes.ToString(CultureInfo.InvariantCulture));
221
222 AvaliableUnits.Remove(DurationUnits.Minutes);
223 }
224
225 if (this.DurationValue.Seconds != 0)
226 {
227 if (!PreviousUnits.Contains(DurationUnits.Seconds))
228 this.AddUnit(DurationUnits.Seconds, this.DurationValue.Seconds.ToString(CultureInfo.InvariantCulture));
229
230 AvaliableUnits.Remove(DurationUnits.Seconds);
231 }
232
233 this.negateCheckBox.IsChecked = this.DurationValue.Negation;
234
235 this.durationUnits = AvaliableUnits.ToObservableCollection<DurationUnits>();
236 }
237
242 private void AddUnit(DurationUnits Unit, string Value)
243 {
244 // Setup the Composite Entry
245 CompositeEntry DurationEntry = new()
246 {
248 Margin = 0,
249 Padding = 0,
250 EntryData = (Value == "0") ? "" : Value
251 };
252
253 // Add the Unit Label to the left of the CompositeEntry
254 DurationLabel UnitLabel = new()
255 {
256 Text = ServiceRef.Localizer[Unit.ToString() + "Capitalized"],
257 HorizontalTextAlignment = TextAlignment.Start,
258 VerticalTextAlignment = TextAlignment.Center,
260 LineBreakMode = LineBreakMode.NoWrap,
261 Unit = Unit,
262 };
263
264 DurationEntry.LeftView = UnitLabel;
265
266 this.durationUnits.Remove(Unit); // Remove used units
267
268 DurationEntry.SetBinding(CompositeEntry.IsValidProperty, new Binding(nameof(this.IsValid), source: this));
269
270 DurationEntry.TextChanged += (sender, args) => this.UpdateDuration();
271
272 DurationEntry.Keyboard = Keyboard.Numeric;
273
274
275 // Add delete button to the right of the CompositeEntry
276 ImageButton DeleteButton = new()
277 {
279 PathData = Geometries.CancelPath,
280 VerticalOptions = LayoutOptions.Center,
281 HorizontalOptions = LayoutOptions.End,
282 Command = new RelayCommand(() => this.DeleteUnit(Unit, DurationEntry))
283 };
284
285 DurationEntry.RightView = DeleteButton;
286
287 // Add the CompositeEntry to the Vertical Stack Layout
288 this.durationsContainer.Add(DurationEntry);
289
290 // Sort the entries by unit
291 List<CompositeEntry> SortedDurationEntries = [.. this.durationsContainer.Children
292 .OfType<CompositeEntry>()
293 .OrderBy(x => (x.LeftView as DurationLabel)?.Unit)];
294
295 this.durationsContainer.Clear();
296 foreach (CompositeEntry Entry in SortedDurationEntries)
297 MainThread.BeginInvokeOnMainThread(() => this.durationsContainer.Add(Entry));
298
299 MainThread.BeginInvokeOnMainThread(async () => {
300 await Task.Delay(500);
301 DurationEntry.Focus();
302 });
303 }
304
308 private void DeleteUnit(DurationUnits Unit, CompositeEntry DurationView)
309 {
310 if (!this.durationUnits.Contains(Unit)) // Prevent duplicates from being added
311 this.durationUnits.Add(Unit); // Add back the removed unit
312 this.durationUnits = this.durationUnits.OrderBy(x => x).ToObservableCollection<DurationUnits>();
313
314 this.durationsContainer.Remove(DurationView);
315
316 this.UpdateDuration();
317 }
318
323 [RelayCommand]
324 public async Task<DurationUnits?> OpenDurationPopup()
325 {
326 DurationPopupViewModel ViewModel = new(this.durationUnits);
328
329 return Result;
330 }
331
335 void UpdateDuration()
336 {
337 Duration NewDuration = new();
338 CultureInfo Culture = CultureInfo.InvariantCulture;
339
340 foreach (CompositeEntry DurationEntry in this.durationsContainer.Children.Cast<CompositeEntry>())
341 {
342 DurationLabel UnitLabel = (DurationLabel)DurationEntry.LeftView;
343
344 string Data = DurationEntry.EntryData;
345
346 string FilteredText = new(Data.Where(char.IsDigit).ToArray());
347 if (Data != FilteredText)
348 {
349 DurationEntry.EntryData = FilteredText;
350 Data = FilteredText; // Remove invalid characters
351 }
352
353 if (string.IsNullOrEmpty(Data))
354 Data = "0";
355
356 switch (UnitLabel.Unit)
357 {
358 case DurationUnits.Years:
359 NewDuration.Years = int.Parse(Data, Culture);
360 break;
361 case DurationUnits.Months:
362 NewDuration.Months = int.Parse(Data, Culture);
363 break;
364 case DurationUnits.Days:
365 NewDuration.Days = int.Parse(Data, Culture);
366 break;
367 case DurationUnits.Hours:
368 NewDuration.Hours = int.Parse(Data, Culture);
369 break;
370 case DurationUnits.Minutes:
371 NewDuration.Minutes = int.Parse(Data, Culture);
372 break;
373 case DurationUnits.Seconds:
374 NewDuration.Seconds = int.Parse(Data, Culture);
375 break;
376 }
377 }
378
379 NewDuration.Negation = this.negateCheckBox.IsChecked;
380
381 this.DurationValue = NewDuration;
382 }
383
384 #endregion
385
386 #region Events
387
391 async void AddDurationButton_Clicked()
392 {
393 if (this.durationUnits.Count == 0)
394 return; // No unit available
395
396 DurationUnits? DurationUnit = await this.OpenDurationPopup();
397
398 if (DurationUnit is not DurationUnits Unit)
399 return; // No unit selected or popup canceled
400
401 this.AddUnit(Unit, "");
402 }
403
404 #endregion
405
406 #region Bindable Properties
407
411 public static readonly BindableProperty TitleLabelStyleProperty =
412 BindableProperty.Create(nameof(TitleLabelStyle), typeof(Style), typeof(CompositeInputView));
413
417 public Style TitleLabelStyle
418 {
419 get => (Style)this.GetValue(TitleLabelStyleProperty);
420 set => this.SetValue(TitleLabelStyleProperty, value);
421 }
422
426 public static readonly BindableProperty TitleLabelTextProperty =
427 BindableProperty.Create(nameof(TitleLabelText), typeof(string), typeof(CompositeInputView));
428
432 public string TitleLabelText
433 {
434 get => (string)this.GetValue(TitleLabelTextProperty);
435 set => this.SetValue(TitleLabelTextProperty, value);
436 }
437
441 public static readonly BindableProperty DescriptionLabelStyleProperty =
442 BindableProperty.Create(nameof(DescriptionLabelStyle), typeof(Style), typeof(CompositeInputView));
443
448 {
449 get => (Style)this.GetValue(DescriptionLabelStyleProperty);
450 set => this.SetValue(DescriptionLabelStyleProperty, value);
451 }
452
456 public static readonly BindableProperty DescriptionLabelTextProperty =
457 BindableProperty.Create(nameof(DescriptionLabelText), typeof(string), typeof(CompositeInputView));
458
463 {
464 get => (string)this.GetValue(DescriptionLabelTextProperty);
465 set => this.SetValue(DescriptionLabelTextProperty, value);
466 }
467
471 public static readonly BindableProperty DurationValueProperty = BindableProperty.Create(
472 nameof(DurationValue),
473 typeof(Duration),
475 default(Duration),
476 BindingMode.TwoWay,
477 propertyChanged: OnDurationChanged);
478
479 // IsValid Property
480 public static readonly BindableProperty IsValidProperty =
481 BindableProperty.Create(nameof(IsValid), typeof(bool), typeof(CompositeDurationPicker), true);
482
486 public bool IsValid
487 {
488 get => (bool)this.GetValue(IsValidProperty);
489 set => this.SetValue(IsValidProperty, value);
490 }
491
498 static void OnDurationChanged(BindableObject bindable, object oldValue, object newValue)
499 {
500 if (bindable is CompositeDurationPicker Picker)
501 {
502 List<DurationUnits?> PreviousUnits = [.. Picker.durationsContainer.Children
503 .OfType<CompositeEntry>()
504 .Select(x => (x.LeftView as DurationLabel)?.Unit)];
505
506 Picker.PopulateDurationsContainer(PreviousUnits);
507 }
508 }
509
513 public Duration DurationValue
514 {
515 get => (Duration)this.GetValue(DurationValueProperty);
516 set => this.SetValue(DurationValueProperty, value);
517 }
518 #endregion
519 }
520
521 #region Helper classes
522
526 public enum DurationUnits
527 {
528 Years,
529 Months,
530 Days,
531 Hours,
532 Minutes,
533 Seconds,
534 }
535
539 class DurationLabel : Label, IComparable
540 {
541 private DurationUnits unit;
542
543 public DurationLabel() : base()
544 {
545 }
546
547 public DurationUnits Unit
548 {
549 get => this.unit;
550 set => this.unit = value;
551 }
552
553 public int CompareTo(object? other)
554 {
555 return this.unit.CompareTo((other as DurationLabel)?.Unit);
556 }
557 }
558 #endregion
559}
A strongly-typed resource class, for looking up localized strings, etc.
static string NegateDuration
Looks up a localized string similar to Negative duration: .
static string AddDuration
Looks up a localized string similar to Add duration.
Base class that references services in the app.
Definition: ServiceRef.cs:43
static IPopupService PopupService
Popup service for presenting application popups.
Definition: ServiceRef.cs:142
static IReportingStringLocalizer Localizer
Localization service
Definition: ServiceRef.cs:370
Static class that gives access to app-specific styles
Definition: AppStyles.cs:12
static Style TransparentTemplateButtonBorder
Style for transparent template button border
Definition: AppStyles.cs:548
static Style TransparentTemplateButtonPath
Style for transparent template button path
Definition: AppStyles.cs:572
static Style RegularCompositeEntry
Style for borders in a regular composte entry control.
Definition: AppStyles.cs:325
static Style ImageOnlyButton
Style for buttons containing only an image.
Definition: AppStyles.cs:361
static Style TransparentTemplateButtonLabel
Style for transparent template button label
Definition: AppStyles.cs:560
static Style SectionTitleLabel
Style of section title labels
Definition: AppStyles.cs:205
async Task< DurationUnits?> OpenDurationPopup()
Open QR Popup
static readonly BindableProperty DescriptionLabelStyleProperty
Bindable property for the style applied to the description label.
static readonly BindableProperty TitleLabelTextProperty
Bindable property for the text of the top label.
static readonly BindableProperty TitleLabelStyleProperty
Bindable property for the style applied to the top label.
static readonly BindableProperty DescriptionLabelTextProperty
Bindable Property for the text of the description label.
Duration DurationValue
Gets or sets the text displayed in the entry.
static readonly BindableProperty DurationValueProperty
Bindable property for the text displayed in the entry.
Style DescriptionLabelStyle
Gets or sets the style applied to the description label.
string TitleLabelText
Gets or sets the text applied to the top label.
CompositeDurationPicker()
Constructor for the CompositeDurationPicker. Initializes the layout and the components needed to add ...
string DescriptionLabelText
Gets or sets the text applied to the description label.
bool IsValid
Gets or sets the validity of the DurationPicker
Style TitleLabelStyle
Gets or sets the style applied to the label above the entry.
A customizable entry control that allows setting views on the left and right sides of the entry.
string EntryData
Gets or sets the text displayed in the entry.
Custom class to be able to access the unit used in and entry
TemplatedButton represents a generalization of a Button whose appearance is defined by a ControlTempl...
Static class containing SVG Paths for symbols used in the app.
Definition: Geometries.cs:11
static readonly Geometry ArrowRightPath
Arrow right
Definition: Geometries.cs:516
static readonly Geometry CancelPath
Cancel button glyph
Definition: Geometries.cs:627
DurationUnits
Duration units enumeration
TextAlignment
Text alignment of contents.
Definition: App.xaml.cs:4
Represents a duration value, as defined by the xsd:duration data type: http://www....
Definition: Duration.cs:14