Action mode chapters selection
This commit is contained in:
@@ -2,5 +2,5 @@ package org.koitharu.kotatsu.domain.history
|
||||
|
||||
enum class ChapterExtra {
|
||||
|
||||
READ, CURRENT, UNREAD, NEW
|
||||
READ, CURRENT, UNREAD, NEW, CHECKED
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package org.koitharu.kotatsu.ui.details
|
||||
|
||||
import android.graphics.Color
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.isVisible
|
||||
import kotlinx.android.synthetic.main.item_chapter.*
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.model.MangaChapter
|
||||
@@ -14,6 +16,7 @@ class ChapterHolder(parent: ViewGroup) :
|
||||
override fun onBind(data: MangaChapter, extra: ChapterExtra) {
|
||||
textView_title.text = data.name
|
||||
textView_number.text = data.number.toString()
|
||||
imageView_check.isVisible = extra == ChapterExtra.CHECKED
|
||||
when (extra) {
|
||||
ChapterExtra.UNREAD -> {
|
||||
textView_number.setBackgroundResource(R.drawable.bg_badge_default)
|
||||
@@ -31,6 +34,10 @@ class ChapterHolder(parent: ViewGroup) :
|
||||
textView_number.setBackgroundResource(R.drawable.bg_badge_accent)
|
||||
textView_number.setTextColor(context.getThemeColor(android.R.attr.textColorPrimaryInverse))
|
||||
}
|
||||
ChapterExtra.CHECKED -> {
|
||||
textView_number.setBackgroundResource(R.drawable.bg_badge_accent)
|
||||
textView_number.setTextColor(Color.TRANSPARENT)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,14 @@ import org.koitharu.kotatsu.ui.common.list.OnRecyclerItemClickListener
|
||||
class ChaptersAdapter(onItemClickListener: OnRecyclerItemClickListener<MangaChapter>) :
|
||||
BaseRecyclerAdapter<MangaChapter, ChapterExtra>(onItemClickListener) {
|
||||
|
||||
private val checkedIds = HashSet<Long>()
|
||||
|
||||
val checkedItemsCount: Int
|
||||
get() = checkedIds.size
|
||||
|
||||
val checkedItemsIds: Set<Long>
|
||||
get() = checkedIds
|
||||
|
||||
var currentChapterId: Long? = null
|
||||
set(value) {
|
||||
field = value
|
||||
@@ -26,11 +34,37 @@ class ChaptersAdapter(onItemClickListener: OnRecyclerItemClickListener<MangaChap
|
||||
var currentChapterPosition = RecyclerView.NO_POSITION
|
||||
private set
|
||||
|
||||
fun clearChecked() {
|
||||
checkedIds.clear()
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
fun checkAll() {
|
||||
for (item in dataSet) {
|
||||
checkedIds.add(item.id)
|
||||
}
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
fun setItemIsChecked(itemId: Long, isChecked: Boolean) {
|
||||
if ((isChecked && checkedIds.add(itemId)) || (!isChecked && checkedIds.remove(itemId))) {
|
||||
val pos = findItemPositionById(itemId)
|
||||
if (pos != RecyclerView.NO_POSITION) {
|
||||
notifyItemChanged(pos)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun toggleItemChecked(itemId: Long) {
|
||||
setItemIsChecked(itemId, itemId !in checkedIds)
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup) = ChapterHolder(parent)
|
||||
|
||||
override fun onGetItemId(item: MangaChapter) = item.id
|
||||
|
||||
override fun getExtra(item: MangaChapter, position: Int): ChapterExtra = when {
|
||||
item.id in checkedIds -> ChapterExtra.CHECKED
|
||||
currentChapterPosition == RecyclerView.NO_POSITION
|
||||
|| currentChapterPosition < position -> if (position >= itemCount - newChaptersCount) {
|
||||
ChapterExtra.NEW
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
package org.koitharu.kotatsu.ui.details
|
||||
|
||||
import android.app.ActivityOptions
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import androidx.appcompat.widget.PopupMenu
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.view.ActionMode
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.DividerItemDecoration
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
@@ -21,7 +22,7 @@ import org.koitharu.kotatsu.ui.reader.ReaderActivity
|
||||
import org.koitharu.kotatsu.utils.ext.resolveDp
|
||||
|
||||
class ChaptersFragment : BaseFragment(R.layout.fragment_chapters), MangaDetailsView,
|
||||
OnRecyclerItemClickListener<MangaChapter> {
|
||||
OnRecyclerItemClickListener<MangaChapter>, ActionMode.Callback {
|
||||
|
||||
@Suppress("unused")
|
||||
private val presenter by moxyPresenter(factory = MangaDetailsPresenter.Companion::getInstance)
|
||||
@@ -29,6 +30,7 @@ class ChaptersFragment : BaseFragment(R.layout.fragment_chapters), MangaDetailsV
|
||||
private var manga: Manga? = null
|
||||
|
||||
private lateinit var adapter: ChaptersAdapter
|
||||
private var actionMode: ActionMode? = null
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
@@ -69,6 +71,15 @@ class ChaptersFragment : BaseFragment(R.layout.fragment_chapters), MangaDetailsV
|
||||
override fun onFavouriteChanged(categories: List<FavouriteCategory>) = Unit
|
||||
|
||||
override fun onItemClick(item: MangaChapter, position: Int, view: View) {
|
||||
if (adapter.checkedItemsCount != 0) {
|
||||
adapter.toggleItemChecked(item.id)
|
||||
if (adapter.checkedItemsCount == 0) {
|
||||
actionMode?.finish()
|
||||
} else {
|
||||
actionMode?.invalidate()
|
||||
}
|
||||
return
|
||||
}
|
||||
val options = ActivityOptions.makeScaleUpAnimation(
|
||||
view,
|
||||
0,
|
||||
@@ -86,16 +97,13 @@ class ChaptersFragment : BaseFragment(R.layout.fragment_chapters), MangaDetailsV
|
||||
}
|
||||
|
||||
override fun onItemLongClick(item: MangaChapter, position: Int, view: View): Boolean {
|
||||
if (item.source == MangaSource.LOCAL) {
|
||||
return false
|
||||
if (actionMode == null) {
|
||||
actionMode = (activity as? AppCompatActivity)?.startSupportActionMode(this)
|
||||
}
|
||||
return context?.run {
|
||||
val menu = PopupMenu(this, view)
|
||||
menu.inflate(R.menu.popup_chapter)
|
||||
menu.setOnMenuItemClickListener(PopupMenuListener(this, manga ?: return false, item))
|
||||
menu.show()
|
||||
true
|
||||
} ?: false
|
||||
return actionMode?.also {
|
||||
adapter.setItemIsChecked(item.id, true)
|
||||
it.invalidate()
|
||||
} != null
|
||||
}
|
||||
|
||||
private fun scrollToCurrent() {
|
||||
@@ -107,29 +115,45 @@ class ChaptersFragment : BaseFragment(R.layout.fragment_chapters), MangaDetailsV
|
||||
}
|
||||
}
|
||||
|
||||
private class PopupMenuListener(
|
||||
private val context: Context,
|
||||
private val manga: Manga,
|
||||
private val chapter: MangaChapter
|
||||
) : PopupMenu.OnMenuItemClickListener {
|
||||
|
||||
override fun onMenuItemClick(item: MenuItem?): Boolean = when (item?.itemId) {
|
||||
R.id.action_save_this -> {
|
||||
DownloadService.start(context, manga, setOf(chapter.id))
|
||||
override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
R.id.action_save -> {
|
||||
DownloadService.start(
|
||||
context ?: return false,
|
||||
manga ?: return false,
|
||||
adapter.checkedItemsIds
|
||||
)
|
||||
true
|
||||
}
|
||||
R.id.action_save_this_next -> {
|
||||
DownloadService.start(context, manga, manga.chapters.orEmpty()
|
||||
.filter { x -> x.number >= chapter.number }.map { x -> x.id })
|
||||
true
|
||||
}
|
||||
R.id.action_save_this_prev -> {
|
||||
DownloadService.start(context, manga, manga.chapters.orEmpty()
|
||||
.filter { x -> x.number <= chapter.number }.map { x -> x.id })
|
||||
R.id.action_select_all -> {
|
||||
adapter.checkAll()
|
||||
mode.invalidate()
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
|
||||
mode.menuInflater.inflate(R.menu.mode_chapters, menu)
|
||||
menu.findItem(R.id.action_save).isVisible = manga?.source != MangaSource.LOCAL
|
||||
mode.title = manga?.title
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
|
||||
val count = adapter.checkedItemsCount
|
||||
mode.subtitle = resources.getQuantityString(
|
||||
R.plurals.chapters_from_x,
|
||||
count,
|
||||
count,
|
||||
adapter.itemCount
|
||||
)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onDestroyActionMode(mode: ActionMode?) {
|
||||
adapter.clearChecked()
|
||||
actionMode = null
|
||||
}
|
||||
}
|
||||
@@ -8,10 +8,13 @@ import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.view.ActionMode
|
||||
import androidx.core.content.pm.ShortcutManagerCompat
|
||||
import androidx.core.net.toFile
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import com.google.android.material.tabs.TabLayoutMediator
|
||||
import kotlinx.android.synthetic.main.activity_details.*
|
||||
import kotlinx.coroutines.launch
|
||||
import moxy.MvpDelegate
|
||||
@@ -29,7 +32,8 @@ import org.koitharu.kotatsu.utils.MangaShortcut
|
||||
import org.koitharu.kotatsu.utils.ShareHelper
|
||||
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
|
||||
|
||||
class MangaDetailsActivity : BaseActivity(), MangaDetailsView {
|
||||
class MangaDetailsActivity : BaseActivity(), MangaDetailsView,
|
||||
TabLayoutMediator.TabConfigurationStrategy {
|
||||
|
||||
private val presenter by moxyPresenter(factory = MangaDetailsPresenter.Companion::getInstance)
|
||||
|
||||
@@ -39,8 +43,8 @@ class MangaDetailsActivity : BaseActivity(), MangaDetailsView {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_details)
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
pager.adapter = MangaDetailsAdapter(resources, supportFragmentManager)
|
||||
tabs.setupWithViewPager(pager)
|
||||
pager.adapter = MangaDetailsAdapter(this)
|
||||
TabLayoutMediator(tabs, pager, this).attach()
|
||||
if (savedInstanceState?.containsKey(MvpDelegate.MOXY_DELEGATE_TAGS_KEY) != true) {
|
||||
intent?.getParcelableExtra<Manga>(EXTRA_MANGA)?.let {
|
||||
presenter.loadDetails(it, true)
|
||||
@@ -169,6 +173,24 @@ class MangaDetailsActivity : BaseActivity(), MangaDetailsView {
|
||||
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)
|
||||
pager.isUserInputEnabled = false
|
||||
}
|
||||
|
||||
override fun onSupportActionModeFinished(mode: ActionMode) {
|
||||
super.onSupportActionModeFinished(mode)
|
||||
pager.isUserInputEnabled = true
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val EXTRA_MANGA = "manga"
|
||||
|
||||
@@ -1,24 +1,16 @@
|
||||
package org.koitharu.kotatsu.ui.details
|
||||
|
||||
import android.content.res.Resources
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.fragment.app.FragmentPagerAdapter
|
||||
import org.koitharu.kotatsu.R
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||
|
||||
class MangaDetailsAdapter(private val resources: Resources, fm: FragmentManager) : FragmentPagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
|
||||
class MangaDetailsAdapter(activity: FragmentActivity) : FragmentStateAdapter(activity) {
|
||||
|
||||
override fun getCount() = 2
|
||||
override fun getItemCount() = 2
|
||||
|
||||
override fun getItem(position: Int): Fragment = when(position) {
|
||||
override fun createFragment(position: Int): Fragment = when(position) {
|
||||
0 -> MangaDetailsFragment()
|
||||
1 -> ChaptersFragment()
|
||||
else -> throw IndexOutOfBoundsException("No fragment for position $position")
|
||||
}
|
||||
|
||||
override fun getPageTitle(position: Int): CharSequence? = when(position) {
|
||||
0 -> resources.getString(R.string.details)
|
||||
1 -> resources.getString(R.string.chapters)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
5
app/src/main/res/drawable/ic_check.xml
Normal file
5
app/src/main/res/drawable/ic_check.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z"/>
|
||||
</vector>
|
||||
11
app/src/main/res/drawable/ic_save.xml
Normal file
11
app/src/main/res/drawable/ic_save.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<vector
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?colorControlNormal"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M17,3L5,3c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,7l-4,-4zM19,19L5,19L5,5h11.17L19,7.83L19,19zM12,12c-1.66,0 -3,1.34 -3,3s1.34,3 3,3 3,-1.34 3,-3 -1.34,-3 -3,-3zM6,6h9v4L6,10z" />
|
||||
</vector>
|
||||
11
app/src/main/res/drawable/ic_select_all.xml
Normal file
11
app/src/main/res/drawable/ic_select_all.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<vector
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?colorControlNormal"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M3,5h2L5,3c-1.1,0 -2,0.9 -2,2zM3,13h2v-2L3,11v2zM7,21h2v-2L7,19v2zM3,9h2L5,7L3,7v2zM13,3h-2v2h2L13,3zM19,3v2h2c0,-1.1 -0.9,-2 -2,-2zM5,21v-2L3,19c0,1.1 0.9,2 2,2zM3,17h2v-2L3,15v2zM9,3L7,3v2h2L9,3zM11,21h2v-2h-2v2zM19,13h2v-2h-2v2zM19,21c1.1,0 2,-0.9 2,-2h-2v2zM19,9h2L21,7h-2v2zM19,17h2v-2h-2v2zM15,21h2v-2h-2v2zM15,5h2L17,3h-2v2zM7,17h10L17,7L7,7v10zM9,9h6v6L9,15L9,9z" />
|
||||
</vector>
|
||||
@@ -27,7 +27,7 @@
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.viewpager.widget.ViewPager
|
||||
<androidx.viewpager2.widget.ViewPager2
|
||||
android:id="@+id/pager"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<RelativeLayout
|
||||
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="@dimen/chapter_list_item_height"
|
||||
android:background="?selectableItemBackground"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingEnd="12dp">
|
||||
|
||||
@@ -13,21 +13,41 @@
|
||||
android:id="@+id/textView_number"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerVertical="true"
|
||||
android:background="@drawable/bg_badge_default"
|
||||
android:gravity="center"
|
||||
android:minWidth="26dp"
|
||||
android:textAlignment="center"
|
||||
android:textColor="?android:textColorSecondaryInverse"
|
||||
tools:text="13" />
|
||||
|
||||
<ImageView
|
||||
android:contentDescription="@null"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible"
|
||||
android:id="@+id/imageView_check"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_alignStart="@id/textView_number"
|
||||
android:layout_alignTop="@id/textView_number"
|
||||
android:layout_alignEnd="@id/textView_number"
|
||||
android:layout_alignBottom="@id/textView_number"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/ic_check"
|
||||
android:padding="2dp"
|
||||
app:tint="@android:color/white" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_toEndOf="@id/textView_number"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="2"
|
||||
android:text="?android:textColorPrimary"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
|
||||
tools:text="@tools:sample/lorem[15]" />
|
||||
|
||||
</LinearLayout>
|
||||
</RelativeLayout>
|
||||
18
app/src/main/res/menu/mode_chapters.xml
Normal file
18
app/src/main/res/menu/mode_chapters.xml
Normal file
@@ -0,0 +1,18 @@
|
||||
<?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_select_all"
|
||||
android:icon="@drawable/ic_select_all"
|
||||
android:title="@android:string/selectAll"
|
||||
app:showAsAction="ifRoom|withText" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_save"
|
||||
android:icon="@drawable/ic_save"
|
||||
android:title="@string/save"
|
||||
app:showAsAction="ifRoom|withText" />
|
||||
|
||||
</menu>
|
||||
@@ -15,4 +15,9 @@
|
||||
<item quantity="few">%1$d новых главы</item>
|
||||
<item quantity="many">%1$d новых глав</item>
|
||||
</plurals>
|
||||
<plurals name="chapters_from_x">
|
||||
<item quantity="one">%1$d глава из %2$d</item>
|
||||
<item quantity="few">%1$d главы из %2$d</item>
|
||||
<item quantity="many">%1$d глав из %2$d</item>
|
||||
</plurals>
|
||||
</resources>
|
||||
@@ -12,4 +12,8 @@
|
||||
<item quantity="one">%1$d new chapter</item>
|
||||
<item quantity="other">%1$d new chapters</item>
|
||||
</plurals>
|
||||
<plurals name="chapters_from_x">
|
||||
<item quantity="one">%1$d chapter from %2$d</item>
|
||||
<item quantity="other">%1$d chapters from %2$d</item>
|
||||
</plurals>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user