Fix back button in library search
This commit is contained in:
NerdNumber9 2017-11-29 21:22:48 -05:00 committed by NerdNumber9
commit b766ddea54
32 changed files with 395 additions and 211 deletions

View File

@ -12,7 +12,7 @@ if [ -z "$TRAVIS_TAG" ]; then
else
./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"
${TOOLS}/zipalign -v -p 4 app/build/outputs/apk/standard/release/app-standard-release-unsigned.apk app-aligned.apk

View File

@ -32,7 +32,7 @@ ext {
android {
compileSdkVersion 26
buildToolsVersion "26.0.2"
buildToolsVersion "27.0.1"
publishNonDefault true
defaultConfig {
@ -111,7 +111,7 @@ dependencies {
implementation 'com.github.inorichi:junrar-android:634c1f5'
// 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:appcompat-v7:$support_library_version"
implementation "com.android.support:cardview-v7:$support_library_version"
@ -127,13 +127,13 @@ dependencies {
// ReactiveX
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.f2prateek.rx.preferences:rx-preferences:1.0.2'
implementation 'com.github.pwittchen:reactivenetwork:0.7.0'
// 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'
// REST
@ -250,7 +250,7 @@ dependencies {
}
buildscript {
ext.kotlin_version = '1.1.51'
ext.kotlin_version = '1.1.61'
repositories {
mavenCentral()
}

View File

@ -46,7 +46,7 @@ class BackupCreateService : IntentService(NAME) {
* Make a backup from library
*
* @param context context of application
* @param path path of Uri
* @param uri path of Uri
* @param flags determines what to backup
* @param isJob backup called from job
*/
@ -80,7 +80,7 @@ class BackupCreateService : IntentService(NAME) {
* @param uri path of Uri
* @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
val root = JsonObject()
@ -113,8 +113,9 @@ class BackupCreateService : IntentService(NAME) {
try {
// When BackupCreatorJob
if (isJob) {
// Get dir of file
val dir = UniFile.fromUri(this, uri)
// Get dir of file and create
var dir = UniFile.fromUri(this, uri)
dir = dir.createDirectory("automatic")
// Delete older backups
val numberOfBackups = backupManager.numberOfBackups()

View File

@ -8,13 +8,12 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.getOrDefault
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.io.File
class BackupCreatorJob : Job() {
override fun onRunJob(params: Params): Result {
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
BackupCreateService.makeBackup(context, uri, flags, true)
return Result.SUCCESS

View File

@ -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
}
}

View File

@ -24,10 +24,15 @@ class DownloadManager(context: Context) {
*/
private val provider = DownloadProvider(context)
/**
* Cache of downloaded chapters.
*/
private val cache = DownloadCache(context, provider)
/**
* 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.
@ -94,7 +99,7 @@ class DownloadManager(context: Context) {
* @return an observable containing the list of pages from the chapter.
*/
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.
*/
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 chapter the chapter to check.
* @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? {
return provider.findChapterDir(source, manga, chapter)
fun isChapterDownloaded(chapter: Chapter, manga: Manga, skipCache: Boolean = false): Boolean {
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.
*
* @param source the source of the chapter.
* @param manga the manga of the chapter.
* @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) {
provider.findChapterDir(source, manga, chapter)?.delete()
fun deleteChapter(chapter: Chapter, manga: Manga, source: Source) {
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)
}
}

View File

@ -40,10 +40,10 @@ class DownloadProvider(private val context: Context) {
/**
* 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 source the source of the manga.
*/
internal fun getMangaDir(source: Source, manga: Manga): UniFile {
internal fun getMangaDir(manga: Manga, source: Source): UniFile {
return downloadsDir
.createDirectory(getSourceDirName(source))
.createDirectory(getMangaDirName(manga))
@ -61,10 +61,10 @@ class DownloadProvider(private val context: Context) {
/**
* Returns the download directory for a manga if it exists.
*
* @param source the source of the manga.
* @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)
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.
*
* @param source the source of the chapter.
* @param manga the manga of the chapter.
* @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? {
val mangaDir = findMangaDir(source, manga)
fun findChapterDir(chapter: Chapter, manga: Manga, source: Source): UniFile? {
val mangaDir = findMangaDir(manga, source)
return mangaDir?.findFile(getChapterDirName(chapter))
}

View File

@ -37,8 +37,11 @@ import uy.kohesive.injekt.injectLazy
*
* @param context the application context.
* @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.
@ -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.
val chaptersWithoutDir = async {
val mangaDir = provider.findMangaDir(source, manga)
val mangaDir = provider.findMangaDir(manga, source)
chapters
// 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> {
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 pageListObservable = if (download.pages == null) {
@ -305,7 +308,7 @@ class Downloader(private val context: Context, private val provider: DownloadPro
.toList()
.map { _ -> download }
// Do after download completes
.doOnNext { ensureSuccessfulDownload(download, tmpDir, chapterDirname) }
.doOnNext { ensureSuccessfulDownload(download, mangaDir, tmpDir, chapterDirname) }
// If the page list threw, it will resume here
.onErrorReturn { 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.
*
* @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 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.
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.
if (download.status == Download.DOWNLOADED) {
tmpDir.renameTo(dirname)
cache.addChapter(dirname, mangaDir, download.manga)
}
}

View File

@ -11,6 +11,7 @@ import eu.kanade.tachiyomi.util.asJsoup
import eu.kanade.tachiyomi.util.selectText
import okhttp3.FormBody
import okhttp3.HttpUrl
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
@ -48,6 +49,8 @@ class Batoto : ParsedHttpSource(), LoginSource {
private val staffNotice = Pattern.compile("=+Batoto Staff Notice=+([^=]+)==+", Pattern.CASE_INSENSITIVE)
override val client: OkHttpClient = network.cloudflareClient
override fun headersBuilder() = super.headersBuilder()
.add("Cookie", "lang_option=English")

View File

@ -79,7 +79,7 @@ class LibraryAdapter(private val controller: LibraryController) : RecyclerViewPa
/**
* 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 index = categories.indexOfFirst { it.id == view.category.id }
return if (index == -1) POSITION_NONE else index

View File

@ -192,14 +192,10 @@ class LibraryController(
super.onChangeStarted(handler, type)
if (type.isEnter) {
activity?.tabs?.setupWithViewPager(view?.view_pager)
presenter.subscribeLibrary()
}
}
override fun onAttach(view: View) {
super.onAttach(view)
presenter.subscribeLibrary()
}
override fun onDestroyView(view: View) {
super.onDestroyView(view)
adapter = null
@ -464,7 +460,7 @@ class LibraryController(
presenter.onOpenManga()
router.pushController(RouterTransaction.with(MangaController(manga))
.pushChangeHandler(FadeChangeHandler())
.pushChangeHandler(FadeChangeHandler(false))
.popChangeHandler(FadeChangeHandler()))
}

View File

@ -1,7 +1,6 @@
package eu.kanade.tachiyomi.ui.library
import android.os.Bundle
import com.hippo.unifile.UniFile
import com.jakewharton.rxrelay.BehaviorRelay
import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.database.DatabaseHelper
@ -107,12 +106,6 @@ class LibraryPresenter(
* @param map the map to filter.
*/
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 filterUnread = preferences.filterUnread().getOrDefault()
@ -121,7 +114,7 @@ class LibraryPresenter(
val filterFn: (LibraryItem) -> Boolean = f@ { item ->
// 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.
if (filterUnread && item.manga.unread == 0) {
@ -132,28 +125,14 @@ class LibraryPresenter(
return@f false
}
// Filter when the download directory doesn't exist or is null.
// Filter when there are no downloads.
if (filterDownloaded) {
// Don't bother with directory checking if download count has been set.
if (item.downloadCount != -1) {
return@f item.downloadCount > 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 false
val hasDirs = chapterDirectories.getOrPut(item.manga.id!!) {
mangaDir.listFiles()?.isNotEmpty() ?: false
}
if (!hasDirs) {
return@f false
}
return@f downloadManager.getDownloadCount(item.manga) > 0
}
true
}
@ -177,31 +156,9 @@ class LibraryPresenter(
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 (item in itemList) {
item.downloadCount = downloadCountFn(item)
item.downloadCount = downloadManager.getDownloadCount(item.manga)
}
}
}
@ -360,7 +317,7 @@ class LibraryPresenter(
if (deleteChapters) {
val source = sourceManager.get(manga.source) as? HttpSource
if (source != null) {
downloadManager.findMangaDir(source, manga)?.delete()
downloadManager.deleteManga(manga, source)
}
}
}

View File

@ -128,13 +128,11 @@ class ChaptersPresenter(
* @param chapters the list of chapter from the database.
*/
private fun setDownloadedChapters(chapters: List<ChapterItem>) {
val files = downloadManager.findMangaDir(source, manga)?.listFiles() ?: return
val cached = mutableMapOf<Chapter, String>()
files.mapNotNull { it.name }
.mapNotNull { name -> chapters.find {
name == cached.getOrPut(it) { downloadManager.getChapterDirName(it) }
} }
.forEach { it.status = Download.DOWNLOADED }
for (chapter in chapters) {
if (downloadManager.isChapterDownloaded(chapter, manga)) {
chapter.status = Download.DOWNLOADED
}
}
}
/**
@ -283,7 +281,7 @@ class ChaptersPresenter(
*/
private fun deleteChapter(chapter: ChapterItem) {
downloadManager.queue.remove(chapter)
downloadManager.deleteChapter(source, manga, chapter)
downloadManager.deleteChapter(chapter, manga, source)
chapter.status = Download.NOT_DOWNLOADED
chapter.download = null
}

View File

@ -115,14 +115,14 @@ class MangaInfoPresenter(
* Returns true if the manga has any downloads.
*/
fun hasDownloads(): Boolean {
return downloadManager.findMangaDir(source, manga) != null
return downloadManager.getDownloadCount(manga) > 0
}
/**
* Deletes all the downloads for the manga.
*/
fun deleteDownloads() {
downloadManager.findMangaDir(source, manga)?.delete()
downloadManager.deleteManga(manga, source)
}
/**

View File

@ -79,7 +79,7 @@ class ChapterLoader(
private fun retrievePageList(chapter: ReaderChapter) = Observable.just(chapter)
.flatMap {
// Check if the chapter is downloaded.
chapter.isDownloaded = downloadManager.findChapterDir(source, manga, chapter) != null
chapter.isDownloaded = downloadManager.isChapterDownloaded(chapter, manga, true)
if (chapter.isDownloaded) {
// Fetch the page list from disk.

View File

@ -64,7 +64,7 @@ class ReaderCustomFilterDialog : DialogFragment() {
* @param savedState The last saved instance state of the Fragment.
*/
override fun onCreateDialog(savedState: Bundle?): Dialog {
val dialog = MaterialDialog.Builder(activity)
val dialog = MaterialDialog.Builder(activity!!)
.customView(R.layout.reader_custom_filter_dialog, false)
.positiveText(android.R.string.ok)
.build()

View File

@ -417,7 +417,7 @@ class ReaderPresenter(
fun deleteChapter(chapter: ReaderChapter, manga: Manga) {
chapter.isDownloaded = false
chapter.pages?.forEach { it.status == Page.QUEUE }
downloadManager.deleteChapter(source, manga, chapter)
downloadManager.deleteChapter(chapter, manga, source)
}
/**

View File

@ -24,7 +24,7 @@ class ReaderSettingsDialog : DialogFragment() {
private lateinit var subscriptions: CompositeSubscription
override fun onCreateDialog(savedState: Bundle?): Dialog {
val dialog = MaterialDialog.Builder(activity)
val dialog = MaterialDialog.Builder(activity!!)
.title(R.string.label_settings)
.customView(R.layout.reader_settings_dialog, true)
.positiveText(android.R.string.ok)
@ -40,8 +40,11 @@ class ReaderSettingsDialog : DialogFragment() {
viewer.onItemSelectedListener = IgnoreFirstSpinnerListener { position ->
subscriptions += Observable.timer(250, MILLISECONDS, AndroidSchedulers.mainThread())
.subscribe {
(activity as ReaderActivity).presenter.updateMangaViewer(position)
activity.recreate()
val readerActivity = activity as? ReaderActivity
if (readerActivity != null) {
readerActivity.presenter.updateMangaViewer(position)
readerActivity.recreate()
}
}
}
viewer.setSelection((activity as ReaderActivity).presenter.manga.viewer, false)

View File

@ -100,12 +100,12 @@ abstract class PagerReader : BaseReader() {
/**
* 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.
*/
val blackColor by lazy { ContextCompat.getColor(context, R.color.textColorSecondaryLight) }
val blackColor by lazy { ContextCompat.getColor(context!!, R.color.textColorSecondaryLight) }
/**
* Initializes the pager.

View File

@ -24,7 +24,7 @@ class HorizontalPager(context: Context) : ViewPager(context), Pager {
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
try {
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
}
}
@ -50,7 +50,7 @@ class HorizontalPager(context: Context) : ViewPager(context), Pager {
startDragX = 0f
}
} else if (currentItem == adapter.count - 1) {
} else if (currentItem == adapter!!.count - 1) {
if (ev.action and MotionEvent.ACTION_MASK == MotionEvent.ACTION_UP) {
val displacement = startDragX - ev.x

View File

@ -13,7 +13,7 @@ import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerReader
class LeftToRightReader : PagerReader() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedState: Bundle?): View? {
return HorizontalPager(activity).apply { initializePager(this) }
return HorizontalPager(activity!!).apply { initializePager(this) }
}
}

View File

@ -13,7 +13,7 @@ import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerReader
class RightToLeftReader : PagerReader() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedState: Bundle?): View? {
return HorizontalPager(activity).apply {
return HorizontalPager(activity!!).apply {
rotation = 180f
initializePager(this)
}

View File

@ -13,7 +13,7 @@ import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerReader
class VerticalReader : PagerReader() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedState: Bundle?): View? {
return VerticalPager(activity).apply { initializePager(this) }
return VerticalPager(activity!!).apply { initializePager(this) }
}
}

View File

@ -33,7 +33,6 @@ import android.support.annotation.DrawableRes;
import android.support.v4.os.ParcelableCompat;
import android.support.v4.os.ParcelableCompatCreatorCallbacks;
import android.support.v4.view.AccessibilityDelegateCompat;
import android.support.v4.view.KeyEventCompat;
import android.support.v4.view.MotionEventCompat;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.VelocityTrackerCompat;
@ -2598,14 +2597,10 @@ public class VerticalViewPagerImpl extends ViewGroup {
handled = arrowScroll(FOCUS_RIGHT);
break;
case KeyEvent.KEYCODE_TAB:
if (Build.VERSION.SDK_INT >= 11) {
// The focus finder had a bug handling FOCUS_FORWARD and FOCUS_BACKWARD
// before Android 3.0. Ignore the tab key on those devices.
if (KeyEventCompat.hasNoModifiers(event)) {
handled = arrowScroll(FOCUS_FORWARD);
} else if (KeyEventCompat.hasModifiers(event, KeyEvent.META_SHIFT_ON)) {
handled = arrowScroll(FOCUS_BACKWARD);
}
if (event.hasNoModifiers()) {
handled = arrowScroll(FOCUS_FORWARD);
} else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
handled = arrowScroll(FOCUS_BACKWARD);
}
break;
default:

View File

@ -73,7 +73,7 @@ class WebtoonReader : BaseReader() {
private var scrollDistance: Int = 0
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) {
val metrics = DisplayMetrics()
@ -91,7 +91,7 @@ class WebtoonReader : BaseReader() {
val screenHeight = resources.displayMetrics.heightPixels
scrollDistance = screenHeight * 3 / 4
layoutManager = PreCachingLayoutManager(activity)
layoutManager = PreCachingLayoutManager(activity!!)
layoutManager.extraLayoutSpace = screenHeight / 2
recycler = RecyclerView(activity).apply {

View File

@ -1,7 +1,6 @@
package eu.kanade.tachiyomi.ui.recent_updates
import android.os.Bundle
import com.hippo.unifile.UniFile
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.MangaChapter
import eu.kanade.tachiyomi.data.download.DownloadManager
@ -114,36 +113,11 @@ class RecentChaptersPresenter(
* @param items the list of chapter from the database.
*/
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) {
val manga = item.manga
val chapter = item.chapter
val source = sourceManager.get(manga.source) ?: continue
// 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()
}
// 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) {
if (downloadManager.isChapterDownloaded(chapter, manga)) {
item.status = Download.DOWNLOADED
}
}
@ -216,7 +190,7 @@ class RecentChaptersPresenter(
private fun deleteChapter(item: RecentChapterItem) {
val source = sourceManager.get(item.manga.source) ?: return
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.download = null
}

View File

@ -1,6 +1,5 @@
package eu.kanade.tachiyomi.ui.setting
import android.Manifest.permission.READ_EXTERNAL_STORAGE
import android.Manifest.permission.WRITE_EXTERNAL_STORAGE
import android.app.Activity
import android.app.Dialog
@ -135,7 +134,7 @@ class SettingsBackupController : SettingsController() {
preferences.backupsDirectory().asObservable()
.subscribeUntilDestroy { path ->
val dir = UniFile.fromUri(context, Uri.parse(path))
summary = dir.filePath ?: path
summary = dir.filePath + "/automatic"
}
}
val backupNumber = intListPreference {
@ -160,19 +159,19 @@ class SettingsBackupController : SettingsController() {
when (requestCode) {
CODE_BACKUP_DIR -> if (data != null && resultCode == Activity.RESULT_OK) {
val activity = activity ?: return
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
val uri = Uri.fromFile(File(data.data.path))
preferences.backupsDirectory().set(uri.toString())
} else {
val uri = data.data
// Get uri of backup folder.
val uri = data.data
// Get UriPermission so it's possible to write files post kitkat.
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) {
val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
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) {
val activity = activity ?: return
@ -240,7 +239,7 @@ class SettingsBackupController : SettingsController() {
.itemsDisabledIndices(0)
.itemsCallbackMultiChoice(arrayOf(0, 1, 2, 3, 4), { _, positions, _ ->
var flags = 0
for (i in 1..positions.size - 1) {
for (i in 1 until positions.size) {
when (positions[i]) {
1 -> flags = flags or BackupCreateService.BACKUP_CATEGORY
2 -> flags = flags or BackupCreateService.BACKUP_CHAPTER
@ -281,7 +280,7 @@ class SettingsBackupController : SettingsController() {
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
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)
.title(R.string.backup_created)
.content(activity.getString(R.string.file_saved, unifile.filePath))
@ -315,7 +314,7 @@ class SettingsBackupController : SettingsController() {
val context = applicationContext
if (context != null) {
RestoringBackupDialog().showDialog(router, TAG_RESTORING_BACKUP_DIALOG)
BackupRestoreService.start(context, args.getParcelable<Uri>(KEY_URI))
BackupRestoreService.start(context, args.getParcelable(KEY_URI))
}
}
.build()

View File

@ -194,7 +194,7 @@ class SettingsDownloadController : SettingsController() {
File.separator + "downloads"
return mutableListOf(File(defaultDir)) +
ContextCompat.getExternalFilesDirs(activity, "").filterNotNull()
ContextCompat.getExternalFilesDirs(activity!!, "").filterNotNull()
}
}

View File

@ -1,11 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<eu.kanade.tachiyomi.widget.RevealAnimationView
android:id="@+id/reveal_view"

View File

@ -3,4 +3,5 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/view_pager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
android:layout_height="match_parent"
android:background="?android:attr/colorBackground" />

View File

@ -1,5 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<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="">
<changelogtext>Added a global search feature with a new catalogue screen.</changelogtext>

View File

@ -7,7 +7,7 @@ buildscript {
google()
}
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.zellius:android-shortcut-gradle-plugin:0.1.1'
// NOTE: Do not place your application dependencies here; they belong