public static bool OnlyContains <TSection, THeader, TItem, TKey>( this Diffing <TSection, THeader, TItem, TKey> .Changeset changeset, int insertedSections = 0, int deletedSections = 0, int movedSections = 0, int updatedSections = 0, int insertedItems = 0, int deletedItems = 0, int movedItems = 0, int updatedItems = 0 ) where TKey : IEquatable <TKey> where TSection : IAnimatableSectionModel <THeader, TItem, TKey>, new() where THeader : IDiffable <TKey> where TItem : IDiffable <TKey>, IEquatable <TItem> { if (changeset.InsertedSections.Count != insertedSections) { return(false); } if (changeset.DeletedSections.Count != deletedSections) { return(false); } if (changeset.MovedSections.Count != movedSections) { return(false); } if (changeset.UpdatedSections.Count != updatedSections) { return(false); } if (changeset.InsertedItems.Count != insertedItems) { return(false); } if (changeset.DeletedItems.Count != deletedItems) { return(false); } if (changeset.MovedItems.Count != movedItems) { return(false); } if (changeset.UpdatedItems.Count != updatedItems) { return(false); } return(true); }
public static IObserver <IEnumerable <TSection> > AnimateSections <TSection, THeader, TModel, TKey>( this IReactive <UITableView> reactive, BaseTableViewSource <TSection, THeader, TModel> dataSource) where TKey : IEquatable <TKey> where TSection : IAnimatableSectionModel <THeader, TModel, TKey>, new() where TModel : IDiffable <TKey>, IEquatable <TModel> where THeader : IDiffable <TKey> { return(Observer.Create <IEnumerable <TSection> >(finalSections => { var initialSections = dataSource.Sections; if (initialSections == null || initialSections.Count == 0) { dataSource.SetSections(finalSections); reactive.Base.ReloadData(); return; } // if view is not in view hierarchy, performing batch updates will crash the app if (reactive.Base.Window == null) { dataSource.SetSections(finalSections); reactive.Base.ReloadData(); return; } var stopwatchProvider = Mvx.Resolve <IStopwatchProvider>(); var stopwatch = stopwatchProvider.Create(MeasuredOperation.Diffing); stopwatch.Start(); var diff = new Diffing <TSection, THeader, TModel, TKey>(initialSections, finalSections); var changeset = diff.ComputeDifferences(); stopwatch.Stop(); // The changesets have to be applied one after another. Not in one transaction. // iOS is picky about the changes which can happen in a single transaction. // Don't put BeginUpdates() ... EndUpdates() around the foreach, it has to stay this way, // otherwise the app might crash from time to time. foreach (var difference in changeset) { reactive.Base.BeginUpdates(); dataSource.SetSections(difference.FinalSections); reactive.Base.performChangesetUpdates(difference); reactive.Base.EndUpdates(); foreach (var section in difference.UpdatedSections) { if (reactive.Base.GetHeaderView(section) is BaseTableHeaderFooterView <THeader> headerView) { headerView.Item = difference.FinalSections[section].Header; } } } })); }
private static void performChangesetUpdates <TSection, THeader, TModel, TKey>( this UITableView tableView, Diffing <TSection, THeader, TModel, TKey> .Changeset changes) where TKey : IEquatable <TKey> where TSection : IAnimatableSectionModel <THeader, TModel, TKey>, new() where TModel : IDiffable <TKey>, IEquatable <TModel> where THeader : IDiffable <TKey> { NSIndexSet newIndexSet(List <int> indexes) { var indexSet = new NSMutableIndexSet(); foreach (var i in indexes) { indexSet.Add((nuint)i); } return(indexSet as NSIndexSet); } tableView.DeleteSections(newIndexSet(changes.DeletedSections), UITableViewRowAnimation.Fade); // Updated sections doesn't mean reload entire section, somebody needs to update the section view manually // otherwise all cells will be reloaded for nothing. tableView.InsertSections(newIndexSet(changes.InsertedSections), UITableViewRowAnimation.Fade); foreach (var(from, to) in changes.MovedSections) { tableView.MoveSection(from, to); } tableView.DeleteRows( changes.DeletedItems.Select(item => NSIndexPath.FromRowSection(item.itemIndex, item.sectionIndex)).ToArray(), UITableViewRowAnimation.Top ); tableView.InsertRows( changes.InsertedItems.Select(item => NSIndexPath.FromItemSection(item.itemIndex, item.sectionIndex)).ToArray(), UITableViewRowAnimation.Automatic ); tableView.ReloadRows( changes.UpdatedItems.Select(item => NSIndexPath.FromRowSection(item.itemIndex, item.sectionIndex)) .ToArray(), // No animation so it doesn't fade showing the cells behind it UITableViewRowAnimation.None ); foreach (var(from, to) in changes.MovedItems) { tableView.MoveRow( NSIndexPath.FromRowSection(from.itemIndex, from.sectionIndex), NSIndexPath.FromRowSection(to.itemIndex, to.sectionIndex) ); } }
public static List <TSection> Apply <TSection, THeader, TElement, TKey>( this Diffing <TSection, THeader, TElement, TKey> .Changeset changeset, List <TSection> original ) where TKey : IEquatable <TKey> where TSection : IAnimatableSectionModel <THeader, TElement, TKey>, new() where THeader : IDiffable <TKey> where TElement : IDiffable <TKey>, IEquatable <TElement> { var afterDeletesAndUpdates = changeset.applyDeletesAndUpdates(original); var afterSectionMovesAndInserts = changeset.applySectionMovesAndInserts(afterDeletesAndUpdates); var afterItemInsertsAndMoves = changeset.applyItemInsertsAndMoves(afterSectionMovesAndInserts); return(afterItemInsertsAndMoves); }
private static List <TSection> applyDeletesAndUpdates <TSection, THeader, TElement, TKey>( this Diffing <TSection, THeader, TElement, TKey> .Changeset changeset, List <TSection> original ) where TKey : IEquatable <TKey> where TSection : IAnimatableSectionModel <THeader, TElement, TKey>, new() where THeader : IDiffable <TKey> where TElement : IDiffable <TKey>, IEquatable <TElement> { var resultAfterDeletesAndUpdates = SectionModelTypeWrapper <TSection, THeader, TElement> .Wrap(original); foreach (var index in changeset.UpdatedItems) { resultAfterDeletesAndUpdates[index.sectionIndex].Items[index.itemIndex].Updated = true; } foreach (var index in changeset.DeletedItems) { resultAfterDeletesAndUpdates[index.sectionIndex].Items[index.itemIndex].Deleted = true; } foreach (var section in changeset.DeletedSections) { resultAfterDeletesAndUpdates[section].Deleted = true; } resultAfterDeletesAndUpdates = resultAfterDeletesAndUpdates.Where(section => !section.Deleted).ToList(); for (int sectionIndex = 0; sectionIndex < resultAfterDeletesAndUpdates.Count; sectionIndex++) { var section = resultAfterDeletesAndUpdates[sectionIndex]; section.Items = section.Items.Where(item => !item.Deleted).ToList(); for (int itemIndex = 0; itemIndex < section.Items.Count; itemIndex++) { var item = section.Items[itemIndex]; if (item.Updated) { section.Items[itemIndex] = new ItemModelTypeWrapper <TElement>(changeset.FinalSections[sectionIndex].Items[itemIndex]); } } } return(SectionModelTypeWrapper <TSection, THeader, TElement> .Unwrap(resultAfterDeletesAndUpdates)); }
private static List <TSection> applyItemInsertsAndMoves <TSection, THeader, TElement, TKey>( this Diffing <TSection, THeader, TElement, TKey> .Changeset changeset, List <TSection> original ) where TKey : IEquatable <TKey> where TSection : IAnimatableSectionModel <THeader, TElement, TKey>, new() where THeader : IDiffable <TKey> where TElement : IDiffable <TKey>, IEquatable <TElement> { var resultAfterInsertsAndMoves = original; var sourceIndexesThatShouldBeMoved = new HashSet <ItemPath>(changeset.MovedItems.Select(item => item.Item1).ToList()); var destinationToSourceMapping = new Dictionary <ItemPath, ItemPath>(); foreach (var movedItem in changeset.MovedItems) { destinationToSourceMapping[movedItem.Item2] = movedItem.Item1; } var insertedItemPaths = new HashSet <ItemPath>(changeset.InsertedItems); var insertedPerSection = Enumerable.Repeat(0, original.Count).ToList(); var movedInSection = Enumerable.Repeat(0, original.Count).ToList(); var movedOutSection = Enumerable.Repeat(0, original.Count).ToList(); foreach (var insertedItemPath in changeset.InsertedItems) { insertedPerSection[insertedItemPath.sectionIndex] += 1; } foreach (var moveItem in changeset.MovedItems) { movedInSection[moveItem.Item2.sectionIndex] += 1; movedOutSection[moveItem.Item1.sectionIndex] += 1; } for (int sectionIndex = 0; sectionIndex < resultAfterInsertsAndMoves.Count; sectionIndex++) { var section = resultAfterInsertsAndMoves[sectionIndex]; var originalItems = section.Items; var nextUntouchedSourceItemIndex = -1; bool findNextUntouchedSourceItem() { nextUntouchedSourceItemIndex += 1; while (nextUntouchedSourceItemIndex < section.Items.Count && sourceIndexesThatShouldBeMoved.Contains(new ItemPath(sectionIndex: sectionIndex, itemIndex: nextUntouchedSourceItemIndex))) { nextUntouchedSourceItemIndex += 1; } return(nextUntouchedSourceItemIndex < section.Items.Count); } var totalCount = section.Items.Count + insertedPerSection[sectionIndex] + movedInSection[sectionIndex] - movedOutSection[sectionIndex]; var resultItems = new List <TElement>(); for (int index = 0; index < totalCount; index++) { var itemPath = new ItemPath(sectionIndex, index); if (insertedItemPaths.Contains(itemPath)) { resultItems.Add(changeset.FinalSections[itemPath.sectionIndex].Items[itemPath.itemIndex]); } else { if (destinationToSourceMapping.ContainsKey(itemPath)) { var sourceIndex = destinationToSourceMapping[itemPath]; resultItems.Add(original[sourceIndex.sectionIndex].Items[sourceIndex.itemIndex]); } else { if (!findNextUntouchedSourceItem()) { throw new Exception("Oooops, wrong commands."); } resultItems.Add(originalItems[nextUntouchedSourceItemIndex]); } } } var newSection = new TSection(); newSection.Initialize(section.Header, resultItems); resultAfterInsertsAndMoves[sectionIndex] = newSection; } return(resultAfterInsertsAndMoves); }
private static List <TSection> applySectionMovesAndInserts <TSection, THeader, TElement, TKey>( this Diffing <TSection, THeader, TElement, TKey> .Changeset changeset, List <TSection> original ) where TKey : IEquatable <TKey> where TSection : IAnimatableSectionModel <THeader, TElement, TKey>, new() where THeader : IDiffable <TKey> where TElement : IDiffable <TKey>, IEquatable <TElement> { var sourceSectionIndexes = new HashSet <int>(changeset.MovedSections.Select(movement => movement.Item1)); var destinationToSourceMapping = new Dictionary <int, int>(); foreach (var movement in changeset.MovedSections) { destinationToSourceMapping[movement.Item2] = movement.Item1; } var insertedSectionsIndexes = new HashSet <int>(changeset.InsertedSections); var nextUntouchedSourceSectionIndex = -1; bool findNextUntouchedSourceSection() { nextUntouchedSourceSectionIndex += 1; while (nextUntouchedSourceSectionIndex < original.Count && sourceSectionIndexes.Contains(nextUntouchedSourceSectionIndex)) { nextUntouchedSourceSectionIndex += 1; } return(nextUntouchedSourceSectionIndex < original.Count); } var totalCount = original.Count + changeset.InsertedSections.Count; var results = new List <TSection>(); for (int index = 0; index < totalCount; index++) { if (insertedSectionsIndexes.Contains(index)) { results.Add(changeset.FinalSections[index]); } else { if (destinationToSourceMapping.ContainsKey(index)) { var sourceIndex = destinationToSourceMapping[index]; results.Add(original[sourceIndex]); } else { if (!findNextUntouchedSourceSection()) { throw new Exception("Oooops, wrong commands."); } results.Add(original[nextUntouchedSourceSectionIndex]); } } } return(results); }