jenkins-bot has submitted this change and it was merged. ( 
https://gerrit.wikimedia.org/r/367894 )

Change subject: Compilations: close the loop.
......................................................................


Compilations: close the loop.

This implements downloading of compilations.

Some architecture notes:
This introduces an abstract Fragment that becomes an observer of the
system DownloadManager, by polling it at a predefined interval.

So then, the other fragments that show lists of compilations (or a single
compilation) will automatically become subscribers to the polling of the
DownloadManager.  Upon every poll event, we'll update each compilation
item with the appropriate state that the DownloadManager reports.

If a compilation item is in the middle of being downloaded, we show the
DownloadControlView component inline with the item.

TODO:
- Implement deleting a downloaded compilation from disk.
- Implement more robust behavior upon successful download completion.

Bug: T167523
Bug: T164764
Bug: T167527
Change-Id: I2741c14894d990c59c30bd685546c2dd61c5cab3
---
M app/src/main/java/org/wikipedia/gallery/MediaDownloadReceiver.java
M app/src/main/java/org/wikipedia/history/HistoryFragment.java
M app/src/main/java/org/wikipedia/offline/Compilation.java
M app/src/main/java/org/wikipedia/offline/CompilationDetailFragment.java
M app/src/main/java/org/wikipedia/offline/CompilationDownloadControlView.java
A app/src/main/java/org/wikipedia/offline/DownloadManagerItem.java
A app/src/main/java/org/wikipedia/offline/DownloadManagerObserver.java
A app/src/main/java/org/wikipedia/offline/DownloadObserverFragment.java
M app/src/main/java/org/wikipedia/offline/LocalCompilationsFragment.java
M app/src/main/java/org/wikipedia/offline/OfflineManager.java
M app/src/main/java/org/wikipedia/offline/RemoteCompilationsFragment.java
M app/src/main/java/org/wikipedia/readinglist/ReadingListFragment.java
M app/src/main/java/org/wikipedia/views/PageItemView.java
M app/src/main/res/layout/fragment_compilation_detail.xml
M app/src/main/res/layout/item_page_list_entry.xml
M app/src/main/res/layout/view_compilation_download_widget.xml
A app/src/main/res/menu/menu_remote_compilation_item.xml
M app/src/main/res/values-qq/strings.xml
M app/src/main/res/values/dimens.xml
M app/src/main/res/values/strings.xml
20 files changed, 572 insertions(+), 97 deletions(-)

Approvals:
  jenkins-bot: Verified
  Mholloway: Looks good to me, approved



diff --git a/app/src/main/java/org/wikipedia/gallery/MediaDownloadReceiver.java 
b/app/src/main/java/org/wikipedia/gallery/MediaDownloadReceiver.java
index bc4ae04..c667b47 100644
--- a/app/src/main/java/org/wikipedia/gallery/MediaDownloadReceiver.java
+++ b/app/src/main/java/org/wikipedia/gallery/MediaDownloadReceiver.java
@@ -15,6 +15,7 @@
 import org.wikipedia.R;
 import org.wikipedia.WikipediaApp;
 import org.wikipedia.feed.image.FeaturedImage;
+import org.wikipedia.offline.Compilation;
 import org.wikipedia.util.FileUtil;
 
 import java.io.File;
@@ -32,13 +33,19 @@
         this.callback = callback;
     }
 
-    public void download(@NonNull Context context, @NonNull FeaturedImage 
featuredImage) {
+    public static void download(@NonNull Context context, @NonNull Compilation 
compilation) {
+        String filename = 
FileUtil.sanitizeFileName(compilation.uri().getLastPathSegment());
+        String targetDirectory = Environment.DIRECTORY_DOWNLOADS;
+        performDownloadRequest(context, compilation.uri(), targetDirectory, 
filename, Compilation.MIME_TYPE);
+    }
+
+    public static void download(@NonNull Context context, @NonNull 
FeaturedImage featuredImage) {
         String filename = FileUtil.sanitizeFileName(featuredImage.title());
         String targetDirectory = Environment.DIRECTORY_PICTURES;
         performDownloadRequest(context, featuredImage.image().source(), 
targetDirectory, filename, null);
     }
 
-    public void download(@NonNull Context context, @NonNull GalleryItem 
galleryItem) {
+    public static void download(@NonNull Context context, @NonNull GalleryItem 
galleryItem) {
         String saveFilename = 
FileUtil.sanitizeFileName(trimFileNamespace(galleryItem.getName()));
         String targetDirectoryType;
         if (FileUtil.isVideo(galleryItem.getMimeType())) {
@@ -54,7 +61,7 @@
                 saveFilename, galleryItem.getMimeType());
     }
 
-    private void performDownloadRequest(@NonNull Context context, @NonNull Uri 
uri,
+    private static void performDownloadRequest(@NonNull Context context, 
@NonNull Uri uri,
                                         @NonNull String targetDirectoryType,
                                         @NonNull String targetFileName, 
@Nullable String mimeType) {
         final String targetSubfolderName = 
WikipediaApp.getInstance().getString(R.string.app_name);
@@ -104,7 +111,7 @@
         }
     }
 
-    @NonNull private String trimFileNamespace(@NonNull String filename) {
+    @NonNull private static String trimFileNamespace(@NonNull String filename) 
{
         return filename.startsWith(FILE_NAMESPACE) ? 
filename.substring(FILE_NAMESPACE.length()) : filename;
     }
 
diff --git a/app/src/main/java/org/wikipedia/history/HistoryFragment.java 
b/app/src/main/java/org/wikipedia/history/HistoryFragment.java
index 625957b..8d2fd26 100644
--- a/app/src/main/java/org/wikipedia/history/HistoryFragment.java
+++ b/app/src/main/java/org/wikipedia/history/HistoryFragment.java
@@ -469,10 +469,10 @@
         }
 
         @Override
-        public void onActionClick(@Nullable IndexedHistoryEntry entry, 
@NonNull PageItemView view) {
+        public void onActionClick(@Nullable IndexedHistoryEntry entry, 
@NonNull View view) {
         }
         @Override
-        public void onSecondaryActionClick(@Nullable IndexedHistoryEntry 
entry, @NonNull PageItemView view) {
+        public void onSecondaryActionClick(@Nullable IndexedHistoryEntry 
entry, @NonNull View view) {
         }
     }
 
diff --git a/app/src/main/java/org/wikipedia/offline/Compilation.java 
b/app/src/main/java/org/wikipedia/offline/Compilation.java
index 60911dc..5db5665 100644
--- a/app/src/main/java/org/wikipedia/offline/Compilation.java
+++ b/app/src/main/java/org/wikipedia/offline/Compilation.java
@@ -23,6 +23,7 @@
 import static org.apache.commons.lang3.StringUtils.defaultString;
 
 public class Compilation {
+    public static final String MIME_TYPE = "application/zim";
     private static final int COMPRESSION_DICT_SIZE = 2 * 1024 * 1024;
 
     @Nullable private String name;
@@ -91,6 +92,20 @@
         timestamp = other.timestamp();
     }
 
+    public boolean pathNameMatchesUri(@Nullable Uri otherUri) {
+        if (file == null || otherUri == null) {
+            return false;
+        }
+        return file.getName().equals(otherUri.getLastPathSegment());
+    }
+
+    public boolean uriNameMatchesUri(@Nullable Uri otherUri) {
+        if (uri == null || otherUri == null) {
+            return false;
+        }
+        return uri.getLastPathSegment().equals(otherUri.getLastPathSegment());
+    }
+
     public void close() {
         try {
             if (reader != null) {
@@ -116,6 +131,10 @@
         return file != null ? file.getAbsolutePath() : defaultString(path);
     }
 
+    public boolean existsOnDisk() {
+        return !TextUtils.isEmpty(path());
+    }
+
     public long size() {
         return file != null && size == 0 ? file.length() : size;
     }
diff --git 
a/app/src/main/java/org/wikipedia/offline/CompilationDetailFragment.java 
b/app/src/main/java/org/wikipedia/offline/CompilationDetailFragment.java
index bf3c4a0..83505bf 100644
--- a/app/src/main/java/org/wikipedia/offline/CompilationDetailFragment.java
+++ b/app/src/main/java/org/wikipedia/offline/CompilationDetailFragment.java
@@ -5,7 +5,6 @@
 import android.os.Bundle;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
-import android.support.v4.app.Fragment;
 import android.support.v4.content.ContextCompat;
 import android.support.v7.app.AppCompatActivity;
 import android.support.v7.widget.Toolbar;
@@ -16,6 +15,7 @@
 import android.widget.TextView;
 
 import org.wikipedia.R;
+import org.wikipedia.gallery.MediaDownloadReceiver;
 import org.wikipedia.json.GsonMarshaller;
 import org.wikipedia.json.GsonUnmarshaller;
 import org.wikipedia.util.DimenUtil;
@@ -25,6 +25,7 @@
 
 import butterknife.BindView;
 import butterknife.ButterKnife;
+import butterknife.OnClick;
 import butterknife.Unbinder;
 
 import static 
org.wikipedia.offline.CompilationDetailActivity.EXTRA_COMPILATION;
@@ -32,9 +33,9 @@
 import static org.wikipedia.util.DimenUtil.leadImageHeightForDevice;
 import static org.wikipedia.util.FileUtil.bytesToGB;
 
-public class CompilationDetailFragment extends Fragment {
+public class CompilationDetailFragment extends DownloadObserverFragment {
     @BindView(R.id.compilation_detail_toolbar) Toolbar toolbar;
-    @BindView(R.id.compilation_detail_header_container) View containerView;
+    @BindView(R.id.compilation_detail_header_container) View 
headerContainerView;
     @BindView(R.id.compilation_detail_header_image) 
FaceAndColorDetectImageView imageView;
     @BindView(R.id.compilation_detail_header_gradient) View gradientView;
     @BindView(R.id.view_compilation_info_title) TextView nameView;
@@ -42,9 +43,12 @@
     @BindView(R.id.view_compilation_info_summary) TextView summaryView;
     @BindView(R.id.view_compilation_info_description) TextView descriptionView;
     @BindView(R.id.button_compilation_detail_download) TextView downloadButton;
+    @BindView(R.id.compilation_detail_downloaded_buttons_container) View 
downloadedContainerView;
     @BindView(R.id.view_compilation_detail_download_control) 
CompilationDownloadControlView controls;
 
     private Unbinder unbinder;
+    private Compilation compilation;
+    private boolean downloadPending;
 
     @NonNull
     public static CompilationDetailFragment newInstance(@NonNull Compilation 
info) {
@@ -66,37 +70,97 @@
         
getAppCompatActivity().getSupportActionBar().setDisplayHomeAsUpEnabled(true);
         getAppCompatActivity().getSupportActionBar().setTitle("");
 
-        Compilation comp = GsonUnmarshaller.unmarshal(Compilation.class,
+        compilation = GsonUnmarshaller.unmarshal(Compilation.class,
                 getActivity().getIntent().getStringExtra(EXTRA_COMPILATION));
 
-        Uri featureImageUri = comp.featureImageUri();
+        Uri featureImageUri = compilation.featureImageUri();
         int height = featureImageUri == null ? 
DimenUtil.getContentTopOffsetPx(getContext()) : leadImageHeightForDevice();
         if (featureImageUri == null) {
             toolbar.setBackgroundColor(ContextCompat.getColor(getContext(), 
R.color.main_toolbar_background));
         }
-        DimenUtil.setViewHeight(containerView, height);
+        DimenUtil.setViewHeight(headerContainerView, height);
         ViewUtil.setBackgroundDrawable(gradientView, 
GradientUtil.getCubicGradient(Color.BLACK, Gravity.TOP));
 
         imageView.loadImage(featureImageUri);
-        nameView.setText(comp.name());
+        nameView.setText(compilation.name());
         
dateSizeView.setText(String.format(getString(R.string.offline_compilation_detail_date_size),
-                getShortDateString(comp.timestamp()), bytesToGB(comp.size())));
-        summaryView.setText(comp.summary());
-        descriptionView.setText(comp.description());
+                getShortDateString(compilation.timestamp()), 
bytesToGB(compilation.size())));
+        summaryView.setText(compilation.summary());
+        descriptionView.setText(compilation.description());
         
downloadButton.setText(String.format(getString(R.string.offline_compilation_detail_button_download),
-                bytesToGB(comp.size())));
-        controls.setCompilation(comp);
+                bytesToGB(compilation.size())));
 
+        controls.setCallback(new CompilationDownloadControlView.Callback() {
+            @Override
+            public void onCancel() {
+                getDownloadObserver().remove(compilation);
+            }
+        });
+
+        updateDownloadState(true, null);
         return view;
     }
 
-    @Override public void onDestroyView() {
+    @Override
+    public void onDestroyView() {
         unbinder.unbind();
         unbinder = null;
         super.onDestroyView();
     }
 
+    @OnClick(R.id.button_compilation_detail_download) void onDownloadClick() {
+        if (!getDownloadObserver().isDownloading(compilation)) {
+            MediaDownloadReceiver.download(getContext(), compilation);
+            downloadPending = true;
+            updateDownloadState(true, null);
+        }
+    }
+
+    @OnClick(R.id.button_compilation_detail_view_compilations) void 
onMyCompilationsClick() {
+        getAppCompatActivity().finish();
+    }
+
+    @OnClick(R.id.button_compilation_detail_remove) void onRemoveClick() {
+    }
+
     private AppCompatActivity getAppCompatActivity() {
         return (AppCompatActivity) getActivity();
     }
+
+    private void updateDownloadState(boolean indeterminate, @Nullable 
DownloadManagerItem item) {
+        if (indeterminate) {
+            downloadButton.setVisibility(View.VISIBLE);
+            downloadButton.setEnabled(false);
+            downloadedContainerView.setVisibility(View.GONE);
+            controls.setVisibility(View.GONE);
+            return;
+        }
+        if (item == null && !compilation.existsOnDisk()) {
+            downloadButton.setVisibility(View.VISIBLE);
+            downloadButton.setEnabled(!downloadPending);
+            downloadedContainerView.setVisibility(View.GONE);
+            controls.setVisibility(View.GONE);
+        } else if (CompilationDownloadControlView.shouldShowControls(item)) {
+            downloadButton.setVisibility(View.GONE);
+            downloadedContainerView.setVisibility(View.GONE);
+            controls.setVisibility(View.VISIBLE);
+            controls.update(item);
+        } else {
+            downloadButton.setVisibility(View.GONE);
+            downloadedContainerView.setVisibility(View.VISIBLE);
+            controls.setVisibility(View.GONE);
+        }
+    }
+
+    @Override
+    protected void onPollDownloads() {
+        for (DownloadManagerItem item : getCurrentDownloads()) {
+            if (item.is(compilation)) {
+                downloadPending = false;
+                updateDownloadState(false, item);
+                return;
+            }
+        }
+        updateDownloadState(false, null);
+    }
 }
diff --git 
a/app/src/main/java/org/wikipedia/offline/CompilationDownloadControlView.java 
b/app/src/main/java/org/wikipedia/offline/CompilationDownloadControlView.java
index 9a764b9..d3ab33c 100644
--- 
a/app/src/main/java/org/wikipedia/offline/CompilationDownloadControlView.java
+++ 
b/app/src/main/java/org/wikipedia/offline/CompilationDownloadControlView.java
@@ -1,19 +1,23 @@
 package org.wikipedia.offline;
 
 import android.annotation.TargetApi;
+import android.app.DownloadManager;
 import android.content.Context;
-import android.graphics.drawable.Drawable;
+import android.content.DialogInterface;
 import android.os.Build;
-import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
-import android.support.v4.content.ContextCompat;
+import android.support.v7.app.AlertDialog;
 import android.util.AttributeSet;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.ProgressBar;
 import android.widget.TextView;
 
+import org.wikipedia.Constants;
 import org.wikipedia.R;
+import org.wikipedia.util.FeedbackUtil;
+
+import java.util.concurrent.TimeUnit;
 
 import butterknife.BindView;
 import butterknife.ButterKnife;
@@ -25,10 +29,19 @@
     @BindView(R.id.compilation_download_widget_progress_text) TextView 
progressText;
     @BindView(R.id.compilation_download_widget_progress_time_remaining) 
TextView timeRemainingText;
     @BindView(R.id.compilation_download_progress) ProgressBar progressBar;
-    @BindView(R.id.compilation_download_widget_button_pause_resume) ImageView 
pauseResumeButton;
+    @BindView(R.id.compilation_download_widget_button_cancel) ImageView 
cancelButton;
 
-    private Compilation comp;
-    private boolean downloading;
+    @Nullable private Callback callback;
+
+    public interface Callback {
+        void onCancel();
+    }
+
+    public static boolean shouldShowControls(@Nullable DownloadManagerItem 
item) {
+        return item != null && (item.status() == DownloadManager.STATUS_PENDING
+                || item.status() == DownloadManager.STATUS_RUNNING
+                || item.status() == DownloadManager.STATUS_PAUSED);
+    }
 
     public CompilationDownloadControlView(Context context) {
         super(context);
@@ -51,54 +64,54 @@
         init();
     }
 
-    void setCompilation(@NonNull Compilation comp) {
-        this.comp = comp;
-        updateViews(0f, 0);
+    public void setCallback(@Nullable Callback callback) {
+        this.callback = callback;
     }
 
-    private void updateViews(float amtDownloaded, int minsRemaining) {
+    public void update(@Nullable DownloadManagerItem item) {
+        if (item == null) {
+            return;
+        }
+        if (item.status() == DownloadManager.STATUS_RUNNING) {
+            progressBar.setIndeterminate(false);
+            progressBar.setProgress((int) (item.bytesDownloaded()
+                    * Constants.PROGRESS_BAR_MAX_VALUE / item.bytesTotal()));
+            timeRemainingText.setVisibility(VISIBLE);
+        } else {
+            progressBar.setIndeterminate(true);
+            timeRemainingText.setVisibility(GONE);
+        }
         
progressText.setText(getString(R.string.offline_compilation_download_progress_text,
-                amtDownloaded, bytesToGB(comp.size())));
-        
timeRemainingText.setText(getQuantityString(R.plurals.offline_compilation_download_time_remaining,
-                minsRemaining, minsRemaining));
-    }
-
-    @OnClick(R.id.compilation_download_widget_button_pause_resume)
-    void onPlayPauseToggleClicked() {
-        togglePlayPause();
+                bytesToGB(item.bytesDownloaded()), 
bytesToGB(item.bytesTotal())));
+        long bytesPerMin = item.bytesPerSec() * TimeUnit.MINUTES.toSeconds(1);
+        if (bytesPerMin >= 0) {
+            long minsRemaining = (item.bytesTotal() - item.bytesDownloaded()) 
/ bytesPerMin;
+            
timeRemainingText.setText(getQuantityString(R.plurals.offline_compilation_download_time_remaining,
+                    (int) minsRemaining, minsRemaining));
+        }
     }
 
     @OnClick(R.id.compilation_download_widget_button_cancel)
     void onCancelClicked() {
-        // cancelDownload();
-    }
-
-    private void togglePlayPause() {
-        if (downloading) {
-            // pauseDownload();
-        } else {
-            // resumeDownload();
-        }
-        downloading = !downloading;
-        pauseResumeButton.setImageDrawable(downloading ? getPauseIcon() : 
getResumeIcon());
-        updateProgressBar();
+        new AlertDialog.Builder(getContext())
+                .setMessage(R.string.compilation_download_cancel_confirm)
+                .setPositiveButton(R.string.yes, new 
DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialogInterface, int 
i) {
+                        if (callback != null) {
+                            callback.onCancel();
+                        }
+                    }
+                })
+                .setNegativeButton(R.string.no, null)
+                .show();
     }
 
     private void init() {
         inflate(getContext(), R.layout.view_compilation_download_widget, this);
         ButterKnife.bind(this);
-    }
-
-    private void updateProgressBar() {
-        progressBar.setIndeterminate(downloading);
-    }
-
-    private Drawable getPauseIcon() {
-        return ContextCompat.getDrawable(getContext(), 
R.drawable.ic_pause_white_24px);
-    }
-
-    private Drawable getResumeIcon() {
-        return ContextCompat.getDrawable(getContext(), 
R.drawable.ic_play_arrow_white_24px);
+        progressBar.setMax(Constants.PROGRESS_BAR_MAX_VALUE);
+        FeedbackUtil.setToolbarButtonLongPressToast(cancelButton);
     }
 
     private String getQuantityString(int id, int amt, Object... formatArgs) {
diff --git a/app/src/main/java/org/wikipedia/offline/DownloadManagerItem.java 
b/app/src/main/java/org/wikipedia/offline/DownloadManagerItem.java
new file mode 100644
index 0000000..e2794c5
--- /dev/null
+++ b/app/src/main/java/org/wikipedia/offline/DownloadManagerItem.java
@@ -0,0 +1,60 @@
+package org.wikipedia.offline;
+
+import android.net.Uri;
+import android.support.annotation.NonNull;
+import android.text.TextUtils;
+
+import java.io.File;
+
+public class DownloadManagerItem {
+    private int id;
+    @NonNull private Uri uri;
+    private int status;
+    private long bytesDownloaded;
+    private long bytesTotal;
+    private long bytesPerSec;
+
+    public DownloadManagerItem(int id, @NonNull Uri uri, int status, long 
bytesDownloaded,
+                               long bytesTotal, long bytesPerSec) {
+        this.id = id;
+        this.uri = uri;
+        this.status = status;
+        this.bytesDownloaded = bytesDownloaded;
+        this.bytesTotal = bytesTotal;
+        this.bytesPerSec = bytesPerSec;
+    }
+
+    public int id() {
+        return id;
+    }
+
+    @NonNull
+    public Uri uri() {
+        return uri;
+    }
+
+    public int status() {
+        return status;
+    }
+
+    public long bytesDownloaded() {
+        return bytesDownloaded;
+    }
+
+    public long bytesTotal() {
+        return bytesTotal;
+    }
+
+    public long bytesPerSec() {
+        return bytesPerSec;
+    }
+
+    public boolean is(@NonNull Compilation compilation) {
+        if (!TextUtils.isEmpty(compilation.path())) {
+            return uri.getLastPathSegment().equals(new 
File(compilation.path()).getName());
+        } else if (compilation.uri() != null) {
+            return 
uri.getLastPathSegment().equals(compilation.uri().getLastPathSegment());
+        }
+        return false;
+    }
+}
diff --git 
a/app/src/main/java/org/wikipedia/offline/DownloadManagerObserver.java 
b/app/src/main/java/org/wikipedia/offline/DownloadManagerObserver.java
new file mode 100644
index 0000000..94ae235
--- /dev/null
+++ b/app/src/main/java/org/wikipedia/offline/DownloadManagerObserver.java
@@ -0,0 +1,125 @@
+package org.wikipedia.offline;
+
+import android.app.DownloadManager;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Handler;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.util.SparseArrayCompat;
+import android.text.TextUtils;
+
+import org.wikipedia.WikipediaApp;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+public class DownloadManagerObserver {
+    private static final int POLL_INTERVAL_MS = 500;
+
+    public interface Callback {
+        void onDownloadStatus(@NonNull List<DownloadManagerItem> items);
+    }
+
+    @Nullable private DownloadManager downloadManager;
+    @Nullable private Callback callback;
+    @Nullable private Handler handler;
+    private PollRunnable pollRunnable = new PollRunnable();
+
+    private SparseArrayCompat<Long> downloadAmountCache = new 
SparseArrayCompat<>();
+    private SparseArrayCompat<Long> downloadTimeMillis = new 
SparseArrayCompat<>();
+
+    public void register(@NonNull Callback callback) {
+        downloadManager = (DownloadManager) 
WikipediaApp.getInstance().getSystemService(Context.DOWNLOAD_SERVICE);
+        handler = new Handler(WikipediaApp.getInstance().getMainLooper());
+        this.callback = callback;
+        handler.postDelayed(pollRunnable, POLL_INTERVAL_MS);
+    }
+
+    public void unregister() {
+        downloadManager = null;
+        callback = null;
+        handler = null;
+    }
+
+    public boolean isDownloading(@NonNull Compilation compilation) {
+        for (DownloadManagerItem item : queryCurrentDownloads()) {
+            if (item.is(compilation)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public void remove(@NonNull Compilation compilation) {
+        if (downloadManager == null) {
+            return;
+        }
+        for (DownloadManagerItem item : queryCurrentDownloads()) {
+            if (item.is(compilation)) {
+                downloadManager.remove(item.id());
+            }
+        }
+    }
+
+    private class PollRunnable implements Runnable {
+        @Override
+        public void run() {
+            if (handler == null) {
+                return;
+            }
+            List<DownloadManagerItem> items = queryCurrentDownloads();
+            if (callback != null) {
+                callback.onDownloadStatus(items);
+            }
+
+            handler.postDelayed(pollRunnable, POLL_INTERVAL_MS);
+        }
+    }
+
+    @NonNull
+    private List<DownloadManagerItem> queryCurrentDownloads() {
+        List<DownloadManagerItem> items = new ArrayList<>();
+        if (downloadManager == null) {
+            return items;
+        }
+        DownloadManager.Query query = new DownloadManager.Query();
+        Cursor cursor = downloadManager.query(query);
+        while (cursor.moveToNext()) {
+            String mimeType = 
cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_MEDIA_TYPE));
+            if (!Compilation.MIME_TYPE.equals(mimeType)) {
+                continue;
+            }
+
+            int id = 
cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_ID));
+            String uri = 
cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI));
+            int status = 
cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS));
+            long bytesDownloaded = 
cursor.getLong(cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR));
+            long bytesTotal = 
cursor.getLong(cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES));
+
+            long bytesPerSec = -1;
+
+            if (downloadAmountCache.get(id) == null) {
+                downloadAmountCache.put(id, bytesDownloaded);
+                downloadTimeMillis.put(id, System.currentTimeMillis());
+            }
+
+            if (System.currentTimeMillis() > downloadTimeMillis.get(id)
+                    && bytesDownloaded > downloadAmountCache.get(id)) {
+                bytesPerSec = (bytesDownloaded - downloadAmountCache.get(id))
+                        * TimeUnit.SECONDS.toMillis(1)
+                        / (System.currentTimeMillis() - 
downloadTimeMillis.get(id));
+                downloadAmountCache.put(id, bytesDownloaded);
+                downloadTimeMillis.put(id, System.currentTimeMillis());
+            }
+            if (!TextUtils.isEmpty(uri)) {
+                items.add(new DownloadManagerItem(id, Uri.parse(uri), status, 
bytesDownloaded,
+                        bytesTotal, bytesPerSec));
+            }
+        }
+        cursor.close();
+        return items;
+    }
+}
diff --git 
a/app/src/main/java/org/wikipedia/offline/DownloadObserverFragment.java 
b/app/src/main/java/org/wikipedia/offline/DownloadObserverFragment.java
new file mode 100644
index 0000000..de53b53
--- /dev/null
+++ b/app/src/main/java/org/wikipedia/offline/DownloadObserverFragment.java
@@ -0,0 +1,45 @@
+package org.wikipedia.offline;
+
+import android.support.annotation.NonNull;
+import android.support.v4.app.Fragment;
+
+import java.util.Collections;
+import java.util.List;
+
+public abstract class DownloadObserverFragment extends Fragment {
+
+    private DownloadManagerObserver downloadObserver = new 
DownloadManagerObserver();
+    private DownloadObserverCallback downloadObserverCallback = new 
DownloadObserverCallback();
+    @NonNull private List<DownloadManagerItem> currentDownloads = 
Collections.emptyList();
+
+    @Override
+    public void onPause() {
+        downloadObserver.unregister();
+        super.onPause();
+    }
+
+    @Override
+    public void onResume() {
+        downloadObserver.register(downloadObserverCallback);
+        super.onResume();
+    }
+
+    protected abstract void onPollDownloads();
+
+    protected DownloadManagerObserver getDownloadObserver() {
+        return downloadObserver;
+    }
+
+    @NonNull
+    protected List<DownloadManagerItem> getCurrentDownloads() {
+        return currentDownloads;
+    }
+
+    private class DownloadObserverCallback implements 
DownloadManagerObserver.Callback {
+        @Override
+        public void onDownloadStatus(@NonNull List<DownloadManagerItem> items) 
{
+            currentDownloads = items;
+            onPollDownloads();
+        }
+    }
+}
diff --git 
a/app/src/main/java/org/wikipedia/offline/LocalCompilationsFragment.java 
b/app/src/main/java/org/wikipedia/offline/LocalCompilationsFragment.java
index 8ab4d20..d72dda4 100644
--- a/app/src/main/java/org/wikipedia/offline/LocalCompilationsFragment.java
+++ b/app/src/main/java/org/wikipedia/offline/LocalCompilationsFragment.java
@@ -3,11 +3,11 @@
 import android.os.Bundle;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
-import android.support.v4.app.Fragment;
 import android.support.v7.app.AppCompatActivity;
 import android.support.v7.view.ActionMode;
 import android.support.v7.widget.LinearLayoutManager;
 import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.SimpleItemAnimator;
 import android.text.TextUtils;
 import android.view.LayoutInflater;
 import android.view.Menu;
@@ -21,6 +21,7 @@
 import org.wikipedia.R;
 import org.wikipedia.activity.FragmentUtil;
 import org.wikipedia.history.SearchActionModeCallback;
+import org.wikipedia.util.DimenUtil;
 import org.wikipedia.util.ResourceUtil;
 import org.wikipedia.views.DefaultViewHolder;
 import org.wikipedia.views.DrawableItemDecoration;
@@ -36,7 +37,7 @@
 import butterknife.OnClick;
 import butterknife.Unbinder;
 
-public class LocalCompilationsFragment extends Fragment {
+public class LocalCompilationsFragment extends DownloadObserverFragment {
     @BindView(R.id.compilation_list_container) View listContainer;
     @BindView(R.id.compilation_list) RecyclerView recyclerView;
     @BindView(R.id.search_empty_view) SearchEmptyView searchEmptyView;
@@ -76,6 +77,7 @@
         recyclerView.setAdapter(adapter);
         recyclerView.addItemDecoration(new DrawableItemDecoration(getContext(),
                 ResourceUtil.getThemedAttributeId(getContext(), 
R.attr.list_separator_drawable), true));
+        ((SimpleItemAnimator) 
recyclerView.getItemAnimator()).setSupportsChangeAnimations(false);
 
         errorView.setBackClickListener(new View.OnClickListener() {
             @Override
@@ -84,7 +86,6 @@
             }
         });
 
-        beginUpdate();
         return view;
     }
 
@@ -96,6 +97,7 @@
 
     @Override
     public void onResume() {
+        beginUpdate();
         adapter.notifyDataSetChanged();
         super.onResume();
     }
@@ -133,6 +135,11 @@
         startActivity(RemoteCompilationsActivity.newIntent(getContext()));
     }
 
+    @Override
+    protected void onPollDownloads() {
+        adapter.notifyItemRangeChanged(0, adapter.getItemCount());
+    }
+
     public void onCompilationsRefreshed() {
         updating = false;
         lastError = null;
@@ -143,6 +150,15 @@
         updating = false;
         lastError = t;
         update();
+    }
+
+    private void postBeginUpdate() {
+        listContainer.post(new Runnable() {
+            @Override
+            public void run() {
+                beginUpdate();
+            }
+        });
     }
 
     private void beginUpdate() {
@@ -203,12 +219,50 @@
 
     private class CompilationItemHolder extends 
DefaultViewHolder<PageItemView<Compilation>> {
         private Compilation compilation;
+        private CompilationDownloadControlView controlView;
+        private boolean wasDownloading;
 
         CompilationItemHolder(PageItemView<Compilation> itemView) {
             super(itemView);
+            controlView = new 
CompilationDownloadControlView(itemView.getContext());
+            itemView.addFooter(controlView);
+            
controlView.setPadding(DimenUtil.roundedDpToPx(DimenUtil.getDimension(R.dimen.activity_horizontal_margin)),
+                    
DimenUtil.roundedDpToPx(DimenUtil.getDimension(R.dimen.list_item_footer_padding)),
+                    
DimenUtil.roundedDpToPx(DimenUtil.getDimension(R.dimen.activity_horizontal_margin)),
+                    
DimenUtil.roundedDpToPx(DimenUtil.getDimension(R.dimen.list_item_footer_padding)));
+            
controlView.setBackgroundColor(ResourceUtil.getThemedColor(getContext(), 
R.attr.inline_onboarding_background_color));
+            controlView.setCallback(new 
CompilationDownloadControlView.Callback() {
+                @Override
+                public void onCancel() {
+                    getDownloadObserver().remove(compilation);
+                }
+            });
         }
 
         void bindItem(Compilation compilation) {
+            DownloadManagerItem myItem = null;
+            for (DownloadManagerItem item : getCurrentDownloads()) {
+                if (item.is(compilation)) {
+                    myItem = item;
+                    break;
+                }
+            }
+            if (CompilationDownloadControlView.shouldShowControls(myItem)) {
+                controlView.setVisibility(View.VISIBLE);
+                controlView.update(myItem);
+            } else {
+                controlView.setVisibility(View.GONE);
+            }
+            if (myItem == null && wasDownloading) {
+                postBeginUpdate();
+                wasDownloading = false;
+            } else if (myItem != null) {
+                wasDownloading = true;
+            }
+            if (compilation == this.compilation) {
+                return;
+            }
+            wasDownloading = false;
             this.compilation = compilation;
             getView().setItem(compilation);
             getView().setTitle(compilation.name());
@@ -265,11 +319,11 @@
         }
 
         @Override
-        public void onActionClick(@Nullable Compilation item, @NonNull 
PageItemView view) {
+        public void onActionClick(@Nullable Compilation item, @NonNull View 
view) {
         }
 
         @Override
-        public void onSecondaryActionClick(@Nullable Compilation item, 
@NonNull PageItemView view) {
+        public void onSecondaryActionClick(@Nullable Compilation item, 
@NonNull View view) {
         }
     }
 
diff --git a/app/src/main/java/org/wikipedia/offline/OfflineManager.java 
b/app/src/main/java/org/wikipedia/offline/OfflineManager.java
index aeba0a7..e51c91d 100644
--- a/app/src/main/java/org/wikipedia/offline/OfflineManager.java
+++ b/app/src/main/java/org/wikipedia/offline/OfflineManager.java
@@ -11,6 +11,7 @@
 import java.io.File;
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.Random;
 import java.util.concurrent.TimeUnit;
@@ -20,6 +21,7 @@
     @Nullable private CompilationSearchTask searchTask;
     private long lastSearchTime;
     @NonNull private List<Compilation> compilations = new ArrayList<>();
+    @NonNull private List<Compilation> remoteCompilationCache = 
Collections.emptyList();
 
     public interface Callback {
         void onCompilationsFound(@NonNull List<Compilation> compilations);
@@ -65,6 +67,13 @@
                     }
                     c.close();
                 }
+                for (Compilation result : results) {
+                    for (Compilation remote : remoteCompilationCache) {
+                        if (result.pathNameMatchesUri(remote.uri())) {
+                            result.copyMetadataFrom(remote);
+                        }
+                    }
+                }
                 compilations.clear();
                 compilations.addAll(results);
                 Prefs.setCompilationCache(compilations);
@@ -80,10 +89,10 @@
     }
 
     void updateFromRemoteMetadata(@NonNull List<Compilation> 
remoteCompilations) {
-        for (Compilation remoteCompilation : remoteCompilations) {
+        remoteCompilationCache = remoteCompilations;
+        for (Compilation remoteCompilation : remoteCompilationCache) {
             for (Compilation localCompilation : compilations) {
-                if (remoteCompilation.uri() != null
-                        && new 
File(localCompilation.path()).getName().equals(remoteCompilation.uri().getLastPathSegment()))
 {
+                if 
(localCompilation.pathNameMatchesUri(remoteCompilation.uri())) {
                     localCompilation.copyMetadataFrom(remoteCompilation);
                 }
             }
diff --git 
a/app/src/main/java/org/wikipedia/offline/RemoteCompilationsFragment.java 
b/app/src/main/java/org/wikipedia/offline/RemoteCompilationsFragment.java
index d94851a..94115e2 100644
--- a/app/src/main/java/org/wikipedia/offline/RemoteCompilationsFragment.java
+++ b/app/src/main/java/org/wikipedia/offline/RemoteCompilationsFragment.java
@@ -6,11 +6,12 @@
 import android.support.annotation.Nullable;
 import android.support.design.widget.AppBarLayout;
 import android.support.design.widget.CollapsingToolbarLayout;
-import android.support.v4.app.Fragment;
 import android.support.v7.app.AppCompatActivity;
 import android.support.v7.view.ActionMode;
 import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.PopupMenu;
 import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.SimpleItemAnimator;
 import android.support.v7.widget.Toolbar;
 import android.text.TextUtils;
 import android.view.LayoutInflater;
@@ -23,7 +24,9 @@
 
 import org.wikipedia.R;
 import org.wikipedia.WikipediaApp;
+import org.wikipedia.gallery.MediaDownloadReceiver;
 import org.wikipedia.history.SearchActionModeCallback;
+import org.wikipedia.util.DimenUtil;
 import org.wikipedia.util.ResourceUtil;
 import org.wikipedia.views.DefaultViewHolder;
 import org.wikipedia.views.DrawableItemDecoration;
@@ -38,7 +41,7 @@
 import butterknife.ButterKnife;
 import butterknife.Unbinder;
 
-public class RemoteCompilationsFragment extends Fragment {
+public class RemoteCompilationsFragment extends DownloadObserverFragment {
     @BindView(R.id.compilation_list_toolbar_container) CollapsingToolbarLayout 
toolbarLayout;
     @BindView(R.id.compilation_list_app_bar) AppBarLayout appBarLayout;
     @BindView(R.id.compilation_list_toolbar) Toolbar toolbar;
@@ -83,6 +86,7 @@
         recyclerView.setAdapter(adapter);
         recyclerView.addItemDecoration(new DrawableItemDecoration(getContext(),
                 ResourceUtil.getThemedAttributeId(getContext(), 
R.attr.list_separator_drawable), true));
+        ((SimpleItemAnimator) 
recyclerView.getItemAnimator()).setSupportsChangeAnimations(false);
 
         errorView.setRetryClickListener(new View.OnClickListener() {
             @Override
@@ -99,7 +103,6 @@
         });
 
         beginUpdate();
-
         return view;
     }
 
@@ -179,14 +182,49 @@
         }
     }
 
+    @Override
+    protected void onPollDownloads() {
+        adapter.notifyItemRangeChanged(0, adapter.getItemCount());
+    }
+
     private class CompilationItemHolder extends 
DefaultViewHolder<PageItemView<Compilation>> {
         private Compilation compilation;
+        private CompilationDownloadControlView controlView;
 
         CompilationItemHolder(PageItemView<Compilation> itemView) {
             super(itemView);
+            controlView = new 
CompilationDownloadControlView(itemView.getContext());
+            itemView.addFooter(controlView);
+            
controlView.setPadding(DimenUtil.roundedDpToPx(DimenUtil.getDimension(R.dimen.activity_horizontal_margin)),
+                    
DimenUtil.roundedDpToPx(DimenUtil.getDimension(R.dimen.list_item_footer_padding)),
+                    
DimenUtil.roundedDpToPx(DimenUtil.getDimension(R.dimen.activity_horizontal_margin)),
+                    
DimenUtil.roundedDpToPx(DimenUtil.getDimension(R.dimen.list_item_footer_padding)));
+            
controlView.setBackgroundColor(ResourceUtil.getThemedColor(getContext(), 
R.attr.inline_onboarding_background_color));
+            controlView.setCallback(new 
CompilationDownloadControlView.Callback() {
+                @Override
+                public void onCancel() {
+                    getDownloadObserver().remove(compilation);
+                }
+            });
         }
 
         void bindItem(Compilation compilation) {
+            DownloadManagerItem myItem = null;
+            for (DownloadManagerItem item : getCurrentDownloads()) {
+                if (item.is(compilation)) {
+                    myItem = item;
+                    break;
+                }
+            }
+            if (CompilationDownloadControlView.shouldShowControls(myItem)) {
+                controlView.setVisibility(View.VISIBLE);
+                controlView.update(myItem);
+            } else {
+                controlView.setVisibility(View.GONE);
+            }
+            if (compilation == this.compilation) {
+                return;
+            }
             this.compilation = compilation;
             getView().setItem(compilation);
             getView().setTitle(compilation.name());
@@ -243,11 +281,14 @@
         }
 
         @Override
-        public void onActionClick(@Nullable Compilation item, @NonNull 
PageItemView view) {
+        public void onActionClick(@Nullable Compilation item, @NonNull View 
view) {
+            if (item != null) {
+                showCompilationOverflowMenu(item, view);
+            }
         }
 
         @Override
-        public void onSecondaryActionClick(@Nullable Compilation item, 
@NonNull PageItemView view) {
+        public void onSecondaryActionClick(@Nullable Compilation item, 
@NonNull View view) {
         }
     }
 
@@ -278,9 +319,23 @@
     private class CompilationCallback implements CompilationClient.Callback {
         @Override
         public void success(@NonNull List<Compilation> compilations) {
-            allItems = compilations;
-            updating = false;
             OfflineManager.instance().updateFromRemoteMetadata(compilations);
+
+            allItems.clear();
+            for (Compilation remote : compilations) {
+                boolean haveLocal = false;
+                for (Compilation local : 
OfflineManager.instance().compilations()) {
+                    if (local.pathNameMatchesUri(remote.uri())) {
+                        haveLocal = true;
+                        allItems.add(local);
+                    }
+                }
+                if (!haveLocal) {
+                    allItems.add(remote);
+                }
+            }
+
+            updating = false;
             setSearchQuery(currentSearchQuery);
         }
 
@@ -292,6 +347,24 @@
         }
     }
 
+    private void showCompilationOverflowMenu(@NonNull final Compilation 
compilation, @NonNull View anchorView) {
+        PopupMenu menu = new PopupMenu(anchorView.getContext(), anchorView);
+        menu.getMenuInflater().inflate(R.menu.menu_remote_compilation_item, 
menu.getMenu());
+        menu.setOnMenuItemClickListener(new 
PopupMenu.OnMenuItemClickListener() {
+            @Override
+            public boolean onMenuItemClick(MenuItem menuItem) {
+                switch (menuItem.getItemId()) {
+                    case R.id.menu_compilation_download:
+                        MediaDownloadReceiver.download(getContext(), 
compilation);
+                        return false;
+                    default:
+                        return false;
+                }
+            }
+        });
+        menu.show();
+    }
+
     private AppCompatActivity getAppCompatActivity() {
         return (AppCompatActivity) getActivity();
     }
diff --git 
a/app/src/main/java/org/wikipedia/readinglist/ReadingListFragment.java 
b/app/src/main/java/org/wikipedia/readinglist/ReadingListFragment.java
index 4140b7f..4b2723a 100644
--- a/app/src/main/java/org/wikipedia/readinglist/ReadingListFragment.java
+++ b/app/src/main/java/org/wikipedia/readinglist/ReadingListFragment.java
@@ -708,7 +708,7 @@
         }
 
         @Override
-        public void onActionClick(@Nullable ReadingListPage page, @NonNull 
PageItemView view) {
+        public void onActionClick(@Nullable ReadingListPage page, @NonNull 
View view) {
             if (page == null || readingList == null) {
                 return;
             }
@@ -717,7 +717,7 @@
         }
 
         @Override
-        public void onSecondaryActionClick(@Nullable ReadingListPage page, 
@NonNull PageItemView view) {
+        public void onSecondaryActionClick(@Nullable ReadingListPage page, 
@NonNull View view) {
             if (page != null) {
                 if (page.isSaving()) {
                     Toast.makeText(getContext(), 
R.string.reading_list_article_save_in_progress, Toast.LENGTH_LONG).show();
diff --git a/app/src/main/java/org/wikipedia/views/PageItemView.java 
b/app/src/main/java/org/wikipedia/views/PageItemView.java
index 1b76a42..80ac1fe 100644
--- a/app/src/main/java/org/wikipedia/views/PageItemView.java
+++ b/app/src/main/java/org/wikipedia/views/PageItemView.java
@@ -32,10 +32,11 @@
         void onClick(@Nullable T item);
         boolean onLongClick(@Nullable T item);
         void onThumbClick(@Nullable T item);
-        void onActionClick(@Nullable T item, @NonNull PageItemView view);
-        void onSecondaryActionClick(@Nullable T item, @NonNull PageItemView 
view);
+        void onActionClick(@Nullable T item, @NonNull View view);
+        void onSecondaryActionClick(@Nullable T item, @NonNull View view);
     }
 
+    @BindView(R.id.page_list_item_container) ViewGroup containerView;
     @BindView(R.id.page_list_item_title) TextView titleView;
     @BindView(R.id.page_list_item_description) TextView descriptionView;
     @BindView(R.id.page_list_item_image) SimpleDraweeView imageView;
@@ -104,6 +105,10 @@
         }
     }
 
+    public void addFooter(@NonNull View view) {
+        containerView.addView(view);
+    }
+
     @OnClick(R.id.page_list_item_container) void onClick() {
         if (callback != null) {
             callback.onClick(item);
@@ -123,9 +128,9 @@
         }
     }
 
-    @OnClick(R.id.page_list_item_action_primary) void onActionClick() {
+    @OnClick(R.id.page_list_item_action_primary) void onActionClick(View v) {
         if (callback != null) {
-            callback.onActionClick(item, this);
+            callback.onActionClick(item, v);
         }
     }
 
diff --git a/app/src/main/res/layout/fragment_compilation_detail.xml 
b/app/src/main/res/layout/fragment_compilation_detail.xml
index ae21bc3..e96bf37 100644
--- a/app/src/main/res/layout/fragment_compilation_detail.xml
+++ b/app/src/main/res/layout/fragment_compilation_detail.xml
@@ -44,8 +44,7 @@
             android:layout_marginLeft="10dp"
             android:layout_marginRight="10dp"
             style="@style/App.Button.TransparentBlue"
-            android:text="@string/offline_compilation_detail_button_download"
-            android:visibility="gone"/>
+            android:text="@string/offline_compilation_detail_button_download"/>
 
         <!-- Compilation download controls for when download is in progress -->
         <org.wikipedia.offline.CompilationDownloadControlView
diff --git a/app/src/main/res/layout/item_page_list_entry.xml 
b/app/src/main/res/layout/item_page_list_entry.xml
index dd28a69..d0a1edb 100644
--- a/app/src/main/res/layout/item_page_list_entry.xml
+++ b/app/src/main/res/layout/item_page_list_entry.xml
@@ -9,7 +9,6 @@
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:paddingTop="@dimen/list_item_vertical_padding"
-    android:paddingBottom="@dimen/list_item_vertical_padding"
     android:background="?attr/selectableItemBackground"
     android:gravity="top">
 
@@ -33,7 +32,8 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_marginStart="@dimen/list_item_horizontal_padding"
-        android:layout_marginLeft="@dimen/list_item_horizontal_padding">
+        android:layout_marginLeft="@dimen/list_item_horizontal_padding"
+        android:layout_marginBottom="@dimen/list_item_vertical_padding">
 
         <FrameLayout
             android:id="@+id/page_list_item_image_container"
diff --git a/app/src/main/res/layout/view_compilation_download_widget.xml 
b/app/src/main/res/layout/view_compilation_download_widget.xml
index c18ba95..897cbe1 100644
--- a/app/src/main/res/layout/view_compilation_download_widget.xml
+++ b/app/src/main/res/layout/view_compilation_download_widget.xml
@@ -47,22 +47,13 @@
     </LinearLayout>
 
     <ImageView
-        android:id="@+id/compilation_download_widget_button_pause_resume"
-        android:layout_width="wrap_content"
-        android:layout_height="match_parent"
-        android:layout_marginLeft="16dp"
-        android:layout_marginRight="16dp"
-        android:padding="2dp"
-        android:tint="?attr/feed_text_secondary_color"
-        app:srcCompat="@drawable/ic_pause_white_24px"
-        
android:contentDescription="@string/compilation_download_widget_button_pause_resume_hint"/>
-
-    <ImageView
         android:id="@+id/compilation_download_widget_button_cancel"
         android:layout_width="wrap_content"
         android:layout_height="match_parent"
         android:padding="2dp"
         android:tint="?attr/feed_text_secondary_color"
         app:srcCompat="@drawable/ic_clear_white_24px"
+        android:clickable="true"
+        android:background="?attr/selectableItemBackgroundBorderless"
         
android:contentDescription="@string/compilation_download_widget_button_cancel_hint"/>
 </merge>
\ No newline at end of file
diff --git a/app/src/main/res/menu/menu_remote_compilation_item.xml 
b/app/src/main/res/menu/menu_remote_compilation_item.xml
new file mode 100644
index 0000000..c1d932d
--- /dev/null
+++ b/app/src/main/res/menu/menu_remote_compilation_item.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android";>
+    <item android:id="@+id/menu_compilation_download"
+        android:title="@string/remote_compilation_download"/>
+</menu>
diff --git a/app/src/main/res/values-qq/strings.xml 
b/app/src/main/res/values-qq/strings.xml
index 67311f7..dca2e0e 100644
--- a/app/src/main/res/values-qq/strings.xml
+++ b/app/src/main/res/values-qq/strings.xml
@@ -418,6 +418,7 @@
   <string name="offline_compilation_detail_date_size">Information about the 
date and size of a compilation. The %1$s and %2$.2f symbols will be replaced 
with the date and size. Only the \"GB\" unit needs to be translated (see 
storage_size_gb).</string>
   <string name="offline_compilation_detail_button_download">Label for a button 
for downloading an offline compilation. The %.2f symbol is replaced with the 
compilation size, in gigabytes (GB).</string>
   <string name="offline_compilation_detail_button_remove">Label for a button 
for removing an offline compilation from storage on the user\'s device.</string>
+  <string name="remote_compilation_download">Menu label for downloading a 
selected compilation.</string>
   <string name="storage_size_format">Format specifier where %.2f represents a 
decimal number with two significant figures to the right of the decimal 
point.</string>
   <string name="storage_size_gb">Abbreviated form for the unit of a 
gigabyte.</string>
   <string name="storage_size_free">Label that says how much free space is 
available on the device. The %.2f symbol is replaced with the number in 
gigabytes.</string>
@@ -430,6 +431,7 @@
   </plurals>
   <string 
name="compilation_download_widget_button_pause_resume_hint">Accessibility hint 
text for a button to pause or resume an offline compilation file 
download</string>
   <string name="compilation_download_widget_button_cancel_hint">Accessibility 
hint text for a button to cancel an offline compilation file download</string>
+  <string name="compilation_download_cancel_confirm">Confirmation message 
asking if the user really wants to cancel the download.</string>
   <string name="onboarding_skip">Button label to skip the current onboarding 
or tutorial screen.\n{{Identical|Skip}}</string>
   <string name="onboarding_continue">Button label to continue to the next 
onboarding or tutorial screen.\n{{Identical|Continue}}</string>
   <string name="onboarding_get_started">Button label to finish the current 
onboarding or tutorial workflow.\n{{Identical|Get started}}</string>
diff --git a/app/src/main/res/values/dimens.xml 
b/app/src/main/res/values/dimens.xml
index f272f7c..9be6da9 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -18,6 +18,7 @@
 
     <dimen name="list_item_vertical_padding">16dp</dimen>
     <dimen name="list_item_horizontal_padding">16dp</dimen>
+    <dimen name="list_item_footer_padding">8dp</dimen>
 
     <!-- Default font size for WebView text based on system settings -->
     <dimen name="textSize">16sp</dimen>
diff --git a/app/src/main/res/values/strings.xml 
b/app/src/main/res/values/strings.xml
index 68726c7..3e16fd5 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -472,6 +472,7 @@
     <string name="offline_compilation_detail_date_size">%1$s · %2$.2f 
GB</string>
     <string name="offline_compilation_detail_button_download">Download · %.2f 
GB</string>
     <string name="offline_compilation_detail_button_remove">Remove</string>
+    <string name="remote_compilation_download">Download</string>
     <string name="storage_size_format">%.2f</string>
     <string name="storage_size_gb">GB</string>
     <string name="storage_size_free">%.2f GB free</string>
@@ -484,6 +485,7 @@
     </plurals>
     <string name="compilation_download_widget_button_pause_resume_hint">Pause 
or resume download</string>
     <string name="compilation_download_widget_button_cancel_hint">Cancel 
download</string>
+    <string name="compilation_download_cancel_confirm">Are you sure you want 
to cancel downloading this compilation?</string>
     <!-- /Offline -->
 
     <!-- Onboarding -->

-- 
To view, visit https://gerrit.wikimedia.org/r/367894
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: I2741c14894d990c59c30bd685546c2dd61c5cab3
Gerrit-PatchSet: 12
Gerrit-Project: apps/android/wikipedia
Gerrit-Branch: master
Gerrit-Owner: Dbrant <[email protected]>
Gerrit-Reviewer: Brion VIBBER <[email protected]>
Gerrit-Reviewer: Dbrant <[email protected]>
Gerrit-Reviewer: Mholloway <[email protected]>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to