Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
TemplatedSelector.cs
2using System.Collections.ObjectModel;
3using System.Collections.Specialized;
4using Microsoft.Maui.Controls;
5
7{
8 public enum SelectorSelectionMode
9 {
10 Single,
11 Multiple
12 }
13
14 public class TemplatedSelector : StackLayout
15 {
16 public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create(
17 nameof(ItemsSource), typeof(IEnumerable), typeof(TemplatedSelector), propertyChanged: OnItemsSourceChanged);
18
19 public static readonly BindableProperty ItemTemplateProperty = BindableProperty.Create(
20 nameof(ItemTemplate), typeof(DataTemplate), typeof(TemplatedSelector));
21
22 public static readonly BindableProperty SelectionModeProperty = BindableProperty.Create(
23 nameof(SelectionMode), typeof(SelectorSelectionMode), typeof(TemplatedSelector), SelectorSelectionMode.Single);
24
25 public static readonly BindableProperty SelectedItemProperty = BindableProperty.Create(
26 nameof(SelectedItem), typeof(object), typeof(TemplatedSelector), defaultBindingMode: BindingMode.TwoWay);
27
28 public static readonly BindableProperty SelectedItemsProperty = BindableProperty.Create(
29 nameof(SelectedItems), typeof(IList), typeof(TemplatedSelector), defaultValue: new ObservableCollection<object>(), defaultBindingMode: BindingMode.TwoWay, propertyChanged: OnSelectedItemsChanged);
30 public static readonly BindableProperty SelectOnTapProperty = BindableProperty.Create(
31 nameof(SelectOnTap), typeof(bool), typeof(TemplatedSelector), false);
32
33 public bool SelectOnTap
34 {
35 get => (bool)this.GetValue(SelectOnTapProperty);
36 set => this.SetValue(SelectOnTapProperty, value);
37 }
38 public IEnumerable ItemsSource
39 {
40 get => (IEnumerable)this.GetValue(ItemsSourceProperty);
41 set => this.SetValue(ItemsSourceProperty, value);
42 }
43
44 public DataTemplate ItemTemplate
45 {
46 get => (DataTemplate)this.GetValue(ItemTemplateProperty);
47 set => this.SetValue(ItemTemplateProperty, value);
48 }
49
50 public SelectorSelectionMode SelectionMode
51 {
52 get => (SelectorSelectionMode)this.GetValue(SelectionModeProperty);
53 set => this.SetValue(SelectionModeProperty, value);
54 }
55
56 public object SelectedItem
57 {
58 get => this.GetValue(SelectedItemProperty);
59 set => this.SetValue(SelectedItemProperty, value);
60 }
61
62 public IList SelectedItems
63 {
64 get => (IList)this.GetValue(SelectedItemsProperty);
65 set => this.SetValue(SelectedItemsProperty, value);
66 }
67
68 private bool syncingSelection = false;
69
70 public TemplatedSelector()
71 {
72 this.Orientation = StackOrientation.Vertical;
73 TryAttachSelectedItemsHandler(this.SelectedItems);
74 }
75
76 private static void OnSelectedItemsChanged(BindableObject bindable, object oldValue, object newValue)
77 {
78 if (bindable is TemplatedSelector selector)
79 {
80 selector.TryDetachSelectedItemsHandler(oldValue as IList);
81 selector.TryAttachSelectedItemsHandler(newValue as IList);
82 selector.SyncSelectionFromSelectedItems();
83 }
84 }
85
86 private void TryAttachSelectedItemsHandler(IList? list)
87 {
88 if (list is INotifyCollectionChanged incc)
89 {
90 incc.CollectionChanged += this.SelectedItems_CollectionChanged;
91 }
92 }
93
94 private void TryDetachSelectedItemsHandler(IList? list)
95 {
96 if (list is INotifyCollectionChanged incc)
97 {
98 incc.CollectionChanged -= this.SelectedItems_CollectionChanged;
99 }
100 }
101
102 private void SelectedItems_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
103 {
104 this.SyncSelectionFromSelectedItems();
105 }
106
107 private static void OnItemsSourceChanged(BindableObject bindable, object oldValue, object newValue)
108 {
109 if (bindable is TemplatedSelector selector)
110 {
111 selector.BuildItems();
112 }
113 }
114
115 private void BuildItems()
116 {
117 this.Children.Clear();
118 if (this.ItemsSource == null || this.ItemTemplate == null)
119 return;
120 foreach (object Item in this.ItemsSource)
121 {
123 if (Item is NeuroAccessMaui.Core.ISelectable AlreadySelectable)
124 {
125 Selectable = AlreadySelectable;
126 }
127 else
128 {
129 // Prefer typed wrapper when working with KYC options so compiled bindings match
130 if (Item is NeuroAccessMaui.Services.Kyc.Models.KycOption kycOption)
131 {
133 kycOption,
134 this.SelectedItems?.Contains(kycOption) == true,
135 o => this.ToggleSelection(o));
136 Selectable = option;
137 }
138 else
139 {
140 SelectableOption<object> option = new SelectableOption<object>(Item, this.SelectedItems?.Contains(Item) == true, o => this.ToggleSelection(o));
141 Selectable = option;
142 }
143 }
144 if (Selectable is SelectableOption<object> Option2)
145 {
146 Option2.IsSelectedChanged += this.OnChildIsSelectedChanged;
147 }
148 else if (Selectable is SelectableKycOption Option3)
149 {
150 Option3.IsSelectedChanged += this.OnChildIsSelectedChanged;
151 }
152 View Content = (View)this.ItemTemplate.CreateContent();
153 Content.BindingContext = Selectable;
154 if (this.SelectOnTap)
155 {
156 TapGestureRecognizer TapGesture = new TapGestureRecognizer();
157 TapGesture.Tapped += (s, e) => this.ToggleSelection(Selectable);
158 Content.GestureRecognizers.Add(TapGesture);
159 }
160 this.Children.Add(Content);
161 }
162 // After (re)building, sync selection state visually
163 this.SyncSelectionFromSelectedItems();
164 }
165
166 private void OnChildIsSelectedChanged(object? sender, EventArgs e)
167 {
168 if (this.syncingSelection)
169 {
170 return; // avoid feedback loop during sync
171 }
172 if (sender is NeuroAccessMaui.Core.ISelectable ChangedSelectable)
173 {
174 object Item = GetUnderlyingItem(ChangedSelectable);
175 if (this.SelectionMode == SelectorSelectionMode.Single && ChangedSelectable.IsSelected)
176 {
177 foreach (View ChildView in this.Children.Cast<View>())
178 {
179 if (ChildView.BindingContext is NeuroAccessMaui.Core.ISelectable OtherSelectable && !object.ReferenceEquals(OtherSelectable, ChangedSelectable))
180 {
181 OtherSelectable.IsSelected = false;
182 }
183 }
184 this.SelectedItem = Item;
185 if (this.SelectedItems != null)
186 {
187 this.SelectedItems.Clear();
188 this.SelectedItems.Add(this.SelectedItem);
189 }
190 }
191 else if (this.SelectionMode == SelectorSelectionMode.Multiple)
192 {
193 if (ChangedSelectable.IsSelected)
194 {
195 if (!this.SelectedItems.Contains(Item))
196 {
197 this.SelectedItems.Add(Item);
198 }
199 }
200 else
201 {
202 if (this.SelectedItems.Contains(Item))
203 {
204 this.SelectedItems.Remove(Item);
205 }
206 }
207 }
208 }
209 }
210
211 private void ToggleSelection(object selectableOptionObj)
212 {
213 NeuroAccessMaui.Core.ISelectable? Selectable = selectableOptionObj as NeuroAccessMaui.Core.ISelectable;
214 if (Selectable == null)
215 {
216 return;
217 }
218 object Item = GetUnderlyingItem(Selectable);
219 if (this.SelectionMode == SelectorSelectionMode.Single)
220 {
221 // Deselect all others
222 foreach (View ChildView in this.Children.Cast<View>())
223 {
224 if (ChildView.BindingContext is NeuroAccessMaui.Core.ISelectable OtherSelectable && !object.ReferenceEquals(OtherSelectable, Selectable))
225 {
226 OtherSelectable.IsSelected = false;
227 }
228 }
229 // Select this one
230 Selectable.IsSelected = true;
231 this.SelectedItem = Item;
232 // Ensure only one in SelectedItems
233 if (this.SelectedItems != null)
234 {
235 this.SelectedItems.Clear();
236 this.SelectedItems.Add(Item);
237 }
238 }
239 else
240 {
241 Selectable.IsSelected = !Selectable.IsSelected;
242 if (Selectable.IsSelected)
243 {
244 if (!this.SelectedItems.Contains(Item))
245 {
246 this.SelectedItems.Add(Item);
247 }
248 }
249 else
250 {
251 if (this.SelectedItems.Contains(Item))
252 {
253 this.SelectedItems.Remove(Item);
254 }
255 }
256 }
257 }
258
259 private void SyncSelectionFromSelectedItems()
260 {
261 try
262 {
263 this.syncingSelection = true;
264 HashSet<object> selected = new HashSet<object>(this.SelectedItems?.Cast<object>() ?? Enumerable.Empty<object>());
265 foreach (View child in this.Children)
266 {
267 if (child.BindingContext is NeuroAccessMaui.Core.ISelectable selectable)
268 {
269 object item = GetUnderlyingItem(selectable);
270 bool shouldSelect = selected.Contains(item);
271 selectable.IsSelected = shouldSelect;
272 }
273 }
274 }
275 finally
276 {
277 this.syncingSelection = false;
278 }
279 }
280
281 private static object GetUnderlyingItem(NeuroAccessMaui.Core.ISelectable selectable)
282 {
283 if (selectable is SelectableKycOption kycOption)
284 {
285 return kycOption.Item;
286 }
287 if (selectable is SelectableOption<object> genericOption)
288 {
289 return genericOption.Item;
290 }
291 return selectable;
292 }
293
294 // SelectableOption<T> is now used for item context
295 }
296}