mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-10-31 14:27:57 +01:00 
			
		
		
		
	Download queue's UI in Kotlin
This commit is contained in:
		| @@ -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<DownloadHolder, Download> { | ||||
|  | ||||
|     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<Download> downloads) { | ||||
|         mItems = downloads; | ||||
|         notifyDataSetChanged(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void updateDataSet(String param) {} | ||||
|  | ||||
| } | ||||
| @@ -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<DownloadHolder, Download>() { | ||||
|  | ||||
|     init { | ||||
|         setHasStableIds(true) | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Sets a list of downloads in the adapter. | ||||
|      * | ||||
|      * @param downloads the list to set. | ||||
|      */ | ||||
|     fun setItems(downloads: List<Download>) { | ||||
|         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) { | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -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<DownloadPresenter> { | ||||
|  | ||||
|     @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<Download> 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); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -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<DownloadPresenter>() { | ||||
|  | ||||
|     /** | ||||
|      * 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<Download>) { | ||||
|         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 | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -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); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -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}" | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -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<DownloadFragment> { | ||||
|  | ||||
|     public final static int GET_DOWNLOAD_QUEUE = 1; | ||||
|     @Inject DownloadManager downloadManager; | ||||
|     private DownloadQueue downloadQueue; | ||||
|     private Subscription statusSubscription; | ||||
|     private Subscription pageProgressSubscription; | ||||
|     private HashMap<Download, Subscription> 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); | ||||
|     } | ||||
| } | ||||
| @@ -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<DownloadFragment>() { | ||||
|  | ||||
|     /** | ||||
|      * 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<Download, Subscription>() } | ||||
|  | ||||
|     /** | ||||
|      * 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) | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -79,11 +79,6 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), 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]. | ||||
|          */ | ||||
|   | ||||
		Reference in New Issue
	
	Block a user