Merge branch 'master' of https://github.com/inorichi/tachiyomi
Fix back button in library search
This commit is contained in:
commit
b766ddea54
@ -12,7 +12,7 @@ if [ -z "$TRAVIS_TAG" ]; then
|
|||||||
else
|
else
|
||||||
./gradlew clean assembleStandardRelease
|
./gradlew clean assembleStandardRelease
|
||||||
|
|
||||||
TOOLS="${ANDROID_HOME}/build-tools/26.0.1"
|
TOOLS="$(ls -d ${ANDROID_HOME}/build-tools/* | tail -1)"
|
||||||
export ARTIFACT="tachiyomi-${TRAVIS_TAG}.apk"
|
export ARTIFACT="tachiyomi-${TRAVIS_TAG}.apk"
|
||||||
|
|
||||||
${TOOLS}/zipalign -v -p 4 app/build/outputs/apk/standard/release/app-standard-release-unsigned.apk app-aligned.apk
|
${TOOLS}/zipalign -v -p 4 app/build/outputs/apk/standard/release/app-standard-release-unsigned.apk app-aligned.apk
|
||||||
|
@ -32,7 +32,7 @@ ext {
|
|||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 26
|
compileSdkVersion 26
|
||||||
buildToolsVersion "26.0.2"
|
buildToolsVersion "27.0.1"
|
||||||
publishNonDefault true
|
publishNonDefault true
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
@ -111,7 +111,7 @@ dependencies {
|
|||||||
implementation 'com.github.inorichi:junrar-android:634c1f5'
|
implementation 'com.github.inorichi:junrar-android:634c1f5'
|
||||||
|
|
||||||
// Android support library
|
// Android support library
|
||||||
final support_library_version = '26.1.0'
|
final support_library_version = '27.0.1'
|
||||||
implementation "com.android.support:support-v4:$support_library_version"
|
implementation "com.android.support:support-v4:$support_library_version"
|
||||||
implementation "com.android.support:appcompat-v7:$support_library_version"
|
implementation "com.android.support:appcompat-v7:$support_library_version"
|
||||||
implementation "com.android.support:cardview-v7:$support_library_version"
|
implementation "com.android.support:cardview-v7:$support_library_version"
|
||||||
@ -127,13 +127,13 @@ dependencies {
|
|||||||
|
|
||||||
// ReactiveX
|
// ReactiveX
|
||||||
implementation 'io.reactivex:rxandroid:1.2.1'
|
implementation 'io.reactivex:rxandroid:1.2.1'
|
||||||
implementation 'io.reactivex:rxjava:1.3.3'
|
implementation 'io.reactivex:rxjava:1.3.4'
|
||||||
implementation 'com.jakewharton.rxrelay:rxrelay:1.2.0'
|
implementation 'com.jakewharton.rxrelay:rxrelay:1.2.0'
|
||||||
implementation 'com.f2prateek.rx.preferences:rx-preferences:1.0.2'
|
implementation 'com.f2prateek.rx.preferences:rx-preferences:1.0.2'
|
||||||
implementation 'com.github.pwittchen:reactivenetwork:0.7.0'
|
implementation 'com.github.pwittchen:reactivenetwork:0.7.0'
|
||||||
|
|
||||||
// Network client
|
// Network client
|
||||||
implementation "com.squareup.okhttp3:okhttp:3.9.0"
|
implementation "com.squareup.okhttp3:okhttp:3.9.1"
|
||||||
implementation 'com.squareup.okio:okio:1.13.0'
|
implementation 'com.squareup.okio:okio:1.13.0'
|
||||||
|
|
||||||
// REST
|
// REST
|
||||||
@ -250,7 +250,7 @@ dependencies {
|
|||||||
}
|
}
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
ext.kotlin_version = '1.1.51'
|
ext.kotlin_version = '1.1.61'
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,7 @@ class BackupCreateService : IntentService(NAME) {
|
|||||||
* Make a backup from library
|
* Make a backup from library
|
||||||
*
|
*
|
||||||
* @param context context of application
|
* @param context context of application
|
||||||
* @param path path of Uri
|
* @param uri path of Uri
|
||||||
* @param flags determines what to backup
|
* @param flags determines what to backup
|
||||||
* @param isJob backup called from job
|
* @param isJob backup called from job
|
||||||
*/
|
*/
|
||||||
@ -80,7 +80,7 @@ class BackupCreateService : IntentService(NAME) {
|
|||||||
* @param uri path of Uri
|
* @param uri path of Uri
|
||||||
* @param isJob backup called from job
|
* @param isJob backup called from job
|
||||||
*/
|
*/
|
||||||
fun createBackupFromApp(uri: Uri, flags: Int, isJob: Boolean) {
|
private fun createBackupFromApp(uri: Uri, flags: Int, isJob: Boolean) {
|
||||||
// Create root object
|
// Create root object
|
||||||
val root = JsonObject()
|
val root = JsonObject()
|
||||||
|
|
||||||
@ -113,8 +113,9 @@ class BackupCreateService : IntentService(NAME) {
|
|||||||
try {
|
try {
|
||||||
// When BackupCreatorJob
|
// When BackupCreatorJob
|
||||||
if (isJob) {
|
if (isJob) {
|
||||||
// Get dir of file
|
// Get dir of file and create
|
||||||
val dir = UniFile.fromUri(this, uri)
|
var dir = UniFile.fromUri(this, uri)
|
||||||
|
dir = dir.createDirectory("automatic")
|
||||||
|
|
||||||
// Delete older backups
|
// Delete older backups
|
||||||
val numberOfBackups = backupManager.numberOfBackups()
|
val numberOfBackups = backupManager.numberOfBackups()
|
||||||
|
@ -8,13 +8,12 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
|||||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
class BackupCreatorJob : Job() {
|
class BackupCreatorJob : Job() {
|
||||||
|
|
||||||
override fun onRunJob(params: Params): Result {
|
override fun onRunJob(params: Params): Result {
|
||||||
val preferences = Injekt.get<PreferencesHelper>()
|
val preferences = Injekt.get<PreferencesHelper>()
|
||||||
val uri = Uri.fromFile(File(preferences.backupsDirectory().getOrDefault()))
|
val uri = Uri.parse(preferences.backupsDirectory().getOrDefault())
|
||||||
val flags = BackupCreateService.BACKUP_ALL
|
val flags = BackupCreateService.BACKUP_ALL
|
||||||
BackupCreateService.makeBackup(context, uri, flags, true)
|
BackupCreateService.makeBackup(context, uri, flags, true)
|
||||||
return Result.SUCCESS
|
return Result.SUCCESS
|
||||||
|
@ -0,0 +1,252 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.download
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.Uri
|
||||||
|
import com.hippo.unifile.UniFile
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
|
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
||||||
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cache where we dump the downloads directory from the filesystem. This class is needed because
|
||||||
|
* directory checking is expensive and it slowdowns the app. The cache is invalidated by the time
|
||||||
|
* defined in [renewInterval] as we don't have any control over the filesystem and the user can
|
||||||
|
* delete the folders at any time without the app noticing.
|
||||||
|
*
|
||||||
|
* @param context the application context.
|
||||||
|
* @param provider the downloads directories provider.
|
||||||
|
* @param sourceManager the source manager.
|
||||||
|
* @param preferences the preferences of the app.
|
||||||
|
*/
|
||||||
|
class DownloadCache(private val context: Context,
|
||||||
|
private val provider: DownloadProvider,
|
||||||
|
private val sourceManager: SourceManager = Injekt.get(),
|
||||||
|
private val preferences: PreferencesHelper = Injekt.get()) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The interval after which this cache should be invalidated. 1 hour shouldn't cause major
|
||||||
|
* issues, as the cache is only used for UI feedback.
|
||||||
|
*/
|
||||||
|
private val renewInterval = TimeUnit.HOURS.toMillis(1)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The last time the cache was refreshed.
|
||||||
|
*/
|
||||||
|
private var lastRenew = 0L
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The root directory for downloads.
|
||||||
|
*/
|
||||||
|
private var rootDir = RootDirectory(getDirectoryFromPreference())
|
||||||
|
|
||||||
|
init {
|
||||||
|
preferences.downloadsDirectory().asObservable()
|
||||||
|
.skip(1)
|
||||||
|
.subscribe {
|
||||||
|
lastRenew = 0L // invalidate cache
|
||||||
|
rootDir = RootDirectory(getDirectoryFromPreference())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the downloads directory from the user's preferences.
|
||||||
|
*/
|
||||||
|
private fun getDirectoryFromPreference(): UniFile {
|
||||||
|
val dir = preferences.downloadsDirectory().getOrDefault()
|
||||||
|
return UniFile.fromUri(context, Uri.parse(dir))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the chapter is downloaded.
|
||||||
|
*
|
||||||
|
* @param chapter the chapter to check.
|
||||||
|
* @param manga the manga of the chapter.
|
||||||
|
* @param skipCache whether to skip the directory cache and check in the filesystem.
|
||||||
|
*/
|
||||||
|
fun isChapterDownloaded(chapter: Chapter, manga: Manga, skipCache: Boolean): Boolean {
|
||||||
|
if (skipCache) {
|
||||||
|
val source = sourceManager.get(manga.source) ?: return false
|
||||||
|
return provider.findChapterDir(chapter, manga, source) != null
|
||||||
|
}
|
||||||
|
|
||||||
|
checkRenew()
|
||||||
|
|
||||||
|
val sourceDir = rootDir.files[manga.source]
|
||||||
|
if (sourceDir != null) {
|
||||||
|
val mangaDir = sourceDir.files[provider.getMangaDirName(manga)]
|
||||||
|
if (mangaDir != null) {
|
||||||
|
return provider.getChapterDirName(chapter) in mangaDir.files
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the amount of downloaded chapters for a manga.
|
||||||
|
*
|
||||||
|
* @param manga the manga to check.
|
||||||
|
*/
|
||||||
|
fun getDownloadCount(manga: Manga): Int {
|
||||||
|
checkRenew()
|
||||||
|
|
||||||
|
val sourceDir = rootDir.files[manga.source]
|
||||||
|
if (sourceDir != null) {
|
||||||
|
val mangaDir = sourceDir.files[provider.getMangaDirName(manga)]
|
||||||
|
if (mangaDir != null) {
|
||||||
|
return mangaDir.files.size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the cache needs a renewal and performs it if needed.
|
||||||
|
*/
|
||||||
|
@Synchronized
|
||||||
|
private fun checkRenew() {
|
||||||
|
if (lastRenew + renewInterval < System.currentTimeMillis()) {
|
||||||
|
renew()
|
||||||
|
lastRenew = System.currentTimeMillis()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renews the downloads cache.
|
||||||
|
*/
|
||||||
|
private fun renew() {
|
||||||
|
val onlineSources = sourceManager.getOnlineSources()
|
||||||
|
|
||||||
|
val sourceDirs = rootDir.dir.listFiles()
|
||||||
|
.orEmpty()
|
||||||
|
.associate { it.name to SourceDirectory(it) }
|
||||||
|
.mapNotNullKeys { entry ->
|
||||||
|
onlineSources.find { provider.getSourceDirName(it) == entry.key }?.id
|
||||||
|
}
|
||||||
|
|
||||||
|
rootDir.files = sourceDirs
|
||||||
|
|
||||||
|
sourceDirs.values.forEach { sourceDir ->
|
||||||
|
val mangaDirs = sourceDir.dir.listFiles()
|
||||||
|
.orEmpty()
|
||||||
|
.associateNotNullKeys { it.name to MangaDirectory(it) }
|
||||||
|
|
||||||
|
sourceDir.files = mangaDirs
|
||||||
|
|
||||||
|
mangaDirs.values.forEach { mangaDir ->
|
||||||
|
val chapterDirs = mangaDir.dir.listFiles()
|
||||||
|
.orEmpty()
|
||||||
|
.mapNotNull { it.name }
|
||||||
|
.toHashSet()
|
||||||
|
|
||||||
|
mangaDir.files = chapterDirs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a chapter that has just been download to this cache.
|
||||||
|
*
|
||||||
|
* @param chapterDirName the downloaded chapter's directory name.
|
||||||
|
* @param mangaUniFile the directory of the manga.
|
||||||
|
* @param manga the manga of the chapter.
|
||||||
|
*/
|
||||||
|
@Synchronized
|
||||||
|
fun addChapter(chapterDirName: String, mangaUniFile: UniFile, manga: Manga) {
|
||||||
|
// Retrieve the cached source directory or cache a new one
|
||||||
|
var sourceDir = rootDir.files[manga.source]
|
||||||
|
if (sourceDir == null) {
|
||||||
|
val source = sourceManager.get(manga.source) ?: return
|
||||||
|
val sourceUniFile = provider.findSourceDir(source) ?: return
|
||||||
|
sourceDir = SourceDirectory(sourceUniFile)
|
||||||
|
rootDir.files += manga.source to sourceDir
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve the cached manga directory or cache a new one
|
||||||
|
val mangaDirName = provider.getMangaDirName(manga)
|
||||||
|
var mangaDir = sourceDir.files[mangaDirName]
|
||||||
|
if (mangaDir == null) {
|
||||||
|
mangaDir = MangaDirectory(mangaUniFile)
|
||||||
|
sourceDir.files += mangaDirName to mangaDir
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save the chapter directory
|
||||||
|
mangaDir.files += chapterDirName
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a chapter that has been deleted from this cache.
|
||||||
|
*
|
||||||
|
* @param chapter the chapter to remove.
|
||||||
|
* @param manga the manga of the chapter.
|
||||||
|
*/
|
||||||
|
@Synchronized
|
||||||
|
fun removeChapter(chapter: Chapter, manga: Manga) {
|
||||||
|
val sourceDir = rootDir.files[manga.source] ?: return
|
||||||
|
val mangaDir = sourceDir.files[provider.getMangaDirName(manga)] ?: return
|
||||||
|
val chapterDirName = provider.getChapterDirName(chapter)
|
||||||
|
if (chapterDirName in mangaDir.files) {
|
||||||
|
mangaDir.files -= chapterDirName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a manga that has been deleted from this cache.
|
||||||
|
*
|
||||||
|
* @param manga the manga to remove.
|
||||||
|
*/
|
||||||
|
@Synchronized
|
||||||
|
fun removeManga(manga: Manga) {
|
||||||
|
val sourceDir = rootDir.files[manga.source] ?: return
|
||||||
|
val mangaDirName = provider.getMangaDirName(manga)
|
||||||
|
if (mangaDirName in sourceDir.files) {
|
||||||
|
sourceDir.files -= mangaDirName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class to store the files under the root downloads directory.
|
||||||
|
*/
|
||||||
|
private class RootDirectory(val dir: UniFile,
|
||||||
|
var files: Map<Long, SourceDirectory> = hashMapOf())
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class to store the files under a source directory.
|
||||||
|
*/
|
||||||
|
private class SourceDirectory(val dir: UniFile,
|
||||||
|
var files: Map<String, MangaDirectory> = hashMapOf())
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class to store the files under a manga directory.
|
||||||
|
*/
|
||||||
|
private class MangaDirectory(val dir: UniFile,
|
||||||
|
var files: Set<String> = hashSetOf())
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a new map containing only the key entries of [transform] that are not null.
|
||||||
|
*/
|
||||||
|
private inline fun <K, V, R> Map<out K, V>.mapNotNullKeys(transform: (Map.Entry<K?, V>) -> R?): Map<R, V> {
|
||||||
|
val destination = LinkedHashMap<R, V>()
|
||||||
|
forEach { element -> transform(element)?.let { destination.put(it, element.value) } }
|
||||||
|
return destination
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a map from a list containing only the key entries of [transform] that are not null.
|
||||||
|
*/
|
||||||
|
private inline fun <T, K, V> Array<T>.associateNotNullKeys(transform: (T) -> Pair<K?, V>): Map<K, V> {
|
||||||
|
val destination = LinkedHashMap<K, V>()
|
||||||
|
for (element in this) {
|
||||||
|
val (key, value) = transform(element)
|
||||||
|
if (key != null) {
|
||||||
|
destination.put(key, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return destination
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -24,10 +24,15 @@ class DownloadManager(context: Context) {
|
|||||||
*/
|
*/
|
||||||
private val provider = DownloadProvider(context)
|
private val provider = DownloadProvider(context)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cache of downloaded chapters.
|
||||||
|
*/
|
||||||
|
private val cache = DownloadCache(context, provider)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Downloader whose only task is to download chapters.
|
* Downloader whose only task is to download chapters.
|
||||||
*/
|
*/
|
||||||
private val downloader = Downloader(context, provider)
|
private val downloader = Downloader(context, provider, cache)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Downloads queue, where the pending chapters are stored.
|
* Downloads queue, where the pending chapters are stored.
|
||||||
@ -94,7 +99,7 @@ class DownloadManager(context: Context) {
|
|||||||
* @return an observable containing the list of pages from the chapter.
|
* @return an observable containing the list of pages from the chapter.
|
||||||
*/
|
*/
|
||||||
fun buildPageList(source: Source, manga: Manga, chapter: Chapter): Observable<List<Page>> {
|
fun buildPageList(source: Source, manga: Manga, chapter: Chapter): Observable<List<Page>> {
|
||||||
return buildPageList(provider.findChapterDir(source, manga, chapter))
|
return buildPageList(provider.findChapterDir(chapter, manga, source))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -120,61 +125,45 @@ class DownloadManager(context: Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the directory name for a manga.
|
* Returns true if the chapter is downloaded.
|
||||||
*
|
*
|
||||||
* @param manga the manga to query.
|
* @param chapter the chapter to check.
|
||||||
*/
|
|
||||||
fun getMangaDirName(manga: Manga): String {
|
|
||||||
return provider.getMangaDirName(manga)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the directory name for the given chapter.
|
|
||||||
*
|
|
||||||
* @param chapter the chapter to query.
|
|
||||||
*/
|
|
||||||
fun getChapterDirName(chapter: Chapter): String {
|
|
||||||
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.
|
|
||||||
*
|
|
||||||
* @param source the source of the manga.
|
|
||||||
* @param manga the manga to query.
|
|
||||||
*/
|
|
||||||
fun findMangaDir(source: Source, manga: Manga): UniFile? {
|
|
||||||
return provider.findMangaDir(source, manga)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the directory for the given chapter, if it exists.
|
|
||||||
*
|
|
||||||
* @param source the source of the chapter.
|
|
||||||
* @param manga the manga of the chapter.
|
* @param manga the manga of the chapter.
|
||||||
* @param chapter the chapter to query.
|
* @param skipCache whether to skip the directory cache and check in the filesystem.
|
||||||
*/
|
*/
|
||||||
fun findChapterDir(source: Source, manga: Manga, chapter: Chapter): UniFile? {
|
fun isChapterDownloaded(chapter: Chapter, manga: Manga, skipCache: Boolean = false): Boolean {
|
||||||
return provider.findChapterDir(source, manga, chapter)
|
return cache.isChapterDownloaded(chapter, manga, skipCache)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the amount of downloaded chapters for a manga.
|
||||||
|
*
|
||||||
|
* @param manga the manga to check.
|
||||||
|
*/
|
||||||
|
fun getDownloadCount(manga: Manga): Int {
|
||||||
|
return cache.getDownloadCount(manga)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes the directory of a downloaded chapter.
|
* Deletes the directory of a downloaded chapter.
|
||||||
*
|
*
|
||||||
* @param source the source of the chapter.
|
|
||||||
* @param manga the manga of the chapter.
|
|
||||||
* @param chapter the chapter to delete.
|
* @param chapter the chapter to delete.
|
||||||
|
* @param manga the manga of the chapter.
|
||||||
|
* @param source the source of the chapter.
|
||||||
*/
|
*/
|
||||||
fun deleteChapter(source: Source, manga: Manga, chapter: Chapter) {
|
fun deleteChapter(chapter: Chapter, manga: Manga, source: Source) {
|
||||||
provider.findChapterDir(source, manga, chapter)?.delete()
|
provider.findChapterDir(chapter, manga, source)?.delete()
|
||||||
|
cache.removeChapter(chapter, manga)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the directory of a downloaded manga.
|
||||||
|
*
|
||||||
|
* @param manga the manga to delete.
|
||||||
|
* @param source the source of the manga.
|
||||||
|
*/
|
||||||
|
fun deleteManga(manga: Manga, source: Source) {
|
||||||
|
provider.findMangaDir(manga, source)?.delete()
|
||||||
|
cache.removeManga(manga)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,10 +40,10 @@ class DownloadProvider(private val context: Context) {
|
|||||||
/**
|
/**
|
||||||
* Returns the download directory for a manga. For internal use only.
|
* Returns the download directory for a manga. For internal use only.
|
||||||
*
|
*
|
||||||
* @param source the source of the manga.
|
|
||||||
* @param manga the manga to query.
|
* @param manga the manga to query.
|
||||||
|
* @param source the source of the manga.
|
||||||
*/
|
*/
|
||||||
internal fun getMangaDir(source: Source, manga: Manga): UniFile {
|
internal fun getMangaDir(manga: Manga, source: Source): UniFile {
|
||||||
return downloadsDir
|
return downloadsDir
|
||||||
.createDirectory(getSourceDirName(source))
|
.createDirectory(getSourceDirName(source))
|
||||||
.createDirectory(getMangaDirName(manga))
|
.createDirectory(getMangaDirName(manga))
|
||||||
@ -61,10 +61,10 @@ class DownloadProvider(private val context: Context) {
|
|||||||
/**
|
/**
|
||||||
* Returns the download directory for a manga if it exists.
|
* Returns the download directory for a manga if it exists.
|
||||||
*
|
*
|
||||||
* @param source the source of the manga.
|
|
||||||
* @param manga the manga to query.
|
* @param manga the manga to query.
|
||||||
|
* @param source the source of the manga.
|
||||||
*/
|
*/
|
||||||
fun findMangaDir(source: Source, manga: Manga): UniFile? {
|
fun findMangaDir(manga: Manga, source: Source): UniFile? {
|
||||||
val sourceDir = findSourceDir(source)
|
val sourceDir = findSourceDir(source)
|
||||||
return sourceDir?.findFile(getMangaDirName(manga))
|
return sourceDir?.findFile(getMangaDirName(manga))
|
||||||
}
|
}
|
||||||
@ -72,12 +72,12 @@ class DownloadProvider(private val context: Context) {
|
|||||||
/**
|
/**
|
||||||
* Returns the download directory for a chapter if it exists.
|
* Returns the download directory for a chapter if it exists.
|
||||||
*
|
*
|
||||||
* @param source the source of the chapter.
|
|
||||||
* @param manga the manga of the chapter.
|
|
||||||
* @param chapter the chapter to query.
|
* @param chapter the chapter to query.
|
||||||
|
* @param manga the manga of the chapter.
|
||||||
|
* @param source the source of the chapter.
|
||||||
*/
|
*/
|
||||||
fun findChapterDir(source: Source, manga: Manga, chapter: Chapter): UniFile? {
|
fun findChapterDir(chapter: Chapter, manga: Manga, source: Source): UniFile? {
|
||||||
val mangaDir = findMangaDir(source, manga)
|
val mangaDir = findMangaDir(manga, source)
|
||||||
return mangaDir?.findFile(getChapterDirName(chapter))
|
return mangaDir?.findFile(getChapterDirName(chapter))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,8 +37,11 @@ import uy.kohesive.injekt.injectLazy
|
|||||||
*
|
*
|
||||||
* @param context the application context.
|
* @param context the application context.
|
||||||
* @param provider the downloads directory provider.
|
* @param provider the downloads directory provider.
|
||||||
|
* @param cache the downloads cache, used to add the downloads to the cache after their completion.
|
||||||
*/
|
*/
|
||||||
class Downloader(private val context: Context, private val provider: DownloadProvider) {
|
class Downloader(private val context: Context,
|
||||||
|
private val provider: DownloadProvider,
|
||||||
|
private val cache: DownloadCache) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Store for persisting downloads across restarts.
|
* Store for persisting downloads across restarts.
|
||||||
@ -222,7 +225,7 @@ class Downloader(private val context: Context, private val provider: DownloadPro
|
|||||||
|
|
||||||
// Called in background thread, the operation can be slow with SAF.
|
// Called in background thread, the operation can be slow with SAF.
|
||||||
val chaptersWithoutDir = async {
|
val chaptersWithoutDir = async {
|
||||||
val mangaDir = provider.findMangaDir(source, manga)
|
val mangaDir = provider.findMangaDir(manga, source)
|
||||||
|
|
||||||
chapters
|
chapters
|
||||||
// Avoid downloading chapters with the same name.
|
// Avoid downloading chapters with the same name.
|
||||||
@ -269,7 +272,7 @@ class Downloader(private val context: Context, private val provider: DownloadPro
|
|||||||
*/
|
*/
|
||||||
private fun downloadChapter(download: Download): Observable<Download> {
|
private fun downloadChapter(download: Download): Observable<Download> {
|
||||||
val chapterDirname = provider.getChapterDirName(download.chapter)
|
val chapterDirname = provider.getChapterDirName(download.chapter)
|
||||||
val mangaDir = provider.getMangaDir(download.source, download.manga)
|
val mangaDir = provider.getMangaDir(download.manga, download.source)
|
||||||
val tmpDir = mangaDir.createDirectory("${chapterDirname}_tmp")
|
val tmpDir = mangaDir.createDirectory("${chapterDirname}_tmp")
|
||||||
|
|
||||||
val pageListObservable = if (download.pages == null) {
|
val pageListObservable = if (download.pages == null) {
|
||||||
@ -305,7 +308,7 @@ class Downloader(private val context: Context, private val provider: DownloadPro
|
|||||||
.toList()
|
.toList()
|
||||||
.map { _ -> download }
|
.map { _ -> download }
|
||||||
// Do after download completes
|
// Do after download completes
|
||||||
.doOnNext { ensureSuccessfulDownload(download, tmpDir, chapterDirname) }
|
.doOnNext { ensureSuccessfulDownload(download, mangaDir, tmpDir, chapterDirname) }
|
||||||
// If the page list threw, it will resume here
|
// If the page list threw, it will resume here
|
||||||
.onErrorReturn { error ->
|
.onErrorReturn { error ->
|
||||||
download.status = Download.ERROR
|
download.status = Download.ERROR
|
||||||
@ -411,10 +414,13 @@ class Downloader(private val context: Context, private val provider: DownloadPro
|
|||||||
* Checks if the download was successful.
|
* Checks if the download was successful.
|
||||||
*
|
*
|
||||||
* @param download the download to check.
|
* @param download the download to check.
|
||||||
|
* @param mangaDir the manga directory of the download.
|
||||||
* @param tmpDir the directory where the download is currently stored.
|
* @param tmpDir the directory where the download is currently stored.
|
||||||
* @param dirname the real (non temporary) directory name of the download.
|
* @param dirname the real (non temporary) directory name of the download.
|
||||||
*/
|
*/
|
||||||
private fun ensureSuccessfulDownload(download: Download, tmpDir: UniFile, dirname: String) {
|
private fun ensureSuccessfulDownload(download: Download, mangaDir: UniFile,
|
||||||
|
tmpDir: UniFile, dirname: String) {
|
||||||
|
|
||||||
// Ensure that the chapter folder has all the images.
|
// Ensure that the chapter folder has all the images.
|
||||||
val downloadedImages = tmpDir.listFiles().orEmpty().filterNot { it.name!!.endsWith(".tmp") }
|
val downloadedImages = tmpDir.listFiles().orEmpty().filterNot { it.name!!.endsWith(".tmp") }
|
||||||
|
|
||||||
@ -427,6 +433,7 @@ class Downloader(private val context: Context, private val provider: DownloadPro
|
|||||||
// Only rename the directory if it's downloaded.
|
// Only rename the directory if it's downloaded.
|
||||||
if (download.status == Download.DOWNLOADED) {
|
if (download.status == Download.DOWNLOADED) {
|
||||||
tmpDir.renameTo(dirname)
|
tmpDir.renameTo(dirname)
|
||||||
|
cache.addChapter(dirname, mangaDir, download.manga)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@ import eu.kanade.tachiyomi.util.asJsoup
|
|||||||
import eu.kanade.tachiyomi.util.selectText
|
import eu.kanade.tachiyomi.util.selectText
|
||||||
import okhttp3.FormBody
|
import okhttp3.FormBody
|
||||||
import okhttp3.HttpUrl
|
import okhttp3.HttpUrl
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import org.jsoup.nodes.Document
|
import org.jsoup.nodes.Document
|
||||||
@ -48,6 +49,8 @@ class Batoto : ParsedHttpSource(), LoginSource {
|
|||||||
|
|
||||||
private val staffNotice = Pattern.compile("=+Batoto Staff Notice=+([^=]+)==+", Pattern.CASE_INSENSITIVE)
|
private val staffNotice = Pattern.compile("=+Batoto Staff Notice=+([^=]+)==+", Pattern.CASE_INSENSITIVE)
|
||||||
|
|
||||||
|
override val client: OkHttpClient = network.cloudflareClient
|
||||||
|
|
||||||
override fun headersBuilder() = super.headersBuilder()
|
override fun headersBuilder() = super.headersBuilder()
|
||||||
.add("Cookie", "lang_option=English")
|
.add("Cookie", "lang_option=English")
|
||||||
|
|
||||||
|
@ -79,7 +79,7 @@ class LibraryAdapter(private val controller: LibraryController) : RecyclerViewPa
|
|||||||
/**
|
/**
|
||||||
* Returns the position of the view.
|
* Returns the position of the view.
|
||||||
*/
|
*/
|
||||||
override fun getItemPosition(obj: Any?): Int {
|
override fun getItemPosition(obj: Any): Int {
|
||||||
val view = obj as? LibraryCategoryView ?: return POSITION_NONE
|
val view = obj as? LibraryCategoryView ?: return POSITION_NONE
|
||||||
val index = categories.indexOfFirst { it.id == view.category.id }
|
val index = categories.indexOfFirst { it.id == view.category.id }
|
||||||
return if (index == -1) POSITION_NONE else index
|
return if (index == -1) POSITION_NONE else index
|
||||||
|
@ -192,14 +192,10 @@ class LibraryController(
|
|||||||
super.onChangeStarted(handler, type)
|
super.onChangeStarted(handler, type)
|
||||||
if (type.isEnter) {
|
if (type.isEnter) {
|
||||||
activity?.tabs?.setupWithViewPager(view?.view_pager)
|
activity?.tabs?.setupWithViewPager(view?.view_pager)
|
||||||
|
presenter.subscribeLibrary()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onAttach(view: View) {
|
|
||||||
super.onAttach(view)
|
|
||||||
presenter.subscribeLibrary()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroyView(view: View) {
|
override fun onDestroyView(view: View) {
|
||||||
super.onDestroyView(view)
|
super.onDestroyView(view)
|
||||||
adapter = null
|
adapter = null
|
||||||
@ -464,7 +460,7 @@ class LibraryController(
|
|||||||
presenter.onOpenManga()
|
presenter.onOpenManga()
|
||||||
|
|
||||||
router.pushController(RouterTransaction.with(MangaController(manga))
|
router.pushController(RouterTransaction.with(MangaController(manga))
|
||||||
.pushChangeHandler(FadeChangeHandler())
|
.pushChangeHandler(FadeChangeHandler(false))
|
||||||
.popChangeHandler(FadeChangeHandler()))
|
.popChangeHandler(FadeChangeHandler()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package eu.kanade.tachiyomi.ui.library
|
package eu.kanade.tachiyomi.ui.library
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import com.hippo.unifile.UniFile
|
|
||||||
import com.jakewharton.rxrelay.BehaviorRelay
|
import com.jakewharton.rxrelay.BehaviorRelay
|
||||||
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
|
||||||
@ -107,12 +106,6 @@ class LibraryPresenter(
|
|||||||
* @param map the map to filter.
|
* @param map the map to filter.
|
||||||
*/
|
*/
|
||||||
private fun applyFilters(map: LibraryMap): LibraryMap {
|
private fun applyFilters(map: LibraryMap): LibraryMap {
|
||||||
// Cached list of downloaded manga directories given a source id.
|
|
||||||
val mangaDirsForSource = mutableMapOf<Long, Map<String?, UniFile>>()
|
|
||||||
|
|
||||||
// Cached list of downloaded chapter directories for a manga.
|
|
||||||
val chapterDirectories = mutableMapOf<Long, Boolean>()
|
|
||||||
|
|
||||||
val filterDownloaded = preferences.filterDownloaded().getOrDefault()
|
val filterDownloaded = preferences.filterDownloaded().getOrDefault()
|
||||||
|
|
||||||
val filterUnread = preferences.filterUnread().getOrDefault()
|
val filterUnread = preferences.filterUnread().getOrDefault()
|
||||||
@ -121,7 +114,7 @@ class LibraryPresenter(
|
|||||||
|
|
||||||
val filterFn: (LibraryItem) -> Boolean = f@ { item ->
|
val filterFn: (LibraryItem) -> Boolean = f@ { item ->
|
||||||
// Filter out manga without source.
|
// Filter out manga without source.
|
||||||
val source = sourceManager.get(item.manga.source) ?: return@f false
|
sourceManager.get(item.manga.source) ?: return@f false
|
||||||
|
|
||||||
// Filter when there isn't unread chapters.
|
// Filter when there isn't unread chapters.
|
||||||
if (filterUnread && item.manga.unread == 0) {
|
if (filterUnread && item.manga.unread == 0) {
|
||||||
@ -132,28 +125,14 @@ class LibraryPresenter(
|
|||||||
return@f false
|
return@f false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter when the download directory doesn't exist or is null.
|
// Filter when there are no downloads.
|
||||||
if (filterDownloaded) {
|
if (filterDownloaded) {
|
||||||
// Don't bother with directory checking if download count has been set.
|
// Don't bother with directory checking if download count has been set.
|
||||||
if (item.downloadCount != -1) {
|
if (item.downloadCount != -1) {
|
||||||
return@f item.downloadCount > 0
|
return@f item.downloadCount > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the directories for the source of the manga.
|
return@f downloadManager.getDownloadCount(item.manga) > 0
|
||||||
val dirsForSource = mangaDirsForSource.getOrPut(source.id) {
|
|
||||||
val sourceDir = downloadManager.findSourceDir(source)
|
|
||||||
sourceDir?.listFiles()?.associateBy { it.name }.orEmpty()
|
|
||||||
}
|
|
||||||
|
|
||||||
val mangaDirName = downloadManager.getMangaDirName(item.manga)
|
|
||||||
val mangaDir = dirsForSource[mangaDirName] ?: return@f false
|
|
||||||
|
|
||||||
val hasDirs = chapterDirectories.getOrPut(item.manga.id!!) {
|
|
||||||
mangaDir.listFiles()?.isNotEmpty() ?: false
|
|
||||||
}
|
|
||||||
if (!hasDirs) {
|
|
||||||
return@f false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
@ -177,31 +156,9 @@ class LibraryPresenter(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cached list of downloaded manga directories given a source id.
|
|
||||||
val mangaDirsForSource = mutableMapOf<Long, Map<String?, UniFile>>()
|
|
||||||
|
|
||||||
// Cached list of downloaded chapter directories for a manga.
|
|
||||||
val chapterDirectories = mutableMapOf<Long, Int>()
|
|
||||||
|
|
||||||
val downloadCountFn: (LibraryItem) -> Int = f@ { item ->
|
|
||||||
val source = sourceManager.get(item.manga.source) ?: return@f 0
|
|
||||||
|
|
||||||
// Get the directories for the source of the manga.
|
|
||||||
val dirsForSource = mangaDirsForSource.getOrPut(source.id) {
|
|
||||||
val sourceDir = downloadManager.findSourceDir(source)
|
|
||||||
sourceDir?.listFiles()?.associateBy { it.name }.orEmpty()
|
|
||||||
}
|
|
||||||
val mangaDirName = downloadManager.getMangaDirName(item.manga)
|
|
||||||
val mangaDir = dirsForSource[mangaDirName] ?: return@f 0
|
|
||||||
|
|
||||||
chapterDirectories.getOrPut(item.manga.id!!) {
|
|
||||||
mangaDir.listFiles()?.size ?: 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for ((_, itemList) in map) {
|
for ((_, itemList) in map) {
|
||||||
for (item in itemList) {
|
for (item in itemList) {
|
||||||
item.downloadCount = downloadCountFn(item)
|
item.downloadCount = downloadManager.getDownloadCount(item.manga)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -360,7 +317,7 @@ class LibraryPresenter(
|
|||||||
if (deleteChapters) {
|
if (deleteChapters) {
|
||||||
val source = sourceManager.get(manga.source) as? HttpSource
|
val source = sourceManager.get(manga.source) as? HttpSource
|
||||||
if (source != null) {
|
if (source != null) {
|
||||||
downloadManager.findMangaDir(source, manga)?.delete()
|
downloadManager.deleteManga(manga, source)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -128,13 +128,11 @@ class ChaptersPresenter(
|
|||||||
* @param chapters the list of chapter from the database.
|
* @param chapters the list of chapter from the database.
|
||||||
*/
|
*/
|
||||||
private fun setDownloadedChapters(chapters: List<ChapterItem>) {
|
private fun setDownloadedChapters(chapters: List<ChapterItem>) {
|
||||||
val files = downloadManager.findMangaDir(source, manga)?.listFiles() ?: return
|
for (chapter in chapters) {
|
||||||
val cached = mutableMapOf<Chapter, String>()
|
if (downloadManager.isChapterDownloaded(chapter, manga)) {
|
||||||
files.mapNotNull { it.name }
|
chapter.status = Download.DOWNLOADED
|
||||||
.mapNotNull { name -> chapters.find {
|
}
|
||||||
name == cached.getOrPut(it) { downloadManager.getChapterDirName(it) }
|
}
|
||||||
} }
|
|
||||||
.forEach { it.status = Download.DOWNLOADED }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -283,7 +281,7 @@ class ChaptersPresenter(
|
|||||||
*/
|
*/
|
||||||
private fun deleteChapter(chapter: ChapterItem) {
|
private fun deleteChapter(chapter: ChapterItem) {
|
||||||
downloadManager.queue.remove(chapter)
|
downloadManager.queue.remove(chapter)
|
||||||
downloadManager.deleteChapter(source, manga, chapter)
|
downloadManager.deleteChapter(chapter, manga, source)
|
||||||
chapter.status = Download.NOT_DOWNLOADED
|
chapter.status = Download.NOT_DOWNLOADED
|
||||||
chapter.download = null
|
chapter.download = null
|
||||||
}
|
}
|
||||||
|
@ -115,14 +115,14 @@ class MangaInfoPresenter(
|
|||||||
* Returns true if the manga has any downloads.
|
* Returns true if the manga has any downloads.
|
||||||
*/
|
*/
|
||||||
fun hasDownloads(): Boolean {
|
fun hasDownloads(): Boolean {
|
||||||
return downloadManager.findMangaDir(source, manga) != null
|
return downloadManager.getDownloadCount(manga) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes all the downloads for the manga.
|
* Deletes all the downloads for the manga.
|
||||||
*/
|
*/
|
||||||
fun deleteDownloads() {
|
fun deleteDownloads() {
|
||||||
downloadManager.findMangaDir(source, manga)?.delete()
|
downloadManager.deleteManga(manga, source)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -79,7 +79,7 @@ class ChapterLoader(
|
|||||||
private fun retrievePageList(chapter: ReaderChapter) = Observable.just(chapter)
|
private fun retrievePageList(chapter: ReaderChapter) = Observable.just(chapter)
|
||||||
.flatMap {
|
.flatMap {
|
||||||
// Check if the chapter is downloaded.
|
// Check if the chapter is downloaded.
|
||||||
chapter.isDownloaded = downloadManager.findChapterDir(source, manga, chapter) != null
|
chapter.isDownloaded = downloadManager.isChapterDownloaded(chapter, manga, true)
|
||||||
|
|
||||||
if (chapter.isDownloaded) {
|
if (chapter.isDownloaded) {
|
||||||
// Fetch the page list from disk.
|
// Fetch the page list from disk.
|
||||||
|
@ -64,7 +64,7 @@ class ReaderCustomFilterDialog : DialogFragment() {
|
|||||||
* @param savedState The last saved instance state of the Fragment.
|
* @param savedState The last saved instance state of the Fragment.
|
||||||
*/
|
*/
|
||||||
override fun onCreateDialog(savedState: Bundle?): Dialog {
|
override fun onCreateDialog(savedState: Bundle?): Dialog {
|
||||||
val dialog = MaterialDialog.Builder(activity)
|
val dialog = MaterialDialog.Builder(activity!!)
|
||||||
.customView(R.layout.reader_custom_filter_dialog, false)
|
.customView(R.layout.reader_custom_filter_dialog, false)
|
||||||
.positiveText(android.R.string.ok)
|
.positiveText(android.R.string.ok)
|
||||||
.build()
|
.build()
|
||||||
|
@ -417,7 +417,7 @@ class ReaderPresenter(
|
|||||||
fun deleteChapter(chapter: ReaderChapter, manga: Manga) {
|
fun deleteChapter(chapter: ReaderChapter, manga: Manga) {
|
||||||
chapter.isDownloaded = false
|
chapter.isDownloaded = false
|
||||||
chapter.pages?.forEach { it.status == Page.QUEUE }
|
chapter.pages?.forEach { it.status == Page.QUEUE }
|
||||||
downloadManager.deleteChapter(source, manga, chapter)
|
downloadManager.deleteChapter(chapter, manga, source)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -24,7 +24,7 @@ class ReaderSettingsDialog : DialogFragment() {
|
|||||||
private lateinit var subscriptions: CompositeSubscription
|
private lateinit var subscriptions: CompositeSubscription
|
||||||
|
|
||||||
override fun onCreateDialog(savedState: Bundle?): Dialog {
|
override fun onCreateDialog(savedState: Bundle?): Dialog {
|
||||||
val dialog = MaterialDialog.Builder(activity)
|
val dialog = MaterialDialog.Builder(activity!!)
|
||||||
.title(R.string.label_settings)
|
.title(R.string.label_settings)
|
||||||
.customView(R.layout.reader_settings_dialog, true)
|
.customView(R.layout.reader_settings_dialog, true)
|
||||||
.positiveText(android.R.string.ok)
|
.positiveText(android.R.string.ok)
|
||||||
@ -40,8 +40,11 @@ class ReaderSettingsDialog : DialogFragment() {
|
|||||||
viewer.onItemSelectedListener = IgnoreFirstSpinnerListener { position ->
|
viewer.onItemSelectedListener = IgnoreFirstSpinnerListener { position ->
|
||||||
subscriptions += Observable.timer(250, MILLISECONDS, AndroidSchedulers.mainThread())
|
subscriptions += Observable.timer(250, MILLISECONDS, AndroidSchedulers.mainThread())
|
||||||
.subscribe {
|
.subscribe {
|
||||||
(activity as ReaderActivity).presenter.updateMangaViewer(position)
|
val readerActivity = activity as? ReaderActivity
|
||||||
activity.recreate()
|
if (readerActivity != null) {
|
||||||
|
readerActivity.presenter.updateMangaViewer(position)
|
||||||
|
readerActivity.recreate()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
viewer.setSelection((activity as ReaderActivity).presenter.manga.viewer, false)
|
viewer.setSelection((activity as ReaderActivity).presenter.manga.viewer, false)
|
||||||
|
@ -100,12 +100,12 @@ abstract class PagerReader : BaseReader() {
|
|||||||
/**
|
/**
|
||||||
* Text color for black theme.
|
* Text color for black theme.
|
||||||
*/
|
*/
|
||||||
val whiteColor by lazy { ContextCompat.getColor(context, R.color.textColorSecondaryDark) }
|
val whiteColor by lazy { ContextCompat.getColor(context!!, R.color.textColorSecondaryDark) }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Text color for white theme.
|
* Text color for white theme.
|
||||||
*/
|
*/
|
||||||
val blackColor by lazy { ContextCompat.getColor(context, R.color.textColorSecondaryLight) }
|
val blackColor by lazy { ContextCompat.getColor(context!!, R.color.textColorSecondaryLight) }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the pager.
|
* Initializes the pager.
|
||||||
|
@ -24,7 +24,7 @@ class HorizontalPager(context: Context) : ViewPager(context), Pager {
|
|||||||
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
|
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
|
||||||
try {
|
try {
|
||||||
if (ev.action and MotionEvent.ACTION_MASK == MotionEvent.ACTION_DOWN) {
|
if (ev.action and MotionEvent.ACTION_MASK == MotionEvent.ACTION_DOWN) {
|
||||||
if (currentItem == 0 || currentItem == adapter.count - 1) {
|
if (currentItem == 0 || currentItem == adapter!!.count - 1) {
|
||||||
startDragX = ev.x
|
startDragX = ev.x
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -50,7 +50,7 @@ class HorizontalPager(context: Context) : ViewPager(context), Pager {
|
|||||||
|
|
||||||
startDragX = 0f
|
startDragX = 0f
|
||||||
}
|
}
|
||||||
} else if (currentItem == adapter.count - 1) {
|
} else if (currentItem == adapter!!.count - 1) {
|
||||||
if (ev.action and MotionEvent.ACTION_MASK == MotionEvent.ACTION_UP) {
|
if (ev.action and MotionEvent.ACTION_MASK == MotionEvent.ACTION_UP) {
|
||||||
val displacement = startDragX - ev.x
|
val displacement = startDragX - ev.x
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerReader
|
|||||||
class LeftToRightReader : PagerReader() {
|
class LeftToRightReader : PagerReader() {
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedState: Bundle?): View? {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedState: Bundle?): View? {
|
||||||
return HorizontalPager(activity).apply { initializePager(this) }
|
return HorizontalPager(activity!!).apply { initializePager(this) }
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerReader
|
|||||||
class RightToLeftReader : PagerReader() {
|
class RightToLeftReader : PagerReader() {
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedState: Bundle?): View? {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedState: Bundle?): View? {
|
||||||
return HorizontalPager(activity).apply {
|
return HorizontalPager(activity!!).apply {
|
||||||
rotation = 180f
|
rotation = 180f
|
||||||
initializePager(this)
|
initializePager(this)
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerReader
|
|||||||
class VerticalReader : PagerReader() {
|
class VerticalReader : PagerReader() {
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedState: Bundle?): View? {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedState: Bundle?): View? {
|
||||||
return VerticalPager(activity).apply { initializePager(this) }
|
return VerticalPager(activity!!).apply { initializePager(this) }
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -33,7 +33,6 @@ import android.support.annotation.DrawableRes;
|
|||||||
import android.support.v4.os.ParcelableCompat;
|
import android.support.v4.os.ParcelableCompat;
|
||||||
import android.support.v4.os.ParcelableCompatCreatorCallbacks;
|
import android.support.v4.os.ParcelableCompatCreatorCallbacks;
|
||||||
import android.support.v4.view.AccessibilityDelegateCompat;
|
import android.support.v4.view.AccessibilityDelegateCompat;
|
||||||
import android.support.v4.view.KeyEventCompat;
|
|
||||||
import android.support.v4.view.MotionEventCompat;
|
import android.support.v4.view.MotionEventCompat;
|
||||||
import android.support.v4.view.PagerAdapter;
|
import android.support.v4.view.PagerAdapter;
|
||||||
import android.support.v4.view.VelocityTrackerCompat;
|
import android.support.v4.view.VelocityTrackerCompat;
|
||||||
@ -2598,14 +2597,10 @@ public class VerticalViewPagerImpl extends ViewGroup {
|
|||||||
handled = arrowScroll(FOCUS_RIGHT);
|
handled = arrowScroll(FOCUS_RIGHT);
|
||||||
break;
|
break;
|
||||||
case KeyEvent.KEYCODE_TAB:
|
case KeyEvent.KEYCODE_TAB:
|
||||||
if (Build.VERSION.SDK_INT >= 11) {
|
if (event.hasNoModifiers()) {
|
||||||
// The focus finder had a bug handling FOCUS_FORWARD and FOCUS_BACKWARD
|
handled = arrowScroll(FOCUS_FORWARD);
|
||||||
// before Android 3.0. Ignore the tab key on those devices.
|
} else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
|
||||||
if (KeyEventCompat.hasNoModifiers(event)) {
|
handled = arrowScroll(FOCUS_BACKWARD);
|
||||||
handled = arrowScroll(FOCUS_FORWARD);
|
|
||||||
} else if (KeyEventCompat.hasModifiers(event, KeyEvent.META_SHIFT_ON)) {
|
|
||||||
handled = arrowScroll(FOCUS_BACKWARD);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -73,7 +73,7 @@ class WebtoonReader : BaseReader() {
|
|||||||
private var scrollDistance: Int = 0
|
private var scrollDistance: Int = 0
|
||||||
|
|
||||||
val screenHeight by lazy {
|
val screenHeight by lazy {
|
||||||
val display = activity.windowManager.defaultDisplay
|
val display = activity!!.windowManager.defaultDisplay
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
||||||
val metrics = DisplayMetrics()
|
val metrics = DisplayMetrics()
|
||||||
@ -91,7 +91,7 @@ class WebtoonReader : BaseReader() {
|
|||||||
val screenHeight = resources.displayMetrics.heightPixels
|
val screenHeight = resources.displayMetrics.heightPixels
|
||||||
scrollDistance = screenHeight * 3 / 4
|
scrollDistance = screenHeight * 3 / 4
|
||||||
|
|
||||||
layoutManager = PreCachingLayoutManager(activity)
|
layoutManager = PreCachingLayoutManager(activity!!)
|
||||||
layoutManager.extraLayoutSpace = screenHeight / 2
|
layoutManager.extraLayoutSpace = screenHeight / 2
|
||||||
|
|
||||||
recycler = RecyclerView(activity).apply {
|
recycler = RecyclerView(activity).apply {
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package eu.kanade.tachiyomi.ui.recent_updates
|
package eu.kanade.tachiyomi.ui.recent_updates
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import com.hippo.unifile.UniFile
|
|
||||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||||
import eu.kanade.tachiyomi.data.database.models.MangaChapter
|
import eu.kanade.tachiyomi.data.database.models.MangaChapter
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||||
@ -114,36 +113,11 @@ class RecentChaptersPresenter(
|
|||||||
* @param items the list of chapter from the database.
|
* @param items the list of chapter from the database.
|
||||||
*/
|
*/
|
||||||
private fun setDownloadedChapters(items: List<RecentChapterItem>) {
|
private fun setDownloadedChapters(items: List<RecentChapterItem>) {
|
||||||
// Cached list of downloaded manga directories. Directory name is also cached because
|
|
||||||
// it's slow when using SAF.
|
|
||||||
val mangaDirsForSource = mutableMapOf<Long, Map<String?, UniFile>>()
|
|
||||||
|
|
||||||
// Cached list of downloaded chapter directories for a manga.
|
|
||||||
val chapterDirsForManga = mutableMapOf<Long, Map<String?, UniFile>>()
|
|
||||||
|
|
||||||
for (item in items) {
|
for (item in items) {
|
||||||
val manga = item.manga
|
val manga = item.manga
|
||||||
val chapter = item.chapter
|
val chapter = item.chapter
|
||||||
val source = sourceManager.get(manga.source) ?: continue
|
|
||||||
|
|
||||||
// Get the directories for the source of the manga.
|
if (downloadManager.isChapterDownloaded(chapter, manga)) {
|
||||||
val dirsForSource = mangaDirsForSource.getOrPut(source.id) {
|
|
||||||
val sourceDir = downloadManager.findSourceDir(source)
|
|
||||||
sourceDir?.listFiles()?.associateBy { it.name }.orEmpty()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the manga directory in the source or continue.
|
|
||||||
val mangaDirName = downloadManager.getMangaDirName(manga)
|
|
||||||
val mangaDir = dirsForSource[mangaDirName] ?: continue
|
|
||||||
|
|
||||||
// Get the directories for the manga.
|
|
||||||
val chapterDirs = chapterDirsForManga.getOrPut(manga.id!!) {
|
|
||||||
mangaDir.listFiles()?.associateBy { it.name }.orEmpty()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assign the download if the directory exists.
|
|
||||||
val chapterDirName = downloadManager.getChapterDirName(chapter)
|
|
||||||
if (chapterDirName in chapterDirs) {
|
|
||||||
item.status = Download.DOWNLOADED
|
item.status = Download.DOWNLOADED
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -216,7 +190,7 @@ class RecentChaptersPresenter(
|
|||||||
private fun deleteChapter(item: RecentChapterItem) {
|
private fun deleteChapter(item: RecentChapterItem) {
|
||||||
val source = sourceManager.get(item.manga.source) ?: return
|
val source = sourceManager.get(item.manga.source) ?: return
|
||||||
downloadManager.queue.remove(item.chapter)
|
downloadManager.queue.remove(item.chapter)
|
||||||
downloadManager.deleteChapter(source, item.manga, item.chapter)
|
downloadManager.deleteChapter(item.chapter, item.manga, source)
|
||||||
item.status = Download.NOT_DOWNLOADED
|
item.status = Download.NOT_DOWNLOADED
|
||||||
item.download = null
|
item.download = null
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package eu.kanade.tachiyomi.ui.setting
|
package eu.kanade.tachiyomi.ui.setting
|
||||||
|
|
||||||
import android.Manifest.permission.READ_EXTERNAL_STORAGE
|
|
||||||
import android.Manifest.permission.WRITE_EXTERNAL_STORAGE
|
import android.Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
@ -135,7 +134,7 @@ class SettingsBackupController : SettingsController() {
|
|||||||
preferences.backupsDirectory().asObservable()
|
preferences.backupsDirectory().asObservable()
|
||||||
.subscribeUntilDestroy { path ->
|
.subscribeUntilDestroy { path ->
|
||||||
val dir = UniFile.fromUri(context, Uri.parse(path))
|
val dir = UniFile.fromUri(context, Uri.parse(path))
|
||||||
summary = dir.filePath ?: path
|
summary = dir.filePath + "/automatic"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val backupNumber = intListPreference {
|
val backupNumber = intListPreference {
|
||||||
@ -160,19 +159,19 @@ class SettingsBackupController : SettingsController() {
|
|||||||
when (requestCode) {
|
when (requestCode) {
|
||||||
CODE_BACKUP_DIR -> if (data != null && resultCode == Activity.RESULT_OK) {
|
CODE_BACKUP_DIR -> if (data != null && resultCode == Activity.RESULT_OK) {
|
||||||
val activity = activity ?: return
|
val activity = activity ?: return
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
// Get uri of backup folder.
|
||||||
val uri = Uri.fromFile(File(data.data.path))
|
val uri = data.data
|
||||||
preferences.backupsDirectory().set(uri.toString())
|
|
||||||
} else {
|
// Get UriPermission so it's possible to write files post kitkat.
|
||||||
val uri = data.data
|
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) {
|
||||||
val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
|
val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
|
||||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||||
|
|
||||||
activity.contentResolver.takePersistableUriPermission(uri, flags)
|
activity.contentResolver.takePersistableUriPermission(uri, flags)
|
||||||
|
|
||||||
val file = UniFile.fromUri(activity, uri)
|
|
||||||
preferences.backupsDirectory().set(file.uri.toString())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set backup Uri.
|
||||||
|
preferences.backupsDirectory().set(uri.toString())
|
||||||
}
|
}
|
||||||
CODE_BACKUP_CREATE -> if (data != null && resultCode == Activity.RESULT_OK) {
|
CODE_BACKUP_CREATE -> if (data != null && resultCode == Activity.RESULT_OK) {
|
||||||
val activity = activity ?: return
|
val activity = activity ?: return
|
||||||
@ -240,7 +239,7 @@ class SettingsBackupController : SettingsController() {
|
|||||||
.itemsDisabledIndices(0)
|
.itemsDisabledIndices(0)
|
||||||
.itemsCallbackMultiChoice(arrayOf(0, 1, 2, 3, 4), { _, positions, _ ->
|
.itemsCallbackMultiChoice(arrayOf(0, 1, 2, 3, 4), { _, positions, _ ->
|
||||||
var flags = 0
|
var flags = 0
|
||||||
for (i in 1..positions.size - 1) {
|
for (i in 1 until positions.size) {
|
||||||
when (positions[i]) {
|
when (positions[i]) {
|
||||||
1 -> flags = flags or BackupCreateService.BACKUP_CATEGORY
|
1 -> flags = flags or BackupCreateService.BACKUP_CATEGORY
|
||||||
2 -> flags = flags or BackupCreateService.BACKUP_CHAPTER
|
2 -> flags = flags or BackupCreateService.BACKUP_CHAPTER
|
||||||
@ -281,7 +280,7 @@ class SettingsBackupController : SettingsController() {
|
|||||||
|
|
||||||
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
|
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
|
||||||
val activity = activity!!
|
val activity = activity!!
|
||||||
val unifile = UniFile.fromUri(activity, args.getParcelable<Uri>(KEY_URI))
|
val unifile = UniFile.fromUri(activity, args.getParcelable(KEY_URI))
|
||||||
return MaterialDialog.Builder(activity)
|
return MaterialDialog.Builder(activity)
|
||||||
.title(R.string.backup_created)
|
.title(R.string.backup_created)
|
||||||
.content(activity.getString(R.string.file_saved, unifile.filePath))
|
.content(activity.getString(R.string.file_saved, unifile.filePath))
|
||||||
@ -315,7 +314,7 @@ class SettingsBackupController : SettingsController() {
|
|||||||
val context = applicationContext
|
val context = applicationContext
|
||||||
if (context != null) {
|
if (context != null) {
|
||||||
RestoringBackupDialog().showDialog(router, TAG_RESTORING_BACKUP_DIALOG)
|
RestoringBackupDialog().showDialog(router, TAG_RESTORING_BACKUP_DIALOG)
|
||||||
BackupRestoreService.start(context, args.getParcelable<Uri>(KEY_URI))
|
BackupRestoreService.start(context, args.getParcelable(KEY_URI))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.build()
|
.build()
|
||||||
|
@ -194,7 +194,7 @@ class SettingsDownloadController : SettingsController() {
|
|||||||
File.separator + "downloads"
|
File.separator + "downloads"
|
||||||
|
|
||||||
return mutableListOf(File(defaultDir)) +
|
return mutableListOf(File(defaultDir)) +
|
||||||
ContextCompat.getExternalFilesDirs(activity, "").filterNotNull()
|
ContextCompat.getExternalFilesDirs(activity!!, "").filterNotNull()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<android.support.design.widget.CoordinatorLayout
|
<android.support.design.widget.CoordinatorLayout
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
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"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<eu.kanade.tachiyomi.widget.RevealAnimationView
|
<eu.kanade.tachiyomi.widget.RevealAnimationView
|
||||||
android:id="@+id/reveal_view"
|
android:id="@+id/reveal_view"
|
||||||
|
@ -3,4 +3,5 @@
|
|||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:id="@+id/view_pager"
|
android:id="@+id/view_pager"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent" />
|
android:layout_height="match_parent"
|
||||||
|
android:background="?android:attr/colorBackground" />
|
@ -1,5 +1,15 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<changelog bulletedList="true">
|
<changelog bulletedList="true">
|
||||||
|
<changelogversion versionName="v0.6.5" changeDate="">
|
||||||
|
<changelogtext>Added a download cache for faster navigation.</changelogtext>
|
||||||
|
|
||||||
|
<changelogtext>Enabled Cloudflare for Batoto.</changelogtext>
|
||||||
|
|
||||||
|
<changelogtext>Fixed some issues with automatic backups.</changelogtext>
|
||||||
|
|
||||||
|
<changelogtext>Fixed a bootloop issue with devices running Cyanogenmod 12 or 13.</changelogtext>
|
||||||
|
</changelogversion>
|
||||||
|
|
||||||
<changelogversion versionName="v0.6.4" changeDate="">
|
<changelogversion versionName="v0.6.4" changeDate="">
|
||||||
<changelogtext>Added a global search feature with a new catalogue screen.</changelogtext>
|
<changelogtext>Added a global search feature with a new catalogue screen.</changelogtext>
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ buildscript {
|
|||||||
google()
|
google()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:3.0.0'
|
classpath 'com.android.tools.build:gradle:3.0.1'
|
||||||
classpath 'com.github.ben-manes:gradle-versions-plugin:0.17.0'
|
classpath 'com.github.ben-manes:gradle-versions-plugin:0.17.0'
|
||||||
classpath 'com.github.zellius:android-shortcut-gradle-plugin:0.1.1'
|
classpath 'com.github.zellius:android-shortcut-gradle-plugin:0.1.1'
|
||||||
// NOTE: Do not place your application dependencies here; they belong
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
|
Loading…
x
Reference in New Issue
Block a user