using System; using System.Collections.Generic; namespace AlicizaX.UI { internal interface IItemRenderCacheOwner { void ReleaseAllItemRenders(); } public class Adapter : IAdapter, IItemRenderCacheOwner where T : ISimpleViewData { private sealed class ItemRenderEntry { public ItemRenderEntry(string viewName, IItemRender itemRender) { ViewName = viewName; ItemRender = itemRender; } public string ViewName { get; } public IItemRender ItemRender { get; } } protected RecyclerView recyclerView; protected List list; protected int choiceIndex = -1; private readonly Dictionary itemRenderDefinitions = new(StringComparer.Ordinal); private readonly Dictionary itemRenders = new(); private ItemRenderResolver.ItemRenderDefinition defaultItemRenderDefinition; public int ChoiceIndex { get => choiceIndex; set => SetChoiceIndex(value); } internal Action OnChoiceIndexChanged; public Adapter(RecyclerView recyclerView) : this(recyclerView, new List()) { } public Adapter(RecyclerView recyclerView, List list) { this.recyclerView = recyclerView; this.list = list ?? new List(); } public virtual int GetItemCount() { return list == null ? 0 : list.Count; } public virtual int GetRealCount() { return GetItemCount(); } public virtual string GetViewName(int index) { return ""; } public virtual void OnBindViewHolder(ViewHolder viewHolder, int index) { if (viewHolder == null) return; if (!TryGetBindData(index, out var data)) return; string viewName = GetViewName(index); viewHolder.AdvanceBindingVersion(); viewHolder.DataIndex = index; if (TryGetOrCreateItemRender(viewHolder, viewName, out var itemRender)) { if (itemRender is ITypedItemRender typedItemRender) { typedItemRender.BindData(data, index); } else { itemRender.Bind(data, index); } itemRender.UpdateSelection(index == choiceIndex); return; } string resolvedViewName = string.IsNullOrEmpty(viewName) ? "" : viewName; throw new InvalidOperationException( $"RecyclerView item render is missing for view '{resolvedViewName}'. Holder='{viewHolder.GetType().Name}', Adapter='{GetType().Name}'."); } public virtual void OnRecycleViewHolder(ViewHolder viewHolder) { if (viewHolder == null) return; if (TryGetItemRender(viewHolder, out var itemRender)) { itemRender.UpdateSelection(false); itemRender.Unbind(); } } public virtual void NotifyDataChanged() { CoerceChoiceIndex(); recyclerView.RequestLayout(); recyclerView.Refresh(); } public virtual void SetList(List list) { this.list = list ?? new List(); recyclerView.Reset(); NotifyDataChanged(); } public virtual void NotifyItemChanged(int index, bool relayout = false) { if (index < 0 || index >= GetRealCount()) { return; } CoerceChoiceIndex(); if (relayout) { recyclerView.RequestLayout(); recyclerView.Refresh(); return; } recyclerView.RebindVisibleDataIndex(index); } public virtual void NotifyItemRangeChanged(int index, int count, bool relayout = false) { if (count <= 0 || index < 0 || index >= GetRealCount()) { return; } CoerceChoiceIndex(); if (relayout) { recyclerView.RequestLayout(); recyclerView.Refresh(); return; } recyclerView.RebindVisibleDataRange(index, count); } public virtual void NotifyItemInserted(int index) { CoerceChoiceIndex(); recyclerView.RequestLayout(); recyclerView.Refresh(); } public virtual void NotifyItemRangeInserted(int index, int count) { if (count <= 0) { return; } CoerceChoiceIndex(); recyclerView.RequestLayout(); recyclerView.Refresh(); } public virtual void NotifyItemRemoved(int index) { CoerceChoiceIndex(); recyclerView.RequestLayout(); recyclerView.Refresh(); } public virtual void NotifyItemRangeRemoved(int index, int count) { if (count <= 0) { return; } CoerceChoiceIndex(); recyclerView.RequestLayout(); recyclerView.Refresh(); } public void RegisterItemRender(string viewName = "") where TItemRender : ItemRenderBase { RegisterItemRender(typeof(TItemRender), viewName); } public void RegisterItemRender(Type itemRenderType, string viewName = "") { var definition = ItemRenderResolver.GetOrCreate(itemRenderType); if (string.IsNullOrEmpty(viewName)) { ReleaseCachedItemRenders(string.Empty); defaultItemRenderDefinition = definition; return; } ReleaseCachedItemRenders(viewName); itemRenderDefinitions[viewName] = definition; } public bool UnregisterItemRender(string viewName = "") { if (string.IsNullOrEmpty(viewName)) { bool hadDefault = defaultItemRenderDefinition != null; defaultItemRenderDefinition = null; if (hadDefault) { ReleaseCachedItemRenders(string.Empty); } return hadDefault; } bool removed = itemRenderDefinitions.Remove(viewName); if (removed) { ReleaseCachedItemRenders(viewName); } return removed; } public void ClearItemRenderRegistrations() { ReleaseAllItemRenders(); itemRenderDefinitions.Clear(); defaultItemRenderDefinition = null; } public T GetData(int index) { if (index < 0 || index >= GetItemCount()) return default; return list[index]; } public void Add(T item) { if (list == null) { list = new List(); } list.Add(item); NotifyItemInserted(list.Count - 1); } public void AddRange(IEnumerable collection) { if (collection == null) { return; } int startIndex = list.Count; list.AddRange(collection); if (collection is ICollection itemCollection) { NotifyItemRangeInserted(startIndex, itemCollection.Count); return; } NotifyDataChanged(); } public void Insert(int index, T item) { list.Insert(index, item); NotifyItemInserted(index); } public void InsertRange(int index, IEnumerable collection) { if (collection == null) { return; } list.InsertRange(index, collection); if (collection is ICollection itemCollection) { NotifyItemRangeInserted(index, itemCollection.Count); return; } NotifyDataChanged(); } public void Remove(T item) { int index = list.IndexOf(item); RemoveAt(index); } public void RemoveAt(int index) { if (index < 0 || index >= GetItemCount()) return; list.RemoveAt(index); NotifyItemRemoved(index); } public void RemoveRange(int index, int count) { list.RemoveRange(index, count); NotifyItemRangeRemoved(index, count); } public void RemoveAll(Predicate match) { list.RemoveAll(match); NotifyDataChanged(); } public void Clear() { if (list == null || list.Count == 0) { return; } int count = list.Count; list.Clear(); NotifyItemRangeRemoved(0, count); } public void Reverse(int index, int count) { list.Reverse(index, count); NotifyDataChanged(); } public void Reverse() { list.Reverse(); NotifyDataChanged(); } public void Sort(Comparison comparison) { list.Sort(comparison); NotifyDataChanged(); } protected void SetChoiceIndex(int index) { int itemCount = GetRealCount(); if (itemCount <= 0) { index = -1; } else if (index >= itemCount) { index = itemCount - 1; } else if (index < -1) { index = -1; } if (index == choiceIndex) return; if (choiceIndex != -1 && TryGetViewHolder(choiceIndex, out var oldHolder)) { UpdateSelectionState(oldHolder, false); } choiceIndex = index; if (choiceIndex != -1 && TryGetViewHolder(choiceIndex, out var newHolder)) { UpdateSelectionState(newHolder, true); } OnChoiceIndexChanged?.Invoke(choiceIndex); } protected virtual bool TryGetBindData(int index, out T data) { if (list == null || index < 0 || index >= list.Count) { data = default; return false; } data = list[index]; return true; } private bool TryGetViewHolder(int index, out ViewHolder viewHolder) { viewHolder = recyclerView.ViewProvider.GetViewHolderByDataIndex(index); return viewHolder != null; } private bool TryGetItemRenderDefinition(string viewName, out ItemRenderResolver.ItemRenderDefinition definition) { if (!string.IsNullOrEmpty(viewName) && itemRenderDefinitions.TryGetValue(viewName, out definition)) { return true; } definition = defaultItemRenderDefinition; return definition != null; } private void UpdateSelectionState(ViewHolder viewHolder, bool selected) { if (viewHolder == null) { return; } if (TryGetItemRender(viewHolder, out var itemRender)) { itemRender.UpdateSelection(selected); return; } string viewName = string.IsNullOrEmpty(viewHolder.Name) ? "" : viewHolder.Name; throw new InvalidOperationException( $"RecyclerView item render is missing for selection update. View='{viewName}', Holder='{viewHolder.GetType().Name}', Adapter='{GetType().Name}'."); } private bool TryGetItemRender(ViewHolder viewHolder, out IItemRender itemRender) { if (viewHolder != null && itemRenders.TryGetValue(viewHolder, out var entry) && entry.ItemRender != null) { itemRender = entry.ItemRender; return true; } itemRender = null; return false; } private static void ReleaseItemRender(ItemRenderEntry entry) { if (entry?.ItemRender == null) { return; } entry.ItemRender.Unbind(); if (entry.ItemRender is ItemRenderBase itemRender) { itemRender.Detach(); } } private bool TryGetOrCreateItemRender(ViewHolder viewHolder, string viewName, out IItemRender itemRender) { if (viewHolder == null) { itemRender = null; return false; } if (itemRenders.TryGetValue(viewHolder, out var entry)) { if (entry.ItemRender != null && string.Equals(entry.ViewName, viewName, StringComparison.Ordinal)) { itemRender = entry.ItemRender; return true; } ReleaseItemRender(entry); viewHolder.Destroyed -= OnViewHolderDestroyed; itemRenders.Remove(viewHolder); } if (!TryGetItemRenderDefinition(viewName, out var definition)) { itemRender = null; return false; } itemRender = definition.Create(viewHolder, recyclerView, this, SetChoiceIndex); itemRenders[viewHolder] = new ItemRenderEntry(viewName, itemRender); viewHolder.Destroyed += OnViewHolderDestroyed; return true; } void IItemRenderCacheOwner.ReleaseAllItemRenders() { ReleaseAllItemRenders(); } private void ReleaseAllItemRenders() { foreach (var pair in itemRenders) { ReleaseItemRender(pair.Value); if (pair.Key != null) { pair.Key.Destroyed -= OnViewHolderDestroyed; } } itemRenders.Clear(); } private void ReleaseCachedItemRenders(string viewName) { if (itemRenders.Count == 0) { return; } List viewHoldersToRemove = null; foreach (var pair in itemRenders) { if (!string.Equals(pair.Value.ViewName, viewName, StringComparison.Ordinal)) { continue; } ReleaseItemRender(pair.Value); pair.Key.Destroyed -= OnViewHolderDestroyed; viewHoldersToRemove ??= new List(); viewHoldersToRemove.Add(pair.Key); } if (viewHoldersToRemove == null) { return; } for (int i = 0; i < viewHoldersToRemove.Count; i++) { itemRenders.Remove(viewHoldersToRemove[i]); } } private void OnViewHolderDestroyed(ViewHolder viewHolder) { if (viewHolder == null) { return; } viewHolder.Destroyed -= OnViewHolderDestroyed; if (itemRenders.TryGetValue(viewHolder, out var entry)) { ReleaseItemRender(entry); itemRenders.Remove(viewHolder); } } private void CoerceChoiceIndex() { int itemCount = GetRealCount(); if (itemCount <= 0) { SetChoiceIndex(-1); return; } if (choiceIndex >= itemCount) { SetChoiceIndex(itemCount - 1); } } } }