diff --git a/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt b/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt index 25b91118ca..0f0d7b1897 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt @@ -25,7 +25,7 @@ object Migrations { if (BuildConfig.INCLUDE_UPDATER && preferences.automaticUpdates()) { UpdaterJob.setupTask() } - return false + return BuildConfig.DEBUG } if (oldVersion < 14) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt index 9319f77f5d..a4cd28f076 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt @@ -455,12 +455,13 @@ class LibraryUpdateService( .concatMap { manga -> val source = sourceManager.get(manga.source) as? HttpSource ?: return@concatMap Observable.empty() - source.fetchMangaDetails(manga) .map { networkManga -> + val thumbnailUrl = manga.thumbnail_url manga.copyFrom(networkManga) db.insertManga(manga).executeAsBlocking() - MangaImpl.setLastCoverFetch(manga.id!!, Date().time) + if (thumbnailUrl != networkManga.thumbnail_url) + MangaImpl.setLastCoverFetch(manga.id!!, Date().time) manga } .onErrorReturn { manga } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueController.kt index 2d6997644b..9a18c3a3cc 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueController.kt @@ -28,10 +28,11 @@ import eu.kanade.tachiyomi.ui.catalogue.global_search.CatalogueSearchController import eu.kanade.tachiyomi.ui.catalogue.latest.LatestUpdatesController import eu.kanade.tachiyomi.ui.setting.SettingsSourcesController import eu.kanade.tachiyomi.util.view.RecyclerWindowInsetsListener -import eu.kanade.tachiyomi.util.view.applyWindowInsetsForController +import eu.kanade.tachiyomi.util.view.applyWindowInsetsForRootController import eu.kanade.tachiyomi.widget.preference.SourceLoginDialog import kotlinx.android.parcel.Parcelize import kotlinx.android.synthetic.main.catalogue_main_controller.* +import kotlinx.android.synthetic.main.main_activity.* import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get @@ -102,7 +103,7 @@ class CatalogueController : NucleusController(), */ override fun onViewCreated(view: View) { super.onViewCreated(view) - view.applyWindowInsetsForController() + view.applyWindowInsetsForRootController(activity!!.navigationView) adapter = CatalogueAdapter(this) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryHolder.kt index 0c846ff28a..96ee53745f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryHolder.kt @@ -53,8 +53,8 @@ class CategoryHolder(view: View, val adapter: CategoryAdapter) : BaseFlexibleVie title.setTextColor(ContextCompat.getColor(itemView.context, R.color.textColorHint)) regularDrawable = ContextCompat.getDrawable(itemView.context, R.drawable .ic_add_white_24dp) - edit_button.gone() image.gone() + edit_button.setImageDrawable(null) edit_text.setText("") edit_text.hint = title.text } @@ -62,7 +62,6 @@ class CategoryHolder(view: View, val adapter: CategoryAdapter) : BaseFlexibleVie title.setTextColor(ContextCompat.getColor(itemView.context, R.color.textColorPrimary)) regularDrawable = ContextCompat.getDrawable(itemView.context, R.drawable .ic_reorder_grey_24dp) - edit_button.visible() image.visible() edit_text.setText(title.text) } @@ -94,12 +93,13 @@ class CategoryHolder(view: View, val adapter: CategoryAdapter) : BaseFlexibleVie else { if (!createCategory) { setDragHandleView(reorder) + edit_button.setImageDrawable(ContextCompat.getDrawable(itemView.context, R.drawable.ic_edit_white_24dp)) } else { + edit_button.setImageDrawable(null) reorder.setOnTouchListener { _, _ -> true} } edit_text.clearFocus() - edit_button.setImageDrawable(ContextCompat.getDrawable(itemView.context, R.drawable.ic_edit_white_24dp)) edit_button.drawable.mutate().setTint(ContextCompat.getColor(itemView.context, R .color.gray_button)) reorder.setImageDrawable(regularDrawable) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryPresenter.kt index d21ff188dc..7b9cd1c7b3 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryPresenter.kt @@ -59,7 +59,7 @@ class CategoryPresenter( */ fun createCategory(name: String): Boolean { // Do not allow duplicate categories. - if (categoryExists(name)) { + if (categoryExists(name, null)) { controller.onCategoryExistsError() return false } @@ -116,7 +116,7 @@ class CategoryPresenter( */ fun renameCategory(category: Category, name: String): Boolean { // Do not allow duplicate categories. - if (categoryExists(name)) { + if (categoryExists(name, category.id)) { controller.onCategoryExistsError() return false } @@ -131,8 +131,8 @@ class CategoryPresenter( /** * Returns true if a category with the given name already exists. */ - private fun categoryExists(name: String): Boolean { - return categories.any { it.name.equals(name, true) } + private fun categoryExists(name: String, id: Int?): Boolean { + return categories.any { it.name.equals(name, true) && id != it.id } } companion object { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt index c882759869..b8a15061f6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt @@ -51,7 +51,7 @@ import eu.kanade.tachiyomi.ui.migration.manga.process.MigrationProcedureConfig import eu.kanade.tachiyomi.ui.reader.ReaderActivity import eu.kanade.tachiyomi.util.system.getResourceColor import eu.kanade.tachiyomi.util.system.launchUI -import eu.kanade.tachiyomi.util.view.applyWindowInsetsForController +import eu.kanade.tachiyomi.util.view.applyWindowInsetsForRootController import eu.kanade.tachiyomi.util.view.setOnQueryTextChangeListener import eu.kanade.tachiyomi.util.view.snack import kotlinx.android.synthetic.main.filter_bottom_sheet.* @@ -176,7 +176,7 @@ open class LibraryController( override fun onViewCreated(view: View) { super.onViewCreated(view) - view.applyWindowInsetsForController() + view.applyWindowInsetsForRootController(activity!!.navigationView) mangaPerRow = getColumnsPreferenceForCurrentOrientation().getOrDefault() if (!::presenter.isInitialized) presenter = LibraryPresenter(this) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryGridHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryGridHolder.kt index ab6efca0fd..2a21f7df60 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryGridHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryGridHolder.kt @@ -69,7 +69,8 @@ class LibraryGridHolder( var glide = GlideApp.with(adapter.recyclerView.context).load(item.manga) .diskCacheStrategy(DiskCacheStrategy.AUTOMATIC) .signature(ObjectKey(MangaImpl.getLastCoverFetch(id).toString())) - glide = if (fixedSize) glide.centerCrop() else glide.override(cover_thumbnail.maxHeight) + glide = if (fixedSize) glide.centerCrop().override(cover_thumbnail.maxHeight) + else glide.override(cover_thumbnail.maxHeight) glide.into(cover_thumbnail) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryItem.kt index 53bd7f8f4e..73799631ea 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryItem.kt @@ -5,6 +5,7 @@ import android.view.Gravity import android.view.View import android.view.ViewGroup import android.widget.FrameLayout +import android.widget.ImageView import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.content.ContextCompat import androidx.recyclerview.widget.RecyclerView @@ -21,6 +22,7 @@ import eu.kanade.tachiyomi.util.system.dpToPx import eu.kanade.tachiyomi.util.view.updateLayoutParams import eu.kanade.tachiyomi.widget.AutofitRecyclerView import kotlinx.android.synthetic.main.catalogue_grid_item.view.* +import timber.log.Timber import uy.kohesive.injekt.injectLazy class LibraryItem(val manga: LibraryManga, @@ -70,7 +72,9 @@ class LibraryItem(val manga: LibraryManga, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT ) cover_thumbnail.maxHeight = (parent.itemWidth / 3f * 3.7f).toInt() + cover_thumbnail.minimumHeight = (parent.itemWidth / 3f * 3.7f).toInt() constraint_layout.minHeight = 0 + cover_thumbnail.scaleType = ImageView.ScaleType.CENTER_CROP cover_thumbnail.adjustViewBounds = false cover_thumbnail.layoutParams = FrameLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt index e4e1456cbd..fe8b475b22 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt @@ -1,5 +1,8 @@ package eu.kanade.tachiyomi.ui.main +import android.animation.Animator +import android.animation.AnimatorSet +import android.animation.ValueAnimator import android.app.SearchManager import android.content.Intent import android.content.res.Configuration @@ -7,7 +10,6 @@ import android.graphics.Color import android.graphics.Rect import android.os.Build import android.os.Bundle -import android.provider.Settings import android.view.GestureDetector import android.view.MotionEvent import android.view.View @@ -86,6 +88,10 @@ open class MainActivity : BaseActivity(), DownloadServiceListener { private var extraViewForUndo:View? = null private var canDismissSnackBar = false + private var animationSet: AnimatorSet? = null + + private var bottomNavHeight = 0 + fun setUndoSnackBar(snackBar: Snackbar?, extraViewToCheck: View? = null) { this.snackBar = snackBar canDismissSnackBar = false @@ -306,11 +312,12 @@ open class MainActivity : BaseActivity(), DownloadServiceListener { } navigationView.visibility = if (router.backstackSize > 1) View.GONE else View.VISIBLE + navigationView.alpha = if (router.backstackSize > 1) 0f else 1f router.addChangeListener(object : ControllerChangeHandler.ControllerChangeListener { override fun onChangeStarted(to: Controller?, from: Controller?, isPush: Boolean, container: ViewGroup, handler: ControllerChangeHandler) { - syncActivityViewWithController(to, from) + syncActivityViewWithController(to, from, isPush) } override fun onChangeCompleted(to: Controller?, from: Controller?, isPush: Boolean, @@ -360,7 +367,7 @@ open class MainActivity : BaseActivity(), DownloadServiceListener { return super.startSupportActionMode(callback) } - override fun onSupportActionModeFinished(mode: androidx.appcompat.view.ActionMode) { + /* override fun onSupportActionModeFinished(mode: androidx.appcompat.view.ActionMode) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) launchUI { val scale = Settings.Global.getFloat( contentResolver, Settings.Global.ANIMATOR_DURATION_SCALE, 1.0f @@ -371,7 +378,7 @@ open class MainActivity : BaseActivity(), DownloadServiceListener { window?.statusBarColor = getResourceColor(android.R.attr.statusBarColor) } super.onSupportActionModeFinished(mode) - } + }*/ private fun setExtensionsBadge() { val updates = preferences.extensionUpdatesCount().getOrDefault() @@ -542,7 +549,8 @@ open class MainActivity : BaseActivity(), DownloadServiceListener { return super.dispatchTouchEvent(ev) } - protected open fun syncActivityViewWithController(to: Controller?, from: Controller? = null) { + protected open fun syncActivityViewWithController(to: Controller?, from: Controller? = null, + isPush: Boolean = false) { if (from is DialogController || to is DialogController) { return } @@ -577,8 +585,35 @@ open class MainActivity : BaseActivity(), DownloadServiceListener { appbar.enableElevation() } - if (to !is DialogController) - navigationView.visibility = if (router.backstackSize > 1) View.GONE else View.VISIBLE + if (to !is DialogController) { + navigationView.visibility = if (router.backstackSize == 0 || + (router.backstackSize <= 1 && !isPush)) + View.VISIBLE else navigationView.visibility + animationSet?.cancel() + animationSet = AnimatorSet() + val alphaAnimation = ValueAnimator.ofFloat( + navigationView.alpha, + if (router.backstackSize > 1) 0f else 1f + ) + alphaAnimation.addUpdateListener { valueAnimator -> + navigationView.alpha = valueAnimator.animatedValue as Float + } + alphaAnimation.addListener(object : Animator.AnimatorListener { + override fun onAnimationEnd(animation: Animator?) { + navigationView.visibility = if (router.backstackSize > 1) View.GONE else View.VISIBLE + } + + override fun onAnimationCancel(animation: Animator?) { } + + override fun onAnimationRepeat(animation: Animator?) { } + + override fun onAnimationStart(animation: Animator?) { } + }) + alphaAnimation.duration = 200 + alphaAnimation.startDelay = 50 + animationSet?.playTogether(alphaAnimation) + animationSet?.start() + } } override fun downloadStatusChanged(downloading: Boolean) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/main/SearchActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/main/SearchActivity.kt index 15b731136f..f3cfb38c4c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/main/SearchActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/main/SearchActivity.kt @@ -39,7 +39,8 @@ class SearchActivity: MainActivity() { } } - override fun syncActivityViewWithController(to: Controller?, from: Controller?) { + override fun syncActivityViewWithController(to: Controller?, from: Controller?, isPush: + Boolean) { if (from is DialogController || to is DialogController) { return } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/ChaptersSortBottomSheet.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/ChaptersSortBottomSheet.kt index 9d2b290c69..dd6d05fc0a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/ChaptersSortBottomSheet.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/ChaptersSortBottomSheet.kt @@ -6,34 +6,24 @@ import android.os.Bundle import android.view.View import android.view.ViewGroup import android.widget.CompoundButton -import android.widget.RadioButton -import android.widget.RadioGroup -import com.f2prateek.rx.preferences.Preference import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.Manga -import eu.kanade.tachiyomi.data.preference.PreferencesHelper -import eu.kanade.tachiyomi.data.preference.getOrDefault import eu.kanade.tachiyomi.util.system.dpToPx import eu.kanade.tachiyomi.util.view.setBottomEdge import eu.kanade.tachiyomi.util.view.setEdgeToEdge import eu.kanade.tachiyomi.util.view.visibleIf import kotlinx.android.synthetic.main.chapter_sort_bottom_sheet.* -import uy.kohesive.injekt.injectLazy -class ChaptersSortBottomSheet(private val controller: MangaChaptersController) : BottomSheetDialog +class ChaptersSortBottomSheet(controller: MangaChaptersController) : BottomSheetDialog (controller.activity!!, R.style.BottomSheetDialogTheme) { val activity = controller.activity!! - /** - * Preferences helper. - */ - private val preferences by injectLazy() - private var sheetBehavior: BottomSheetBehavior<*> + private val presenter = controller.presenter init { // Use activity theme for this layout @@ -45,7 +35,7 @@ class ChaptersSortBottomSheet(private val controller: MangaChaptersController) : val height = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { activity.window.decorView.rootWindowInsets.systemWindowInsetBottom } else 0 - sheetBehavior.peekHeight = 220.dpToPx + height + sheetBehavior.peekHeight = 380.dpToPx + height sheetBehavior.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() { override fun onSlide(bottomSheet: View, progress: Float) { } @@ -62,7 +52,6 @@ class ChaptersSortBottomSheet(private val controller: MangaChaptersController) : override fun onStart() { super.onStart() sheetBehavior.skipCollapsed = true - sheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED } /** @@ -71,87 +60,79 @@ class ChaptersSortBottomSheet(private val controller: MangaChaptersController) : override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) initGeneralPreferences() - setBottomEdge(show_bookmark, activity) - close_button.setOnClickListener { - dismiss() - true - } + setBottomEdge(hide_titles, activity) + close_button.setOnClickListener { dismiss() } settings_scroll_view.viewTreeObserver.addOnGlobalLayoutListener { val isScrollable = settings_scroll_view!!.height < bottom_sheet.height + settings_scroll_view.paddingTop + settings_scroll_view.paddingBottom close_button.visibleIf(isScrollable) } + + setOnDismissListener { + presenter.setFilters( + show_read.isChecked, + show_unread.isChecked, + show_download.isChecked, + show_bookmark.isChecked + ) + } } private fun initGeneralPreferences() { - - show_read.isChecked = controller.presenter.onlyRead() - show_unread.isChecked = controller.presenter.onlyUnread() - show_download.isChecked = controller.presenter.onlyDownloaded() - show_bookmark.isChecked = controller.presenter.onlyBookmarked() + show_read.isChecked = presenter.onlyRead() + show_unread.isChecked = presenter.onlyUnread() + show_download.isChecked = presenter.onlyDownloaded() + show_bookmark.isChecked = presenter.onlyBookmarked() show_all.isChecked = !(show_read.isChecked || show_unread.isChecked || show_download.isChecked || show_bookmark.isChecked) - if (controller.presenter.onlyRead()) - //Disable unread filter option if read filter is enabled. - show_unread.isEnabled = false - if (controller.presenter.onlyUnread()) - //Disable read filter option if unread filter is enabled. - show_read.isEnabled = false - - sort_group.check(if (controller.presenter.manga.sortDescending()) R.id.sort_newest else + sort_group.check(if (presenter.manga.sortDescending()) R.id.sort_newest else R.id.sort_oldest) - show_titles.isChecked = controller.presenter.manga.displayMode == Manga.DISPLAY_NAME - sort_by_source.isChecked = controller.presenter.manga.sorting == Manga.SORTING_SOURCE + hide_titles.isChecked = presenter.manga.displayMode != Manga.DISPLAY_NAME + sort_method_group.check(if (presenter.manga.sorting == Manga.SORTING_SOURCE) R.id.sort_by_source else + R.id.sort_by_number) sort_group.setOnCheckedChangeListener { _, checkedId -> - controller.presenter.setSortOrder(checkedId == R.id.sort_oldest) + presenter.setSortOrder(checkedId == R.id.sort_oldest) dismiss() } - /*sort_group.bindToPreference(preferences.libraryLayout()) { - controller.reattachAdapter() - if (sheetBehavior.state == BottomSheetBehavior.STATE_COLLAPSED) - dismiss() + sort_method_group.setOnCheckedChangeListener { _, checkedId -> + presenter.setSortMethod(checkedId == R.id.sort_by_source) } - uniform_grid.bindToPreference(preferences.uniformGrid()) { - controller.reattachAdapter() + + hide_titles.setOnCheckedChangeListener { _, isChecked -> + presenter.hideTitle(isChecked) } - grid_size_toggle_group.bindToPreference(preferences.gridSize()) { - controller.reattachAdapter() - } - download_badge.bindToPreference(preferences.downloadBadge()) { - controller.presenter.requestDownloadBadgesUpdate() - } - unread_badge_group.bindToPreference(preferences.unreadBadgeType()) { - controller.presenter.requestUnreadBadgesUpdate() - }*/ + + show_all.setOnCheckedChangeListener(::checkedFilter) + show_read.setOnCheckedChangeListener(::checkedFilter) + show_unread.setOnCheckedChangeListener(::checkedFilter) + show_download.setOnCheckedChangeListener(::checkedFilter) + show_bookmark.setOnCheckedChangeListener(::checkedFilter) } - /** - * Binds a checkbox or switch view with a boolean preference. - */ - private fun CompoundButton.bindToPreference(pref: Preference, block: () -> Unit) { - isChecked = pref.getOrDefault() - setOnCheckedChangeListener { _, isChecked -> - pref.set(isChecked) - block() + private fun checkedFilter(checkBox: CompoundButton, isChecked: Boolean) { + if (isChecked) { + if (show_all == checkBox) { + show_read.isChecked = false + show_unread.isChecked = false + show_download.isChecked = false + show_bookmark.isChecked = false + } + else { + show_all.isChecked = false + if (show_read == checkBox) show_unread.isChecked = false + else if (show_unread == checkBox) show_read.isChecked = false + } } - } - - /** - * Binds a radio group with a int preference. - */ - private fun RadioGroup.bindToPreference(pref: Preference, block: () -> Unit) { - (getChildAt(pref.getOrDefault()) as RadioButton).isChecked = true - setOnCheckedChangeListener { _, checkedId -> - val index = indexOfChild(findViewById(checkedId)) - pref.set(index) - block() + else if (!show_read.isChecked && !show_unread.isChecked && + !show_download.isChecked && !show_bookmark.isChecked) { + show_all.isChecked = true } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/ChooseShapeDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/ChooseShapeDialog.kt new file mode 100644 index 0000000000..3492d61fda --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/ChooseShapeDialog.kt @@ -0,0 +1,43 @@ +package eu.kanade.tachiyomi.ui.manga + +import android.app.Dialog +import android.os.Bundle +import com.afollestad.materialdialogs.MaterialDialog +import com.afollestad.materialdialogs.list.listItemsSingleChoice +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.ui.base.controller.DialogController +import eu.kanade.tachiyomi.ui.manga.info.MangaInfoController + +/** + * Dialog to choose a shape for the icon. + */ +class ChooseShapeDialog(bundle: Bundle? = null) : DialogController(bundle) { + + constructor(target: MangaInfoController) : this() { + targetController = target + } + + constructor(target: MangaChaptersController) : 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(activity!!) + .title(R.string.icon_shape) + .negativeButton(android.R.string.cancel) + .listItemsSingleChoice ( + items = modes.map { activity?.getString(it) as CharSequence }, + waitForPositiveButton = false) + { _, i, _ -> + (targetController as? MangaInfoController)?.createShortcutForShape(i) + (targetController as? MangaChaptersController)?.createShortcutForShape(i) + dismissDialog() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaChaptersController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaChaptersController.kt index 1e227db855..0fac1e1e16 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaChaptersController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaChaptersController.kt @@ -2,8 +2,13 @@ package eu.kanade.tachiyomi.ui.manga import android.animation.ValueAnimator import android.app.Activity +import android.app.PendingIntent +import android.content.ClipData +import android.content.ClipboardManager +import android.content.Context import android.content.Intent import android.content.res.Configuration +import android.graphics.Bitmap import android.graphics.Color import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.Drawable @@ -16,16 +21,19 @@ import android.view.MenuInflater import android.view.MenuItem import android.view.View import android.view.ViewGroup -import androidx.appcompat.view.ActionMode import androidx.appcompat.widget.PopupMenu +import androidx.core.content.pm.ShortcutInfoCompat +import androidx.core.content.pm.ShortcutManagerCompat import androidx.core.graphics.ColorUtils +import androidx.core.graphics.drawable.IconCompat import androidx.palette.graphics.Palette import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager -import androidx.vectordrawable.graphics.drawable.ArgbEvaluator +import com.afollestad.materialdialogs.MaterialDialog import com.bluelinelabs.conductor.ControllerChangeHandler import com.bluelinelabs.conductor.ControllerChangeType import com.bumptech.glide.load.engine.DiskCacheStrategy +import com.bumptech.glide.load.resource.bitmap.RoundedCorners import com.bumptech.glide.request.target.CustomTarget import com.bumptech.glide.request.transition.Transition import com.bumptech.glide.signature.ObjectKey @@ -38,12 +46,15 @@ import eu.kanade.tachiyomi.data.database.models.Category import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.MangaImpl +import eu.kanade.tachiyomi.data.download.DownloadService import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.data.glide.GlideApp import eu.kanade.tachiyomi.data.notification.NotificationReceiver import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.SourceManager +import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.ui.base.controller.BaseController +import eu.kanade.tachiyomi.ui.base.controller.DialogController import eu.kanade.tachiyomi.ui.base.controller.NoToolbarElevationController import eu.kanade.tachiyomi.ui.catalogue.CatalogueController import eu.kanade.tachiyomi.ui.library.ChangeMangaCategoriesDialog @@ -54,25 +65,31 @@ import eu.kanade.tachiyomi.ui.manga.MangaController.Companion.FROM_CATALOGUE_EXT import eu.kanade.tachiyomi.ui.manga.chapter.ChapterItem import eu.kanade.tachiyomi.ui.manga.chapter.ChapterMatHolder import eu.kanade.tachiyomi.ui.manga.chapter.ChaptersAdapter +import eu.kanade.tachiyomi.ui.manga.info.EditMangaDialog import eu.kanade.tachiyomi.ui.reader.ReaderActivity import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate +import eu.kanade.tachiyomi.ui.webview.WebViewActivity +import eu.kanade.tachiyomi.util.storage.getUriCompat import eu.kanade.tachiyomi.util.system.dpToPx import eu.kanade.tachiyomi.util.system.getResourceColor import eu.kanade.tachiyomi.util.system.launchUI +import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.view.doOnApplyWindowInsets import eu.kanade.tachiyomi.util.view.getText import eu.kanade.tachiyomi.util.view.snack import eu.kanade.tachiyomi.util.view.updateLayoutParams import eu.kanade.tachiyomi.util.view.updatePaddingRelative +import jp.wasabeef.glide.transformations.CropSquareTransformation +import jp.wasabeef.glide.transformations.MaskTransformation import kotlinx.android.synthetic.main.big_manga_controller.* import kotlinx.android.synthetic.main.big_manga_controller.swipe_refresh import kotlinx.android.synthetic.main.main_activity.* import kotlinx.android.synthetic.main.manga_info_controller.* import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get +import java.io.File class MangaChaptersController : BaseController, - ActionMode.Callback, FlexibleAdapter.OnItemClickListener, FlexibleAdapter.OnItemLongClickListener, ChaptersAdapter.MangaHeaderInterface, @@ -184,7 +201,7 @@ class MangaChaptersController : BaseController, ) colorAnimator?.cancel() colorAnimator = ValueAnimator.ofObject( - ArgbEvaluator(), colorFrom, colorTo + android.animation.ArgbEvaluator(), colorFrom, colorTo ) colorAnimator?.duration = 250 // milliseconds colorAnimator?.addUpdateListener { animator -> @@ -226,7 +243,7 @@ class MangaChaptersController : BaseController, colorBack ) else it?.getDarkMutedColor(colorBack)) ?: colorBack - onCoverLoaded(backDropColor) + coverColor = backDropColor (recycler.findViewHolderForAdapterPosition(0) as? MangaHeaderHolder) ?.setBackDrop(backDropColor) if (toolbarIsColored) { @@ -264,11 +281,15 @@ class MangaChaptersController : BaseController, override fun onChangeStarted(handler: ControllerChangeHandler, type: ControllerChangeType) { super.onChangeStarted(handler, type) if (type == ControllerChangeType.PUSH_ENTER || type == ControllerChangeType.POP_ENTER) { + if (type == ControllerChangeType.POP_ENTER) + return (activity as MainActivity).appbar.setBackgroundColor(Color.TRANSPARENT) (activity as MainActivity).toolbar.setBackgroundColor(Color.TRANSPARENT) activity?.window?.statusBarColor = Color.TRANSPARENT } else if (type == ControllerChangeType.PUSH_EXIT || type == ControllerChangeType.POP_EXIT) { + if (router.backstack.lastOrNull()?.controller() is DialogController) + return colorAnimator?.cancel() (activity as MainActivity).toolbar.setBackgroundColor(activity?.getResourceColor( @@ -295,6 +316,7 @@ class MangaChaptersController : BaseController, listOf(ChapterItem(presenter.headerItem, presenter.manga)) + presenter.chapters ) } + activity?.invalidateOptionsMenu() } @@ -305,7 +327,10 @@ class MangaChaptersController : BaseController, presenter.fetchChaptersFromSource() } adapter?.updateDataSet(listOf(presenter.headerItem) + chapters) -} + activity?.invalidateOptionsMenu() + } + + fun refreshAdapter() = adapter?.notifyDataSetChanged() override fun onItemClick(view: View?, position: Int): Boolean { val adapter = adapter ?: return false @@ -392,43 +417,231 @@ class MangaChaptersController : BaseController, } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { - inflater.inflate(R.menu.chapters, menu) + inflater.inflate(R.menu.manga_details, menu) + val editItem = menu.findItem(R.id.action_edit) + editItem.isVisible = presenter.manga.favorite && !presenter.isLockedFromSearch + menu.findItem(R.id.action_download).isVisible = !presenter.isLockedFromSearch + menu.findItem(R.id.action_mark_all_as_read).isVisible = + presenter.getNextUnreadChapter() != null && !presenter.isLockedFromSearch + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.action_edit -> EditMangaDialog(this, presenter.manga).showDialog(router) + R.id.action_open_in_web_view -> openInWebView() + R.id.action_share -> prepareToShareManga() + R.id.action_add_to_home_screen -> addToHomeScreen() + R.id.action_mark_all_as_read -> { + MaterialDialog(view!!.context) + .message(R.string.mark_all_as_read_message) + .positiveButton(R.string.action_mark_as_read) { + markAsRead(presenter.chapters) + } + .negativeButton(android.R.string.cancel) + .show() + } + R.id.download_next, R.id.download_next_5, R.id.download_next_10, + R.id.download_custom, R.id.download_unread, R.id.download_all + -> downloadChapters(item.itemId) + else -> return super.onOptionsItemSelected(item) + } + return true + } + + /** + * Called to run Intent with [Intent.ACTION_SEND], which show share dialog. + */ + override fun prepareToShareManga() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && manga_cover.drawable != null) + GlideApp.with(activity!!).asBitmap().load(presenter.manga).into(object : + CustomTarget() { + override fun onResourceReady(resource: Bitmap, transition: Transition?) { + presenter.shareManga(resource) + } + override fun onLoadCleared(placeholder: Drawable?) {} + + override fun onLoadFailed(errorDrawable: Drawable?) { + shareManga() + } + }) + else shareManga() + } + + /** + * Called to run Intent with [Intent.ACTION_SEND], which show share dialog. + */ + fun shareManga(cover: File? = null) { + val context = view?.context ?: return + + val source = presenter.source as? HttpSource ?: return + val stream = cover?.getUriCompat(context) + try { + val url = source.mangaDetailsRequest(presenter.manga).url.toString() + val intent = Intent(Intent.ACTION_SEND).apply { + type = "text/*" + putExtra(Intent.EXTRA_TEXT, url) + putExtra(Intent.EXTRA_TITLE, presenter.manga.currentTitle()) + flags = Intent.FLAG_GRANT_READ_URI_PERMISSION + if (stream != null) { + clipData = ClipData.newRawUri(null, stream) + } + } + startActivity(Intent.createChooser(intent, context.getString(R.string.action_share))) + } catch (e: Exception) { + context.toast(e.message) + } + } + + private fun openInWebView() { + val source = presenter.source as? HttpSource ?: return + + val url = try { + source.mangaDetailsRequest(presenter.manga).url.toString() + } catch (e: Exception) { + return + } + + val activity = activity ?: return + val intent = WebViewActivity.newIntent(activity.applicationContext, source.id, url, presenter.manga + .originalTitle()) + startActivity(intent) + } + + private fun downloadChapters(choice: Int) { + val chaptersToDownload = when (choice) { + R.id.download_next -> presenter.getUnreadChaptersSorted().take(1) + R.id.download_next_5 -> presenter.getUnreadChaptersSorted().take(5) + R.id.download_next_10 -> presenter.getUnreadChaptersSorted().take(10) + R.id.download_custom -> { + showCustomDownloadDialog() + return + } + R.id.download_unread -> presenter.chapters.filter { !it.read } + R.id.download_all -> presenter.chapters + else -> emptyList() + } + if (chaptersToDownload.isNotEmpty()) { + downloadChapters(chaptersToDownload) + } + } + + private fun downloadChapters(chapters: List) { + val view = view + presenter.downloadChapters(chapters) + if (view != null && !presenter.manga.favorite && (snack == null || + snack?.getText() != view.context.getString(R.string.snack_add_to_library))) { + snack = view.snack(view.context.getString(R.string.snack_add_to_library), Snackbar.LENGTH_INDEFINITE) { + setAction(R.string.action_add) { + presenter.setFavorite(true) + } + addCallback(object : BaseTransientBottomBar.BaseCallback() { + override fun onDismissed(transientBottomBar: Snackbar?, event: Int) { + super.onDismissed(transientBottomBar, event) + if (snack == transientBottomBar) snack = null + } + }) + } + (activity as? MainActivity)?.setUndoSnackBar(snack) + } + } + + /** + * 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) + } + } + + /** + * 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. + */ + 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 : CustomTarget(128, 128) { + override fun onResourceReady(resource: Bitmap, transition: Transition?) { + createShortcut(resource) + } + + override fun onLoadCleared(placeholder: Drawable?) { } + + override fun onLoadFailed(errorDrawable: Drawable?) { + activity?.toast(R.string.icon_creation_fail) + } + }) + } + + /** + * Create shortcut using ShortcutManager. + * + * @param icon The image of the shortcut. + */ + private fun createShortcut(icon: Bitmap) { + val activity = activity ?: return + + // Create the shortcut intent. + val shortcutIntent = activity.intent + .setAction(MainActivity.SHORTCUT_MANGA) + .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) + .putExtra(MangaController.MANGA_EXTRA, presenter.manga.id) + + // Check if shortcut placement is supported + if (ShortcutManagerCompat.isRequestPinShortcutSupported(activity)) { + val shortcutId = "manga-shortcut-${presenter.manga.originalTitle()}-${presenter.source.name}" + + // Create shortcut info + val shortcutInfo = ShortcutInfoCompat.Builder(activity, shortcutId) + .setShortLabel(presenter.manga.currentTitle()) + .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) + } + } + + private fun showCustomDownloadDialog() { + // DownloadCustomChaptersDialog(this, presenter.chapters.size).showDialog(router) } override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View { return inflater.inflate(R.layout.big_manga_controller, container, false) } - override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean { - return true - } - - override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean { - return true - - } - - override fun onDestroyActionMode(mode: ActionMode?) { - - } - - override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean { - return true - - } - - fun onCoverLoaded(color: Int) { - if (view == null) return - coverColor = color - //activity?.window?.statusBarColor = color - } - override fun coverColor(): Int? = coverColor override fun topCoverHeight(): Int = headerHeight - override fun nextChapter(): Chapter? = presenter.getNextUnreadChapter() - override fun mangaSource(): Source = presenter.source - override fun readNextChapter() { if (activity is SearchActivity && presenter.isLockedFromSearch) { SecureActivityDelegate.promptLockIfNeeded(activity) @@ -451,17 +664,17 @@ class MangaChaptersController : BaseController, } override fun downloadChapter(position: Int) { - val adapter = adapter ?: return - val chapter = adapter.getItem(position) ?: return + val view = view ?: return + val chapter = adapter?.getItem(position) ?: return if (chapter.isHeader) return if (chapter.status != Download.NOT_DOWNLOADED && chapter.status != Download.ERROR) { presenter.deleteChapters(listOf(chapter)) } else { - val isError = chapter.status == Download.ERROR - presenter.downloadChapters(listOf(chapter)) - if (isError) - presenter.restartDownloads() + if (chapter.status == Download.ERROR) + DownloadService.start(view.context) + else + downloadChapters(listOf(chapter)) } } @@ -477,8 +690,6 @@ class MangaChaptersController : BaseController, ChaptersSortBottomSheet(this).show() } - override fun chapterCount():Int = presenter.chapters.size - override fun favoriteManga(longPress: Boolean) { if (presenter.isLockedFromSearch) { SecureActivityDelegate.promptLockIfNeeded(activity) @@ -560,8 +771,29 @@ class MangaChaptersController : BaseController, (activity as? MainActivity)?.setUndoSnackBar(snack, fab_favorite) } + override fun mangaPresenter(): MangaPresenter = presenter + override fun updateCategoriesForMangas(mangas: List, categories: List) { val manga = mangas.firstOrNull() ?: return presenter.moveMangaToCategories(manga, categories) } + + /** + * 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, resId: Int) { + if (content.isBlank()) return + + val activity = activity ?: return + val view = view ?: return + + val clipboard = activity.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + clipboard.setPrimaryClip(ClipData.newPlainText(label, content)) + + snack = view.snack(view.context.getString(R.string.copied_to_clipboard, view.context + .getString(resId))) + } } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaHeaderHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaHeaderHolder.kt index ce832c48d3..0763ffb336 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaHeaderHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaHeaderHolder.kt @@ -7,6 +7,7 @@ import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.content.ContextCompat import androidx.core.graphics.ColorUtils import com.bumptech.glide.load.engine.DiskCacheStrategy +import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions import com.bumptech.glide.signature.ObjectKey import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.Manga @@ -44,9 +45,9 @@ class MangaHeaderHolder( manga_genres_tags.setOnTagClickListener { adapter.coverListener?.tagClicked(it) } - filter_button.setOnClickListener { - adapter.coverListener?.showChapterFilter() - } + filter_button.setOnClickListener { adapter.coverListener?.showChapterFilter() } + filters_text.setOnClickListener { adapter.coverListener?.showChapterFilter() } + share_button.setOnClickListener { adapter.coverListener?.prepareToShareManga() } favorite_button.setOnClickListener { adapter.coverListener?.favoriteManga(false) } @@ -66,6 +67,7 @@ class MangaHeaderHolder( } override fun bind(item: ChapterItem, manga: Manga) { + val presenter = adapter.coverListener?.mangaPresenter() ?: return manga_full_title.text = manga.currentTitle() if (manga.currentGenres().isNullOrBlank().not()) @@ -83,7 +85,8 @@ class MangaHeaderHolder( .no_description) manga_summary.post { - if (manga_summary.lineCount < 3 && manga.currentGenres().isNullOrBlank()) { + if ((manga_summary.lineCount < 3 && manga.currentGenres().isNullOrBlank()) + || less_button.visibility == View.VISIBLE) { more_button_group.gone() } else @@ -127,11 +130,11 @@ class MangaHeaderHolder( itemView.context.getResourceColor(R.attr .colorOnSurface), 31)) } - true_backdrop.setBackgroundColor(adapter.coverListener?.coverColor() ?: + true_backdrop.setBackgroundColor(adapter.coverListener.coverColor() ?: itemView.context.getResourceColor(android.R.attr.colorBackground)) with(start_reading_button) { - val nextChapter = adapter.coverListener?.nextChapter() + val nextChapter = presenter.getNextUnreadChapter() visibleIf(nextChapter != null && !item.isLocked) if (nextChapter != null) { val number = adapter.decimalFormat.format(nextChapter.chapter_number.toDouble()) @@ -147,11 +150,11 @@ class MangaHeaderHolder( } } - val count = adapter.coverListener?.chapterCount() ?: 0 + val count = presenter.chapters.size chapters_title.text = itemView.resources.getQuantityString(R.plurals.chapters, count, count) top_view.updateLayoutParams { - height = adapter.coverListener?.topCoverHeight() ?: 0 + height = adapter.coverListener.topCoverHeight() ?: 0 } manga_status.text = (itemView.context.getString( when (manga.status) { @@ -160,7 +163,9 @@ class MangaHeaderHolder( SManga.LICENSED -> R.string.licensed else -> R.string.unknown_status })) - manga_source.text = adapter.coverListener?.mangaSource()?.toString() + manga_source.text = presenter.source.toString() + + filters_text.text = presenter.currentFilters() if (!manga.initialized) return GlideApp.with(view.context).load(manga) @@ -171,6 +176,7 @@ class MangaHeaderHolder( .diskCacheStrategy(DiskCacheStrategy.AUTOMATIC) .signature(ObjectKey(MangaImpl.getLastCoverFetch(manga.id!!).toString())) .centerCrop() + .transition(DrawableTransitionOptions.withCrossFade()) .into(backdrop) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt index 72da5d9fee..2fdc00366d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt @@ -1,5 +1,8 @@ package eu.kanade.tachiyomi.ui.manga +import android.app.Application +import android.graphics.Bitmap +import android.net.Uri import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.cache.CoverCache import eu.kanade.tachiyomi.data.database.DatabaseHelper @@ -18,9 +21,11 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.source.LocalSource import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.ui.manga.chapter.ChapterItem import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource +import eu.kanade.tachiyomi.util.storage.DiskUtil import eu.kanade.tachiyomi.util.system.launchUI import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -30,6 +35,9 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get +import java.io.File +import java.io.FileOutputStream +import java.io.OutputStream import java.util.Date import kotlin.coroutines.CoroutineContext @@ -89,22 +97,24 @@ class MangaPresenter(private val controller: MangaChaptersController, val chapters = withContext(Dispatchers.IO) { db.getChapters(manga).executeAsBlocking().map { it.toModel() } } - // Store the last emission - this.chapters = applyChapterFilters(chapters) // Find downloaded chapters setDownloadedChapters(chapters) + + // Store the last emission + this.chapters = applyChapterFilters(chapters) + } private fun updateChapters(fetchedChapters: List? = null) { val chapters = (fetchedChapters ?: db.getChapters(manga).executeAsBlocking()).map { it.toModel() } - // Store the last emission - this.chapters = applyChapterFilters(chapters) - // Find downloaded chapters setDownloadedChapters(chapters) + + // Store the last emission + this.chapters = applyChapterFilters(chapters) } /** @@ -148,9 +158,10 @@ class MangaPresenter(private val controller: MangaChaptersController, * Sets the active display mode. * @param mode the mode to set. */ - fun setDisplayMode(mode: Int) { - manga.displayMode = mode + fun hideTitle(hide: Boolean) { + manga.displayMode = if (hide) Manga.DISPLAY_NUMBER else Manga.DISPLAY_NAME db.updateFlags(manga).executeAsBlocking() + controller.refreshAdapter() } /** @@ -240,6 +251,11 @@ class MangaPresenter(private val controller: MangaChaptersController, return chapters.sortedByDescending { it.source_order }.find { !it.read } } + fun getUnreadChaptersSorted() = chapters + .filter { !it.read && it.status == Download.NOT_DOWNLOADED } + .distinctBy { it.name } + .sortedByDescending { it.source_order } + /** * Returns the next unread chapter or null if everything is read. */ @@ -404,13 +420,50 @@ class MangaPresenter(private val controller: MangaChaptersController, } /** - * Reverses the sorting and requests an UI update. + * Sets the sorting order and requests an UI update. */ fun setSortOrder(desend: Boolean) { manga.setChapterOrder(if (desend) Manga.SORT_ASC else Manga.SORT_DESC) - db.updateFlags(manga).executeAsBlocking() - updateChapters() - controller.updateChapters(chapters) + asyncUpdateMangaAndChapters() + } + + /** + * Sets the sorting method and requests an UI update. + */ + fun setSortMethod(bySource: Boolean) { + manga.sorting = if (bySource) Manga.SORTING_SOURCE else Manga.SORTING_NUMBER + asyncUpdateMangaAndChapters() + } + + /** + * Removes all filters and requests an UI update. + */ + fun setFilters(read: Boolean, unread: Boolean, downloaded: Boolean, bookmarked: Boolean) { + manga.readFilter = when { + read -> Manga.SHOW_READ + unread -> Manga.SHOW_UNREAD + else -> Manga.SHOW_ALL + } + manga.downloadedFilter = if (downloaded) Manga.SHOW_DOWNLOADED else Manga.SHOW_ALL + manga.bookmarkedFilter = if (bookmarked) Manga.SHOW_BOOKMARKED else Manga.SHOW_ALL + asyncUpdateMangaAndChapters() + } + + private fun asyncUpdateMangaAndChapters() { + launch { + withContext(Dispatchers.IO) { db.updateFlags(manga).executeAsBlocking() } + updateChapters() + withContext(Dispatchers.Main) { controller.updateChapters(chapters) } + } + } + + fun currentFilters(): String { + val filtersId = mutableListOf() + filtersId.add(if (onlyRead()) R.string.action_filter_read else null) + filtersId.add(if (onlyUnread()) R.string.action_filter_unread else null) + filtersId.add(if (onlyDownloaded()) R.string.action_filter_downloaded else null) + filtersId.add(if (onlyBookmarked()) R.string.action_filter_bookmarked else null) + return filtersId.filterNotNull().joinToString(", ") { preferences.context.getString(it) } } fun toggleFavorite(): Boolean { @@ -479,4 +532,122 @@ class MangaPresenter(private val controller: MangaChaptersController, fetchChapters() } } + + fun shareManga(cover: Bitmap) { + val context = Injekt.get() + + val destDir = File(context.cacheDir, "shared_image") + + launch(Dispatchers.IO) { + destDir.deleteRecursively() + try { + val image = saveImage(cover, destDir, manga) + if (image != null) + controller.shareManga(image) + else controller.shareManga() + } + catch (e:java.lang.Exception) { } + } + } + + private fun saveImage(cover:Bitmap, directory: File, manga: Manga): File? { + directory.mkdirs() + + // Build destination file. + val filename = DiskUtil.buildValidFilename("${manga.originalTitle()} - Cover.jpg") + + val destFile = File(directory, filename) + val stream: OutputStream = FileOutputStream(destFile) + cover.compress(Bitmap.CompressFormat.JPEG, 75, stream) + stream.flush() + stream.close() + return destFile + } + + fun updateManga(title:String?, author:String?, artist: String?, uri: Uri?, + description: String?, tags: Array?) { + if (manga.source == LocalSource.ID) { + manga.title = if (title.isNullOrBlank()) manga.url else title.trim() + manga.author = author?.trim() + manga.artist = artist?.trim() + manga.description = description?.trim() + val tagsString = tags?.joinToString(", ") { it.capitalize() } + manga.genre = if (tags.isNullOrEmpty()) null else tagsString?.trim() + LocalSource(downloadManager.context).updateMangaInfo(manga) + db.updateMangaInfo(manga).executeAsBlocking() + } + else { + var changed = false + val title = title?.trim() + if (!title.isNullOrBlank() && manga.originalTitle().isBlank()) { + manga.title = title + changed = true + } + else if (title.isNullOrBlank() && manga.currentTitle() != manga.originalTitle()) { + manga.title = manga.originalTitle() + changed = true + } else if (!title.isNullOrBlank() && title != manga.currentTitle()) { + manga.title = "${title}${SManga.splitter}${manga.originalTitle()}" + changed = true + } + + val author = author?.trim() + if (author.isNullOrBlank() && manga.currentAuthor() != manga.originalAuthor()) { + manga.author = manga.originalAuthor() + changed = true + } else if (!author.isNullOrBlank() && author != manga.currentAuthor()) { + manga.author = "${author}${SManga.splitter}${manga.originalAuthor() ?: ""}" + changed = true + } + + val artist = artist?.trim() + if (artist.isNullOrBlank() && manga.currentArtist() != manga.originalArtist()) { + manga.artist = manga.originalArtist() + changed = true + } else if (!artist.isNullOrBlank() && artist != manga.currentArtist()) { + manga.artist = "${artist}${SManga.splitter}${manga.originalArtist() ?: ""}" + changed = true + } + + val description = description?.trim() + if (description.isNullOrBlank() && manga.currentDesc() != manga.originalDesc()) { + manga.description = manga.originalDesc() + changed = true + } else if (!description.isNullOrBlank() && description != manga.currentDesc()) { + manga.description = "${description}${SManga.splitter}${manga.originalDesc() ?: ""}" + changed = true + } + + var tagsString = tags?.joinToString(", ") + if ((tagsString.isNullOrBlank() && manga.currentGenres() != manga.originalGenres()) + || tagsString == manga.originalGenres()) { + manga.genre = manga.originalGenres() + changed = true + } else if (!tagsString.isNullOrBlank() && tagsString != manga.currentGenres()) { + tagsString = tags?.joinToString(", ") { it.capitalize() } + manga.genre = "${tagsString}${SManga.splitter}${manga.originalGenres() ?: ""}" + changed = true + } + if (changed) db.updateMangaInfo(manga).executeAsBlocking() + } + if (uri != null) editCoverWithStream(uri) + controller.updateHeader() + } + + private fun editCoverWithStream(uri: Uri): Boolean { + val inputStream = downloadManager.context.contentResolver.openInputStream(uri) ?: + return false + if (manga.source == LocalSource.ID) { + LocalSource.updateCover(downloadManager.context, manga, inputStream) + return true + } + + if (manga.thumbnail_url != null && manga.favorite) { + Injekt.get().refreshCoversToo().set(false) + coverCache.copyToCache(manga.thumbnail_url!!, inputStream) + MangaImpl.setLastCoverFetch(manga.id!!, Date().time) + return true + } + return false + } } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersAdapter.kt index 03f94ab2ae..01dd376595 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersAdapter.kt @@ -5,11 +5,10 @@ import android.view.MenuItem import androidx.fragment.app.FragmentActivity import eu.davidea.flexibleadapter.FlexibleAdapter import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.getOrDefault -import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.ui.base.controller.BaseController +import eu.kanade.tachiyomi.ui.manga.MangaPresenter import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate import eu.kanade.tachiyomi.util.system.getResourceColor import uy.kohesive.injekt.injectLazy @@ -60,13 +59,12 @@ class ChaptersAdapter( interface MangaHeaderInterface { fun coverColor(): Int? - fun nextChapter(): Chapter? + fun mangaPresenter(): MangaPresenter + fun prepareToShareManga() fun readNextChapter() fun downloadChapter(position: Int) fun topCoverHeight(): Int - fun chapterCount(): Int fun tagClicked(text: String) - fun mangaSource(): Source fun showChapterFilter() fun favoriteManga(longPress: Boolean) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/EditMangaDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/EditMangaDialog.kt index f457793101..53af9b7d6b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/EditMangaDialog.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/EditMangaDialog.kt @@ -17,6 +17,7 @@ import eu.kanade.tachiyomi.data.database.models.MangaImpl import eu.kanade.tachiyomi.data.glide.GlideApp import eu.kanade.tachiyomi.source.LocalSource import eu.kanade.tachiyomi.ui.base.controller.DialogController +import eu.kanade.tachiyomi.ui.manga.MangaChaptersController import eu.kanade.tachiyomi.util.lang.chop import eu.kanade.tachiyomi.util.system.toast import kotlinx.android.synthetic.main.edit_manga_dialog.view.* @@ -34,9 +35,9 @@ class EditMangaDialog : DialogController { private var customCoverUri:Uri? = null private val infoController - get() = targetController as MangaInfoController + get() = targetController as MangaChaptersController - constructor(target: MangaInfoController, manga: Manga) : super(Bundle() + constructor(target: MangaChaptersController, manga: Manga) : super(Bundle() .apply { putLong(KEY_MANGA, manga.id!!) }) { @@ -168,7 +169,6 @@ class EditMangaDialog : DialogController { dialogView?.manga_author?.text.toString(), dialogView?.manga_artist?.text.toString(), customCoverUri, dialogView?.manga_description?.text.toString(), dialogView?.manga_genres_tags?.tags) - infoController.updateTitle() } private companion object { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoController.kt index 3fffd8daca..43c355ea1b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoController.kt @@ -5,7 +5,6 @@ import android.animation.AnimatorListenerAdapter import android.animation.AnimatorSet import android.animation.ObjectAnimator import android.app.Activity -import android.app.Dialog import android.app.PendingIntent import android.content.ClipData import android.content.ClipboardManager @@ -15,7 +14,6 @@ import android.content.res.Configuration import android.graphics.Bitmap import android.graphics.drawable.Drawable import android.os.Build -import android.os.Bundle import android.view.LayoutInflater import android.view.Menu import android.view.MenuInflater @@ -31,8 +29,6 @@ import androidx.transition.ChangeBounds import androidx.transition.ChangeImageTransform import androidx.transition.TransitionManager import androidx.transition.TransitionSet -import com.afollestad.materialdialogs.MaterialDialog -import com.afollestad.materialdialogs.list.listItemsSingleChoice import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.load.resource.bitmap.RoundedCorners import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions @@ -55,13 +51,13 @@ import eu.kanade.tachiyomi.data.preference.getOrDefault 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.library.LibraryController import eu.kanade.tachiyomi.ui.main.MainActivity +import eu.kanade.tachiyomi.ui.manga.ChooseShapeDialog import eu.kanade.tachiyomi.ui.manga.MangaController import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate import eu.kanade.tachiyomi.ui.webview.WebViewActivity @@ -236,7 +232,7 @@ class MangaInfoController : NucleusController(), override fun onOptionsItemSelected(item: MenuItem): Boolean { when (item.itemId) { - R.id.action_edit -> EditMangaDialog(this, presenter.manga).showDialog(router) + //R.id.action_edit -> EditMangaDialog(this, presenter.manga).showDialog(router) R.id.action_open_in_web_view -> openInWebView() R.id.action_share -> prepareToShareManga() R.id.action_add_to_home_screen -> addToHomeScreen() @@ -608,41 +604,13 @@ class MangaInfoController : NucleusController(), } } - /** - * 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(activity!!) - .title(R.string.icon_shape) - .negativeButton(android.R.string.cancel) - .listItemsSingleChoice ( - items = modes.map { activity?.getString(it) as CharSequence }, - waitForPositiveButton = false) - { _, i, _ -> - (targetController as? MangaInfoController)?.createShortcutForShape(i) - dismissDialog() - } - } - } - /** * 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) { + fun createShortcutForShape(i: Int = 0) { if (activity == null) return GlideApp.with(activity!!) .asBitmap() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersController.kt index af19b4290c..a5e9a5182e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersController.kt @@ -28,7 +28,7 @@ import eu.kanade.tachiyomi.ui.manga.MangaChaptersController import eu.kanade.tachiyomi.ui.reader.ReaderActivity import eu.kanade.tachiyomi.ui.recently_read.RecentlyReadController import eu.kanade.tachiyomi.util.system.notificationManager -import eu.kanade.tachiyomi.util.view.applyWindowInsetsForController +import eu.kanade.tachiyomi.util.view.applyWindowInsetsForRootController import eu.kanade.tachiyomi.util.view.snack import kotlinx.android.synthetic.main.main_activity.* import kotlinx.android.synthetic.main.recent_chapters_controller.* @@ -82,7 +82,8 @@ class RecentChaptersController : NucleusController(), */ override fun onViewCreated(view: View) { super.onViewCreated(view) - view.applyWindowInsetsForController() + view.applyWindowInsetsForRootController(activity!!.navigationView) + view.context.notificationManager.cancel(Notifications.ID_NEW_CHAPTERS) // Init RecyclerView and adapter val layoutManager = LinearLayoutManager(view.context) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadController.kt index 4cd67376b9..27207c8444 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadController.kt @@ -26,8 +26,9 @@ import eu.kanade.tachiyomi.ui.recent_updates.RecentChaptersController import eu.kanade.tachiyomi.util.system.launchUI import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.view.RecyclerWindowInsetsListener -import eu.kanade.tachiyomi.util.view.applyWindowInsetsForController +import eu.kanade.tachiyomi.util.view.applyWindowInsetsForRootController import eu.kanade.tachiyomi.util.view.setOnQueryTextChangeListener +import kotlinx.android.synthetic.main.main_activity.* import kotlinx.android.synthetic.main.recently_read_controller.* import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get @@ -80,16 +81,7 @@ class RecentlyReadController(bundle: Bundle? = null) : BaseController(bundle), */ override fun onViewCreated(view: View) { super.onViewCreated(view) - view.applyWindowInsetsForController() - /*view.updateLayoutParams { - val attrsArray = intArrayOf(android.R.attr.actionBarSize) - val array = view.context.obtainStyledAttributes(attrsArray) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - topMargin = activity!!.window.decorView.rootWindowInsets.systemWindowInsetTop + array - .getDimensionPixelSize(0, 0) - } - array.recycle() - }*/ + view.applyWindowInsetsForRootController(activity!!.navigationView) // Initialize adapter adapter = RecentlyReadAdapter(this) recycler.adapter = adapter diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsController.kt index b61b12d2af..613e4e5d6e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsController.kt @@ -17,6 +17,8 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.ui.base.controller.BaseController import eu.kanade.tachiyomi.util.view.RecyclerWindowInsetsListener import eu.kanade.tachiyomi.util.view.applyWindowInsetsForController +import eu.kanade.tachiyomi.util.view.applyWindowInsetsForRootController +import kotlinx.android.synthetic.main.main_activity.* import rx.Observable import rx.Subscription import rx.subscriptions.CompositeSubscription @@ -35,24 +37,12 @@ abstract class SettingsController : PreferenceController() { untilDestroySubscriptions = CompositeSubscription() } val view = super.onCreateView(inflater, container, savedInstanceState) - /*view.updateLayoutParams { - val attrsArray = intArrayOf(android.R.attr.actionBarSize) - val array = view.context.obtainStyledAttributes(attrsArray) - topMargin = array.getDimensionPixelSize(0, 0) - array.recycle() - }*/ - - view.applyWindowInsetsForController() - /*view.updateLayoutParams { - val attrsArray = intArrayOf(android.R.attr.actionBarSize) - val array = view.context.obtainStyledAttributes(attrsArray) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - topMargin = activity!!.window.decorView.rootWindowInsets.systemWindowInsetTop + array - .getDimensionPixelSize(0, 0) - } - array.recycle() - }*/ - listView.setOnApplyWindowInsetsListener(RecyclerWindowInsetsListener) + if (this is SettingsMainController) + view.applyWindowInsetsForRootController(activity!!.navigationView) + else { + view.applyWindowInsetsForController() + listView.setOnApplyWindowInsetsListener(RecyclerWindowInsetsListener) + } return view } diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt index f8aea5c918..6ec2891d5e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt @@ -12,6 +12,7 @@ import android.graphics.drawable.GradientDrawable import android.os.Build import android.view.View import android.view.ViewGroup +import android.view.ViewTreeObserver import android.view.WindowInsets import android.widget.Button import android.widget.FrameLayout @@ -207,6 +208,28 @@ fun View.applyWindowInsetsForController() { requestApplyInsetsWhenAttached() } +fun View.applyWindowInsetsForRootController(bottomNav: View) { + viewTreeObserver.addOnGlobalLayoutListener( + object : ViewTreeObserver.OnGlobalLayoutListener { + override fun onGlobalLayout() { + if (bottomNav.height > 0) { + viewTreeObserver.removeOnGlobalLayoutListener(this) + setOnApplyWindowInsetsListener { view, insets -> + view.updateLayoutParams { + val attrsArray = intArrayOf(android.R.attr.actionBarSize) + val array = view.context.obtainStyledAttributes(attrsArray) + topMargin = insets.systemWindowInsetTop + array.getDimensionPixelSize(0, 0) + bottomMargin = bottomNav.height + array.recycle() + } + insets + } + requestApplyInsetsWhenAttached() + } + } + } + ) +} fun View.requestApplyInsetsWhenAttached() { if (isAttachedToWindow) { diff --git a/app/src/main/res/layout/categories_item.xml b/app/src/main/res/layout/categories_item.xml index 0b0345af65..775f76ca42 100644 --- a/app/src/main/res/layout/categories_item.xml +++ b/app/src/main/res/layout/categories_item.xml @@ -52,6 +52,7 @@ android:imeOptions="actionDone" android:inputType="none" android:maxLines="1" + android:singleLine="true" android:textColor="@color/textColorPrimary" android:textSize="16sp" app:layout_constraintEnd_toEndOf="@id/title" diff --git a/app/src/main/res/layout/chapter_sort_bottom_sheet.xml b/app/src/main/res/layout/chapter_sort_bottom_sheet.xml index 1c62026cd6..6e5fbe1036 100644 --- a/app/src/main/res/layout/chapter_sort_bottom_sheet.xml +++ b/app/src/main/res/layout/chapter_sort_bottom_sheet.xml @@ -30,7 +30,7 @@ + android:text="@string/newest_first" /> + android:text="@string/oldest_first" /> - - - - - + android:text="@string/show_all" /> + android:text="@string/show_read_chapters" /> + android:text="@string/show_unread_chapters" /> + android:text="@string/show_downloaded_chapters" /> + android:text="@string/show_bookmarked_chapters" /> + + + + + + + + + + + diff --git a/app/src/main/res/layout/main_activity.xml b/app/src/main/res/layout/main_activity.xml index 461c2acf16..5efe0b2ece 100644 --- a/app/src/main/res/layout/main_activity.xml +++ b/app/src/main/res/layout/main_activity.xml @@ -10,8 +10,8 @@ android:id="@+id/controller_container" android:layout_width="match_parent" android:layout_height="0dp" - app:layout_constraintBottom_toTopOf="@+id/navigationView" app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"> diff --git a/app/src/main/res/layout/manga_header_item.xml b/app/src/main/res/layout/manga_header_item.xml index 54335c9438..811e0b6790 100644 --- a/app/src/main/res/layout/manga_header_item.xml +++ b/app/src/main/res/layout/manga_header_item.xml @@ -165,7 +165,7 @@ app:icon="@drawable/ic_sync_black_24dp" /> + android:contentDescription="@string/action_share" + android:src="@drawable/ic_share_white_24dp" /> + app:layout_constraintTop_toBottomOf="@id/start_reading_button" /> + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="@id/chapters_title" /> + app:layout_constraintEnd_toStartOf="@id/filter_button" + app:layout_constraintStart_toEndOf="@+id/chapters_title" + app:layout_constraintTop_toTopOf="@id/filter_button" + tools:text="Read, Unread, Bookmarked, Downloaded, All" /> \ No newline at end of file diff --git a/app/src/main/res/menu/manga_details.xml b/app/src/main/res/menu/manga_details.xml new file mode 100644 index 0000000000..fd032306c4 --- /dev/null +++ b/app/src/main/res/menu/manga_details.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a69fb1ccc2..9a36359139 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -68,6 +68,7 @@ Don\'t migrate Select all Mark as read + Mark all as read Mark as unread Mark previous as read Mark multiple @@ -507,6 +508,7 @@ %1$d chapters No description + Mark all chapters as read? Start reading @@ -710,5 +712,16 @@ Auto More Less + More options + Show bookmarked chapters + Show downloaded chapters + Show unread chapters + Show read chapters + Show All + Hide chapter titles + Sort by source\'s order + Sort by chapter number + Newest to oldest + Oldest to newest