1 /* Copyright (c) 2021 Samsung Electronics Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
7 * http://www.apache.org/licenses/LICENSE-2.0
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
18 using System.Collections;
19 using System.Collections.Specialized;
21 namespace Tizen.NUI.Components
23 internal class ObservableItemSource : IItemSource
25 readonly IEnumerable itemsSource;
26 readonly ICollectionChangedNotifier notifier;
29 public ObservableItemSource(IEnumerable itemSource, ICollectionChangedNotifier changedNotifier)
31 itemsSource = itemSource as IList ?? itemSource as IEnumerable;
32 notifier = changedNotifier;
34 ((INotifyCollectionChanged)itemSource).CollectionChanged += CollectionChanged;
38 internal event NotifyCollectionChangedEventHandler CollectionItemsSourceChanged;
40 public int Count => ItemsCount() + (HasHeader ? 1 : 0) + (HasFooter ? 1 : 0);
42 public bool HasHeader { get; set; }
43 public bool HasFooter { get; set; }
50 public bool IsFooter(int index)
52 return HasFooter && index == Count - 1;
55 public bool IsHeader(int index)
57 return HasHeader && index == 0;
60 public int GetPosition(object item)
62 for (int n = 0; n < ItemsCount(); n++)
64 var elementByIndex = ElementAt(n);
65 var isEqual = elementByIndex == item || (elementByIndex != null && item != null && elementByIndex.Equals(item));
69 return AdjustPositionForHeader(n);
76 public object GetItem(int position)
78 return ElementAt(AdjustIndexForHeader(position));
81 protected virtual void Dispose(bool disposing)
92 ((INotifyCollectionChanged)itemsSource).CollectionChanged -= CollectionChanged;
96 int AdjustIndexForHeader(int index)
98 return index - (HasHeader ? 1 : 0);
101 int AdjustPositionForHeader(int position)
103 return position + (HasHeader ? 1 : 0);
106 void CollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
108 if (Device.IsInvokeRequired)
110 Device.BeginInvokeOnMainThread(() => CollectionChanged(args));
114 CollectionChanged(args);
119 void CollectionChanged(NotifyCollectionChangedEventArgs args)
123 case NotifyCollectionChangedAction.Add:
126 case NotifyCollectionChangedAction.Remove:
129 case NotifyCollectionChangedAction.Replace:
132 case NotifyCollectionChangedAction.Move:
135 case NotifyCollectionChangedAction.Reset:
136 notifier.NotifyDataSetChanged();
139 throw new ArgumentOutOfRangeException(nameof(args));
141 CollectionItemsSourceChanged?.Invoke(this, args);
144 void Move(NotifyCollectionChangedEventArgs args)
146 var count = args.NewItems.Count;
150 // For a single item, we can use NotifyItemMoved and get the animation
151 notifier.NotifyItemMoved(this, AdjustPositionForHeader(args.OldStartingIndex), AdjustPositionForHeader(args.NewStartingIndex));
155 var start = AdjustPositionForHeader(Math.Min(args.OldStartingIndex, args.NewStartingIndex));
156 var end = AdjustPositionForHeader(Math.Max(args.OldStartingIndex, args.NewStartingIndex) + count);
157 notifier.NotifyItemRangeChanged(this, start, end);
160 void Add(NotifyCollectionChangedEventArgs args)
162 var startIndex = args.NewStartingIndex > -1 ? args.NewStartingIndex : IndexOf(args.NewItems[0]);
163 startIndex = AdjustPositionForHeader(startIndex);
164 var count = args.NewItems.Count;
168 notifier.NotifyItemInserted(this, startIndex);
172 notifier.NotifyItemRangeInserted(this, startIndex, count);
175 void Remove(NotifyCollectionChangedEventArgs args)
177 var startIndex = args.OldStartingIndex;
181 // INCC implementation isn't giving us enough information to know where the removed items were in the
182 // collection. So the best we can do is a NotifyDataSetChanged()
183 notifier.NotifyDataSetChanged();
187 startIndex = AdjustPositionForHeader(startIndex);
189 // If we have a start index, we can be more clever about removing the item(s) (and get the nifty animations)
190 int count = args.OldItems?.Count ?? 0;
194 notifier.NotifyItemRemoved(this, startIndex);
198 notifier.NotifyItemRangeRemoved(this, startIndex, count);
201 void Replace(NotifyCollectionChangedEventArgs args)
203 var newItems = args.NewItems;
204 var oldItems = args.OldItems;
205 if (newItems == null || oldItems == null)
209 var startIndex = args.NewStartingIndex > -1 ? args.NewStartingIndex : IndexOf(args.NewItems[0]);
210 startIndex = AdjustPositionForHeader(startIndex);
212 int newCount = newItems.Count;
213 int oldCount = oldItems.Count;
214 if (newCount == oldCount)
216 // We are replacing one set of items with a set of equal size; we can do a simple item or range
217 // notification to the adapter
220 notifier.NotifyItemChanged(this, startIndex);
224 notifier.NotifyItemRangeChanged(this, startIndex, newCount);
230 // The original and replacement sets are of unequal size; this means that everything currently in view will
231 // have to be updated. So we just have to use NotifyDataSetChanged and let the RecyclerView update everything
232 notifier.NotifyDataSetChanged();
235 internal int ItemsCount()
237 if (itemsSource is IList list)
241 foreach (var item in itemsSource)
246 internal object ElementAt(int index)
248 if (itemsSource is IList list)
252 foreach (var item in itemsSource)
262 internal int IndexOf(object item)
264 if (itemsSource is IList list)
265 return list.IndexOf(item);
268 foreach (var i in itemsSource)