|  | @@ -1,577 +1,577 @@
 | 
	
		
			
				|  |  | -package eu.kanade.tachiyomi.ui.manga.info
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -import android.app.Dialog
 | 
	
		
			
				|  |  | -import android.app.PendingIntent
 | 
	
		
			
				|  |  | -import android.content.ClipData
 | 
	
		
			
				|  |  | -import android.content.ClipboardManager
 | 
	
		
			
				|  |  | -import android.content.Context
 | 
	
		
			
				|  |  | -import android.content.Intent
 | 
	
		
			
				|  |  | -import android.graphics.Bitmap
 | 
	
		
			
				|  |  | -import android.graphics.drawable.Drawable
 | 
	
		
			
				|  |  | -import android.net.Uri
 | 
	
		
			
				|  |  | -import android.os.Build
 | 
	
		
			
				|  |  | -import android.os.Bundle
 | 
	
		
			
				|  |  | -import android.support.customtabs.CustomTabsIntent
 | 
	
		
			
				|  |  | -import android.support.v4.content.pm.ShortcutInfoCompat
 | 
	
		
			
				|  |  | -import android.support.v4.content.pm.ShortcutManagerCompat
 | 
	
		
			
				|  |  | -import android.support.v4.graphics.drawable.IconCompat
 | 
	
		
			
				|  |  | -import android.view.*
 | 
	
		
			
				|  |  | -import android.widget.Toast
 | 
	
		
			
				|  |  | -import com.afollestad.materialdialogs.MaterialDialog
 | 
	
		
			
				|  |  | -import com.bumptech.glide.load.engine.DiskCacheStrategy
 | 
	
		
			
				|  |  | -import com.bumptech.glide.load.resource.bitmap.RoundedCorners
 | 
	
		
			
				|  |  | -import com.bumptech.glide.request.target.SimpleTarget
 | 
	
		
			
				|  |  | -import com.bumptech.glide.request.transition.Transition
 | 
	
		
			
				|  |  | -import com.jakewharton.rxbinding.support.v4.widget.refreshes
 | 
	
		
			
				|  |  | -import com.jakewharton.rxbinding.view.clicks
 | 
	
		
			
				|  |  | -import com.jakewharton.rxbinding.view.longClicks
 | 
	
		
			
				|  |  | -import eu.kanade.tachiyomi.R
 | 
	
		
			
				|  |  | -import eu.kanade.tachiyomi.data.database.models.Category
 | 
	
		
			
				|  |  | -import eu.kanade.tachiyomi.data.database.models.Manga
 | 
	
		
			
				|  |  | -import eu.kanade.tachiyomi.data.glide.GlideApp
 | 
	
		
			
				|  |  | -import eu.kanade.tachiyomi.data.notification.NotificationReceiver
 | 
	
		
			
				|  |  | -import eu.kanade.tachiyomi.data.preference.PreferencesHelper
 | 
	
		
			
				|  |  | -import eu.kanade.tachiyomi.source.Source
 | 
	
		
			
				|  |  | -import eu.kanade.tachiyomi.source.model.SManga
 | 
	
		
			
				|  |  | -import eu.kanade.tachiyomi.source.online.HttpSource
 | 
	
		
			
				|  |  | -import eu.kanade.tachiyomi.ui.base.controller.DialogController
 | 
	
		
			
				|  |  | -import eu.kanade.tachiyomi.ui.base.controller.NucleusController
 | 
	
		
			
				|  |  | -import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
 | 
	
		
			
				|  |  | -import eu.kanade.tachiyomi.ui.catalogue.global_search.CatalogueSearchController
 | 
	
		
			
				|  |  | -import eu.kanade.tachiyomi.ui.library.ChangeMangaCategoriesDialog
 | 
	
		
			
				|  |  | -import eu.kanade.tachiyomi.ui.main.MainActivity
 | 
	
		
			
				|  |  | -import eu.kanade.tachiyomi.ui.manga.MangaController
 | 
	
		
			
				|  |  | -import eu.kanade.tachiyomi.util.getResourceColor
 | 
	
		
			
				|  |  | -import eu.kanade.tachiyomi.util.openInBrowser
 | 
	
		
			
				|  |  | -import eu.kanade.tachiyomi.util.snack
 | 
	
		
			
				|  |  | -import eu.kanade.tachiyomi.util.toast
 | 
	
		
			
				|  |  | -import eu.kanade.tachiyomi.util.truncateCenter
 | 
	
		
			
				|  |  | -import jp.wasabeef.glide.transformations.CropSquareTransformation
 | 
	
		
			
				|  |  | -import jp.wasabeef.glide.transformations.MaskTransformation
 | 
	
		
			
				|  |  | -import kotlinx.android.synthetic.main.manga_info_controller.*
 | 
	
		
			
				|  |  | -import uy.kohesive.injekt.injectLazy
 | 
	
		
			
				|  |  | -import java.text.DateFormat
 | 
	
		
			
				|  |  | -import java.text.DecimalFormat
 | 
	
		
			
				|  |  | -import java.util.Date
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -/**
 | 
	
		
			
				|  |  | - * Fragment that shows manga information.
 | 
	
		
			
				|  |  | - * Uses R.layout.manga_info_controller.
 | 
	
		
			
				|  |  | - * UI related actions should be called from here.
 | 
	
		
			
				|  |  | - */
 | 
	
		
			
				|  |  | -class MangaInfoController : NucleusController<MangaInfoPresenter>(),
 | 
	
		
			
				|  |  | -        ChangeMangaCategoriesDialog.Listener {
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    /**
 | 
	
		
			
				|  |  | -     * Preferences helper.
 | 
	
		
			
				|  |  | -     */
 | 
	
		
			
				|  |  | -    private val preferences: PreferencesHelper by injectLazy()
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    init {
 | 
	
		
			
				|  |  | -        setHasOptionsMenu(true)
 | 
	
		
			
				|  |  | -        setOptionsMenuHidden(true)
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    override fun createPresenter(): MangaInfoPresenter {
 | 
	
		
			
				|  |  | -        val ctrl = parentController as MangaController
 | 
	
		
			
				|  |  | -        return MangaInfoPresenter(ctrl.manga!!, ctrl.source!!,
 | 
	
		
			
				|  |  | -                ctrl.chapterCountRelay, ctrl.lastUpdateRelay, ctrl.mangaFavoriteRelay)
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
 | 
	
		
			
				|  |  | -        return inflater.inflate(R.layout.manga_info_controller, container, false)
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    override fun onViewCreated(view: View) {
 | 
	
		
			
				|  |  | -        super.onViewCreated(view)
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        // Set onclickListener to toggle favorite when FAB clicked.
 | 
	
		
			
				|  |  | -        fab_favorite.clicks().subscribeUntilDestroy { onFabClick() }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        // Set onLongClickListener to manage categories when FAB is clicked.
 | 
	
		
			
				|  |  | -        fab_favorite.longClicks().subscribeUntilDestroy{ onFabLongClick() }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        // Set SwipeRefresh to refresh manga data.
 | 
	
		
			
				|  |  | -        swipe_refresh.refreshes().subscribeUntilDestroy { fetchMangaFromSource() }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        manga_full_title.longClicks().subscribeUntilDestroy {
 | 
	
		
			
				|  |  | -            copyToClipboard(view.context.getString(R.string.title), manga_full_title.text.toString())
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        manga_full_title.clicks().subscribeUntilDestroy {
 | 
	
		
			
				|  |  | -            performGlobalSearch(manga_full_title.text.toString())
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        manga_artist.longClicks().subscribeUntilDestroy {
 | 
	
		
			
				|  |  | -            copyToClipboard(manga_artist_label.text.toString(), manga_artist.text.toString())
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        manga_artist.clicks().subscribeUntilDestroy {
 | 
	
		
			
				|  |  | -            performGlobalSearch(manga_artist.text.toString())
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        manga_author.longClicks().subscribeUntilDestroy {
 | 
	
		
			
				|  |  | -            copyToClipboard(manga_author.text.toString(), manga_author.text.toString())
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        manga_author.clicks().subscribeUntilDestroy {
 | 
	
		
			
				|  |  | -            performGlobalSearch(manga_author.text.toString())
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        manga_summary.longClicks().subscribeUntilDestroy {
 | 
	
		
			
				|  |  | -            copyToClipboard(view.context.getString(R.string.description), manga_summary.text.toString())
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        //manga_genres_tags.setOnTagClickListener { tag -> performGlobalSearch(tag) }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        manga_cover.longClicks().subscribeUntilDestroy {
 | 
	
		
			
				|  |  | -            copyToClipboard(view.context.getString(R.string.title), presenter.manga.title)
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
 | 
	
		
			
				|  |  | -        inflater.inflate(R.menu.manga_info, menu)
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    override fun onOptionsItemSelected(item: MenuItem): Boolean {
 | 
	
		
			
				|  |  | -        when (item.itemId) {
 | 
	
		
			
				|  |  | -            R.id.action_open_in_browser -> openInBrowser()
 | 
	
		
			
				|  |  | -            R.id.action_open_in_web_view -> openInWebView()
 | 
	
		
			
				|  |  | -            R.id.action_share -> shareManga()
 | 
	
		
			
				|  |  | -            R.id.action_add_to_home_screen -> addToHomeScreen()
 | 
	
		
			
				|  |  | -            else -> return super.onOptionsItemSelected(item)
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -        return true
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    /**
 | 
	
		
			
				|  |  | -     * Check if manga is initialized.
 | 
	
		
			
				|  |  | -     * If true update view with manga information,
 | 
	
		
			
				|  |  | -     * if false fetch manga information
 | 
	
		
			
				|  |  | -     *
 | 
	
		
			
				|  |  | -     * @param manga  manga object containing information about manga.
 | 
	
		
			
				|  |  | -     * @param source the source of the manga.
 | 
	
		
			
				|  |  | -     */
 | 
	
		
			
				|  |  | -    fun onNextManga(manga: Manga, source: Source) {
 | 
	
		
			
				|  |  | -        if (manga.initialized) {
 | 
	
		
			
				|  |  | -            // Update view.
 | 
	
		
			
				|  |  | -            setMangaInfo(manga, source)
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        } else {
 | 
	
		
			
				|  |  | -            // Initialize manga.
 | 
	
		
			
				|  |  | -            fetchMangaFromSource()
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    /**
 | 
	
		
			
				|  |  | -     * Update the view with manga information.
 | 
	
		
			
				|  |  | -     *
 | 
	
		
			
				|  |  | -     * @param manga manga object containing information about manga.
 | 
	
		
			
				|  |  | -     * @param source the source of the manga.
 | 
	
		
			
				|  |  | -     */
 | 
	
		
			
				|  |  | -    private fun setMangaInfo(manga: Manga, source: Source?) {
 | 
	
		
			
				|  |  | -        val view = view ?: return
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        //update full title TextView.
 | 
	
		
			
				|  |  | -        manga_full_title.text = if (manga.title.isBlank()) {
 | 
	
		
			
				|  |  | -            view.context.getString(R.string.unknown)
 | 
	
		
			
				|  |  | -        } else {
 | 
	
		
			
				|  |  | -            manga.title
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        // Update artist TextView.
 | 
	
		
			
				|  |  | -        manga_artist.text = if (manga.artist.isNullOrBlank()) {
 | 
	
		
			
				|  |  | -            view.context.getString(R.string.unknown)
 | 
	
		
			
				|  |  | -        } else {
 | 
	
		
			
				|  |  | -            manga.artist
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        // Update author TextView.
 | 
	
		
			
				|  |  | -        manga_author.text = if (manga.author.isNullOrBlank()) {
 | 
	
		
			
				|  |  | -            view.context.getString(R.string.unknown)
 | 
	
		
			
				|  |  | -        } else {
 | 
	
		
			
				|  |  | -            manga.author
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        // If manga source is known update source TextView.
 | 
	
		
			
				|  |  | -        manga_source.text = if (source == null) {
 | 
	
		
			
				|  |  | -            view.context.getString(R.string.unknown)
 | 
	
		
			
				|  |  | -        } else {
 | 
	
		
			
				|  |  | -            source.toString()
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        // Update genres list
 | 
	
		
			
				|  |  | -        if (manga.genre.isNullOrBlank().not()) {
 | 
	
		
			
				|  |  | -            manga_genres_tags.setTags(manga.genre?.split(", "))
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        // Update description TextView.
 | 
	
		
			
				|  |  | -        manga_summary.text = if (manga.description.isNullOrBlank()) {
 | 
	
		
			
				|  |  | -            view.context.getString(R.string.unknown)
 | 
	
		
			
				|  |  | -        } else {
 | 
	
		
			
				|  |  | -            manga.description
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        // Update status TextView.
 | 
	
		
			
				|  |  | -        manga_status.setText(when (manga.status) {
 | 
	
		
			
				|  |  | -            SManga.ONGOING -> R.string.ongoing
 | 
	
		
			
				|  |  | -            SManga.COMPLETED -> R.string.completed
 | 
	
		
			
				|  |  | -            SManga.LICENSED -> R.string.licensed
 | 
	
		
			
				|  |  | -            else -> R.string.unknown
 | 
	
		
			
				|  |  | -        })
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        // Set the favorite drawable to the correct one.
 | 
	
		
			
				|  |  | -        setFavoriteDrawable(manga.favorite)
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        // Set cover if it wasn't already.
 | 
	
		
			
				|  |  | -        if (manga_cover.drawable == null && !manga.thumbnail_url.isNullOrEmpty()) {
 | 
	
		
			
				|  |  | -            GlideApp.with(view.context)
 | 
	
		
			
				|  |  | -                    .load(manga)
 | 
	
		
			
				|  |  | -                    .diskCacheStrategy(DiskCacheStrategy.RESOURCE)
 | 
	
		
			
				|  |  | -                    .centerCrop()
 | 
	
		
			
				|  |  | -                    .into(manga_cover)
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -            if (backdrop != null) {
 | 
	
		
			
				|  |  | -                GlideApp.with(view.context)
 | 
	
		
			
				|  |  | -                        .load(manga)
 | 
	
		
			
				|  |  | -                        .diskCacheStrategy(DiskCacheStrategy.RESOURCE)
 | 
	
		
			
				|  |  | -                        .centerCrop()
 | 
	
		
			
				|  |  | -                        .into(backdrop)
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    override fun onDestroyView(view: View) {
 | 
	
		
			
				|  |  | -        manga_genres_tags.setOnTagClickListener(null)
 | 
	
		
			
				|  |  | -        super.onDestroyView(view)
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    /**
 | 
	
		
			
				|  |  | -     * Update chapter count TextView.
 | 
	
		
			
				|  |  | -     *
 | 
	
		
			
				|  |  | -     * @param count number of chapters.
 | 
	
		
			
				|  |  | -     */
 | 
	
		
			
				|  |  | -    fun setChapterCount(count: Float) {
 | 
	
		
			
				|  |  | -        if (count > 0f) {
 | 
	
		
			
				|  |  | -            manga_chapters?.text = DecimalFormat("#.#").format(count)
 | 
	
		
			
				|  |  | -        } else {
 | 
	
		
			
				|  |  | -            manga_chapters?.text = resources?.getString(R.string.unknown)
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    fun setLastUpdateDate(date: Date) {
 | 
	
		
			
				|  |  | -        if (date.time != 0L) {
 | 
	
		
			
				|  |  | -            manga_last_update?.text = DateFormat.getDateInstance(DateFormat.SHORT).format(date)
 | 
	
		
			
				|  |  | -        } else {
 | 
	
		
			
				|  |  | -            manga_last_update?.text = resources?.getString(R.string.unknown)
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    /**
 | 
	
		
			
				|  |  | -     * Toggles the favorite status and asks for confirmation to delete downloaded chapters.
 | 
	
		
			
				|  |  | -     */
 | 
	
		
			
				|  |  | -    private fun toggleFavorite() {
 | 
	
		
			
				|  |  | -        val view = view
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        val isNowFavorite = presenter.toggleFavorite()
 | 
	
		
			
				|  |  | -        if (view != null && !isNowFavorite && presenter.hasDownloads()) {
 | 
	
		
			
				|  |  | -            view.snack(view.context.getString(R.string.delete_downloads_for_manga)) {
 | 
	
		
			
				|  |  | -                setAction(R.string.action_delete) {
 | 
	
		
			
				|  |  | -                    presenter.deleteDownloads()
 | 
	
		
			
				|  |  | -                }
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    /**
 | 
	
		
			
				|  |  | -     * Open the manga in browser.
 | 
	
		
			
				|  |  | -     */
 | 
	
		
			
				|  |  | -    private fun openInBrowser() {
 | 
	
		
			
				|  |  | -        val context = view?.context ?: return
 | 
	
		
			
				|  |  | -        val source = presenter.source as? HttpSource ?: return
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        context.openInBrowser(source.mangaDetailsRequest(presenter.manga).url().toString())
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    private fun openInWebView() {
 | 
	
		
			
				|  |  | -        val source = presenter.source as? HttpSource ?: return
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        val url = try {
 | 
	
		
			
				|  |  | -            source.mangaDetailsRequest(presenter.manga).url().toString()
 | 
	
		
			
				|  |  | -        } catch (e: Exception) {
 | 
	
		
			
				|  |  | -            return
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        parentController?.router?.pushController(MangaWebViewController(source.id, url)
 | 
	
		
			
				|  |  | -            .withFadeTransaction())
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    /**
 | 
	
		
			
				|  |  | -     * Called to run Intent with [Intent.ACTION_SEND], which show share dialog.
 | 
	
		
			
				|  |  | -     */
 | 
	
		
			
				|  |  | -    private fun shareManga() {
 | 
	
		
			
				|  |  | -        val context = view?.context ?: return
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        val source = presenter.source as? HttpSource ?: return
 | 
	
		
			
				|  |  | -        try {
 | 
	
		
			
				|  |  | -            val url = source.mangaDetailsRequest(presenter.manga).url().toString()
 | 
	
		
			
				|  |  | -            val intent = Intent(Intent.ACTION_SEND).apply {
 | 
	
		
			
				|  |  | -                type = "text/plain"
 | 
	
		
			
				|  |  | -                putExtra(Intent.EXTRA_TEXT, url)
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | -            startActivity(Intent.createChooser(intent, context.getString(R.string.action_share)))
 | 
	
		
			
				|  |  | -        } catch (e: Exception) {
 | 
	
		
			
				|  |  | -            context.toast(e.message)
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    /**
 | 
	
		
			
				|  |  | -     * Update FAB with correct drawable.
 | 
	
		
			
				|  |  | -     *
 | 
	
		
			
				|  |  | -     * @param isFavorite determines if manga is favorite or not.
 | 
	
		
			
				|  |  | -     */
 | 
	
		
			
				|  |  | -    private fun setFavoriteDrawable(isFavorite: Boolean) {
 | 
	
		
			
				|  |  | -        // Set the Favorite drawable to the correct one.
 | 
	
		
			
				|  |  | -        // Border drawable if false, filled drawable if true.
 | 
	
		
			
				|  |  | -        fab_favorite?.setImageResource(if (isFavorite)
 | 
	
		
			
				|  |  | -            R.drawable.ic_bookmark_white_24dp
 | 
	
		
			
				|  |  | -        else
 | 
	
		
			
				|  |  | -            R.drawable.ic_add_to_library_24dp)
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    /**
 | 
	
		
			
				|  |  | -     * Start fetching manga information from source.
 | 
	
		
			
				|  |  | -     */
 | 
	
		
			
				|  |  | -    private fun fetchMangaFromSource() {
 | 
	
		
			
				|  |  | -        setRefreshing(true)
 | 
	
		
			
				|  |  | -        // Call presenter and start fetching manga information
 | 
	
		
			
				|  |  | -        presenter.fetchMangaFromSource()
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    /**
 | 
	
		
			
				|  |  | -     * Update swipe refresh to stop showing refresh in progress spinner.
 | 
	
		
			
				|  |  | -     */
 | 
	
		
			
				|  |  | -    fun onFetchMangaDone() {
 | 
	
		
			
				|  |  | -        setRefreshing(false)
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    /**
 | 
	
		
			
				|  |  | -     * Update swipe refresh to start showing refresh in progress spinner.
 | 
	
		
			
				|  |  | -     */
 | 
	
		
			
				|  |  | -    fun onFetchMangaError(error: Throwable) {
 | 
	
		
			
				|  |  | -        setRefreshing(false)
 | 
	
		
			
				|  |  | -        activity?.toast(error.message)
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    /**
 | 
	
		
			
				|  |  | -     * Set swipe refresh status.
 | 
	
		
			
				|  |  | -     *
 | 
	
		
			
				|  |  | -     * @param value whether it should be refreshing or not.
 | 
	
		
			
				|  |  | -     */
 | 
	
		
			
				|  |  | -    private fun setRefreshing(value: Boolean) {
 | 
	
		
			
				|  |  | -        swipe_refresh?.isRefreshing = value
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    /**
 | 
	
		
			
				|  |  | -     * Called when the fab is clicked.
 | 
	
		
			
				|  |  | -     */
 | 
	
		
			
				|  |  | -    private fun onFabClick() {
 | 
	
		
			
				|  |  | -        val manga = presenter.manga
 | 
	
		
			
				|  |  | -        toggleFavorite()
 | 
	
		
			
				|  |  | -        if (manga.favorite) {
 | 
	
		
			
				|  |  | -            val categories = presenter.getCategories()
 | 
	
		
			
				|  |  | -            val defaultCategoryId = preferences.defaultCategory()
 | 
	
		
			
				|  |  | -            val defaultCategory = categories.find { it.id == defaultCategoryId }
 | 
	
		
			
				|  |  | -            when {
 | 
	
		
			
				|  |  | -                defaultCategory != null -> presenter.moveMangaToCategory(manga, defaultCategory)
 | 
	
		
			
				|  |  | -                defaultCategoryId == 0 || categories.isEmpty() -> // 'Default' or no category
 | 
	
		
			
				|  |  | -                    presenter.moveMangaToCategory(manga, null)
 | 
	
		
			
				|  |  | -                else -> {
 | 
	
		
			
				|  |  | -                    val ids = presenter.getMangaCategoryIds(manga)
 | 
	
		
			
				|  |  | -                    val preselected = ids.mapNotNull { id ->
 | 
	
		
			
				|  |  | -                        categories.indexOfFirst { it.id == id }.takeIf { it != -1 }
 | 
	
		
			
				|  |  | -                    }.toTypedArray()
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                    ChangeMangaCategoriesDialog(this, listOf(manga), categories, preselected)
 | 
	
		
			
				|  |  | -                            .showDialog(router)
 | 
	
		
			
				|  |  | -                }
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | -            activity?.toast(activity?.getString(R.string.manga_added_library))
 | 
	
		
			
				|  |  | -        } else {
 | 
	
		
			
				|  |  | -            activity?.toast(activity?.getString(R.string.manga_removed_library))
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    /**
 | 
	
		
			
				|  |  | -     * Called when the fab is long clicked.
 | 
	
		
			
				|  |  | -     */
 | 
	
		
			
				|  |  | -    private fun onFabLongClick() {
 | 
	
		
			
				|  |  | -        val manga = presenter.manga
 | 
	
		
			
				|  |  | -        if (!manga.favorite) {
 | 
	
		
			
				|  |  | -            toggleFavorite()
 | 
	
		
			
				|  |  | -            activity?.toast(activity?.getString(R.string.manga_added_library))
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -        val categories = presenter.getCategories()
 | 
	
		
			
				|  |  | -        if (categories.isEmpty()) {
 | 
	
		
			
				|  |  | -            // no categories exist, display a message about adding categories
 | 
	
		
			
				|  |  | -            activity?.toast(activity?.getString(R.string.action_add_category))
 | 
	
		
			
				|  |  | -        } else {
 | 
	
		
			
				|  |  | -            val ids = presenter.getMangaCategoryIds(manga)
 | 
	
		
			
				|  |  | -            val preselected = ids.mapNotNull { id ->
 | 
	
		
			
				|  |  | -                categories.indexOfFirst { it.id == id }.takeIf { it != -1 }
 | 
	
		
			
				|  |  | -            }.toTypedArray()
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -            ChangeMangaCategoriesDialog(this, listOf(manga), categories, preselected)
 | 
	
		
			
				|  |  | -                    .showDialog(router)
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    override fun updateCategoriesForMangas(mangas: List<Manga>, categories: List<Category>) {
 | 
	
		
			
				|  |  | -        val manga = mangas.firstOrNull() ?: return
 | 
	
		
			
				|  |  | -        presenter.moveMangaToCategories(manga, categories)
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    /**
 | 
	
		
			
				|  |  | -     * Add a shortcut of the manga to the home screen
 | 
	
		
			
				|  |  | -     */
 | 
	
		
			
				|  |  | -    private fun addToHomeScreen() {
 | 
	
		
			
				|  |  | -        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
 | 
	
		
			
				|  |  | -            // TODO are transformations really unsupported or is it just the Pixel Launcher?
 | 
	
		
			
				|  |  | -            createShortcutForShape()
 | 
	
		
			
				|  |  | -        } else {
 | 
	
		
			
				|  |  | -            ChooseShapeDialog(this).showDialog(router)
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    /**
 | 
	
		
			
				|  |  | -     * Dialog to choose a shape for the icon.
 | 
	
		
			
				|  |  | -     */
 | 
	
		
			
				|  |  | -    private class ChooseShapeDialog(bundle: Bundle? = null) : DialogController(bundle) {
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        constructor(target: MangaInfoController) : this() {
 | 
	
		
			
				|  |  | -            targetController = target
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        override fun onCreateDialog(savedViewState: Bundle?): Dialog {
 | 
	
		
			
				|  |  | -            val modes = intArrayOf(R.string.circular_icon,
 | 
	
		
			
				|  |  | -                    R.string.rounded_icon,
 | 
	
		
			
				|  |  | -                    R.string.square_icon,
 | 
	
		
			
				|  |  | -                    R.string.star_icon)
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -            return MaterialDialog.Builder(activity!!)
 | 
	
		
			
				|  |  | -                    .title(R.string.icon_shape)
 | 
	
		
			
				|  |  | -                    .negativeText(android.R.string.cancel)
 | 
	
		
			
				|  |  | -                    .items(modes.map { activity?.getString(it) })
 | 
	
		
			
				|  |  | -                    .itemsCallback { _, _, i, _ ->
 | 
	
		
			
				|  |  | -                        (targetController as? MangaInfoController)?.createShortcutForShape(i)
 | 
	
		
			
				|  |  | -                    }
 | 
	
		
			
				|  |  | -                    .build()
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    /**
 | 
	
		
			
				|  |  | -     * Retrieves the bitmap of the shortcut with the requested shape and calls [createShortcut] when
 | 
	
		
			
				|  |  | -     * the resource is available.
 | 
	
		
			
				|  |  | -     *
 | 
	
		
			
				|  |  | -     * @param i The shape index to apply. Defaults to circle crop transformation.
 | 
	
		
			
				|  |  | -     */
 | 
	
		
			
				|  |  | -    private fun createShortcutForShape(i: Int = 0) {
 | 
	
		
			
				|  |  | -        if (activity == null) return
 | 
	
		
			
				|  |  | -        GlideApp.with(activity!!)
 | 
	
		
			
				|  |  | -                .asBitmap()
 | 
	
		
			
				|  |  | -                .load(presenter.manga)
 | 
	
		
			
				|  |  | -                .diskCacheStrategy(DiskCacheStrategy.NONE)
 | 
	
		
			
				|  |  | -                .apply {
 | 
	
		
			
				|  |  | -                    when (i) {
 | 
	
		
			
				|  |  | -                        0 -> circleCrop()
 | 
	
		
			
				|  |  | -                        1 -> transform(RoundedCorners(5))
 | 
	
		
			
				|  |  | -                        2 -> transform(CropSquareTransformation())
 | 
	
		
			
				|  |  | -                        3 -> centerCrop().transform(MaskTransformation(R.drawable.mask_star))
 | 
	
		
			
				|  |  | -                    }
 | 
	
		
			
				|  |  | -                }
 | 
	
		
			
				|  |  | -                .into(object : SimpleTarget<Bitmap>(96, 96) {
 | 
	
		
			
				|  |  | -                    override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
 | 
	
		
			
				|  |  | -                        createShortcut(resource)
 | 
	
		
			
				|  |  | -                    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                    override fun onLoadFailed(errorDrawable: Drawable?) {
 | 
	
		
			
				|  |  | -                        activity?.toast(R.string.icon_creation_fail)
 | 
	
		
			
				|  |  | -                    }
 | 
	
		
			
				|  |  | -                })
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    /**
 | 
	
		
			
				|  |  | -     * Copies a string to clipboard
 | 
	
		
			
				|  |  | -     *
 | 
	
		
			
				|  |  | -     * @param label Label to show to the user describing the content
 | 
	
		
			
				|  |  | -     * @param content the actual text to copy to the board
 | 
	
		
			
				|  |  | -     */
 | 
	
		
			
				|  |  | -    private fun copyToClipboard(label: String, content: String) {
 | 
	
		
			
				|  |  | -        if (content.isBlank()) return
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        val activity = activity ?: return
 | 
	
		
			
				|  |  | -        val view = view ?: return
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        val clipboard = activity.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
 | 
	
		
			
				|  |  | -        clipboard.primaryClip = ClipData.newPlainText(label, content)
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        activity.toast(view.context.getString(R.string.copied_to_clipboard, content.truncateCenter(20)),
 | 
	
		
			
				|  |  | -                Toast.LENGTH_SHORT)
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    /**
 | 
	
		
			
				|  |  | -     * Perform a global search using the provided query.
 | 
	
		
			
				|  |  | -     *
 | 
	
		
			
				|  |  | -     * @param query the search query to pass to the search controller
 | 
	
		
			
				|  |  | -     */
 | 
	
		
			
				|  |  | -    fun performGlobalSearch(query: String) {
 | 
	
		
			
				|  |  | -        val router = parentController?.router ?: return
 | 
	
		
			
				|  |  | -        router.pushController(CatalogueSearchController(query).withFadeTransaction())
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    /**
 | 
	
		
			
				|  |  | -     * Create shortcut using ShortcutManager.
 | 
	
		
			
				|  |  | -     *
 | 
	
		
			
				|  |  | -     * @param icon The image of the shortcut.
 | 
	
		
			
				|  |  | -     */
 | 
	
		
			
				|  |  | -    private fun createShortcut(icon: Bitmap) {
 | 
	
		
			
				|  |  | -        val activity = activity ?: return
 | 
	
		
			
				|  |  | -        val mangaControllerArgs = parentController?.args ?: return
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        // Create the shortcut intent.
 | 
	
		
			
				|  |  | -        val shortcutIntent = activity.intent
 | 
	
		
			
				|  |  | -                .setAction(MainActivity.SHORTCUT_MANGA)
 | 
	
		
			
				|  |  | -                .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
 | 
	
		
			
				|  |  | -                .putExtra(MangaController.MANGA_EXTRA,
 | 
	
		
			
				|  |  | -                        mangaControllerArgs.getLong(MangaController.MANGA_EXTRA))
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        // Check if shortcut placement is supported
 | 
	
		
			
				|  |  | -        if (ShortcutManagerCompat.isRequestPinShortcutSupported(activity)) {
 | 
	
		
			
				|  |  | -            val shortcutId = "manga-shortcut-${presenter.manga.title}-${presenter.source.name}"
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -            // Create shortcut info
 | 
	
		
			
				|  |  | -            val shortcutInfo = ShortcutInfoCompat.Builder(activity, shortcutId)
 | 
	
		
			
				|  |  | -                    .setShortLabel(presenter.manga.title)
 | 
	
		
			
				|  |  | -                    .setIcon(IconCompat.createWithBitmap(icon))
 | 
	
		
			
				|  |  | -                    .setIntent(shortcutIntent)
 | 
	
		
			
				|  |  | -                    .build()
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -            val successCallback = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
 | 
	
		
			
				|  |  | -                // Create the CallbackIntent.
 | 
	
		
			
				|  |  | -                val intent = ShortcutManagerCompat.createShortcutResultIntent(activity, shortcutInfo)
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                // Configure the intent so that the broadcast receiver gets the callback successfully.
 | 
	
		
			
				|  |  | -                PendingIntent.getBroadcast(activity, 0, intent, 0)
 | 
	
		
			
				|  |  | -            } else {
 | 
	
		
			
				|  |  | -                NotificationReceiver.shortcutCreatedBroadcast(activity)
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -            // Request shortcut.
 | 
	
		
			
				|  |  | -            ShortcutManagerCompat.requestPinShortcut(activity, shortcutInfo,
 | 
	
		
			
				|  |  | -                    successCallback.intentSender)
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | +package eu.kanade.tachiyomi.ui.manga.info
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +import android.app.Dialog
 | 
	
		
			
				|  |  | +import android.app.PendingIntent
 | 
	
		
			
				|  |  | +import android.content.ClipData
 | 
	
		
			
				|  |  | +import android.content.ClipboardManager
 | 
	
		
			
				|  |  | +import android.content.Context
 | 
	
		
			
				|  |  | +import android.content.Intent
 | 
	
		
			
				|  |  | +import android.graphics.Bitmap
 | 
	
		
			
				|  |  | +import android.graphics.drawable.Drawable
 | 
	
		
			
				|  |  | +import android.net.Uri
 | 
	
		
			
				|  |  | +import android.os.Build
 | 
	
		
			
				|  |  | +import android.os.Bundle
 | 
	
		
			
				|  |  | +import android.support.customtabs.CustomTabsIntent
 | 
	
		
			
				|  |  | +import android.support.v4.content.pm.ShortcutInfoCompat
 | 
	
		
			
				|  |  | +import android.support.v4.content.pm.ShortcutManagerCompat
 | 
	
		
			
				|  |  | +import android.support.v4.graphics.drawable.IconCompat
 | 
	
		
			
				|  |  | +import android.view.*
 | 
	
		
			
				|  |  | +import android.widget.Toast
 | 
	
		
			
				|  |  | +import com.afollestad.materialdialogs.MaterialDialog
 | 
	
		
			
				|  |  | +import com.bumptech.glide.load.engine.DiskCacheStrategy
 | 
	
		
			
				|  |  | +import com.bumptech.glide.load.resource.bitmap.RoundedCorners
 | 
	
		
			
				|  |  | +import com.bumptech.glide.request.target.SimpleTarget
 | 
	
		
			
				|  |  | +import com.bumptech.glide.request.transition.Transition
 | 
	
		
			
				|  |  | +import com.jakewharton.rxbinding.support.v4.widget.refreshes
 | 
	
		
			
				|  |  | +import com.jakewharton.rxbinding.view.clicks
 | 
	
		
			
				|  |  | +import com.jakewharton.rxbinding.view.longClicks
 | 
	
		
			
				|  |  | +import eu.kanade.tachiyomi.R
 | 
	
		
			
				|  |  | +import eu.kanade.tachiyomi.data.database.models.Category
 | 
	
		
			
				|  |  | +import eu.kanade.tachiyomi.data.database.models.Manga
 | 
	
		
			
				|  |  | +import eu.kanade.tachiyomi.data.glide.GlideApp
 | 
	
		
			
				|  |  | +import eu.kanade.tachiyomi.data.notification.NotificationReceiver
 | 
	
		
			
				|  |  | +import eu.kanade.tachiyomi.data.preference.PreferencesHelper
 | 
	
		
			
				|  |  | +import eu.kanade.tachiyomi.source.Source
 | 
	
		
			
				|  |  | +import eu.kanade.tachiyomi.source.model.SManga
 | 
	
		
			
				|  |  | +import eu.kanade.tachiyomi.source.online.HttpSource
 | 
	
		
			
				|  |  | +import eu.kanade.tachiyomi.ui.base.controller.DialogController
 | 
	
		
			
				|  |  | +import eu.kanade.tachiyomi.ui.base.controller.NucleusController
 | 
	
		
			
				|  |  | +import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
 | 
	
		
			
				|  |  | +import eu.kanade.tachiyomi.ui.catalogue.global_search.CatalogueSearchController
 | 
	
		
			
				|  |  | +import eu.kanade.tachiyomi.ui.library.ChangeMangaCategoriesDialog
 | 
	
		
			
				|  |  | +import eu.kanade.tachiyomi.ui.main.MainActivity
 | 
	
		
			
				|  |  | +import eu.kanade.tachiyomi.ui.manga.MangaController
 | 
	
		
			
				|  |  | +import eu.kanade.tachiyomi.util.getResourceColor
 | 
	
		
			
				|  |  | +import eu.kanade.tachiyomi.util.openInBrowser
 | 
	
		
			
				|  |  | +import eu.kanade.tachiyomi.util.snack
 | 
	
		
			
				|  |  | +import eu.kanade.tachiyomi.util.toast
 | 
	
		
			
				|  |  | +import eu.kanade.tachiyomi.util.truncateCenter
 | 
	
		
			
				|  |  | +import jp.wasabeef.glide.transformations.CropSquareTransformation
 | 
	
		
			
				|  |  | +import jp.wasabeef.glide.transformations.MaskTransformation
 | 
	
		
			
				|  |  | +import kotlinx.android.synthetic.main.manga_info_controller.*
 | 
	
		
			
				|  |  | +import uy.kohesive.injekt.injectLazy
 | 
	
		
			
				|  |  | +import java.text.DateFormat
 | 
	
		
			
				|  |  | +import java.text.DecimalFormat
 | 
	
		
			
				|  |  | +import java.util.Date
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +/**
 | 
	
		
			
				|  |  | + * Fragment that shows manga information.
 | 
	
		
			
				|  |  | + * Uses R.layout.manga_info_controller.
 | 
	
		
			
				|  |  | + * UI related actions should be called from here.
 | 
	
		
			
				|  |  | + */
 | 
	
		
			
				|  |  | +class MangaInfoController : NucleusController<MangaInfoPresenter>(),
 | 
	
		
			
				|  |  | +        ChangeMangaCategoriesDialog.Listener {
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * Preferences helper.
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    private val preferences: PreferencesHelper by injectLazy()
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    init {
 | 
	
		
			
				|  |  | +        setHasOptionsMenu(true)
 | 
	
		
			
				|  |  | +        setOptionsMenuHidden(true)
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    override fun createPresenter(): MangaInfoPresenter {
 | 
	
		
			
				|  |  | +        val ctrl = parentController as MangaController
 | 
	
		
			
				|  |  | +        return MangaInfoPresenter(ctrl.manga!!, ctrl.source!!,
 | 
	
		
			
				|  |  | +                ctrl.chapterCountRelay, ctrl.lastUpdateRelay, ctrl.mangaFavoriteRelay)
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
 | 
	
		
			
				|  |  | +        return inflater.inflate(R.layout.manga_info_controller, container, false)
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    override fun onViewCreated(view: View) {
 | 
	
		
			
				|  |  | +        super.onViewCreated(view)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        // Set onclickListener to toggle favorite when FAB clicked.
 | 
	
		
			
				|  |  | +        fab_favorite.clicks().subscribeUntilDestroy { onFabClick() }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        // Set onLongClickListener to manage categories when FAB is clicked.
 | 
	
		
			
				|  |  | +        fab_favorite.longClicks().subscribeUntilDestroy{ onFabLongClick() }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        // Set SwipeRefresh to refresh manga data.
 | 
	
		
			
				|  |  | +        swipe_refresh.refreshes().subscribeUntilDestroy { fetchMangaFromSource() }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        manga_full_title.longClicks().subscribeUntilDestroy {
 | 
	
		
			
				|  |  | +            copyToClipboard(view.context.getString(R.string.title), manga_full_title.text.toString())
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        manga_full_title.clicks().subscribeUntilDestroy {
 | 
	
		
			
				|  |  | +            performGlobalSearch(manga_full_title.text.toString())
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        manga_artist.longClicks().subscribeUntilDestroy {
 | 
	
		
			
				|  |  | +            copyToClipboard(manga_artist_label.text.toString(), manga_artist.text.toString())
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        manga_artist.clicks().subscribeUntilDestroy {
 | 
	
		
			
				|  |  | +            performGlobalSearch(manga_artist.text.toString())
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        manga_author.longClicks().subscribeUntilDestroy {
 | 
	
		
			
				|  |  | +            copyToClipboard(manga_author.text.toString(), manga_author.text.toString())
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        manga_author.clicks().subscribeUntilDestroy {
 | 
	
		
			
				|  |  | +            performGlobalSearch(manga_author.text.toString())
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        manga_summary.longClicks().subscribeUntilDestroy {
 | 
	
		
			
				|  |  | +            copyToClipboard(view.context.getString(R.string.description), manga_summary.text.toString())
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        //manga_genres_tags.setOnTagClickListener { tag -> performGlobalSearch(tag) }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        manga_cover.longClicks().subscribeUntilDestroy {
 | 
	
		
			
				|  |  | +            copyToClipboard(view.context.getString(R.string.title), presenter.manga.title)
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
 | 
	
		
			
				|  |  | +        inflater.inflate(R.menu.manga_info, menu)
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    override fun onOptionsItemSelected(item: MenuItem): Boolean {
 | 
	
		
			
				|  |  | +        when (item.itemId) {
 | 
	
		
			
				|  |  | +            R.id.action_open_in_browser -> openInBrowser()
 | 
	
		
			
				|  |  | +            R.id.action_open_in_web_view -> openInWebView()
 | 
	
		
			
				|  |  | +            R.id.action_share -> shareManga()
 | 
	
		
			
				|  |  | +            R.id.action_add_to_home_screen -> addToHomeScreen()
 | 
	
		
			
				|  |  | +            else -> return super.onOptionsItemSelected(item)
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        return true
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * Check if manga is initialized.
 | 
	
		
			
				|  |  | +     * If true update view with manga information,
 | 
	
		
			
				|  |  | +     * if false fetch manga information
 | 
	
		
			
				|  |  | +     *
 | 
	
		
			
				|  |  | +     * @param manga  manga object containing information about manga.
 | 
	
		
			
				|  |  | +     * @param source the source of the manga.
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    fun onNextManga(manga: Manga, source: Source) {
 | 
	
		
			
				|  |  | +        if (manga.initialized) {
 | 
	
		
			
				|  |  | +            // Update view.
 | 
	
		
			
				|  |  | +            setMangaInfo(manga, source)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        } else {
 | 
	
		
			
				|  |  | +            // Initialize manga.
 | 
	
		
			
				|  |  | +            fetchMangaFromSource()
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * Update the view with manga information.
 | 
	
		
			
				|  |  | +     *
 | 
	
		
			
				|  |  | +     * @param manga manga object containing information about manga.
 | 
	
		
			
				|  |  | +     * @param source the source of the manga.
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    private fun setMangaInfo(manga: Manga, source: Source?) {
 | 
	
		
			
				|  |  | +        val view = view ?: return
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        //update full title TextView.
 | 
	
		
			
				|  |  | +        manga_full_title.text = if (manga.title.isBlank()) {
 | 
	
		
			
				|  |  | +            view.context.getString(R.string.unknown)
 | 
	
		
			
				|  |  | +        } else {
 | 
	
		
			
				|  |  | +            manga.title
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        // Update artist TextView.
 | 
	
		
			
				|  |  | +        manga_artist.text = if (manga.artist.isNullOrBlank()) {
 | 
	
		
			
				|  |  | +            view.context.getString(R.string.unknown)
 | 
	
		
			
				|  |  | +        } else {
 | 
	
		
			
				|  |  | +            manga.artist
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        // Update author TextView.
 | 
	
		
			
				|  |  | +        manga_author.text = if (manga.author.isNullOrBlank()) {
 | 
	
		
			
				|  |  | +            view.context.getString(R.string.unknown)
 | 
	
		
			
				|  |  | +        } else {
 | 
	
		
			
				|  |  | +            manga.author
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        // If manga source is known update source TextView.
 | 
	
		
			
				|  |  | +        manga_source.text = if (source == null) {
 | 
	
		
			
				|  |  | +            view.context.getString(R.string.unknown)
 | 
	
		
			
				|  |  | +        } else {
 | 
	
		
			
				|  |  | +            source.toString()
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        // Update genres list
 | 
	
		
			
				|  |  | +        if (manga.genre.isNullOrBlank().not()) {
 | 
	
		
			
				|  |  | +            manga_genres_tags.setTags(manga.genre?.split(", "))
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        // Update description TextView.
 | 
	
		
			
				|  |  | +        manga_summary.text = if (manga.description.isNullOrBlank()) {
 | 
	
		
			
				|  |  | +            view.context.getString(R.string.unknown)
 | 
	
		
			
				|  |  | +        } else {
 | 
	
		
			
				|  |  | +            manga.description
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        // Update status TextView.
 | 
	
		
			
				|  |  | +        manga_status.setText(when (manga.status) {
 | 
	
		
			
				|  |  | +            SManga.ONGOING -> R.string.ongoing
 | 
	
		
			
				|  |  | +            SManga.COMPLETED -> R.string.completed
 | 
	
		
			
				|  |  | +            SManga.LICENSED -> R.string.licensed
 | 
	
		
			
				|  |  | +            else -> R.string.unknown
 | 
	
		
			
				|  |  | +        })
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        // Set the favorite drawable to the correct one.
 | 
	
		
			
				|  |  | +        setFavoriteDrawable(manga.favorite)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        // Set cover if it wasn't already.
 | 
	
		
			
				|  |  | +        if (manga_cover.drawable == null && !manga.thumbnail_url.isNullOrEmpty()) {
 | 
	
		
			
				|  |  | +            GlideApp.with(view.context)
 | 
	
		
			
				|  |  | +                    .load(manga)
 | 
	
		
			
				|  |  | +                    .diskCacheStrategy(DiskCacheStrategy.RESOURCE)
 | 
	
		
			
				|  |  | +                    .centerCrop()
 | 
	
		
			
				|  |  | +                    .into(manga_cover)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            if (backdrop != null) {
 | 
	
		
			
				|  |  | +                GlideApp.with(view.context)
 | 
	
		
			
				|  |  | +                        .load(manga)
 | 
	
		
			
				|  |  | +                        .diskCacheStrategy(DiskCacheStrategy.RESOURCE)
 | 
	
		
			
				|  |  | +                        .centerCrop()
 | 
	
		
			
				|  |  | +                        .into(backdrop)
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    override fun onDestroyView(view: View) {
 | 
	
		
			
				|  |  | +        manga_genres_tags.setOnTagClickListener(null)
 | 
	
		
			
				|  |  | +        super.onDestroyView(view)
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * Update chapter count TextView.
 | 
	
		
			
				|  |  | +     *
 | 
	
		
			
				|  |  | +     * @param count number of chapters.
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    fun setChapterCount(count: Float) {
 | 
	
		
			
				|  |  | +        if (count > 0f) {
 | 
	
		
			
				|  |  | +            manga_chapters?.text = DecimalFormat("#.#").format(count)
 | 
	
		
			
				|  |  | +        } else {
 | 
	
		
			
				|  |  | +            manga_chapters?.text = resources?.getString(R.string.unknown)
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    fun setLastUpdateDate(date: Date) {
 | 
	
		
			
				|  |  | +        if (date.time != 0L) {
 | 
	
		
			
				|  |  | +            manga_last_update?.text = DateFormat.getDateInstance(DateFormat.SHORT).format(date)
 | 
	
		
			
				|  |  | +        } else {
 | 
	
		
			
				|  |  | +            manga_last_update?.text = resources?.getString(R.string.unknown)
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * Toggles the favorite status and asks for confirmation to delete downloaded chapters.
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    private fun toggleFavorite() {
 | 
	
		
			
				|  |  | +        val view = view
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        val isNowFavorite = presenter.toggleFavorite()
 | 
	
		
			
				|  |  | +        if (view != null && !isNowFavorite && presenter.hasDownloads()) {
 | 
	
		
			
				|  |  | +            view.snack(view.context.getString(R.string.delete_downloads_for_manga)) {
 | 
	
		
			
				|  |  | +                setAction(R.string.action_delete) {
 | 
	
		
			
				|  |  | +                    presenter.deleteDownloads()
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * Open the manga in browser.
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    private fun openInBrowser() {
 | 
	
		
			
				|  |  | +        val context = view?.context ?: return
 | 
	
		
			
				|  |  | +        val source = presenter.source as? HttpSource ?: return
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        context.openInBrowser(source.mangaDetailsRequest(presenter.manga).url().toString())
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    private fun openInWebView() {
 | 
	
		
			
				|  |  | +        val source = presenter.source as? HttpSource ?: return
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        val url = try {
 | 
	
		
			
				|  |  | +            source.mangaDetailsRequest(presenter.manga).url().toString()
 | 
	
		
			
				|  |  | +        } catch (e: Exception) {
 | 
	
		
			
				|  |  | +            return
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        parentController?.router?.pushController(MangaWebViewController(source.id, url)
 | 
	
		
			
				|  |  | +            .withFadeTransaction())
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * Called to run Intent with [Intent.ACTION_SEND], which show share dialog.
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    private fun shareManga() {
 | 
	
		
			
				|  |  | +        val context = view?.context ?: return
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        val source = presenter.source as? HttpSource ?: return
 | 
	
		
			
				|  |  | +        try {
 | 
	
		
			
				|  |  | +            val url = source.mangaDetailsRequest(presenter.manga).url().toString()
 | 
	
		
			
				|  |  | +            val intent = Intent(Intent.ACTION_SEND).apply {
 | 
	
		
			
				|  |  | +                type = "text/plain"
 | 
	
		
			
				|  |  | +                putExtra(Intent.EXTRA_TEXT, url)
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            startActivity(Intent.createChooser(intent, context.getString(R.string.action_share)))
 | 
	
		
			
				|  |  | +        } catch (e: Exception) {
 | 
	
		
			
				|  |  | +            context.toast(e.message)
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * Update FAB with correct drawable.
 | 
	
		
			
				|  |  | +     *
 | 
	
		
			
				|  |  | +     * @param isFavorite determines if manga is favorite or not.
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    private fun setFavoriteDrawable(isFavorite: Boolean) {
 | 
	
		
			
				|  |  | +        // Set the Favorite drawable to the correct one.
 | 
	
		
			
				|  |  | +        // Border drawable if false, filled drawable if true.
 | 
	
		
			
				|  |  | +        fab_favorite?.setImageResource(if (isFavorite)
 | 
	
		
			
				|  |  | +            R.drawable.ic_bookmark_white_24dp
 | 
	
		
			
				|  |  | +        else
 | 
	
		
			
				|  |  | +            R.drawable.ic_add_to_library_24dp)
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * Start fetching manga information from source.
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    private fun fetchMangaFromSource() {
 | 
	
		
			
				|  |  | +        setRefreshing(true)
 | 
	
		
			
				|  |  | +        // Call presenter and start fetching manga information
 | 
	
		
			
				|  |  | +        presenter.fetchMangaFromSource()
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * Update swipe refresh to stop showing refresh in progress spinner.
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    fun onFetchMangaDone() {
 | 
	
		
			
				|  |  | +        setRefreshing(false)
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * Update swipe refresh to start showing refresh in progress spinner.
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    fun onFetchMangaError(error: Throwable) {
 | 
	
		
			
				|  |  | +        setRefreshing(false)
 | 
	
		
			
				|  |  | +        activity?.toast(error.message)
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * Set swipe refresh status.
 | 
	
		
			
				|  |  | +     *
 | 
	
		
			
				|  |  | +     * @param value whether it should be refreshing or not.
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    private fun setRefreshing(value: Boolean) {
 | 
	
		
			
				|  |  | +        swipe_refresh?.isRefreshing = value
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * Called when the fab is clicked.
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    private fun onFabClick() {
 | 
	
		
			
				|  |  | +        val manga = presenter.manga
 | 
	
		
			
				|  |  | +        toggleFavorite()
 | 
	
		
			
				|  |  | +        if (manga.favorite) {
 | 
	
		
			
				|  |  | +            val categories = presenter.getCategories()
 | 
	
		
			
				|  |  | +            val defaultCategoryId = preferences.defaultCategory()
 | 
	
		
			
				|  |  | +            val defaultCategory = categories.find { it.id == defaultCategoryId }
 | 
	
		
			
				|  |  | +            when {
 | 
	
		
			
				|  |  | +                defaultCategory != null -> presenter.moveMangaToCategory(manga, defaultCategory)
 | 
	
		
			
				|  |  | +                defaultCategoryId == 0 || categories.isEmpty() -> // 'Default' or no category
 | 
	
		
			
				|  |  | +                    presenter.moveMangaToCategory(manga, null)
 | 
	
		
			
				|  |  | +                else -> {
 | 
	
		
			
				|  |  | +                    val ids = presenter.getMangaCategoryIds(manga)
 | 
	
		
			
				|  |  | +                    val preselected = ids.mapNotNull { id ->
 | 
	
		
			
				|  |  | +                        categories.indexOfFirst { it.id == id }.takeIf { it != -1 }
 | 
	
		
			
				|  |  | +                    }.toTypedArray()
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                    ChangeMangaCategoriesDialog(this, listOf(manga), categories, preselected)
 | 
	
		
			
				|  |  | +                            .showDialog(router)
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            activity?.toast(activity?.getString(R.string.manga_added_library))
 | 
	
		
			
				|  |  | +        } else {
 | 
	
		
			
				|  |  | +            activity?.toast(activity?.getString(R.string.manga_removed_library))
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * Called when the fab is long clicked.
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    private fun onFabLongClick() {
 | 
	
		
			
				|  |  | +        val manga = presenter.manga
 | 
	
		
			
				|  |  | +        if (!manga.favorite) {
 | 
	
		
			
				|  |  | +            toggleFavorite()
 | 
	
		
			
				|  |  | +            activity?.toast(activity?.getString(R.string.manga_added_library))
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        val categories = presenter.getCategories()
 | 
	
		
			
				|  |  | +        if (categories.isEmpty()) {
 | 
	
		
			
				|  |  | +            // no categories exist, display a message about adding categories
 | 
	
		
			
				|  |  | +            activity?.toast(activity?.getString(R.string.action_add_category))
 | 
	
		
			
				|  |  | +        } else {
 | 
	
		
			
				|  |  | +            val ids = presenter.getMangaCategoryIds(manga)
 | 
	
		
			
				|  |  | +            val preselected = ids.mapNotNull { id ->
 | 
	
		
			
				|  |  | +                categories.indexOfFirst { it.id == id }.takeIf { it != -1 }
 | 
	
		
			
				|  |  | +            }.toTypedArray()
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            ChangeMangaCategoriesDialog(this, listOf(manga), categories, preselected)
 | 
	
		
			
				|  |  | +                    .showDialog(router)
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    override fun updateCategoriesForMangas(mangas: List<Manga>, categories: List<Category>) {
 | 
	
		
			
				|  |  | +        val manga = mangas.firstOrNull() ?: return
 | 
	
		
			
				|  |  | +        presenter.moveMangaToCategories(manga, categories)
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * Add a shortcut of the manga to the home screen
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    private fun addToHomeScreen() {
 | 
	
		
			
				|  |  | +        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
 | 
	
		
			
				|  |  | +            // TODO are transformations really unsupported or is it just the Pixel Launcher?
 | 
	
		
			
				|  |  | +            createShortcutForShape()
 | 
	
		
			
				|  |  | +        } else {
 | 
	
		
			
				|  |  | +            ChooseShapeDialog(this).showDialog(router)
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * Dialog to choose a shape for the icon.
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    private class ChooseShapeDialog(bundle: Bundle? = null) : DialogController(bundle) {
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        constructor(target: MangaInfoController) : this() {
 | 
	
		
			
				|  |  | +            targetController = target
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        override fun onCreateDialog(savedViewState: Bundle?): Dialog {
 | 
	
		
			
				|  |  | +            val modes = intArrayOf(R.string.circular_icon,
 | 
	
		
			
				|  |  | +                    R.string.rounded_icon,
 | 
	
		
			
				|  |  | +                    R.string.square_icon,
 | 
	
		
			
				|  |  | +                    R.string.star_icon)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            return MaterialDialog.Builder(activity!!)
 | 
	
		
			
				|  |  | +                    .title(R.string.icon_shape)
 | 
	
		
			
				|  |  | +                    .negativeText(android.R.string.cancel)
 | 
	
		
			
				|  |  | +                    .items(modes.map { activity?.getString(it) })
 | 
	
		
			
				|  |  | +                    .itemsCallback { _, _, i, _ ->
 | 
	
		
			
				|  |  | +                        (targetController as? MangaInfoController)?.createShortcutForShape(i)
 | 
	
		
			
				|  |  | +                    }
 | 
	
		
			
				|  |  | +                    .build()
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * Retrieves the bitmap of the shortcut with the requested shape and calls [createShortcut] when
 | 
	
		
			
				|  |  | +     * the resource is available.
 | 
	
		
			
				|  |  | +     *
 | 
	
		
			
				|  |  | +     * @param i The shape index to apply. Defaults to circle crop transformation.
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    private fun createShortcutForShape(i: Int = 0) {
 | 
	
		
			
				|  |  | +        if (activity == null) return
 | 
	
		
			
				|  |  | +        GlideApp.with(activity!!)
 | 
	
		
			
				|  |  | +                .asBitmap()
 | 
	
		
			
				|  |  | +                .load(presenter.manga)
 | 
	
		
			
				|  |  | +                .diskCacheStrategy(DiskCacheStrategy.NONE)
 | 
	
		
			
				|  |  | +                .apply {
 | 
	
		
			
				|  |  | +                    when (i) {
 | 
	
		
			
				|  |  | +                        0 -> circleCrop()
 | 
	
		
			
				|  |  | +                        1 -> transform(RoundedCorners(5))
 | 
	
		
			
				|  |  | +                        2 -> transform(CropSquareTransformation())
 | 
	
		
			
				|  |  | +                        3 -> centerCrop().transform(MaskTransformation(R.drawable.mask_star))
 | 
	
		
			
				|  |  | +                    }
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +                .into(object : SimpleTarget<Bitmap>(96, 96) {
 | 
	
		
			
				|  |  | +                    override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
 | 
	
		
			
				|  |  | +                        createShortcut(resource)
 | 
	
		
			
				|  |  | +                    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                    override fun onLoadFailed(errorDrawable: Drawable?) {
 | 
	
		
			
				|  |  | +                        activity?.toast(R.string.icon_creation_fail)
 | 
	
		
			
				|  |  | +                    }
 | 
	
		
			
				|  |  | +                })
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * Copies a string to clipboard
 | 
	
		
			
				|  |  | +     *
 | 
	
		
			
				|  |  | +     * @param label Label to show to the user describing the content
 | 
	
		
			
				|  |  | +     * @param content the actual text to copy to the board
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    private fun copyToClipboard(label: String, content: String) {
 | 
	
		
			
				|  |  | +        if (content.isBlank()) return
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        val activity = activity ?: return
 | 
	
		
			
				|  |  | +        val view = view ?: return
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        val clipboard = activity.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
 | 
	
		
			
				|  |  | +        clipboard.primaryClip = ClipData.newPlainText(label, content)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        activity.toast(view.context.getString(R.string.copied_to_clipboard, content.truncateCenter(20)),
 | 
	
		
			
				|  |  | +                Toast.LENGTH_SHORT)
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * Perform a global search using the provided query.
 | 
	
		
			
				|  |  | +     *
 | 
	
		
			
				|  |  | +     * @param query the search query to pass to the search controller
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    fun performGlobalSearch(query: String) {
 | 
	
		
			
				|  |  | +        val router = parentController?.router ?: return
 | 
	
		
			
				|  |  | +        router.pushController(CatalogueSearchController(query).withFadeTransaction())
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * Create shortcut using ShortcutManager.
 | 
	
		
			
				|  |  | +     *
 | 
	
		
			
				|  |  | +     * @param icon The image of the shortcut.
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    private fun createShortcut(icon: Bitmap) {
 | 
	
		
			
				|  |  | +        val activity = activity ?: return
 | 
	
		
			
				|  |  | +        val mangaControllerArgs = parentController?.args ?: return
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        // Create the shortcut intent.
 | 
	
		
			
				|  |  | +        val shortcutIntent = activity.intent
 | 
	
		
			
				|  |  | +                .setAction(MainActivity.SHORTCUT_MANGA)
 | 
	
		
			
				|  |  | +                .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
 | 
	
		
			
				|  |  | +                .putExtra(MangaController.MANGA_EXTRA,
 | 
	
		
			
				|  |  | +                        mangaControllerArgs.getLong(MangaController.MANGA_EXTRA))
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        // Check if shortcut placement is supported
 | 
	
		
			
				|  |  | +        if (ShortcutManagerCompat.isRequestPinShortcutSupported(activity)) {
 | 
	
		
			
				|  |  | +            val shortcutId = "manga-shortcut-${presenter.manga.title}-${presenter.source.name}"
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            // Create shortcut info
 | 
	
		
			
				|  |  | +            val shortcutInfo = ShortcutInfoCompat.Builder(activity, shortcutId)
 | 
	
		
			
				|  |  | +                    .setShortLabel(presenter.manga.title)
 | 
	
		
			
				|  |  | +                    .setIcon(IconCompat.createWithBitmap(icon))
 | 
	
		
			
				|  |  | +                    .setIntent(shortcutIntent)
 | 
	
		
			
				|  |  | +                    .build()
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            val successCallback = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
 | 
	
		
			
				|  |  | +                // Create the CallbackIntent.
 | 
	
		
			
				|  |  | +                val intent = ShortcutManagerCompat.createShortcutResultIntent(activity, shortcutInfo)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                // Configure the intent so that the broadcast receiver gets the callback successfully.
 | 
	
		
			
				|  |  | +                PendingIntent.getBroadcast(activity, 0, intent, 0)
 | 
	
		
			
				|  |  | +            } else {
 | 
	
		
			
				|  |  | +                NotificationReceiver.shortcutCreatedBroadcast(activity)
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            // Request shortcut.
 | 
	
		
			
				|  |  | +            ShortcutManagerCompat.requestPinShortcut(activity, shortcutInfo,
 | 
	
		
			
				|  |  | +                    successCallback.intentSender)
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +}
 |