From f73f0cc3414c83c20e268673460633b092406dee Mon Sep 17 00:00:00 2001 From: inorichi Date: Fri, 26 Feb 2016 18:10:13 +0100 Subject: [PATCH] Download queue's UI in Kotlin --- .../ui/download/DownloadAdapter.java | 50 ----- .../tachiyomi/ui/download/DownloadAdapter.kt | 70 +++++++ .../ui/download/DownloadFragment.java | 147 -------------- .../tachiyomi/ui/download/DownloadFragment.kt | 179 ++++++++++++++++++ .../tachiyomi/ui/download/DownloadHolder.java | 49 ----- .../tachiyomi/ui/download/DownloadHolder.kt | 60 ++++++ .../ui/download/DownloadPresenter.java | 128 ------------- .../ui/download/DownloadPresenter.kt | 174 +++++++++++++++++ .../tachiyomi/ui/library/LibraryFragment.kt | 5 - .../res/layout/fragment_download_queue.xml | 2 +- 10 files changed, 484 insertions(+), 380 deletions(-) delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadAdapter.java create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadAdapter.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadFragment.java create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadFragment.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadHolder.java create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadHolder.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadPresenter.java create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadPresenter.kt diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadAdapter.java b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadAdapter.java deleted file mode 100644 index f10ba392a9..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadAdapter.java +++ /dev/null @@ -1,50 +0,0 @@ -package eu.kanade.tachiyomi.ui.download; - -import android.content.Context; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import java.util.ArrayList; -import java.util.List; - -import eu.davidea.flexibleadapter.FlexibleAdapter; -import eu.kanade.tachiyomi.R; -import eu.kanade.tachiyomi.data.download.model.Download; - -public class DownloadAdapter extends FlexibleAdapter { - - private Context context; - - public DownloadAdapter(Context context) { - this.context = context; - mItems = new ArrayList<>(); - setHasStableIds(true); - } - - @Override - public DownloadHolder onCreateViewHolder(ViewGroup parent, int viewType) { - View v = LayoutInflater.from(context).inflate(R.layout.item_download, parent, false); - return new DownloadHolder(v); - } - - @Override - public void onBindViewHolder(DownloadHolder holder, int position) { - final Download download = getItem(position); - holder.onSetValues(download); - } - - @Override - public long getItemId(int position) { - return getItem(position).chapter.id; - } - - public void setItems(List downloads) { - mItems = downloads; - notifyDataSetChanged(); - } - - @Override - public void updateDataSet(String param) {} - -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadAdapter.kt new file mode 100644 index 0000000000..0919e7ccf2 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadAdapter.kt @@ -0,0 +1,70 @@ +package eu.kanade.tachiyomi.ui.download + +import android.content.Context +import android.view.ViewGroup +import eu.davidea.flexibleadapter.FlexibleAdapter +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.data.download.model.Download +import eu.kanade.tachiyomi.util.inflate + +/** + * Adapter storing a list of downloads. + * + * @param context the context of the fragment containing this adapter. + */ +class DownloadAdapter(private val context: Context) : FlexibleAdapter() { + + init { + setHasStableIds(true) + } + + /** + * Sets a list of downloads in the adapter. + * + * @param downloads the list to set. + */ + fun setItems(downloads: List) { + mItems = downloads + notifyDataSetChanged() + } + + /** + * Returns the identifier for a download. + * + * @param position the position in the adapter. + * @return an identifier for the item. + */ + override fun getItemId(position: Int): Long { + return getItem(position).chapter.id + } + + /** + * Creates a new view holder. + * + * @param parent the parent view. + * @param viewType the type of the holder. + * @return a new view holder for a manga. + */ + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DownloadHolder { + val view = parent.inflate(R.layout.item_download) + return DownloadHolder(view) + } + + /** + * Binds a holder with a new position. + * + * @param holder the holder to bind. + * @param position the position to bind. + */ + override fun onBindViewHolder(holder: DownloadHolder, position: Int) { + val download = getItem(position) + holder.onSetValues(download) + } + + /** + * Used to filter the list. Not used. + */ + override fun updateDataSet(param: String) { + } + +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadFragment.java b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadFragment.java deleted file mode 100644 index eb6bb4a103..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadFragment.java +++ /dev/null @@ -1,147 +0,0 @@ -package eu.kanade.tachiyomi.ui.download; - -import android.os.Bundle; -import android.support.annotation.Nullable; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; - -import java.util.List; - -import butterknife.Bind; -import butterknife.ButterKnife; -import eu.kanade.tachiyomi.R; -import eu.kanade.tachiyomi.data.download.DownloadService; -import eu.kanade.tachiyomi.data.download.model.Download; -import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment; -import nucleus.factory.RequiresPresenter; -import rx.Subscription; - -@RequiresPresenter(DownloadPresenter.class) -public class DownloadFragment extends BaseRxFragment { - - @Bind(R.id.download_list) RecyclerView recyclerView; - private DownloadAdapter adapter; - - private MenuItem startButton; - private MenuItem pauseButton; - private MenuItem clearButton; - - private Subscription queueStatusSubscription; - private boolean isRunning; - - public static DownloadFragment newInstance() { - return new DownloadFragment(); - } - - @Override - public void onCreate(Bundle bundle) { - super.onCreate(bundle); - setHasOptionsMenu(true); - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - // Inflate the layout for this fragment - View view = inflater.inflate(R.layout.fragment_download_queue, container, false); - ButterKnife.bind(this, view); - - setToolbarTitle(R.string.label_download_queue); - - recyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); - recyclerView.setHasFixedSize(true); - createAdapter(); - - return view; - } - - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - inflater.inflate(R.menu.download_queue, menu); - startButton = menu.findItem(R.id.start_queue); - pauseButton = menu.findItem(R.id.pause_queue); - clearButton = menu.findItem(R.id.clear_queue); - - if(adapter.getItemCount() > 0) { - clearButton.setVisible(true); - } - - // Menu seems to be inflated after onResume in fragments, so we initialize them here - startButton.setVisible(!isRunning && !getPresenter().downloadManager.getQueue().isEmpty()); - pauseButton.setVisible(isRunning); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.start_queue: - DownloadService.start(getActivity()); - break; - case R.id.pause_queue: - DownloadService.stop(getActivity()); - break; - case R.id.clear_queue: - DownloadService.stop(getActivity()); - getPresenter().clearQueue(); - clearButton.setVisible(false); - break; - } - return super.onOptionsItemSelected(item); - } - - @Override - public void onResume() { - super.onResume(); - queueStatusSubscription = getPresenter().downloadManager.getRunningSubject() - .subscribe(this::onRunningChange); - } - - @Override - public void onPause() { - queueStatusSubscription.unsubscribe(); - super.onPause(); - } - - private void onRunningChange(boolean running) { - isRunning = running; - if (startButton != null) - startButton.setVisible(!running && !getPresenter().downloadManager.getQueue().isEmpty()); - if (pauseButton != null) - pauseButton.setVisible(running); - } - - private void createAdapter() { - adapter = new DownloadAdapter(getActivity()); - recyclerView.setAdapter(adapter); - } - - public void onNextDownloads(List downloads) { - adapter.setItems(downloads); - } - - public void updateProgress(Download download) { - DownloadHolder holder = getHolder(download); - if (holder != null) { - holder.setDownloadProgress(download); - } - } - - public void updateDownloadedPages(Download download) { - DownloadHolder holder = getHolder(download); - if (holder != null) { - holder.setDownloadedPages(download); - } - } - - @Nullable - private DownloadHolder getHolder(Download download) { - return (DownloadHolder) recyclerView.findViewHolderForItemId(download.chapter.id); - } - -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadFragment.kt new file mode 100644 index 0000000000..5e3a13db06 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadFragment.kt @@ -0,0 +1,179 @@ +package eu.kanade.tachiyomi.ui.download + +import android.os.Bundle +import android.support.v7.widget.LinearLayoutManager +import android.view.* +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.data.download.DownloadService +import eu.kanade.tachiyomi.data.download.model.Download +import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment +import kotlinx.android.synthetic.main.fragment_download_queue.* +import nucleus.factory.RequiresPresenter +import rx.Subscription + +/** + * Fragment that shows the currently active downloads. + * Uses R.layout.fragment_download_queue. + */ +@RequiresPresenter(DownloadPresenter::class) +class DownloadFragment : BaseRxFragment() { + + /** + * Adapter containing the active downloads. + */ + private lateinit var adapter: DownloadAdapter + + /** + * Menu item to start the queue. + */ + private var startButton: MenuItem? = null + + /** + * Menu item to pause the queue. + */ + private var pauseButton: MenuItem? = null + + /** + * Menu item to clear the queue. + */ + private var clearButton: MenuItem? = null + + /** + * Subscription to know if the download queue is running. + */ + private var queueStatusSubscription: Subscription? = null + + /** + * Whether the download queue is running or not. + */ + private var isRunning: Boolean = false + + companion object { + /** + * Creates a new instance of this fragment. + * + * @return a new instance of [DownloadFragment]. + */ + @JvmStatic + fun newInstance(): DownloadFragment { + return DownloadFragment() + } + } + + override fun onCreate(bundle: Bundle?) { + super.onCreate(bundle) + setHasOptionsMenu(true) + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedState: Bundle?): View? { + return inflater.inflate(R.layout.fragment_download_queue, container, false) + } + + override fun onViewCreated(view: View, savedState: Bundle?) { + setToolbarTitle(R.string.label_download_queue) + + // Initialize adapter. + adapter = DownloadAdapter(activity) + recycler.adapter = adapter + + // Set the layout manager for the recycler and fixed size. + recycler.layoutManager = LinearLayoutManager(activity) + recycler.setHasFixedSize(true) + } + + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + inflater.inflate(R.menu.download_queue, menu) + + // Set start button visibility. + startButton = menu.findItem(R.id.start_queue).apply { + isVisible = !isRunning && !presenter.downloadQueue.isEmpty() + } + + // Set pause button visibility. + pauseButton = menu.findItem(R.id.pause_queue).apply { + isVisible = isRunning + } + + // Set clear button visibility. + clearButton = menu.findItem(R.id.clear_queue).apply { + if (adapter.itemCount > 0) { + isVisible = true + } + } + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.start_queue -> DownloadService.start(activity) + R.id.pause_queue -> DownloadService.stop(activity) + R.id.clear_queue -> { + DownloadService.stop(activity) + presenter.clearQueue() + clearButton?.isVisible = false + } + else -> return super.onOptionsItemSelected(item) + } + return true + } + + override fun onResume() { + super.onResume() + queueStatusSubscription = presenter.downloadManager.runningSubject + .subscribe { onQueueStatusChange(it) } + } + + override fun onPause() { + queueStatusSubscription?.unsubscribe() + super.onPause() + } + + /** + * Called when the queue's status has changed. Updates the visibility of the buttons. + * + * @param running whether the queue is now running or not. + */ + private fun onQueueStatusChange(running: Boolean) { + isRunning = running + startButton?.isVisible = !running && !presenter.downloadQueue.isEmpty() + pauseButton?.isVisible = running + } + + /** + * Called from the presenter to assign the downloads for the adapter. + * + * @param downloads the downloads from the queue. + */ + fun onNextDownloads(downloads: List) { + adapter.setItems(downloads) + } + + /** + * Called from the presenter when the status of a download changes. + * + * @param download the download whose status has changed. + */ + fun onUpdateProgress(download: Download) { + getHolder(download)?.notifyProgress() + } + + /** + * Called from the presenter when a page of a download is downloaded. + * + * @param download the download whose page has been downloaded. + */ + fun onUpdateDownloadedPages(download: Download) { + getHolder(download)?.notifyDownloadedPages() + } + + /** + * Returns the holder for the given download. + * + * @param download the download to find. + * @return the holder of the download or null if it's not bound. + */ + private fun getHolder(download: Download): DownloadHolder? { + return recycler.findViewHolderForItemId(download.chapter.id) as? DownloadHolder + } + +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadHolder.java b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadHolder.java deleted file mode 100644 index c7fa0b324f..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadHolder.java +++ /dev/null @@ -1,49 +0,0 @@ -package eu.kanade.tachiyomi.ui.download; - -import android.support.v7.widget.RecyclerView; -import android.view.View; -import android.widget.ProgressBar; -import android.widget.TextView; - -import butterknife.Bind; -import butterknife.ButterKnife; -import eu.kanade.tachiyomi.R; -import eu.kanade.tachiyomi.data.download.model.Download; - -public class DownloadHolder extends RecyclerView.ViewHolder { - - @Bind(R.id.download_title) TextView downloadTitle; - @Bind(R.id.download_progress) ProgressBar downloadProgress; - @Bind(R.id.download_progress_text) TextView downloadProgressText; - - public DownloadHolder(View view) { - super(view); - ButterKnife.bind(this, view); - } - - public void onSetValues(Download download) { - downloadTitle.setText(download.chapter.name); - - if (download.pages == null) { - downloadProgress.setProgress(0); - downloadProgress.setMax(1); - downloadProgressText.setText(""); - } else { - downloadProgress.setMax(download.pages.size() * 100); - setDownloadProgress(download); - setDownloadedPages(download); - } - } - - public void setDownloadedPages(Download download) { - String progressText = download.downloadedImages + "/" + download.pages.size(); - downloadProgressText.setText(progressText); - } - - public void setDownloadProgress(Download download) { - if (downloadProgress.getMax() == 1) - downloadProgress.setMax(download.pages.size() * 100); - downloadProgress.setProgress(download.totalProgress); - } - -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadHolder.kt new file mode 100644 index 0000000000..75ab822cac --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadHolder.kt @@ -0,0 +1,60 @@ +package eu.kanade.tachiyomi.ui.download + +import android.support.v7.widget.RecyclerView +import android.view.View +import eu.kanade.tachiyomi.data.download.model.Download +import kotlinx.android.synthetic.main.item_download.view.* + +/** + * Class used to hold the data of a download. + * All the elements from the layout file "item_download" are available in this class. + * + * @param view the inflated view for this holder. + * @constructor creates a new library holder. + */ +class DownloadHolder(private val view: View) : RecyclerView.ViewHolder(view) { + + private lateinit var download: Download + + /** + * Method called from [DownloadAdapter.onBindViewHolder]. It updates the data for this + * holder with the given download. + * + * @param download the download to bind. + */ + fun onSetValues(download: Download) { + this.download = download + + // Update the chapter name. + view.download_title.text = download.chapter.name + + // Update the progress bar and the number of downloaded pages + if (download.pages == null) { + view.download_progress.progress = 0 + view.download_progress.max = 1 + view.download_progress_text.text = "" + } else { + view.download_progress.max = download.pages.size * 100 + notifyProgress() + notifyDownloadedPages() + } + } + + /** + * Updates the progress bar of the download. + */ + fun notifyProgress() { + if (view.download_progress.max == 1) { + view.download_progress.max = download.pages.size * 100 + } + view.download_progress.progress = download.totalProgress + } + + /** + * Updates the text field of the number of downloaded pages. + */ + fun notifyDownloadedPages() { + view.download_progress_text.text = "${download.downloadedImages}/${download.pages.size}" + } + +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadPresenter.java b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadPresenter.java deleted file mode 100644 index b6c19a9502..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadPresenter.java +++ /dev/null @@ -1,128 +0,0 @@ -package eu.kanade.tachiyomi.ui.download; - -import android.os.Bundle; - -import java.util.HashMap; -import java.util.concurrent.TimeUnit; - -import javax.inject.Inject; - -import eu.kanade.tachiyomi.data.download.DownloadManager; -import eu.kanade.tachiyomi.data.download.model.Download; -import eu.kanade.tachiyomi.data.download.model.DownloadQueue; -import eu.kanade.tachiyomi.data.source.model.Page; -import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter; -import rx.Observable; -import rx.Subscription; -import rx.android.schedulers.AndroidSchedulers; -import rx.schedulers.Schedulers; -import timber.log.Timber; - -public class DownloadPresenter extends BasePresenter { - - public final static int GET_DOWNLOAD_QUEUE = 1; - @Inject DownloadManager downloadManager; - private DownloadQueue downloadQueue; - private Subscription statusSubscription; - private Subscription pageProgressSubscription; - private HashMap progressSubscriptions; - - @Override - protected void onCreate(Bundle savedState) { - super.onCreate(savedState); - - downloadQueue = downloadManager.getQueue(); - progressSubscriptions = new HashMap<>(); - - restartableLatestCache(GET_DOWNLOAD_QUEUE, - () -> Observable.just(downloadQueue), - DownloadFragment::onNextDownloads, - (view, error) -> Timber.e(error.getMessage())); - - if (savedState == null) - start(GET_DOWNLOAD_QUEUE); - } - - @Override - protected void onTakeView(DownloadFragment view) { - super.onTakeView(view); - - add(statusSubscription = downloadQueue.getStatusObservable() - .startWith(downloadQueue.getActiveDownloads()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(download -> { - processStatus(download, view); - })); - - add(pageProgressSubscription = downloadQueue.getProgressObservable() - .onBackpressureBuffer() - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(view::updateDownloadedPages)); - } - - @Override - protected void onDropView() { - destroySubscriptions(); - super.onDropView(); - } - - private void processStatus(Download download, DownloadFragment view) { - switch (download.getStatus()) { - case Download.DOWNLOADING: - observeProgress(download, view); - // Initial update of the downloaded pages - view.updateDownloadedPages(download); - break; - case Download.DOWNLOADED: - unsubscribeProgress(download); - view.updateProgress(download); - view.updateDownloadedPages(download); - break; - case Download.ERROR: - unsubscribeProgress(download); - break; - } - } - - private void observeProgress(Download download, DownloadFragment view) { - Subscription subscription = Observable.interval(50, TimeUnit.MILLISECONDS, Schedulers.newThread()) - .flatMap(tick -> Observable.from(download.pages) - .map(Page::getProgress) - .reduce((x, y) -> x + y)) - .onBackpressureLatest() - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(progress -> { - if (download.totalProgress != progress) { - download.totalProgress = progress; - view.updateProgress(download); - } - }); - - // Avoid leaking subscriptions - Subscription oldSubscription = progressSubscriptions.remove(download); - if (oldSubscription != null) oldSubscription.unsubscribe(); - - progressSubscriptions.put(download, subscription); - } - - private void unsubscribeProgress(Download download) { - Subscription subscription = progressSubscriptions.remove(download); - if (subscription != null) - subscription.unsubscribe(); - } - - private void destroySubscriptions() { - for (Subscription subscription : progressSubscriptions.values()) { - subscription.unsubscribe(); - } - progressSubscriptions.clear(); - - remove(pageProgressSubscription); - remove(statusSubscription); - } - - public void clearQueue() { - downloadQueue.clear(); - start(GET_DOWNLOAD_QUEUE); - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadPresenter.kt new file mode 100644 index 0000000000..e4cb1d29fc --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadPresenter.kt @@ -0,0 +1,174 @@ +package eu.kanade.tachiyomi.ui.download + +import android.os.Bundle +import eu.kanade.tachiyomi.data.download.DownloadManager +import eu.kanade.tachiyomi.data.download.model.Download +import eu.kanade.tachiyomi.data.download.model.DownloadQueue +import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter +import rx.Observable +import rx.Subscription +import rx.android.schedulers.AndroidSchedulers +import rx.schedulers.Schedulers +import timber.log.Timber +import java.util.* +import java.util.concurrent.TimeUnit +import javax.inject.Inject + +/** + * Presenter of [DownloadFragment]. + */ +class DownloadPresenter : BasePresenter() { + + /** + * Download manager. + */ + @Inject lateinit var downloadManager: DownloadManager + + /** + * Property to get the queue from the download manager. + */ + val downloadQueue: DownloadQueue + get() = downloadManager.queue + + /** + * Map of subscriptions for active downloads. + */ + private val progressSubscriptions by lazy { HashMap() } + + /** + * Subscription for status changes on downloads. + */ + private var statusSubscription: Subscription? = null + + /** + * Subscription for downloaded pages for active downloads. + */ + private var pageProgressSubscription: Subscription? = null + + companion object { + /** + * Id of the restartable that returns the download queue. + */ + const val GET_DOWNLOAD_QUEUE = 1 + } + + override fun onCreate(savedState: Bundle?) { + super.onCreate(savedState) + + restartableLatestCache(GET_DOWNLOAD_QUEUE, + { Observable.just(downloadQueue) }, + { view, downloads -> view.onNextDownloads(downloads) }, + { view, error -> Timber.e(error.message) }) + + if (savedState == null) { + start(GET_DOWNLOAD_QUEUE) + } + } + + override fun onTakeView(view: DownloadFragment) { + super.onTakeView(view) + + statusSubscription = downloadQueue.statusObservable + .startWith(downloadQueue.activeDownloads) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { processStatus(it, view) } + + add(statusSubscription) + + pageProgressSubscription = downloadQueue.progressObservable + .onBackpressureBuffer() + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { view.onUpdateDownloadedPages(it) } + + add(pageProgressSubscription) + } + + override fun onDropView() { + destroySubscriptions() + super.onDropView() + } + + /** + * Process the status of a download when its status has changed and notify the view. + * + * @param download the download whose status has changed. + * @param view the view. + */ + private fun processStatus(download: Download, view: DownloadFragment) { + when (download.status) { + Download.DOWNLOADING -> { + observeProgress(download, view) + // Initial update of the downloaded pages + view.onUpdateDownloadedPages(download) + } + Download.DOWNLOADED -> { + unsubscribeProgress(download) + view.onUpdateProgress(download) + view.onUpdateDownloadedPages(download) + } + Download.ERROR -> unsubscribeProgress(download) + } + } + + /** + * Observe the progress of a download and notify the view. + * + * @param download the download to observe its progress. + * @param view the view. + */ + private fun observeProgress(download: Download, view: DownloadFragment) { + val subscription = Observable.interval(50, TimeUnit.MILLISECONDS, Schedulers.newThread()) + // Get the sum of percentages for all the pages. + .flatMap { + Observable.from(download.pages) + .map { it.progress } + .reduce { x, y -> x + y } + } + // Keep only the latest emission to avoid backpressure. + .onBackpressureLatest() + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { progress -> + // Update the view only if the progress has changed. + if (download.totalProgress != progress) { + download.totalProgress = progress + view.onUpdateProgress(download) + } + } + + // Avoid leaking subscriptions + progressSubscriptions.remove(download)?.unsubscribe() + + progressSubscriptions.put(download, subscription) + } + + /** + * Unsubscribes the given download from the progress subscriptions. + * + * @param download the download to unsubscribe. + */ + private fun unsubscribeProgress(download: Download) { + progressSubscriptions.remove(download)?.unsubscribe() + } + + /** + * Destroys all the subscriptions of the presenter. + */ + private fun destroySubscriptions() { + for (subscription in progressSubscriptions.values) { + subscription.unsubscribe() + } + progressSubscriptions.clear() + + remove(pageProgressSubscription) + remove(statusSubscription) + } + + /** + * Clears the download queue. + */ + fun clearQueue() { + downloadQueue.clear() + start(GET_DOWNLOAD_QUEUE) + } + +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.kt index 16a3ef22c9..81eb726da2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.kt @@ -79,11 +79,6 @@ class LibraryFragment : BaseRxFragment(), ActionMode.Callback */ const val REQUEST_IMAGE_OPEN = 101 - /** - * Key to add a manga to an [Intent]. - */ - const val MANGA_EXTRA = "manga_extra" - /** * Key to save and restore [query] from a [Bundle]. */ diff --git a/app/src/main/res/layout/fragment_download_queue.xml b/app/src/main/res/layout/fragment_download_queue.xml index 9156977127..5bec7ea126 100644 --- a/app/src/main/res/layout/fragment_download_queue.xml +++ b/app/src/main/res/layout/fragment_download_queue.xml @@ -6,7 +6,7 @@ + android:id="@+id/recycler">