Filter chapters by not d/l'd, not bookmarked

Closes #736
This commit is contained in:
Jays2Kings 2021-07-11 03:36:08 -04:00
parent b44e343466
commit c4ebc6b58e
13 changed files with 283 additions and 51 deletions

View File

@ -51,6 +51,7 @@ import eu.kanade.tachiyomi.util.system.ImageUtil
import eu.kanade.tachiyomi.util.manga.MangaShortcutManager import eu.kanade.tachiyomi.util.manga.MangaShortcutManager
import eu.kanade.tachiyomi.util.system.executeOnIO import eu.kanade.tachiyomi.util.system.executeOnIO
import eu.kanade.tachiyomi.util.system.launchIO import eu.kanade.tachiyomi.util.system.launchIO
import eu.kanade.tachiyomi.widget.TriStateCheckBox
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
@ -531,14 +532,26 @@ class MangaDetailsPresenter(
/** /**
* Removes all filters and requests an UI update. * Removes all filters and requests an UI update.
*/ */
fun setFilters(read: Boolean, unread: Boolean, downloaded: Boolean, bookmarked: Boolean) { fun setFilters(
manga.readFilter = when { unread: TriStateCheckBox.State,
read -> Manga.CHAPTER_SHOW_READ downloaded: TriStateCheckBox.State,
unread -> Manga.CHAPTER_SHOW_UNREAD bookmarked: TriStateCheckBox.State
) {
manga.readFilter = when (unread) {
TriStateCheckBox.State.CHECKED -> Manga.CHAPTER_SHOW_UNREAD
TriStateCheckBox.State.INVERSED -> Manga.CHAPTER_SHOW_READ
else -> Manga.SHOW_ALL
}
manga.downloadedFilter = when (downloaded) {
TriStateCheckBox.State.CHECKED -> Manga.CHAPTER_SHOW_DOWNLOADED
TriStateCheckBox.State.INVERSED -> Manga.CHAPTER_SHOW_NOT_DOWNLOADED
else -> Manga.SHOW_ALL
}
manga.bookmarkedFilter = when (bookmarked) {
TriStateCheckBox.State.CHECKED -> Manga.CHAPTER_SHOW_BOOKMARKED
TriStateCheckBox.State.INVERSED -> Manga.CHAPTER_SHOW_NOT_BOOKMARKED
else -> Manga.SHOW_ALL else -> Manga.SHOW_ALL
} }
manga.downloadedFilter = if (downloaded) Manga.CHAPTER_SHOW_DOWNLOADED else Manga.SHOW_ALL
manga.bookmarkedFilter = if (bookmarked) Manga.CHAPTER_SHOW_BOOKMARKED else Manga.SHOW_ALL
asyncUpdateMangaAndChapters() asyncUpdateMangaAndChapters()
} }
@ -555,7 +568,9 @@ class MangaDetailsPresenter(
filtersId.add(if (manga.readFilter == Manga.CHAPTER_SHOW_READ) R.string.read else null) filtersId.add(if (manga.readFilter == Manga.CHAPTER_SHOW_READ) R.string.read else null)
filtersId.add(if (manga.readFilter == Manga.CHAPTER_SHOW_UNREAD) R.string.unread else null) filtersId.add(if (manga.readFilter == Manga.CHAPTER_SHOW_UNREAD) R.string.unread else null)
filtersId.add(if (manga.downloadedFilter == Manga.CHAPTER_SHOW_DOWNLOADED) R.string.downloaded else null) filtersId.add(if (manga.downloadedFilter == Manga.CHAPTER_SHOW_DOWNLOADED) R.string.downloaded else null)
filtersId.add(if (manga.downloadedFilter == Manga.CHAPTER_SHOW_NOT_DOWNLOADED) R.string.not_downloaded else null)
filtersId.add(if (manga.bookmarkedFilter == Manga.CHAPTER_SHOW_BOOKMARKED) R.string.bookmarked else null) filtersId.add(if (manga.bookmarkedFilter == Manga.CHAPTER_SHOW_BOOKMARKED) R.string.bookmarked else null)
filtersId.add(if (manga.bookmarkedFilter == Manga.CHAPTER_SHOW_NOT_BOOKMARKED) R.string.not_bookmarked else null)
return filtersId.filterNotNull().joinToString(", ") { preferences.context.getString(it) } return filtersId.filterNotNull().joinToString(", ") { preferences.context.getString(it) }
} }

View File

@ -2,10 +2,10 @@ package eu.kanade.tachiyomi.ui.manga.chapter
import android.content.Context import android.content.Context
import android.util.AttributeSet import android.util.AttributeSet
import android.widget.CompoundButton
import android.widget.LinearLayout import android.widget.LinearLayout
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.databinding.ChapterFilterLayoutBinding import eu.kanade.tachiyomi.databinding.ChapterFilterLayoutBinding
import eu.kanade.tachiyomi.widget.TriStateCheckBox
class ChapterFilterLayout @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : class ChapterFilterLayout @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
LinearLayout(context, attrs) { LinearLayout(context, attrs) {
@ -16,38 +16,52 @@ class ChapterFilterLayout @JvmOverloads constructor(context: Context, attrs: Att
super.onFinishInflate() super.onFinishInflate()
binding = ChapterFilterLayoutBinding.bind(this) binding = ChapterFilterLayoutBinding.bind(this)
binding.showAll.setOnCheckedChangeListener(::checkedFilter) binding.showAll.setOnCheckedChangeListener(::checkedFilter)
binding.showRead.setOnCheckedChangeListener(::checkedFilter)
binding.showUnread.setOnCheckedChangeListener(::checkedFilter) binding.showUnread.setOnCheckedChangeListener(::checkedFilter)
binding.showDownload.setOnCheckedChangeListener(::checkedFilter) binding.showDownload.setOnCheckedChangeListener(::checkedFilter)
binding.showBookmark.setOnCheckedChangeListener(::checkedFilter) binding.showBookmark.setOnCheckedChangeListener(::checkedFilter)
} }
private fun checkedFilter(checkBox: CompoundButton, isChecked: Boolean) { private fun checkedFilter(checkBox: TriStateCheckBox, state: TriStateCheckBox.State) {
if (isChecked) { if (state != TriStateCheckBox.State.UNCHECKED) {
if (binding.showAll == checkBox) { if (binding.showAll == checkBox && state == TriStateCheckBox.State.CHECKED) {
binding.showRead.isChecked = false binding.showUnread.animateDrawableToState(TriStateCheckBox.State.UNCHECKED)
binding.showUnread.isChecked = false binding.showDownload.animateDrawableToState(TriStateCheckBox.State.UNCHECKED)
binding.showDownload.isChecked = false binding.showBookmark.animateDrawableToState(TriStateCheckBox.State.UNCHECKED)
binding.showBookmark.isChecked = false
} else { } else {
binding.showAll.isChecked = false if (binding.showAll == checkBox) {
if (binding.showRead == checkBox) binding.showUnread.isChecked = false binding.showAll.state = TriStateCheckBox.State.CHECKED
else if (binding.showUnread == checkBox) binding.showRead.isChecked = false } else {
binding.showAll.animateDrawableToState(TriStateCheckBox.State.UNCHECKED)
} }
} else if (!binding.showRead.isChecked && !binding.showUnread.isChecked && !binding.showDownload.isChecked && !binding.showBookmark.isChecked) { }
binding.showAll.isChecked = true } else if (
binding.showUnread.isUnchecked &&
binding.showDownload.isUnchecked &&
binding.showBookmark.isUnchecked
) {
binding.showAll.animateDrawableToState(TriStateCheckBox.State.CHECKED)
} }
} }
fun setCheckboxes(manga: Manga) { fun setCheckboxes(manga: Manga) {
binding.showRead.isChecked = manga.readFilter == Manga.CHAPTER_SHOW_READ binding.showUnread.state = when (manga.readFilter) {
binding.showUnread.isChecked = manga.readFilter == Manga.CHAPTER_SHOW_UNREAD Manga.CHAPTER_SHOW_UNREAD -> TriStateCheckBox.State.CHECKED
binding.showDownload.isChecked = manga.downloadedFilter == Manga.CHAPTER_SHOW_DOWNLOADED Manga.CHAPTER_SHOW_READ -> TriStateCheckBox.State.INVERSED
binding.showBookmark.isChecked = manga.bookmarkedFilter == Manga.CHAPTER_SHOW_BOOKMARKED else -> TriStateCheckBox.State.UNCHECKED
}
binding.showDownload.state = when (manga.downloadedFilter) {
Manga.CHAPTER_SHOW_DOWNLOADED -> TriStateCheckBox.State.CHECKED
Manga.CHAPTER_SHOW_NOT_DOWNLOADED -> TriStateCheckBox.State.INVERSED
else -> TriStateCheckBox.State.UNCHECKED
}
binding.showBookmark.state = when (manga.bookmarkedFilter) {
Manga.CHAPTER_SHOW_BOOKMARKED -> TriStateCheckBox.State.CHECKED
Manga.CHAPTER_SHOW_NOT_BOOKMARKED -> TriStateCheckBox.State.INVERSED
else -> TriStateCheckBox.State.UNCHECKED
}
binding.showAll.isChecked = !( binding.showAll.isChecked = binding.showUnread.isUnchecked &&
binding.showRead.isChecked || binding.showUnread.isChecked || binding.showDownload.isUnchecked &&
binding.showDownload.isChecked || binding.showBookmark.isChecked binding.showBookmark.isUnchecked
)
} }
} }

View File

@ -70,10 +70,9 @@ class ChaptersSortBottomSheet(controller: MangaDetailsController) :
setOnDismissListener { setOnDismissListener {
presenter.setFilters( presenter.setFilters(
binding.chapterFilterLayout.showRead.isChecked, binding.chapterFilterLayout.showUnread.state,
binding.chapterFilterLayout.showUnread.isChecked, binding.chapterFilterLayout.showDownload.state,
binding.chapterFilterLayout.showDownload.isChecked, binding.chapterFilterLayout.showBookmark.state
binding.chapterFilterLayout.showBookmark.isChecked
) )
} }
} }

View File

@ -14,15 +14,19 @@ class ChapterFilter(val preferences: PreferencesHelper = Injekt.get(), val downl
val readEnabled = manga.readFilter == Manga.CHAPTER_SHOW_READ val readEnabled = manga.readFilter == Manga.CHAPTER_SHOW_READ
val unreadEnabled = manga.readFilter == Manga.CHAPTER_SHOW_UNREAD val unreadEnabled = manga.readFilter == Manga.CHAPTER_SHOW_UNREAD
val downloadEnabled = manga.downloadedFilter == Manga.CHAPTER_SHOW_DOWNLOADED val downloadEnabled = manga.downloadedFilter == Manga.CHAPTER_SHOW_DOWNLOADED
val notDownloadEnabled = manga.downloadedFilter == Manga.CHAPTER_SHOW_NOT_DOWNLOADED
val bookmarkEnabled = manga.bookmarkedFilter == Manga.CHAPTER_SHOW_BOOKMARKED val bookmarkEnabled = manga.bookmarkedFilter == Manga.CHAPTER_SHOW_BOOKMARKED
val notBookmarkEnabled = manga.bookmarkedFilter == Manga.CHAPTER_SHOW_NOT_BOOKMARKED
// if none of the filters are enabled skip the filtering of them // if none of the filters are enabled skip the filtering of them
return if (readEnabled || unreadEnabled || downloadEnabled || bookmarkEnabled) { return if (readEnabled || unreadEnabled || downloadEnabled || notDownloadEnabled || bookmarkEnabled || notBookmarkEnabled) {
chapters.filter { chapters.filter {
if (readEnabled && it.read.not() || if (readEnabled && it.read.not() ||
(unreadEnabled && it.read) || (unreadEnabled && it.read) ||
(bookmarkEnabled && it.bookmark.not()) || (bookmarkEnabled && it.bookmark.not()) ||
(downloadEnabled && downloadManager.isChapterDownloaded(it, manga).not()) (notBookmarkEnabled && it.bookmark) ||
(downloadEnabled && downloadManager.isChapterDownloaded(it, manga).not()) ||
(notDownloadEnabled && downloadManager.isChapterDownloaded(it, manga))
) { ) {
return@filter false return@filter false
} }

View File

@ -0,0 +1,172 @@
package eu.kanade.tachiyomi.widget
import android.content.Context
import android.content.res.ColorStateList
import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.FrameLayout
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.databinding.TriStateCheckBoxBinding
import eu.kanade.tachiyomi.util.system.getResourceColor
import eu.kanade.tachiyomi.util.view.setAnimVectorCompat
import eu.kanade.tachiyomi.util.view.setVectorCompat
class TriStateCheckBox constructor(context: Context, attrs: AttributeSet?) :
FrameLayout(context, attrs) {
var text: CharSequence
get() {
return binding.textView.text
}
set(value) {
binding.textView.text = value
}
var state: State = State.UNCHECKED
set(value) {
field = value
updateDrawable()
}
var isUnchecked: Boolean
get() = state == State.UNCHECKED
set(value) {
state = if (value) State.UNCHECKED else State.CHECKED
}
var isChecked: Boolean
get() = state == State.UNCHECKED
set(value) {
state = if (value) State.CHECKED else State.UNCHECKED
}
private val binding = TriStateCheckBoxBinding.inflate(
LayoutInflater.from(context),
this,
false
)
private var mOnCheckedChangeListener: OnCheckedChangeListener? = null
init {
addView(binding.root)
val a = context.obtainStyledAttributes(attrs, R.styleable.TriStateCheckBox, 0, 0)
val str = a.getString(R.styleable.TriStateCheckBox_android_text) ?: ""
text = str
val maxLines = a.getInt(R.styleable.TriStateCheckBox_android_maxLines, Int.MAX_VALUE)
binding.textView.maxLines = maxLines
a.recycle()
setOnClickListener {
setState(
when (state) {
State.CHECKED -> State.INVERSED
State.UNCHECKED -> State.CHECKED
else -> State.UNCHECKED
},
true
)
mOnCheckedChangeListener?.onCheckedChanged(this, state)
}
}
fun setState(state: State, animated: Boolean = false) {
if (animated) {
animateDrawableToState(state)
} else {
this.state = state
}
}
/**
* Register a callback to be invoked when the checked state of this button
* changes.
*
* @param listener the callback to call on checked state change
*/
fun setOnCheckedChangeListener(listener: OnCheckedChangeListener?) {
mOnCheckedChangeListener = listener
}
fun animateDrawableToState(state: State) {
val oldState = this.state
if (state == oldState) return
this.state = state
with(binding.triStateBox) {
when (state) {
State.UNCHECKED -> {
setAnimVectorCompat(
when (oldState) {
State.INVERSED -> R.drawable.anim_check_box_x_to_blank_24dp
else -> R.drawable.anim_checkbox_checked_to_blank_24dp
},
R.attr.colorControlNormal
)
backgroundTintList = ColorStateList.valueOf(context.getResourceColor(R.attr.colorControlNormal))
}
State.CHECKED -> {
setAnimVectorCompat(
R.drawable.anim_check_box_blank_to_checked_24dp,
R.attr.colorAccent
)
backgroundTintList = ColorStateList.valueOf(context.getResourceColor(R.attr.colorAccent))
}
State.INVERSED -> {
setAnimVectorCompat(
R.drawable.anim_check_box_checked_to_x_24dp,
R.attr.colorAccentText
)
backgroundTintList = ColorStateList.valueOf(context.getResourceColor(R.attr.colorAccentText))
}
}
}
}
fun updateDrawable() {
with(binding.triStateBox) {
when (state) {
State.UNCHECKED -> {
setVectorCompat(
R.drawable.ic_check_box_outline_blank_24dp,
R.attr.colorControlNormal
)
backgroundTintList = ColorStateList.valueOf(context.getResourceColor(R.attr.colorControlNormal))
}
State.CHECKED -> {
setVectorCompat(R.drawable.ic_check_box_24dp, R.attr.colorAccent)
backgroundTintList = ColorStateList.valueOf(context.getResourceColor(R.attr.colorAccent))
}
State.INVERSED -> {
setVectorCompat(
R.drawable.ic_check_box_x_24dp,
R.attr.colorAccentText
)
backgroundTintList = ColorStateList.valueOf(context.getResourceColor(R.attr.colorAccentText))
}
}
}
}
enum class State {
UNCHECKED,
CHECKED,
INVERSED,
;
}
/**
* Interface definition for a callback to be invoked when the checked state
* of a compound button changed.
*/
fun interface OnCheckedChangeListener {
/**
* Called when the checked state of a compound button has changed.
*
* @param buttonView The compound button view whose state has changed.
* @param state The new checked state of buttonView.
*/
fun onCheckedChanged(buttonView: TriStateCheckBox, state: State)
}
}

View File

@ -13,6 +13,7 @@ class QuadStateCheckBox @JvmOverloads constructor(context: Context, attrs: Attri
var state: State = State.UNCHECKED var state: State = State.UNCHECKED
fun animateDrawableToState(state: State) { fun animateDrawableToState(state: State) {
if (state == this.state) return
when (state) { when (state) {
State.UNCHECKED -> setAnimVectorCompat( State.UNCHECKED -> setAnimVectorCompat(
when (this.state) { when (this.state) {

View File

@ -398,7 +398,6 @@
android:padding="5dp" android:padding="5dp"
android:textAlignment="textEnd" android:textAlignment="textEnd"
android:textColor="?android:textColorHint" android:textColor="?android:textColorHint"
app:layout_constraintBaseline_toBaselineOf="@id/chapters_title"
app:layout_constraintBottom_toBottomOf="@id/filter_button" app:layout_constraintBottom_toBottomOf="@id/filter_button"
app:layout_constraintEnd_toStartOf="@id/filter_button" app:layout_constraintEnd_toStartOf="@id/filter_button"
app:layout_constraintStart_toEndOf="@+id/chapters_title" app:layout_constraintStart_toEndOf="@+id/chapters_title"

View File

@ -398,7 +398,6 @@
android:padding="5dp" android:padding="5dp"
android:textAlignment="textEnd" android:textAlignment="textEnd"
android:textColor="?android:textColorHint" android:textColor="?android:textColorHint"
app:layout_constraintBaseline_toBaselineOf="@id/chapters_title"
app:layout_constraintBottom_toBottomOf="@id/filter_button" app:layout_constraintBottom_toBottomOf="@id/filter_button"
app:layout_constraintEnd_toStartOf="@id/filter_button" app:layout_constraintEnd_toStartOf="@id/filter_button"
app:layout_constraintStart_toEndOf="@+id/chapters_title" app:layout_constraintStart_toEndOf="@+id/chapters_title"

View File

@ -2,6 +2,7 @@
<eu.kanade.tachiyomi.ui.manga.chapter.ChapterFilterLayout xmlns:android="http://schemas.android.com/apk/res/android" <eu.kanade.tachiyomi.ui.manga.chapter.ChapterFilterLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"> android:orientation="vertical">
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
@ -13,7 +14,7 @@
android:paddingEnd="12dp" android:paddingEnd="12dp"
android:text="@string/filter" /> android:text="@string/filter" />
<com.google.android.material.checkbox.MaterialCheckBox <eu.kanade.tachiyomi.widget.TriStateCheckBox
android:id="@+id/show_all" android:id="@+id/show_all"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -21,15 +22,7 @@
android:layout_marginEnd="12dp" android:layout_marginEnd="12dp"
android:text="@string/show_all" /> android:text="@string/show_all" />
<com.google.android.material.checkbox.MaterialCheckBox <eu.kanade.tachiyomi.widget.TriStateCheckBox
android:id="@+id/show_read"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:text="@string/show_read_chapters" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/show_unread" android:id="@+id/show_unread"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -37,15 +30,15 @@
android:layout_marginEnd="12dp" android:layout_marginEnd="12dp"
android:text="@string/show_unread_chapters" /> android:text="@string/show_unread_chapters" />
<com.google.android.material.checkbox.MaterialCheckBox <eu.kanade.tachiyomi.widget.TriStateCheckBox
android:id="@+id/show_download" android:id="@+id/show_download"
android:text="@string/show_downloaded_chapters"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="12dp" android:layout_marginStart="12dp"
android:layout_marginEnd="12dp" android:layout_marginEnd="12dp" />
android:text="@string/show_downloaded_chapters" />
<com.google.android.material.checkbox.MaterialCheckBox <eu.kanade.tachiyomi.widget.TriStateCheckBox
android:id="@+id/show_bookmark" android:id="@+id/show_bookmark"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"

View File

@ -398,7 +398,6 @@
android:padding="5dp" android:padding="5dp"
android:textAlignment="textEnd" android:textAlignment="textEnd"
android:textColor="?android:textColorHint" android:textColor="?android:textColorHint"
app:layout_constraintBaseline_toBaselineOf="@id/chapters_title"
app:layout_constraintBottom_toBottomOf="@id/filter_button" app:layout_constraintBottom_toBottomOf="@id/filter_button"
app:layout_constraintEnd_toStartOf="@id/filter_button" app:layout_constraintEnd_toStartOf="@id/filter_button"
app:layout_constraintStart_toEndOf="@+id/chapters_title" app:layout_constraintStart_toEndOf="@+id/chapters_title"

View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:orientation="horizontal">
<ImageView
android:id="@+id/tri_state_box"
android:contentDescription="@string/enable"
android:padding="4dp"
app:srcCompat="@drawable/ic_check_box_outline_blank_24dp"
tools:tint="?colorControlNormal"
android:background="@drawable/round_ripple"
style="@style/MD_ListItem_Control" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/text_view"
android:layout_marginStart="2dp"
android:layout_marginEnd="4dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:textSize="14sp"
style="@style/TextAppearance.Regular"
tools:text="Item" />
</LinearLayout>

View File

@ -26,6 +26,11 @@
<attr name="summary" format="reference|string" /> <attr name="summary" format="reference|string" />
</declare-styleable> </declare-styleable>
<declare-styleable name="TriStateCheckBox">
<attr name="android:text" format="reference|string"/>
<attr name="android:maxLines"/>
</declare-styleable>
<declare-styleable name="MenuSheetItemView"> <declare-styleable name="MenuSheetItemView">
<attr name="android:text"/> <attr name="android:text"/>
<attr name="android:maxLines"/> <attr name="android:maxLines"/>

View File

@ -49,6 +49,7 @@
<string name="chapter_x_of_y">Chapter %1$d of %2$d</string> <string name="chapter_x_of_y">Chapter %1$d of %2$d</string>
<string name="all_chapters_read">All chapters read</string> <string name="all_chapters_read">All chapters read</string>
<string name="bookmarked">Bookmarked</string> <string name="bookmarked">Bookmarked</string>
<string name="not_bookmarked">Not bookmarked</string>
<string name="marked_as_read">Marked as read</string> <string name="marked_as_read">Marked as read</string>
<string name="marked_as_unread">Marked as unread</string> <string name="marked_as_unread">Marked as unread</string>
<string name="removed_bookmark">Removed bookmark</string> <string name="removed_bookmark">Removed bookmark</string>