mirror of
https://github.com/mihonapp/mihon.git
synced 2025-07-01 21:47:50 +02:00
Compare commits
24 Commits
Author | SHA1 | Date | |
---|---|---|---|
fd76255cf6 | |||
d180631877 | |||
1977e21363 | |||
e1a3ee1b81 | |||
cc43d9daed | |||
79705df499 | |||
36bbb906c1 | |||
816cc17ed3 | |||
97e3b5d2ab | |||
79ab9d80f2 | |||
32511149d1 | |||
cc9fd53abb | |||
4061c7450b | |||
9ad535bde6 | |||
b067096fc7 | |||
2dd58e5f7d | |||
7c42ab885b | |||
26b283d44d | |||
8c1b07c4ba | |||
f98e0858a7 | |||
8b60d5bfcb | |||
30b4c6e755 | |||
3d2a98451b | |||
aba528b227 |
@ -38,8 +38,8 @@ android {
|
|||||||
minSdkVersion 16
|
minSdkVersion 16
|
||||||
targetSdkVersion 25
|
targetSdkVersion 25
|
||||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||||
versionCode 16
|
versionCode 17
|
||||||
versionName "0.4.0"
|
versionName "0.4.1"
|
||||||
|
|
||||||
buildConfigField "String", "COMMIT_COUNT", "\"${getCommitCount()}\""
|
buildConfigField "String", "COMMIT_COUNT", "\"${getCommitCount()}\""
|
||||||
buildConfigField "String", "COMMIT_SHA", "\"${getGitSha()}\""
|
buildConfigField "String", "COMMIT_SHA", "\"${getGitSha()}\""
|
||||||
|
@ -2,10 +2,12 @@ package eu.kanade.tachiyomi
|
|||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.res.Configuration
|
||||||
import android.support.multidex.MultiDex
|
import android.support.multidex.MultiDex
|
||||||
import com.evernote.android.job.JobManager
|
import com.evernote.android.job.JobManager
|
||||||
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
|
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
|
||||||
import eu.kanade.tachiyomi.data.updater.UpdateCheckerJob
|
import eu.kanade.tachiyomi.data.updater.UpdateCheckerJob
|
||||||
|
import eu.kanade.tachiyomi.util.LocaleHelper
|
||||||
import org.acra.ACRA
|
import org.acra.ACRA
|
||||||
import org.acra.annotation.ReportsCrashes
|
import org.acra.annotation.ReportsCrashes
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
@ -31,6 +33,8 @@ open class App : Application() {
|
|||||||
|
|
||||||
setupAcra()
|
setupAcra()
|
||||||
setupJobManager()
|
setupJobManager()
|
||||||
|
|
||||||
|
LocaleHelper.updateCfg(this, baseContext.resources.configuration)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun attachBaseContext(base: Context) {
|
override fun attachBaseContext(base: Context) {
|
||||||
@ -40,6 +44,11 @@ open class App : Application() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||||
|
super.onConfigurationChanged(newConfig)
|
||||||
|
LocaleHelper.updateCfg(this, newConfig)
|
||||||
|
}
|
||||||
|
|
||||||
protected open fun setupAcra() {
|
protected open fun setupAcra() {
|
||||||
ACRA.init(this)
|
ACRA.init(this)
|
||||||
}
|
}
|
||||||
|
@ -6,4 +6,5 @@ object Constants {
|
|||||||
const val NOTIFICATION_DOWNLOAD_CHAPTER_ID = 3
|
const val NOTIFICATION_DOWNLOAD_CHAPTER_ID = 3
|
||||||
const val NOTIFICATION_DOWNLOAD_CHAPTER_ERROR_ID = 4
|
const val NOTIFICATION_DOWNLOAD_CHAPTER_ERROR_ID = 4
|
||||||
const val NOTIFICATION_DOWNLOAD_IMAGE_ID = 5
|
const val NOTIFICATION_DOWNLOAD_IMAGE_ID = 5
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,6 @@ interface HistoryQueries : DbProvider {
|
|||||||
.build())
|
.build())
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the history last read.
|
* Updates the history last read.
|
||||||
* Inserts history object if not yet in database
|
* Inserts history object if not yet in database
|
||||||
|
@ -7,6 +7,7 @@ import eu.kanade.tachiyomi.data.database.DbProvider
|
|||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.data.database.resolvers.LibraryMangaGetResolver
|
import eu.kanade.tachiyomi.data.database.resolvers.LibraryMangaGetResolver
|
||||||
import eu.kanade.tachiyomi.data.database.resolvers.MangaFlagsPutResolver
|
import eu.kanade.tachiyomi.data.database.resolvers.MangaFlagsPutResolver
|
||||||
|
import eu.kanade.tachiyomi.data.database.resolvers.MangaLastUpdatedPutResolver
|
||||||
import eu.kanade.tachiyomi.data.database.tables.ChapterTable
|
import eu.kanade.tachiyomi.data.database.tables.ChapterTable
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable
|
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable
|
import eu.kanade.tachiyomi.data.database.tables.MangaTable
|
||||||
@ -29,7 +30,7 @@ interface MangaQueries : DbProvider {
|
|||||||
.withGetResolver(LibraryMangaGetResolver.INSTANCE)
|
.withGetResolver(LibraryMangaGetResolver.INSTANCE)
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
open fun getFavoriteMangas() = db.get()
|
fun getFavoriteMangas() = db.get()
|
||||||
.listOfObjects(Manga::class.java)
|
.listOfObjects(Manga::class.java)
|
||||||
.withQuery(Query.builder()
|
.withQuery(Query.builder()
|
||||||
.table(MangaTable.TABLE)
|
.table(MangaTable.TABLE)
|
||||||
@ -66,6 +67,11 @@ interface MangaQueries : DbProvider {
|
|||||||
.withPutResolver(MangaFlagsPutResolver())
|
.withPutResolver(MangaFlagsPutResolver())
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
|
fun updateLastUpdated(manga: Manga) = db.put()
|
||||||
|
.`object`(manga)
|
||||||
|
.withPutResolver(MangaLastUpdatedPutResolver())
|
||||||
|
.prepare()
|
||||||
|
|
||||||
fun deleteManga(manga: Manga) = db.delete().`object`(manga).prepare()
|
fun deleteManga(manga: Manga) = db.delete().`object`(manga).prepare()
|
||||||
|
|
||||||
fun deleteMangas(mangas: List<Manga>) = db.delete().objects(mangas).prepare()
|
fun deleteMangas(mangas: List<Manga>) = db.delete().objects(mangas).prepare()
|
||||||
@ -78,4 +84,11 @@ interface MangaQueries : DbProvider {
|
|||||||
.build())
|
.build())
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
|
fun getLastReadManga() = db.get()
|
||||||
|
.listOfObjects(Manga::class.java)
|
||||||
|
.withQuery(RawQuery.builder()
|
||||||
|
.query(getLastReadMangaQuery())
|
||||||
|
.observesTables(MangaTable.TABLE)
|
||||||
|
.build())
|
||||||
|
.prepare()
|
||||||
}
|
}
|
@ -73,6 +73,18 @@ fun getHistoryByMangaId() = """
|
|||||||
WHERE ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} = ? AND ${History.TABLE}.${History.COL_CHAPTER_ID} = ${Chapter.TABLE}.${Chapter.COL_ID}
|
WHERE ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} = ? AND ${History.TABLE}.${History.COL_CHAPTER_ID} = ${Chapter.TABLE}.${Chapter.COL_ID}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
fun getLastReadMangaQuery() = """
|
||||||
|
SELECT ${Manga.TABLE}.*, MAX(${History.TABLE}.${History.COL_LAST_READ}) AS max
|
||||||
|
FROM ${Manga.TABLE}
|
||||||
|
JOIN ${Chapter.TABLE}
|
||||||
|
ON ${Manga.TABLE}.${Manga.COL_ID} = ${Chapter.TABLE}.${Chapter.COL_MANGA_ID}
|
||||||
|
JOIN ${History.TABLE}
|
||||||
|
ON ${Chapter.TABLE}.${Chapter.COL_ID} = ${History.TABLE}.${History.COL_CHAPTER_ID}
|
||||||
|
WHERE ${Manga.TABLE}.${Manga.COL_FAVORITE} = 1
|
||||||
|
GROUP BY ${Manga.TABLE}.${Manga.COL_ID}
|
||||||
|
ORDER BY max DESC
|
||||||
|
"""
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Query to get the categories for a manga.
|
* Query to get the categories for a manga.
|
||||||
*/
|
*/
|
||||||
|
@ -0,0 +1,33 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.database.resolvers
|
||||||
|
|
||||||
|
import android.content.ContentValues
|
||||||
|
import com.pushtorefresh.storio.sqlite.StorIOSQLite
|
||||||
|
import com.pushtorefresh.storio.sqlite.operations.put.PutResolver
|
||||||
|
import com.pushtorefresh.storio.sqlite.operations.put.PutResult
|
||||||
|
import com.pushtorefresh.storio.sqlite.queries.UpdateQuery
|
||||||
|
import eu.kanade.tachiyomi.data.database.inTransactionReturn
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
|
import eu.kanade.tachiyomi.data.database.tables.MangaTable
|
||||||
|
|
||||||
|
class MangaLastUpdatedPutResolver : PutResolver<Manga>() {
|
||||||
|
|
||||||
|
override fun performPut(db: StorIOSQLite, manga: Manga) = db.inTransactionReturn {
|
||||||
|
val updateQuery = mapToUpdateQuery(manga)
|
||||||
|
val contentValues = mapToContentValues(manga)
|
||||||
|
|
||||||
|
val numberOfRowsUpdated = db.lowLevel().update(updateQuery, contentValues)
|
||||||
|
PutResult.newUpdateResult(numberOfRowsUpdated, updateQuery.table())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun mapToUpdateQuery(manga: Manga) = UpdateQuery.builder()
|
||||||
|
.table(MangaTable.TABLE)
|
||||||
|
.where("${MangaTable.COL_ID} = ?")
|
||||||
|
.whereArgs(manga.id)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
fun mapToContentValues(manga: Manga) = ContentValues(1).apply {
|
||||||
|
put(MangaTable.COL_LAST_UPDATE, manga.last_update)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -110,6 +110,15 @@ class DownloadManager(context: Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the directory name for a manga.
|
||||||
|
*
|
||||||
|
* @param manga the manga to query.
|
||||||
|
*/
|
||||||
|
fun getMangaDirName(manga: Manga): String {
|
||||||
|
return provider.getMangaDirName(manga)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the directory name for the given chapter.
|
* Returns the directory name for the given chapter.
|
||||||
*
|
*
|
||||||
@ -119,6 +128,15 @@ class DownloadManager(context: Context) {
|
|||||||
return provider.getChapterDirName(chapter)
|
return provider.getChapterDirName(chapter)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the download directory for a source if it exists.
|
||||||
|
*
|
||||||
|
* @param source the source to query.
|
||||||
|
*/
|
||||||
|
fun findSourceDir(source: Source): UniFile? {
|
||||||
|
return provider.findSourceDir(source)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the directory for the given manga, if it exists.
|
* Returns the directory for the given manga, if it exists.
|
||||||
*
|
*
|
||||||
|
@ -6,6 +6,7 @@ import com.hippo.unifile.UniFile
|
|||||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
|
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
||||||
import eu.kanade.tachiyomi.data.source.Source
|
import eu.kanade.tachiyomi.data.source.Source
|
||||||
import eu.kanade.tachiyomi.util.DiskUtil
|
import eu.kanade.tachiyomi.util.DiskUtil
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
@ -26,10 +27,13 @@ class DownloadProvider(private val context: Context) {
|
|||||||
/**
|
/**
|
||||||
* The root directory for downloads.
|
* The root directory for downloads.
|
||||||
*/
|
*/
|
||||||
private lateinit var downloadsDir: UniFile
|
private var downloadsDir = preferences.downloadsDirectory().getOrDefault().let {
|
||||||
|
UniFile.fromUri(context, Uri.parse(it))
|
||||||
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
preferences.downloadsDirectory().asObservable()
|
preferences.downloadsDirectory().asObservable()
|
||||||
|
.skip(1)
|
||||||
.subscribe { downloadsDir = UniFile.fromUri(context, Uri.parse(it)) }
|
.subscribe { downloadsDir = UniFile.fromUri(context, Uri.parse(it)) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,6 +49,15 @@ class DownloadProvider(private val context: Context) {
|
|||||||
.createDirectory(getMangaDirName(manga))
|
.createDirectory(getMangaDirName(manga))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the download directory for a source if it exists.
|
||||||
|
*
|
||||||
|
* @param source the source to query.
|
||||||
|
*/
|
||||||
|
fun findSourceDir(source: Source): UniFile? {
|
||||||
|
return downloadsDir.findFile(getSourceDirName(source))
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the download directory for a manga if it exists.
|
* Returns the download directory for a manga if it exists.
|
||||||
*
|
*
|
||||||
@ -52,7 +65,7 @@ class DownloadProvider(private val context: Context) {
|
|||||||
* @param manga the manga to query.
|
* @param manga the manga to query.
|
||||||
*/
|
*/
|
||||||
fun findMangaDir(source: Source, manga: Manga): UniFile? {
|
fun findMangaDir(source: Source, manga: Manga): UniFile? {
|
||||||
val sourceDir = downloadsDir.findFile(getSourceDirName(source))
|
val sourceDir = findSourceDir(source)
|
||||||
return sourceDir?.findFile(getMangaDirName(manga))
|
return sourceDir?.findFile(getMangaDirName(manga))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,7 +13,10 @@ import eu.kanade.tachiyomi.Constants
|
|||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||||
import eu.kanade.tachiyomi.data.database.models.Category
|
import eu.kanade.tachiyomi.data.database.models.Category
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
|
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||||
|
import eu.kanade.tachiyomi.data.download.DownloadService
|
||||||
import eu.kanade.tachiyomi.data.library.LibraryUpdateService.Companion.start
|
import eu.kanade.tachiyomi.data.library.LibraryUpdateService.Companion.start
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
||||||
@ -53,6 +56,8 @@ class LibraryUpdateService : Service() {
|
|||||||
*/
|
*/
|
||||||
val preferences: PreferencesHelper by injectLazy()
|
val preferences: PreferencesHelper by injectLazy()
|
||||||
|
|
||||||
|
val downloadManager: DownloadManager by injectLazy()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wake lock that will be held until the service is destroyed.
|
* Wake lock that will be held until the service is destroyed.
|
||||||
*/
|
*/
|
||||||
@ -243,32 +248,55 @@ class LibraryUpdateService : Service() {
|
|||||||
// If there's any error, return empty update and continue.
|
// If there's any error, return empty update and continue.
|
||||||
.onErrorReturn {
|
.onErrorReturn {
|
||||||
failedUpdates.add(manga)
|
failedUpdates.add(manga)
|
||||||
Pair(0, 0)
|
Pair(emptyList<Chapter>(), emptyList<Chapter>())
|
||||||
}
|
}
|
||||||
// Filter out mangas without new chapters (or failed).
|
// Filter out mangas without new chapters (or failed).
|
||||||
.filter { pair -> pair.first > 0 }
|
.filter { pair -> pair.first.size > 0 }
|
||||||
|
.doOnNext {
|
||||||
|
if (preferences.downloadNew()) {
|
||||||
|
downloadChapters(manga, it.first)
|
||||||
|
}
|
||||||
|
}
|
||||||
// Convert to the manga that contains new chapters.
|
// Convert to the manga that contains new chapters.
|
||||||
.map { manga }
|
.map { manga }
|
||||||
}
|
}
|
||||||
// Add manga with new chapters to the list.
|
// Add manga with new chapters to the list.
|
||||||
.doOnNext { newUpdates.add(it) }
|
.doOnNext { manga ->
|
||||||
|
// Set last updated time
|
||||||
|
manga.last_update = Date().time
|
||||||
|
db.updateLastUpdated(manga).executeAsBlocking()
|
||||||
|
// Add to the list
|
||||||
|
newUpdates.add(manga)
|
||||||
|
}
|
||||||
// Notify result of the overall update.
|
// Notify result of the overall update.
|
||||||
.doOnCompleted {
|
.doOnCompleted {
|
||||||
if (newUpdates.isEmpty()) {
|
if (newUpdates.isEmpty()) {
|
||||||
cancelNotification()
|
cancelNotification()
|
||||||
} else {
|
} else {
|
||||||
|
if (preferences.downloadNew()) {
|
||||||
|
DownloadService.start(this)
|
||||||
|
}
|
||||||
showResultNotification(newUpdates, failedUpdates)
|
showResultNotification(newUpdates, failedUpdates)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun downloadChapters(manga: Manga, chapters: List<Chapter>) {
|
||||||
|
// we need to get the chapters from the db so we have chapter ids
|
||||||
|
val mangaChapters = db.getChapters(manga).executeAsBlocking()
|
||||||
|
val dbChapters = chapters.map {
|
||||||
|
mangaChapters.find { mangaChapter -> mangaChapter.url == it.url }!!
|
||||||
|
}
|
||||||
|
downloadManager.downloadChapters(manga, dbChapters)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the chapters for the given manga and adds them to the database.
|
* Updates the chapters for the given manga and adds them to the database.
|
||||||
*
|
*
|
||||||
* @param manga the manga to update.
|
* @param manga the manga to update.
|
||||||
* @return a pair of the inserted and removed chapters.
|
* @return a pair of the inserted and removed chapters.
|
||||||
*/
|
*/
|
||||||
fun updateManga(manga: Manga): Observable<Pair<Int, Int>> {
|
fun updateManga(manga: Manga): Observable<Pair<List<Chapter>, List<Chapter>>> {
|
||||||
val source = sourceManager.get(manga.source) as? OnlineSource ?: return Observable.empty()
|
val source = sourceManager.get(manga.source) as? OnlineSource ?: return Observable.empty()
|
||||||
return source.fetchChapterList(manga)
|
return source.fetchChapterList(manga)
|
||||||
.map { syncChaptersWithSource(db, it, manga, source) }
|
.map { syncChaptersWithSource(db, it, manga, source) }
|
||||||
|
@ -46,6 +46,15 @@ fun Call.asObservable(): Observable<Response> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Call.asObservableSuccess(): Observable<Response> {
|
||||||
|
return asObservable().doOnNext { response ->
|
||||||
|
if (!response.isSuccessful) {
|
||||||
|
response.close()
|
||||||
|
throw Exception("Unsuccessful code ${response.code()}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun OkHttpClient.newCallWithProgress(request: Request, listener: ProgressListener): Call {
|
fun OkHttpClient.newCallWithProgress(request: Request, listener: ProgressListener): Call {
|
||||||
val progressClient = newBuilder()
|
val progressClient = newBuilder()
|
||||||
.cache(null)
|
.cache(null)
|
||||||
|
@ -83,10 +83,14 @@ class PreferenceKeys(context: Context) {
|
|||||||
|
|
||||||
val filterUnread = context.getString(R.string.pref_filter_unread_key)
|
val filterUnread = context.getString(R.string.pref_filter_unread_key)
|
||||||
|
|
||||||
|
val librarySortingMode = context.getString(R.string.pref_library_sorting_mode_key)
|
||||||
|
|
||||||
val automaticUpdates = context.getString(R.string.pref_enable_automatic_updates_key)
|
val automaticUpdates = context.getString(R.string.pref_enable_automatic_updates_key)
|
||||||
|
|
||||||
val startScreen = context.getString(R.string.pref_start_screen_key)
|
val startScreen = context.getString(R.string.pref_start_screen_key)
|
||||||
|
|
||||||
|
val downloadNew = context.getString(R.string.pref_download_new_key)
|
||||||
|
|
||||||
fun sourceUsername(sourceId: Int) = "pref_source_username_$sourceId"
|
fun sourceUsername(sourceId: Int) = "pref_source_username_$sourceId"
|
||||||
|
|
||||||
fun sourcePassword(sourceId: Int) = "pref_source_password_$sourceId"
|
fun sourcePassword(sourceId: Int) = "pref_source_password_$sourceId"
|
||||||
@ -97,4 +101,6 @@ class PreferenceKeys(context: Context) {
|
|||||||
|
|
||||||
val libraryAsList = context.getString(R.string.pref_display_library_as_list)
|
val libraryAsList = context.getString(R.string.pref_display_library_as_list)
|
||||||
|
|
||||||
|
val lang = context.getString(R.string.pref_language_key)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,8 @@ import java.io.File
|
|||||||
|
|
||||||
fun <T> Preference<T>.getOrDefault(): T = get() ?: defaultValue()!!
|
fun <T> Preference<T>.getOrDefault(): T = get() ?: defaultValue()!!
|
||||||
|
|
||||||
|
fun Preference<Boolean>.invert(): Boolean = getOrDefault().let { set(!it); !it }
|
||||||
|
|
||||||
class PreferencesHelper(context: Context) {
|
class PreferencesHelper(context: Context) {
|
||||||
|
|
||||||
val keys = PreferenceKeys(context)
|
val keys = PreferenceKeys(context)
|
||||||
@ -126,8 +128,16 @@ class PreferencesHelper(context: Context) {
|
|||||||
|
|
||||||
fun filterUnread() = rxPrefs.getBoolean(keys.filterUnread, false)
|
fun filterUnread() = rxPrefs.getBoolean(keys.filterUnread, false)
|
||||||
|
|
||||||
|
fun librarySortingMode() = rxPrefs.getInteger(keys.librarySortingMode, 0)
|
||||||
|
|
||||||
|
fun librarySortingAscending() = rxPrefs.getBoolean("library_sorting_ascending", true)
|
||||||
|
|
||||||
fun automaticUpdates() = prefs.getBoolean(keys.automaticUpdates, false)
|
fun automaticUpdates() = prefs.getBoolean(keys.automaticUpdates, false)
|
||||||
|
|
||||||
fun hiddenCatalogues() = rxPrefs.getStringSet("hidden_catalogues", emptySet())
|
fun hiddenCatalogues() = rxPrefs.getStringSet("hidden_catalogues", emptySet())
|
||||||
|
|
||||||
|
fun downloadNew() = prefs.getBoolean(keys.downloadNew, false)
|
||||||
|
|
||||||
|
fun lang() = prefs.getInt(keys.lang, 0)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ import eu.kanade.tachiyomi.data.database.models.Chapter
|
|||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.data.network.GET
|
import eu.kanade.tachiyomi.data.network.GET
|
||||||
import eu.kanade.tachiyomi.data.network.NetworkHelper
|
import eu.kanade.tachiyomi.data.network.NetworkHelper
|
||||||
import eu.kanade.tachiyomi.data.network.asObservable
|
import eu.kanade.tachiyomi.data.network.asObservableSuccess
|
||||||
import eu.kanade.tachiyomi.data.network.newCallWithProgress
|
import eu.kanade.tachiyomi.data.network.newCallWithProgress
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.data.source.Language
|
import eu.kanade.tachiyomi.data.source.Language
|
||||||
@ -93,7 +93,7 @@ abstract class OnlineSource() : Source {
|
|||||||
*/
|
*/
|
||||||
open fun fetchPopularManga(page: MangasPage): Observable<MangasPage> = client
|
open fun fetchPopularManga(page: MangasPage): Observable<MangasPage> = client
|
||||||
.newCall(popularMangaRequest(page))
|
.newCall(popularMangaRequest(page))
|
||||||
.asObservable()
|
.asObservableSuccess()
|
||||||
.map { response ->
|
.map { response ->
|
||||||
popularMangaParse(response, page)
|
popularMangaParse(response, page)
|
||||||
page
|
page
|
||||||
@ -136,7 +136,7 @@ abstract class OnlineSource() : Source {
|
|||||||
*/
|
*/
|
||||||
open fun fetchSearchManga(page: MangasPage, query: String, filters: List<Filter>): Observable<MangasPage> = client
|
open fun fetchSearchManga(page: MangasPage, query: String, filters: List<Filter>): Observable<MangasPage> = client
|
||||||
.newCall(searchMangaRequest(page, query, filters))
|
.newCall(searchMangaRequest(page, query, filters))
|
||||||
.asObservable()
|
.asObservableSuccess()
|
||||||
.map { response ->
|
.map { response ->
|
||||||
searchMangaParse(response, page, query, filters)
|
searchMangaParse(response, page, query, filters)
|
||||||
page
|
page
|
||||||
@ -178,7 +178,7 @@ abstract class OnlineSource() : Source {
|
|||||||
*/
|
*/
|
||||||
open fun fetchLatestUpdates(page: MangasPage): Observable<MangasPage> = client
|
open fun fetchLatestUpdates(page: MangasPage): Observable<MangasPage> = client
|
||||||
.newCall(latestUpdatesRequest(page))
|
.newCall(latestUpdatesRequest(page))
|
||||||
.asObservable()
|
.asObservableSuccess()
|
||||||
.map { response ->
|
.map { response ->
|
||||||
latestUpdatesParse(response, page)
|
latestUpdatesParse(response, page)
|
||||||
page
|
page
|
||||||
@ -212,7 +212,7 @@ abstract class OnlineSource() : Source {
|
|||||||
*/
|
*/
|
||||||
override fun fetchMangaDetails(manga: Manga): Observable<Manga> = client
|
override fun fetchMangaDetails(manga: Manga): Observable<Manga> = client
|
||||||
.newCall(mangaDetailsRequest(manga))
|
.newCall(mangaDetailsRequest(manga))
|
||||||
.asObservable()
|
.asObservableSuccess()
|
||||||
.map { response ->
|
.map { response ->
|
||||||
Manga.create(manga.url, id).apply {
|
Manga.create(manga.url, id).apply {
|
||||||
mangaDetailsParse(response, this)
|
mangaDetailsParse(response, this)
|
||||||
@ -246,7 +246,7 @@ abstract class OnlineSource() : Source {
|
|||||||
*/
|
*/
|
||||||
override fun fetchChapterList(manga: Manga): Observable<List<Chapter>> = client
|
override fun fetchChapterList(manga: Manga): Observable<List<Chapter>> = client
|
||||||
.newCall(chapterListRequest(manga))
|
.newCall(chapterListRequest(manga))
|
||||||
.asObservable()
|
.asObservableSuccess()
|
||||||
.map { response ->
|
.map { response ->
|
||||||
mutableListOf<Chapter>().apply {
|
mutableListOf<Chapter>().apply {
|
||||||
chapterListParse(response, this)
|
chapterListParse(response, this)
|
||||||
@ -292,11 +292,8 @@ abstract class OnlineSource() : Source {
|
|||||||
*/
|
*/
|
||||||
open fun fetchPageListFromNetwork(chapter: Chapter): Observable<List<Page>> = client
|
open fun fetchPageListFromNetwork(chapter: Chapter): Observable<List<Page>> = client
|
||||||
.newCall(pageListRequest(chapter))
|
.newCall(pageListRequest(chapter))
|
||||||
.asObservable()
|
.asObservableSuccess()
|
||||||
.map { response ->
|
.map { response ->
|
||||||
if (!response.isSuccessful) {
|
|
||||||
throw Exception("Webpage sent ${response.code()} code")
|
|
||||||
}
|
|
||||||
mutableListOf<Page>().apply {
|
mutableListOf<Page>().apply {
|
||||||
pageListParse(response, this)
|
pageListParse(response, this)
|
||||||
if (isEmpty()) {
|
if (isEmpty()) {
|
||||||
@ -338,7 +335,7 @@ abstract class OnlineSource() : Source {
|
|||||||
page.status = Page.LOAD_PAGE
|
page.status = Page.LOAD_PAGE
|
||||||
return client
|
return client
|
||||||
.newCall(imageUrlRequest(page))
|
.newCall(imageUrlRequest(page))
|
||||||
.asObservable()
|
.asObservableSuccess()
|
||||||
.map { imageUrlParse(it) }
|
.map { imageUrlParse(it) }
|
||||||
.doOnError { page.status = Page.ERROR }
|
.doOnError { page.status = Page.ERROR }
|
||||||
.onErrorReturn { null }
|
.onErrorReturn { null }
|
||||||
@ -381,13 +378,7 @@ abstract class OnlineSource() : Source {
|
|||||||
*/
|
*/
|
||||||
fun imageResponse(page: Page): Observable<Response> = client
|
fun imageResponse(page: Page): Observable<Response> = client
|
||||||
.newCallWithProgress(imageRequest(page), page)
|
.newCallWithProgress(imageRequest(page), page)
|
||||||
.asObservable()
|
.asObservableSuccess()
|
||||||
.doOnNext {
|
|
||||||
if (!it.isSuccessful) {
|
|
||||||
it.close()
|
|
||||||
throw RuntimeException("Not a valid response")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the request for getting the source image. Override only if it's needed to override
|
* Returns the request for getting the source image. Override only if it's needed to override
|
||||||
|
@ -1,9 +1,27 @@
|
|||||||
package eu.kanade.tachiyomi.ui.base.activity
|
package eu.kanade.tachiyomi.ui.base.activity
|
||||||
|
|
||||||
import android.support.v7.app.AppCompatActivity
|
import android.support.v7.app.AppCompatActivity
|
||||||
|
import eu.kanade.tachiyomi.util.LocaleHelper
|
||||||
|
|
||||||
abstract class BaseActivity : AppCompatActivity(), ActivityMixin {
|
abstract class BaseActivity : AppCompatActivity(), ActivityMixin {
|
||||||
|
|
||||||
|
init {
|
||||||
|
LocaleHelper.updateCfg(this)
|
||||||
|
}
|
||||||
|
|
||||||
override fun getActivity() = this
|
override fun getActivity() = this
|
||||||
|
|
||||||
|
var resumed = false
|
||||||
|
private set
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
resumed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPause() {
|
||||||
|
resumed = false
|
||||||
|
super.onPause()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -3,10 +3,15 @@ package eu.kanade.tachiyomi.ui.base.activity
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import eu.kanade.tachiyomi.App
|
import eu.kanade.tachiyomi.App
|
||||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
||||||
|
import eu.kanade.tachiyomi.util.LocaleHelper
|
||||||
import nucleus.view.NucleusAppCompatActivity
|
import nucleus.view.NucleusAppCompatActivity
|
||||||
|
|
||||||
abstract class BaseRxActivity<P : BasePresenter<*>> : NucleusAppCompatActivity<P>(), ActivityMixin {
|
abstract class BaseRxActivity<P : BasePresenter<*>> : NucleusAppCompatActivity<P>(), ActivityMixin {
|
||||||
|
|
||||||
|
init {
|
||||||
|
LocaleHelper.updateCfg(this)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreate(savedState: Bundle?) {
|
override fun onCreate(savedState: Bundle?) {
|
||||||
val superFactory = presenterFactory
|
val superFactory = presenterFactory
|
||||||
setPresenterFactory {
|
setPresenterFactory {
|
||||||
@ -20,4 +25,17 @@ abstract class BaseRxActivity<P : BasePresenter<*>> : NucleusAppCompatActivity<P
|
|||||||
|
|
||||||
override fun getActivity() = this
|
override fun getActivity() = this
|
||||||
|
|
||||||
|
var resumed = false
|
||||||
|
private set
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
resumed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPause() {
|
||||||
|
resumed = false
|
||||||
|
super.onPause()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -221,6 +221,7 @@ open class CatalogueFragment : BaseRxFragment<CataloguePresenter>(), FlexibleVie
|
|||||||
|
|
||||||
// Setup filters button
|
// Setup filters button
|
||||||
menu.findItem(R.id.action_set_filter).apply {
|
menu.findItem(R.id.action_set_filter).apply {
|
||||||
|
icon.mutate()
|
||||||
if (presenter.source.filters.isEmpty()) {
|
if (presenter.source.filters.isEmpty()) {
|
||||||
isEnabled = false
|
isEnabled = false
|
||||||
icon.alpha = 128
|
icon.alpha = 128
|
||||||
|
@ -21,7 +21,7 @@ import rx.schedulers.Schedulers
|
|||||||
import rx.subjects.PublishSubject
|
import rx.subjects.PublishSubject
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.util.NoSuchElementException
|
import java.util.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Presenter of [CatalogueFragment].
|
* Presenter of [CatalogueFragment].
|
||||||
|
@ -27,7 +27,9 @@ import nucleus.factory.RequiresPresenter
|
|||||||
* UI related actions should be called from here.
|
* UI related actions should be called from here.
|
||||||
*/
|
*/
|
||||||
@RequiresPresenter(CategoryPresenter::class)
|
@RequiresPresenter(CategoryPresenter::class)
|
||||||
class CategoryActivity : BaseRxActivity<CategoryPresenter>(), ActionMode.Callback, FlexibleViewHolder.OnListItemClickListener, OnStartDragListener {
|
class CategoryActivity :
|
||||||
|
BaseRxActivity<CategoryPresenter>(),
|
||||||
|
ActionMode.Callback, FlexibleViewHolder.OnListItemClickListener, OnStartDragListener {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Object used to show actionMode toolbar.
|
* Object used to show actionMode toolbar.
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package eu.kanade.tachiyomi.ui.category
|
package eu.kanade.tachiyomi.ui.category
|
||||||
|
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import com.amulyakhare.textdrawable.util.ColorGenerator
|
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.database.models.Category
|
import eu.kanade.tachiyomi.data.database.models.Category
|
||||||
@ -17,18 +16,10 @@ import java.util.*
|
|||||||
* @param activity activity that created adapter
|
* @param activity activity that created adapter
|
||||||
* @constructor Creates a CategoryAdapter object
|
* @constructor Creates a CategoryAdapter object
|
||||||
*/
|
*/
|
||||||
class CategoryAdapter(private val activity: CategoryActivity) : FlexibleAdapter<CategoryHolder, Category>(), ItemTouchHelperAdapter {
|
class CategoryAdapter(private val activity: CategoryActivity) :
|
||||||
|
FlexibleAdapter<CategoryHolder, Category>(), ItemTouchHelperAdapter {
|
||||||
/**
|
|
||||||
* Generator used to generate circle letter icons
|
|
||||||
*/
|
|
||||||
private val generator: ColorGenerator
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
// Let generator use Material Design colors.
|
|
||||||
// Material design is love, material design is live!
|
|
||||||
generator = ColorGenerator.MATERIAL
|
|
||||||
|
|
||||||
// Set unique id's
|
// Set unique id's
|
||||||
setHasStableIds(true)
|
setHasStableIds(true)
|
||||||
}
|
}
|
||||||
@ -54,7 +45,7 @@ class CategoryAdapter(private val activity: CategoryActivity) : FlexibleAdapter<
|
|||||||
override fun onBindViewHolder(holder: CategoryHolder, position: Int) {
|
override fun onBindViewHolder(holder: CategoryHolder, position: Int) {
|
||||||
// Update holder values.
|
// Update holder values.
|
||||||
val category = getItem(position)
|
val category = getItem(position)
|
||||||
holder.onSetValues(category, generator)
|
holder.onSetValues(category)
|
||||||
|
|
||||||
//When user scrolls this bind the correct selection status
|
//When user scrolls this bind the correct selection status
|
||||||
holder.itemView.isActivated = isSelected(position)
|
holder.itemView.isActivated = isSelected(position)
|
||||||
|
@ -24,7 +24,12 @@ import kotlinx.android.synthetic.main.item_edit_categories.view.*
|
|||||||
*
|
*
|
||||||
* @constructor Create CategoryHolder object
|
* @constructor Create CategoryHolder object
|
||||||
*/
|
*/
|
||||||
class CategoryHolder(view: View, adapter: CategoryAdapter, listener: FlexibleViewHolder.OnListItemClickListener, dragListener: OnStartDragListener) : FlexibleViewHolder(view, adapter, listener) {
|
class CategoryHolder(
|
||||||
|
view: View,
|
||||||
|
adapter: CategoryAdapter,
|
||||||
|
listener: FlexibleViewHolder.OnListItemClickListener,
|
||||||
|
dragListener: OnStartDragListener
|
||||||
|
) : FlexibleViewHolder(view, adapter, listener) {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
// Create round letter image onclick to simulate long click
|
// Create round letter image onclick to simulate long click
|
||||||
@ -46,29 +51,31 @@ class CategoryHolder(view: View, adapter: CategoryAdapter, listener: FlexibleVie
|
|||||||
* Update category item values.
|
* Update category item values.
|
||||||
*
|
*
|
||||||
* @param category category of item.
|
* @param category category of item.
|
||||||
* @param generator generator used to generate circle letter icons.
|
|
||||||
*/
|
*/
|
||||||
fun onSetValues(category: Category, generator: ColorGenerator) {
|
fun onSetValues(category: Category) {
|
||||||
// Set capitalized title.
|
// Set capitalized title.
|
||||||
itemView.title.text = category.name.capitalize()
|
itemView.title.text = category.name.capitalize()
|
||||||
|
|
||||||
// Update circle letter image.
|
// Update circle letter image.
|
||||||
itemView.image.setImageDrawable(getRound(category.name.substring(0, 1).toUpperCase(), generator))
|
itemView.post {
|
||||||
|
itemView.image.setImageDrawable(getRound(category.name.take(1).toUpperCase()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns circle letter image
|
* Returns circle letter image
|
||||||
*
|
*
|
||||||
* @param text first letter of string
|
* @param text first letter of string
|
||||||
* @param generator the generator used to generate circle letter image
|
|
||||||
*/
|
*/
|
||||||
private fun getRound(text: String, generator: ColorGenerator): TextDrawable {
|
private fun getRound(text: String): TextDrawable {
|
||||||
|
val size = Math.min(itemView.image.width, itemView.image.height)
|
||||||
return TextDrawable.builder()
|
return TextDrawable.builder()
|
||||||
.beginConfig()
|
.beginConfig()
|
||||||
|
.width(size)
|
||||||
|
.height(size)
|
||||||
.textColor(Color.WHITE)
|
.textColor(Color.WHITE)
|
||||||
.useFont(Typeface.DEFAULT)
|
.useFont(Typeface.DEFAULT)
|
||||||
.toUpperCase()
|
|
||||||
.endConfig()
|
.endConfig()
|
||||||
.buildRound(text, generator.getColor(text))
|
.buildRound(text, ColorGenerator.MATERIAL.getColor(text))
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -48,8 +48,9 @@ class DownloadHolder(private val view: View) : RecyclerView.ViewHolder(view) {
|
|||||||
* Updates the progress bar of the download.
|
* Updates the progress bar of the download.
|
||||||
*/
|
*/
|
||||||
fun notifyProgress() {
|
fun notifyProgress() {
|
||||||
|
val pages = download.pages ?: return
|
||||||
if (view.download_progress.max == 1) {
|
if (view.download_progress.max == 1) {
|
||||||
view.download_progress.max = download.pages!!.size * 100
|
view.download_progress.max = pages.size * 100
|
||||||
}
|
}
|
||||||
view.download_progress.progress = download.totalProgress
|
view.download_progress.progress = download.totalProgress
|
||||||
}
|
}
|
||||||
@ -58,7 +59,8 @@ class DownloadHolder(private val view: View) : RecyclerView.ViewHolder(view) {
|
|||||||
* Updates the text field of the number of downloaded pages.
|
* Updates the text field of the number of downloaded pages.
|
||||||
*/
|
*/
|
||||||
fun notifyDownloadedPages() {
|
fun notifyDownloadedPages() {
|
||||||
view.download_progress_text.text = "${download.downloadedImages}/${download.pages!!.size}"
|
val pages = download.pages ?: return
|
||||||
|
view.download_progress_text.text = "${download.downloadedImages}/${pages.size}"
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,7 @@ class LibraryCategoryAdapter(val fragment: LibraryCategoryView) :
|
|||||||
/**
|
/**
|
||||||
* The list of manga in this category.
|
* The list of manga in this category.
|
||||||
*/
|
*/
|
||||||
private var mangas: List<Manga>? = null
|
private var mangas: List<Manga> = emptyList()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
setHasStableIds(true)
|
setHasStableIds(true)
|
||||||
@ -37,7 +37,7 @@ class LibraryCategoryAdapter(val fragment: LibraryCategoryView) :
|
|||||||
fun setItems(list: List<Manga>) {
|
fun setItems(list: List<Manga>) {
|
||||||
mItems = list
|
mItems = list
|
||||||
|
|
||||||
// A copy of manga that it's always unfiltered
|
// A copy of manga always unfiltered.
|
||||||
mangas = ArrayList(list)
|
mangas = ArrayList(list)
|
||||||
updateDataSet(null)
|
updateDataSet(null)
|
||||||
}
|
}
|
||||||
@ -58,10 +58,8 @@ class LibraryCategoryAdapter(val fragment: LibraryCategoryView) :
|
|||||||
* @param param the filter. Not used.
|
* @param param the filter. Not used.
|
||||||
*/
|
*/
|
||||||
override fun updateDataSet(param: String?) {
|
override fun updateDataSet(param: String?) {
|
||||||
mangas?.let {
|
filterItems(mangas)
|
||||||
filterItems(it)
|
notifyDataSetChanged()
|
||||||
notifyDataSetChanged()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -3,9 +3,12 @@ package eu.kanade.tachiyomi.ui.library
|
|||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
|
import android.graphics.Color
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.support.design.widget.TabLayout
|
import android.support.design.widget.TabLayout
|
||||||
|
import android.support.v4.graphics.drawable.DrawableCompat
|
||||||
import android.support.v4.view.ViewPager
|
import android.support.v4.view.ViewPager
|
||||||
|
import android.support.v4.widget.DrawerLayout
|
||||||
import android.support.v7.view.ActionMode
|
import android.support.v7.view.ActionMode
|
||||||
import android.support.v7.widget.SearchView
|
import android.support.v7.widget.SearchView
|
||||||
import android.view.*
|
import android.view.*
|
||||||
@ -20,6 +23,7 @@ import eu.kanade.tachiyomi.data.preference.getOrDefault
|
|||||||
import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment
|
import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment
|
||||||
import eu.kanade.tachiyomi.ui.category.CategoryActivity
|
import eu.kanade.tachiyomi.ui.category.CategoryActivity
|
||||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||||
|
import eu.kanade.tachiyomi.util.inflate
|
||||||
import eu.kanade.tachiyomi.util.toast
|
import eu.kanade.tachiyomi.util.toast
|
||||||
import kotlinx.android.synthetic.main.activity_main.*
|
import kotlinx.android.synthetic.main.activity_main.*
|
||||||
import kotlinx.android.synthetic.main.fragment_library.*
|
import kotlinx.android.synthetic.main.fragment_library.*
|
||||||
@ -73,22 +77,36 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
|
|||||||
*/
|
*/
|
||||||
private var selectedCoverManga: Manga? = null
|
private var selectedCoverManga: Manga? = null
|
||||||
|
|
||||||
/**
|
|
||||||
* Status of isFilterDownloaded
|
|
||||||
*/
|
|
||||||
var isFilterDownloaded = false
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Status of isFilterUnread
|
|
||||||
*/
|
|
||||||
var isFilterUnread = false
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Number of manga per row in grid mode.
|
* Number of manga per row in grid mode.
|
||||||
*/
|
*/
|
||||||
var mangaPerRow = 0
|
var mangaPerRow = 0
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigation view containing filter/sort/display items.
|
||||||
|
*/
|
||||||
|
private lateinit var navView: LibraryNavigationView
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Drawer listener to allow swipe only for closing the drawer.
|
||||||
|
*/
|
||||||
|
private val drawerListener by lazy {
|
||||||
|
object : DrawerLayout.SimpleDrawerListener() {
|
||||||
|
override fun onDrawerClosed(drawerView: View) {
|
||||||
|
if (drawerView == navView) {
|
||||||
|
activity.drawer.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, navView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDrawerOpened(drawerView: View) {
|
||||||
|
if (drawerView == navView) {
|
||||||
|
activity.drawer.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, navView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subscription for the number of manga per row.
|
* Subscription for the number of manga per row.
|
||||||
*/
|
*/
|
||||||
@ -123,8 +141,6 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
|
|||||||
override fun onCreate(savedState: Bundle?) {
|
override fun onCreate(savedState: Bundle?) {
|
||||||
super.onCreate(savedState)
|
super.onCreate(savedState)
|
||||||
setHasOptionsMenu(true)
|
setHasOptionsMenu(true)
|
||||||
isFilterDownloaded = preferences.filterDownloaded().get() as Boolean
|
|
||||||
isFilterUnread = preferences.filterUnread().get() as Boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedState: Bundle?): View? {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedState: Bundle?): View? {
|
||||||
@ -146,7 +162,7 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
|
|||||||
if (savedState != null) {
|
if (savedState != null) {
|
||||||
activeCategory = savedState.getInt(CATEGORY_KEY)
|
activeCategory = savedState.getInt(CATEGORY_KEY)
|
||||||
query = savedState.getString(QUERY_KEY)
|
query = savedState.getString(QUERY_KEY)
|
||||||
presenter.searchSubject.onNext(query)
|
presenter.searchSubject.call(query)
|
||||||
if (presenter.selectedMangas.isNotEmpty()) {
|
if (presenter.selectedMangas.isNotEmpty()) {
|
||||||
createActionModeIfNeeded()
|
createActionModeIfNeeded()
|
||||||
}
|
}
|
||||||
@ -159,6 +175,25 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
|
|||||||
.skip(1)
|
.skip(1)
|
||||||
// Set again the adapter to recalculate the covers height
|
// Set again the adapter to recalculate the covers height
|
||||||
.subscribe { reattachAdapter() }
|
.subscribe { reattachAdapter() }
|
||||||
|
|
||||||
|
|
||||||
|
// Inflate and prepare drawer
|
||||||
|
navView = activity.drawer.inflate(R.layout.library_drawer) as LibraryNavigationView
|
||||||
|
activity.drawer.addView(navView)
|
||||||
|
activity.drawer.addDrawerListener(drawerListener)
|
||||||
|
|
||||||
|
navView.post {
|
||||||
|
if (isAdded && !activity.drawer.isDrawerOpen(navView))
|
||||||
|
activity.drawer.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, navView)
|
||||||
|
}
|
||||||
|
|
||||||
|
navView.onGroupClicked = { group ->
|
||||||
|
when (group) {
|
||||||
|
is LibraryNavigationView.FilterGroup -> onFilterChanged()
|
||||||
|
is LibraryNavigationView.SortGroup -> onSortChanged()
|
||||||
|
is LibraryNavigationView.DisplayGroup -> reattachAdapter()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
@ -167,6 +202,8 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
|
activity.drawer.removeDrawerListener(drawerListener)
|
||||||
|
activity.drawer.removeView(navView)
|
||||||
numColumnsSubscription?.unsubscribe()
|
numColumnsSubscription?.unsubscribe()
|
||||||
tabs.setupWithViewPager(null)
|
tabs.setupWithViewPager(null)
|
||||||
tabs.visibility = View.GONE
|
tabs.visibility = View.GONE
|
||||||
@ -182,9 +219,6 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
|
|||||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||||
inflater.inflate(R.menu.library, menu)
|
inflater.inflate(R.menu.library, menu)
|
||||||
|
|
||||||
// Initialize search menu
|
|
||||||
val filterDownloadedItem = menu.findItem(R.id.action_filter_downloaded)
|
|
||||||
val filterUnreadItem = menu.findItem(R.id.action_filter_unread)
|
|
||||||
val searchItem = menu.findItem(R.id.action_search)
|
val searchItem = menu.findItem(R.id.action_search)
|
||||||
val searchView = searchItem.actionView as SearchView
|
val searchView = searchItem.actionView as SearchView
|
||||||
|
|
||||||
@ -194,8 +228,8 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
|
|||||||
searchView.clearFocus()
|
searchView.clearFocus()
|
||||||
}
|
}
|
||||||
|
|
||||||
filterDownloadedItem.isChecked = isFilterDownloaded
|
// Mutate the filter icon because it needs to be tinted and the resource is shared.
|
||||||
filterUnreadItem.isChecked = isFilterUnread
|
menu.findItem(R.id.action_filter).icon.mutate()
|
||||||
|
|
||||||
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
|
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
|
||||||
override fun onQueryTextSubmit(query: String): Boolean {
|
override fun onQueryTextSubmit(query: String): Boolean {
|
||||||
@ -211,35 +245,19 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onPrepareOptionsMenu(menu: Menu) {
|
||||||
|
val filterItem = menu.findItem(R.id.action_filter)
|
||||||
|
|
||||||
|
// Tint icon if there's a filter active
|
||||||
|
val filterColor = if (navView.hasActiveFilters()) Color.rgb(255, 238, 7) else Color.WHITE
|
||||||
|
DrawableCompat.setTint(filterItem.icon, filterColor)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
when (item.itemId) {
|
when (item.itemId) {
|
||||||
R.id.action_filter_unread -> {
|
R.id.action_filter -> {
|
||||||
// Change unread filter status.
|
activity.drawer.openDrawer(Gravity.END)
|
||||||
isFilterUnread = !isFilterUnread
|
|
||||||
// Update settings.
|
|
||||||
preferences.filterUnread().set(isFilterUnread)
|
|
||||||
// Apply filter.
|
|
||||||
onFilterCheckboxChanged()
|
|
||||||
}
|
}
|
||||||
R.id.action_filter_downloaded -> {
|
|
||||||
// Change downloaded filter status.
|
|
||||||
isFilterDownloaded = !isFilterDownloaded
|
|
||||||
// Update settings.
|
|
||||||
preferences.filterDownloaded().set(isFilterDownloaded)
|
|
||||||
// Apply filter.
|
|
||||||
onFilterCheckboxChanged()
|
|
||||||
}
|
|
||||||
R.id.action_filter_empty -> {
|
|
||||||
// Remove filter status.
|
|
||||||
isFilterUnread = false
|
|
||||||
isFilterDownloaded = false
|
|
||||||
// Update settings.
|
|
||||||
preferences.filterUnread().set(isFilterUnread)
|
|
||||||
preferences.filterDownloaded().set(isFilterDownloaded)
|
|
||||||
// Apply filter
|
|
||||||
onFilterCheckboxChanged()
|
|
||||||
}
|
|
||||||
R.id.action_library_display_mode -> swapDisplayMode()
|
|
||||||
R.id.action_update_library -> {
|
R.id.action_update_library -> {
|
||||||
LibraryUpdateService.start(activity)
|
LibraryUpdateService.start(activity)
|
||||||
}
|
}
|
||||||
@ -254,19 +272,18 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Applies filter change
|
* Called when a filter is changed.
|
||||||
*/
|
*/
|
||||||
private fun onFilterCheckboxChanged() {
|
private fun onFilterChanged() {
|
||||||
presenter.resubscribeLibrary()
|
presenter.requestFilterUpdate()
|
||||||
activity.supportInvalidateOptionsMenu()
|
activity.supportInvalidateOptionsMenu()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Swap display mode
|
* Called when the sorting mode is changed.
|
||||||
*/
|
*/
|
||||||
private fun swapDisplayMode() {
|
private fun onSortChanged() {
|
||||||
presenter.swapDisplayMode()
|
presenter.requestSortUpdate()
|
||||||
reattachAdapter()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -302,7 +319,7 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
|
|||||||
|
|
||||||
// Notify the subject the query has changed.
|
// Notify the subject the query has changed.
|
||||||
if (isResumed) {
|
if (isResumed) {
|
||||||
presenter.searchSubject.onNext(query)
|
presenter.searchSubject.call(query)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -330,7 +347,7 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
|
|||||||
view_pager.post { if (isAdded) tabs.setScrollPosition(view_pager.currentItem, 0f, true) }
|
view_pager.post { if (isAdded) tabs.setScrollPosition(view_pager.currentItem, 0f, true) }
|
||||||
|
|
||||||
// Send the manga map to child fragments after the adapter is updated.
|
// Send the manga map to child fragments after the adapter is updated.
|
||||||
presenter.libraryMangaSubject.onNext(LibraryMangaEvent(mangaMap))
|
presenter.libraryMangaSubject.call(LibraryMangaEvent(mangaMap))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -0,0 +1,191 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.library
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
|
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
||||||
|
import eu.kanade.tachiyomi.widget.ExtendedNavigationView
|
||||||
|
import eu.kanade.tachiyomi.widget.ExtendedNavigationView.Item.MultiSort.Companion.SORT_ASC
|
||||||
|
import eu.kanade.tachiyomi.widget.ExtendedNavigationView.Item.MultiSort.Companion.SORT_DESC
|
||||||
|
import eu.kanade.tachiyomi.widget.ExtendedNavigationView.Item.MultiSort.Companion.SORT_NONE
|
||||||
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The navigation view shown in a drawer with the different options to show the library.
|
||||||
|
*/
|
||||||
|
class LibraryNavigationView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null)
|
||||||
|
: ExtendedNavigationView(context, attrs) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Preferences helper.
|
||||||
|
*/
|
||||||
|
private val preferences: PreferencesHelper by injectLazy()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of groups shown in the view.
|
||||||
|
*/
|
||||||
|
private val groups = listOf(FilterGroup(), SortGroup(), DisplayGroup())
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adapter instance.
|
||||||
|
*/
|
||||||
|
private val adapter = Adapter(groups.map { it.createItems() }.flatten())
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Click listener to notify the parent fragment when an item from a group is clicked.
|
||||||
|
*/
|
||||||
|
var onGroupClicked: (Group) -> Unit = {}
|
||||||
|
|
||||||
|
init {
|
||||||
|
recycler.adapter = adapter
|
||||||
|
|
||||||
|
groups.forEach { it.initModels() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if there's at least one filter from [FilterGroup] active.
|
||||||
|
*/
|
||||||
|
fun hasActiveFilters(): Boolean {
|
||||||
|
return (groups[0] as FilterGroup).items.any { it.checked }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adapter of the recycler view.
|
||||||
|
*/
|
||||||
|
inner class Adapter(items: List<Item>) : ExtendedNavigationView.Adapter(items) {
|
||||||
|
|
||||||
|
override fun onItemClicked(item: Item) {
|
||||||
|
if (item is GroupedItem) {
|
||||||
|
item.group.onItemClicked(item)
|
||||||
|
onGroupClicked(item.group)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters group (unread, downloaded, ...).
|
||||||
|
*/
|
||||||
|
inner class FilterGroup : Group {
|
||||||
|
|
||||||
|
private val downloaded = Item.CheckboxGroup(R.string.action_filter_downloaded, this)
|
||||||
|
|
||||||
|
private val unread = Item.CheckboxGroup(R.string.action_filter_unread, this)
|
||||||
|
|
||||||
|
override val items = listOf(downloaded, unread)
|
||||||
|
|
||||||
|
override val header = Item.Header(R.string.action_filter)
|
||||||
|
|
||||||
|
override val footer = Item.Separator()
|
||||||
|
|
||||||
|
override fun initModels() {
|
||||||
|
downloaded.checked = preferences.filterDownloaded().getOrDefault()
|
||||||
|
unread.checked = preferences.filterUnread().getOrDefault()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onItemClicked(item: Item) {
|
||||||
|
item as Item.CheckboxGroup
|
||||||
|
item.checked = !item.checked
|
||||||
|
when (item) {
|
||||||
|
downloaded -> preferences.filterDownloaded().set(item.checked)
|
||||||
|
unread -> preferences.filterUnread().set(item.checked)
|
||||||
|
}
|
||||||
|
|
||||||
|
adapter.notifyItemChanged(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sorting group (alphabetically, by last read, ...) and ascending or descending.
|
||||||
|
*/
|
||||||
|
inner class SortGroup : Group {
|
||||||
|
|
||||||
|
private val alphabetically = Item.MultiSort(R.string.action_sort_alpha, this)
|
||||||
|
|
||||||
|
private val lastRead = Item.MultiSort(R.string.action_sort_last_read, this)
|
||||||
|
|
||||||
|
private val lastUpdated = Item.MultiSort(R.string.action_sort_last_updated, this)
|
||||||
|
|
||||||
|
private val unread = Item.MultiSort(R.string.action_filter_unread, this)
|
||||||
|
|
||||||
|
override val items = listOf(alphabetically, lastRead, lastUpdated, unread)
|
||||||
|
|
||||||
|
override val header = Item.Header(R.string.action_sort)
|
||||||
|
|
||||||
|
override val footer = Item.Separator()
|
||||||
|
|
||||||
|
override fun initModels() {
|
||||||
|
val sorting = preferences.librarySortingMode().getOrDefault()
|
||||||
|
val order = if (preferences.librarySortingAscending().getOrDefault())
|
||||||
|
SORT_ASC else SORT_DESC
|
||||||
|
|
||||||
|
alphabetically.state = if (sorting == LibrarySort.ALPHA) order else SORT_NONE
|
||||||
|
lastRead.state = if (sorting == LibrarySort.LAST_READ) order else SORT_NONE
|
||||||
|
lastUpdated.state = if (sorting == LibrarySort.LAST_UPDATED) order else SORT_NONE
|
||||||
|
unread.state = if (sorting == LibrarySort.UNREAD) order else SORT_NONE
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onItemClicked(item: Item) {
|
||||||
|
item as Item.MultiStateGroup
|
||||||
|
val prevState = item.state
|
||||||
|
|
||||||
|
item.group.items.forEach { (it as Item.MultiStateGroup).state = SORT_NONE }
|
||||||
|
item.state = when (prevState) {
|
||||||
|
SORT_NONE -> SORT_ASC
|
||||||
|
SORT_ASC -> SORT_DESC
|
||||||
|
SORT_DESC -> SORT_ASC
|
||||||
|
else -> throw Exception("Unknown state")
|
||||||
|
}
|
||||||
|
|
||||||
|
preferences.librarySortingMode().set(when (item) {
|
||||||
|
alphabetically -> LibrarySort.ALPHA
|
||||||
|
lastRead -> LibrarySort.LAST_READ
|
||||||
|
lastUpdated -> LibrarySort.LAST_UPDATED
|
||||||
|
unread -> LibrarySort.UNREAD
|
||||||
|
else -> throw Exception("Unknown sorting")
|
||||||
|
})
|
||||||
|
preferences.librarySortingAscending().set(if (item.state == SORT_ASC) true else false)
|
||||||
|
|
||||||
|
item.group.items.forEach { adapter.notifyItemChanged(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display group, to show the library as a list or a grid.
|
||||||
|
*/
|
||||||
|
inner class DisplayGroup : Group {
|
||||||
|
|
||||||
|
private val grid = Item.Radio(R.string.action_display_grid, this)
|
||||||
|
|
||||||
|
private val list = Item.Radio(R.string.action_display_list, this)
|
||||||
|
|
||||||
|
override val items = listOf(grid, list)
|
||||||
|
|
||||||
|
override val header = Item.Header(R.string.action_display)
|
||||||
|
|
||||||
|
override val footer = null
|
||||||
|
|
||||||
|
override fun initModels() {
|
||||||
|
val asList = preferences.libraryAsList().getOrDefault()
|
||||||
|
grid.checked = !asList
|
||||||
|
list.checked = asList
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onItemClicked(item: Item) {
|
||||||
|
item as Item.Radio
|
||||||
|
if (item.checked) return
|
||||||
|
|
||||||
|
item.group.items.forEach { (it as Item.Radio).checked = false }
|
||||||
|
item.checked = true
|
||||||
|
|
||||||
|
preferences.libraryAsList().set(if (item == list) true else false)
|
||||||
|
|
||||||
|
item.group.items.forEach { adapter.notifyItemChanged(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -2,6 +2,9 @@ package eu.kanade.tachiyomi.ui.library
|
|||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Pair
|
import android.util.Pair
|
||||||
|
import com.hippo.unifile.UniFile
|
||||||
|
import com.jakewharton.rxrelay.BehaviorRelay
|
||||||
|
import com.jakewharton.rxrelay.PublishRelay
|
||||||
import eu.kanade.tachiyomi.data.cache.CoverCache
|
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||||
import eu.kanade.tachiyomi.data.database.models.Category
|
import eu.kanade.tachiyomi.data.database.models.Category
|
||||||
@ -12,11 +15,12 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
|||||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
||||||
import eu.kanade.tachiyomi.data.source.SourceManager
|
import eu.kanade.tachiyomi.data.source.SourceManager
|
||||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
||||||
|
import eu.kanade.tachiyomi.util.combineLatest
|
||||||
|
import eu.kanade.tachiyomi.util.isNullOrUnsubscribed
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
|
import rx.Subscription
|
||||||
import rx.android.schedulers.AndroidSchedulers
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
import rx.schedulers.Schedulers
|
import rx.schedulers.Schedulers
|
||||||
import rx.subjects.BehaviorSubject
|
|
||||||
import rx.subjects.PublishSubject
|
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
@ -27,6 +31,31 @@ import java.util.*
|
|||||||
*/
|
*/
|
||||||
class LibraryPresenter : BasePresenter<LibraryFragment>() {
|
class LibraryPresenter : BasePresenter<LibraryFragment>() {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Database.
|
||||||
|
*/
|
||||||
|
private val db: DatabaseHelper by injectLazy()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Preferences.
|
||||||
|
*/
|
||||||
|
private val preferences: PreferencesHelper by injectLazy()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cover cache.
|
||||||
|
*/
|
||||||
|
private val coverCache: CoverCache by injectLazy()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Source manager.
|
||||||
|
*/
|
||||||
|
private val sourceManager: SourceManager by injectLazy()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Download manager.
|
||||||
|
*/
|
||||||
|
private val downloadManager: DownloadManager by injectLazy()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Categories of the library.
|
* Categories of the library.
|
||||||
*/
|
*/
|
||||||
@ -40,61 +69,139 @@ class LibraryPresenter : BasePresenter<LibraryFragment>() {
|
|||||||
/**
|
/**
|
||||||
* Search query of the library.
|
* Search query of the library.
|
||||||
*/
|
*/
|
||||||
val searchSubject: BehaviorSubject<String> = BehaviorSubject.create()
|
val searchSubject: BehaviorRelay<String> = BehaviorRelay.create()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subject to notify the library's viewpager for updates.
|
* Subject to notify the library's viewpager for updates.
|
||||||
*/
|
*/
|
||||||
val libraryMangaSubject: BehaviorSubject<LibraryMangaEvent> = BehaviorSubject.create()
|
val libraryMangaSubject: BehaviorRelay<LibraryMangaEvent> = BehaviorRelay.create()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subject to notify the UI of selection updates.
|
* Subject to notify the UI of selection updates.
|
||||||
*/
|
*/
|
||||||
val selectionSubject: PublishSubject<LibrarySelectionEvent> = PublishSubject.create()
|
val selectionSubject: PublishRelay<LibrarySelectionEvent> = PublishRelay.create()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Database.
|
* Relay used to apply the UI filters to the last emission of the library.
|
||||||
*/
|
*/
|
||||||
val db: DatabaseHelper by injectLazy()
|
private val filterTriggerRelay = BehaviorRelay.create(Unit)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Preferences.
|
* Relay used to apply the selected sorting method to the last emission of the library.
|
||||||
*/
|
*/
|
||||||
val preferences: PreferencesHelper by injectLazy()
|
private val sortTriggerRelay = BehaviorRelay.create(Unit)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cover cache.
|
* Library subscription.
|
||||||
*/
|
*/
|
||||||
val coverCache: CoverCache by injectLazy()
|
private var librarySubscription: Subscription? = null
|
||||||
|
|
||||||
/**
|
|
||||||
* Source manager.
|
|
||||||
*/
|
|
||||||
val sourceManager: SourceManager by injectLazy()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Download manager.
|
|
||||||
*/
|
|
||||||
val downloadManager: DownloadManager by injectLazy()
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
/**
|
|
||||||
* Id of the restartable that listens for library updates.
|
|
||||||
*/
|
|
||||||
const val GET_LIBRARY = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreate(savedState: Bundle?) {
|
override fun onCreate(savedState: Bundle?) {
|
||||||
super.onCreate(savedState)
|
super.onCreate(savedState)
|
||||||
|
subscribeLibrary()
|
||||||
|
}
|
||||||
|
|
||||||
restartableLatestCache(GET_LIBRARY,
|
/**
|
||||||
{ getLibraryObservable() },
|
* Subscribes to library if needed.
|
||||||
{ view, pair -> view.onNextLibraryUpdate(pair.first, pair.second) })
|
*/
|
||||||
|
fun subscribeLibrary() {
|
||||||
|
if (librarySubscription.isNullOrUnsubscribed()) {
|
||||||
|
librarySubscription = getLibraryObservable()
|
||||||
|
.combineLatest(filterTriggerRelay.observeOn(Schedulers.io()),
|
||||||
|
{ lib, tick -> Pair(lib.first, applyFilters(lib.second)) })
|
||||||
|
.combineLatest(sortTriggerRelay.observeOn(Schedulers.io()),
|
||||||
|
{ lib, tick -> Pair(lib.first, applySort(lib.second)) })
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribeLatestCache({ view, pair ->
|
||||||
|
view.onNextLibraryUpdate(pair.first, pair.second)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (savedState == null) {
|
/**
|
||||||
start(GET_LIBRARY)
|
* Applies library filters to the given map of manga.
|
||||||
|
*
|
||||||
|
* @param map the map to filter.
|
||||||
|
*/
|
||||||
|
private fun applyFilters(map: Map<Int, List<Manga>>): Map<Int, List<Manga>> {
|
||||||
|
// Cached list of downloaded manga directories given a source id.
|
||||||
|
val mangaDirectories = mutableMapOf<Int, Array<UniFile>>()
|
||||||
|
|
||||||
|
// Cached list of downloaded chapter directories for a manga.
|
||||||
|
val chapterDirectories = mutableMapOf<Long, Boolean>()
|
||||||
|
|
||||||
|
val filterDownloaded = preferences.filterDownloaded().getOrDefault()
|
||||||
|
|
||||||
|
val filterUnread = preferences.filterUnread().getOrDefault()
|
||||||
|
|
||||||
|
val filterFn: (Manga) -> Boolean = f@ { manga ->
|
||||||
|
// Filter out manga without source.
|
||||||
|
val source = sourceManager.get(manga.source) ?: return@f false
|
||||||
|
|
||||||
|
// Filter when there isn't unread chapters.
|
||||||
|
if (filterUnread && manga.unread == 0) {
|
||||||
|
return@f false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter when the download directory doesn't exist or is null.
|
||||||
|
if (filterDownloaded) {
|
||||||
|
val mangaDirs = mangaDirectories.getOrPut(source.id) {
|
||||||
|
downloadManager.findSourceDir(source)?.listFiles() ?: emptyArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
val mangaDirName = downloadManager.getMangaDirName(manga)
|
||||||
|
val mangaDir = mangaDirs.find { it.name == mangaDirName } ?: return@f false
|
||||||
|
|
||||||
|
val hasDirs = chapterDirectories.getOrPut(manga.id!!) {
|
||||||
|
(mangaDir.listFiles() ?: emptyArray()).isNotEmpty()
|
||||||
|
}
|
||||||
|
if (!hasDirs) {
|
||||||
|
return@f false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return map.mapValues { entry -> entry.value.filter(filterFn) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies library sorting to the given map of manga.
|
||||||
|
*
|
||||||
|
* @param map the map to sort.
|
||||||
|
*/
|
||||||
|
private fun applySort(map: Map<Int, List<Manga>>): Map<Int, List<Manga>> {
|
||||||
|
val sortingMode = preferences.librarySortingMode().getOrDefault()
|
||||||
|
|
||||||
|
// TODO lazy initialization in kotlin 1.1
|
||||||
|
var lastReadManga: Map<Long, Int>? = null
|
||||||
|
if (sortingMode == LibrarySort.LAST_READ) {
|
||||||
|
var counter = 0
|
||||||
|
lastReadManga = db.getLastReadManga().executeAsBlocking()
|
||||||
|
.associate { it.id!! to counter++ }
|
||||||
|
}
|
||||||
|
|
||||||
|
val sortFn: (Manga, Manga) -> Int = { manga1, manga2 ->
|
||||||
|
when (sortingMode) {
|
||||||
|
LibrarySort.ALPHA -> manga1.title.compareTo(manga2.title)
|
||||||
|
LibrarySort.LAST_READ -> {
|
||||||
|
// Get index of manga, set equal to list if size unknown.
|
||||||
|
val manga1LastRead = lastReadManga!![manga1.id!!] ?: lastReadManga!!.size
|
||||||
|
val manga2LastRead = lastReadManga!![manga2.id!!] ?: lastReadManga!!.size
|
||||||
|
manga1LastRead.compareTo(manga2LastRead)
|
||||||
|
}
|
||||||
|
LibrarySort.LAST_UPDATED -> manga2.last_update.compareTo(manga1.last_update)
|
||||||
|
LibrarySort.UNREAD -> manga1.unread.compareTo(manga2.unread)
|
||||||
|
else -> throw Exception("Unknown sorting mode")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val comparator = if (preferences.librarySortingAscending().getOrDefault())
|
||||||
|
Comparator(sortFn)
|
||||||
|
else
|
||||||
|
Collections.reverseOrder(sortFn)
|
||||||
|
|
||||||
|
return map.mapValues { entry -> entry.value.sortedWith(comparator) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -102,7 +209,7 @@ class LibraryPresenter : BasePresenter<LibraryFragment>() {
|
|||||||
*
|
*
|
||||||
* @return an observable of the categories and its manga.
|
* @return an observable of the categories and its manga.
|
||||||
*/
|
*/
|
||||||
fun getLibraryObservable(): Observable<Pair<List<Category>, Map<Int, List<Manga>>>> {
|
private fun getLibraryObservable(): Observable<Pair<List<Category>, Map<Int, List<Manga>>>> {
|
||||||
return Observable.combineLatest(getCategoriesObservable(), getLibraryMangasObservable(),
|
return Observable.combineLatest(getCategoriesObservable(), getLibraryMangasObservable(),
|
||||||
{ dbCategories, libraryManga ->
|
{ dbCategories, libraryManga ->
|
||||||
val categories = if (libraryManga.containsKey(0))
|
val categories = if (libraryManga.containsKey(0))
|
||||||
@ -113,7 +220,6 @@ class LibraryPresenter : BasePresenter<LibraryFragment>() {
|
|||||||
this.categories = categories
|
this.categories = categories
|
||||||
Pair(categories, libraryManga)
|
Pair(categories, libraryManga)
|
||||||
})
|
})
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -121,7 +227,7 @@ class LibraryPresenter : BasePresenter<LibraryFragment>() {
|
|||||||
*
|
*
|
||||||
* @return an observable of the categories.
|
* @return an observable of the categories.
|
||||||
*/
|
*/
|
||||||
fun getCategoriesObservable(): Observable<List<Category>> {
|
private fun getCategoriesObservable(): Observable<List<Category>> {
|
||||||
return db.getCategories().asRxObservable()
|
return db.getCategories().asRxObservable()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,76 +237,23 @@ class LibraryPresenter : BasePresenter<LibraryFragment>() {
|
|||||||
* @return an observable containing a map with the category id as key and a list of manga as the
|
* @return an observable containing a map with the category id as key and a list of manga as the
|
||||||
* value.
|
* value.
|
||||||
*/
|
*/
|
||||||
fun getLibraryMangasObservable(): Observable<Map<Int, List<Manga>>> {
|
private fun getLibraryMangasObservable(): Observable<Map<Int, List<Manga>>> {
|
||||||
return db.getLibraryMangas().asRxObservable()
|
return db.getLibraryMangas().asRxObservable()
|
||||||
.flatMap { mangas ->
|
.map { list -> list.groupBy { it.category } }
|
||||||
Observable.from(mangas)
|
|
||||||
// Filter library by options
|
|
||||||
.filter { filterManga(it) }
|
|
||||||
.groupBy { it.category }
|
|
||||||
.flatMap { group -> group.toList().map { Pair(group.key, it) } }
|
|
||||||
.toMap({ it.first }, { it.second })
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resubscribes to library if needed.
|
* Requests the library to be filtered.
|
||||||
*/
|
*/
|
||||||
fun subscribeLibrary() {
|
fun requestFilterUpdate() {
|
||||||
if (isUnsubscribed(GET_LIBRARY)) {
|
filterTriggerRelay.call(Unit)
|
||||||
start(GET_LIBRARY)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resubscribes to library.
|
* Requests the library to be sorted.
|
||||||
*/
|
*/
|
||||||
fun resubscribeLibrary() {
|
fun requestSortUpdate() {
|
||||||
start(GET_LIBRARY)
|
sortTriggerRelay.call(Unit)
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Filters an entry of the library.
|
|
||||||
*
|
|
||||||
* @param manga a favorite manga from the database.
|
|
||||||
* @return true if the entry is included, false otherwise.
|
|
||||||
*/
|
|
||||||
fun filterManga(manga: Manga): Boolean {
|
|
||||||
// Filter out manga without source
|
|
||||||
val source = sourceManager.get(manga.source) ?: return false
|
|
||||||
|
|
||||||
val prefFilterDownloaded = preferences.filterDownloaded().getOrDefault()
|
|
||||||
val prefFilterUnread = preferences.filterUnread().getOrDefault()
|
|
||||||
|
|
||||||
// Check if filter option is selected
|
|
||||||
if (prefFilterDownloaded || prefFilterUnread) {
|
|
||||||
|
|
||||||
// Does it have downloaded chapters.
|
|
||||||
var hasDownloaded = false
|
|
||||||
var hasUnread = false
|
|
||||||
|
|
||||||
if (prefFilterUnread) {
|
|
||||||
// Does it have unread chapters.
|
|
||||||
hasUnread = manga.unread > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
if (prefFilterDownloaded) {
|
|
||||||
val mangaDir = downloadManager.findMangaDir(source, manga)
|
|
||||||
|
|
||||||
if (mangaDir != null) {
|
|
||||||
hasDownloaded = mangaDir.listFiles()?.any { it.isDirectory } ?: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return correct filter status
|
|
||||||
if (prefFilterDownloaded && prefFilterUnread) {
|
|
||||||
return (hasDownloaded && hasUnread)
|
|
||||||
} else {
|
|
||||||
return (hasDownloaded || hasUnread)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -208,7 +261,7 @@ class LibraryPresenter : BasePresenter<LibraryFragment>() {
|
|||||||
*/
|
*/
|
||||||
fun onOpenManga() {
|
fun onOpenManga() {
|
||||||
// Avoid further db updates for the library when it's not needed
|
// Avoid further db updates for the library when it's not needed
|
||||||
stop(GET_LIBRARY)
|
librarySubscription?.let { remove(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -220,10 +273,10 @@ class LibraryPresenter : BasePresenter<LibraryFragment>() {
|
|||||||
fun setSelection(manga: Manga, selected: Boolean) {
|
fun setSelection(manga: Manga, selected: Boolean) {
|
||||||
if (selected) {
|
if (selected) {
|
||||||
selectedMangas.add(manga)
|
selectedMangas.add(manga)
|
||||||
selectionSubject.onNext(LibrarySelectionEvent.Selected(manga))
|
selectionSubject.call(LibrarySelectionEvent.Selected(manga))
|
||||||
} else {
|
} else {
|
||||||
selectedMangas.remove(manga)
|
selectedMangas.remove(manga)
|
||||||
selectionSubject.onNext(LibrarySelectionEvent.Unselected(manga))
|
selectionSubject.call(LibrarySelectionEvent.Unselected(manga))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -232,7 +285,7 @@ class LibraryPresenter : BasePresenter<LibraryFragment>() {
|
|||||||
*/
|
*/
|
||||||
fun clearSelections() {
|
fun clearSelections() {
|
||||||
selectedMangas.clear()
|
selectedMangas.clear()
|
||||||
selectionSubject.onNext(LibrarySelectionEvent.Cleared())
|
selectionSubject.call(LibrarySelectionEvent.Cleared())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -296,12 +349,4 @@ class LibraryPresenter : BasePresenter<LibraryFragment>() {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Changes the active display mode.
|
|
||||||
*/
|
|
||||||
fun swapDisplayMode() {
|
|
||||||
val displayAsList = preferences.libraryAsList().getOrDefault()
|
|
||||||
preferences.libraryAsList().set(!displayAsList)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,10 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.library
|
||||||
|
|
||||||
|
object LibrarySort {
|
||||||
|
|
||||||
|
const val ALPHA = 0
|
||||||
|
const val LAST_READ = 1
|
||||||
|
const val LAST_UPDATED = 2
|
||||||
|
const val UNREAD = 3
|
||||||
|
|
||||||
|
}
|
@ -94,7 +94,9 @@ class MainActivity : BaseActivity() {
|
|||||||
override fun onBackPressed() {
|
override fun onBackPressed() {
|
||||||
val fragment = supportFragmentManager.findFragmentById(R.id.frame_container)
|
val fragment = supportFragmentManager.findFragmentById(R.id.frame_container)
|
||||||
if (fragment != null && fragment.tag.toInt() != startScreenId) {
|
if (fragment != null && fragment.tag.toInt() != startScreenId) {
|
||||||
setSelectedDrawerItem(startScreenId)
|
if (resumed) {
|
||||||
|
setSelectedDrawerItem(startScreenId)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
super.onBackPressed()
|
super.onBackPressed()
|
||||||
}
|
}
|
||||||
@ -110,6 +112,8 @@ class MainActivity : BaseActivity() {
|
|||||||
} else if (resultCode and SettingsActivity.FLAG_THEME_CHANGED != 0) {
|
} else if (resultCode and SettingsActivity.FLAG_THEME_CHANGED != 0) {
|
||||||
// Delay activity recreation to avoid fragment leaks.
|
// Delay activity recreation to avoid fragment leaks.
|
||||||
nav_view.post { recreate() }
|
nav_view.post { recreate() }
|
||||||
|
} else if (resultCode and SettingsActivity.FLAG_LANG_CHANGED != 0) {
|
||||||
|
nav_view.post { recreate() }
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
super.onActivityResult(requestCode, resultCode, data)
|
super.onActivityResult(requestCode, resultCode, data)
|
||||||
|
@ -13,6 +13,7 @@ import eu.kanade.tachiyomi.ui.manga.chapter.ChaptersFragment
|
|||||||
import eu.kanade.tachiyomi.ui.manga.info.MangaInfoFragment
|
import eu.kanade.tachiyomi.ui.manga.info.MangaInfoFragment
|
||||||
import eu.kanade.tachiyomi.ui.manga.myanimelist.MyAnimeListFragment
|
import eu.kanade.tachiyomi.ui.manga.myanimelist.MyAnimeListFragment
|
||||||
import eu.kanade.tachiyomi.util.SharedData
|
import eu.kanade.tachiyomi.util.SharedData
|
||||||
|
import eu.kanade.tachiyomi.util.toast
|
||||||
import kotlinx.android.synthetic.main.activity_manga.*
|
import kotlinx.android.synthetic.main.activity_manga.*
|
||||||
import kotlinx.android.synthetic.main.toolbar.*
|
import kotlinx.android.synthetic.main.toolbar.*
|
||||||
import nucleus.factory.RequiresPresenter
|
import nucleus.factory.RequiresPresenter
|
||||||
@ -50,12 +51,19 @@ class MangaActivity : BaseRxActivity<MangaPresenter>() {
|
|||||||
|
|
||||||
val fromLauncher = intent.getBooleanExtra(FROM_LAUNCHER_EXTRA, false)
|
val fromLauncher = intent.getBooleanExtra(FROM_LAUNCHER_EXTRA, false)
|
||||||
|
|
||||||
//Remove any current manga if we are launching from launcher
|
// Remove any current manga if we are launching from launcher
|
||||||
if(fromLauncher) SharedData.remove(MangaEvent::class.java)
|
if (fromLauncher) SharedData.remove(MangaEvent::class.java)
|
||||||
|
|
||||||
presenter.setMangaEvent(SharedData.getOrPut(MangaEvent::class.java) {
|
presenter.setMangaEvent(SharedData.getOrPut(MangaEvent::class.java) {
|
||||||
val id = intent.getLongExtra(MANGA_EXTRA, 0)
|
val id = intent.getLongExtra(MANGA_EXTRA, 0)
|
||||||
MangaEvent(presenter.db.getManga(id).executeAsBlocking()!!)
|
val dbManga = presenter.db.getManga(id).executeAsBlocking()
|
||||||
|
if (dbManga != null) {
|
||||||
|
MangaEvent(dbManga)
|
||||||
|
} else {
|
||||||
|
toast(R.string.manga_not_in_db)
|
||||||
|
finish()
|
||||||
|
return
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
setupToolbar(toolbar)
|
setupToolbar(toolbar)
|
||||||
|
@ -394,7 +394,8 @@ class ChaptersFragment : BaseRxFragment<ChaptersPresenter>(), ActionMode.Callbac
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun dismissDeletingDialog() {
|
fun dismissDeletingDialog() {
|
||||||
(childFragmentManager.findFragmentByTag(DeletingChaptersDialog.TAG) as? DialogFragment)?.dismiss()
|
(childFragmentManager.findFragmentByTag(DeletingChaptersDialog.TAG) as? DialogFragment)
|
||||||
|
?.dismissAllowingStateLoss()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onListItemClick(position: Int): Boolean {
|
override fun onListItemClick(position: Int): Boolean {
|
||||||
|
@ -189,7 +189,7 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() {
|
|||||||
KeyEvent.KEYCODE_VOLUME_DOWN -> {
|
KeyEvent.KEYCODE_VOLUME_DOWN -> {
|
||||||
if (volumeKeysEnabled) {
|
if (volumeKeysEnabled) {
|
||||||
if (event.action == KeyEvent.ACTION_UP) {
|
if (event.action == KeyEvent.ACTION_UP) {
|
||||||
viewer?.moveToNext()
|
viewer?.moveDown()
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -197,7 +197,7 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() {
|
|||||||
KeyEvent.KEYCODE_VOLUME_UP -> {
|
KeyEvent.KEYCODE_VOLUME_UP -> {
|
||||||
if (volumeKeysEnabled) {
|
if (volumeKeysEnabled) {
|
||||||
if (event.action == KeyEvent.ACTION_UP) {
|
if (event.action == KeyEvent.ACTION_UP) {
|
||||||
viewer?.moveToPrevious()
|
viewer?.moveUp()
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -210,12 +210,15 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() {
|
|||||||
override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean {
|
override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean {
|
||||||
if (!isFinishing) {
|
if (!isFinishing) {
|
||||||
when (keyCode) {
|
when (keyCode) {
|
||||||
KeyEvent.KEYCODE_DPAD_RIGHT -> viewer?.moveToNext()
|
KeyEvent.KEYCODE_DPAD_RIGHT -> viewer?.moveRight()
|
||||||
KeyEvent.KEYCODE_DPAD_LEFT -> viewer?.moveToPrevious()
|
KeyEvent.KEYCODE_DPAD_LEFT -> viewer?.moveLeft()
|
||||||
|
KeyEvent.KEYCODE_DPAD_DOWN -> viewer?.moveDown()
|
||||||
|
KeyEvent.KEYCODE_DPAD_UP -> viewer?.moveUp()
|
||||||
KeyEvent.KEYCODE_MENU -> toggleMenu()
|
KeyEvent.KEYCODE_MENU -> toggleMenu()
|
||||||
|
else -> return super.onKeyUp(keyCode, event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return super.onKeyUp(keyCode, event)
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onChapterError(error: Throwable) {
|
fun onChapterError(error: Throwable) {
|
||||||
@ -224,14 +227,14 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() {
|
|||||||
toast(error.message)
|
toast(error.message)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onLongPress(page: Page) {
|
fun onLongClick(page: Page) {
|
||||||
MaterialDialog.Builder(this)
|
MaterialDialog.Builder(this)
|
||||||
.title(getString(R.string.options))
|
.title(getString(R.string.options))
|
||||||
.items(R.array.reader_image_options)
|
.items(R.array.reader_image_options)
|
||||||
.itemsIds(R.array.reader_image_options_values)
|
.itemsIds(R.array.reader_image_options_values)
|
||||||
.itemsCallback { materialDialog, view, i, charSequence ->
|
.itemsCallback { materialDialog, view, i, charSequence ->
|
||||||
when (i) {
|
when (i) {
|
||||||
0 -> presenter.setCover(page)
|
0 -> setImageAsCover(page)
|
||||||
1 -> shareImage(page)
|
1 -> shareImage(page)
|
||||||
2 -> presenter.savePage(page)
|
2 -> presenter.savePage(page)
|
||||||
}
|
}
|
||||||
@ -313,14 +316,14 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() {
|
|||||||
val mangaViewer = if (manga.viewer == 0) preferences.defaultViewer() else manga.viewer
|
val mangaViewer = if (manga.viewer == 0) preferences.defaultViewer() else manga.viewer
|
||||||
|
|
||||||
// Try to reuse the viewer using its tag
|
// Try to reuse the viewer using its tag
|
||||||
var fragment: BaseReader? = supportFragmentManager.findFragmentByTag(manga.viewer.toString()) as? BaseReader
|
var fragment = supportFragmentManager.findFragmentByTag(manga.viewer.toString()) as? BaseReader
|
||||||
if (fragment == null) {
|
if (fragment == null) {
|
||||||
// Create a new viewer
|
// Create a new viewer
|
||||||
when (mangaViewer) {
|
fragment = when (mangaViewer) {
|
||||||
RIGHT_TO_LEFT -> fragment = RightToLeftReader()
|
RIGHT_TO_LEFT -> RightToLeftReader()
|
||||||
VERTICAL -> fragment = VerticalReader()
|
VERTICAL -> VerticalReader()
|
||||||
WEBTOON -> fragment = WebtoonReader()
|
WEBTOON -> WebtoonReader()
|
||||||
else -> fragment = LeftToRightReader()
|
else -> LeftToRightReader()
|
||||||
}
|
}
|
||||||
|
|
||||||
supportFragmentManager.beginTransaction().replace(R.id.reader, fragment, manga.viewer.toString()).commit()
|
supportFragmentManager.beginTransaction().replace(R.id.reader, fragment, manga.viewer.toString()).commit()
|
||||||
@ -469,23 +472,6 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Start a share intent that lets user share image
|
|
||||||
*
|
|
||||||
* @param page page object containing image information.
|
|
||||||
*/
|
|
||||||
fun shareImage(page: Page) {
|
|
||||||
if (page.status != Page.READY)
|
|
||||||
return
|
|
||||||
|
|
||||||
val intent = Intent(Intent.ACTION_SEND).apply {
|
|
||||||
putExtra(Intent.EXTRA_STREAM, page.uri)
|
|
||||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION
|
|
||||||
type = "image/*"
|
|
||||||
}
|
|
||||||
startActivity(Intent.createChooser(intent, getString(R.string.action_share)))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the brightness of the screen. Range is [-75, 100].
|
* Sets the brightness of the screen. Range is [-75, 100].
|
||||||
* From -75 to -1 a semi-transparent black view is shown at the top with the minimum brightness.
|
* From -75 to -1 a semi-transparent black view is shown at the top with the minimum brightness.
|
||||||
@ -570,4 +556,39 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start a share intent that lets user share image
|
||||||
|
*
|
||||||
|
* @param page page object containing image information.
|
||||||
|
*/
|
||||||
|
private fun shareImage(page: Page) {
|
||||||
|
if (page.status != Page.READY)
|
||||||
|
return
|
||||||
|
|
||||||
|
val intent = Intent(Intent.ACTION_SEND).apply {
|
||||||
|
putExtra(Intent.EXTRA_STREAM, page.uri)
|
||||||
|
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||||
|
type = "image/*"
|
||||||
|
}
|
||||||
|
startActivity(Intent.createChooser(intent, getString(R.string.action_share)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the given page as the cover of the manga.
|
||||||
|
*
|
||||||
|
* @param page the page containing the image to set as cover.
|
||||||
|
*/
|
||||||
|
private fun setImageAsCover(page: Page) {
|
||||||
|
if (page.status != Page.READY)
|
||||||
|
return
|
||||||
|
|
||||||
|
MaterialDialog.Builder(this)
|
||||||
|
.content(getString(R.string.confirm_set_image_as_cover))
|
||||||
|
.positiveText(android.R.string.yes)
|
||||||
|
.negativeText(android.R.string.no)
|
||||||
|
.onPositive { dialog, which -> presenter.setImageAsCover(page) }
|
||||||
|
.show()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -523,19 +523,13 @@ class ReaderPresenter : BasePresenter<ReaderActivity>() {
|
|||||||
/**
|
/**
|
||||||
* Update cover with page file.
|
* Update cover with page file.
|
||||||
*/
|
*/
|
||||||
internal fun setCover(page: Page) {
|
internal fun setImageAsCover(page: Page) {
|
||||||
if (page.status != Page.READY)
|
|
||||||
return
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
val thumbUrl = manga.thumbnail_url ?: throw Exception("Image url not found")
|
||||||
if (manga.favorite) {
|
if (manga.favorite) {
|
||||||
if (manga.thumbnail_url != null) {
|
val input = context.contentResolver.openInputStream(page.uri)
|
||||||
val input = context.contentResolver.openInputStream(page.uri)
|
coverCache.copyToCache(thumbUrl, input)
|
||||||
coverCache.copyToCache(manga.thumbnail_url!!, input)
|
context.toast(R.string.cover_updated)
|
||||||
context.toast(R.string.cover_updated)
|
|
||||||
} else {
|
|
||||||
throw Exception("Image url not found")
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
context.toast(R.string.notification_first_add_to_library)
|
context.toast(R.string.notification_first_add_to_library)
|
||||||
}
|
}
|
||||||
|
@ -4,12 +4,11 @@ import android.app.PendingIntent
|
|||||||
import android.content.BroadcastReceiver
|
import android.content.BroadcastReceiver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
|
||||||
import android.support.v4.content.FileProvider
|
import android.support.v4.content.FileProvider
|
||||||
import eu.kanade.tachiyomi.BuildConfig
|
import eu.kanade.tachiyomi.BuildConfig
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.util.notificationManager
|
import eu.kanade.tachiyomi.util.notificationManager
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import eu.kanade.tachiyomi.Constants.NOTIFICATION_DOWNLOAD_IMAGE_ID as defaultNotification
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The BroadcastReceiver of [ImageNotifier]
|
* The BroadcastReceiver of [ImageNotifier]
|
||||||
@ -18,21 +17,16 @@ import java.io.File
|
|||||||
class ImageNotificationReceiver : BroadcastReceiver() {
|
class ImageNotificationReceiver : BroadcastReceiver() {
|
||||||
override fun onReceive(context: Context, intent: Intent) {
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
when (intent.action) {
|
when (intent.action) {
|
||||||
ACTION_SHARE_IMAGE -> {
|
|
||||||
shareImage(context, intent.getStringExtra(EXTRA_FILE_LOCATION))
|
|
||||||
context.notificationManager.cancel(intent.getIntExtra(NOTIFICATION_ID, 5))
|
|
||||||
}
|
|
||||||
ACTION_SHOW_IMAGE ->
|
|
||||||
showImage(context, intent.getStringExtra(EXTRA_FILE_LOCATION))
|
|
||||||
ACTION_DELETE_IMAGE -> {
|
ACTION_DELETE_IMAGE -> {
|
||||||
deleteImage(intent.getStringExtra(EXTRA_FILE_LOCATION))
|
deleteImage(intent.getStringExtra(EXTRA_FILE_LOCATION))
|
||||||
context.notificationManager.cancel(intent.getIntExtra(NOTIFICATION_ID, 5))
|
context.notificationManager.cancel(intent.getIntExtra(NOTIFICATION_ID, defaultNotification))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called to delete image
|
* Called to delete image
|
||||||
|
*
|
||||||
* @param path path of file
|
* @param path path of file
|
||||||
*/
|
*/
|
||||||
private fun deleteImage(path: String) {
|
private fun deleteImage(path: String) {
|
||||||
@ -40,60 +34,42 @@ class ImageNotificationReceiver : BroadcastReceiver() {
|
|||||||
if (file.exists()) file.delete()
|
if (file.exists()) file.delete()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Called to start share intent to share image
|
|
||||||
* @param context context of application
|
|
||||||
* @param path path of file
|
|
||||||
*/
|
|
||||||
private fun shareImage(context: Context, path: String) {
|
|
||||||
val intent = Intent(Intent.ACTION_SEND).apply {
|
|
||||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK
|
|
||||||
putExtra(Intent.EXTRA_STREAM, Uri.parse(path))
|
|
||||||
type = "image/*"
|
|
||||||
}
|
|
||||||
context.startActivity(Intent.createChooser(intent, context.getString(R.string.action_share)))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called to show image in gallery application
|
|
||||||
* @param context context of application
|
|
||||||
* @param path path of file
|
|
||||||
*/
|
|
||||||
private fun showImage(context: Context, path: String) {
|
|
||||||
val intent = Intent(Intent.ACTION_VIEW).apply {
|
|
||||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION
|
|
||||||
val uri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".provider", File(path))
|
|
||||||
setDataAndType(uri, "image/*")
|
|
||||||
}
|
|
||||||
context.startActivity(intent)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val ACTION_SHARE_IMAGE = "eu.kanade.SHARE_IMAGE"
|
|
||||||
|
|
||||||
private const val ACTION_SHOW_IMAGE = "eu.kanade.SHOW_IMAGE"
|
|
||||||
|
|
||||||
private const val ACTION_DELETE_IMAGE = "eu.kanade.DELETE_IMAGE"
|
private const val ACTION_DELETE_IMAGE = "eu.kanade.DELETE_IMAGE"
|
||||||
|
|
||||||
private const val EXTRA_FILE_LOCATION = "file_location"
|
private const val EXTRA_FILE_LOCATION = "file_location"
|
||||||
|
|
||||||
private const val NOTIFICATION_ID = "notification_id"
|
private const val NOTIFICATION_ID = "notification_id"
|
||||||
|
|
||||||
internal fun shareImageIntent(context: Context, path: String, notificationId: Int): PendingIntent {
|
/**
|
||||||
val intent = Intent(context, ImageNotificationReceiver::class.java).apply {
|
* Called to start share intent to share image
|
||||||
action = ACTION_SHARE_IMAGE
|
*
|
||||||
putExtra(EXTRA_FILE_LOCATION, path)
|
* @param context context of application
|
||||||
putExtra(NOTIFICATION_ID, notificationId)
|
* @param path path of file
|
||||||
|
*/
|
||||||
|
internal fun shareImageIntent(context: Context, path: String): PendingIntent {
|
||||||
|
val intent = Intent(Intent.ACTION_SEND).apply {
|
||||||
|
val uri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".provider", File(path))
|
||||||
|
putExtra(Intent.EXTRA_STREAM, uri)
|
||||||
|
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||||
|
type = "image/*"
|
||||||
}
|
}
|
||||||
return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
|
return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called to show image in gallery application
|
||||||
|
*
|
||||||
|
* @param context context of application
|
||||||
|
* @param path path of file
|
||||||
|
*/
|
||||||
internal fun showImageIntent(context: Context, path: String): PendingIntent {
|
internal fun showImageIntent(context: Context, path: String): PendingIntent {
|
||||||
val intent = Intent(context, ImageNotificationReceiver::class.java).apply {
|
val intent = Intent(Intent.ACTION_VIEW).apply {
|
||||||
action = ACTION_SHOW_IMAGE
|
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||||
putExtra(EXTRA_FILE_LOCATION, path)
|
val uri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".provider", File(path))
|
||||||
|
setDataAndType(uri, "image/*")
|
||||||
}
|
}
|
||||||
return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
|
return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun deleteImageIntent(context: Context, path: String, notificationId: Int): PendingIntent {
|
internal fun deleteImageIntent(context: Context, path: String, notificationId: Int): PendingIntent {
|
||||||
|
@ -62,7 +62,7 @@ class ImageNotifier(private val context: Context) {
|
|||||||
// Share action
|
// Share action
|
||||||
addAction(R.drawable.ic_share_grey_24dp,
|
addAction(R.drawable.ic_share_grey_24dp,
|
||||||
context.getString(R.string.action_share),
|
context.getString(R.string.action_share),
|
||||||
ImageNotificationReceiver.shareImageIntent(context, file.absolutePath, notificationId))
|
ImageNotificationReceiver.shareImageIntent(context, file.absolutePath))
|
||||||
// Delete action
|
// Delete action
|
||||||
addAction(R.drawable.ic_delete_grey_24dp,
|
addAction(R.drawable.ic_delete_grey_24dp,
|
||||||
context.getString(R.string.action_delete),
|
context.getString(R.string.action_delete),
|
||||||
|
@ -189,14 +189,38 @@ abstract class BaseReader : BaseFragment() {
|
|||||||
abstract fun onChapterAppended(chapter: ReaderChapter)
|
abstract fun onChapterAppended(chapter: ReaderChapter)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Moves pages forward. Implementations decide how to move (by a page, by some distance...).
|
* Moves pages to right. Implementations decide how to move (by a page, by some distance...).
|
||||||
*/
|
*/
|
||||||
abstract fun moveToNext()
|
abstract fun moveRight()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Moves pages backward. Implementations decide how to move (by a page, by some distance...).
|
* Moves pages to left. Implementations decide how to move (by a page, by some distance...).
|
||||||
*/
|
*/
|
||||||
abstract fun moveToPrevious()
|
abstract fun moveLeft()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves pages down. Implementations decide how to move (by a page, by some distance...).
|
||||||
|
*/
|
||||||
|
open fun moveDown() {
|
||||||
|
moveRight()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves pages up. Implementations decide how to move (by a page, by some distance...).
|
||||||
|
*/
|
||||||
|
open fun moveUp() {
|
||||||
|
moveLeft()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method the implementations can call to show a menu with options for the given page.
|
||||||
|
*/
|
||||||
|
fun onLongClick(page: Page?): Boolean {
|
||||||
|
if (isAdded && page != null) {
|
||||||
|
readerActivity.onLongClick(page)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the active decoder class.
|
* Sets the active decoder class.
|
||||||
|
@ -71,6 +71,7 @@ class PageView @JvmOverloads constructor(context: Context, attrs: AttributeSet?
|
|||||||
setBitmapDecoderClass(reader.bitmapDecoderClass)
|
setBitmapDecoderClass(reader.bitmapDecoderClass)
|
||||||
setVerticalScrollingParent(reader is VerticalReader)
|
setVerticalScrollingParent(reader is VerticalReader)
|
||||||
setOnTouchListener { v, motionEvent -> reader.gestureDetector.onTouchEvent(motionEvent) }
|
setOnTouchListener { v, motionEvent -> reader.gestureDetector.onTouchEvent(motionEvent) }
|
||||||
|
setOnLongClickListener { reader.onLongClick(page) }
|
||||||
setOnImageEventListener(object : SubsamplingScaleImageView.DefaultOnImageEventListener() {
|
setOnImageEventListener(object : SubsamplingScaleImageView.DefaultOnImageEventListener() {
|
||||||
override fun onReady() {
|
override fun onReady() {
|
||||||
onImageDecoded(reader)
|
onImageDecoded(reader)
|
||||||
|
@ -66,7 +66,7 @@ abstract class PagerReader : BaseReader() {
|
|||||||
/**
|
/**
|
||||||
* Gesture detector for touch events.
|
* Gesture detector for touch events.
|
||||||
*/
|
*/
|
||||||
val gestureDetector by lazy { createGestureDetector() }
|
val gestureDetector by lazy { GestureDetector(context, ImageGestureListener()) }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subscriptions for reader settings.
|
* Subscriptions for reader settings.
|
||||||
@ -166,37 +166,24 @@ abstract class PagerReader : BaseReader() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates the gesture detector for the pager.
|
* Gesture detector for Subsampling Scale Image View.
|
||||||
*
|
|
||||||
* @return a gesture detector.
|
|
||||||
*/
|
*/
|
||||||
protected fun createGestureDetector(): GestureDetector {
|
inner class ImageGestureListener : GestureDetector.SimpleOnGestureListener() {
|
||||||
return GestureDetector(activity, object : GestureDetector.SimpleOnGestureListener() {
|
|
||||||
override fun onSingleTapConfirmed(e: MotionEvent): Boolean {
|
|
||||||
if (isAdded) {
|
|
||||||
val positionX = e.x
|
|
||||||
|
|
||||||
if (positionX < pager.width * LEFT_REGION) {
|
override fun onSingleTapConfirmed(e: MotionEvent): Boolean {
|
||||||
if (tappingEnabled) onLeftSideTap()
|
if (isAdded) {
|
||||||
} else if (positionX > pager.width * RIGHT_REGION) {
|
val positionX = e.x
|
||||||
if (tappingEnabled) onRightSideTap()
|
|
||||||
} else {
|
|
||||||
readerActivity.toggleMenu()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onLongPress(e: MotionEvent?) {
|
if (positionX < pager.width * LEFT_REGION) {
|
||||||
if (isAdded) {
|
if (tappingEnabled) moveLeft()
|
||||||
val page = adapter.pages.getOrNull(pager.currentItem)
|
} else if (positionX > pager.width * RIGHT_REGION) {
|
||||||
if (page != null)
|
if (tappingEnabled) moveRight()
|
||||||
readerActivity.onLongPress(page)
|
} else {
|
||||||
else
|
readerActivity.toggleMenu()
|
||||||
context.toast(getString(R.string.unknown_error))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -258,23 +245,23 @@ abstract class PagerReader : BaseReader() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the left side of the screen was clicked.
|
* Moves a page to the right.
|
||||||
*/
|
*/
|
||||||
protected open fun onLeftSideTap() {
|
override fun moveRight() {
|
||||||
moveToPrevious()
|
moveToNext()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the right side of the screen was clicked.
|
* Moves a page to the left.
|
||||||
*/
|
*/
|
||||||
protected open fun onRightSideTap() {
|
override fun moveLeft() {
|
||||||
moveToNext()
|
moveToPrevious()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Moves to the next page or requests the next chapter if it's the last one.
|
* Moves to the next page or requests the next chapter if it's the last one.
|
||||||
*/
|
*/
|
||||||
override fun moveToNext() {
|
protected fun moveToNext() {
|
||||||
if (pager.currentItem != pager.adapter.count - 1) {
|
if (pager.currentItem != pager.adapter.count - 1) {
|
||||||
pager.setCurrentItem(pager.currentItem + 1, transitions)
|
pager.setCurrentItem(pager.currentItem + 1, transitions)
|
||||||
} else {
|
} else {
|
||||||
@ -285,7 +272,7 @@ abstract class PagerReader : BaseReader() {
|
|||||||
/**
|
/**
|
||||||
* Moves to the previous page or requests the previous chapter if it's the first one.
|
* Moves to the previous page or requests the previous chapter if it's the first one.
|
||||||
*/
|
*/
|
||||||
override fun moveToPrevious() {
|
protected fun moveToPrevious() {
|
||||||
if (pager.currentItem != 0) {
|
if (pager.currentItem != 0) {
|
||||||
pager.setCurrentItem(pager.currentItem - 1, transitions)
|
pager.setCurrentItem(pager.currentItem - 1, transitions)
|
||||||
} else {
|
} else {
|
||||||
|
@ -19,11 +19,31 @@ class RightToLeftReader : PagerReader() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onLeftSideTap() {
|
/**
|
||||||
|
* Moves a page to the right.
|
||||||
|
*/
|
||||||
|
override fun moveRight() {
|
||||||
|
moveToPrevious()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves a page to the left.
|
||||||
|
*/
|
||||||
|
override fun moveLeft() {
|
||||||
moveToNext()
|
moveToNext()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRightSideTap() {
|
/**
|
||||||
|
* Moves a page down.
|
||||||
|
*/
|
||||||
|
override fun moveDown() {
|
||||||
|
moveToNext()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves a page up.
|
||||||
|
*/
|
||||||
|
override fun moveUp() {
|
||||||
moveToPrevious()
|
moveToPrevious()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ class WebtoonAdapter(val fragment: WebtoonReader) : RecyclerView.Adapter<Webtoon
|
|||||||
/**
|
/**
|
||||||
* Touch listener for images in holders.
|
* Touch listener for images in holders.
|
||||||
*/
|
*/
|
||||||
val touchListener = View.OnTouchListener { v, ev -> fragment.gestureDetector.onTouchEvent(ev) }
|
val touchListener = View.OnTouchListener { v, ev -> fragment.imageGestureDetector.onTouchEvent(ev) }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the number of pages.
|
* Returns the number of pages.
|
||||||
|
@ -64,6 +64,7 @@ class WebtoonHolder(private val view: View, private val adapter: WebtoonAdapter)
|
|||||||
setBitmapDecoderClass(webtoonReader.bitmapDecoderClass)
|
setBitmapDecoderClass(webtoonReader.bitmapDecoderClass)
|
||||||
setVerticalScrollingParent(true)
|
setVerticalScrollingParent(true)
|
||||||
setOnTouchListener(adapter.touchListener)
|
setOnTouchListener(adapter.touchListener)
|
||||||
|
setOnLongClickListener { webtoonReader.onLongClick(page) }
|
||||||
setOnImageEventListener(object : SubsamplingScaleImageView.DefaultOnImageEventListener() {
|
setOnImageEventListener(object : SubsamplingScaleImageView.DefaultOnImageEventListener() {
|
||||||
override fun onReady() {
|
override fun onReady() {
|
||||||
onImageDecoded()
|
onImageDecoded()
|
||||||
|
@ -3,14 +3,11 @@ package eu.kanade.tachiyomi.ui.reader.viewer.webtoon
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.support.v7.widget.RecyclerView
|
import android.support.v7.widget.RecyclerView
|
||||||
import android.view.*
|
import android.view.*
|
||||||
import android.view.GestureDetector.SimpleOnGestureListener
|
|
||||||
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
|
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.data.source.model.Page
|
import eu.kanade.tachiyomi.data.source.model.Page
|
||||||
import eu.kanade.tachiyomi.ui.reader.ReaderChapter
|
import eu.kanade.tachiyomi.ui.reader.ReaderChapter
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.base.BaseReader
|
import eu.kanade.tachiyomi.ui.reader.viewer.base.BaseReader
|
||||||
import eu.kanade.tachiyomi.util.toast
|
|
||||||
import eu.kanade.tachiyomi.widget.PreCachingLayoutManager
|
import eu.kanade.tachiyomi.widget.PreCachingLayoutManager
|
||||||
import rx.subscriptions.CompositeSubscription
|
import rx.subscriptions.CompositeSubscription
|
||||||
|
|
||||||
@ -55,9 +52,9 @@ class WebtoonReader : BaseReader() {
|
|||||||
private set
|
private set
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gesture detector for touch events.
|
* Gesture detector for image touch events.
|
||||||
*/
|
*/
|
||||||
val gestureDetector by lazy { createGestureDetector() }
|
val imageGestureDetector by lazy { GestureDetector(context, ImageGestureListener()) }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subscriptions used while the view exists.
|
* Subscriptions used while the view exists.
|
||||||
@ -122,39 +119,24 @@ class WebtoonReader : BaseReader() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates the gesture detector for the reader.
|
* Gesture detector for Subsampling Scale Image View.
|
||||||
*
|
|
||||||
* @return a gesture detector.
|
|
||||||
*/
|
*/
|
||||||
protected fun createGestureDetector(): GestureDetector {
|
inner class ImageGestureListener : GestureDetector.SimpleOnGestureListener() {
|
||||||
return GestureDetector(context, object : SimpleOnGestureListener() {
|
|
||||||
override fun onSingleTapConfirmed(e: MotionEvent): Boolean {
|
|
||||||
if (isAdded) {
|
|
||||||
val positionX = e.x
|
|
||||||
|
|
||||||
if (positionX < recycler.width * LEFT_REGION) {
|
override fun onSingleTapConfirmed(e: MotionEvent): Boolean {
|
||||||
if (tappingEnabled) moveToPrevious()
|
if (isAdded) {
|
||||||
} else if (positionX > recycler.width * RIGHT_REGION) {
|
val positionX = e.x
|
||||||
if (tappingEnabled) moveToNext()
|
|
||||||
} else {
|
|
||||||
readerActivity.toggleMenu()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onLongPress(e: MotionEvent) {
|
if (positionX < recycler.width * LEFT_REGION) {
|
||||||
if (isAdded) {
|
if (tappingEnabled) moveLeft()
|
||||||
val child = recycler.findChildViewUnder(e.rawX, e.rawY)
|
} else if (positionX > recycler.width * RIGHT_REGION) {
|
||||||
val position = recycler.getChildAdapterPosition(child)
|
if (tappingEnabled) moveRight()
|
||||||
val page = adapter.pages?.getOrNull(position)
|
} else {
|
||||||
if (page != null)
|
readerActivity.toggleMenu()
|
||||||
readerActivity.onLongPress(page)
|
|
||||||
else
|
|
||||||
context.toast(getString(R.string.unknown_error))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -205,14 +187,14 @@ class WebtoonReader : BaseReader() {
|
|||||||
/**
|
/**
|
||||||
* Moves to the next page or requests the next chapter if it's the last one.
|
* Moves to the next page or requests the next chapter if it's the last one.
|
||||||
*/
|
*/
|
||||||
override fun moveToNext() {
|
override fun moveRight() {
|
||||||
recycler.smoothScrollBy(0, scrollDistance)
|
recycler.smoothScrollBy(0, scrollDistance)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Moves to the previous page or requests the previous chapter if it's the first one.
|
* Moves to the previous page or requests the previous chapter if it's the first one.
|
||||||
*/
|
*/
|
||||||
override fun moveToPrevious() {
|
override fun moveLeft() {
|
||||||
recycler.smoothScrollBy(0, -scrollDistance)
|
recycler.smoothScrollBy(0, -scrollDistance)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,43 +43,17 @@ class RecentChaptersPresenter : BasePresenter<RecentChaptersFragment>() {
|
|||||||
*/
|
*/
|
||||||
private var chapters: List<RecentChapter>? = null
|
private var chapters: List<RecentChapter>? = null
|
||||||
|
|
||||||
/**
|
|
||||||
* The id of the restartable.
|
|
||||||
*/
|
|
||||||
val GET_RECENT_CHAPTERS = 1
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The id of the restartable.
|
|
||||||
*/
|
|
||||||
val CHAPTER_STATUS_CHANGES = 2
|
|
||||||
|
|
||||||
override fun onCreate(savedState: Bundle?) {
|
override fun onCreate(savedState: Bundle?) {
|
||||||
super.onCreate(savedState)
|
super.onCreate(savedState)
|
||||||
|
|
||||||
// Used to get recent chapters
|
getRecentChaptersObservable()
|
||||||
restartableLatestCache(GET_RECENT_CHAPTERS,
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
{ getRecentChaptersObservable() },
|
.subscribeLatestCache(RecentChaptersFragment::onNextRecentChapters)
|
||||||
{ view, chapters ->
|
|
||||||
// Update adapter to show recent manga's
|
|
||||||
view.onNextRecentChapters(chapters)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// Used to update download status
|
getChapterStatusObservable()
|
||||||
restartableLatestCache(CHAPTER_STATUS_CHANGES,
|
.subscribeLatestCache(RecentChaptersFragment::onChapterStatusChange,
|
||||||
{ getChapterStatusObservable() },
|
{ view, error -> Timber.e(error) })
|
||||||
{ view, download ->
|
|
||||||
// Set chapter status
|
|
||||||
view.onChapterStatusChange(download)
|
|
||||||
},
|
|
||||||
{ view, error -> Timber.e(error) }
|
|
||||||
)
|
|
||||||
|
|
||||||
if (savedState == null) {
|
|
||||||
// Start fetching recent chapters
|
|
||||||
start(GET_RECENT_CHAPTERS)
|
|
||||||
start(CHAPTER_STATUS_CHANGES)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -119,7 +93,6 @@ class RecentChaptersPresenter : BasePresenter<RecentChaptersFragment>() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -156,14 +129,29 @@ class RecentChaptersPresenter : BasePresenter<RecentChaptersFragment>() {
|
|||||||
* @param chapters the list of chapter from the database.
|
* @param chapters the list of chapter from the database.
|
||||||
*/
|
*/
|
||||||
private fun setDownloadedChapters(chapters: List<RecentChapter>) {
|
private fun setDownloadedChapters(chapters: List<RecentChapter>) {
|
||||||
val cachedDirs = mutableMapOf<Long, UniFile?>()
|
// Cached list of downloaded manga directories.
|
||||||
|
val mangaDirectories = mutableMapOf<Int, Array<UniFile>>()
|
||||||
|
|
||||||
chapters.forEach { chapter ->
|
// Cached list of downloaded chapter directories for a manga.
|
||||||
|
val chapterDirectories = mutableMapOf<Long, Array<UniFile>>()
|
||||||
|
|
||||||
|
for (chapter in chapters) {
|
||||||
val manga = chapter.manga
|
val manga = chapter.manga
|
||||||
val mangaDir = cachedDirs.getOrPut(manga.id!!)
|
val source = sourceManager.get(manga.source) ?: continue
|
||||||
{ downloadManager.findMangaDir(sourceManager.get(manga.source)!!, manga) }
|
|
||||||
|
|
||||||
if (mangaDir?.findFile(downloadManager.getChapterDirName(chapter)) != null) {
|
val mangaDirs = mangaDirectories.getOrPut(source.id) {
|
||||||
|
downloadManager.findSourceDir(source)?.listFiles() ?: emptyArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
val mangaDirName = downloadManager.getMangaDirName(manga)
|
||||||
|
val mangaDir = mangaDirs.find { it.name == mangaDirName } ?: continue
|
||||||
|
|
||||||
|
val chapterDirs = chapterDirectories.getOrPut(manga.id!!) {
|
||||||
|
mangaDir.listFiles() ?: emptyArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
val chapterDirName = downloadManager.getChapterDirName(chapter)
|
||||||
|
if (chapterDirs.any { it.name == chapterDirName }) {
|
||||||
chapter.status = Download.DOWNLOADED
|
chapter.status = Download.DOWNLOADED
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -78,6 +78,7 @@ class SettingsActivity : BaseActivity(),
|
|||||||
companion object {
|
companion object {
|
||||||
const val FLAG_THEME_CHANGED = 0x1
|
const val FLAG_THEME_CHANGED = 0x1
|
||||||
const val FLAG_DATABASE_CLEARED = 0x2
|
const val FLAG_DATABASE_CLEARED = 0x2
|
||||||
|
const val FLAG_LANG_CHANGED = 0x4
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ import eu.kanade.tachiyomi.R
|
|||||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||||
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
|
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
|
import eu.kanade.tachiyomi.util.LocaleHelper
|
||||||
import eu.kanade.tachiyomi.util.plusAssign
|
import eu.kanade.tachiyomi.util.plusAssign
|
||||||
import eu.kanade.tachiyomi.widget.preference.IntListPreference
|
import eu.kanade.tachiyomi.widget.preference.IntListPreference
|
||||||
import eu.kanade.tachiyomi.widget.preference.LibraryColumnsDialog
|
import eu.kanade.tachiyomi.widget.preference.LibraryColumnsDialog
|
||||||
@ -17,6 +18,7 @@ import net.xpece.android.support.preference.MultiSelectListPreference
|
|||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.android.schedulers.AndroidSchedulers
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
class SettingsGeneralFragment : SettingsFragment(),
|
class SettingsGeneralFragment : SettingsFragment(),
|
||||||
PreferenceFragmentCompat.OnPreferenceDisplayDialogCallback {
|
PreferenceFragmentCompat.OnPreferenceDisplayDialogCallback {
|
||||||
@ -44,6 +46,8 @@ class SettingsGeneralFragment : SettingsFragment(),
|
|||||||
|
|
||||||
val categoryUpdate: MultiSelectListPreference by bindPref(R.string.pref_library_update_categories_key)
|
val categoryUpdate: MultiSelectListPreference by bindPref(R.string.pref_library_update_categories_key)
|
||||||
|
|
||||||
|
val langPreference: IntListPreference by bindPref(R.string.pref_language_key)
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedState: Bundle?) {
|
override fun onViewCreated(view: View, savedState: Bundle?) {
|
||||||
super.onViewCreated(view, savedState)
|
super.onViewCreated(view, savedState)
|
||||||
|
|
||||||
@ -101,6 +105,15 @@ class SettingsGeneralFragment : SettingsFragment(),
|
|||||||
activity.recreate()
|
activity.recreate()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
langPreference.setOnPreferenceChangeListener { preference, newValue ->
|
||||||
|
(activity as SettingsActivity).parentFlags = SettingsActivity.FLAG_LANG_CHANGED
|
||||||
|
LocaleHelper.setLocale(Locale(LocaleHelper.intToLangCode(newValue.toString().toInt())))
|
||||||
|
LocaleHelper.updateCfg(activity.application, activity.baseContext.resources.configuration)
|
||||||
|
activity.recreate()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPreferenceDisplayDialog(p0: PreferenceFragmentCompat?, p: Preference): Boolean {
|
override fun onPreferenceDisplayDialog(p0: PreferenceFragmentCompat?, p: Preference): Boolean {
|
||||||
|
@ -19,7 +19,7 @@ import java.util.*
|
|||||||
fun syncChaptersWithSource(db: DatabaseHelper,
|
fun syncChaptersWithSource(db: DatabaseHelper,
|
||||||
sourceChapters: List<Chapter>,
|
sourceChapters: List<Chapter>,
|
||||||
manga: Manga,
|
manga: Manga,
|
||||||
source: Source) : Pair<Int, Int> {
|
source: Source) : Pair<List<Chapter>, List<Chapter>> {
|
||||||
|
|
||||||
// Chapters from db.
|
// Chapters from db.
|
||||||
val dbChapters = db.getChapters(manga).executeAsBlocking()
|
val dbChapters = db.getChapters(manga).executeAsBlocking()
|
||||||
@ -44,22 +44,19 @@ fun syncChaptersWithSource(db: DatabaseHelper,
|
|||||||
// Chapters from the db not in the source.
|
// Chapters from the db not in the source.
|
||||||
val toDelete = dbChapters.filterNot { it in sourceChapters }
|
val toDelete = dbChapters.filterNot { it in sourceChapters }
|
||||||
|
|
||||||
// Amount of chapters added and deleted.
|
val readded = mutableListOf<Chapter>()
|
||||||
var added = 0
|
|
||||||
var deleted = 0
|
|
||||||
|
|
||||||
// Amount of chapters readded (different url but the same chapter number).
|
|
||||||
var readded = 0
|
|
||||||
|
|
||||||
db.inTransaction {
|
db.inTransaction {
|
||||||
|
val deletedChapterNumbers = TreeSet<Float>()
|
||||||
val deletedReadChapterNumbers = TreeSet<Float>()
|
val deletedReadChapterNumbers = TreeSet<Float>()
|
||||||
if (!toDelete.isEmpty()) {
|
if (!toDelete.isEmpty()) {
|
||||||
for (c in toDelete) {
|
for (c in toDelete) {
|
||||||
if (c.read) {
|
if (c.read) {
|
||||||
deletedReadChapterNumbers.add(c.chapter_number)
|
deletedReadChapterNumbers.add(c.chapter_number)
|
||||||
}
|
}
|
||||||
|
deletedChapterNumbers.add(c.chapter_number)
|
||||||
}
|
}
|
||||||
deleted = db.deleteChapters(toDelete).executeAsBlocking().results().size
|
db.deleteChapters(toDelete).executeAsBlocking()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!toAdd.isEmpty()) {
|
if (!toAdd.isEmpty()) {
|
||||||
@ -73,14 +70,16 @@ fun syncChaptersWithSource(db: DatabaseHelper,
|
|||||||
// Try to mark already read chapters as read when the source deletes them
|
// Try to mark already read chapters as read when the source deletes them
|
||||||
if (c.isRecognizedNumber && c.chapter_number in deletedReadChapterNumbers) {
|
if (c.isRecognizedNumber && c.chapter_number in deletedReadChapterNumbers) {
|
||||||
c.read = true
|
c.read = true
|
||||||
readded++
|
}
|
||||||
|
if (c.isRecognizedNumber && c.chapter_number in deletedChapterNumbers) {
|
||||||
|
readded.add(c)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
added = db.insertChapters(toAdd).executeAsBlocking().numberOfInserts()
|
db.insertChapters(toAdd).executeAsBlocking()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fix order in source.
|
// Fix order in source.
|
||||||
db.fixChaptersSourceOrder(sourceChapters).executeAsBlocking()
|
db.fixChaptersSourceOrder(sourceChapters).executeAsBlocking()
|
||||||
}
|
}
|
||||||
return Pair(added - readded, deleted - readded)
|
return Pair(toAdd.subtract(readded).toList(), toDelete.subtract(readded).toList())
|
||||||
}
|
}
|
||||||
|
47
app/src/main/java/eu/kanade/tachiyomi/util/LocaleHelper.kt
Normal file
47
app/src/main/java/eu/kanade/tachiyomi/util/LocaleHelper.kt
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package eu.kanade.tachiyomi.util
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import android.content.res.Configuration
|
||||||
|
import android.os.Build
|
||||||
|
import android.view.ContextThemeWrapper
|
||||||
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
object LocaleHelper {
|
||||||
|
|
||||||
|
private val preferences: PreferencesHelper by injectLazy()
|
||||||
|
|
||||||
|
private var pLocale = Locale(intToLangCode(preferences.lang()))
|
||||||
|
|
||||||
|
fun setLocale(locale: Locale) {
|
||||||
|
pLocale = locale
|
||||||
|
Locale.setDefault(pLocale)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateCfg(wrapper: ContextThemeWrapper) {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
||||||
|
val config = Configuration()
|
||||||
|
config.setLocale(pLocale)
|
||||||
|
wrapper.applyOverrideConfiguration(config)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateCfg(app: Application, config: Configuration) {
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
||||||
|
config.locale = pLocale
|
||||||
|
app.baseContext.resources.updateConfiguration(config, app.baseContext.resources.displayMetrics)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun intToLangCode(i: Int): String {
|
||||||
|
return when(i) {
|
||||||
|
1 -> "en"
|
||||||
|
2 -> "es"
|
||||||
|
3 -> "it"
|
||||||
|
4 -> "pt"
|
||||||
|
else -> "" // System Language
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,8 +1,13 @@
|
|||||||
package eu.kanade.tachiyomi.util
|
package eu.kanade.tachiyomi.util
|
||||||
|
|
||||||
|
import rx.Observable
|
||||||
import rx.Subscription
|
import rx.Subscription
|
||||||
import rx.subscriptions.CompositeSubscription
|
import rx.subscriptions.CompositeSubscription
|
||||||
|
|
||||||
fun Subscription?.isNullOrUnsubscribed() = this == null || isUnsubscribed
|
fun Subscription?.isNullOrUnsubscribed() = this == null || isUnsubscribed
|
||||||
|
|
||||||
operator fun CompositeSubscription.plusAssign(subscription: Subscription) = add(subscription)
|
operator fun CompositeSubscription.plusAssign(subscription: Subscription) = add(subscription)
|
||||||
|
|
||||||
|
fun <T, U, R> Observable<T>.combineLatest(o2: Observable<U>, combineFn: (T, U) -> R): Observable<R> {
|
||||||
|
return Observable.combineLatest(this, o2, combineFn)
|
||||||
|
}
|
@ -0,0 +1,363 @@
|
|||||||
|
package eu.kanade.tachiyomi.widget
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.support.annotation.CallSuper
|
||||||
|
import android.support.design.R
|
||||||
|
import android.support.design.internal.ScrimInsetsFrameLayout
|
||||||
|
import android.support.graphics.drawable.VectorDrawableCompat
|
||||||
|
import android.support.v4.content.ContextCompat
|
||||||
|
import android.support.v4.view.ViewCompat
|
||||||
|
import android.support.v7.widget.LinearLayoutManager
|
||||||
|
import android.support.v7.widget.RecyclerView
|
||||||
|
import android.support.v7.widget.TintTypedArray
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.CheckBox
|
||||||
|
import android.widget.CheckedTextView
|
||||||
|
import android.widget.RadioButton
|
||||||
|
import android.widget.TextView
|
||||||
|
import eu.kanade.tachiyomi.util.getResourceColor
|
||||||
|
import eu.kanade.tachiyomi.util.inflate
|
||||||
|
import eu.kanade.tachiyomi.R as TR
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An alternative implementation of [android.support.design.widget.NavigationView], without menu
|
||||||
|
* inflation and allowing customizable items (multiple selections, custom views, etc).
|
||||||
|
*/
|
||||||
|
@Suppress("LeakingThis")
|
||||||
|
@SuppressLint("PrivateResource")
|
||||||
|
open class ExtendedNavigationView @JvmOverloads constructor(
|
||||||
|
context: Context,
|
||||||
|
attrs: AttributeSet? = null,
|
||||||
|
defStyleAttr: Int = 0)
|
||||||
|
: ScrimInsetsFrameLayout(context, attrs, defStyleAttr) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Max width of the navigation view.
|
||||||
|
*/
|
||||||
|
private var maxWidth: Int
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recycler view containing all the items.
|
||||||
|
*/
|
||||||
|
protected val recycler = RecyclerView(context)
|
||||||
|
|
||||||
|
init {
|
||||||
|
// Custom attributes
|
||||||
|
val a = TintTypedArray.obtainStyledAttributes(context, attrs,
|
||||||
|
R.styleable.NavigationView, defStyleAttr,
|
||||||
|
R.style.Widget_Design_NavigationView)
|
||||||
|
|
||||||
|
ViewCompat.setBackground(
|
||||||
|
this, a.getDrawable(R.styleable.NavigationView_android_background))
|
||||||
|
|
||||||
|
if (a.hasValue(R.styleable.NavigationView_elevation)) {
|
||||||
|
ViewCompat.setElevation(this, a.getDimensionPixelSize(
|
||||||
|
R.styleable.NavigationView_elevation, 0).toFloat())
|
||||||
|
}
|
||||||
|
|
||||||
|
ViewCompat.setFitsSystemWindows(this,
|
||||||
|
a.getBoolean(R.styleable.NavigationView_android_fitsSystemWindows, false))
|
||||||
|
|
||||||
|
maxWidth = a.getDimensionPixelSize(R.styleable.NavigationView_android_maxWidth, 0)
|
||||||
|
|
||||||
|
a.recycle()
|
||||||
|
|
||||||
|
recycler.layoutManager = LinearLayoutManager(context)
|
||||||
|
addView(recycler)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overriden to measure the width of the navigation view.
|
||||||
|
*/
|
||||||
|
override fun onMeasure(widthSpec: Int, heightSpec: Int) {
|
||||||
|
val width = when (MeasureSpec.getMode(widthSpec)) {
|
||||||
|
MeasureSpec.AT_MOST -> MeasureSpec.makeMeasureSpec(
|
||||||
|
Math.min(MeasureSpec.getSize(widthSpec), maxWidth), MeasureSpec.EXACTLY)
|
||||||
|
MeasureSpec.UNSPECIFIED -> MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.EXACTLY)
|
||||||
|
else -> widthSpec
|
||||||
|
}
|
||||||
|
// Let super sort out the height
|
||||||
|
super.onMeasure(width, heightSpec)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Every item of the nav view. Generic items must belong to this list, custom items could be
|
||||||
|
* implemented by an abstract class. If more customization is needed in the future, this can be
|
||||||
|
* changed to an interface instead of sealed class.
|
||||||
|
*/
|
||||||
|
sealed class Item {
|
||||||
|
/**
|
||||||
|
* A view separator.
|
||||||
|
*/
|
||||||
|
class Separator(val paddingTop: Int = 0, val paddingBottom: Int = 0) : Item()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A header with a title.
|
||||||
|
*/
|
||||||
|
class Header(val resTitle: Int) : Item()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A checkbox.
|
||||||
|
*/
|
||||||
|
open class Checkbox(val resTitle: Int, var checked: Boolean = false) : Item()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A checkbox belonging to a group. The group must handle selections and restrictions.
|
||||||
|
*/
|
||||||
|
class CheckboxGroup(resTitle: Int, override val group: Group, checked: Boolean = false)
|
||||||
|
: Checkbox(resTitle, checked), GroupedItem
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A radio belonging to a group (a sole radio makes no sense). The group must handle
|
||||||
|
* selections and restrictions.
|
||||||
|
*/
|
||||||
|
class Radio(val resTitle: Int, override val group: Group, var checked: Boolean = false)
|
||||||
|
: Item(), GroupedItem
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An item with which needs more than two states (selected/deselected).
|
||||||
|
*/
|
||||||
|
abstract class MultiState(val resTitle: Int, var state: Int = 0) : Item() {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the drawable associated to every possible each state.
|
||||||
|
*/
|
||||||
|
abstract fun getStateDrawable(context: Context): Drawable?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a vector tinted with the accent color.
|
||||||
|
*
|
||||||
|
* @param context any context.
|
||||||
|
* @param resId the vector resource to load and tint
|
||||||
|
*/
|
||||||
|
fun tintVector(context: Context, resId: Int): Drawable {
|
||||||
|
return VectorDrawableCompat.create(context.resources, resId, context.theme)!!.apply {
|
||||||
|
setTint(context.theme.getResourceColor(TR.attr.colorAccent))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An item with which needs more than two states (selected/deselected) belonging to a group.
|
||||||
|
* The group must handle selections and restrictions.
|
||||||
|
*/
|
||||||
|
abstract class MultiStateGroup(resTitle: Int, override val group: Group, state: Int = 0)
|
||||||
|
: MultiState(resTitle, state), GroupedItem
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A multistate item for sorting lists (unselected, ascending, descending).
|
||||||
|
*/
|
||||||
|
class MultiSort(resId: Int, group: Group) : MultiStateGroup(resId, group) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val SORT_NONE = 0
|
||||||
|
const val SORT_ASC = 1
|
||||||
|
const val SORT_DESC = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getStateDrawable(context: Context): Drawable? {
|
||||||
|
return when (state) {
|
||||||
|
SORT_ASC -> tintVector(context, TR.drawable.ic_keyboard_arrow_up_black_32dp)
|
||||||
|
SORT_DESC -> tintVector(context, TR.drawable.ic_keyboard_arrow_down_black_32dp)
|
||||||
|
SORT_NONE -> ContextCompat.getDrawable(context, TR.drawable.empty_drawable_32dp)
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for an item belonging to a group.
|
||||||
|
*/
|
||||||
|
interface GroupedItem {
|
||||||
|
val group: Group
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A group containing a list of items.
|
||||||
|
*/
|
||||||
|
interface Group {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An optional header for the group, typically a [Item.Header].
|
||||||
|
*/
|
||||||
|
val header: Item?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An optional footer for the group, typically a [Item.Separator].
|
||||||
|
*/
|
||||||
|
val footer: Item?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The items of the group, excluding header and footer.
|
||||||
|
*/
|
||||||
|
val items: List<Item>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates all the elements of this group. Implementations can override this method for more
|
||||||
|
* customization.
|
||||||
|
*/
|
||||||
|
fun createItems() = (mutableListOf<Item>() + header + items + footer).filterNotNull()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called after creating the list of items. Implementations should load the current values
|
||||||
|
* into the models.
|
||||||
|
*/
|
||||||
|
fun initModels()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when an item of this group is clicked. The group is responsible for all the
|
||||||
|
* selections of its items.
|
||||||
|
*/
|
||||||
|
fun onItemClicked(item: Item)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base view holder.
|
||||||
|
*/
|
||||||
|
abstract class Holder(view: View) : RecyclerView.ViewHolder(view)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Separator view holder.
|
||||||
|
*/
|
||||||
|
class SeparatorHolder(parent: ViewGroup)
|
||||||
|
: Holder(parent.inflate(R.layout.design_navigation_item_separator))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Header view holder.
|
||||||
|
*/
|
||||||
|
class HeaderHolder(parent: ViewGroup)
|
||||||
|
: Holder(parent.inflate(R.layout.design_navigation_item_subheader))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clickable view holder.
|
||||||
|
*/
|
||||||
|
abstract class ClickableHolder(view: View, listener: View.OnClickListener?) : Holder(view) {
|
||||||
|
init {
|
||||||
|
itemView.setOnClickListener(listener)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Radio view holder.
|
||||||
|
*/
|
||||||
|
class RadioHolder(parent: ViewGroup, listener: View.OnClickListener?)
|
||||||
|
: ClickableHolder(parent.inflate(TR.layout.navigation_view_radio), listener) {
|
||||||
|
|
||||||
|
val radio = itemView.findViewById(TR.id.nav_view_item) as RadioButton
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checkbox view holder.
|
||||||
|
*/
|
||||||
|
class CheckboxHolder(parent: ViewGroup, listener: View.OnClickListener?)
|
||||||
|
: ClickableHolder(parent.inflate(TR.layout.navigation_view_checkbox), listener) {
|
||||||
|
|
||||||
|
val check = itemView.findViewById(TR.id.nav_view_item) as CheckBox
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Multi state view holder.
|
||||||
|
*/
|
||||||
|
class MultiStateHolder(parent: ViewGroup, listener: View.OnClickListener?)
|
||||||
|
: ClickableHolder(parent.inflate(TR.layout.navigation_view_checkedtext), listener) {
|
||||||
|
|
||||||
|
val text = itemView.findViewById(TR.id.nav_view_item) as CheckedTextView
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base adapter for the navigation view. It knows how to create and render every subclass of
|
||||||
|
* [Item].
|
||||||
|
*/
|
||||||
|
abstract inner class Adapter(private val items: List<Item>) : RecyclerView.Adapter<Holder>() {
|
||||||
|
|
||||||
|
private val onClick = View.OnClickListener {
|
||||||
|
val pos = recycler.getChildAdapterPosition(it)
|
||||||
|
val item = items[pos]
|
||||||
|
onItemClicked(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun notifyItemChanged(item: Item) {
|
||||||
|
val pos = items.indexOf(item)
|
||||||
|
if (pos != -1) notifyItemChanged(pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount(): Int {
|
||||||
|
return items.size
|
||||||
|
}
|
||||||
|
|
||||||
|
@CallSuper
|
||||||
|
override fun getItemViewType(position: Int): Int {
|
||||||
|
val item = items[position]
|
||||||
|
return when (item) {
|
||||||
|
is Item.Header -> VIEW_TYPE_HEADER
|
||||||
|
is Item.Separator -> VIEW_TYPE_SEPARATOR
|
||||||
|
is Item.Radio -> VIEW_TYPE_RADIO
|
||||||
|
is Item.Checkbox -> VIEW_TYPE_CHECKBOX
|
||||||
|
is Item.MultiState -> VIEW_TYPE_MULTISTATE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@CallSuper
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
|
||||||
|
return when (viewType) {
|
||||||
|
VIEW_TYPE_HEADER -> HeaderHolder(parent)
|
||||||
|
VIEW_TYPE_SEPARATOR -> SeparatorHolder(parent)
|
||||||
|
VIEW_TYPE_RADIO -> RadioHolder(parent, onClick)
|
||||||
|
VIEW_TYPE_CHECKBOX -> CheckboxHolder(parent, onClick)
|
||||||
|
VIEW_TYPE_MULTISTATE -> MultiStateHolder(parent, onClick)
|
||||||
|
else -> throw Exception("Unknown view type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@CallSuper
|
||||||
|
override fun onBindViewHolder(holder: Holder, position: Int) {
|
||||||
|
when (holder) {
|
||||||
|
is HeaderHolder -> {
|
||||||
|
val view = holder.itemView as TextView
|
||||||
|
val item = items[position] as Item.Header
|
||||||
|
view.setText(item.resTitle)
|
||||||
|
}
|
||||||
|
is SeparatorHolder -> {
|
||||||
|
val view = holder.itemView
|
||||||
|
val item = items[position] as Item.Separator
|
||||||
|
view.setPadding(0, item.paddingTop, 0, item.paddingBottom)
|
||||||
|
}
|
||||||
|
is RadioHolder -> {
|
||||||
|
val item = items[position] as Item.Radio
|
||||||
|
holder.radio.setText(item.resTitle)
|
||||||
|
holder.radio.isChecked = item.checked
|
||||||
|
}
|
||||||
|
is CheckboxHolder -> {
|
||||||
|
val item = items[position] as Item.CheckboxGroup
|
||||||
|
holder.check.setText(item.resTitle)
|
||||||
|
holder.check.isChecked = item.checked
|
||||||
|
}
|
||||||
|
is MultiStateHolder -> {
|
||||||
|
val item = items[position] as Item.MultiStateGroup
|
||||||
|
val drawable = item.getStateDrawable(context)
|
||||||
|
holder.text.setText(item.resTitle)
|
||||||
|
holder.text.setCompoundDrawablesWithIntrinsicBounds(drawable, null, null, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract fun onItemClicked(item: Item)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val VIEW_TYPE_HEADER = 100
|
||||||
|
private const val VIEW_TYPE_SEPARATOR = 101
|
||||||
|
private const val VIEW_TYPE_RADIO = 102
|
||||||
|
private const val VIEW_TYPE_CHECKBOX = 103
|
||||||
|
private const val VIEW_TYPE_MULTISTATE = 104
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
8
app/src/main/res/drawable/empty_drawable_32dp.xml
Normal file
8
app/src/main/res/drawable/empty_drawable_32dp.xml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<shape
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<solid android:color="@android:color/transparent"/>
|
||||||
|
<size
|
||||||
|
android:width="32dp"
|
||||||
|
android:height="32dp" />
|
||||||
|
</shape>
|
@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="32dp"
|
||||||
|
android:height="32dp"
|
||||||
|
android:viewportWidth="24.0"
|
||||||
|
android:viewportHeight="24.0">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M7.41,7.84L12,12.42l4.59,-4.58L18,9.25l-6,6 -6,-6z"/>
|
||||||
|
</vector>
|
@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="32dp"
|
||||||
|
android:height="32dp"
|
||||||
|
android:viewportWidth="24.0"
|
||||||
|
android:viewportHeight="24.0">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M7.41,15.41L12,10.83l4.59,4.58L18,14l-6,-6 -6,6z"/>
|
||||||
|
</vector>
|
9
app/src/main/res/drawable/ic_sort_white_24dp.xml
Normal file
9
app/src/main/res/drawable/ic_sort_white_24dp.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24.0"
|
||||||
|
android:viewportHeight="24.0">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFFFF"
|
||||||
|
android:pathData="M3,18h6v-2L3,16v2zM3,6v2h18L21,6L3,6zM3,13h12v-2L3,11v2z"/>
|
||||||
|
</vector>
|
@ -57,7 +57,9 @@
|
|||||||
android:layout_gravity="bottom"
|
android:layout_gravity="bottom"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:background="?colorPrimary"
|
android:background="?colorPrimary"
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal"
|
||||||
|
android:focusable="false"
|
||||||
|
android:descendantFocusability="blocksDescendants">
|
||||||
|
|
||||||
<ImageButton
|
<ImageButton
|
||||||
android:id="@+id/left_chapter"
|
android:id="@+id/left_chapter"
|
||||||
|
@ -1,53 +1,43 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<FrameLayout
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
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"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:layout_height="?android:attr/listPreferredItemHeightLarge"
|
android:layout_width="match_parent"
|
||||||
android:paddingTop="8dp"
|
android:layout_height="@dimen/material_component_lists_single_line_with_avatar_height"
|
||||||
android:paddingBottom="8dp"
|
android:background="?attr/selectable_list_drawable">
|
||||||
android:background="?attr/selectable_list_drawable"
|
|
||||||
>
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/image"
|
android:id="@+id/image"
|
||||||
android:layout_width="50dp"
|
android:layout_width="@dimen/material_component_lists_single_line_with_avatar_height"
|
||||||
android:layout_height="50dp"
|
android:layout_height="@dimen/material_component_lists_single_line_with_avatar_height"
|
||||||
android:layout_alignParentLeft="true"
|
|
||||||
android:layout_alignParentStart="true"
|
|
||||||
android:layout_centerInParent="true"
|
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:layout_marginLeft="@dimen/margin_left"
|
android:paddingLeft="@dimen/material_component_lists_icon_left_padding"
|
||||||
android:layout_marginStart="@dimen/margin_left"
|
android:paddingStart="@dimen/material_component_lists_icon_left_padding"
|
||||||
android:layout_marginRight="@dimen/margin_right"
|
android:paddingRight="0dp"
|
||||||
android:layout_marginEnd="@dimen/margin_right"/>
|
android:paddingEnd="0dp"/>
|
||||||
|
|
||||||
<android.support.v7.widget.AppCompatImageView
|
|
||||||
android:id="@+id/reorder"
|
|
||||||
android:layout_width="50dp"
|
|
||||||
android:layout_height="50dp"
|
|
||||||
android:layout_marginLeft="@dimen/margin_left"
|
|
||||||
android:layout_marginStart="@dimen/margin_left"
|
|
||||||
android:layout_marginRight="@dimen/margin_right"
|
|
||||||
android:layout_marginEnd="@dimen/margin_right"
|
|
||||||
android:scaleType="center"
|
|
||||||
android:layout_centerInParent="true"
|
|
||||||
android:layout_alignParentRight="true"
|
|
||||||
android:layout_alignParentEnd="true"
|
|
||||||
app:srcCompat="@drawable/ic_reorder_grey_24dp"
|
|
||||||
android:tint="?android:attr/textColorPrimary"/>
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/title"
|
android:id="@+id/title"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_toRightOf="@id/image"
|
android:layout_marginLeft="@dimen/material_component_lists_text_left_padding"
|
||||||
android:layout_toEndOf="@id/image"
|
android:layout_marginStart="@dimen/material_component_lists_text_left_padding"
|
||||||
android:layout_toLeftOf="@id/reorder"
|
android:layout_marginRight="@dimen/material_component_lists_single_line_with_avatar_height"
|
||||||
android:layout_toStartOf="@id/reorder"
|
android:layout_marginEnd="@dimen/material_component_lists_single_line_with_avatar_height"
|
||||||
android:layout_centerInParent="true"
|
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
android:maxLines="1"
|
android:maxLines="1"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
android:textAppearance="@style/TextAppearance.Regular.SubHeading"
|
android:textAppearance="@style/TextAppearance.Regular.SubHeading"
|
||||||
tools:text="Title"/>
|
tools:text="Title"/>
|
||||||
|
|
||||||
</RelativeLayout>
|
<ImageView
|
||||||
|
android:id="@+id/reorder"
|
||||||
|
android:layout_width="@dimen/material_component_lists_single_line_with_avatar_height"
|
||||||
|
android:layout_height="@dimen/material_component_lists_single_line_with_avatar_height"
|
||||||
|
android:scaleType="center"
|
||||||
|
android:layout_gravity="end"
|
||||||
|
app:srcCompat="@drawable/ic_reorder_grey_24dp"
|
||||||
|
android:tint="?android:attr/textColorPrimary"/>
|
||||||
|
|
||||||
|
</FrameLayout>
|
8
app/src/main/res/layout/library_drawer.xml
Normal file
8
app/src/main/res/layout/library_drawer.xml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<eu.kanade.tachiyomi.ui.library.LibraryNavigationView
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/nav_view2"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_gravity="end"
|
||||||
|
android:fitsSystemWindows="false" />
|
23
app/src/main/res/layout/navigation_view_checkbox.xml
Normal file
23
app/src/main/res/layout/navigation_view_checkbox.xml
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="?attr/listPreferredItemHeightSmall"
|
||||||
|
android:paddingLeft="?attr/listPreferredItemPaddingLeft"
|
||||||
|
android:paddingRight="?attr/listPreferredItemPaddingRight"
|
||||||
|
android:background="?attr/selectableItemBackground"
|
||||||
|
android:focusable="true">
|
||||||
|
|
||||||
|
<CheckBox
|
||||||
|
android:id="@+id/nav_view_item"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:paddingLeft="@dimen/material_component_lists_icon_left_padding"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:gravity="center_vertical|start"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:clickable="false"
|
||||||
|
android:textAppearance="@style/TextAppearance.AppCompat.Body2" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
21
app/src/main/res/layout/navigation_view_checkedtext.xml
Normal file
21
app/src/main/res/layout/navigation_view_checkedtext.xml
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="?attr/listPreferredItemHeightSmall"
|
||||||
|
android:paddingLeft="?attr/listPreferredItemPaddingLeft"
|
||||||
|
android:paddingRight="?attr/listPreferredItemPaddingRight"
|
||||||
|
android:background="?attr/selectableItemBackground"
|
||||||
|
android:focusable="true">
|
||||||
|
|
||||||
|
<CheckedTextView
|
||||||
|
android:id="@+id/nav_view_item"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:drawablePadding="@dimen/material_component_lists_icon_left_padding"
|
||||||
|
android:gravity="center_vertical|start"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textAppearance="@style/TextAppearance.AppCompat.Body2" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
23
app/src/main/res/layout/navigation_view_radio.xml
Normal file
23
app/src/main/res/layout/navigation_view_radio.xml
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="?attr/listPreferredItemHeightSmall"
|
||||||
|
android:paddingLeft="?attr/listPreferredItemPaddingLeft"
|
||||||
|
android:paddingRight="?attr/listPreferredItemPaddingRight"
|
||||||
|
android:background="?attr/selectableItemBackground"
|
||||||
|
android:focusable="true">
|
||||||
|
|
||||||
|
<RadioButton
|
||||||
|
android:id="@+id/nav_view_item"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:paddingLeft="@dimen/material_component_lists_icon_left_padding"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:gravity="center_vertical|start"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:clickable="false"
|
||||||
|
android:textAppearance="@style/TextAppearance.AppCompat.Body2" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
@ -6,6 +6,11 @@
|
|||||||
android:title="@string/action_download"
|
android:title="@string/action_download"
|
||||||
android:visible="true" />
|
android:visible="true" />
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_delete"
|
||||||
|
android:title="@string/action_delete"
|
||||||
|
android:visible="false"/>
|
||||||
|
|
||||||
<item android:id="@+id/action_bookmark"
|
<item android:id="@+id/action_bookmark"
|
||||||
android:title="@string/action_bookmark"
|
android:title="@string/action_bookmark"
|
||||||
android:visible="true" />
|
android:visible="true" />
|
||||||
@ -14,10 +19,6 @@
|
|||||||
android:title="@string/action_remove_bookmark"
|
android:title="@string/action_remove_bookmark"
|
||||||
android:visible="true" />
|
android:visible="true" />
|
||||||
|
|
||||||
<item android:id="@+id/action_delete"
|
|
||||||
android:title="@string/action_delete"
|
|
||||||
android:visible="false" />
|
|
||||||
|
|
||||||
<item android:id="@+id/action_mark_as_read"
|
<item android:id="@+id/action_mark_as_read"
|
||||||
android:title="@string/action_mark_as_read" />
|
android:title="@string/action_mark_as_read" />
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools" tools:context=".MainActivity">
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
tools:context=".MainActivity">
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/action_search"
|
android:id="@+id/action_search"
|
||||||
@ -13,36 +14,17 @@
|
|||||||
android:id="@+id/action_filter"
|
android:id="@+id/action_filter"
|
||||||
android:icon="@drawable/ic_filter_list_white_24dp"
|
android:icon="@drawable/ic_filter_list_white_24dp"
|
||||||
android:title="@string/action_filter"
|
android:title="@string/action_filter"
|
||||||
app:showAsAction="ifRoom">
|
app:showAsAction="ifRoom"/>
|
||||||
<menu>
|
|
||||||
<item
|
|
||||||
android:id="@+id/action_filter_downloaded"
|
|
||||||
android:checkable="true"
|
|
||||||
android:title="@string/action_filter_downloaded"/>
|
|
||||||
<item
|
|
||||||
android:id="@+id/action_filter_unread"
|
|
||||||
android:checkable="true"
|
|
||||||
android:title="@string/action_filter_unread"/>
|
|
||||||
<item
|
|
||||||
android:id="@+id/action_filter_empty"
|
|
||||||
android:title="@string/action_filter_empty"/>
|
|
||||||
</menu>
|
|
||||||
</item>
|
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/action_update_library"
|
android:id="@+id/action_update_library"
|
||||||
android:icon="@drawable/ic_refresh_white_24dp"
|
android:icon="@drawable/ic_refresh_white_24dp"
|
||||||
android:title="@string/action_update_library"
|
android:title="@string/action_update_library"
|
||||||
app:showAsAction="ifRoom" />
|
app:showAsAction="ifRoom"/>
|
||||||
|
|
||||||
<item
|
|
||||||
android:id="@+id/action_library_display_mode"
|
|
||||||
android:title="@string/action_display_mode"
|
|
||||||
app:showAsAction="never"/>
|
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/action_edit_categories"
|
android:id="@+id/action_edit_categories"
|
||||||
android:title="@string/action_edit_categories"
|
android:title="@string/action_edit_categories"
|
||||||
app:showAsAction="never" />
|
app:showAsAction="never"/>
|
||||||
|
|
||||||
</menu>
|
</menu>
|
||||||
|
@ -1,6 +1,22 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<changelog bulletedList="true">
|
<changelog bulletedList="true">
|
||||||
|
|
||||||
|
<changelogversion versionName="v0.4.1" changeDate="">
|
||||||
|
<changelogtext>Added an app's language selector.</changelogtext>
|
||||||
|
|
||||||
|
<changelogtext>Added options to sort the library and merged them with the filters.</changelogtext>
|
||||||
|
|
||||||
|
<changelogtext>Added an option to automatically download chapters.</changelogtext>
|
||||||
|
|
||||||
|
<changelogtext>Fixed performance issues when using a custom downloads directory, especially in the library updates tab.</changelogtext>
|
||||||
|
|
||||||
|
<changelogtext>Fixed gesture conflicts with the contextual menu and the webtoon reader.</changelogtext>
|
||||||
|
|
||||||
|
<changelogtext>Fixed wrong page direction when using volume keys for the right to left reader.</changelogtext>
|
||||||
|
|
||||||
|
<changelogtext>Fixed many crashes.</changelogtext>
|
||||||
|
</changelogversion>
|
||||||
|
|
||||||
<changelogversion versionName="v0.4.0" changeDate="">
|
<changelogversion versionName="v0.4.0" changeDate="">
|
||||||
<changelogtext>The download manager has been rewritten and it's possible some of your downloads
|
<changelogtext>The download manager has been rewritten and it's possible some of your downloads
|
||||||
aren't recognized anymore. It's recommended to manually delete everything and start over.
|
aren't recognized anymore. It's recommended to manually delete everything and start over.
|
||||||
|
@ -188,4 +188,20 @@
|
|||||||
<item>2</item>
|
<item>2</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="languages">
|
||||||
|
<item>@string/system_default</item>
|
||||||
|
<item>@string/english</item>
|
||||||
|
<item>@string/spanish</item>
|
||||||
|
<item>@string/italian</item>
|
||||||
|
<item>@string/portuguese</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="languages_values">
|
||||||
|
<item>0</item>
|
||||||
|
<item>1</item>
|
||||||
|
<item>2</item>
|
||||||
|
<item>3</item>
|
||||||
|
<item>4</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
@ -21,6 +21,7 @@
|
|||||||
<string name="pref_theme_key">pref_theme_key</string>
|
<string name="pref_theme_key">pref_theme_key</string>
|
||||||
<string name="pref_library_update_restriction_key">library_update_restriction</string>
|
<string name="pref_library_update_restriction_key">library_update_restriction</string>
|
||||||
<string name="pref_start_screen_key">start_screen</string>
|
<string name="pref_start_screen_key">start_screen</string>
|
||||||
|
<string name="pref_language_key">language</string>
|
||||||
|
|
||||||
<string name="pref_default_viewer_key">pref_default_viewer_key</string>
|
<string name="pref_default_viewer_key">pref_default_viewer_key</string>
|
||||||
<string name="pref_image_scale_type_key">pref_image_scale_type_key</string>
|
<string name="pref_image_scale_type_key">pref_image_scale_type_key</string>
|
||||||
@ -41,6 +42,7 @@
|
|||||||
<string name="pref_read_with_tapping_key">reader_tap</string>
|
<string name="pref_read_with_tapping_key">reader_tap</string>
|
||||||
<string name="pref_filter_downloaded_key">pref_filter_downloaded_key</string>
|
<string name="pref_filter_downloaded_key">pref_filter_downloaded_key</string>
|
||||||
<string name="pref_filter_unread_key">pref_filter_unread_key</string>
|
<string name="pref_filter_unread_key">pref_filter_unread_key</string>
|
||||||
|
<string name="pref_library_sorting_mode_key">library_sorting_mode</string>
|
||||||
|
|
||||||
<string name="pref_download_directory_key">download_directory</string>
|
<string name="pref_download_directory_key">download_directory</string>
|
||||||
<string name="pref_download_slots_key">pref_download_slots_key</string>
|
<string name="pref_download_slots_key">pref_download_slots_key</string>
|
||||||
@ -64,6 +66,8 @@
|
|||||||
<string name="pref_display_catalogue_as_list">pref_display_catalogue_as_list</string>
|
<string name="pref_display_catalogue_as_list">pref_display_catalogue_as_list</string>
|
||||||
<string name="pref_last_catalogue_source_key">pref_last_catalogue_source_key</string>
|
<string name="pref_last_catalogue_source_key">pref_last_catalogue_source_key</string>
|
||||||
|
|
||||||
|
<string name="pref_download_new_key">download_new</string>
|
||||||
|
|
||||||
<!-- String Fonts -->
|
<!-- String Fonts -->
|
||||||
<string name="font_roboto_medium">sans-serif</string>
|
<string name="font_roboto_medium">sans-serif</string>
|
||||||
<string name="font_roboto_regular">sans-serif</string>
|
<string name="font_roboto_regular">sans-serif</string>
|
||||||
|
@ -23,6 +23,9 @@
|
|||||||
<string name="action_filter_unread">Unread</string>
|
<string name="action_filter_unread">Unread</string>
|
||||||
<string name="action_filter_read">Read</string>
|
<string name="action_filter_read">Read</string>
|
||||||
<string name="action_filter_empty">Remove filter</string>
|
<string name="action_filter_empty">Remove filter</string>
|
||||||
|
<string name="action_sort_alpha">Alphabetically</string>
|
||||||
|
<string name="action_sort_last_read">Last read</string>
|
||||||
|
<string name="action_sort_last_updated">Last updated</string>
|
||||||
<string name="action_search">Search</string>
|
<string name="action_search">Search</string>
|
||||||
<string name="action_select_all">Select all</string>
|
<string name="action_select_all">Select all</string>
|
||||||
<string name="action_mark_as_read">Mark as read</string>
|
<string name="action_mark_as_read">Mark as read</string>
|
||||||
@ -57,6 +60,9 @@
|
|||||||
<string name="action_open_in_browser">Open in browser</string>
|
<string name="action_open_in_browser">Open in browser</string>
|
||||||
<string name="action_add_to_home_screen">Add to home screen</string>
|
<string name="action_add_to_home_screen">Add to home screen</string>
|
||||||
<string name="action_display_mode">Change display mode</string>
|
<string name="action_display_mode">Change display mode</string>
|
||||||
|
<string name="action_display">Display</string>
|
||||||
|
<string name="action_display_grid">Grid</string>
|
||||||
|
<string name="action_display_list">List</string>
|
||||||
<string name="action_set_filter">Set filter</string>
|
<string name="action_set_filter">Set filter</string>
|
||||||
<string name="action_cancel">Cancel</string>
|
<string name="action_cancel">Cancel</string>
|
||||||
<string name="action_sort">Sort</string>
|
<string name="action_sort">Sort</string>
|
||||||
@ -105,6 +111,14 @@
|
|||||||
<string name="light_theme">Main theme</string>
|
<string name="light_theme">Main theme</string>
|
||||||
<string name="dark_theme">Dark theme</string>
|
<string name="dark_theme">Dark theme</string>
|
||||||
<string name="pref_start_screen">Start screen</string>
|
<string name="pref_start_screen">Start screen</string>
|
||||||
|
<string name="pref_language">Language</string>
|
||||||
|
|
||||||
|
<!-- Languages -->
|
||||||
|
<string name="system_default">System Default</string>
|
||||||
|
<string name="english">English</string>
|
||||||
|
<string name="spanish">Spanish</string>
|
||||||
|
<string name="italian">Italian</string>
|
||||||
|
<string name="portuguese">Portuguese</string>
|
||||||
|
|
||||||
<!-- Reader section -->
|
<!-- Reader section -->
|
||||||
<string name="pref_fullscreen">Fullscreen</string>
|
<string name="pref_fullscreen">Fullscreen</string>
|
||||||
@ -163,6 +177,7 @@
|
|||||||
<string name="third_to_last">Third to last chapter</string>
|
<string name="third_to_last">Third to last chapter</string>
|
||||||
<string name="fourth_to_last">Fourth to last chapter</string>
|
<string name="fourth_to_last">Fourth to last chapter</string>
|
||||||
<string name="fifth_to_last">Fifth to last chapter</string>
|
<string name="fifth_to_last">Fifth to last chapter</string>
|
||||||
|
<string name="pref_download_new">Download new chapters</string>
|
||||||
|
|
||||||
<!-- Sync section -->
|
<!-- Sync section -->
|
||||||
<string name="services">Services</string>
|
<string name="services">Services</string>
|
||||||
@ -211,6 +226,9 @@
|
|||||||
<string name="select_source">Select a source</string>
|
<string name="select_source">Select a source</string>
|
||||||
<string name="no_valid_sources">Please enable at least one valid source</string>
|
<string name="no_valid_sources">Please enable at least one valid source</string>
|
||||||
|
|
||||||
|
<!-- Manga activity -->
|
||||||
|
<string name="manga_not_in_db">This manga was removed from the database!</string>
|
||||||
|
|
||||||
<!-- Manga info fragment -->
|
<!-- Manga info fragment -->
|
||||||
<string name="manga_detail_tab">Info</string>
|
<string name="manga_detail_tab">Info</string>
|
||||||
<string name="description">Description</string>
|
<string name="description">Description</string>
|
||||||
@ -293,6 +311,7 @@
|
|||||||
<string name="no_previous_chapter">Previous chapter not found</string>
|
<string name="no_previous_chapter">Previous chapter not found</string>
|
||||||
<string name="decode_image_error">Image could not be loaded.\nTry changing the image decoder or with one of the options below</string>
|
<string name="decode_image_error">Image could not be loaded.\nTry changing the image decoder or with one of the options below</string>
|
||||||
<string name="confirm_update_manga_sync">Update last chapter read in enabled services to %1$d?</string>
|
<string name="confirm_update_manga_sync">Update last chapter read in enabled services to %1$d?</string>
|
||||||
|
<string name="confirm_set_image_as_cover">Do you want to set this image as the cover?</string>
|
||||||
<string name="viewer_for_this_series">Viewer for this series</string>
|
<string name="viewer_for_this_series">Viewer for this series</string>
|
||||||
|
|
||||||
<!-- Backup fragment -->
|
<!-- Backup fragment -->
|
||||||
|
@ -44,6 +44,15 @@
|
|||||||
android:summary="%s"
|
android:summary="%s"
|
||||||
android:title="@string/pref_remove_after_read" />
|
android:title="@string/pref_remove_after_read" />
|
||||||
|
|
||||||
|
<PreferenceCategory
|
||||||
|
android:persistent="false"
|
||||||
|
android:title="@string/pref_download_new" />
|
||||||
|
|
||||||
|
<SwitchPreference
|
||||||
|
android:defaultValue="false"
|
||||||
|
android:key="@string/pref_download_new_key"
|
||||||
|
android:title="@string/pref_download_new"/>
|
||||||
|
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
||||||
|
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
@ -10,6 +10,14 @@
|
|||||||
android:title="@string/pref_category_general"
|
android:title="@string/pref_category_general"
|
||||||
app:asp_tintEnabled="true">
|
app:asp_tintEnabled="true">
|
||||||
|
|
||||||
|
<eu.kanade.tachiyomi.widget.preference.IntListPreference
|
||||||
|
android:defaultValue="0"
|
||||||
|
android:entries="@array/languages"
|
||||||
|
android:entryValues="@array/languages_values"
|
||||||
|
android:key="@string/pref_language_key"
|
||||||
|
android:summary="%s"
|
||||||
|
android:title="@string/pref_language" />
|
||||||
|
|
||||||
<eu.kanade.tachiyomi.widget.preference.IntListPreference
|
<eu.kanade.tachiyomi.widget.preference.IntListPreference
|
||||||
android:defaultValue="1"
|
android:defaultValue="1"
|
||||||
android:entries="@array/themes"
|
android:entries="@array/themes"
|
||||||
|
@ -6,7 +6,7 @@ buildscript {
|
|||||||
jcenter()
|
jcenter()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:2.2.2'
|
classpath 'com.android.tools.build:gradle:2.2.3'
|
||||||
classpath 'com.github.ben-manes:gradle-versions-plugin:0.13.0'
|
classpath 'com.github.ben-manes:gradle-versions-plugin:0.13.0'
|
||||||
// NOTE: Do not place your application dependencies here; they belong
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
// in the individual module build.gradle files
|
// in the individual module build.gradle files
|
||||||
|
Reference in New Issue
Block a user