Redesign details screen
This commit is contained in:
@@ -2,17 +2,22 @@ package org.koitharu.kotatsu.base.ui.widgets
|
||||
|
||||
import android.animation.LayoutTransition
|
||||
import android.content.Context
|
||||
import android.transition.AutoTransition
|
||||
import android.transition.TransitionManager
|
||||
import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.WindowInsets
|
||||
import android.view.animation.AccelerateDecelerateInterpolator
|
||||
import android.view.animation.DecelerateInterpolator
|
||||
import androidx.annotation.AttrRes
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
import androidx.core.content.withStyledAttributes
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.*
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import com.google.android.material.R as materialR
|
||||
import com.google.android.material.appbar.AppBarLayout
|
||||
import com.google.android.material.appbar.MaterialToolbar
|
||||
@@ -28,7 +33,7 @@ class BottomSheetHeaderBar @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
@AttrRes defStyleAttr: Int = materialR.attr.appBarLayoutStyle,
|
||||
) : AppBarLayout(context, attrs, defStyleAttr) {
|
||||
) : AppBarLayout(context, attrs, defStyleAttr), MenuHost {
|
||||
|
||||
private val binding = LayoutSheetHeaderBinding.inflate(LayoutInflater.from(context), this)
|
||||
private val closeDrawable = context.getThemeDrawable(materialR.attr.actionModeCloseDrawable)
|
||||
@@ -36,10 +41,25 @@ class BottomSheetHeaderBar @JvmOverloads constructor(
|
||||
private var bottomSheetBehavior: BottomSheetBehavior<*>? = null
|
||||
private val locationBuffer = IntArray(2)
|
||||
private val expansionListeners = LinkedList<OnExpansionChangeListener>()
|
||||
private var fitStatusBar = false
|
||||
private var transition: AutoTransition? = null
|
||||
|
||||
@Deprecated("")
|
||||
val toolbar: MaterialToolbar
|
||||
get() = binding.toolbar
|
||||
|
||||
var title: CharSequence?
|
||||
get() = binding.toolbar.title
|
||||
set(value) {
|
||||
binding.toolbar.title = value
|
||||
}
|
||||
|
||||
var subtitle: CharSequence?
|
||||
get() = binding.toolbar.subtitle
|
||||
set(value) {
|
||||
binding.toolbar.subtitle = value
|
||||
}
|
||||
|
||||
init {
|
||||
setBackgroundResource(R.drawable.sheet_toolbar_background)
|
||||
layoutTransition = LayoutTransition().apply {
|
||||
@@ -47,6 +67,7 @@ class BottomSheetHeaderBar @JvmOverloads constructor(
|
||||
}
|
||||
context.withStyledAttributes(attrs, R.styleable.BottomSheetHeaderBar, defStyleAttr) {
|
||||
binding.toolbar.title = getString(R.styleable.BottomSheetHeaderBar_title)
|
||||
fitStatusBar = getBoolean(R.styleable.BottomSheetHeaderBar_fitStatusBar, fitStatusBar)
|
||||
val menuResId = getResourceId(R.styleable.BottomSheetHeaderBar_menu, 0)
|
||||
if (menuResId != 0) {
|
||||
binding.toolbar.inflateMenu(menuResId)
|
||||
@@ -89,6 +110,31 @@ class BottomSheetHeaderBar @JvmOverloads constructor(
|
||||
}
|
||||
}
|
||||
|
||||
override fun onApplyWindowInsets(insets: WindowInsets?): WindowInsets {
|
||||
dispatchInsets(if (insets != null) WindowInsetsCompat.toWindowInsetsCompat(insets) else null)
|
||||
return super.onApplyWindowInsets(insets)
|
||||
}
|
||||
|
||||
override fun addMenuProvider(provider: MenuProvider) {
|
||||
binding.toolbar.addMenuProvider(provider)
|
||||
}
|
||||
|
||||
override fun addMenuProvider(provider: MenuProvider, owner: LifecycleOwner) {
|
||||
binding.toolbar.addMenuProvider(provider, owner)
|
||||
}
|
||||
|
||||
override fun addMenuProvider(provider: MenuProvider, owner: LifecycleOwner, state: Lifecycle.State) {
|
||||
binding.toolbar.addMenuProvider(provider, owner, state)
|
||||
}
|
||||
|
||||
override fun removeMenuProvider(provider: MenuProvider) {
|
||||
binding.toolbar.removeMenuProvider(provider)
|
||||
}
|
||||
|
||||
override fun invalidateMenu() {
|
||||
binding.toolbar.invalidateMenu()
|
||||
}
|
||||
|
||||
fun setNavigationOnClickListener(onClickListener: OnClickListener) {
|
||||
binding.toolbar.setNavigationOnClickListener(onClickListener)
|
||||
}
|
||||
@@ -115,9 +161,24 @@ class BottomSheetHeaderBar @JvmOverloads constructor(
|
||||
if (isExpanded == binding.dragHandle.isGone) {
|
||||
return
|
||||
}
|
||||
TransitionManager.beginDelayedTransition(this, getTransition())
|
||||
binding.toolbar.navigationIcon = (if (isExpanded) closeDrawable else null)
|
||||
binding.dragHandle.isGone = isExpanded
|
||||
expansionListeners.forEach { it.onExpansionStateChanged(this, isExpanded) }
|
||||
dispatchInsets(ViewCompat.getRootWindowInsets(this))
|
||||
}
|
||||
|
||||
private fun dispatchInsets(insets: WindowInsetsCompat?) {
|
||||
if (!fitStatusBar) {
|
||||
return
|
||||
}
|
||||
val isExpanded = binding.dragHandle.isGone
|
||||
if (isExpanded) {
|
||||
val topInset = insets?.getInsets(WindowInsetsCompat.Type.systemBars())?.top ?: 0
|
||||
updatePadding(top = topInset)
|
||||
} else {
|
||||
updatePadding(top = 0)
|
||||
}
|
||||
}
|
||||
|
||||
private fun findParentBottomSheetBehavior(): BottomSheetBehavior<*>? {
|
||||
@@ -142,7 +203,12 @@ class BottomSheetHeaderBar @JvmOverloads constructor(
|
||||
}
|
||||
|
||||
private fun dismissBottomSheet() {
|
||||
bottomSheetBehavior?.state = BottomSheetBehavior.STATE_HIDDEN
|
||||
val behavior = bottomSheetBehavior ?: return
|
||||
if (behavior.isHideable) {
|
||||
behavior.state = BottomSheetBehavior.STATE_HIDDEN
|
||||
} else {
|
||||
behavior.state = BottomSheetBehavior.STATE_COLLAPSED
|
||||
}
|
||||
}
|
||||
|
||||
private fun shouldAddView(child: View?): Boolean {
|
||||
@@ -167,6 +233,15 @@ class BottomSheetHeaderBar @JvmOverloads constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun getTransition(): AutoTransition {
|
||||
transition?.let { return it }
|
||||
val t = AutoTransition()
|
||||
t.duration = context.getAnimationDuration(R.integer.config_tinyAnimTime)
|
||||
// t.interpolator = AccelerateDecelerateInterpolator()
|
||||
transition = t
|
||||
return t
|
||||
}
|
||||
|
||||
private inner class Callback : BottomSheetBehavior.BottomSheetCallback(), View.OnClickListener {
|
||||
|
||||
override fun onStateChanged(bottomSheet: View, newState: Int) {
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
package org.koitharu.kotatsu.details.ui
|
||||
|
||||
import android.view.View
|
||||
import android.view.View.OnLayoutChangeListener
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.appcompat.view.ActionMode
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import org.koitharu.kotatsu.base.ui.util.ActionModeListener
|
||||
import org.koitharu.kotatsu.base.ui.widgets.BottomSheetHeaderBar
|
||||
|
||||
class ChaptersBottomSheetMediator(
|
||||
bottomSheet: View,
|
||||
) : OnBackPressedCallback(false),
|
||||
ActionModeListener,
|
||||
BottomSheetHeaderBar.OnExpansionChangeListener,
|
||||
OnLayoutChangeListener {
|
||||
|
||||
private val behavior = BottomSheetBehavior.from(bottomSheet)
|
||||
private var lockCounter = 0
|
||||
|
||||
override fun handleOnBackPressed() {
|
||||
behavior.state = BottomSheetBehavior.STATE_COLLAPSED
|
||||
}
|
||||
|
||||
override fun onActionModeStarted(mode: ActionMode) {
|
||||
lock()
|
||||
}
|
||||
|
||||
override fun onActionModeFinished(mode: ActionMode) {
|
||||
unlock()
|
||||
}
|
||||
|
||||
override fun onExpansionStateChanged(headerBar: BottomSheetHeaderBar, isExpanded: Boolean) {
|
||||
isEnabled = isExpanded
|
||||
}
|
||||
|
||||
override fun onLayoutChange(
|
||||
v: View?,
|
||||
left: Int,
|
||||
top: Int,
|
||||
right: Int,
|
||||
bottom: Int,
|
||||
oldLeft: Int,
|
||||
oldTop: Int,
|
||||
oldRight: Int,
|
||||
oldBottom: Int,
|
||||
) {
|
||||
val height = bottom - top
|
||||
if (height != behavior.peekHeight) {
|
||||
behavior.peekHeight = height
|
||||
}
|
||||
}
|
||||
|
||||
fun lock() {
|
||||
lockCounter++
|
||||
behavior.isDraggable = lockCounter <= 0
|
||||
}
|
||||
|
||||
fun unlock() {
|
||||
lockCounter--
|
||||
behavior.isDraggable = lockCounter <= 0
|
||||
}
|
||||
}
|
||||
@@ -2,16 +2,14 @@ package org.koitharu.kotatsu.details.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.*
|
||||
import android.widget.AdapterView
|
||||
import android.widget.Spinner
|
||||
import androidx.appcompat.view.ActionMode
|
||||
import androidx.appcompat.widget.SearchView
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.core.view.MenuProvider
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import kotlin.math.roundToInt
|
||||
import org.koitharu.kotatsu.R
|
||||
@@ -19,7 +17,6 @@ import org.koitharu.kotatsu.base.ui.BaseFragment
|
||||
import org.koitharu.kotatsu.base.ui.list.ListSelectionController
|
||||
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.databinding.FragmentChaptersBinding
|
||||
import org.koitharu.kotatsu.details.ui.adapter.BranchesAdapter
|
||||
import org.koitharu.kotatsu.details.ui.adapter.ChaptersAdapter
|
||||
import org.koitharu.kotatsu.details.ui.adapter.ChaptersSelectionDecoration
|
||||
import org.koitharu.kotatsu.details.ui.model.ChapterListItem
|
||||
@@ -29,16 +26,13 @@ import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.reader.ui.ReaderActivity
|
||||
import org.koitharu.kotatsu.reader.ui.ReaderState
|
||||
import org.koitharu.kotatsu.utils.RecyclerViewScrollCallback
|
||||
import org.koitharu.kotatsu.utils.ext.addMenuProvider
|
||||
import org.koitharu.kotatsu.utils.ext.parents
|
||||
import org.koitharu.kotatsu.utils.ext.scaleUpActivityOptionsOf
|
||||
|
||||
class ChaptersFragment :
|
||||
BaseFragment<FragmentChaptersBinding>(),
|
||||
OnListItemClickListener<ChapterListItem>,
|
||||
AdapterView.OnItemSelectedListener,
|
||||
MenuItem.OnActionExpandListener,
|
||||
SearchView.OnQueryTextListener,
|
||||
ListSelectionController.Callback {
|
||||
ListSelectionController.Callback2 {
|
||||
|
||||
private val viewModel by activityViewModels<DetailsViewModel>()
|
||||
|
||||
@@ -64,23 +58,16 @@ class ChaptersFragment :
|
||||
setHasFixedSize(true)
|
||||
adapter = chaptersAdapter
|
||||
}
|
||||
binding.spinnerBranches?.let(::initSpinner)
|
||||
viewModel.isLoading.observe(viewLifecycleOwner, this::onLoadingStateChanged)
|
||||
viewModel.chapters.observe(viewLifecycleOwner, this::onChaptersChanged)
|
||||
viewModel.isChaptersReversed.observe(viewLifecycleOwner) {
|
||||
activity?.invalidateOptionsMenu()
|
||||
}
|
||||
viewModel.isChaptersEmpty.observe(viewLifecycleOwner) {
|
||||
binding.textViewHolder.isVisible = it
|
||||
activity?.invalidateOptionsMenu()
|
||||
}
|
||||
addMenuProvider(ChaptersMenuProvider())
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
chaptersAdapter = null
|
||||
selectionController = null
|
||||
binding.spinnerBranches?.adapter = null
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
@@ -106,7 +93,7 @@ class ChaptersFragment :
|
||||
return selectionController?.onItemLongClick(item.chapter.id) ?: false
|
||||
}
|
||||
|
||||
override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
|
||||
override fun onActionItemClicked(controller: ListSelectionController, mode: ActionMode, item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
R.id.action_save -> {
|
||||
DownloadService.start(
|
||||
@@ -136,7 +123,6 @@ class ChaptersFragment :
|
||||
true
|
||||
}
|
||||
R.id.action_select_range -> {
|
||||
val controller = selectionController ?: return false
|
||||
val items = chaptersAdapter?.items ?: return false
|
||||
val ids = HashSet(controller.peekCheckedIds())
|
||||
val buffer = HashSet<Long>()
|
||||
@@ -164,19 +150,12 @@ class ChaptersFragment :
|
||||
}
|
||||
}
|
||||
|
||||
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
|
||||
val spinner = binding.spinnerBranches ?: return
|
||||
viewModel.setSelectedBranch(spinner.selectedItem as String?)
|
||||
}
|
||||
|
||||
override fun onNothingSelected(parent: AdapterView<*>?) = Unit
|
||||
|
||||
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
|
||||
override fun onCreateActionMode(controller: ListSelectionController, mode: ActionMode, menu: Menu): Boolean {
|
||||
mode.menuInflater.inflate(R.menu.mode_chapters, menu)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
|
||||
override fun onPrepareActionMode(controller: ListSelectionController, mode: ActionMode, menu: Menu): Boolean {
|
||||
val selectedIds = selectionController?.peekCheckedIds() ?: return false
|
||||
val allItems = chaptersAdapter?.items.orEmpty()
|
||||
val items = allItems.withIndex().filter { (_, x) -> x.chapter.id in selectedIds }
|
||||
@@ -199,49 +178,19 @@ class ChaptersFragment :
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onSelectionChanged(count: Int) {
|
||||
override fun onSelectionChanged(controller: ListSelectionController, count: Int) {
|
||||
binding.recyclerViewChapters.invalidateItemDecorations()
|
||||
}
|
||||
|
||||
override fun onMenuItemActionExpand(item: MenuItem?): Boolean = true
|
||||
|
||||
override fun onMenuItemActionCollapse(item: MenuItem?): Boolean {
|
||||
(item?.actionView as? SearchView)?.setQuery("", false)
|
||||
viewModel.performChapterSearch(null)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onQueryTextSubmit(query: String?): Boolean = false
|
||||
|
||||
override fun onQueryTextChange(newText: String?): Boolean {
|
||||
viewModel.performChapterSearch(newText)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onWindowInsetsChanged(insets: Insets) {
|
||||
binding.recyclerViewChapters.updatePadding(
|
||||
bottom = insets.bottom + (binding.spinnerBranches?.height ?: 0),
|
||||
bottom = insets.bottom,
|
||||
)
|
||||
binding.recyclerViewChapters.fastScroller.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||
bottomMargin = insets.bottom
|
||||
}
|
||||
}
|
||||
|
||||
private fun initSpinner(spinner: Spinner) {
|
||||
val branchesAdapter = BranchesAdapter()
|
||||
spinner.adapter = branchesAdapter
|
||||
spinner.onItemSelectedListener = this
|
||||
viewModel.branches.observe(viewLifecycleOwner) {
|
||||
branchesAdapter.setItems(it)
|
||||
spinner.isVisible = it.size > 1
|
||||
}
|
||||
viewModel.selectedBranchIndex.observe(viewLifecycleOwner) {
|
||||
if (it != -1 && it != spinner.selectedItemPosition) {
|
||||
spinner.setSelection(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onChaptersChanged(list: List<ChapterListItem>) {
|
||||
val adapter = chaptersAdapter ?: return
|
||||
if (adapter.itemCount == 0) {
|
||||
@@ -261,29 +210,17 @@ class ChaptersFragment :
|
||||
binding.progressBar.isVisible = isLoading
|
||||
}
|
||||
|
||||
private inner class ChaptersMenuProvider : MenuProvider {
|
||||
|
||||
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
|
||||
menuInflater.inflate(R.menu.opt_chapters, menu)
|
||||
val searchMenuItem = menu.findItem(R.id.action_search)
|
||||
searchMenuItem.setOnActionExpandListener(this@ChaptersFragment)
|
||||
val searchView = searchMenuItem.actionView as SearchView
|
||||
searchView.setOnQueryTextListener(this@ChaptersFragment)
|
||||
searchView.setIconifiedByDefault(false)
|
||||
searchView.queryHint = searchMenuItem.title
|
||||
}
|
||||
|
||||
override fun onPrepareMenu(menu: Menu) {
|
||||
menu.findItem(R.id.action_reversed).isChecked = viewModel.isChaptersReversed.value == true
|
||||
menu.findItem(R.id.action_search).isVisible = viewModel.isChaptersEmpty.value == false
|
||||
}
|
||||
|
||||
override fun onMenuItemSelected(menuItem: MenuItem): Boolean = when (menuItem.itemId) {
|
||||
R.id.action_reversed -> {
|
||||
viewModel.setChaptersReversed(!menuItem.isChecked)
|
||||
true
|
||||
private fun findBottomSheetBehavior(): BottomSheetBehavior<*>? {
|
||||
val v = view ?: return null
|
||||
for (p in v.parents) {
|
||||
val layoutParams = (p as? View)?.layoutParams
|
||||
if (layoutParams is CoordinatorLayout.LayoutParams) {
|
||||
val behavior = layoutParams.behavior
|
||||
if (behavior is BottomSheetBehavior<*>) {
|
||||
return behavior
|
||||
}
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package org.koitharu.kotatsu.details.ui
|
||||
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import androidx.appcompat.widget.SearchView
|
||||
import androidx.core.view.MenuProvider
|
||||
import org.koitharu.kotatsu.R
|
||||
|
||||
class ChaptersMenuProvider(
|
||||
private val viewModel: DetailsViewModel,
|
||||
private val bottomSheetMediator: ChaptersBottomSheetMediator?,
|
||||
) : MenuProvider, SearchView.OnQueryTextListener, MenuItem.OnActionExpandListener {
|
||||
|
||||
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
|
||||
menuInflater.inflate(R.menu.opt_chapters, menu)
|
||||
val searchMenuItem = menu.findItem(R.id.action_search)
|
||||
searchMenuItem.setOnActionExpandListener(this)
|
||||
val searchView = searchMenuItem.actionView as SearchView
|
||||
searchView.setOnQueryTextListener(this)
|
||||
searchView.setIconifiedByDefault(false)
|
||||
searchView.queryHint = searchMenuItem.title
|
||||
}
|
||||
|
||||
override fun onPrepareMenu(menu: Menu) {
|
||||
menu.findItem(R.id.action_reversed).isChecked = viewModel.isChaptersReversed.value == true
|
||||
menu.findItem(R.id.action_search).isVisible = viewModel.isChaptersEmpty.value == false
|
||||
}
|
||||
|
||||
override fun onMenuItemSelected(menuItem: MenuItem): Boolean = when (menuItem.itemId) {
|
||||
R.id.action_reversed -> {
|
||||
viewModel.setChaptersReversed(!menuItem.isChecked)
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
|
||||
override fun onMenuItemActionExpand(item: MenuItem?): Boolean {
|
||||
bottomSheetMediator?.lock()
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onMenuItemActionCollapse(item: MenuItem?): Boolean {
|
||||
(item?.actionView as? SearchView)?.setQuery("", false)
|
||||
viewModel.performChapterSearch(null)
|
||||
bottomSheetMediator?.unlock()
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onQueryTextSubmit(query: String?): Boolean = false
|
||||
|
||||
override fun onQueryTextChange(newText: String?): Boolean {
|
||||
viewModel.performChapterSearch(newText)
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -6,52 +6,41 @@ import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.widget.AdapterView
|
||||
import android.widget.Spinner
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.view.ActionMode
|
||||
import androidx.core.content.pm.ShortcutManagerCompat
|
||||
import androidx.appcompat.widget.PopupMenu
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.commit
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.google.android.material.badge.BadgeDrawable
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import com.google.android.material.tabs.TabLayoutMediator
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.domain.MangaIntent
|
||||
import org.koitharu.kotatsu.base.ui.BaseActivity
|
||||
import org.koitharu.kotatsu.browser.BrowserActivity
|
||||
import org.koitharu.kotatsu.base.ui.widgets.BottomSheetHeaderBar
|
||||
import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
|
||||
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
|
||||
import org.koitharu.kotatsu.core.os.ShortcutsUpdater
|
||||
import org.koitharu.kotatsu.databinding.ActivityDetailsBinding
|
||||
import org.koitharu.kotatsu.details.ui.adapter.BranchesAdapter
|
||||
import org.koitharu.kotatsu.details.ui.model.HistoryInfo
|
||||
import org.koitharu.kotatsu.download.ui.service.DownloadService
|
||||
import org.koitharu.kotatsu.list.ui.adapter.bindBadge
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.util.mapNotNullToSet
|
||||
import org.koitharu.kotatsu.reader.ui.ReaderActivity
|
||||
import org.koitharu.kotatsu.reader.ui.ReaderState
|
||||
import org.koitharu.kotatsu.scrobbling.ui.selector.ScrobblingSelectorBottomSheet
|
||||
import org.koitharu.kotatsu.search.ui.multi.MultiSearchActivity
|
||||
import org.koitharu.kotatsu.utils.ext.assistedViewModels
|
||||
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
|
||||
import org.koitharu.kotatsu.utils.ext.isReportable
|
||||
import org.koitharu.kotatsu.utils.ext.report
|
||||
import org.koitharu.kotatsu.utils.ext.*
|
||||
|
||||
@AndroidEntryPoint
|
||||
class DetailsActivity :
|
||||
BaseActivity<ActivityDetailsBinding>(),
|
||||
TabLayoutMediator.TabConfigurationStrategy,
|
||||
AdapterView.OnItemSelectedListener {
|
||||
View.OnClickListener,
|
||||
BottomSheetHeaderBar.OnExpansionChangeListener {
|
||||
|
||||
@Inject
|
||||
lateinit var viewModelFactory: DetailsViewModel.Factory
|
||||
@@ -59,9 +48,12 @@ class DetailsActivity :
|
||||
@Inject
|
||||
lateinit var shortcutsUpdater: ShortcutsUpdater
|
||||
|
||||
private val viewModel by assistedViewModels<DetailsViewModel> {
|
||||
private var badge: BadgeDrawable? = null
|
||||
|
||||
private val viewModel: DetailsViewModel by assistedViewModels {
|
||||
viewModelFactory.create(MangaIntent(intent))
|
||||
}
|
||||
private lateinit var chaptersMenuProvider: ChaptersMenuProvider
|
||||
|
||||
private val downloadReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
@@ -77,23 +69,51 @@ class DetailsActivity :
|
||||
setDisplayHomeAsUpEnabled(true)
|
||||
setDisplayShowTitleEnabled(false)
|
||||
}
|
||||
val pager = binding.pager
|
||||
if (pager != null) {
|
||||
pager.adapter = MangaDetailsAdapter(this)
|
||||
TabLayoutMediator(checkNotNull(binding.tabs), pager, this).attach()
|
||||
binding.buttonRead.setOnClickListener(this)
|
||||
binding.buttonDropdown.setOnClickListener(this)
|
||||
|
||||
chaptersMenuProvider = if (binding.layoutBottom != null) {
|
||||
val bsMediator = ChaptersBottomSheetMediator(checkNotNull(binding.layoutBottom))
|
||||
actionModeDelegate.addListener(bsMediator)
|
||||
checkNotNull(binding.headerChapters).addOnExpansionChangeListener(bsMediator)
|
||||
checkNotNull(binding.headerChapters).addOnLayoutChangeListener(bsMediator)
|
||||
onBackPressedDispatcher.addCallback(bsMediator)
|
||||
ChaptersMenuProvider(viewModel, bsMediator)
|
||||
} else {
|
||||
ChaptersMenuProvider(viewModel, null)
|
||||
}
|
||||
gcFragments()
|
||||
binding.spinnerBranches?.let(::initSpinner)
|
||||
|
||||
viewModel.manga.observe(this, ::onMangaUpdated)
|
||||
viewModel.newChaptersCount.observe(this, ::onNewChaptersChanged)
|
||||
viewModel.onMangaRemoved.observe(this, ::onMangaRemoved)
|
||||
viewModel.onError.observe(this, ::onError)
|
||||
viewModel.onShowToast.observe(this) {
|
||||
binding.snackbar.show(messageText = getString(it))
|
||||
}
|
||||
viewModel.historyInfo.observe(this, ::onHistoryChanged)
|
||||
viewModel.selectedBranchName.observe(this) {
|
||||
binding.headerChapters?.subtitle = it
|
||||
binding.textViewSubtitle?.textAndVisible = it
|
||||
}
|
||||
viewModel.isChaptersReversed.observe(this) {
|
||||
binding.headerChapters?.invalidateMenu() ?: invalidateOptionsMenu()
|
||||
}
|
||||
viewModel.favouriteCategories.observe(this) {
|
||||
invalidateOptionsMenu()
|
||||
}
|
||||
viewModel.branches.observe(this) {
|
||||
binding.buttonDropdown.isVisible = it.size > 1
|
||||
}
|
||||
|
||||
registerReceiver(downloadReceiver, IntentFilter(DownloadService.ACTION_DOWNLOAD_COMPLETE))
|
||||
addMenuProvider(
|
||||
DetailsMenuProvider(
|
||||
activity = this,
|
||||
viewModel = viewModel,
|
||||
snackbarHost = binding.containerChapters,
|
||||
shortcutsUpdater = shortcutsUpdater,
|
||||
),
|
||||
)
|
||||
binding.headerChapters?.addOnExpansionChangeListener(this) ?: addMenuProvider(chaptersMenuProvider)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
@@ -101,8 +121,39 @@ class DetailsActivity :
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
override fun onClick(v: View) {
|
||||
val manga = viewModel.manga.value ?: return
|
||||
when (v.id) {
|
||||
R.id.button_read -> {
|
||||
val chapterId = viewModel.historyInfo.value?.history?.chapterId
|
||||
if (chapterId != null && manga.chapters?.none { x -> x.id == chapterId } == true) {
|
||||
showChapterMissingDialog(chapterId)
|
||||
} else {
|
||||
startActivity(
|
||||
ReaderActivity.newIntent(
|
||||
context = this,
|
||||
manga = manga,
|
||||
branch = viewModel.selectedBranchValue,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
R.id.button_dropdown -> showBranchPopupMenu()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onExpansionStateChanged(headerBar: BottomSheetHeaderBar, isExpanded: Boolean) {
|
||||
if (isExpanded) {
|
||||
headerBar.addMenuProvider(chaptersMenuProvider)
|
||||
} else {
|
||||
headerBar.removeMenuProvider(chaptersMenuProvider)
|
||||
}
|
||||
binding.buttonRead.isGone = isExpanded
|
||||
}
|
||||
|
||||
private fun onMangaUpdated(manga: Manga) {
|
||||
title = manga.title
|
||||
binding.buttonRead.isEnabled = !manga.chapters.isNullOrEmpty()
|
||||
invalidateOptionsMenu()
|
||||
}
|
||||
|
||||
@@ -124,149 +175,65 @@ class DetailsActivity :
|
||||
Toast.makeText(this, e.getDisplayMessage(resources), Toast.LENGTH_LONG).show()
|
||||
finishAfterTransition()
|
||||
}
|
||||
e.isReportable() -> {
|
||||
binding.snackbar.show(
|
||||
messageText = e.getDisplayMessage(resources),
|
||||
actionId = R.string.report,
|
||||
duration = if (viewModel.manga.value?.chapters == null) {
|
||||
else -> {
|
||||
val snackbar = Snackbar.make(
|
||||
binding.containerDetails,
|
||||
e.getDisplayMessage(resources),
|
||||
if (viewModel.manga.value?.chapters == null) {
|
||||
Snackbar.LENGTH_INDEFINITE
|
||||
} else {
|
||||
Snackbar.LENGTH_LONG
|
||||
},
|
||||
onActionClick = {
|
||||
e.report("DetailsActivity::onError")
|
||||
dismiss()
|
||||
},
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
binding.snackbar.show(e.getDisplayMessage(resources))
|
||||
if (e.isReportable()) {
|
||||
snackbar.setAction(R.string.report) {
|
||||
e.report("DetailsActivity::onError")
|
||||
}
|
||||
}
|
||||
snackbar.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onWindowInsetsChanged(insets: Insets) {
|
||||
binding.snackbar.updatePadding(
|
||||
bottom = insets.bottom,
|
||||
)
|
||||
binding.root.updatePadding(
|
||||
left = insets.left,
|
||||
right = insets.right,
|
||||
)
|
||||
if (insets.bottom > 0) {
|
||||
window.setNavigationBarTransparentCompat(this, binding.layoutBottom?.elevation ?: 0f)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onHistoryChanged(info: HistoryInfo?) {
|
||||
with(binding.buttonRead) {
|
||||
if (info?.history != null) {
|
||||
setText(R.string._continue)
|
||||
setIconResource(R.drawable.ic_play)
|
||||
} else {
|
||||
setText(R.string.read)
|
||||
setIconResource(R.drawable.ic_read)
|
||||
}
|
||||
}
|
||||
val text = when {
|
||||
info == null -> getString(R.string.loading_)
|
||||
info.currentChapter >= 0 -> getString(R.string.chapter_d_of_d, info.currentChapter + 1, info.totalChapters)
|
||||
info.totalChapters == 0 -> getString(R.string.no_chapters)
|
||||
else -> resources.getQuantityString(R.plurals.chapters, info.totalChapters, info.totalChapters)
|
||||
}
|
||||
binding.headerChapters?.title = text
|
||||
binding.textViewTitle?.text = text
|
||||
}
|
||||
|
||||
private fun onNewChaptersChanged(newChapters: Int) {
|
||||
val tab = binding.tabs?.getTabAt(1) ?: return
|
||||
if (newChapters == 0) {
|
||||
tab.removeBadge()
|
||||
} else {
|
||||
val badge = tab.orCreateBadge
|
||||
badge.number = newChapters
|
||||
badge.isVisible = true
|
||||
}
|
||||
badge = binding.buttonRead.bindBadge(badge, newChapters)
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
super.onCreateOptionsMenu(menu)
|
||||
menuInflater.inflate(R.menu.opt_details, menu)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onPrepareOptionsMenu(menu: Menu): Boolean {
|
||||
val manga = viewModel.manga.value
|
||||
menu.findItem(R.id.action_save).isVisible = manga?.source != null && manga.source != MangaSource.LOCAL
|
||||
menu.findItem(R.id.action_delete).isVisible = manga?.source == MangaSource.LOCAL
|
||||
menu.findItem(R.id.action_browser).isVisible = manga?.source != MangaSource.LOCAL
|
||||
menu.findItem(R.id.action_shortcut).isVisible = ShortcutManagerCompat.isRequestPinShortcutSupported(this)
|
||||
menu.findItem(R.id.action_shiki_track).isVisible = viewModel.isScrobblingAvailable
|
||||
return super.onPrepareOptionsMenu(menu)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
|
||||
R.id.action_delete -> {
|
||||
val title = viewModel.manga.value?.title.orEmpty()
|
||||
MaterialAlertDialogBuilder(this)
|
||||
.setTitle(R.string.delete_manga)
|
||||
.setMessage(getString(R.string.text_delete_local_manga, title))
|
||||
.setPositiveButton(R.string.delete) { _, _ ->
|
||||
viewModel.deleteLocal()
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
true
|
||||
}
|
||||
R.id.action_save -> {
|
||||
viewModel.manga.value?.let {
|
||||
val chaptersCount = it.chapters?.size ?: 0
|
||||
val branches = viewModel.branches.value.orEmpty()
|
||||
if (chaptersCount > 5 || branches.size > 1) {
|
||||
showSaveConfirmation(it, chaptersCount, branches)
|
||||
} else {
|
||||
DownloadService.start(this, it)
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
R.id.action_browser -> {
|
||||
viewModel.manga.value?.let {
|
||||
startActivity(BrowserActivity.newIntent(this, it.publicUrl, it.title))
|
||||
}
|
||||
true
|
||||
}
|
||||
R.id.action_related -> {
|
||||
viewModel.manga.value?.let {
|
||||
startActivity(MultiSearchActivity.newIntent(this, it.title))
|
||||
}
|
||||
true
|
||||
}
|
||||
R.id.action_shiki_track -> {
|
||||
viewModel.manga.value?.let {
|
||||
ScrobblingSelectorBottomSheet.show(supportFragmentManager, it)
|
||||
}
|
||||
true
|
||||
}
|
||||
R.id.action_shortcut -> {
|
||||
viewModel.manga.value?.let {
|
||||
lifecycleScope.launch {
|
||||
if (!shortcutsUpdater.requestPinShortcut(it)) {
|
||||
binding.snackbar.show(getString(R.string.operation_not_supported))
|
||||
}
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
override fun onConfigureTab(tab: TabLayout.Tab, position: Int) {
|
||||
tab.text = when (position) {
|
||||
0 -> getString(R.string.details)
|
||||
1 -> getString(R.string.chapters)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSupportActionModeStarted(mode: ActionMode) {
|
||||
super.onSupportActionModeStarted(mode)
|
||||
binding.pager?.isUserInputEnabled = false
|
||||
}
|
||||
|
||||
override fun onSupportActionModeFinished(mode: ActionMode) {
|
||||
super.onSupportActionModeFinished(mode)
|
||||
binding.pager?.isUserInputEnabled = true
|
||||
}
|
||||
|
||||
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
|
||||
val spinner = binding.spinnerBranches ?: return
|
||||
viewModel.setSelectedBranch(spinner.selectedItem as String?)
|
||||
}
|
||||
|
||||
override fun onNothingSelected(parent: AdapterView<*>?) = Unit
|
||||
|
||||
fun showChapterMissingDialog(chapterId: Long) {
|
||||
val remoteManga = viewModel.getRemoteManga()
|
||||
if (remoteManga == null) {
|
||||
binding.snackbar.show(getString(R.string.chapter_is_missing))
|
||||
Snackbar.make(binding.containerDetails, R.string.chapter_is_missing, Snackbar.LENGTH_SHORT)
|
||||
.show()
|
||||
return
|
||||
}
|
||||
MaterialAlertDialogBuilder(this).apply {
|
||||
@@ -289,19 +256,19 @@ class DetailsActivity :
|
||||
}.show()
|
||||
}
|
||||
|
||||
private fun initSpinner(spinner: Spinner) {
|
||||
val branchesAdapter = BranchesAdapter()
|
||||
spinner.adapter = branchesAdapter
|
||||
spinner.onItemSelectedListener = this
|
||||
viewModel.branches.observe(this) {
|
||||
branchesAdapter.setItems(it)
|
||||
spinner.isVisible = it.size > 1
|
||||
private fun showBranchPopupMenu() {
|
||||
val menu = PopupMenu(this, binding.headerChapters ?: binding.buttonDropdown)
|
||||
val currentBranch = viewModel.selectedBranchValue
|
||||
for (branch in viewModel.branches.value ?: return) {
|
||||
val item = menu.menu.add(R.id.group_branches, Menu.NONE, Menu.NONE, branch)
|
||||
item.isChecked = branch == currentBranch
|
||||
}
|
||||
viewModel.selectedBranchIndex.observe(this) {
|
||||
if (it != -1 && it != spinner.selectedItemPosition) {
|
||||
spinner.setSelection(it)
|
||||
}
|
||||
menu.menu.setGroupCheckable(R.id.group_branches, true, true)
|
||||
menu.setOnMenuItemClickListener { item ->
|
||||
viewModel.setSelectedBranch(item.title?.toString())
|
||||
true
|
||||
}
|
||||
menu.show()
|
||||
}
|
||||
|
||||
private fun resolveError(e: Throwable) {
|
||||
@@ -315,52 +282,7 @@ class DetailsActivity :
|
||||
}
|
||||
}
|
||||
|
||||
private fun gcFragments() {
|
||||
val mustHaveId = binding.pager == null
|
||||
val fm = supportFragmentManager
|
||||
val fragmentsToRemove = fm.fragments.filter { f ->
|
||||
(f.id == 0) == mustHaveId
|
||||
}
|
||||
if (fragmentsToRemove.isEmpty()) {
|
||||
return
|
||||
}
|
||||
fm.commit {
|
||||
setReorderingAllowed(true)
|
||||
for (f in fragmentsToRemove) {
|
||||
remove(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showSaveConfirmation(manga: Manga, chaptersCount: Int, branches: List<String?>) {
|
||||
val dialogBuilder = MaterialAlertDialogBuilder(this)
|
||||
.setTitle(R.string.save_manga)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
if (branches.size > 1) {
|
||||
val items = Array(branches.size) { i -> branches[i].orEmpty() }
|
||||
val currentBranch = viewModel.selectedBranchIndex.value ?: -1
|
||||
val checkedIndices = BooleanArray(branches.size) { i -> i == currentBranch }
|
||||
dialogBuilder.setMultiChoiceItems(items, checkedIndices) { _, i, checked ->
|
||||
checkedIndices[i] = checked
|
||||
}.setPositiveButton(R.string.save) { _, _ ->
|
||||
val selectedBranches = branches.filterIndexedTo(HashSet()) { i, _ -> checkedIndices[i] }
|
||||
val chaptersIds = manga.chapters?.mapNotNullToSet { c ->
|
||||
if (c.branch in selectedBranches) c.id else null
|
||||
}
|
||||
DownloadService.start(this, manga, chaptersIds)
|
||||
}
|
||||
} else {
|
||||
dialogBuilder.setMessage(
|
||||
getString(
|
||||
R.string.large_manga_save_confirm,
|
||||
resources.getQuantityString(R.plurals.chapters, chaptersCount, chaptersCount),
|
||||
),
|
||||
).setPositiveButton(R.string.save) { _, _ ->
|
||||
DownloadService.start(this, manga)
|
||||
}
|
||||
}
|
||||
dialogBuilder.show()
|
||||
}
|
||||
private fun isTabletLayout() = binding.layoutBottom == null
|
||||
|
||||
companion object {
|
||||
|
||||
|
||||
@@ -2,13 +2,13 @@ package org.koitharu.kotatsu.details.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.text.method.LinkMovementMethod
|
||||
import android.view.*
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.widget.PopupMenu
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.core.net.toFile
|
||||
import androidx.core.net.toUri
|
||||
import androidx.core.view.MenuProvider
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.updatePadding
|
||||
@@ -30,7 +30,6 @@ import org.koitharu.kotatsu.bookmarks.ui.adapter.BookmarksAdapter
|
||||
import org.koitharu.kotatsu.core.model.MangaHistory
|
||||
import org.koitharu.kotatsu.databinding.FragmentDetailsBinding
|
||||
import org.koitharu.kotatsu.details.ui.scrobbling.ScrobblingInfoBottomSheet
|
||||
import org.koitharu.kotatsu.favourites.ui.categories.select.FavouriteCategoriesBottomSheet
|
||||
import org.koitharu.kotatsu.history.domain.PROGRESS_NONE
|
||||
import org.koitharu.kotatsu.image.ui.ImageActivity
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
@@ -43,7 +42,6 @@ import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblingInfo
|
||||
import org.koitharu.kotatsu.search.ui.MangaListActivity
|
||||
import org.koitharu.kotatsu.search.ui.SearchActivity
|
||||
import org.koitharu.kotatsu.utils.FileSize
|
||||
import org.koitharu.kotatsu.utils.ShareHelper
|
||||
import org.koitharu.kotatsu.utils.ext.*
|
||||
|
||||
@AndroidEntryPoint
|
||||
@@ -67,21 +65,16 @@ class DetailsFragment :
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
binding.textViewAuthor.setOnClickListener(this)
|
||||
binding.buttonFavorite.setOnClickListener(this)
|
||||
binding.buttonRead.setOnClickListener(this)
|
||||
binding.buttonRead.setOnLongClickListener(this)
|
||||
binding.imageViewCover.setOnClickListener(this)
|
||||
binding.scrobblingLayout.root.setOnClickListener(this)
|
||||
binding.textViewDescription.movementMethod = LinkMovementMethod.getInstance()
|
||||
binding.chipsTags.onChipClickListener = this
|
||||
viewModel.manga.observe(viewLifecycleOwner, ::onMangaUpdated)
|
||||
viewModel.isLoading.observe(viewLifecycleOwner, ::onLoadingStateChanged)
|
||||
viewModel.favouriteCategories.observe(viewLifecycleOwner, ::onFavouriteChanged)
|
||||
viewModel.readingHistory.observe(viewLifecycleOwner, ::onHistoryChanged)
|
||||
viewModel.bookmarks.observe(viewLifecycleOwner, ::onBookmarksChanged)
|
||||
viewModel.scrobblingInfo.observe(viewLifecycleOwner, ::onScrobblingInfoChanged)
|
||||
viewModel.description.observe(viewLifecycleOwner, ::onDescriptionChanged)
|
||||
addMenuProvider(DetailsMenuProvider())
|
||||
}
|
||||
|
||||
override fun onItemClick(item: Bookmark, view: View) {
|
||||
@@ -165,9 +158,6 @@ class DetailsFragment :
|
||||
|
||||
infoLayout.textViewNsfw.isVisible = manga.isNsfw
|
||||
|
||||
// Buttons
|
||||
buttonRead.isEnabled = !manga.chapters.isNullOrEmpty()
|
||||
|
||||
// Chips
|
||||
bindTags(manga)
|
||||
}
|
||||
@@ -182,27 +172,9 @@ class DetailsFragment :
|
||||
}
|
||||
|
||||
private fun onHistoryChanged(history: MangaHistory?) {
|
||||
with(binding.buttonRead) {
|
||||
if (history == null) {
|
||||
setText(R.string.read)
|
||||
setIconResource(R.drawable.ic_read)
|
||||
} else {
|
||||
setText(R.string._continue)
|
||||
setIconResource(R.drawable.ic_play)
|
||||
}
|
||||
}
|
||||
binding.progressView.setPercent(history?.percent ?: PROGRESS_NONE, animate = true)
|
||||
}
|
||||
|
||||
private fun onFavouriteChanged(isFavourite: Boolean) {
|
||||
val iconRes = if (isFavourite) {
|
||||
R.drawable.ic_heart
|
||||
} else {
|
||||
R.drawable.ic_heart_outline
|
||||
}
|
||||
binding.buttonFavorite.setIconResource(iconRes)
|
||||
}
|
||||
|
||||
private fun onLoadingStateChanged(isLoading: Boolean) {
|
||||
if (isLoading) {
|
||||
binding.progressBar.show()
|
||||
@@ -251,26 +223,9 @@ class DetailsFragment :
|
||||
override fun onClick(v: View) {
|
||||
val manga = viewModel.manga.value ?: return
|
||||
when (v.id) {
|
||||
R.id.button_favorite -> {
|
||||
FavouriteCategoriesBottomSheet.show(childFragmentManager, manga)
|
||||
}
|
||||
R.id.scrobbling_layout -> {
|
||||
ScrobblingInfoBottomSheet.show(childFragmentManager)
|
||||
}
|
||||
R.id.button_read -> {
|
||||
val chapterId = viewModel.readingHistory.value?.chapterId
|
||||
if (chapterId != null && manga.chapters?.none { x -> x.id == chapterId } == true) {
|
||||
(activity as? DetailsActivity)?.showChapterMissingDialog(chapterId)
|
||||
} else {
|
||||
startActivity(
|
||||
ReaderActivity.newIntent(
|
||||
context = context ?: return,
|
||||
manga = manga,
|
||||
branch = viewModel.selectedBranchValue,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
R.id.textView_author -> {
|
||||
startActivity(
|
||||
SearchActivity.newIntent(
|
||||
@@ -331,8 +286,6 @@ class DetailsFragment :
|
||||
|
||||
override fun onWindowInsetsChanged(insets: Insets) {
|
||||
binding.root.updatePadding(
|
||||
left = insets.left,
|
||||
right = insets.right,
|
||||
bottom = insets.bottom,
|
||||
)
|
||||
}
|
||||
@@ -368,26 +321,4 @@ class DetailsFragment :
|
||||
} ?: request.fallback(R.drawable.ic_placeholder)
|
||||
request.enqueueWith(coil)
|
||||
}
|
||||
|
||||
private inner class DetailsMenuProvider : MenuProvider {
|
||||
|
||||
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
|
||||
menuInflater.inflate(R.menu.opt_details_info, menu)
|
||||
}
|
||||
|
||||
override fun onMenuItemSelected(menuItem: MenuItem): Boolean = when (menuItem.itemId) {
|
||||
R.id.action_share -> {
|
||||
viewModel.manga.value?.let {
|
||||
val context = requireContext()
|
||||
if (it.source == MangaSource.LOCAL) {
|
||||
ShareHelper(context).shareCbz(listOf(it.url.toUri().toFile()))
|
||||
} else {
|
||||
ShareHelper(context).shareMangaLink(it)
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
package org.koitharu.kotatsu.details.ui
|
||||
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import androidx.core.content.pm.ShortcutManagerCompat
|
||||
import androidx.core.net.toFile
|
||||
import androidx.core.net.toUri
|
||||
import androidx.core.view.MenuProvider
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.browser.BrowserActivity
|
||||
import org.koitharu.kotatsu.core.os.ShortcutsUpdater
|
||||
import org.koitharu.kotatsu.download.ui.service.DownloadService
|
||||
import org.koitharu.kotatsu.favourites.ui.categories.select.FavouriteCategoriesBottomSheet
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.util.mapNotNullToSet
|
||||
import org.koitharu.kotatsu.scrobbling.ui.selector.ScrobblingSelectorBottomSheet
|
||||
import org.koitharu.kotatsu.search.ui.multi.MultiSearchActivity
|
||||
import org.koitharu.kotatsu.utils.ShareHelper
|
||||
|
||||
class DetailsMenuProvider(
|
||||
private val activity: FragmentActivity,
|
||||
private val viewModel: DetailsViewModel,
|
||||
private val snackbarHost: View,
|
||||
private val shortcutsUpdater: ShortcutsUpdater,
|
||||
) : MenuProvider {
|
||||
|
||||
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
|
||||
menuInflater.inflate(R.menu.opt_details, menu)
|
||||
}
|
||||
|
||||
override fun onPrepareMenu(menu: Menu) {
|
||||
val manga = viewModel.manga.value
|
||||
menu.findItem(R.id.action_save).isVisible = manga?.source != null && manga.source != MangaSource.LOCAL
|
||||
menu.findItem(R.id.action_delete).isVisible = manga?.source == MangaSource.LOCAL
|
||||
menu.findItem(R.id.action_browser).isVisible = manga?.source != MangaSource.LOCAL
|
||||
menu.findItem(R.id.action_shortcut).isVisible = ShortcutManagerCompat.isRequestPinShortcutSupported(activity)
|
||||
menu.findItem(R.id.action_shiki_track).isVisible = viewModel.isScrobblingAvailable
|
||||
menu.findItem(R.id.action_favourite).setIcon(
|
||||
if (viewModel.favouriteCategories.value == true) R.drawable.ic_heart else R.drawable.ic_heart_outline,
|
||||
)
|
||||
}
|
||||
|
||||
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
|
||||
when (menuItem.itemId) {
|
||||
R.id.action_share -> {
|
||||
viewModel.manga.value?.let {
|
||||
val shareHelper = ShareHelper(activity)
|
||||
if (it.source == MangaSource.LOCAL) {
|
||||
shareHelper.shareCbz(listOf(it.url.toUri().toFile()))
|
||||
} else {
|
||||
shareHelper.shareMangaLink(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
R.id.action_favourite -> {
|
||||
viewModel.manga.value?.let {
|
||||
FavouriteCategoriesBottomSheet.show(activity.supportFragmentManager, it)
|
||||
}
|
||||
}
|
||||
R.id.action_delete -> {
|
||||
val title = viewModel.manga.value?.title.orEmpty()
|
||||
MaterialAlertDialogBuilder(activity)
|
||||
.setTitle(R.string.delete_manga)
|
||||
.setMessage(activity.getString(R.string.text_delete_local_manga, title))
|
||||
.setPositiveButton(R.string.delete) { _, _ ->
|
||||
viewModel.deleteLocal()
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
R.id.action_save -> {
|
||||
viewModel.manga.value?.let {
|
||||
val chaptersCount = it.chapters?.size ?: 0
|
||||
val branches = viewModel.branches.value.orEmpty()
|
||||
if (chaptersCount > 5 || branches.size > 1) {
|
||||
showSaveConfirmation(it, chaptersCount, branches)
|
||||
} else {
|
||||
DownloadService.start(activity, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
R.id.action_browser -> {
|
||||
viewModel.manga.value?.let {
|
||||
activity.startActivity(BrowserActivity.newIntent(activity, it.publicUrl, it.title))
|
||||
}
|
||||
}
|
||||
R.id.action_related -> {
|
||||
viewModel.manga.value?.let {
|
||||
activity.startActivity(MultiSearchActivity.newIntent(activity, it.title))
|
||||
}
|
||||
}
|
||||
R.id.action_shiki_track -> {
|
||||
viewModel.manga.value?.let {
|
||||
ScrobblingSelectorBottomSheet.show(activity.supportFragmentManager, it)
|
||||
}
|
||||
}
|
||||
R.id.action_shortcut -> {
|
||||
viewModel.manga.value?.let {
|
||||
activity.lifecycleScope.launch {
|
||||
if (!shortcutsUpdater.requestPinShortcut(it)) {
|
||||
Snackbar.make(snackbarHost, R.string.operation_not_supported, Snackbar.LENGTH_SHORT)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun showSaveConfirmation(manga: Manga, chaptersCount: Int, branches: List<String?>) {
|
||||
val dialogBuilder = MaterialAlertDialogBuilder(activity)
|
||||
.setTitle(R.string.save_manga)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
if (branches.size > 1) {
|
||||
val items = Array(branches.size) { i -> branches[i].orEmpty() }
|
||||
val currentBranch = viewModel.selectedBranchIndex.value ?: -1
|
||||
val checkedIndices = BooleanArray(branches.size) { i -> i == currentBranch }
|
||||
dialogBuilder.setMultiChoiceItems(items, checkedIndices) { _, i, checked ->
|
||||
checkedIndices[i] = checked
|
||||
}.setPositiveButton(R.string.save) { _, _ ->
|
||||
val selectedBranches = branches.filterIndexedTo(HashSet()) { i, _ -> checkedIndices[i] }
|
||||
val chaptersIds = manga.chapters?.mapNotNullToSet { c ->
|
||||
if (c.branch in selectedBranches) c.id else null
|
||||
}
|
||||
DownloadService.start(activity, manga, chaptersIds)
|
||||
}
|
||||
} else {
|
||||
dialogBuilder.setMessage(
|
||||
activity.getString(
|
||||
R.string.large_manga_save_confirm,
|
||||
activity.resources.getQuantityString(R.plurals.chapters, chaptersCount, chaptersCount),
|
||||
),
|
||||
).setPositiveButton(R.string.save) { _, _ ->
|
||||
DownloadService.start(activity, manga)
|
||||
}
|
||||
}
|
||||
dialogBuilder.show()
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,7 @@ import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.prefs.observeAsFlow
|
||||
import org.koitharu.kotatsu.details.domain.BranchComparator
|
||||
import org.koitharu.kotatsu.details.ui.model.ChapterListItem
|
||||
import org.koitharu.kotatsu.details.ui.model.HistoryInfo
|
||||
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
|
||||
import org.koitharu.kotatsu.history.domain.HistoryRepository
|
||||
import org.koitharu.kotatsu.local.domain.LocalMangaRepository
|
||||
@@ -36,6 +37,7 @@ import org.koitharu.kotatsu.scrobbling.domain.Scrobbler
|
||||
import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblingStatus
|
||||
import org.koitharu.kotatsu.tracker.domain.TrackingRepository
|
||||
import org.koitharu.kotatsu.utils.SingleLiveEvent
|
||||
import org.koitharu.kotatsu.utils.asFlowLiveData
|
||||
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
|
||||
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
|
||||
|
||||
@@ -85,9 +87,18 @@ class DetailsViewModel @AssistedInject constructor(
|
||||
val manga = delegate.manga.filterNotNull().asLiveData(viewModelScope.coroutineContext)
|
||||
val favouriteCategories = favourite.asLiveData(viewModelScope.coroutineContext)
|
||||
val newChaptersCount = newChapters.asLiveData(viewModelScope.coroutineContext)
|
||||
|
||||
@Deprecated("")
|
||||
val readingHistory = history.asLiveData(viewModelScope.coroutineContext)
|
||||
val isChaptersReversed = chaptersReversed.asLiveData(viewModelScope.coroutineContext)
|
||||
|
||||
val historyInfo = combine(
|
||||
delegate.manga,
|
||||
history,
|
||||
) { m, h ->
|
||||
HistoryInfo(m, h)
|
||||
}.asFlowLiveData(viewModelScope.coroutineContext + Dispatchers.Default, null)
|
||||
|
||||
val bookmarks = delegate.manga.flatMapLatest {
|
||||
if (it != null) bookmarksRepository.observeBookmarks(it) else flowOf(emptyList())
|
||||
}.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default, emptyList())
|
||||
@@ -114,7 +125,7 @@ class DetailsViewModel @AssistedInject constructor(
|
||||
val branches: LiveData<List<String?>> = delegate.manga.map {
|
||||
val chapters = it?.chapters ?: return@map emptyList()
|
||||
chapters.mapToSet { x -> x.branch }.sortedWith(BranchComparator())
|
||||
}.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default, emptyList())
|
||||
}.asFlowLiveData(viewModelScope.coroutineContext + Dispatchers.Default, emptyList())
|
||||
|
||||
val selectedBranchIndex = combine(
|
||||
branches.asFlow(),
|
||||
@@ -123,6 +134,9 @@ class DetailsViewModel @AssistedInject constructor(
|
||||
branches.indexOf(selected)
|
||||
}.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default, -1)
|
||||
|
||||
val selectedBranchName = delegate.selectedBranch
|
||||
.asFlowLiveData(viewModelScope.coroutineContext, null)
|
||||
|
||||
val isChaptersEmpty: LiveData<Boolean> = combine(
|
||||
delegate.manga,
|
||||
isLoading.asFlow(),
|
||||
|
||||
@@ -6,10 +6,11 @@ import android.graphics.Color
|
||||
import android.graphics.Paint
|
||||
import android.graphics.RectF
|
||||
import android.view.View
|
||||
import androidx.core.graphics.ColorUtils
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.R as materialR
|
||||
import org.koitharu.kotatsu.base.ui.list.decor.AbstractSelectionItemDecoration
|
||||
import org.koitharu.kotatsu.utils.ext.getThemeColor
|
||||
import com.google.android.material.R as materialR
|
||||
|
||||
class ChaptersSelectionDecoration(context: Context) : AbstractSelectionItemDecoration() {
|
||||
|
||||
@@ -17,7 +18,10 @@ class ChaptersSelectionDecoration(context: Context) : AbstractSelectionItemDecor
|
||||
private val radius = context.resources.getDimension(materialR.dimen.abc_control_corner_material)
|
||||
|
||||
init {
|
||||
paint.color = context.getThemeColor(materialR.attr.colorSecondaryContainer, Color.LTGRAY)
|
||||
paint.color = ColorUtils.setAlphaComponent(
|
||||
context.getThemeColor(materialR.attr.colorPrimary, Color.DKGRAY),
|
||||
98,
|
||||
)
|
||||
paint.style = Paint.Style.FILL
|
||||
}
|
||||
|
||||
@@ -30,4 +34,4 @@ class ChaptersSelectionDecoration(context: Context) : AbstractSelectionItemDecor
|
||||
) {
|
||||
canvas.drawRoundRect(bounds, radius, radius, paint)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package org.koitharu.kotatsu.details.ui.model
|
||||
|
||||
import org.koitharu.kotatsu.core.model.MangaHistory
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
|
||||
class HistoryInfo(
|
||||
val totalChapters: Int,
|
||||
val currentChapter: Int,
|
||||
val history: MangaHistory?,
|
||||
) {
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as HistoryInfo
|
||||
|
||||
if (totalChapters != other.totalChapters) return false
|
||||
if (currentChapter != other.currentChapter) return false
|
||||
if (history != other.history) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = totalChapters
|
||||
result = 31 * result + currentChapter
|
||||
result = 31 * result + (history?.hashCode() ?: 0)
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("FunctionName")
|
||||
fun HistoryInfo(manga: Manga?, history: MangaHistory?): HistoryInfo? {
|
||||
val chapters = manga?.chapters ?: return null
|
||||
return HistoryInfo(
|
||||
totalChapters = chapters.size,
|
||||
currentChapter = if (history != null) {
|
||||
chapters.indexOfFirst { it.id == history.chapterId }
|
||||
} else {
|
||||
-1
|
||||
},
|
||||
history = history,
|
||||
)
|
||||
}
|
||||
@@ -31,8 +31,8 @@ class DownloadNotification(private val context: Context, startId: Int) {
|
||||
context,
|
||||
startId,
|
||||
DownloadService.getCancelIntent(startId),
|
||||
PendingIntent.FLAG_CANCEL_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE
|
||||
)
|
||||
PendingIntent.FLAG_CANCEL_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE,
|
||||
),
|
||||
)
|
||||
private val listIntent = PendingIntent.getActivity(
|
||||
context,
|
||||
@@ -63,7 +63,7 @@ class DownloadNotification(private val context: Context, startId: Int) {
|
||||
NotificationCompat.VISIBILITY_PRIVATE
|
||||
} else {
|
||||
NotificationCompat.VISIBILITY_PUBLIC
|
||||
}
|
||||
},
|
||||
)
|
||||
when (state) {
|
||||
is DownloadState.Cancelled -> {
|
||||
@@ -143,7 +143,7 @@ class DownloadNotification(private val context: Context, startId: Int) {
|
||||
context,
|
||||
manga.hashCode(),
|
||||
DetailsActivity.newIntent(context, manga),
|
||||
PendingIntent.FLAG_CANCEL_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE
|
||||
PendingIntent.FLAG_CANCEL_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE,
|
||||
)
|
||||
|
||||
companion object {
|
||||
@@ -158,7 +158,7 @@ class DownloadNotification(private val context: Context, startId: Int) {
|
||||
val channel = NotificationChannel(
|
||||
CHANNEL_ID,
|
||||
context.getString(R.string.downloads),
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
NotificationManager.IMPORTANCE_LOW,
|
||||
)
|
||||
channel.enableVibration(false)
|
||||
channel.enableLights(false)
|
||||
@@ -168,4 +168,4 @@ class DownloadNotification(private val context: Context, startId: Int) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ abstract class HistoryDao {
|
||||
WHERE history.deleted_at = 0
|
||||
GROUP BY manga_tags.tag_id
|
||||
ORDER BY COUNT(manga_tags.manga_id) DESC
|
||||
LIMIT :limit"""
|
||||
LIMIT :limit""",
|
||||
)
|
||||
abstract suspend fun findPopularTags(limit: Int): List<TagEntity>
|
||||
|
||||
@@ -49,7 +49,9 @@ abstract class HistoryDao {
|
||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||
abstract suspend fun insert(entity: HistoryEntity): Long
|
||||
|
||||
@Query("UPDATE history SET page = :page, chapter_id = :chapterId, scroll = :scroll, percent = :percent, updated_at = :updatedAt WHERE manga_id = :mangaId")
|
||||
@Query(
|
||||
"UPDATE history SET page = :page, chapter_id = :chapterId, scroll = :scroll, percent = :percent, updated_at = :updatedAt, deleted_at = 0 WHERE manga_id = :mangaId",
|
||||
)
|
||||
abstract suspend fun update(
|
||||
mangaId: Long,
|
||||
page: Int,
|
||||
@@ -76,7 +78,7 @@ abstract class HistoryDao {
|
||||
chapterId = entity.chapterId,
|
||||
scroll = entity.scroll,
|
||||
percent = entity.percent,
|
||||
updatedAt = entity.updatedAt
|
||||
updatedAt = entity.updatedAt,
|
||||
)
|
||||
|
||||
@Transaction
|
||||
|
||||
@@ -331,9 +331,9 @@ class ReaderActivity :
|
||||
binding.toastView.showTemporary(uiState.chapterName, TOAST_DURATION)
|
||||
}
|
||||
}
|
||||
if (uiState.totalPages > 0) {
|
||||
binding.slider.value = uiState.currentPage.toFloat()
|
||||
if (uiState.totalPages > 1) {
|
||||
binding.slider.valueTo = uiState.totalPages.toFloat() - 1
|
||||
binding.slider.value = uiState.currentPage.toFloat()
|
||||
binding.slider.isVisible = true
|
||||
} else {
|
||||
binding.slider.isVisible = false
|
||||
|
||||
@@ -6,6 +6,7 @@ import android.view.View
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.MultiSelectListPreference
|
||||
import androidx.preference.Preference
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.BasePreferenceFragment
|
||||
import org.koitharu.kotatsu.core.model.ZoomMode
|
||||
@@ -15,6 +16,7 @@ import org.koitharu.kotatsu.parsers.util.names
|
||||
import org.koitharu.kotatsu.settings.utils.MultiSummaryProvider
|
||||
import org.koitharu.kotatsu.utils.ext.setDefaultValueCompat
|
||||
|
||||
@AndroidEntryPoint
|
||||
class ReaderSettingsFragment :
|
||||
BasePreferenceFragment(R.string.reader_settings),
|
||||
SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
@@ -65,4 +67,4 @@ class ReaderSettingsFragment :
|
||||
isEnabled = settings.defaultReaderMode != ReaderMode.WEBTOON
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,5 +48,5 @@ fun DialogFragment.showAllowStateLoss(manager: FragmentManager, tag: String?) {
|
||||
}
|
||||
|
||||
fun Fragment.addMenuProvider(provider: MenuProvider) {
|
||||
requireActivity().addMenuProvider(provider, viewLifecycleOwner, Lifecycle.State.RESUMED)
|
||||
requireActivity().addMenuProvider(provider, viewLifecycleOwner, Lifecycle.State.STARTED)
|
||||
}
|
||||
|
||||
@@ -107,47 +107,6 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/button_favorite"
|
||||
style="@style/Widget.Material3.Button.OutlinedButton.Icon"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:contentDescription="@string/add_to_favourites"
|
||||
android:paddingStart="0dp"
|
||||
android:paddingEnd="0dp"
|
||||
app:icon="@drawable/ic_heart_outline"
|
||||
app:iconGravity="textTop"
|
||||
app:iconPadding="0dp"
|
||||
app:layout_constraintBottom_toBottomOf="@id/button_read"
|
||||
app:layout_constraintDimensionRatio="1:1"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/button_read" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/button_read"
|
||||
style="@style/Widget.Material3.Button.TonalButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:enabled="false"
|
||||
android:text="@string/read"
|
||||
android:textAllCaps="false"
|
||||
app:iconGravity="textStart"
|
||||
app:iconPadding="8dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/button_favorite"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="@string/_continue" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
|
||||
@@ -5,14 +5,15 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
tools:context=".details.ui.DetailsActivity">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/appbar"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:elevation="0dp"
|
||||
android:fitsSystemWindows="true"
|
||||
android:stateListAnimator="@null"
|
||||
app:elevation="0dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
@@ -23,17 +24,28 @@
|
||||
android:id="@id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:theme="?attr/actionBarTheme">
|
||||
android:background="@drawable/m3_tabs_background"
|
||||
android:theme="?attr/actionBarTheme"
|
||||
app:layout_scrollFlags="noScroll"
|
||||
tools:ignore="PrivateResource"
|
||||
tools:menu="@menu/opt_details">
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinner_branches"
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/button_read"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:visibility="gone"
|
||||
tools:listitem="@layout/item_branch"
|
||||
tools:visibility="visible" />
|
||||
android:layout_gravity="end|center_vertical"
|
||||
android:layout_marginHorizontal="@dimen/toolbar_button_margin"
|
||||
android:enabled="false"
|
||||
android:text="@string/read"
|
||||
android:textAllCaps="false"
|
||||
app:iconGravity="textStart"
|
||||
app:iconPadding="8dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/appbar"
|
||||
tools:enabled="true"
|
||||
tools:icon="@drawable/ic_read" />
|
||||
|
||||
</com.google.android.material.appbar.MaterialToolbar>
|
||||
|
||||
@@ -44,12 +56,64 @@
|
||||
android:name="org.koitharu.kotatsu.details.ui.DetailsFragment"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/guideline"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/appbar"
|
||||
app:layout_constraintWidth_percent="0.5"
|
||||
tools:layout="@layout/fragment_details" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/button_dropdown"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?selectableItemBackgroundBorderless"
|
||||
android:padding="@dimen/margin_small"
|
||||
android:src="@drawable/ic_expand_more"
|
||||
app:layout_constraintBottom_toTopOf="@id/divider"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/appbar" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView_title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="@dimen/margin_normal"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="?textAppearanceTitleMedium"
|
||||
app:layout_constraintBottom_toTopOf="@id/textView_subtitle"
|
||||
app:layout_constraintEnd_toStartOf="@id/button_dropdown"
|
||||
app:layout_constraintStart_toStartOf="@id/container_chapters"
|
||||
app:layout_constraintTop_toBottomOf="@id/appbar"
|
||||
app:layout_constraintVertical_chainStyle="packed"
|
||||
tools:text="@string/chapter_d_of_d" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView_subtitle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="@dimen/margin_normal"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="?textAppearanceTitleSmall"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toTopOf="@id/divider"
|
||||
app:layout_constraintEnd_toStartOf="@id/button_dropdown"
|
||||
app:layout_constraintStart_toStartOf="@id/container_chapters"
|
||||
app:layout_constraintTop_toBottomOf="@id/textView_title"
|
||||
tools:text="English"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<View
|
||||
android:id="@+id/divider"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="1dp"
|
||||
android:layout_marginTop="48dp"
|
||||
android:background="?colorSecondaryContainer"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/container_details"
|
||||
app:layout_constraintTop_toBottomOf="@id/appbar" />
|
||||
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/container_chapters"
|
||||
android:name="org.koitharu.kotatsu.details.ui.ChaptersFragment"
|
||||
@@ -57,30 +121,8 @@
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/guideline"
|
||||
app:layout_constraintTop_toBottomOf="@id/appbar"
|
||||
app:layout_constraintStart_toEndOf="@id/container_details"
|
||||
app:layout_constraintTop_toBottomOf="@id/divider"
|
||||
tools:layout="@layout/fragment_chapters" />
|
||||
|
||||
<org.koitharu.kotatsu.base.ui.widgets.FadingSnackbar
|
||||
android:id="@+id/snackbar"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<View
|
||||
android:id="@+id/guideline"
|
||||
android:layout_width="1dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginBottom="6dp"
|
||||
android:background="?colorOutline"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.6"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/appbar" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<org.koitharu.kotatsu.base.ui.list.fastscroll.FastScrollRecyclerView
|
||||
android:id="@+id/recyclerView_chapters"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
android:orientation="vertical"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
tools:listitem="@layout/item_chapter" />
|
||||
|
||||
<com.google.android.material.progressindicator.CircularProgressIndicator
|
||||
android:id="@+id/progressBar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:indeterminate="true"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView_holder"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_margin="@dimen/margin_normal"
|
||||
android:gravity="center"
|
||||
android:text="@string/chapters_empty"
|
||||
android:textAlignment="center"
|
||||
android:textAppearance="?attr/textAppearanceBodyLarge"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
|
||||
</FrameLayout>
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<org.koitharu.kotatsu.base.ui.widgets.KotatsuCoordinatorLayout
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
@@ -7,12 +7,15 @@
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".details.ui.DetailsActivity">
|
||||
|
||||
<com.google.android.material.appbar.KotatsuAppBarLayout
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/appbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:elevation="0dp"
|
||||
android:fitsSystemWindows="true"
|
||||
app:elevation="0dp">
|
||||
android:stateListAnimator="@null"
|
||||
app:elevation="0dp"
|
||||
app:liftOnScroll="false">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@id/toolbar"
|
||||
@@ -20,32 +23,69 @@
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="@drawable/m3_tabs_background"
|
||||
android:theme="?attr/actionBarTheme"
|
||||
app:layout_scrollFlags="scroll|enterAlways|snap"
|
||||
tools:ignore="PrivateResource">
|
||||
app:layout_scrollFlags="noScroll"
|
||||
tools:ignore="PrivateResource" />
|
||||
|
||||
<com.google.android.material.tabs.TabLayout
|
||||
android:id="@+id/tabs"
|
||||
style="@style/Widget.Kotatsu.SuperTabs"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:background="@null" />
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
</com.google.android.material.appbar.MaterialToolbar>
|
||||
|
||||
</com.google.android.material.appbar.KotatsuAppBarLayout>
|
||||
|
||||
<androidx.viewpager2.widget.ViewPager2
|
||||
android:id="@+id/pager"
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/container_details"
|
||||
android:name="org.koitharu.kotatsu.details.ui.DetailsFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" />
|
||||
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
|
||||
tools:layout="@layout/fragment_details" />
|
||||
|
||||
<org.koitharu.kotatsu.base.ui.widgets.FadingSnackbar
|
||||
android:id="@+id/snackbar"
|
||||
<LinearLayout
|
||||
android:id="@+id/layout_bottom"
|
||||
style="@style/Widget.Material3.BottomSheet"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom"
|
||||
android:visibility="gone" />
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
app:behavior_hideable="false"
|
||||
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior"
|
||||
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Kotatsu.BottomSheet">
|
||||
|
||||
</org.koitharu.kotatsu.base.ui.widgets.KotatsuCoordinatorLayout>
|
||||
<org.koitharu.kotatsu.base.ui.widgets.BottomSheetHeaderBar
|
||||
android:id="@+id/header_chapters"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:theme="@style/ThemeOverlay.Kotatsu.MainToolbar"
|
||||
app:fitStatusBar="true"
|
||||
tools:menu="@menu/opt_chapters">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/button_dropdown"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="start|center_vertical"
|
||||
android:background="?selectableItemBackgroundBorderless"
|
||||
android:padding="@dimen/margin_small"
|
||||
android:src="@drawable/ic_expand_more" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/button_read"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end|center_vertical"
|
||||
android:layout_marginHorizontal="@dimen/toolbar_button_margin"
|
||||
android:enabled="false"
|
||||
android:text="@string/read"
|
||||
android:textAllCaps="false"
|
||||
app:iconGravity="textStart"
|
||||
app:iconPadding="8dp"
|
||||
tools:enabled="true"
|
||||
tools:icon="@drawable/ic_read" />
|
||||
|
||||
</org.koitharu.kotatsu.base.ui.widgets.BottomSheetHeaderBar>
|
||||
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/container_chapters"
|
||||
android:name="org.koitharu.kotatsu.details.ui.ChaptersFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:layout="@layout/fragment_chapters" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
@@ -1,31 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinner_branches"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:visibility="gone"
|
||||
tools:listitem="@layout/item_branch"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<org.koitharu.kotatsu.base.ui.list.fastscroll.FastScrollRecyclerView
|
||||
android:id="@+id/recyclerView_chapters"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_alignWithParentIfMissing="true"
|
||||
android:layout_below="@id/spinner_branches"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
android:orientation="vertical"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
@@ -35,7 +19,6 @@
|
||||
android:id="@+id/progressBar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:layout_gravity="center"
|
||||
android:indeterminate="true"
|
||||
android:visibility="gone"
|
||||
@@ -45,12 +28,11 @@
|
||||
android:id="@+id/textView_holder"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignWithParentIfMissing="true"
|
||||
android:layout_below="@id/spinner_branches"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_margin="@dimen/margin_normal"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginStart="@dimen/margin_normal"
|
||||
android:layout_marginTop="@dimen/margin_normal"
|
||||
android:layout_marginEnd="@dimen/margin_normal"
|
||||
android:layout_marginBottom="@dimen/margin_normal"
|
||||
android:gravity="center"
|
||||
android:text="@string/chapters_empty"
|
||||
android:textAlignment="center"
|
||||
@@ -58,4 +40,4 @@
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
|
||||
</RelativeLayout>
|
||||
</FrameLayout>
|
||||
|
||||
@@ -46,8 +46,8 @@
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:maxLines="5"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="5"
|
||||
android:textAppearance="?attr/textAppearanceHeadlineSmall"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/imageView_cover"
|
||||
@@ -121,40 +121,6 @@
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/barrier_header" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/button_favorite"
|
||||
style="@style/Widget.Material3.Button.OutlinedButton.Icon"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:contentDescription="@string/add_to_favourites"
|
||||
android:paddingStart="0dp"
|
||||
android:paddingEnd="0dp"
|
||||
app:icon="@drawable/ic_heart_outline"
|
||||
app:iconGravity="textTop"
|
||||
app:iconPadding="0dp"
|
||||
app:layout_constraintBottom_toBottomOf="@id/button_read"
|
||||
app:layout_constraintDimensionRatio="1:1"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/button_read" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/button_read"
|
||||
style="@style/Widget.Material3.Button.TonalButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:enabled="false"
|
||||
android:text="@string/read"
|
||||
android:textAllCaps="false"
|
||||
app:iconGravity="textStart"
|
||||
app:iconPadding="8dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/button_favorite"
|
||||
app:layout_constraintTop_toBottomOf="@id/info_layout"
|
||||
tools:text="@string/_continue" />
|
||||
|
||||
<org.koitharu.kotatsu.base.ui.widgets.ChipsView
|
||||
android:id="@+id/chips_tags"
|
||||
android:layout_width="0dp"
|
||||
@@ -166,7 +132,7 @@
|
||||
app:chipSpacingVertical="6dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/button_read" />
|
||||
app:layout_constraintTop_toBottomOf="@+id/info_layout" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView_bookmarks"
|
||||
|
||||
@@ -18,4 +18,4 @@
|
||||
android:title="@string/reverse"
|
||||
app:showAsAction="never" />
|
||||
|
||||
</menu>
|
||||
</menu>
|
||||
|
||||
@@ -3,6 +3,20 @@
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/action_favourite"
|
||||
android:icon="@drawable/ic_heart_outline"
|
||||
android:orderInCategory="10"
|
||||
android:title="@string/add_to_favourites"
|
||||
app:showAsAction="ifRoom" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_share"
|
||||
android:icon="?actionModeShareDrawable"
|
||||
android:orderInCategory="15"
|
||||
android:title="@string/share"
|
||||
app:showAsAction="ifRoom" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_save"
|
||||
android:orderInCategory="40"
|
||||
@@ -41,4 +55,4 @@
|
||||
android:title="@string/create_shortcut"
|
||||
app:showAsAction="never" />
|
||||
|
||||
</menu>
|
||||
</menu>
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/action_share"
|
||||
android:icon="?actionModeShareDrawable"
|
||||
android:orderInCategory="15"
|
||||
android:title="@string/share"
|
||||
app:showAsAction="ifRoom" />
|
||||
|
||||
</menu>
|
||||
@@ -72,6 +72,7 @@
|
||||
<declare-styleable name="BottomSheetHeaderBar">
|
||||
<attr name="title" />
|
||||
<attr name="menu" />
|
||||
<attr name="fitStatusBar" format="boolean" />
|
||||
</declare-styleable>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<item name="toolbar" type="id" />
|
||||
<item name="container" type="id" />
|
||||
<item name="action_leaks" type="id" />
|
||||
<item name="toolbar" type="id" />
|
||||
<item name="container" type="id" />
|
||||
<item name="action_leaks" type="id" />
|
||||
<item name="fast_scroller" type="id" />
|
||||
</resources>
|
||||
<item name="group_branches" type="id" />
|
||||
</resources>
|
||||
|
||||
@@ -360,4 +360,5 @@
|
||||
<string name="not_found_404">Content not found or removed</string>
|
||||
<string name="incognito_mode">Incognito mode</string>
|
||||
<string name="app_update_available_s">Application update available: %s</string>
|
||||
<string name="no_chapters">No chapters</string>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user