chromium/chrome/browser/download/internal/android/java/src/org/chromium/chrome/browser/download/home/list/mutator/DateOrderedListMutator.java

// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.chrome.browser.download.home.list.mutator;

import org.chromium.chrome.browser.download.home.JustNowProvider;
import org.chromium.chrome.browser.download.home.filter.OfflineItemFilterObserver;
import org.chromium.chrome.browser.download.home.filter.OfflineItemFilterSource;
import org.chromium.chrome.browser.download.home.list.ListItem;
import org.chromium.chrome.browser.download.home.list.ListItem.OfflineItemListItem;
import org.chromium.chrome.browser.download.home.list.ListItemModel;
import org.chromium.components.offline_items_collection.OfflineItem;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;

/**
 * A class responsible for turning a {@link Collection} of {@link OfflineItem}s into a list meant
 * to be displayed in the download home UI.  This list has the following properties:
 * - Converts changes in the form of {@link Collection}s to delta changes on the list.
 * - Sorting, and adding headers is done by the downstream {@link ListConsumer}s.
 */
public class DateOrderedListMutator implements OfflineItemFilterObserver {
    /**
     * Handles pagination for the list and adds a pagination header at the end, if the list is
     * longer than the desired length. Tracks the number of pages currently being displayed to the
     * user.
     */
    public interface ListPaginator extends ListConsumer {
        /**
         * Increments the currently displayed page count. Called when the pagination header is
         * clicked.
         */
        void loadMorePages();

        /**
         * Resets the pagination tracking. To be called when the filter type of the list is changed.
         */
        void reset();
    }

    private final OfflineItemFilterSource mSource;
    private final JustNowProvider mJustNowProvider;
    private final ListItemModel mModel;
    private ListConsumer mListConsumer;
    private ArrayList<ListItem> mSortedItems = new ArrayList<>();

    /**
     * Creates an DateOrderedList instance that will reflect {@code source}.
     * @param source The source of data for this list.
     * @param model  The model that will be the storage for the updated list.
     * @param justNowProvider The provider for Just Now section.
     */
    public DateOrderedListMutator(
            OfflineItemFilterSource source, ListItemModel model, JustNowProvider justNowProvider) {
        mSource = source;
        mModel = model;
        mJustNowProvider = justNowProvider;
        mSource.addObserver(this);
        onItemsAdded(mSource.getItems());
    }

    /** Sets a {@link ListConsumer} to be notified whenever the list changes. */
    public ListConsumer setListConsumer(ListConsumer consumer) {
        mListConsumer = consumer;
        return mListConsumer;
    }

    /** Called to reload the list and display. */
    public void reload() {
        if (mListConsumer == null) return;
        mListConsumer.onListUpdated(mSortedItems);
    }

    // OfflineItemFilterObserver implementation.
    @Override
    public void onItemsAdded(Collection<OfflineItem> items) {
        for (OfflineItem offlineItem : items) {
            OfflineItemListItem listItem = new OfflineItemListItem(offlineItem);
            mSortedItems.add(listItem);
        }

        reload();
    }

    @Override
    public void onItemsRemoved(Collection<OfflineItem> items) {
        for (OfflineItem itemToRemove : items) {
            for (int i = 0; i < mSortedItems.size(); i++) {
                OfflineItem offlineItem = ((OfflineItemListItem) mSortedItems.get(i)).item;
                if (itemToRemove.id.equals(offlineItem.id)) mSortedItems.remove(i);
            }
        }

        reload();
    }

    @Override
    public void onItemUpdated(OfflineItem oldItem, OfflineItem item) {
        assert oldItem.id.equals(item.id);

        // If the update changed the creation time or filter type, remove and add the element to get
        // it positioned.
        if (oldItem.creationTimeMs != item.creationTimeMs
                || oldItem.filter != item.filter
                || mJustNowProvider.isJustNowItem(oldItem)
                        != mJustNowProvider.isJustNowItem(item)) {
            // TODO(shaktisahu): Collect UMA when this happens.
            onItemsRemoved(Collections.singletonList(oldItem));
            onItemsAdded(Collections.singletonList(item));
        } else {
            for (int i = 0; i < mSortedItems.size(); i++) {
                if (item.id.equals(((OfflineItemListItem) mSortedItems.get(i)).item.id)) {
                    mSortedItems.set(i, new OfflineItemListItem(item));
                }
            }
            updateModelListItem(item);
        }

        mModel.dispatchLastEvent();
    }

    private void updateModelListItem(OfflineItem item) {
        for (int i = 0; i < mModel.size(); i++) {
            ListItem listItem = mModel.get(i);
            if (!(listItem instanceof OfflineItemListItem)) continue;

            OfflineItemListItem existingItem = (OfflineItemListItem) listItem;
            if (item.id.equals(existingItem.item.id)) {
                existingItem.item = item;
                mModel.update(i, existingItem);
                break;
            }
        }
    }
}