mirror of
https://github.com/mihonapp/mihon.git
synced 2024-12-24 18:08:24 +01:00
add option to skip chapters marked read (#1791)
This commit is contained in:
parent
a62a7d5330
commit
77296348a0
@ -107,6 +107,8 @@ object PreferenceKeys {
|
|||||||
|
|
||||||
const val defaultCategory = "default_category"
|
const val defaultCategory = "default_category"
|
||||||
|
|
||||||
|
const val skipRead = "skip_read"
|
||||||
|
|
||||||
const val downloadBadge = "display_download_badge"
|
const val downloadBadge = "display_download_badge"
|
||||||
|
|
||||||
@Deprecated("Use the preferences of the source")
|
@Deprecated("Use the preferences of the source")
|
||||||
|
@ -167,6 +167,8 @@ class PreferencesHelper(val context: Context) {
|
|||||||
|
|
||||||
fun defaultCategory() = prefs.getInt(Keys.defaultCategory, -1)
|
fun defaultCategory() = prefs.getInt(Keys.defaultCategory, -1)
|
||||||
|
|
||||||
|
fun skipRead() = prefs.getBoolean(Keys.skipRead, false)
|
||||||
|
|
||||||
fun migrateFlags() = rxPrefs.getInteger("migrate_flags", Int.MAX_VALUE)
|
fun migrateFlags() = rxPrefs.getInteger("migrate_flags", Int.MAX_VALUE)
|
||||||
|
|
||||||
fun trustedSignatures() = rxPrefs.getStringSet("trusted_signatures", emptySet())
|
fun trustedSignatures() = rxPrefs.getStringSet("trusted_signatures", emptySet())
|
||||||
|
@ -32,7 +32,7 @@ import timber.log.Timber
|
|||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.Date
|
import java.util.*
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -84,12 +84,25 @@ class ReaderPresenter(
|
|||||||
private val chapterList by lazy {
|
private val chapterList by lazy {
|
||||||
val manga = manga!!
|
val manga = manga!!
|
||||||
val dbChapters = db.getChapters(manga).executeAsBlocking()
|
val dbChapters = db.getChapters(manga).executeAsBlocking()
|
||||||
|
|
||||||
val selectedChapter = dbChapters.find { it.id == chapterId }
|
val selectedChapter = dbChapters.find { it.id == chapterId }
|
||||||
?: error("Requested chapter of id $chapterId not found in chapter list")
|
?: error("Requested chapter of id $chapterId not found in chapter list")
|
||||||
|
|
||||||
|
val chaptersForReader =
|
||||||
|
if (preferences.skipRead()) {
|
||||||
|
var list = dbChapters.filter { it -> !it.read }.toMutableList()
|
||||||
|
val find = list.find { it.id == chapterId }
|
||||||
|
if (find == null) {
|
||||||
|
list.add(selectedChapter)
|
||||||
|
}
|
||||||
|
list
|
||||||
|
} else {
|
||||||
|
dbChapters
|
||||||
|
}
|
||||||
|
|
||||||
when (manga.sorting) {
|
when (manga.sorting) {
|
||||||
Manga.SORTING_SOURCE -> ChapterLoadBySource().get(dbChapters)
|
Manga.SORTING_SOURCE -> ChapterLoadBySource().get(chaptersForReader)
|
||||||
Manga.SORTING_NUMBER -> ChapterLoadByNumber().get(dbChapters, selectedChapter)
|
Manga.SORTING_NUMBER -> ChapterLoadByNumber().get(chaptersForReader, selectedChapter)
|
||||||
else -> error("Unknown sorting method")
|
else -> error("Unknown sorting method")
|
||||||
}.map(::ReaderChapter)
|
}.map(::ReaderChapter)
|
||||||
}
|
}
|
||||||
@ -165,12 +178,12 @@ class ReaderPresenter(
|
|||||||
if (!needsInit()) return
|
if (!needsInit()) return
|
||||||
|
|
||||||
db.getManga(mangaId).asRxObservable()
|
db.getManga(mangaId).asRxObservable()
|
||||||
.first()
|
.first()
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.doOnNext { init(it, initialChapterId) }
|
.doOnNext { init(it, initialChapterId) }
|
||||||
.subscribeFirst({ _, _ ->
|
.subscribeFirst({ _, _ ->
|
||||||
// Ignore onNext event
|
// Ignore onNext event
|
||||||
}, ReaderActivity::setInitialChapterError)
|
}, ReaderActivity::setInitialChapterError)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -193,13 +206,13 @@ class ReaderPresenter(
|
|||||||
// Read chapterList from an io thread because it's retrieved lazily and would block main.
|
// Read chapterList from an io thread because it's retrieved lazily and would block main.
|
||||||
activeChapterSubscription?.unsubscribe()
|
activeChapterSubscription?.unsubscribe()
|
||||||
activeChapterSubscription = Observable
|
activeChapterSubscription = Observable
|
||||||
.fromCallable { chapterList.first { chapterId == it.chapter.id } }
|
.fromCallable { chapterList.first { chapterId == it.chapter.id } }
|
||||||
.flatMap { getLoadObservable(loader!!, it) }
|
.flatMap { getLoadObservable(loader!!, it) }
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribeFirst({ _, _ ->
|
.subscribeFirst({ _, _ ->
|
||||||
// Ignore onNext event
|
// Ignore onNext event
|
||||||
}, ReaderActivity::setInitialChapterError)
|
}, ReaderActivity::setInitialChapterError)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -214,23 +227,23 @@ class ReaderPresenter(
|
|||||||
chapter: ReaderChapter
|
chapter: ReaderChapter
|
||||||
): Observable<ViewerChapters> {
|
): Observable<ViewerChapters> {
|
||||||
return loader.loadChapter(chapter)
|
return loader.loadChapter(chapter)
|
||||||
.andThen(Observable.fromCallable {
|
.andThen(Observable.fromCallable {
|
||||||
val chapterPos = chapterList.indexOf(chapter)
|
val chapterPos = chapterList.indexOf(chapter)
|
||||||
|
|
||||||
ViewerChapters(chapter,
|
ViewerChapters(chapter,
|
||||||
chapterList.getOrNull(chapterPos - 1),
|
chapterList.getOrNull(chapterPos - 1),
|
||||||
chapterList.getOrNull(chapterPos + 1))
|
chapterList.getOrNull(chapterPos + 1))
|
||||||
})
|
})
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.doOnNext { newChapters ->
|
.doOnNext { newChapters ->
|
||||||
val oldChapters = viewerChaptersRelay.value
|
val oldChapters = viewerChaptersRelay.value
|
||||||
|
|
||||||
// Add new references first to avoid unnecessary recycling
|
// Add new references first to avoid unnecessary recycling
|
||||||
newChapters.ref()
|
newChapters.ref()
|
||||||
oldChapters?.unref()
|
oldChapters?.unref()
|
||||||
|
|
||||||
viewerChaptersRelay.call(newChapters)
|
viewerChaptersRelay.call(newChapters)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -244,10 +257,10 @@ class ReaderPresenter(
|
|||||||
|
|
||||||
activeChapterSubscription?.unsubscribe()
|
activeChapterSubscription?.unsubscribe()
|
||||||
activeChapterSubscription = getLoadObservable(loader, chapter)
|
activeChapterSubscription = getLoadObservable(loader, chapter)
|
||||||
.toCompletable()
|
.toCompletable()
|
||||||
.onErrorComplete()
|
.onErrorComplete()
|
||||||
.subscribe()
|
.subscribe()
|
||||||
.also(::add)
|
.also(::add)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -262,13 +275,13 @@ class ReaderPresenter(
|
|||||||
|
|
||||||
activeChapterSubscription?.unsubscribe()
|
activeChapterSubscription?.unsubscribe()
|
||||||
activeChapterSubscription = getLoadObservable(loader, chapter)
|
activeChapterSubscription = getLoadObservable(loader, chapter)
|
||||||
.doOnSubscribe { isLoadingAdjacentChapterRelay.call(true) }
|
.doOnSubscribe { isLoadingAdjacentChapterRelay.call(true) }
|
||||||
.doOnUnsubscribe { isLoadingAdjacentChapterRelay.call(false) }
|
.doOnUnsubscribe { isLoadingAdjacentChapterRelay.call(false) }
|
||||||
.subscribeFirst({ view, _ ->
|
.subscribeFirst({ view, _ ->
|
||||||
view.moveToPageIndex(0)
|
view.moveToPageIndex(0)
|
||||||
}, { _, _ ->
|
}, { _, _ ->
|
||||||
// Ignore onError event, viewers handle that state
|
// Ignore onError event, viewers handle that state
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -285,12 +298,12 @@ class ReaderPresenter(
|
|||||||
val loader = loader ?: return
|
val loader = loader ?: return
|
||||||
|
|
||||||
loader.loadChapter(chapter)
|
loader.loadChapter(chapter)
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
// Update current chapters whenever a chapter is preloaded
|
// Update current chapters whenever a chapter is preloaded
|
||||||
.doOnCompleted { viewerChaptersRelay.value?.let(viewerChaptersRelay::call) }
|
.doOnCompleted { viewerChaptersRelay.value?.let(viewerChaptersRelay::call) }
|
||||||
.onErrorComplete()
|
.onErrorComplete()
|
||||||
.subscribe()
|
.subscribe()
|
||||||
.also(::add)
|
.also(::add)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -331,9 +344,9 @@ class ReaderPresenter(
|
|||||||
*/
|
*/
|
||||||
private fun saveChapterProgress(chapter: ReaderChapter) {
|
private fun saveChapterProgress(chapter: ReaderChapter) {
|
||||||
db.updateChapterProgress(chapter.chapter).asRxCompletable()
|
db.updateChapterProgress(chapter.chapter).asRxCompletable()
|
||||||
.onErrorComplete()
|
.onErrorComplete()
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.subscribe()
|
.subscribe()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -342,9 +355,9 @@ class ReaderPresenter(
|
|||||||
private fun saveChapterHistory(chapter: ReaderChapter) {
|
private fun saveChapterHistory(chapter: ReaderChapter) {
|
||||||
val history = History.create(chapter.chapter).apply { last_read = Date().time }
|
val history = History.create(chapter.chapter).apply { last_read = Date().time }
|
||||||
db.updateHistoryLastRead(history).asRxCompletable()
|
db.updateHistoryLastRead(history).asRxCompletable()
|
||||||
.onErrorComplete()
|
.onErrorComplete()
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.subscribe()
|
.subscribe()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -394,18 +407,18 @@ class ReaderPresenter(
|
|||||||
db.updateMangaViewer(manga).executeAsBlocking()
|
db.updateMangaViewer(manga).executeAsBlocking()
|
||||||
|
|
||||||
Observable.timer(250, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
|
Observable.timer(250, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
|
||||||
.subscribeFirst({ view, _ ->
|
.subscribeFirst({ view, _ ->
|
||||||
val currChapters = viewerChaptersRelay.value
|
val currChapters = viewerChaptersRelay.value
|
||||||
if (currChapters != null) {
|
if (currChapters != null) {
|
||||||
// Save current page
|
// Save current page
|
||||||
val currChapter = currChapters.currChapter
|
val currChapter = currChapters.currChapter
|
||||||
currChapter.requestedPage = currChapter.chapter.last_page_read
|
currChapter.requestedPage = currChapter.chapter.last_page_read
|
||||||
|
|
||||||
// Emit manga and chapters to the new viewer
|
// Emit manga and chapters to the new viewer
|
||||||
view.setManga(manga)
|
view.setManga(manga)
|
||||||
view.setChapters(currChapters)
|
view.setChapters(currChapters)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -446,22 +459,22 @@ class ReaderPresenter(
|
|||||||
|
|
||||||
// Pictures directory.
|
// Pictures directory.
|
||||||
val destDir = File(Environment.getExternalStorageDirectory().absolutePath +
|
val destDir = File(Environment.getExternalStorageDirectory().absolutePath +
|
||||||
File.separator + Environment.DIRECTORY_PICTURES +
|
File.separator + Environment.DIRECTORY_PICTURES +
|
||||||
File.separator + "Tachiyomi")
|
File.separator + "Tachiyomi")
|
||||||
|
|
||||||
// Copy file in background.
|
// Copy file in background.
|
||||||
Observable.fromCallable { saveImage(page, destDir, manga) }
|
Observable.fromCallable { saveImage(page, destDir, manga) }
|
||||||
.doOnNext { file ->
|
.doOnNext { file ->
|
||||||
DiskUtil.scanMedia(context, file)
|
DiskUtil.scanMedia(context, file)
|
||||||
notifier.onComplete(file)
|
notifier.onComplete(file)
|
||||||
}
|
}
|
||||||
.doOnError { notifier.onError(it.message) }
|
.doOnError { notifier.onError(it.message) }
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribeFirst(
|
.subscribeFirst(
|
||||||
{ view, file -> view.onSaveImageResult(SaveImageResult.Success(file)) },
|
{ view, file -> view.onSaveImageResult(SaveImageResult.Success(file)) },
|
||||||
{ view, error -> view.onSaveImageResult(SaveImageResult.Error(error)) }
|
{ view, error -> view.onSaveImageResult(SaveImageResult.Error(error)) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -479,13 +492,13 @@ class ReaderPresenter(
|
|||||||
val destDir = File(context.cacheDir, "shared_image")
|
val destDir = File(context.cacheDir, "shared_image")
|
||||||
|
|
||||||
Observable.fromCallable { destDir.delete() } // Keep only the last shared file
|
Observable.fromCallable { destDir.delete() } // Keep only the last shared file
|
||||||
.map { saveImage(page, destDir, manga) }
|
.map { saveImage(page, destDir, manga) }
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribeFirst(
|
.subscribeFirst(
|
||||||
{ view, file -> view.onShareImageResult(file) },
|
{ view, file -> view.onShareImageResult(file) },
|
||||||
{ view, error -> /* Empty */ }
|
{ view, error -> /* Empty */ }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -497,28 +510,28 @@ class ReaderPresenter(
|
|||||||
val stream = page.stream ?: return
|
val stream = page.stream ?: return
|
||||||
|
|
||||||
Observable
|
Observable
|
||||||
.fromCallable {
|
.fromCallable {
|
||||||
if (manga.source == LocalSource.ID) {
|
if (manga.source == LocalSource.ID) {
|
||||||
val context = Injekt.get<Application>()
|
val context = Injekt.get<Application>()
|
||||||
LocalSource.updateCover(context, manga, stream())
|
LocalSource.updateCover(context, manga, stream())
|
||||||
R.string.cover_updated
|
R.string.cover_updated
|
||||||
SetAsCoverResult.Success
|
|
||||||
} else {
|
|
||||||
val thumbUrl = manga.thumbnail_url ?: throw Exception("Image url not found")
|
|
||||||
if (manga.favorite) {
|
|
||||||
coverCache.copyToCache(thumbUrl, stream())
|
|
||||||
SetAsCoverResult.Success
|
SetAsCoverResult.Success
|
||||||
} else {
|
} else {
|
||||||
SetAsCoverResult.AddToLibraryFirst
|
val thumbUrl = manga.thumbnail_url ?: throw Exception("Image url not found")
|
||||||
|
if (manga.favorite) {
|
||||||
|
coverCache.copyToCache(thumbUrl, stream())
|
||||||
|
SetAsCoverResult.Success
|
||||||
|
} else {
|
||||||
|
SetAsCoverResult.AddToLibraryFirst
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
.subscribeOn(Schedulers.io())
|
||||||
.subscribeOn(Schedulers.io())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.subscribeFirst(
|
||||||
.subscribeFirst(
|
{ view, result -> view.onSetAsCoverResult(result) },
|
||||||
{ view, result -> view.onSetAsCoverResult(result) },
|
{ view, _ -> view.onSetAsCoverResult(SetAsCoverResult.Error) }
|
||||||
{ view, _ -> view.onSetAsCoverResult(SetAsCoverResult.Error) }
|
)
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -559,26 +572,26 @@ class ReaderPresenter(
|
|||||||
val trackManager = Injekt.get<TrackManager>()
|
val trackManager = Injekt.get<TrackManager>()
|
||||||
|
|
||||||
db.getTracks(manga).asRxSingle()
|
db.getTracks(manga).asRxSingle()
|
||||||
.flatMapCompletable { trackList ->
|
.flatMapCompletable { trackList ->
|
||||||
Completable.concat(trackList.map { track ->
|
Completable.concat(trackList.map { track ->
|
||||||
val service = trackManager.getService(track.sync_id)
|
val service = trackManager.getService(track.sync_id)
|
||||||
if (service != null && service.isLogged && lastChapterRead > track.last_chapter_read) {
|
if (service != null && service.isLogged && lastChapterRead > track.last_chapter_read) {
|
||||||
track.last_chapter_read = lastChapterRead
|
track.last_chapter_read = lastChapterRead
|
||||||
|
|
||||||
// We wan't these to execute even if the presenter is destroyed and leaks
|
// We wan't these to execute even if the presenter is destroyed and leaks
|
||||||
// for a while. The view can still be garbage collected.
|
// for a while. The view can still be garbage collected.
|
||||||
Observable.defer { service.update(track) }
|
Observable.defer { service.update(track) }
|
||||||
.map { db.insertTrack(track).executeAsBlocking() }
|
.map { db.insertTrack(track).executeAsBlocking() }
|
||||||
.toCompletable()
|
.toCompletable()
|
||||||
.onErrorComplete()
|
.onErrorComplete()
|
||||||
} else {
|
} else {
|
||||||
Completable.complete()
|
Completable.complete()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
.onErrorComplete()
|
.onErrorComplete()
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.subscribe()
|
.subscribe()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -594,19 +607,19 @@ class ReaderPresenter(
|
|||||||
if (removeAfterReadSlots == -1) return
|
if (removeAfterReadSlots == -1) return
|
||||||
|
|
||||||
Completable
|
Completable
|
||||||
.fromCallable {
|
.fromCallable {
|
||||||
// Position of the read chapter
|
// Position of the read chapter
|
||||||
val position = chapterList.indexOf(chapter)
|
val position = chapterList.indexOf(chapter)
|
||||||
|
|
||||||
// Retrieve chapter to delete according to preference
|
// Retrieve chapter to delete according to preference
|
||||||
val chapterToDelete = chapterList.getOrNull(position - removeAfterReadSlots)
|
val chapterToDelete = chapterList.getOrNull(position - removeAfterReadSlots)
|
||||||
if (chapterToDelete != null) {
|
if (chapterToDelete != null) {
|
||||||
downloadManager.enqueueDeleteChapters(listOf(chapterToDelete.chapter), manga)
|
downloadManager.enqueueDeleteChapters(listOf(chapterToDelete.chapter), manga)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
.onErrorComplete()
|
||||||
.onErrorComplete()
|
.subscribeOn(Schedulers.io())
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribe()
|
||||||
.subscribe()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -615,9 +628,9 @@ class ReaderPresenter(
|
|||||||
*/
|
*/
|
||||||
private fun deletePendingChapters() {
|
private fun deletePendingChapters() {
|
||||||
Completable.fromCallable { downloadManager.deletePendingChapters() }
|
Completable.fromCallable { downloadManager.deletePendingChapters() }
|
||||||
.onErrorComplete()
|
.onErrorComplete()
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.subscribe()
|
.subscribe()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -63,6 +63,11 @@ class SettingsReaderController : SettingsController() {
|
|||||||
defaultValue = "500"
|
defaultValue = "500"
|
||||||
summary = "%s"
|
summary = "%s"
|
||||||
}
|
}
|
||||||
|
switchPreference {
|
||||||
|
key = Keys.skipRead
|
||||||
|
titleRes = R.string.pref_skip_read_chapters
|
||||||
|
defaultValue = false
|
||||||
|
}
|
||||||
switchPreference {
|
switchPreference {
|
||||||
key = Keys.fullscreen
|
key = Keys.fullscreen
|
||||||
titleRes = R.string.pref_fullscreen
|
titleRes = R.string.pref_fullscreen
|
||||||
|
@ -178,6 +178,7 @@
|
|||||||
<string name="pref_custom_brightness">Use custom brightness</string>
|
<string name="pref_custom_brightness">Use custom brightness</string>
|
||||||
<string name="pref_custom_color_filter">Use custom color filter</string>
|
<string name="pref_custom_color_filter">Use custom color filter</string>
|
||||||
<string name="pref_keep_screen_on">Keep screen on</string>
|
<string name="pref_keep_screen_on">Keep screen on</string>
|
||||||
|
<string name="pref_skip_read_chapters">Skip chapters marked read</string>
|
||||||
<string name="pref_reader_navigation">Navigation</string>
|
<string name="pref_reader_navigation">Navigation</string>
|
||||||
<string name="pref_read_with_volume_keys">Volume keys</string>
|
<string name="pref_read_with_volume_keys">Volume keys</string>
|
||||||
<string name="pref_read_with_volume_keys_inverted">Invert volume keys</string>
|
<string name="pref_read_with_volume_keys_inverted">Invert volume keys</string>
|
||||||
|
Loading…
Reference in New Issue
Block a user