mirror of
https://github.com/mihonapp/mihon.git
synced 2025-06-30 13:07:52 +02:00
Compare commits
31 Commits
Author | SHA1 | Date | |
---|---|---|---|
0cffb9e503 | |||
e2ecf0ce5f | |||
5d396bfb7c | |||
de6cc8394e | |||
4b7159648a | |||
eb9c5f95db | |||
55e9d2880c | |||
ec9c19ce7d | |||
31731e8f26 | |||
bfb12bc7c1 | |||
4befcf3819 | |||
cb58145361 | |||
b83efd90a8 | |||
9f0da3f1d6 | |||
50ae08ed8d | |||
5385642a5b | |||
0a27d4e185 | |||
bd8b9febd2 | |||
a30705f197 | |||
877032a757 | |||
19bf47b6d2 | |||
a9bfeb058b | |||
9213fc6999 | |||
447dfd1e3c | |||
638d3a32cf | |||
17c59657c3 | |||
81bce8ef76 | |||
78314077bb | |||
a7840bc247 | |||
6d0254c5e5 | |||
06681a3db7 |
@ -1,6 +1,6 @@
|
|||||||
| Build | Download | Auto Update |
|
| Build | Download | Auto Update |
|
||||||
|-------|----------|-------------|
|
|-------|----------|-------------|
|
||||||
| [](https://teamcity.kanade.eu/project.html?projectId=tachiyomi) [](https://travis-ci.org/inorichi/tachiyomi) | [](https://github.com/inorichi/tachiyomi/releases) [](http://tachiyomi.kanade.eu/latest/app-debug.apk) | [](https://f-droid.org/repository/browse/?fdid=eu.kanade.tachiyomi) [](//github.com/inorichi/tachiyomi/wiki/FDroid-for-debug-versions) |
|
| [](https://teamcity.kanade.eu/project.html?projectId=tachiyomi) [](https://travis-ci.org/inorichi/tachiyomi) | [](https://github.com/inorichi/tachiyomi/releases) [](http://tachiyomi.kanade.eu/latest/app-debug.apk) | [](https://f-droid.org/repository/browse/?fdid=eu.kanade.tachiyomi) [](//github.com/inorichi/tachiyomi/wiki/FDroid-for-debug-versions) |
|
||||||
|
|
||||||
## [Report an issue](https://github.com/inorichi/tachiyomi/blob/master/.github/CONTRIBUTING.md)
|
## [Report an issue](https://github.com/inorichi/tachiyomi/blob/master/.github/CONTRIBUTING.md)
|
||||||
|
|
||||||
|
@ -38,8 +38,8 @@ android {
|
|||||||
minSdkVersion 16
|
minSdkVersion 16
|
||||||
targetSdkVersion 23
|
targetSdkVersion 23
|
||||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||||
versionCode 6
|
versionCode 7
|
||||||
versionName "0.2.0"
|
versionName "0.2.1"
|
||||||
|
|
||||||
buildConfigField "String", "COMMIT_COUNT", "\"${getCommitCount()}\""
|
buildConfigField "String", "COMMIT_COUNT", "\"${getCommitCount()}\""
|
||||||
buildConfigField "String", "COMMIT_SHA", "\"${getGitSha()}\""
|
buildConfigField "String", "COMMIT_SHA", "\"${getGitSha()}\""
|
||||||
@ -142,7 +142,7 @@ dependencies {
|
|||||||
kapt "com.pushtorefresh.storio:sqlite-annotations-processor:$STORIO_VERSION"
|
kapt "com.pushtorefresh.storio:sqlite-annotations-processor:$STORIO_VERSION"
|
||||||
|
|
||||||
// Model View Presenter
|
// Model View Presenter
|
||||||
compile 'info.android15.nucleus:nucleus:2.0.5'
|
compile 'info.android15.nucleus:nucleus:3.0.0-beta'
|
||||||
|
|
||||||
// Dependency injection
|
// Dependency injection
|
||||||
compile "com.google.dagger:dagger:$DAGGER_VERSION"
|
compile "com.google.dagger:dagger:$DAGGER_VERSION"
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest package="eu.kanade.tachiyomi"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
package="eu.kanade.tachiyomi">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
@ -68,7 +68,19 @@
|
|||||||
</receiver>
|
</receiver>
|
||||||
|
|
||||||
<receiver
|
<receiver
|
||||||
android:name=".data.library.LibraryUpdateService$LibraryUpdateReceiver">
|
android:name=".data.library.LibraryUpdateService$SyncOnPowerConnected"
|
||||||
|
android:enabled="false">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.ACTION_POWER_CONNECTED" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
|
<receiver
|
||||||
|
android:name=".data.library.LibraryUpdateService$CancelUpdateReceiver">
|
||||||
|
</receiver>
|
||||||
|
|
||||||
|
<receiver
|
||||||
|
android:name=".data.updater.UpdateDownloader$InstallOnReceived">
|
||||||
</receiver>
|
</receiver>
|
||||||
|
|
||||||
<receiver
|
<receiver
|
||||||
@ -79,6 +91,15 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
|
|
||||||
|
<receiver
|
||||||
|
android:name=".data.updater.UpdateDownloaderAlarm">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.BOOT_COMPLETED"/>
|
||||||
|
<action android:name="eu.kanade.CHECK_UPDATE"/>
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="eu.kanade.tachiyomi.data.cache.CoverGlideModule"
|
android:name="eu.kanade.tachiyomi.data.cache.CoverGlideModule"
|
||||||
android:value="GlideModule" />
|
android:value="GlideModule" />
|
||||||
|
@ -7,6 +7,7 @@ import com.google.gson.reflect.TypeToken
|
|||||||
import com.jakewharton.disklrucache.DiskLruCache
|
import com.jakewharton.disklrucache.DiskLruCache
|
||||||
import eu.kanade.tachiyomi.data.source.model.Page
|
import eu.kanade.tachiyomi.data.source.model.Page
|
||||||
import eu.kanade.tachiyomi.util.DiskUtils
|
import eu.kanade.tachiyomi.util.DiskUtils
|
||||||
|
import eu.kanade.tachiyomi.util.saveImageTo
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import okio.Okio
|
import okio.Okio
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
@ -184,7 +185,7 @@ class ChapterCache(private val context: Context) {
|
|||||||
* @throws IOException image error.
|
* @throws IOException image error.
|
||||||
*/
|
*/
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
fun putImageToCache(imageUrl: String, response: Response) {
|
fun putImageToCache(imageUrl: String, response: Response, reencode: Boolean) {
|
||||||
// Initialize editor (edits the values for an entry).
|
// Initialize editor (edits the values for an entry).
|
||||||
var editor: DiskLruCache.Editor? = null
|
var editor: DiskLruCache.Editor? = null
|
||||||
|
|
||||||
@ -194,17 +195,12 @@ class ChapterCache(private val context: Context) {
|
|||||||
editor = diskCache.edit(key) ?: throw IOException("Unable to edit key")
|
editor = diskCache.edit(key) ?: throw IOException("Unable to edit key")
|
||||||
|
|
||||||
// Get OutputStream and write image with Okio.
|
// Get OutputStream and write image with Okio.
|
||||||
Okio.buffer(Okio.sink(editor.newOutputStream(0))).use {
|
response.body().source().saveImageTo(editor.newOutputStream(0), reencode)
|
||||||
it.writeAll(response.body().source())
|
|
||||||
it.flush()
|
|
||||||
}
|
|
||||||
|
|
||||||
diskCache.flush()
|
diskCache.flush()
|
||||||
editor.commit()
|
editor.commit()
|
||||||
} catch (e: Exception) {
|
|
||||||
response.body().close()
|
|
||||||
throw IOException("Unable to save image")
|
|
||||||
} finally {
|
} finally {
|
||||||
|
response.body().close()
|
||||||
editor?.abortUnlessCommitted()
|
editor?.abortUnlessCommitted()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,7 +33,7 @@ class CoverCache(private val context: Context) {
|
|||||||
* @param headers headers included in Glide request.
|
* @param headers headers included in Glide request.
|
||||||
* @param onReady function to call when the image is ready
|
* @param onReady function to call when the image is ready
|
||||||
*/
|
*/
|
||||||
fun save(thumbnailUrl: String?, headers: LazyHeaders, onReady: ((File) -> Unit)? = null) {
|
fun save(thumbnailUrl: String?, headers: LazyHeaders?, onReady: ((File) -> Unit)? = null) {
|
||||||
// Check if url is empty.
|
// Check if url is empty.
|
||||||
if (thumbnailUrl.isNullOrEmpty())
|
if (thumbnailUrl.isNullOrEmpty())
|
||||||
return
|
return
|
||||||
@ -62,7 +62,7 @@ class CoverCache(private val context: Context) {
|
|||||||
* @param headers headers included in Glide request.
|
* @param headers headers included in Glide request.
|
||||||
* @param onReady function to call when the image is ready
|
* @param onReady function to call when the image is ready
|
||||||
*/
|
*/
|
||||||
fun saveOrLoadFromCache(thumbnailUrl: String?, headers: LazyHeaders, onReady: ((File) -> Unit)?) {
|
fun saveOrLoadFromCache(thumbnailUrl: String?, headers: LazyHeaders?, onReady: ((File) -> Unit)?) {
|
||||||
// Check if url is empty.
|
// Check if url is empty.
|
||||||
if (thumbnailUrl.isNullOrEmpty())
|
if (thumbnailUrl.isNullOrEmpty())
|
||||||
return
|
return
|
||||||
|
@ -260,6 +260,14 @@ open class DatabaseHelper(context: Context) {
|
|||||||
|
|
||||||
fun deleteMangaSync(manga: MangaSync) = db.delete().`object`(manga).prepare()
|
fun deleteMangaSync(manga: MangaSync) = db.delete().`object`(manga).prepare()
|
||||||
|
|
||||||
|
fun deleteMangaSyncForManga(manga: Manga) = db.delete()
|
||||||
|
.byQuery(DeleteQuery.builder()
|
||||||
|
.table(MangaSyncTable.TABLE)
|
||||||
|
.where("${MangaSyncTable.COLUMN_MANGA_ID} = ?")
|
||||||
|
.whereArgs(manga.id)
|
||||||
|
.build())
|
||||||
|
.prepare()
|
||||||
|
|
||||||
// Categories related queries
|
// Categories related queries
|
||||||
|
|
||||||
fun getCategories() = db.get()
|
fun getCategories() = db.get()
|
||||||
|
@ -95,6 +95,13 @@ public class Manga implements Serializable {
|
|||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Manga create(String pathUrl, int source) {
|
||||||
|
Manga m = new Manga();
|
||||||
|
m.url = pathUrl;
|
||||||
|
m.source = source;
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
public void setUrl(String url) {
|
public void setUrl(String url) {
|
||||||
this.url = UrlUtil.getPath(url);
|
this.url = UrlUtil.getPath(url);
|
||||||
}
|
}
|
||||||
|
@ -10,14 +10,12 @@ import eu.kanade.tachiyomi.data.database.models.Manga
|
|||||||
import eu.kanade.tachiyomi.data.download.model.Download
|
import eu.kanade.tachiyomi.data.download.model.Download
|
||||||
import eu.kanade.tachiyomi.data.download.model.DownloadQueue
|
import eu.kanade.tachiyomi.data.download.model.DownloadQueue
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
|
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
||||||
import eu.kanade.tachiyomi.data.source.SourceManager
|
import eu.kanade.tachiyomi.data.source.SourceManager
|
||||||
import eu.kanade.tachiyomi.data.source.base.Source
|
import eu.kanade.tachiyomi.data.source.base.Source
|
||||||
import eu.kanade.tachiyomi.data.source.model.Page
|
import eu.kanade.tachiyomi.data.source.model.Page
|
||||||
import eu.kanade.tachiyomi.event.DownloadChaptersEvent
|
import eu.kanade.tachiyomi.event.DownloadChaptersEvent
|
||||||
import eu.kanade.tachiyomi.util.DiskUtils
|
import eu.kanade.tachiyomi.util.*
|
||||||
import eu.kanade.tachiyomi.util.DynamicConcurrentMergeOperator
|
|
||||||
import eu.kanade.tachiyomi.util.UrlUtil
|
|
||||||
import eu.kanade.tachiyomi.util.toast
|
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.Subscription
|
import rx.Subscription
|
||||||
import rx.android.schedulers.AndroidSchedulers
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
@ -26,10 +24,9 @@ import rx.subjects.BehaviorSubject
|
|||||||
import rx.subjects.PublishSubject
|
import rx.subjects.PublishSubject
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileOutputStream
|
|
||||||
import java.io.FileReader
|
import java.io.FileReader
|
||||||
import java.io.IOException
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
class DownloadManager(private val context: Context, private val sourceManager: SourceManager, private val preferences: PreferencesHelper) {
|
class DownloadManager(private val context: Context, private val sourceManager: SourceManager, private val preferences: PreferencesHelper) {
|
||||||
|
|
||||||
@ -175,7 +172,7 @@ class DownloadManager(private val context: Context, private val sourceManager: S
|
|||||||
// Or if the page list already exists, start from the file
|
// Or if the page list already exists, start from the file
|
||||||
Observable.just(download.pages)
|
Observable.just(download.pages)
|
||||||
|
|
||||||
return Observable.defer<Download> { pageListObservable
|
return Observable.defer { pageListObservable
|
||||||
.doOnNext { pages ->
|
.doOnNext { pages ->
|
||||||
download.downloadedImages = 0
|
download.downloadedImages = 0
|
||||||
download.status = Download.DOWNLOADING
|
download.status = Download.DOWNLOADING
|
||||||
@ -231,10 +228,21 @@ class DownloadManager(private val context: Context, private val sourceManager: S
|
|||||||
private fun downloadImage(page: Page, source: Source, directory: File, filename: String): Observable<Page> {
|
private fun downloadImage(page: Page, source: Source, directory: File, filename: String): Observable<Page> {
|
||||||
page.status = Page.DOWNLOAD_IMAGE
|
page.status = Page.DOWNLOAD_IMAGE
|
||||||
return source.getImageProgressResponse(page)
|
return source.getImageProgressResponse(page)
|
||||||
.flatMap({ resp ->
|
.flatMap {
|
||||||
DiskUtils.saveBufferedSourceToDirectory(resp.body().source(), directory, filename)
|
try {
|
||||||
|
val file = File(directory, filename)
|
||||||
|
file.parentFile.mkdirs()
|
||||||
|
it.body().source().saveImageTo(file.outputStream(), preferences.reencodeImage())
|
||||||
|
} catch (e: Exception) {
|
||||||
|
it.body().close()
|
||||||
|
throw e
|
||||||
|
}
|
||||||
Observable.just(page)
|
Observable.just(page)
|
||||||
}).retry(2)
|
}
|
||||||
|
.retryWhen {
|
||||||
|
it.zipWith(Observable.range(1, 3)) { errors, retries -> retries }
|
||||||
|
.flatMap { retries -> Observable.timer((retries * 2).toLong(), TimeUnit.SECONDS) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Public method to get the image from the filesystem. It does NOT provide any way to download the image
|
// Public method to get the image from the filesystem. It does NOT provide any way to download the image
|
||||||
@ -306,26 +314,14 @@ class DownloadManager(private val context: Context, private val sourceManager: S
|
|||||||
val chapterDir = getAbsoluteChapterDirectory(source, manga, chapter)
|
val chapterDir = getAbsoluteChapterDirectory(source, manga, chapter)
|
||||||
val pagesFile = File(chapterDir, PAGE_LIST_FILE)
|
val pagesFile = File(chapterDir, PAGE_LIST_FILE)
|
||||||
|
|
||||||
var reader: JsonReader? = null
|
return try {
|
||||||
try {
|
JsonReader(FileReader(pagesFile)).use {
|
||||||
if (pagesFile.exists()) {
|
val collectionType = object : TypeToken<List<Page>>() {}.type
|
||||||
reader = JsonReader(FileReader(pagesFile.absolutePath))
|
gson.fromJson(it, collectionType)
|
||||||
val collectionType = object : TypeToken<List<Page>>() {
|
|
||||||
|
|
||||||
}.type
|
|
||||||
return gson.fromJson<List<Page>>(reader, collectionType)
|
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Timber.e(e.cause, e.message)
|
null
|
||||||
} finally {
|
|
||||||
if (reader != null) try {
|
|
||||||
reader.close()
|
|
||||||
} catch (e: IOException) {
|
|
||||||
/* Do nothing */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shortcut for the method above
|
// Shortcut for the method above
|
||||||
@ -338,20 +334,13 @@ class DownloadManager(private val context: Context, private val sourceManager: S
|
|||||||
val chapterDir = getAbsoluteChapterDirectory(source, manga, chapter)
|
val chapterDir = getAbsoluteChapterDirectory(source, manga, chapter)
|
||||||
val pagesFile = File(chapterDir, PAGE_LIST_FILE)
|
val pagesFile = File(chapterDir, PAGE_LIST_FILE)
|
||||||
|
|
||||||
var out: FileOutputStream? = null
|
pagesFile.outputStream().use {
|
||||||
try {
|
try {
|
||||||
out = FileOutputStream(pagesFile)
|
it.write(gson.toJson(pages).toByteArray())
|
||||||
out.write(gson.toJson(pages).toByteArray())
|
it.flush()
|
||||||
out.flush()
|
} catch (e: Exception) {
|
||||||
} catch (e: IOException) {
|
Timber.e(e, e.message)
|
||||||
Timber.e(e.cause, e.message)
|
|
||||||
} finally {
|
|
||||||
if (out != null) try {
|
|
||||||
out.close()
|
|
||||||
} catch (e: IOException) {
|
|
||||||
/* Do nothing */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -365,7 +354,7 @@ class DownloadManager(private val context: Context, private val sourceManager: S
|
|||||||
File.separator +
|
File.separator +
|
||||||
manga.title.replace("[^\\sa-zA-Z0-9.-]".toRegex(), "_")
|
manga.title.replace("[^\\sa-zA-Z0-9.-]".toRegex(), "_")
|
||||||
|
|
||||||
return File(preferences.downloadsDirectory, mangaRelativePath)
|
return File(preferences.downloadsDirectory().getOrDefault(), mangaRelativePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the absolute path to the chapter directory
|
// Get the absolute path to the chapter directory
|
||||||
|
@ -4,9 +4,9 @@ import eu.kanade.tachiyomi.data.database.models.Chapter
|
|||||||
import eu.kanade.tachiyomi.data.source.model.Page
|
import eu.kanade.tachiyomi.data.source.model.Page
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.subjects.PublishSubject
|
import rx.subjects.PublishSubject
|
||||||
import java.util.*
|
import java.util.concurrent.CopyOnWriteArrayList
|
||||||
|
|
||||||
class DownloadQueue : ArrayList<Download>() {
|
class DownloadQueue : CopyOnWriteArrayList<Download>() {
|
||||||
|
|
||||||
private val statusSubject = PublishSubject.create<Download>()
|
private val statusSubject = PublishSubject.create<Download>()
|
||||||
|
|
||||||
|
@ -9,6 +9,8 @@ import android.os.IBinder
|
|||||||
import android.os.PowerManager
|
import android.os.PowerManager
|
||||||
import android.support.v4.app.NotificationCompat
|
import android.support.v4.app.NotificationCompat
|
||||||
import android.util.Pair
|
import android.util.Pair
|
||||||
|
import com.github.pwittchen.reactivenetwork.library.ConnectivityStatus
|
||||||
|
import com.github.pwittchen.reactivenetwork.library.ReactiveNetwork
|
||||||
import eu.kanade.tachiyomi.App
|
import eu.kanade.tachiyomi.App
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||||
@ -16,42 +18,14 @@ import eu.kanade.tachiyomi.data.database.models.Manga
|
|||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.data.source.SourceManager
|
import eu.kanade.tachiyomi.data.source.SourceManager
|
||||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||||
import eu.kanade.tachiyomi.util.AndroidComponentUtil
|
import eu.kanade.tachiyomi.util.*
|
||||||
import eu.kanade.tachiyomi.util.DeviceUtil
|
|
||||||
import eu.kanade.tachiyomi.util.notification
|
|
||||||
import eu.kanade.tachiyomi.util.notificationManager
|
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.Subscription
|
import rx.Subscription
|
||||||
import rx.schedulers.Schedulers
|
import rx.schedulers.Schedulers
|
||||||
import timber.log.Timber
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
// Intent key for forced library update
|
|
||||||
val UPDATE_IS_FORCED = "is_forced"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the start intent for [LibraryUpdateService].
|
|
||||||
* @param context the application context.
|
|
||||||
* @param isForced true when forcing library update
|
|
||||||
* @return the intent of the service.
|
|
||||||
*/
|
|
||||||
fun getIntent(context: Context, isForced: Boolean = false): Intent {
|
|
||||||
return Intent(context, LibraryUpdateService::class.java).apply {
|
|
||||||
putExtra(UPDATE_IS_FORCED, isForced)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the status of the service.
|
|
||||||
* @param context the application context.
|
|
||||||
* @return true if the service is running, false otherwise.
|
|
||||||
*/
|
|
||||||
fun isRunning(context: Context): Boolean {
|
|
||||||
return AndroidComponentUtil.isServiceRunning(context, LibraryUpdateService::class.java)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class will take care of updating the chapters of the manga from the library. It can be
|
* This class will take care of updating the chapters of the manga from the library. It can be
|
||||||
* started calling the [start] method. If it's already running, it won't do anything.
|
* started calling the [start] method. If it's already running, it won't do anything.
|
||||||
@ -77,6 +51,30 @@ class LibraryUpdateService : Service() {
|
|||||||
companion object {
|
companion object {
|
||||||
val UPDATE_NOTIFICATION_ID = 1
|
val UPDATE_NOTIFICATION_ID = 1
|
||||||
|
|
||||||
|
// Intent key for manual library update
|
||||||
|
val UPDATE_IS_MANUAL = "is_manual"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the start intent for [LibraryUpdateService].
|
||||||
|
* @param context the application context.
|
||||||
|
* @param isManual true when user triggers library update.
|
||||||
|
* @return the intent of the service.
|
||||||
|
*/
|
||||||
|
fun getIntent(context: Context, isManual: Boolean = false): Intent {
|
||||||
|
return Intent(context, LibraryUpdateService::class.java).apply {
|
||||||
|
putExtra(UPDATE_IS_MANUAL, isManual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the status of the service.
|
||||||
|
* @param context the application context.
|
||||||
|
* @return true if the service is running, false otherwise.
|
||||||
|
*/
|
||||||
|
fun isRunning(context: Context): Boolean {
|
||||||
|
return AndroidComponentUtil.isServiceRunning(context, LibraryUpdateService::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Static method to start the service. It will be started only if there isn't another
|
* Static method to start the service. It will be started only if there isn't another
|
||||||
* instance already running.
|
* instance already running.
|
||||||
@ -125,36 +123,52 @@ class LibraryUpdateService : Service() {
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method called when the service receives an intent. In this case, the content of the intent
|
* Method called when the service receives an intent.
|
||||||
* is irrelevant, because everything required is fetched in [updateLibrary].
|
* @param intent the start intent from.
|
||||||
* @param intent the intent from [start].
|
|
||||||
* @param flags the flags of the command.
|
* @param flags the flags of the command.
|
||||||
* @param startId the start id of this command.
|
* @param startId the start id of this command.
|
||||||
* @return the start value of the command.
|
* @return the start value of the command.
|
||||||
*/
|
*/
|
||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
// If there's no network available, set a component to start this service again when
|
|
||||||
// a connection is available.
|
// Get connectivity status
|
||||||
if (!DeviceUtil.isNetworkConnected(this)) {
|
val connection = ReactiveNetwork().getConnectivityStatus(this, true)
|
||||||
Timber.i("Sync canceled, connection not available")
|
|
||||||
showWarningNotification(getString(R.string.notification_no_connection_title),
|
// Get library update restrictions
|
||||||
getString(R.string.notification_no_connection_body))
|
val restrictions = preferences.libraryUpdateRestriction()
|
||||||
|
|
||||||
|
// Check if users updates library manual
|
||||||
|
val isManualUpdate = intent?.getBooleanExtra(UPDATE_IS_MANUAL, false) ?: false
|
||||||
|
|
||||||
|
// Whether to cancel the update.
|
||||||
|
var cancelUpdate = false
|
||||||
|
|
||||||
|
// Check if device has internet connection
|
||||||
|
// Check if device has wifi connection if only wifi is enabled
|
||||||
|
if (connection == ConnectivityStatus.OFFLINE || ("wifi" in restrictions
|
||||||
|
&& connection != ConnectivityStatus.WIFI_CONNECTED_HAS_INTERNET)) {
|
||||||
|
|
||||||
|
if (isManualUpdate) {
|
||||||
|
toast(R.string.notification_no_connection_title)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable library update when connection available
|
||||||
AndroidComponentUtil.toggleComponent(this, SyncOnConnectionAvailable::class.java, true)
|
AndroidComponentUtil.toggleComponent(this, SyncOnConnectionAvailable::class.java, true)
|
||||||
|
cancelUpdate = true
|
||||||
|
}
|
||||||
|
if (!isManualUpdate && "ac" in restrictions && !DeviceUtil.isPowerConnected(this)) {
|
||||||
|
AndroidComponentUtil.toggleComponent(this, SyncOnPowerConnected::class.java, true)
|
||||||
|
cancelUpdate = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cancelUpdate) {
|
||||||
stopSelf(startId)
|
stopSelf(startId)
|
||||||
return Service.START_NOT_STICKY
|
return Service.START_NOT_STICKY
|
||||||
}
|
}
|
||||||
|
|
||||||
// If user doesn't want to update while phone is not charging, cancel sync
|
// Stop enabled components.
|
||||||
else if (preferences.updateOnlyWhenCharging() && !(intent?.getBooleanExtra(UPDATE_IS_FORCED, false) ?: false) && !DeviceUtil.isPowerConnected(this)) {
|
AndroidComponentUtil.toggleComponent(this, SyncOnConnectionAvailable::class.java, false)
|
||||||
Timber.i("Sync canceled, not connected to ac power")
|
AndroidComponentUtil.toggleComponent(this, SyncOnPowerConnected::class.java, false)
|
||||||
// Create force library update intent
|
|
||||||
val forceIntent = getLibraryUpdateReceiverIntent(LibraryUpdateReceiver.FORCE_LIBRARY_UPDATE)
|
|
||||||
// Show warning
|
|
||||||
showWarningNotification(getString(R.string.notification_not_connected_to_ac_title),
|
|
||||||
getString(R.string.notification_not_connected_to_ac_body), forceIntent)
|
|
||||||
stopSelf(startId)
|
|
||||||
return Service.START_NOT_STICKY
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unsubscribe from any previous subscription if needed.
|
// Unsubscribe from any previous subscription if needed.
|
||||||
subscription?.unsubscribe()
|
subscription?.unsubscribe()
|
||||||
@ -173,15 +187,6 @@ class LibraryUpdateService : Service() {
|
|||||||
return Service.START_STICKY
|
return Service.START_STICKY
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a PendingIntent for LibraryUpdate broadcast class
|
|
||||||
* @param action id of action
|
|
||||||
*/
|
|
||||||
fun getLibraryUpdateReceiverIntent(action: String): PendingIntent {
|
|
||||||
return PendingIntent.getBroadcast(this, 0,
|
|
||||||
Intent(this, LibraryUpdateReceiver::class.java).apply { this.action = action }, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method that updates the library. It's called in a background thread, so it's safe to do
|
* Method that updates the library. It's called in a background thread, so it's safe to do
|
||||||
* heavy operations or network calls here.
|
* heavy operations or network calls here.
|
||||||
@ -195,7 +200,8 @@ class LibraryUpdateService : Service() {
|
|||||||
val newUpdates = ArrayList<Manga>()
|
val newUpdates = ArrayList<Manga>()
|
||||||
val failedUpdates = ArrayList<Manga>()
|
val failedUpdates = ArrayList<Manga>()
|
||||||
|
|
||||||
val cancelIntent = getLibraryUpdateReceiverIntent(LibraryUpdateReceiver.CANCEL_LIBRARY_UPDATE)
|
val cancelIntent = PendingIntent.getBroadcast(this, 0,
|
||||||
|
Intent(this, CancelUpdateReceiver::class.java), 0)
|
||||||
|
|
||||||
// Get the manga list that is going to be updated.
|
// Get the manga list that is going to be updated.
|
||||||
val allLibraryMangas = db.getFavoriteMangas().executeAsBlocking()
|
val allLibraryMangas = db.getFavoriteMangas().executeAsBlocking()
|
||||||
@ -299,12 +305,11 @@ class LibraryUpdateService : Service() {
|
|||||||
* @param body the body of the notification.
|
* @param body the body of the notification.
|
||||||
*/
|
*/
|
||||||
private fun showNotification(title: String, body: String) {
|
private fun showNotification(title: String, body: String) {
|
||||||
val n = notification() {
|
notificationManager.notify(UPDATE_NOTIFICATION_ID, notification() {
|
||||||
setSmallIcon(R.drawable.ic_refresh_white_24dp_img)
|
setSmallIcon(R.drawable.ic_refresh_white_24dp_img)
|
||||||
setContentTitle(title)
|
setContentTitle(title)
|
||||||
setContentText(body)
|
setContentText(body)
|
||||||
}
|
})
|
||||||
notificationManager.notify(UPDATE_NOTIFICATION_ID, n)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -314,35 +319,15 @@ class LibraryUpdateService : Service() {
|
|||||||
* @param total the total progress.
|
* @param total the total progress.
|
||||||
*/
|
*/
|
||||||
private fun showProgressNotification(manga: Manga, current: Int, total: Int, cancelIntent: PendingIntent) {
|
private fun showProgressNotification(manga: Manga, current: Int, total: Int, cancelIntent: PendingIntent) {
|
||||||
val n = notification() {
|
notificationManager.notify(UPDATE_NOTIFICATION_ID, notification() {
|
||||||
setSmallIcon(R.drawable.ic_refresh_white_24dp_img)
|
setSmallIcon(R.drawable.ic_refresh_white_24dp_img)
|
||||||
setContentTitle(manga.title)
|
setContentTitle(manga.title)
|
||||||
setProgress(total, current, false)
|
setProgress(total, current, false)
|
||||||
setOngoing(true)
|
setOngoing(true)
|
||||||
addAction(R.drawable.ic_clear_grey_24dp_img, getString(android.R.string.cancel), cancelIntent)
|
addAction(R.drawable.ic_clear_grey_24dp_img, getString(android.R.string.cancel), cancelIntent)
|
||||||
}
|
})
|
||||||
notificationManager.notify(UPDATE_NOTIFICATION_ID, n)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Show warning message when library can't be updated
|
|
||||||
* @param warningTitle title of warning
|
|
||||||
* @param warningBody warning information
|
|
||||||
* @param pendingIntent Intent called when action clicked
|
|
||||||
*/
|
|
||||||
private fun showWarningNotification(warningTitle: String, warningBody: String, pendingIntent: PendingIntent? = null) {
|
|
||||||
val n = notification() {
|
|
||||||
setSmallIcon(R.drawable.ic_warning_white_24dp_img)
|
|
||||||
setContentTitle(warningTitle)
|
|
||||||
setStyle(NotificationCompat.BigTextStyle().bigText(warningBody))
|
|
||||||
setContentIntent(notificationIntent)
|
|
||||||
if (pendingIntent != null) {
|
|
||||||
addAction(R.drawable.ic_refresh_grey_24dp_img, getString(R.string.action_force), pendingIntent)
|
|
||||||
}
|
|
||||||
setAutoCancel(true)
|
|
||||||
}
|
|
||||||
notificationManager.notify(UPDATE_NOTIFICATION_ID, n)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows the notification containing the result of the update done by the service.
|
* Shows the notification containing the result of the update done by the service.
|
||||||
@ -353,14 +338,13 @@ class LibraryUpdateService : Service() {
|
|||||||
val title = getString(R.string.notification_update_completed)
|
val title = getString(R.string.notification_update_completed)
|
||||||
val body = getUpdatedMangasBody(updates, failed)
|
val body = getUpdatedMangasBody(updates, failed)
|
||||||
|
|
||||||
val n = notification() {
|
notificationManager.notify(UPDATE_NOTIFICATION_ID, notification() {
|
||||||
setSmallIcon(R.drawable.ic_refresh_white_24dp_img)
|
setSmallIcon(R.drawable.ic_refresh_white_24dp_img)
|
||||||
setContentTitle(title)
|
setContentTitle(title)
|
||||||
setStyle(NotificationCompat.BigTextStyle().bigText(body))
|
setStyle(NotificationCompat.BigTextStyle().bigText(body))
|
||||||
setContentIntent(notificationIntent)
|
setContentIntent(notificationIntent)
|
||||||
setAutoCancel(true)
|
setAutoCancel(true)
|
||||||
}
|
})
|
||||||
notificationManager.notify(UPDATE_NOTIFICATION_ID, n)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -385,7 +369,6 @@ class LibraryUpdateService : Service() {
|
|||||||
* network changes.
|
* network changes.
|
||||||
*/
|
*/
|
||||||
class SyncOnConnectionAvailable : BroadcastReceiver() {
|
class SyncOnConnectionAvailable : BroadcastReceiver() {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method called when a network change occurs.
|
* Method called when a network change occurs.
|
||||||
* @param context the application context.
|
* @param context the application context.
|
||||||
@ -394,36 +377,39 @@ class LibraryUpdateService : Service() {
|
|||||||
override fun onReceive(context: Context, intent: Intent) {
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
if (DeviceUtil.isNetworkConnected(context)) {
|
if (DeviceUtil.isNetworkConnected(context)) {
|
||||||
AndroidComponentUtil.toggleComponent(context, this.javaClass, false)
|
AndroidComponentUtil.toggleComponent(context, this.javaClass, false)
|
||||||
context.startService(getIntent(context))
|
start(context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class that triggers the library to update.
|
* Class that triggers the library to update when connected to power.
|
||||||
*/
|
*/
|
||||||
class LibraryUpdateReceiver : BroadcastReceiver() {
|
class SyncOnPowerConnected: BroadcastReceiver() {
|
||||||
companion object {
|
/**
|
||||||
// Cancel library update action
|
* Method called when AC is connected.
|
||||||
val CANCEL_LIBRARY_UPDATE = "eu.kanade.CANCEL_LIBRARY_UPDATE"
|
* @param context the application context.
|
||||||
// Force library update
|
* @param intent the intent received.
|
||||||
val FORCE_LIBRARY_UPDATE = "eu.kanade.FORCE_LIBRARY_UPDATE"
|
*/
|
||||||
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
|
AndroidComponentUtil.toggleComponent(context, this.javaClass, false)
|
||||||
|
start(context)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class that stops updating the library.
|
||||||
|
*/
|
||||||
|
class CancelUpdateReceiver : BroadcastReceiver() {
|
||||||
/**
|
/**
|
||||||
* Method called when user wants a library update.
|
* Method called when user wants a library update.
|
||||||
* @param context the application context.
|
* @param context the application context.
|
||||||
* @param intent the intent received.
|
* @param intent the intent received.
|
||||||
*/
|
*/
|
||||||
override fun onReceive(context: Context, intent: Intent) {
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
when (intent.action) {
|
|
||||||
CANCEL_LIBRARY_UPDATE -> {
|
|
||||||
LibraryUpdateService.stop(context)
|
LibraryUpdateService.stop(context)
|
||||||
context.notificationManager.cancel(UPDATE_NOTIFICATION_ID)
|
context.notificationManager.cancel(UPDATE_NOTIFICATION_ID)
|
||||||
}
|
}
|
||||||
FORCE_LIBRARY_UPDATE -> LibraryUpdateService.start(context, true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -24,8 +24,8 @@ abstract class MangaSyncService(private val context: Context, val id: Int) {
|
|||||||
abstract fun login(username: String, password: String): Observable<Boolean>
|
abstract fun login(username: String, password: String): Observable<Boolean>
|
||||||
|
|
||||||
open val isLogged: Boolean
|
open val isLogged: Boolean
|
||||||
get() = !preferences.getMangaSyncUsername(this).isEmpty() &&
|
get() = !preferences.mangaSyncUsername(this).isEmpty() &&
|
||||||
!preferences.getMangaSyncPassword(this).isEmpty()
|
!preferences.mangaSyncPassword(this).isEmpty()
|
||||||
|
|
||||||
abstract fun update(manga: MangaSync): Observable<Response>
|
abstract fun update(manga: MangaSync): Observable<Response>
|
||||||
|
|
||||||
|
@ -46,8 +46,8 @@ class MyAnimeList(private val context: Context, id: Int) : MangaSyncService(cont
|
|||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val username = preferences.getMangaSyncUsername(this)
|
val username = preferences.mangaSyncUsername(this)
|
||||||
val password = preferences.getMangaSyncPassword(this)
|
val password = preferences.mangaSyncPassword(this)
|
||||||
|
|
||||||
if (!username.isEmpty() && !password.isEmpty()) {
|
if (!username.isEmpty() && !password.isEmpty()) {
|
||||||
createHeaders(username, password)
|
createHeaders(username, password)
|
||||||
|
@ -10,10 +10,13 @@ import java.net.CookieStore
|
|||||||
|
|
||||||
class NetworkHelper(context: Context) {
|
class NetworkHelper(context: Context) {
|
||||||
|
|
||||||
private val client: OkHttpClient
|
private val cacheDir = File(context.cacheDir, "network_cache")
|
||||||
private val forceCacheClient: OkHttpClient
|
|
||||||
|
|
||||||
private val cookieManager: CookieManager
|
private val cacheSize = 5L * 1024 * 1024 // 5 MiB
|
||||||
|
|
||||||
|
private val cookieManager = CookieManager().apply {
|
||||||
|
setCookiePolicy(CookiePolicy.ACCEPT_ALL)
|
||||||
|
}
|
||||||
|
|
||||||
private val forceCacheInterceptor = { chain: Interceptor.Chain ->
|
private val forceCacheInterceptor = { chain: Interceptor.Chain ->
|
||||||
val originalResponse = chain.proceed(chain.request())
|
val originalResponse = chain.proceed(chain.request())
|
||||||
@ -23,41 +26,39 @@ class NetworkHelper(context: Context) {
|
|||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
private val cacheSize = 5L * 1024 * 1024 // 5 MiB
|
private val client = OkHttpClient.Builder()
|
||||||
private val cacheDir = "network_cache"
|
|
||||||
|
|
||||||
init {
|
|
||||||
val cacheDir = File(context.cacheDir, cacheDir)
|
|
||||||
|
|
||||||
cookieManager = CookieManager()
|
|
||||||
cookieManager.setCookiePolicy(CookiePolicy.ACCEPT_ALL)
|
|
||||||
|
|
||||||
client = OkHttpClient.Builder()
|
|
||||||
.cookieJar(JavaNetCookieJar(cookieManager))
|
.cookieJar(JavaNetCookieJar(cookieManager))
|
||||||
.cache(Cache(cacheDir, cacheSize))
|
.cache(Cache(cacheDir, cacheSize))
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
forceCacheClient = client.newBuilder()
|
private val forceCacheClient = client.newBuilder()
|
||||||
.addNetworkInterceptor(forceCacheInterceptor)
|
.addNetworkInterceptor(forceCacheInterceptor)
|
||||||
.build()
|
.build()
|
||||||
}
|
|
||||||
|
val cookies: CookieStore
|
||||||
|
get() = cookieManager.cookieStore
|
||||||
|
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
fun request(request: Request, forceCache: Boolean = false): Observable<Response> {
|
fun request(request: Request, forceCache: Boolean = false): Observable<Response> {
|
||||||
return Observable.fromCallable {
|
return Observable.fromCallable {
|
||||||
val c = if (forceCache) forceCacheClient else client
|
val c = if (forceCache) forceCacheClient else client
|
||||||
c.newCall(request).execute()
|
c.newCall(request).execute().apply { body().close() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
fun requestBody(request: Request, forceCache: Boolean = false): Observable<String> {
|
fun requestBody(request: Request, forceCache: Boolean = false): Observable<String> {
|
||||||
return request(request, forceCache)
|
return Observable.fromCallable {
|
||||||
.map { it.body().string() }
|
val c = if (forceCache) forceCacheClient else client
|
||||||
|
c.newCall(request).execute().body().string()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun requestBodyProgress(request: Request, listener: ProgressListener): Observable<Response> {
|
fun requestBodyProgress(request: Request, listener: ProgressListener): Observable<Response> {
|
||||||
return Observable.fromCallable {
|
return Observable.fromCallable { requestBodyProgressBlocking(request, listener) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun requestBodyProgressBlocking(request: Request, listener: ProgressListener): Response {
|
||||||
val progressClient = client.newBuilder()
|
val progressClient = client.newBuilder()
|
||||||
.cache(null)
|
.cache(null)
|
||||||
.addNetworkInterceptor { chain ->
|
.addNetworkInterceptor { chain ->
|
||||||
@ -68,11 +69,8 @@ class NetworkHelper(context: Context) {
|
|||||||
}
|
}
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
progressClient.newCall(request).execute()
|
return progressClient.newCall(request).execute()
|
||||||
}.retry(1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val cookies: CookieStore
|
|
||||||
get() = cookieManager.cookieStore
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,90 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.preference
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class stores the keys for the preferences in the application. Most of them are defined
|
||||||
|
* in the file "keys.xml". By using this class we can define preferences in one place and get them
|
||||||
|
* referenced here.
|
||||||
|
*/
|
||||||
|
class PreferenceKeys(context: Context) {
|
||||||
|
|
||||||
|
val rotation = context.getString(R.string.pref_rotation_type_key)
|
||||||
|
|
||||||
|
val enableTransitions = context.getString(R.string.pref_enable_transitions_key)
|
||||||
|
|
||||||
|
val showPageNumber = context.getString(R.string.pref_show_page_number_key)
|
||||||
|
|
||||||
|
val hideStatusBar = context.getString(R.string.pref_hide_status_bar_key)
|
||||||
|
|
||||||
|
val keepScreenOn = context.getString(R.string.pref_keep_screen_on_key)
|
||||||
|
|
||||||
|
val customBrightness = context.getString(R.string.pref_custom_brightness_key)
|
||||||
|
|
||||||
|
val customBrightnessValue = context.getString(R.string.pref_custom_brightness_value_key)
|
||||||
|
|
||||||
|
val defaultViewer = context.getString(R.string.pref_default_viewer_key)
|
||||||
|
|
||||||
|
val imageScaleType = context.getString(R.string.pref_image_scale_type_key)
|
||||||
|
|
||||||
|
val imageDecoder = context.getString(R.string.pref_image_decoder_key)
|
||||||
|
|
||||||
|
val zoomStart = context.getString(R.string.pref_zoom_start_key)
|
||||||
|
|
||||||
|
val readerTheme = context.getString(R.string.pref_reader_theme_key)
|
||||||
|
|
||||||
|
val readWithTapping = context.getString(R.string.pref_read_with_tapping_key)
|
||||||
|
|
||||||
|
val readWithVolumeKeys = context.getString(R.string.pref_read_with_volume_keys_key)
|
||||||
|
|
||||||
|
val reencodeImage = context.getString(R.string.pref_reencode_key)
|
||||||
|
|
||||||
|
val portraitColumns = context.getString(R.string.pref_library_columns_portrait_key)
|
||||||
|
|
||||||
|
val landscapeColumns = context.getString(R.string.pref_library_columns_landscape_key)
|
||||||
|
|
||||||
|
val updateOnlyNonCompleted = context.getString(R.string.pref_update_only_non_completed_key)
|
||||||
|
|
||||||
|
val autoUpdateMangaSync = context.getString(R.string.pref_auto_update_manga_sync_key)
|
||||||
|
|
||||||
|
val askUpdateMangaSync = context.getString(R.string.pref_ask_update_manga_sync_key)
|
||||||
|
|
||||||
|
val lastUsedCatalogueSource = context.getString(R.string.pref_last_catalogue_source_key)
|
||||||
|
|
||||||
|
val seamlessMode = context.getString(R.string.pref_seamless_mode_key)
|
||||||
|
|
||||||
|
val catalogueAsList = context.getString(R.string.pref_display_catalogue_as_list)
|
||||||
|
|
||||||
|
val enabledLanguages = context.getString(R.string.pref_source_languages)
|
||||||
|
|
||||||
|
val downloadsDirectory = context.getString(R.string.pref_download_directory_key)
|
||||||
|
|
||||||
|
val downloadThreads = context.getString(R.string.pref_download_slots_key)
|
||||||
|
|
||||||
|
val downloadOnlyOverWifi = context.getString(R.string.pref_download_only_over_wifi_key)
|
||||||
|
|
||||||
|
val removeAfterRead = context.getString(R.string.pref_remove_after_read_key)
|
||||||
|
|
||||||
|
val removeAfterReadPrevious = context.getString(R.string.pref_remove_after_read_previous_key)
|
||||||
|
|
||||||
|
val removeAfterMarkedAsRead = context.getString(R.string.pref_remove_after_marked_as_read_key)
|
||||||
|
|
||||||
|
val libraryUpdateInterval = context.getString(R.string.pref_library_update_interval_key)
|
||||||
|
|
||||||
|
val libraryUpdateRestriction = context.getString(R.string.pref_library_update_restriction_key)
|
||||||
|
|
||||||
|
val filterDownloaded = context.getString(R.string.pref_filter_downloaded_key)
|
||||||
|
|
||||||
|
val filterUnread = context.getString(R.string.pref_filter_unread_key)
|
||||||
|
|
||||||
|
fun sourceUsername(sourceId: Int) = "pref_source_username_$sourceId"
|
||||||
|
|
||||||
|
|
||||||
|
fun sourcePassword(sourceId: Int) = "pref_source_password_$sourceId"
|
||||||
|
|
||||||
|
fun syncUsername(syncId: Int) = "pref_mangasync_username_$syncId"
|
||||||
|
|
||||||
|
fun syncPassword(syncId: Int) = "pref_mangasync_password_$syncId"
|
||||||
|
|
||||||
|
}
|
@ -15,36 +15,35 @@ fun <T> Preference<T>.getOrDefault(): T = get() ?: defaultValue()!!
|
|||||||
|
|
||||||
class PreferencesHelper(private val context: Context) {
|
class PreferencesHelper(private val context: Context) {
|
||||||
|
|
||||||
|
val keys = PreferenceKeys(context)
|
||||||
|
|
||||||
private val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
private val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
private val rxPrefs = RxSharedPreferences.create(prefs)
|
private val rxPrefs = RxSharedPreferences.create(prefs)
|
||||||
|
|
||||||
private val defaultDownloadsDir: File
|
private val defaultDownloadsDir = File(Environment.getExternalStorageDirectory().absolutePath +
|
||||||
|
|
||||||
init {
|
|
||||||
defaultDownloadsDir = File(Environment.getExternalStorageDirectory().absolutePath +
|
|
||||||
File.separator + context.getString(R.string.app_name), "downloads")
|
File.separator + context.getString(R.string.app_name), "downloads")
|
||||||
|
|
||||||
|
init {
|
||||||
// Don't display downloaded chapters in gallery apps creating a ".nomedia" file
|
// Don't display downloaded chapters in gallery apps creating a ".nomedia" file
|
||||||
try {
|
try {
|
||||||
File(downloadsDirectory, ".nomedia").createNewFile()
|
File(downloadsDirectory().getOrDefault(), ".nomedia").createNewFile()
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
/* Ignore */
|
/* Ignore */
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
const val SOURCE_ACCOUNT_USERNAME = "pref_source_username_"
|
|
||||||
const val SOURCE_ACCOUNT_PASSWORD = "pref_source_password_"
|
|
||||||
const val MANGASYNC_ACCOUNT_USERNAME = "pref_mangasync_username_"
|
|
||||||
const val MANGASYNC_ACCOUNT_PASSWORD = "pref_mangasync_password_"
|
|
||||||
|
|
||||||
fun getLibraryUpdateInterval(context: Context): Int {
|
fun getLibraryUpdateInterval(context: Context): Int {
|
||||||
return PreferenceManager.getDefaultSharedPreferences(context).getInt(
|
return PreferenceManager.getDefaultSharedPreferences(context).getInt(
|
||||||
context.getString(R.string.pref_library_update_interval_key), 0)
|
context.getString(R.string.pref_library_update_interval_key), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getAutomaticUpdateStatus(context: Context): Boolean {
|
||||||
|
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(
|
||||||
|
context.getString(R.string.pref_enable_automatic_updates), false)
|
||||||
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun getTheme(context: Context): Int {
|
fun getTheme(context: Context): Int {
|
||||||
return PreferenceManager.getDefaultSharedPreferences(context).getInt(
|
return PreferenceManager.getDefaultSharedPreferences(context).getInt(
|
||||||
@ -52,165 +51,98 @@ class PreferencesHelper(private val context: Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getKey(keyResource: Int): String {
|
|
||||||
return context.getString(keyResource)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun clear() {
|
fun clear() {
|
||||||
prefs.edit().clear().apply()
|
prefs.edit().clear().apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun rotation(): Preference<Int> {
|
fun rotation() = rxPrefs.getInteger(keys.rotation, 1)
|
||||||
return rxPrefs.getInteger(getKey(R.string.pref_rotation_type_key), 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun enableTransitions(): Preference<Boolean> {
|
fun enableTransitions() = rxPrefs.getBoolean(keys.enableTransitions, true)
|
||||||
return rxPrefs.getBoolean(getKey(R.string.pref_enable_transitions_key), true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun showPageNumber(): Preference<Boolean> {
|
fun showPageNumber() = rxPrefs.getBoolean(keys.showPageNumber, true)
|
||||||
return rxPrefs.getBoolean(getKey(R.string.pref_show_page_number_key), true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun hideStatusBar(): Preference<Boolean> {
|
fun hideStatusBar() = rxPrefs.getBoolean(keys.hideStatusBar, true)
|
||||||
return rxPrefs.getBoolean(getKey(R.string.pref_hide_status_bar_key), true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun keepScreenOn(): Preference<Boolean> {
|
fun keepScreenOn() = rxPrefs.getBoolean(keys.keepScreenOn, true)
|
||||||
return rxPrefs.getBoolean(getKey(R.string.pref_keep_screen_on_key), true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun customBrightness(): Preference<Boolean> {
|
fun customBrightness() = rxPrefs.getBoolean(keys.customBrightness, false)
|
||||||
return rxPrefs.getBoolean(getKey(R.string.pref_custom_brightness_key), false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun customBrightnessValue(): Preference<Float> {
|
fun customBrightnessValue() = rxPrefs.getFloat(keys.customBrightnessValue, 0f)
|
||||||
return rxPrefs.getFloat(getKey(R.string.pref_custom_brightness_value_key), 0f)
|
|
||||||
}
|
|
||||||
|
|
||||||
val defaultViewer: Int
|
fun defaultViewer() = prefs.getInt(keys.defaultViewer, 1)
|
||||||
get() = prefs.getInt(getKey(R.string.pref_default_viewer_key), 1)
|
|
||||||
|
|
||||||
fun imageScaleType(): Preference<Int> {
|
fun imageScaleType() = rxPrefs.getInteger(keys.imageScaleType, 1)
|
||||||
return rxPrefs.getInteger(getKey(R.string.pref_image_scale_type_key), 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun imageDecoder(): Preference<Int> {
|
fun imageDecoder() = rxPrefs.getInteger(keys.imageDecoder, 0)
|
||||||
return rxPrefs.getInteger(getKey(R.string.pref_image_decoder_key), 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun zoomStart(): Preference<Int> {
|
fun zoomStart() = rxPrefs.getInteger(keys.zoomStart, 1)
|
||||||
return rxPrefs.getInteger(getKey(R.string.pref_zoom_start_key), 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun readerTheme(): Preference<Int> {
|
fun readerTheme() = rxPrefs.getInteger(keys.readerTheme, 0)
|
||||||
return rxPrefs.getInteger(getKey(R.string.pref_reader_theme_key), 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun portraitColumns(): Preference<Int> {
|
fun readWithTapping() = rxPrefs.getBoolean(keys.readWithTapping, true)
|
||||||
return rxPrefs.getInteger(getKey(R.string.pref_library_columns_portrait_key), 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun landscapeColumns(): Preference<Int> {
|
fun readWithVolumeKeys() = rxPrefs.getBoolean(keys.readWithVolumeKeys, false)
|
||||||
return rxPrefs.getInteger(getKey(R.string.pref_library_columns_landscape_key), 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateOnlyNonCompleted(): Boolean {
|
fun reencodeImage() = prefs.getBoolean(keys.reencodeImage, false)
|
||||||
return prefs.getBoolean(getKey(R.string.pref_update_only_non_completed_key), false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun autoUpdateMangaSync(): Boolean {
|
fun portraitColumns() = rxPrefs.getInteger(keys.portraitColumns, 0)
|
||||||
return prefs.getBoolean(getKey(R.string.pref_auto_update_manga_sync_key), true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun askUpdateMangaSync(): Boolean {
|
fun landscapeColumns() = rxPrefs.getInteger(keys.landscapeColumns, 0)
|
||||||
return prefs.getBoolean(getKey(R.string.pref_ask_update_manga_sync_key), false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun lastUsedCatalogueSource(): Preference<Int> {
|
fun updateOnlyNonCompleted() = prefs.getBoolean(keys.updateOnlyNonCompleted, false)
|
||||||
return rxPrefs.getInteger(getKey(R.string.pref_last_catalogue_source_key), -1)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun seamlessMode(): Boolean {
|
fun autoUpdateMangaSync() = prefs.getBoolean(keys.autoUpdateMangaSync, true)
|
||||||
return prefs.getBoolean(getKey(R.string.pref_seamless_mode_key), true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun catalogueAsList(): Preference<Boolean> {
|
fun askUpdateMangaSync() = prefs.getBoolean(keys.askUpdateMangaSync, false)
|
||||||
return rxPrefs.getBoolean(getKey(R.string.pref_display_catalogue_as_list), false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun enabledLanguages(): Preference<MutableSet<String>> {
|
fun lastUsedCatalogueSource() = rxPrefs.getInteger(keys.lastUsedCatalogueSource, -1)
|
||||||
return rxPrefs.getStringSet(getKey(R.string.pref_source_languages), setOf("EN"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getSourceUsername(source: Source): String {
|
fun seamlessMode() = prefs.getBoolean(keys.seamlessMode, true)
|
||||||
return prefs.getString(SOURCE_ACCOUNT_USERNAME + source.id, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getSourcePassword(source: Source): String {
|
fun catalogueAsList() = rxPrefs.getBoolean(keys.catalogueAsList, false)
|
||||||
return prefs.getString(SOURCE_ACCOUNT_PASSWORD + source.id, "")
|
|
||||||
}
|
fun enabledLanguages() = rxPrefs.getStringSet(keys.enabledLanguages, setOf("EN"))
|
||||||
|
|
||||||
|
fun sourceUsername(source: Source) = prefs.getString(keys.sourceUsername(source.id), "")
|
||||||
|
|
||||||
|
fun sourcePassword(source: Source) = prefs.getString(keys.sourcePassword(source.id), "")
|
||||||
|
|
||||||
fun setSourceCredentials(source: Source, username: String, password: String) {
|
fun setSourceCredentials(source: Source, username: String, password: String) {
|
||||||
prefs.edit()
|
prefs.edit()
|
||||||
.putString(SOURCE_ACCOUNT_USERNAME + source.id, username)
|
.putString(keys.sourceUsername(source.id), username)
|
||||||
.putString(SOURCE_ACCOUNT_PASSWORD + source.id, password)
|
.putString(keys.sourcePassword(source.id), password)
|
||||||
.apply()
|
.apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getMangaSyncUsername(sync: MangaSyncService): String {
|
fun mangaSyncUsername(sync: MangaSyncService) = prefs.getString(keys.syncUsername(sync.id), "")
|
||||||
return prefs.getString(MANGASYNC_ACCOUNT_USERNAME + sync.id, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getMangaSyncPassword(sync: MangaSyncService): String {
|
fun mangaSyncPassword(sync: MangaSyncService) = prefs.getString(keys.syncPassword(sync.id), "")
|
||||||
return prefs.getString(MANGASYNC_ACCOUNT_PASSWORD + sync.id, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setMangaSyncCredentials(sync: MangaSyncService, username: String, password: String) {
|
fun setMangaSyncCredentials(sync: MangaSyncService, username: String, password: String) {
|
||||||
prefs.edit()
|
prefs.edit()
|
||||||
.putString(MANGASYNC_ACCOUNT_USERNAME + sync.id, username)
|
.putString(keys.syncUsername(sync.id), username)
|
||||||
.putString(MANGASYNC_ACCOUNT_PASSWORD + sync.id, password)
|
.putString(keys.syncPassword(sync.id), password)
|
||||||
.apply()
|
.apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
var downloadsDirectory: String
|
fun downloadsDirectory() = rxPrefs.getString(keys.downloadsDirectory, defaultDownloadsDir.absolutePath)
|
||||||
get() = prefs.getString(getKey(R.string.pref_download_directory_key), defaultDownloadsDir.absolutePath)
|
|
||||||
set(path) = prefs.edit().putString(getKey(R.string.pref_download_directory_key), path).apply()
|
|
||||||
|
|
||||||
fun downloadThreads(): Preference<Int> {
|
fun downloadThreads() = rxPrefs.getInteger(keys.downloadThreads, 1)
|
||||||
return rxPrefs.getInteger(getKey(R.string.pref_download_slots_key), 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun downloadOnlyOverWifi(): Boolean {
|
fun downloadOnlyOverWifi() = prefs.getBoolean(keys.downloadOnlyOverWifi, true)
|
||||||
return prefs.getBoolean(getKey(R.string.pref_download_only_over_wifi_key), true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun removeAfterRead(): Boolean {
|
fun removeAfterRead() = prefs.getBoolean(keys.removeAfterRead, false)
|
||||||
return prefs.getBoolean(getKey(R.string.pref_remove_after_read_key), false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun removeAfterReadPrevious(): Boolean {
|
fun removeAfterReadPrevious() = prefs.getBoolean(keys.removeAfterReadPrevious, false)
|
||||||
return prefs.getBoolean(getKey(R.string.pref_remove_after_read_previous_key), false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun removeAfterMarkedAsRead(): Boolean {
|
fun removeAfterMarkedAsRead() = prefs.getBoolean(keys.removeAfterMarkedAsRead, false)
|
||||||
return prefs.getBoolean(getKey(R.string.pref_remove_after_marked_as_read_key), false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateOnlyWhenCharging(): Boolean {
|
fun libraryUpdateInterval() = rxPrefs.getInteger(keys.libraryUpdateInterval, 0)
|
||||||
return prefs.getBoolean(getKey(R.string.pref_update_only_when_charging_key), false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun libraryUpdateInterval(): Preference<Int> {
|
fun libraryUpdateRestriction() = prefs.getStringSet(keys.libraryUpdateRestriction, emptySet())
|
||||||
return rxPrefs.getInteger(getKey(R.string.pref_library_update_interval_key), 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun filterDownloaded(): Preference<Boolean> {
|
fun filterDownloaded() = rxPrefs.getBoolean(keys.filterDownloaded, false)
|
||||||
return rxPrefs.getBoolean(getKey(R.string.pref_filter_downloaded_key), false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun filterUnread(): Preference<Boolean> {
|
fun filterUnread() = rxPrefs.getBoolean(keys.filterUnread, false)
|
||||||
return rxPrefs.getBoolean(getKey(R.string.pref_filter_unread_key), false)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
package eu.kanade.tachiyomi.data.source
|
package eu.kanade.tachiyomi.data.source
|
||||||
|
|
||||||
class Language(val lang: String, val code: String)
|
class Language(val code: String, val lang: String)
|
||||||
|
|
||||||
val EN = Language("English", "EN")
|
val EN = Language("EN", "English")
|
||||||
val RU = Language("Russian", "RU")
|
val RU = Language("RU", "Russian")
|
||||||
|
|
||||||
fun getLanguages(): List<Language> = listOf(EN, RU)
|
fun getLanguages() = listOf(EN, RU)
|
@ -181,13 +181,19 @@ abstract class Source(context: Context) : BaseSource() {
|
|||||||
page.status = Page.DOWNLOAD_IMAGE
|
page.status = Page.DOWNLOAD_IMAGE
|
||||||
return getImageProgressResponse(page)
|
return getImageProgressResponse(page)
|
||||||
.flatMap { resp ->
|
.flatMap { resp ->
|
||||||
chapterCache.putImageToCache(page.imageUrl, resp)
|
chapterCache.putImageToCache(page.imageUrl, resp, prefs.reencodeImage())
|
||||||
Observable.just(page)
|
Observable.just(page)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun getImageProgressResponse(page: Page): Observable<Response> {
|
open fun getImageProgressResponse(page: Page): Observable<Response> {
|
||||||
return networkService.requestBodyProgress(imageRequest(page), page)
|
return networkService.requestBodyProgress(imageRequest(page), page)
|
||||||
|
.doOnNext {
|
||||||
|
if (!it.isSuccessful) {
|
||||||
|
it.body().close()
|
||||||
|
throw RuntimeException("Not a valid response")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun savePageList(chapterUrl: String, pages: List<Page>?) {
|
fun savePageList(chapterUrl: String, pages: List<Page>?) {
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package eu.kanade.tachiyomi.data.source.model;
|
package eu.kanade.tachiyomi.data.source.model;
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Chapter;
|
import eu.kanade.tachiyomi.data.database.models.Chapter;
|
||||||
@ -25,6 +24,14 @@ public class Page implements ProgressListener {
|
|||||||
public static final int READY = 3;
|
public static final int READY = 3;
|
||||||
public static final int ERROR = 4;
|
public static final int ERROR = 4;
|
||||||
|
|
||||||
|
public Page(int pageNumber, String url) {
|
||||||
|
this(pageNumber, url, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Page(int pageNumber, String url, String imageUrl) {
|
||||||
|
this(pageNumber, url, imageUrl, null);
|
||||||
|
}
|
||||||
|
|
||||||
public Page(int pageNumber, String url, String imageUrl, String imagePath) {
|
public Page(int pageNumber, String url, String imageUrl, String imagePath) {
|
||||||
this.pageNumber = pageNumber;
|
this.pageNumber = pageNumber;
|
||||||
this.url = url;
|
this.url = url;
|
||||||
@ -32,10 +39,6 @@ public class Page implements ProgressListener {
|
|||||||
this.imagePath = imagePath;
|
this.imagePath = imagePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Page(int pageNumber, String url) {
|
|
||||||
this(pageNumber, url, null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getPageNumber() {
|
public int getPageNumber() {
|
||||||
return pageNumber;
|
return pageNumber;
|
||||||
}
|
}
|
||||||
|
@ -372,8 +372,8 @@ public class Batoto extends LoginSource {
|
|||||||
@Override
|
@Override
|
||||||
public Observable<List<Chapter>> pullChaptersFromNetwork(final String mangaUrl) {
|
public Observable<List<Chapter>> pullChaptersFromNetwork(final String mangaUrl) {
|
||||||
Observable<List<Chapter>> observable;
|
Observable<List<Chapter>> observable;
|
||||||
String username = getPrefs().getSourceUsername(this);
|
String username = getPrefs().sourceUsername(this);
|
||||||
String password = getPrefs().getSourcePassword(this);
|
String password = getPrefs().sourcePassword(this);
|
||||||
if (username.isEmpty() && password.isEmpty()) {
|
if (username.isEmpty() && password.isEmpty()) {
|
||||||
observable = Observable.error(new Exception("User not logged"));
|
observable = Observable.error(new Exception("User not logged"));
|
||||||
}
|
}
|
||||||
|
@ -1,99 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.updater;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.AsyncTask;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.net.HttpURLConnection;
|
|
||||||
import java.net.URL;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.App;
|
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper;
|
|
||||||
|
|
||||||
public class UpdateDownloader extends AsyncTask<String, Void, Void> {
|
|
||||||
/**
|
|
||||||
* Name of cache directory.
|
|
||||||
*/
|
|
||||||
private static final String PARAMETER_CACHE_DIRECTORY = "apk_downloads";
|
|
||||||
/**
|
|
||||||
* Interface to global information about an application environment.
|
|
||||||
*/
|
|
||||||
private final Context context;
|
|
||||||
/**
|
|
||||||
* Cache directory used for cache management.
|
|
||||||
*/
|
|
||||||
private final File cacheDir;
|
|
||||||
@Inject PreferencesHelper preferencesHelper;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor of UpdaterCache.
|
|
||||||
*
|
|
||||||
* @param context application environment interface.
|
|
||||||
*/
|
|
||||||
public UpdateDownloader(Context context) {
|
|
||||||
App.get(context).getComponent().inject(this);
|
|
||||||
this.context = context;
|
|
||||||
|
|
||||||
// Get cache directory from parameter.
|
|
||||||
cacheDir = new File(preferencesHelper.getDownloadsDirectory(), PARAMETER_CACHE_DIRECTORY);
|
|
||||||
|
|
||||||
// Create cache directory.
|
|
||||||
createCacheDir();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create cache directory if it doesn't exist
|
|
||||||
*
|
|
||||||
* @return true if cache dir is created otherwise false.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("UnusedReturnValue")
|
|
||||||
private boolean createCacheDir() {
|
|
||||||
return !cacheDir.exists() && cacheDir.mkdirs();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Void doInBackground(String... args) {
|
|
||||||
try {
|
|
||||||
createCacheDir();
|
|
||||||
|
|
||||||
URL url = new URL(args[0]);
|
|
||||||
HttpURLConnection c = (HttpURLConnection) url.openConnection();
|
|
||||||
c.connect();
|
|
||||||
|
|
||||||
File outputFile = new File(cacheDir, "update.apk");
|
|
||||||
if (outputFile.exists()) {
|
|
||||||
//noinspection ResultOfMethodCallIgnored
|
|
||||||
outputFile.delete();
|
|
||||||
}
|
|
||||||
FileOutputStream fos = new FileOutputStream(outputFile);
|
|
||||||
|
|
||||||
InputStream is = c.getInputStream();
|
|
||||||
|
|
||||||
byte[] buffer = new byte[1024];
|
|
||||||
int len1;
|
|
||||||
while ((len1 = is.read(buffer)) != -1) {
|
|
||||||
fos.write(buffer, 0, len1);
|
|
||||||
}
|
|
||||||
fos.close();
|
|
||||||
is.close();
|
|
||||||
|
|
||||||
// Prompt install interface
|
|
||||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
|
||||||
intent.setDataAndType(Uri.fromFile(outputFile), "application/vnd.android.package-archive");
|
|
||||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); // without this flag android returned a intent error!
|
|
||||||
context.startActivity(intent);
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,199 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.updater
|
||||||
|
|
||||||
|
import android.app.Notification
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.AsyncTask
|
||||||
|
import android.support.v4.app.NotificationCompat
|
||||||
|
import eu.kanade.tachiyomi.App
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.data.network.NetworkHelper
|
||||||
|
import eu.kanade.tachiyomi.data.network.ProgressListener
|
||||||
|
import eu.kanade.tachiyomi.data.network.get
|
||||||
|
import eu.kanade.tachiyomi.util.notificationManager
|
||||||
|
import eu.kanade.tachiyomi.util.saveTo
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.io.File
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class UpdateDownloader(private val context: Context) :
|
||||||
|
AsyncTask<String, Int, UpdateDownloader.DownloadResult>() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* Prompt user with apk install intent
|
||||||
|
* @param context context
|
||||||
|
* @param file file of apk that is installed
|
||||||
|
*/
|
||||||
|
fun installAPK(context: Context, file: File) {
|
||||||
|
// Prompt install interface
|
||||||
|
val intent = Intent(Intent.ACTION_VIEW)
|
||||||
|
intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive")
|
||||||
|
// Without this flag android returned a intent error!
|
||||||
|
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||||
|
context.startActivity(intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject lateinit var network: NetworkHelper
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default download dir
|
||||||
|
*/
|
||||||
|
val apkFile = File(context.externalCacheDir, "update.apk")
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notification builder
|
||||||
|
*/
|
||||||
|
val notificationBuilder = NotificationCompat.Builder(context)
|
||||||
|
|
||||||
|
init {
|
||||||
|
App.get(context).component.inject(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class containing download result
|
||||||
|
* @param url url of file
|
||||||
|
* @param successful status of download
|
||||||
|
*/
|
||||||
|
class DownloadResult(var url: String, var successful: Boolean)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called before downloading
|
||||||
|
*/
|
||||||
|
override fun onPreExecute() {
|
||||||
|
// Create download notification
|
||||||
|
with(notificationBuilder) {
|
||||||
|
setContentTitle(context.getString(R.string.update_check_notification_file_download))
|
||||||
|
setContentText(context.getString(R.string.update_check_notification_download_in_progress))
|
||||||
|
setSmallIcon(android.R.drawable.stat_sys_download)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun doInBackground(vararg params: String?): DownloadResult {
|
||||||
|
// Initialize information array containing path and url to file.
|
||||||
|
val result = DownloadResult(params[0]!!, false)
|
||||||
|
|
||||||
|
// Progress of the download
|
||||||
|
var savedProgress = 0
|
||||||
|
|
||||||
|
val progressListener = object : ProgressListener {
|
||||||
|
override fun update(bytesRead: Long, contentLength: Long, done: Boolean) {
|
||||||
|
val progress = (100 * bytesRead / contentLength).toInt()
|
||||||
|
if (progress > savedProgress) {
|
||||||
|
savedProgress = progress
|
||||||
|
publishProgress(progress)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Make the request and download the file
|
||||||
|
val response = network.requestBodyProgressBlocking(get(result.url), progressListener)
|
||||||
|
|
||||||
|
if (response.isSuccessful) {
|
||||||
|
response.body().source().saveTo(apkFile)
|
||||||
|
// Set download successful
|
||||||
|
result.successful = true
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e, e.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when progress is updated
|
||||||
|
* @param values values containing progress
|
||||||
|
*/
|
||||||
|
override fun onProgressUpdate(vararg values: Int?) {
|
||||||
|
// Notify notification manager to update notification
|
||||||
|
values.getOrNull(0)?.let {
|
||||||
|
notificationBuilder.setProgress(100, it, false)
|
||||||
|
// Displays the progress bar on notification
|
||||||
|
context.notificationManager.notify(InstallOnReceived.notificationId, notificationBuilder.build())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when download done
|
||||||
|
* @param result string containing download information
|
||||||
|
*/
|
||||||
|
override fun onPostExecute(result: DownloadResult) {
|
||||||
|
with(notificationBuilder) {
|
||||||
|
if (result.successful) {
|
||||||
|
setContentTitle(context.getString(R.string.app_name))
|
||||||
|
setContentText(context.getString(R.string.update_check_notification_download_complete))
|
||||||
|
addAction(R.drawable.ic_system_update_grey_24dp_img, context.getString(R.string.action_install),
|
||||||
|
getInstallOnReceivedIntent(InstallOnReceived.INSTALL_APK, apkFile.absolutePath))
|
||||||
|
addAction(R.drawable.ic_clear_grey_24dp_img, context.getString(R.string.action_cancel),
|
||||||
|
getInstallOnReceivedIntent(InstallOnReceived.CANCEL_NOTIFICATION))
|
||||||
|
} else {
|
||||||
|
setContentText(context.getString(R.string.update_check_notification_download_error))
|
||||||
|
addAction(R.drawable.ic_refresh_grey_24dp_img, context.getString(R.string.action_retry),
|
||||||
|
getInstallOnReceivedIntent(InstallOnReceived.RETRY_DOWNLOAD, result.url))
|
||||||
|
addAction(R.drawable.ic_clear_grey_24dp_img, context.getString(R.string.action_cancel),
|
||||||
|
getInstallOnReceivedIntent(InstallOnReceived.CANCEL_NOTIFICATION))
|
||||||
|
}
|
||||||
|
setSmallIcon(android.R.drawable.stat_sys_download_done)
|
||||||
|
setProgress(0, 0, false)
|
||||||
|
}
|
||||||
|
val notification = notificationBuilder.build()
|
||||||
|
notification.flags = Notification.FLAG_NO_CLEAR
|
||||||
|
context.notificationManager.notify(InstallOnReceived.notificationId, notification)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns broadcast intent
|
||||||
|
* @param action action name of broadcast intent
|
||||||
|
* @param path path of file | url of file
|
||||||
|
* @return broadcast intent
|
||||||
|
*/
|
||||||
|
fun getInstallOnReceivedIntent(action: String, path: String = ""): PendingIntent {
|
||||||
|
val intent = Intent(context, InstallOnReceived::class.java).apply {
|
||||||
|
this.action = action
|
||||||
|
putExtra(InstallOnReceived.FILE_LOCATION, path)
|
||||||
|
}
|
||||||
|
return PendingIntent.getBroadcast(context, 0, intent, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BroadcastEvent used to install apk or retry download
|
||||||
|
*/
|
||||||
|
class InstallOnReceived : BroadcastReceiver() {
|
||||||
|
companion object {
|
||||||
|
// Install apk action
|
||||||
|
val INSTALL_APK = "eu.kanade.INSTALL_APK"
|
||||||
|
|
||||||
|
// Retry download action
|
||||||
|
val RETRY_DOWNLOAD = "eu.kanade.RETRY_DOWNLOAD"
|
||||||
|
|
||||||
|
// Retry download action
|
||||||
|
val CANCEL_NOTIFICATION = "eu.kanade.CANCEL_NOTIFICATION"
|
||||||
|
|
||||||
|
// Absolute path of file || URL of file
|
||||||
|
val FILE_LOCATION = "file_location"
|
||||||
|
|
||||||
|
// Id of the notification
|
||||||
|
val notificationId = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
|
when (intent.action) {
|
||||||
|
// Install apk.
|
||||||
|
INSTALL_APK -> UpdateDownloader.installAPK(context, File(intent.getStringExtra(FILE_LOCATION)))
|
||||||
|
// Retry download.
|
||||||
|
RETRY_DOWNLOAD -> UpdateDownloader(context).execute(intent.getStringExtra(FILE_LOCATION))
|
||||||
|
|
||||||
|
CANCEL_NOTIFICATION -> context.notificationManager.cancel(notificationId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,109 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.updater
|
||||||
|
|
||||||
|
import android.app.AlarmManager
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.SystemClock
|
||||||
|
import eu.kanade.tachiyomi.BuildConfig
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
|
import eu.kanade.tachiyomi.util.DeviceUtil
|
||||||
|
import eu.kanade.tachiyomi.util.alarmManager
|
||||||
|
import eu.kanade.tachiyomi.util.notification
|
||||||
|
import eu.kanade.tachiyomi.util.notificationManager
|
||||||
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
|
import rx.schedulers.Schedulers
|
||||||
|
|
||||||
|
class UpdateDownloaderAlarm : BroadcastReceiver() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val CHECK_UPDATE_ACTION = "eu.kanade.CHECK_UPDATE"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the alarm to run the intent that checks for update
|
||||||
|
* @param context the application context.
|
||||||
|
* @param intervalInHours the time in hours when it will be executed.
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
@JvmOverloads
|
||||||
|
fun startAlarm(context: Context, intervalInHours: Int = 12, isEnabled: Boolean = PreferencesHelper.getAutomaticUpdateStatus(context)) {
|
||||||
|
// Stop previous running alarms if needed, and do not restart it if the interval is 0.
|
||||||
|
UpdateDownloaderAlarm.stopAlarm(context)
|
||||||
|
if (intervalInHours == 0 || !isEnabled)
|
||||||
|
return
|
||||||
|
|
||||||
|
// Get the time the alarm should fire the event to update.
|
||||||
|
val intervalInMillis = intervalInHours * 60 * 60 * 1000
|
||||||
|
val nextRun = SystemClock.elapsedRealtime() + intervalInMillis
|
||||||
|
|
||||||
|
// Start the alarm.
|
||||||
|
val pendingIntent = getPendingIntent(context)
|
||||||
|
context.alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
|
||||||
|
nextRun, intervalInMillis.toLong(), pendingIntent)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops the alarm if it's running.
|
||||||
|
* @param context the application context.
|
||||||
|
*/
|
||||||
|
fun stopAlarm(context: Context) {
|
||||||
|
val pendingIntent = getPendingIntent(context)
|
||||||
|
context.alarmManager.cancel(pendingIntent)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns broadcast intent
|
||||||
|
* @param context the application context.
|
||||||
|
* @return broadcast intent
|
||||||
|
*/
|
||||||
|
fun getPendingIntent(context: Context): PendingIntent {
|
||||||
|
return PendingIntent.getBroadcast(context, 0,
|
||||||
|
Intent(context, UpdateDownloaderAlarm::class.java).apply {
|
||||||
|
this.action = CHECK_UPDATE_ACTION
|
||||||
|
}, PendingIntent.FLAG_UPDATE_CURRENT)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
|
when (intent.action) {
|
||||||
|
// Start the alarm when the system is booted.
|
||||||
|
Intent.ACTION_BOOT_COMPLETED -> startAlarm(context)
|
||||||
|
// Update the library when the alarm fires an event.
|
||||||
|
CHECK_UPDATE_ACTION -> checkVersion(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun checkVersion(context: Context) {
|
||||||
|
if (DeviceUtil.isNetworkConnected(context)) {
|
||||||
|
val updateChecker = GithubUpdateChecker(context)
|
||||||
|
updateChecker.checkForApplicationUpdate()
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe({ release ->
|
||||||
|
//Get version of latest release
|
||||||
|
var newVersion = release.version
|
||||||
|
newVersion = newVersion.replace("[^\\d.]".toRegex(), "")
|
||||||
|
|
||||||
|
//Check if latest version is different from current version
|
||||||
|
if (newVersion != BuildConfig.VERSION_NAME) {
|
||||||
|
val downloadLink = release.downloadLink
|
||||||
|
|
||||||
|
val n = context.notification() {
|
||||||
|
setContentTitle(context.getString(R.string.update_check_notification_update_available))
|
||||||
|
addAction(android.R.drawable.stat_sys_download_done, context.getString(eu.kanade.tachiyomi.R.string.action_download),
|
||||||
|
UpdateDownloader(context).getInstallOnReceivedIntent(UpdateDownloader.InstallOnReceived.RETRY_DOWNLOAD, downloadLink))
|
||||||
|
setSmallIcon(android.R.drawable.stat_sys_download_done)
|
||||||
|
}
|
||||||
|
// Displays the progress bar on notification
|
||||||
|
context.notificationManager.notify(0, n);
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
it.printStackTrace()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -5,7 +5,7 @@ import eu.kanade.tachiyomi.data.database.models.Manga
|
|||||||
|
|
||||||
class LibraryMangaEvent(val mangas: Map<Int, List<Manga>>) {
|
class LibraryMangaEvent(val mangas: Map<Int, List<Manga>>) {
|
||||||
|
|
||||||
fun getMangasForCategory(category: Category): List<Manga>? {
|
fun getMangaForCategory(category: Category): List<Manga>? {
|
||||||
return mangas[category.id]
|
return mangas[category.id]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -57,7 +57,7 @@ class BackupFragment : BaseRxFragment<BackupPresenter>() {
|
|||||||
restore_button.setOnClickListener {
|
restore_button.setOnClickListener {
|
||||||
val intent = Intent(Intent.ACTION_GET_CONTENT)
|
val intent = Intent(Intent.ACTION_GET_CONTENT)
|
||||||
intent.addCategory(Intent.CATEGORY_OPENABLE)
|
intent.addCategory(Intent.CATEGORY_OPENABLE)
|
||||||
intent.type = "application/octet-stream"
|
intent.type = "application/*"
|
||||||
val chooser = Intent.createChooser(intent, getString(R.string.file_select_backup))
|
val chooser = Intent.createChooser(intent, getString(R.string.file_select_backup))
|
||||||
startActivityForResult(chooser, REQUEST_BACKUP_OPEN)
|
startActivityForResult(chooser, REQUEST_BACKUP_OPEN)
|
||||||
}
|
}
|
||||||
@ -76,7 +76,7 @@ class BackupFragment : BaseRxFragment<BackupPresenter>() {
|
|||||||
fun onBackupCompleted(file: File) {
|
fun onBackupCompleted(file: File) {
|
||||||
dismissBackupDialog()
|
dismissBackupDialog()
|
||||||
val intent = Intent(Intent.ACTION_SEND)
|
val intent = Intent(Intent.ACTION_SEND)
|
||||||
intent.type = "text/plain"
|
intent.type = "application/json"
|
||||||
intent.putExtra(Intent.EXTRA_STREAM, Uri.parse("file://" + file))
|
intent.putExtra(Intent.EXTRA_STREAM, Uri.parse("file://" + file))
|
||||||
startActivity(Intent.createChooser(intent, ""))
|
startActivity(Intent.createChooser(intent, ""))
|
||||||
}
|
}
|
||||||
|
@ -73,5 +73,4 @@ open class BaseActivity : AppCompatActivity() {
|
|||||||
snack.f()
|
snack.f()
|
||||||
snack.show()
|
snack.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -11,7 +11,6 @@ import nucleus.presenter.Presenter;
|
|||||||
import nucleus.view.PresenterLifecycleDelegate;
|
import nucleus.view.PresenterLifecycleDelegate;
|
||||||
import nucleus.view.ViewWithPresenter;
|
import nucleus.view.ViewWithPresenter;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class is an example of how an activity could controls it's presenter.
|
* This class is an example of how an activity could controls it's presenter.
|
||||||
* You can inherit from this class or copy/paste this class's code to
|
* You can inherit from this class or copy/paste this class's code to
|
||||||
@ -87,8 +86,9 @@ public abstract class BaseRxActivity<P extends Presenter> extends BaseActivity i
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPause() {
|
protected void onDestroy() {
|
||||||
super.onPause();
|
super.onDestroy();
|
||||||
presenterDelegate.onPause(isFinishing());
|
presenterDelegate.onDropView();
|
||||||
|
presenterDelegate.onDestroy(!isChangingConfigurations());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package eu.kanade.tachiyomi.ui.base.fragment;
|
package eu.kanade.tachiyomi.ui.base.fragment;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v4.app.Fragment;
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.App;
|
import eu.kanade.tachiyomi.App;
|
||||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter;
|
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter;
|
||||||
@ -85,13 +84,14 @@ public abstract class BaseRxFragment<P extends Presenter> extends BaseFragment i
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPause() {
|
public void onDestroyView() {
|
||||||
super.onPause();
|
super.onDestroyView();
|
||||||
presenterDelegate.onPause(getActivity().isFinishing() || isRemoving(this));
|
presenterDelegate.onDropView();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isRemoving(Fragment fragment) {
|
@Override
|
||||||
Fragment parent = fragment.getParentFragment();
|
public void onDestroy() {
|
||||||
return fragment.isRemoving() || (parent != null && isRemoving(parent));
|
super.onDestroy();
|
||||||
|
presenterDelegate.onDestroy(!getActivity().isChangingConfigurations());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,14 @@
|
|||||||
package eu.kanade.tachiyomi.ui.catalogue
|
package eu.kanade.tachiyomi.ui.catalogue
|
||||||
|
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
|
import android.widget.RelativeLayout
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.util.inflate
|
import eu.kanade.tachiyomi.util.inflate
|
||||||
|
import kotlinx.android.synthetic.main.fragment_catalogue.*
|
||||||
|
import kotlinx.android.synthetic.main.item_catalogue_grid.view.*
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -68,6 +72,7 @@ class CatalogueAdapter(val fragment: CatalogueFragment) : FlexibleAdapter<Catalo
|
|||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CatalogueHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CatalogueHolder {
|
||||||
if (parent.id == R.id.catalogue_grid) {
|
if (parent.id == R.id.catalogue_grid) {
|
||||||
val v = parent.inflate(R.layout.item_catalogue_grid)
|
val v = parent.inflate(R.layout.item_catalogue_grid)
|
||||||
|
v.image_container.layoutParams = RelativeLayout.LayoutParams(MATCH_PARENT, coverHeight)
|
||||||
return CatalogueGridHolder(v, this, fragment)
|
return CatalogueGridHolder(v, this, fragment)
|
||||||
} else {
|
} else {
|
||||||
val v = parent.inflate(R.layout.item_catalogue_list)
|
val v = parent.inflate(R.layout.item_catalogue_list)
|
||||||
@ -86,4 +91,10 @@ class CatalogueAdapter(val fragment: CatalogueFragment) : FlexibleAdapter<Catalo
|
|||||||
holder.onSetValues(manga)
|
holder.onSetValues(manga)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Property to return the height for the covers based on the width to keep an aspect ratio.
|
||||||
|
*/
|
||||||
|
val coverHeight: Int
|
||||||
|
get() = fragment.catalogue_grid.itemWidth / 3 * 4
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package eu.kanade.tachiyomi.ui.catalogue
|
package eu.kanade.tachiyomi.ui.catalogue
|
||||||
|
|
||||||
|
import android.content.res.Configuration
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.support.v7.widget.GridLayoutManager
|
import android.support.v7.widget.GridLayoutManager
|
||||||
import android.support.v7.widget.SearchView
|
import android.support.v7.widget.SearchView
|
||||||
@ -11,6 +12,7 @@ import android.widget.ArrayAdapter
|
|||||||
import android.widget.ProgressBar
|
import android.widget.ProgressBar
|
||||||
import android.widget.Spinner
|
import android.widget.Spinner
|
||||||
import com.afollestad.materialdialogs.MaterialDialog
|
import com.afollestad.materialdialogs.MaterialDialog
|
||||||
|
import com.f2prateek.rx.preferences.Preference
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder
|
import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder
|
||||||
@ -31,7 +33,7 @@ import rx.Subscription
|
|||||||
import rx.android.schedulers.AndroidSchedulers
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
import rx.subjects.PublishSubject
|
import rx.subjects.PublishSubject
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit.MILLISECONDS
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fragment that shows the manga from the catalogue.
|
* Fragment that shows the manga from the catalogue.
|
||||||
@ -63,7 +65,8 @@ class CatalogueFragment : BaseRxFragment<CataloguePresenter>(), FlexibleViewHold
|
|||||||
/**
|
/**
|
||||||
* Query of the search box.
|
* Query of the search box.
|
||||||
*/
|
*/
|
||||||
private var query = ""
|
private val query: String?
|
||||||
|
get() = presenter.query
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Selected index of the spinner (selected source).
|
* Selected index of the spinner (selected source).
|
||||||
@ -85,6 +88,11 @@ class CatalogueFragment : BaseRxFragment<CataloguePresenter>(), FlexibleViewHold
|
|||||||
*/
|
*/
|
||||||
private var queryDebouncerSubscription: Subscription? = null
|
private var queryDebouncerSubscription: Subscription? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscription of the number of manga per row.
|
||||||
|
*/
|
||||||
|
private var numColumnsSubscription: Subscription? = null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display mode of the catalogue (list or grid mode).
|
* Display mode of the catalogue (list or grid mode).
|
||||||
*/
|
*/
|
||||||
@ -102,23 +110,11 @@ class CatalogueFragment : BaseRxFragment<CataloguePresenter>(), FlexibleViewHold
|
|||||||
get() = (activity as MainActivity).toolbar
|
get() = (activity as MainActivity).toolbar
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
/**
|
|
||||||
* Key to save and restore [query] from a [Bundle].
|
|
||||||
*/
|
|
||||||
const val QUERY_KEY = "query_key"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Key to save and restore [selectedIndex] from a [Bundle].
|
|
||||||
*/
|
|
||||||
const val SELECTED_INDEX_KEY = "selected_index_key"
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new instance of this fragment.
|
* Creates a new instance of this fragment.
|
||||||
*
|
*
|
||||||
* @return a new instance of [CatalogueFragment].
|
* @return a new instance of [CatalogueFragment].
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
|
||||||
fun newInstance(): CatalogueFragment {
|
fun newInstance(): CatalogueFragment {
|
||||||
return CatalogueFragment()
|
return CatalogueFragment()
|
||||||
}
|
}
|
||||||
@ -127,13 +123,6 @@ class CatalogueFragment : BaseRxFragment<CataloguePresenter>(), FlexibleViewHold
|
|||||||
override fun onCreate(savedState: Bundle?) {
|
override fun onCreate(savedState: Bundle?) {
|
||||||
super.onCreate(savedState)
|
super.onCreate(savedState)
|
||||||
setHasOptionsMenu(true)
|
setHasOptionsMenu(true)
|
||||||
|
|
||||||
if (savedState != null) {
|
|
||||||
selectedIndex = savedState.getInt(SELECTED_INDEX_KEY)
|
|
||||||
query = savedState.getString(QUERY_KEY)
|
|
||||||
} else {
|
|
||||||
selectedIndex = presenter.getLastUsedSourceIndex()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedState: Bundle?): View? {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedState: Bundle?): View? {
|
||||||
@ -162,6 +151,12 @@ class CatalogueFragment : BaseRxFragment<CataloguePresenter>(), FlexibleViewHold
|
|||||||
switcher.showNext()
|
switcher.showNext()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
numColumnsSubscription = getColumnsPreferenceForCurrentOrientation().asObservable()
|
||||||
|
.doOnNext { catalogue_grid.spanCount = it }
|
||||||
|
.skip(1)
|
||||||
|
// Set again the adapter to recalculate the covers height
|
||||||
|
.subscribe { catalogue_grid.adapter = adapter }
|
||||||
|
|
||||||
switcher.inAnimation = AnimationUtils.loadAnimation(activity, android.R.anim.fade_in)
|
switcher.inAnimation = AnimationUtils.loadAnimation(activity, android.R.anim.fade_in)
|
||||||
switcher.outAnimation = AnimationUtils.loadAnimation(activity, android.R.anim.fade_out)
|
switcher.outAnimation = AnimationUtils.loadAnimation(activity, android.R.anim.fade_out)
|
||||||
|
|
||||||
@ -175,19 +170,15 @@ class CatalogueFragment : BaseRxFragment<CataloguePresenter>(), FlexibleViewHold
|
|||||||
val onItemSelected = object : AdapterView.OnItemSelectedListener {
|
val onItemSelected = object : AdapterView.OnItemSelectedListener {
|
||||||
override fun onItemSelected(parent: AdapterView<*>, view: View?, position: Int, id: Long) {
|
override fun onItemSelected(parent: AdapterView<*>, view: View?, position: Int, id: Long) {
|
||||||
val source = spinnerAdapter.getItem(position)
|
val source = spinnerAdapter.getItem(position)
|
||||||
if (selectedIndex != position || adapter.isEmpty) {
|
|
||||||
// Set previous selection if it's not a valid source and notify the user
|
|
||||||
if (!presenter.isValidSource(source)) {
|
if (!presenter.isValidSource(source)) {
|
||||||
spinner.setSelection(presenter.findFirstValidSource())
|
spinner.setSelection(selectedIndex)
|
||||||
context.toast(R.string.source_requires_login)
|
context.toast(R.string.source_requires_login)
|
||||||
} else {
|
} else if (source != presenter.source) {
|
||||||
selectedIndex = position
|
selectedIndex = position
|
||||||
presenter.setEnabledSource(selectedIndex)
|
|
||||||
showProgressBar()
|
showProgressBar()
|
||||||
glm.scrollToPositionWithOffset(0, 0)
|
glm.scrollToPositionWithOffset(0, 0)
|
||||||
llm.scrollToPositionWithOffset(0, 0)
|
llm.scrollToPositionWithOffset(0, 0)
|
||||||
presenter.startRequesting(source)
|
presenter.setActiveSource(source)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -197,18 +188,15 @@ class CatalogueFragment : BaseRxFragment<CataloguePresenter>(), FlexibleViewHold
|
|||||||
|
|
||||||
spinner = Spinner(themedContext).apply {
|
spinner = Spinner(themedContext).apply {
|
||||||
adapter = spinnerAdapter
|
adapter = spinnerAdapter
|
||||||
|
selectedIndex = presenter.sources.indexOf(presenter.source)
|
||||||
setSelection(selectedIndex)
|
setSelection(selectedIndex)
|
||||||
onItemSelectedListener = onItemSelected
|
onItemSelectedListener = onItemSelected
|
||||||
}
|
}
|
||||||
|
|
||||||
setToolbarTitle("")
|
setToolbarTitle("")
|
||||||
toolbar.addView(spinner)
|
toolbar.addView(spinner)
|
||||||
}
|
|
||||||
|
|
||||||
override fun onSaveInstanceState(outState: Bundle) {
|
showProgressBar()
|
||||||
outState.putInt(SELECTED_INDEX_KEY, selectedIndex)
|
|
||||||
outState.putString(QUERY_KEY, query)
|
|
||||||
super.onSaveInstanceState(outState)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||||
@ -255,17 +243,20 @@ class CatalogueFragment : BaseRxFragment<CataloguePresenter>(), FlexibleViewHold
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStart() {
|
override fun onResume() {
|
||||||
super.onStart()
|
super.onResume()
|
||||||
initializeSearchSubscription()
|
queryDebouncerSubscription = queryDebouncerSubject.debounce(SEARCH_TIMEOUT, MILLISECONDS)
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe { searchWithQuery(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStop() {
|
override fun onPause() {
|
||||||
destroySearchSubscription()
|
queryDebouncerSubscription?.unsubscribe()
|
||||||
super.onStop()
|
super.onPause()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
|
numColumnsSubscription?.unsubscribe()
|
||||||
searchItem?.let {
|
searchItem?.let {
|
||||||
if (it.isActionViewExpanded) it.collapseActionView()
|
if (it.isActionViewExpanded) it.collapseActionView()
|
||||||
}
|
}
|
||||||
@ -274,51 +265,34 @@ class CatalogueFragment : BaseRxFragment<CataloguePresenter>(), FlexibleViewHold
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Listen for query events on the debouncer.
|
* Called when the input text changes or is submitted.
|
||||||
*/
|
|
||||||
private fun initializeSearchSubscription() {
|
|
||||||
queryDebouncerSubscription = queryDebouncerSubject.debounce(SEARCH_TIMEOUT, TimeUnit.MILLISECONDS)
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.subscribe { restartRequest(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unsubscribe from the query debouncer.
|
|
||||||
*/
|
|
||||||
private fun destroySearchSubscription() {
|
|
||||||
queryDebouncerSubscription?.unsubscribe()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when the input text changes or is submitted
|
|
||||||
*
|
*
|
||||||
* @param query the new query.
|
* @param query the new query.
|
||||||
* @param now whether to send the network call now or debounce it by [SEARCH_TIMEOUT].
|
* @param now whether to send the network call now or debounce it by [SEARCH_TIMEOUT].
|
||||||
*/
|
*/
|
||||||
private fun onSearchEvent(query: String, now: Boolean) {
|
private fun onSearchEvent(query: String, now: Boolean) {
|
||||||
if (now) {
|
if (now) {
|
||||||
restartRequest(query)
|
searchWithQuery(query)
|
||||||
} else {
|
} else {
|
||||||
queryDebouncerSubject.onNext(query)
|
queryDebouncerSubject.onNext(query)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Restarts the request.
|
* Restarts the request with a new query.
|
||||||
*
|
*
|
||||||
* @param newQuery the new query.
|
* @param newQuery the new query.
|
||||||
*/
|
*/
|
||||||
private fun restartRequest(newQuery: String) {
|
private fun searchWithQuery(newQuery: String) {
|
||||||
// If text didn't change, do nothing
|
// If text didn't change, do nothing
|
||||||
if (query == newQuery)
|
if (query == newQuery)
|
||||||
return
|
return
|
||||||
|
|
||||||
query = newQuery
|
|
||||||
showProgressBar()
|
showProgressBar()
|
||||||
catalogue_grid.layoutManager.scrollToPosition(0)
|
catalogue_grid.layoutManager.scrollToPosition(0)
|
||||||
catalogue_list.layoutManager.scrollToPosition(0)
|
catalogue_list.layoutManager.scrollToPosition(0)
|
||||||
|
|
||||||
presenter.restartRequest(query)
|
presenter.restartPager(newQuery)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -359,7 +333,7 @@ class CatalogueFragment : BaseRxFragment<CataloguePresenter>(), FlexibleViewHold
|
|||||||
catalogue_view.snack(error.message ?: "") {
|
catalogue_view.snack(error.message ?: "") {
|
||||||
setAction(R.string.action_retry) {
|
setAction(R.string.action_retry) {
|
||||||
showProgressBar()
|
showProgressBar()
|
||||||
presenter.retryRequest()
|
presenter.retryPage()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -391,6 +365,18 @@ class CatalogueFragment : BaseRxFragment<CataloguePresenter>(), FlexibleViewHold
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a preference for the number of manga per row based on the current orientation.
|
||||||
|
*
|
||||||
|
* @return the preference.
|
||||||
|
*/
|
||||||
|
fun getColumnsPreferenceForCurrentOrientation(): Preference<Int> {
|
||||||
|
return if (resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT)
|
||||||
|
presenter.prefs.portraitColumns()
|
||||||
|
else
|
||||||
|
presenter.prefs.landscapeColumns()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the view holder for the given manga.
|
* Returns the view holder for the given manga.
|
||||||
*
|
*
|
||||||
@ -432,8 +418,7 @@ class CatalogueFragment : BaseRxFragment<CataloguePresenter>(), FlexibleViewHold
|
|||||||
override fun onListItemClick(position: Int): Boolean {
|
override fun onListItemClick(position: Int): Boolean {
|
||||||
val item = adapter.getItem(position) ?: return false
|
val item = adapter.getItem(position) ?: return false
|
||||||
|
|
||||||
val intent = MangaActivity.newIntent(activity, item)
|
val intent = MangaActivity.newIntent(activity, item, true)
|
||||||
intent.putExtra(MangaActivity.FROM_CATALOGUE, true)
|
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -444,16 +429,16 @@ class CatalogueFragment : BaseRxFragment<CataloguePresenter>(), FlexibleViewHold
|
|||||||
* @param position the position of the element clicked.
|
* @param position the position of the element clicked.
|
||||||
*/
|
*/
|
||||||
override fun onListItemLongClick(position: Int) {
|
override fun onListItemLongClick(position: Int) {
|
||||||
val selectedManga = adapter.getItem(position)
|
val manga = adapter.getItem(position) ?: return
|
||||||
|
|
||||||
val textRes = if (selectedManga.favorite) R.string.remove_from_library else R.string.add_to_library
|
val textRes = if (manga.favorite) R.string.remove_from_library else R.string.add_to_library
|
||||||
|
|
||||||
MaterialDialog.Builder(activity)
|
MaterialDialog.Builder(activity)
|
||||||
.items(getString(textRes))
|
.items(getString(textRes))
|
||||||
.itemsCallback { dialog, itemView, which, text ->
|
.itemsCallback { dialog, itemView, which, text ->
|
||||||
when (which) {
|
when (which) {
|
||||||
0 -> {
|
0 -> {
|
||||||
presenter.changeMangaFavorite(selectedManga)
|
presenter.changeMangaFavorite(manga)
|
||||||
adapter.notifyItemChanged(position)
|
adapter.notifyItemChanged(position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
|||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
||||||
|
import eu.kanade.tachiyomi.data.source.EN
|
||||||
import eu.kanade.tachiyomi.data.source.SourceManager
|
import eu.kanade.tachiyomi.data.source.SourceManager
|
||||||
import eu.kanade.tachiyomi.data.source.base.Source
|
import eu.kanade.tachiyomi.data.source.base.Source
|
||||||
import eu.kanade.tachiyomi.data.source.model.MangasPage
|
import eu.kanade.tachiyomi.data.source.model.MangasPage
|
||||||
@ -51,12 +52,13 @@ class CataloguePresenter : BasePresenter<CatalogueFragment>() {
|
|||||||
/**
|
/**
|
||||||
* Query from the view.
|
* Query from the view.
|
||||||
*/
|
*/
|
||||||
private var query: String? = null
|
var query = ""
|
||||||
|
private set
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pager containing a list of manga results.
|
* Pager containing a list of manga results.
|
||||||
*/
|
*/
|
||||||
private lateinit var pager: RxPager<Manga>
|
private var pager = RxPager<Manga>()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Last fetched page from network.
|
* Last fetched page from network.
|
||||||
@ -76,45 +78,36 @@ class CataloguePresenter : BasePresenter<CatalogueFragment>() {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
/**
|
/**
|
||||||
* Id of the restartable that delivers a list of manga from network.
|
* Id of the restartable that delivers a list of manga.
|
||||||
*/
|
*/
|
||||||
const val GET_MANGA_LIST = 1
|
const val PAGER = 1
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Id of the restartable that requests the list of manga from network.
|
* Id of the restartable that requests a page of manga from network.
|
||||||
*/
|
*/
|
||||||
const val GET_MANGA_PAGE = 2
|
const val REQUEST_PAGE = 2
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Id of the restartable that initializes the details of a manga.
|
* Id of the restartable that initializes the details of manga.
|
||||||
*/
|
*/
|
||||||
const val GET_MANGA_DETAIL = 3
|
const val GET_MANGA_DETAILS = 3
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Key to save and restore [source] from a [Bundle].
|
* Key to save and restore [query] from a [Bundle].
|
||||||
*/
|
*/
|
||||||
const val ACTIVE_SOURCE_KEY = "active_source"
|
const val QUERY_KEY = "query_key"
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreate(savedState: Bundle?) {
|
override fun onCreate(savedState: Bundle?) {
|
||||||
super.onCreate(savedState)
|
super.onCreate(savedState)
|
||||||
|
|
||||||
|
source = getLastUsedSource()
|
||||||
|
|
||||||
if (savedState != null) {
|
if (savedState != null) {
|
||||||
source = sourceManager.get(savedState.getInt(ACTIVE_SOURCE_KEY))!!
|
query = savedState.getString(QUERY_KEY, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
pager = RxPager()
|
startableLatestCache(GET_MANGA_DETAILS,
|
||||||
|
|
||||||
startableReplay(GET_MANGA_LIST,
|
|
||||||
{ pager.results() },
|
|
||||||
{ view, pair -> view.onAddPage(pair.first, pair.second) })
|
|
||||||
|
|
||||||
startableFirst(GET_MANGA_PAGE,
|
|
||||||
{ pager.request { page -> getMangasPageObservable(page + 1) } },
|
|
||||||
{ view, next -> },
|
|
||||||
{ view, error -> view.onAddPageError(error) })
|
|
||||||
|
|
||||||
startableLatestCache(GET_MANGA_DETAIL,
|
|
||||||
{ mangaDetailSubject.observeOn(Schedulers.io())
|
{ mangaDetailSubject.observeOn(Schedulers.io())
|
||||||
.flatMap { Observable.from(it) }
|
.flatMap { Observable.from(it) }
|
||||||
.filter { !it.initialized }
|
.filter { !it.initialized }
|
||||||
@ -126,10 +119,22 @@ class CataloguePresenter : BasePresenter<CatalogueFragment>() {
|
|||||||
|
|
||||||
add(prefs.catalogueAsList().asObservable()
|
add(prefs.catalogueAsList().asObservable()
|
||||||
.subscribe { setDisplayMode(it) })
|
.subscribe { setDisplayMode(it) })
|
||||||
|
|
||||||
|
startableReplay(PAGER,
|
||||||
|
{ pager.results() },
|
||||||
|
{ view, pair -> view.onAddPage(pair.first, pair.second) })
|
||||||
|
|
||||||
|
startableFirst(REQUEST_PAGE,
|
||||||
|
{ pager.request { page -> getMangasPageObservable(page + 1) } },
|
||||||
|
{ view, next -> },
|
||||||
|
{ view, error -> view.onAddPageError(error) })
|
||||||
|
|
||||||
|
start(PAGER)
|
||||||
|
start(REQUEST_PAGE)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSave(state: Bundle) {
|
override fun onSave(state: Bundle) {
|
||||||
state.putInt(ACTIVE_SOURCE_KEY, source.id)
|
state.putString(QUERY_KEY, query)
|
||||||
super.onSave(state)
|
super.onSave(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,37 +146,38 @@ class CataloguePresenter : BasePresenter<CatalogueFragment>() {
|
|||||||
private fun setDisplayMode(asList: Boolean) {
|
private fun setDisplayMode(asList: Boolean) {
|
||||||
isListMode = asList
|
isListMode = asList
|
||||||
if (asList) {
|
if (asList) {
|
||||||
stop(GET_MANGA_DETAIL)
|
stop(GET_MANGA_DETAILS)
|
||||||
} else {
|
} else {
|
||||||
start(GET_MANGA_DETAIL)
|
start(GET_MANGA_DETAILS)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts the request with the given source.
|
* Sets the active source and restarts the pager.
|
||||||
*
|
*
|
||||||
* @param source the active source.
|
* @param source the new active source.
|
||||||
*/
|
*/
|
||||||
fun startRequesting(source: Source) {
|
fun setActiveSource(source: Source) {
|
||||||
|
prefs.lastUsedCatalogueSource().set(source.id)
|
||||||
this.source = source
|
this.source = source
|
||||||
restartRequest(null)
|
restartPager()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Restarts the request for the active source with a query.
|
* Restarts the request for the active source.
|
||||||
*
|
*
|
||||||
* @param query a query, or null if searching popular manga.
|
* @param query the query, or null if searching popular manga.
|
||||||
*/
|
*/
|
||||||
fun restartRequest(query: String?) {
|
fun restartPager(query: String = "") {
|
||||||
this.query = query
|
this.query = query
|
||||||
stop(GET_MANGA_PAGE)
|
stop(REQUEST_PAGE)
|
||||||
lastMangasPage = null
|
lastMangasPage = null
|
||||||
|
|
||||||
if (!isListMode) {
|
if (!isListMode) {
|
||||||
start(GET_MANGA_DETAIL)
|
start(GET_MANGA_DETAILS)
|
||||||
}
|
}
|
||||||
start(GET_MANGA_LIST)
|
start(PAGER)
|
||||||
start(GET_MANGA_PAGE)
|
start(REQUEST_PAGE)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -179,15 +185,22 @@ class CataloguePresenter : BasePresenter<CatalogueFragment>() {
|
|||||||
*/
|
*/
|
||||||
fun requestNext() {
|
fun requestNext() {
|
||||||
if (hasNextPage()) {
|
if (hasNextPage()) {
|
||||||
start(GET_MANGA_PAGE)
|
start(REQUEST_PAGE)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retry a failed request.
|
* Returns true if the last fetched page has a next page.
|
||||||
*/
|
*/
|
||||||
fun retryRequest() {
|
fun hasNextPage(): Boolean {
|
||||||
start(GET_MANGA_PAGE)
|
return lastMangasPage?.nextPageUrl != null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retries the current request that failed.
|
||||||
|
*/
|
||||||
|
fun retryPage() {
|
||||||
|
start(REQUEST_PAGE)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -202,12 +215,12 @@ class CataloguePresenter : BasePresenter<CatalogueFragment>() {
|
|||||||
nextMangasPage.url = lastMangasPage!!.nextPageUrl
|
nextMangasPage.url = lastMangasPage!!.nextPageUrl
|
||||||
}
|
}
|
||||||
|
|
||||||
val obs = if (query.isNullOrEmpty())
|
val observable = if (query.isEmpty())
|
||||||
source.pullPopularMangasFromNetwork(nextMangasPage)
|
source.pullPopularMangasFromNetwork(nextMangasPage)
|
||||||
else
|
else
|
||||||
source.searchMangasFromNetwork(nextMangasPage, query!!)
|
source.searchMangasFromNetwork(nextMangasPage, query)
|
||||||
|
|
||||||
return obs.subscribeOn(Schedulers.io())
|
return observable.subscribeOn(Schedulers.io())
|
||||||
.doOnNext { lastMangasPage = it }
|
.doOnNext { lastMangasPage = it }
|
||||||
.flatMap { Observable.from(it.mangas) }
|
.flatMap { Observable.from(it.mangas) }
|
||||||
.map { networkToLocalManga(it) }
|
.map { networkToLocalManga(it) }
|
||||||
@ -259,23 +272,17 @@ class CataloguePresenter : BasePresenter<CatalogueFragment>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the last fetched page has a next page.
|
* Returns the last used source from preferences or the first valid source.
|
||||||
*/
|
|
||||||
fun hasNextPage(): Boolean {
|
|
||||||
return lastMangasPage?.nextPageUrl != null
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the last used source from preferences, or the first valid source.
|
|
||||||
*
|
*
|
||||||
* @return the index of the last used source.
|
* @return a source.
|
||||||
*/
|
*/
|
||||||
fun getLastUsedSourceIndex(): Int {
|
fun getLastUsedSource(): Source {
|
||||||
val index = prefs.lastUsedCatalogueSource().get() ?: -1
|
val id = prefs.lastUsedCatalogueSource().get() ?: -1
|
||||||
if (index < 0 || index >= sources.size || !isValidSource(sources[index])) {
|
val source = sourceManager.get(id)
|
||||||
|
if (!isValidSource(source)) {
|
||||||
return findFirstValidSource()
|
return findFirstValidSource()
|
||||||
}
|
}
|
||||||
return index
|
return source!!
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -284,11 +291,16 @@ class CataloguePresenter : BasePresenter<CatalogueFragment>() {
|
|||||||
* @param source the source to check.
|
* @param source the source to check.
|
||||||
* @return true if the source is valid, false otherwise.
|
* @return true if the source is valid, false otherwise.
|
||||||
*/
|
*/
|
||||||
fun isValidSource(source: Source): Boolean = with(source) {
|
fun isValidSource(source: Source?): Boolean {
|
||||||
if (!isLoginRequired || isLogged)
|
if (source == null) return false
|
||||||
return true
|
|
||||||
|
|
||||||
prefs.getSourceUsername(this) != "" && prefs.getSourcePassword(this) != ""
|
return with(source) {
|
||||||
|
if (!isLoginRequired || isLogged) {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
prefs.sourceUsername(this) != "" && prefs.sourcePassword(this) != ""
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -296,17 +308,8 @@ class CataloguePresenter : BasePresenter<CatalogueFragment>() {
|
|||||||
*
|
*
|
||||||
* @return the index of the first valid source.
|
* @return the index of the first valid source.
|
||||||
*/
|
*/
|
||||||
fun findFirstValidSource(): Int {
|
fun findFirstValidSource(): Source {
|
||||||
return sources.indexOfFirst { isValidSource(it) }
|
return sources.find { isValidSource(it) }!!
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the enabled source.
|
|
||||||
*
|
|
||||||
* @param index the index of the source in [sources].
|
|
||||||
*/
|
|
||||||
fun setEnabledSource(index: Int) {
|
|
||||||
prefs.lastUsedCatalogueSource().set(index)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -317,7 +320,7 @@ class CataloguePresenter : BasePresenter<CatalogueFragment>() {
|
|||||||
|
|
||||||
// Ensure at least one language
|
// Ensure at least one language
|
||||||
if (languages.isEmpty()) {
|
if (languages.isEmpty()) {
|
||||||
languages.add("EN")
|
languages.add(EN.code)
|
||||||
}
|
}
|
||||||
|
|
||||||
return sourceManager.getSources()
|
return sourceManager.getSources()
|
||||||
|
@ -10,7 +10,13 @@ import eu.kanade.tachiyomi.ui.main.MainActivity
|
|||||||
import eu.kanade.tachiyomi.widget.NpaLinearLayoutManager
|
import eu.kanade.tachiyomi.widget.NpaLinearLayoutManager
|
||||||
import kotlinx.android.synthetic.main.fragment_download_queue.*
|
import kotlinx.android.synthetic.main.fragment_download_queue.*
|
||||||
import nucleus.factory.RequiresPresenter
|
import nucleus.factory.RequiresPresenter
|
||||||
|
import rx.Observable
|
||||||
import rx.Subscription
|
import rx.Subscription
|
||||||
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
|
import rx.schedulers.Schedulers
|
||||||
|
import rx.subscriptions.CompositeSubscription
|
||||||
|
import java.util.*
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fragment that shows the currently active downloads.
|
* Fragment that shows the currently active downloads.
|
||||||
@ -40,9 +46,14 @@ class DownloadFragment : BaseRxFragment<DownloadPresenter>() {
|
|||||||
private var clearButton: MenuItem? = null
|
private var clearButton: MenuItem? = null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subscription to know if the download queue is running.
|
* Subscription list to be cleared during [onPause].
|
||||||
*/
|
*/
|
||||||
private var queueStatusSubscription: Subscription? = null
|
private val resumeSubscriptions by lazy { CompositeSubscription() }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map of subscriptions for active downloads.
|
||||||
|
*/
|
||||||
|
private val progressSubscriptions by lazy { HashMap<Download, Subscription>() }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the download queue is running or not.
|
* Whether the download queue is running or not.
|
||||||
@ -66,8 +77,7 @@ class DownloadFragment : BaseRxFragment<DownloadPresenter>() {
|
|||||||
setHasOptionsMenu(true)
|
setHasOptionsMenu(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedState: Bundle?): View {
|
||||||
savedState: Bundle?): View? {
|
|
||||||
return inflater.inflate(R.layout.fragment_download_queue, container, false)
|
return inflater.inflate(R.layout.fragment_download_queue, container, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,15 +133,91 @@ class DownloadFragment : BaseRxFragment<DownloadPresenter>() {
|
|||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
queueStatusSubscription = presenter.downloadManager.runningSubject
|
presenter.downloadManager.runningSubject
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe { onQueueStatusChange(it) }
|
.subscribe { onQueueStatusChange(it) }
|
||||||
|
.apply { resumeSubscriptions.add(this) }
|
||||||
|
|
||||||
|
presenter.getStatusObservable()
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe { onStatusChange(it) }
|
||||||
|
.apply { resumeSubscriptions.add(this) }
|
||||||
|
|
||||||
|
presenter.getProgressObservable()
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe { onUpdateDownloadedPages(it) }
|
||||||
|
.apply { resumeSubscriptions.add(this) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
queueStatusSubscription?.unsubscribe()
|
for (subscription in progressSubscriptions.values) {
|
||||||
|
subscription.unsubscribe()
|
||||||
|
}
|
||||||
|
progressSubscriptions.clear()
|
||||||
|
resumeSubscriptions.clear()
|
||||||
super.onPause()
|
super.onPause()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the status of a download changes.
|
||||||
|
*
|
||||||
|
* @param download the download whose status has changed.
|
||||||
|
*/
|
||||||
|
private fun onStatusChange(download: Download) {
|
||||||
|
when (download.status) {
|
||||||
|
Download.DOWNLOADING -> {
|
||||||
|
observeProgress(download)
|
||||||
|
// Initial update of the downloaded pages
|
||||||
|
onUpdateDownloadedPages(download)
|
||||||
|
}
|
||||||
|
Download.DOWNLOADED -> {
|
||||||
|
unsubscribeProgress(download)
|
||||||
|
onUpdateProgress(download)
|
||||||
|
onUpdateDownloadedPages(download)
|
||||||
|
}
|
||||||
|
Download.ERROR -> unsubscribeProgress(download)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Observe the progress of a download and notify the view.
|
||||||
|
*
|
||||||
|
* @param download the download to observe its progress.
|
||||||
|
*/
|
||||||
|
private fun observeProgress(download: Download) {
|
||||||
|
val subscription = Observable.interval(50, TimeUnit.MILLISECONDS, Schedulers.newThread())
|
||||||
|
// Get the sum of percentages for all the pages.
|
||||||
|
.flatMap {
|
||||||
|
Observable.from(download.pages)
|
||||||
|
.map { it.progress }
|
||||||
|
.reduce { x, y -> x + y }
|
||||||
|
}
|
||||||
|
// Keep only the latest emission to avoid backpressure.
|
||||||
|
.onBackpressureLatest()
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe { progress ->
|
||||||
|
// Update the view only if the progress has changed.
|
||||||
|
if (download.totalProgress != progress) {
|
||||||
|
download.totalProgress = progress
|
||||||
|
onUpdateProgress(download)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Avoid leaking subscriptions
|
||||||
|
progressSubscriptions.remove(download)?.unsubscribe()
|
||||||
|
|
||||||
|
progressSubscriptions.put(download, subscription)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unsubscribes the given download from the progress subscriptions.
|
||||||
|
*
|
||||||
|
* @param download the download to unsubscribe.
|
||||||
|
*/
|
||||||
|
private fun unsubscribeProgress(download: Download) {
|
||||||
|
progressSubscriptions.remove(download)?.unsubscribe()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the queue's status has changed. Updates the visibility of the buttons.
|
* Called when the queue's status has changed. Updates the visibility of the buttons.
|
||||||
*
|
*
|
||||||
@ -157,16 +243,16 @@ class DownloadFragment : BaseRxFragment<DownloadPresenter>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called from the presenter when the status of a download changes.
|
* Called when the progress of a download changes.
|
||||||
*
|
*
|
||||||
* @param download the download whose status has changed.
|
* @param download the download whose progress has changed.
|
||||||
*/
|
*/
|
||||||
fun onUpdateProgress(download: Download) {
|
fun onUpdateProgress(download: Download) {
|
||||||
getHolder(download)?.notifyProgress()
|
getHolder(download)?.notifyProgress()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called from the presenter when a page of a download is downloaded.
|
* Called when a page of a download is downloaded.
|
||||||
*
|
*
|
||||||
* @param download the download whose page has been downloaded.
|
* @param download the download whose page has been downloaded.
|
||||||
*/
|
*/
|
||||||
|
@ -10,7 +10,7 @@ import kotlinx.android.synthetic.main.item_download.view.*
|
|||||||
* All the elements from the layout file "item_download" are available in this class.
|
* All the elements from the layout file "item_download" are available in this class.
|
||||||
*
|
*
|
||||||
* @param view the inflated view for this holder.
|
* @param view the inflated view for this holder.
|
||||||
* @constructor creates a new library holder.
|
* @constructor creates a new download holder.
|
||||||
*/
|
*/
|
||||||
class DownloadHolder(private val view: View) : RecyclerView.ViewHolder(view) {
|
class DownloadHolder(private val view: View) : RecyclerView.ViewHolder(view) {
|
||||||
|
|
||||||
|
@ -6,12 +6,7 @@ import eu.kanade.tachiyomi.data.download.model.Download
|
|||||||
import eu.kanade.tachiyomi.data.download.model.DownloadQueue
|
import eu.kanade.tachiyomi.data.download.model.DownloadQueue
|
||||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.Subscription
|
|
||||||
import rx.android.schedulers.AndroidSchedulers
|
|
||||||
import rx.schedulers.Schedulers
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.*
|
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -19,6 +14,13 @@ import javax.inject.Inject
|
|||||||
*/
|
*/
|
||||||
class DownloadPresenter : BasePresenter<DownloadFragment>() {
|
class DownloadPresenter : BasePresenter<DownloadFragment>() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* Id of the restartable that returns the download queue.
|
||||||
|
*/
|
||||||
|
const val GET_DOWNLOAD_QUEUE = 1
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Download manager.
|
* Download manager.
|
||||||
*/
|
*/
|
||||||
@ -30,28 +32,6 @@ class DownloadPresenter : BasePresenter<DownloadFragment>() {
|
|||||||
val downloadQueue: DownloadQueue
|
val downloadQueue: DownloadQueue
|
||||||
get() = downloadManager.queue
|
get() = downloadManager.queue
|
||||||
|
|
||||||
/**
|
|
||||||
* Map of subscriptions for active downloads.
|
|
||||||
*/
|
|
||||||
private val progressSubscriptions by lazy { HashMap<Download, Subscription>() }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Subscription for status changes on downloads.
|
|
||||||
*/
|
|
||||||
private var statusSubscription: Subscription? = null
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Subscription for downloaded pages for active downloads.
|
|
||||||
*/
|
|
||||||
private var pageProgressSubscription: Subscription? = null
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
/**
|
|
||||||
* Id of the restartable that returns the download queue.
|
|
||||||
*/
|
|
||||||
const val GET_DOWNLOAD_QUEUE = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreate(savedState: Bundle?) {
|
override fun onCreate(savedState: Bundle?) {
|
||||||
super.onCreate(savedState)
|
super.onCreate(savedState)
|
||||||
|
|
||||||
@ -65,102 +45,14 @@ class DownloadPresenter : BasePresenter<DownloadFragment>() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onTakeView(view: DownloadFragment) {
|
fun getStatusObservable(): Observable<Download> {
|
||||||
super.onTakeView(view)
|
return downloadQueue.getStatusObservable()
|
||||||
|
|
||||||
statusSubscription = downloadQueue.getStatusObservable()
|
|
||||||
.startWith(downloadQueue.getActiveDownloads())
|
.startWith(downloadQueue.getActiveDownloads())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
}
|
||||||
.subscribe { processStatus(it, view) }
|
|
||||||
|
|
||||||
add(statusSubscription)
|
fun getProgressObservable(): Observable<Download> {
|
||||||
|
return downloadQueue.getProgressObservable()
|
||||||
pageProgressSubscription = downloadQueue.getProgressObservable()
|
|
||||||
.onBackpressureBuffer()
|
.onBackpressureBuffer()
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.subscribe { view.onUpdateDownloadedPages(it) }
|
|
||||||
|
|
||||||
add(pageProgressSubscription)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDropView() {
|
|
||||||
destroySubscriptions()
|
|
||||||
super.onDropView()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Process the status of a download when its status has changed and notify the view.
|
|
||||||
*
|
|
||||||
* @param download the download whose status has changed.
|
|
||||||
* @param view the view.
|
|
||||||
*/
|
|
||||||
private fun processStatus(download: Download, view: DownloadFragment) {
|
|
||||||
when (download.status) {
|
|
||||||
Download.DOWNLOADING -> {
|
|
||||||
observeProgress(download, view)
|
|
||||||
// Initial update of the downloaded pages
|
|
||||||
view.onUpdateDownloadedPages(download)
|
|
||||||
}
|
|
||||||
Download.DOWNLOADED -> {
|
|
||||||
unsubscribeProgress(download)
|
|
||||||
view.onUpdateProgress(download)
|
|
||||||
view.onUpdateDownloadedPages(download)
|
|
||||||
}
|
|
||||||
Download.ERROR -> unsubscribeProgress(download)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Observe the progress of a download and notify the view.
|
|
||||||
*
|
|
||||||
* @param download the download to observe its progress.
|
|
||||||
* @param view the view.
|
|
||||||
*/
|
|
||||||
private fun observeProgress(download: Download, view: DownloadFragment) {
|
|
||||||
val subscription = Observable.interval(50, TimeUnit.MILLISECONDS, Schedulers.newThread())
|
|
||||||
// Get the sum of percentages for all the pages.
|
|
||||||
.flatMap {
|
|
||||||
Observable.from(download.pages)
|
|
||||||
.map { it.progress }
|
|
||||||
.reduce { x, y -> x + y }
|
|
||||||
}
|
|
||||||
// Keep only the latest emission to avoid backpressure.
|
|
||||||
.onBackpressureLatest()
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.subscribe { progress ->
|
|
||||||
// Update the view only if the progress has changed.
|
|
||||||
if (download.totalProgress != progress) {
|
|
||||||
download.totalProgress = progress
|
|
||||||
view.onUpdateProgress(download)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Avoid leaking subscriptions
|
|
||||||
progressSubscriptions.remove(download)?.unsubscribe()
|
|
||||||
|
|
||||||
progressSubscriptions.put(download, subscription)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unsubscribes the given download from the progress subscriptions.
|
|
||||||
*
|
|
||||||
* @param download the download to unsubscribe.
|
|
||||||
*/
|
|
||||||
private fun unsubscribeProgress(download: Download) {
|
|
||||||
progressSubscriptions.remove(download)?.unsubscribe()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Destroys all the subscriptions of the presenter.
|
|
||||||
*/
|
|
||||||
private fun destroySubscriptions() {
|
|
||||||
for (subscription in progressSubscriptions.values) {
|
|
||||||
subscription.unsubscribe()
|
|
||||||
}
|
|
||||||
progressSubscriptions.clear()
|
|
||||||
|
|
||||||
remove(pageProgressSubscription)
|
|
||||||
remove(statusSubscription)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -15,7 +15,6 @@ import eu.kanade.tachiyomi.ui.base.fragment.BaseFragment
|
|||||||
import eu.kanade.tachiyomi.ui.manga.MangaActivity
|
import eu.kanade.tachiyomi.ui.manga.MangaActivity
|
||||||
import kotlinx.android.synthetic.main.fragment_library_category.*
|
import kotlinx.android.synthetic.main.fragment_library_category.*
|
||||||
import rx.Subscription
|
import rx.Subscription
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fragment containing the library manga for a certain category.
|
* Fragment containing the library manga for a certain category.
|
||||||
@ -34,14 +33,6 @@ class LibraryCategoryFragment : BaseFragment(), FlexibleViewHolder.OnListItemCli
|
|||||||
*/
|
*/
|
||||||
private var position: Int = 0
|
private var position: Int = 0
|
||||||
|
|
||||||
/**
|
|
||||||
* Manga in this category.
|
|
||||||
*/
|
|
||||||
private var mangas: List<Manga>? = null
|
|
||||||
set(value) {
|
|
||||||
field = value ?: ArrayList()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subscription for the library manga.
|
* Subscription for the library manga.
|
||||||
*/
|
*/
|
||||||
@ -119,7 +110,7 @@ class LibraryCategoryFragment : BaseFragment(), FlexibleViewHolder.OnListItemCli
|
|||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
libraryMangaSubscription = libraryPresenter.libraryMangaSubject
|
libraryMangaSubscription = libraryPresenter.libraryMangaSubject
|
||||||
.subscribe { if (it != null) onNextLibraryManga(it) }
|
.subscribe { onNextLibraryManga(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
@ -134,8 +125,8 @@ class LibraryCategoryFragment : BaseFragment(), FlexibleViewHolder.OnListItemCli
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subscribe to [LibraryMangaEvent]. When an event is received, it updates [mangas] if needed
|
* Subscribe to [LibraryMangaEvent]. When an event is received, it updates the content of the
|
||||||
* and refresh the content of the adapter.
|
* adapter.
|
||||||
*
|
*
|
||||||
* @param event the event received.
|
* @param event the event received.
|
||||||
*/
|
*/
|
||||||
@ -146,15 +137,11 @@ class LibraryCategoryFragment : BaseFragment(), FlexibleViewHolder.OnListItemCli
|
|||||||
// When a category is deleted, the index can be greater than the number of categories.
|
// When a category is deleted, the index can be greater than the number of categories.
|
||||||
if (position >= categories.size) return
|
if (position >= categories.size) return
|
||||||
|
|
||||||
// Get the manga list for this category
|
// Get the manga list for this category.
|
||||||
val mangaForCategory = event.getMangasForCategory(categories[position])
|
val mangaForCategory = event.getMangaForCategory(categories[position]) ?: emptyList()
|
||||||
|
|
||||||
// Update the list only if the reference to the list is different, avoiding reseting the
|
// Update the category with its manga.
|
||||||
// adapter after every onResume.
|
adapter.setItems(mangaForCategory)
|
||||||
if (mangas !== mangaForCategory) {
|
|
||||||
mangas = mangaForCategory
|
|
||||||
adapter.setItems(mangas ?: emptyList())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -125,6 +125,11 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
presenter.subscribeLibrary()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
tabs.visibility = View.GONE
|
tabs.visibility = View.GONE
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
@ -385,10 +390,15 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
|
|||||||
* @param mangas the manga list to move.
|
* @param mangas the manga list to move.
|
||||||
*/
|
*/
|
||||||
private fun moveMangasToCategories(mangas: List<Manga>) {
|
private fun moveMangasToCategories(mangas: List<Manga>) {
|
||||||
|
val categories = presenter.categories
|
||||||
|
val commonCategoriesIndexes = presenter.getCommonCategories(mangas)
|
||||||
|
.map { categories.indexOf(it) }
|
||||||
|
.toTypedArray()
|
||||||
|
|
||||||
MaterialDialog.Builder(activity)
|
MaterialDialog.Builder(activity)
|
||||||
.title(R.string.action_move_category)
|
.title(R.string.action_move_category)
|
||||||
.items(presenter.getCategoryNames())
|
.items(categories.map { it.name })
|
||||||
.itemsCallbackMultiChoice(null) { dialog, positions, text ->
|
.itemsCallbackMultiChoice(commonCategoriesIndexes) { dialog, positions, text ->
|
||||||
presenter.moveMangasToCategories(positions, mangas)
|
presenter.moveMangasToCategories(positions, mangas)
|
||||||
destroyActionModeIfNeeded()
|
destroyActionModeIfNeeded()
|
||||||
true
|
true
|
||||||
|
@ -44,7 +44,7 @@ class LibraryPresenter : BasePresenter<LibraryFragment>() {
|
|||||||
/**
|
/**
|
||||||
* Subject to notify the library's viewpager for updates.
|
* Subject to notify the library's viewpager for updates.
|
||||||
*/
|
*/
|
||||||
val libraryMangaSubject = BehaviorSubject.create<LibraryMangaEvent?>()
|
val libraryMangaSubject = BehaviorSubject.create<LibraryMangaEvent>()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Database.
|
* Database.
|
||||||
@ -91,18 +91,6 @@ class LibraryPresenter : BasePresenter<LibraryFragment>() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDropView() {
|
|
||||||
libraryMangaSubject.onNext(null)
|
|
||||||
super.onDropView()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onTakeView(libraryFragment: LibraryFragment) {
|
|
||||||
super.onTakeView(libraryFragment)
|
|
||||||
if (isUnsubscribed(GET_LIBRARY)) {
|
|
||||||
start(GET_LIBRARY)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the categories and all its manga from the database.
|
* Get the categories and all its manga from the database.
|
||||||
*
|
*
|
||||||
@ -114,13 +102,6 @@ class LibraryPresenter : BasePresenter<LibraryFragment>() {
|
|||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the library information
|
|
||||||
*/
|
|
||||||
fun updateLibrary() {
|
|
||||||
start(GET_LIBRARY)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the categories from the database.
|
* Get the categories from the database.
|
||||||
*
|
*
|
||||||
@ -151,6 +132,22 @@ class LibraryPresenter : BasePresenter<LibraryFragment>() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resubscribes to library if needed.
|
||||||
|
*/
|
||||||
|
fun subscribeLibrary() {
|
||||||
|
if (isUnsubscribed(GET_LIBRARY)) {
|
||||||
|
start(GET_LIBRARY)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resubscribes to library.
|
||||||
|
*/
|
||||||
|
fun updateLibrary() {
|
||||||
|
start(GET_LIBRARY)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filter library by preference
|
* Filter library by preference
|
||||||
*
|
*
|
||||||
@ -220,11 +217,13 @@ class LibraryPresenter : BasePresenter<LibraryFragment>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the category names as a list.
|
* Returns the common categories for the given list of manga.
|
||||||
|
*
|
||||||
|
* @param mangas the list of manga.
|
||||||
*/
|
*/
|
||||||
fun getCategoryNames(): List<String> {
|
fun getCommonCategories(mangas: List<Manga>) = mangas.toSet()
|
||||||
return categories.map { it.name }
|
.map { db.getCategoriesForManga(it).executeAsBlocking() }
|
||||||
}
|
.reduce { set1: Iterable<Category>, set2 -> set1.intersect(set2) }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove the selected manga from the library.
|
* Remove the selected manga from the library.
|
||||||
|
@ -39,7 +39,7 @@ class MainActivity : BaseActivity() {
|
|||||||
supportActionBar?.setHomeAsUpIndicator(R.drawable.ic_menu_white_24dp)
|
supportActionBar?.setHomeAsUpIndicator(R.drawable.ic_menu_white_24dp)
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= 21) {
|
if (Build.VERSION.SDK_INT >= 21) {
|
||||||
window.statusBarColor = android.R.color.transparent;
|
window.statusBarColor = android.R.color.transparent
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set behavior of Navigation drawer
|
// Set behavior of Navigation drawer
|
||||||
@ -73,6 +73,16 @@ class MainActivity : BaseActivity() {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onBackPressed() {
|
||||||
|
supportFragmentManager.findFragmentById(R.id.frame_container)?.let {
|
||||||
|
if (it !is LibraryFragment) {
|
||||||
|
setFragment(LibraryFragment.newInstance())
|
||||||
|
} else {
|
||||||
|
super.onBackPressed()
|
||||||
|
}
|
||||||
|
} ?: super.onBackPressed()
|
||||||
|
}
|
||||||
|
|
||||||
fun setFragment(fragment: Fragment) {
|
fun setFragment(fragment: Fragment) {
|
||||||
supportFragmentManager.beginTransaction()
|
supportFragmentManager.beginTransaction()
|
||||||
.replace(R.id.frame_container, fragment)
|
.replace(R.id.frame_container, fragment)
|
||||||
|
@ -23,21 +23,24 @@ class MangaActivity : BaseRxActivity<MangaPresenter>() {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
val FROM_CATALOGUE = "from_catalogue"
|
const val FROM_CATALOGUE_EXTRA = "from_catalogue"
|
||||||
val INFO_FRAGMENT = 0
|
const val MANGA_EXTRA = "manga"
|
||||||
val CHAPTERS_FRAGMENT = 1
|
const val INFO_FRAGMENT = 0
|
||||||
val MYANIMELIST_FRAGMENT = 2
|
const val CHAPTERS_FRAGMENT = 1
|
||||||
|
const val MYANIMELIST_FRAGMENT = 2
|
||||||
|
|
||||||
fun newIntent(context: Context, manga: Manga): Intent {
|
fun newIntent(context: Context, manga: Manga, fromCatalogue: Boolean = false): Intent {
|
||||||
val intent = Intent(context, MangaActivity::class.java)
|
|
||||||
SharedData.put(MangaEvent(manga))
|
SharedData.put(MangaEvent(manga))
|
||||||
return intent
|
return Intent(context, MangaActivity::class.java).apply {
|
||||||
|
putExtra(FROM_CATALOGUE_EXTRA, fromCatalogue)
|
||||||
|
putExtra(MANGA_EXTRA, manga.id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private lateinit var adapter: MangaDetailAdapter
|
private lateinit var adapter: MangaDetailAdapter
|
||||||
|
|
||||||
var isCatalogueManga: Boolean = false
|
var fromCatalogue: Boolean = false
|
||||||
private set
|
private set
|
||||||
|
|
||||||
override fun onCreate(savedState: Bundle?) {
|
override fun onCreate(savedState: Bundle?) {
|
||||||
@ -45,16 +48,21 @@ class MangaActivity : BaseRxActivity<MangaPresenter>() {
|
|||||||
super.onCreate(savedState)
|
super.onCreate(savedState)
|
||||||
setContentView(R.layout.activity_manga)
|
setContentView(R.layout.activity_manga)
|
||||||
|
|
||||||
|
presenter.setMangaEvent(SharedData.getOrPut(MangaEvent::class.java) {
|
||||||
|
val id = intent.getLongExtra(MANGA_EXTRA, 0)
|
||||||
|
MangaEvent(presenter.db.getManga(id).executeAsBlocking()!!)
|
||||||
|
})
|
||||||
|
|
||||||
setupToolbar(toolbar)
|
setupToolbar(toolbar)
|
||||||
|
|
||||||
isCatalogueManga = intent.getBooleanExtra(FROM_CATALOGUE, false)
|
fromCatalogue = intent.getBooleanExtra(FROM_CATALOGUE_EXTRA, false)
|
||||||
|
|
||||||
adapter = MangaDetailAdapter(supportFragmentManager, this)
|
adapter = MangaDetailAdapter(supportFragmentManager, this)
|
||||||
view_pager.adapter = adapter
|
view_pager.adapter = adapter
|
||||||
|
|
||||||
tabs.setupWithViewPager(view_pager)
|
tabs.setupWithViewPager(view_pager)
|
||||||
|
|
||||||
if (!isCatalogueManga)
|
if (!fromCatalogue)
|
||||||
view_pager.currentItem = CHAPTERS_FRAGMENT
|
view_pager.currentItem = CHAPTERS_FRAGMENT
|
||||||
|
|
||||||
requestPermissionsOnMarshmallow()
|
requestPermissionsOnMarshmallow()
|
||||||
@ -72,7 +80,7 @@ class MangaActivity : BaseRxActivity<MangaPresenter>() {
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
pageCount = 2
|
pageCount = 2
|
||||||
if (!activity.isCatalogueManga && activity.presenter.syncManager.myAnimeList.isLogged)
|
if (!activity.fromCatalogue && activity.presenter.syncManager.myAnimeList.isLogged)
|
||||||
pageCount++
|
pageCount++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ import eu.kanade.tachiyomi.event.MangaEvent
|
|||||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
||||||
import eu.kanade.tachiyomi.util.SharedData
|
import eu.kanade.tachiyomi.util.SharedData
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
|
import rx.Subscription
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -31,37 +32,21 @@ class MangaPresenter : BasePresenter<MangaActivity>() {
|
|||||||
*/
|
*/
|
||||||
lateinit var manga: Manga
|
lateinit var manga: Manga
|
||||||
|
|
||||||
/**
|
var mangaSubscription: Subscription? = null
|
||||||
* Key to save and restore [manga] from a bundle.
|
|
||||||
*/
|
|
||||||
private val MANGA_KEY = "manga_key"
|
|
||||||
|
|
||||||
override fun onCreate(savedState: Bundle?) {
|
override fun onCreate(savedState: Bundle?) {
|
||||||
super.onCreate(savedState)
|
super.onCreate(savedState)
|
||||||
|
|
||||||
if (savedState == null) {
|
|
||||||
manga = SharedData.get(MangaEvent::class.java)!!.manga
|
|
||||||
} else {
|
|
||||||
manga = savedState.getSerializable(MANGA_KEY) as Manga
|
|
||||||
SharedData.put(MangaEvent(manga))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare a subject to communicate the chapters and info presenters for the chapter count.
|
// Prepare a subject to communicate the chapters and info presenters for the chapter count.
|
||||||
SharedData.put(ChapterCountEvent())
|
SharedData.put(ChapterCountEvent())
|
||||||
|
}
|
||||||
|
|
||||||
Observable.just(manga)
|
fun setMangaEvent(event: MangaEvent) {
|
||||||
|
if (isUnsubscribed(mangaSubscription)) {
|
||||||
|
manga = event.manga
|
||||||
|
mangaSubscription = Observable.just(manga)
|
||||||
.subscribeLatestCache({ view, manga -> view.onSetManga(manga) })
|
.subscribeLatestCache({ view, manga -> view.onSetManga(manga) })
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
|
||||||
SharedData.remove(MangaEvent::class.java)
|
|
||||||
SharedData.remove(ChapterCountEvent::class.java)
|
|
||||||
super.onDestroy()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onSave(state: Bundle) {
|
|
||||||
state.putSerializable(MANGA_KEY, manga)
|
|
||||||
super.onSave(state)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -181,11 +181,10 @@ class ChaptersFragment : BaseRxFragment<ChaptersPresenter>(), ActionMode.Callbac
|
|||||||
}
|
}
|
||||||
|
|
||||||
val isCatalogueManga: Boolean
|
val isCatalogueManga: Boolean
|
||||||
get() = (activity as MangaActivity).isCatalogueManga
|
get() = (activity as MangaActivity).fromCatalogue
|
||||||
|
|
||||||
fun openChapter(chapter: Chapter, hasAnimation: Boolean = false) {
|
fun openChapter(chapter: Chapter, hasAnimation: Boolean = false) {
|
||||||
presenter.onOpenChapter(chapter)
|
val intent = ReaderActivity.newIntent(activity, presenter.manga, chapter)
|
||||||
val intent = ReaderActivity.newIntent(activity)
|
|
||||||
if (hasAnimation) {
|
if (hasAnimation) {
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)
|
intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)
|
||||||
}
|
}
|
||||||
|
@ -159,10 +159,6 @@ class ChaptersPresenter : BasePresenter<ChaptersFragment>() {
|
|||||||
refreshChapters()
|
refreshChapters()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onOpenChapter(chapter: Chapter) {
|
|
||||||
SharedData.put(ReaderEvent(manga, chapter))
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getNextUnreadChapter(): Chapter? {
|
fun getNextUnreadChapter(): Chapter? {
|
||||||
return db.getNextUnreadChapter(manga).executeAsBlocking()
|
return db.getNextUnreadChapter(manga).executeAsBlocking()
|
||||||
}
|
}
|
||||||
|
@ -98,7 +98,8 @@ class MangaInfoFragment : BaseRxFragment<MangaInfoPresenter>() {
|
|||||||
val coverCache = presenter.coverCache
|
val coverCache = presenter.coverCache
|
||||||
val headers = presenter.source.glideHeaders
|
val headers = presenter.source.glideHeaders
|
||||||
|
|
||||||
// Check if thumbnail_url is given.
|
// Set cover if it wasn't already.
|
||||||
|
if (manga_cover.drawable == null) {
|
||||||
manga.thumbnail_url?.let { url ->
|
manga.thumbnail_url?.let { url ->
|
||||||
if (manga.favorite) {
|
if (manga.favorite) {
|
||||||
coverCache.saveOrLoadFromCache(url, headers) {
|
coverCache.saveOrLoadFromCache(url, headers) {
|
||||||
@ -133,6 +134,7 @@ class MangaInfoFragment : BaseRxFragment<MangaInfoPresenter>() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update chapter count TextView.
|
* Update chapter count TextView.
|
||||||
|
@ -91,9 +91,7 @@ class MyAnimeListDialogFragment : DialogFragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun onPositiveButtonClick() {
|
private fun onPositiveButtonClick() {
|
||||||
selectedItem?.let {
|
presenter.registerManga(selectedItem)
|
||||||
presenter.registerManga(it)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun search(query: String) {
|
private fun search(query: String) {
|
||||||
|
@ -56,6 +56,10 @@ class MyAnimeListFragment : BaseRxFragment<MyAnimeListPresenter>() {
|
|||||||
myanimelist_status.text = presenter.myAnimeList.getStatus(it.status)
|
myanimelist_status.text = presenter.myAnimeList.getStatus(it.status)
|
||||||
} ?: run {
|
} ?: run {
|
||||||
myanimelist_title.setTextAppearance(context, R.style.TextAppearance_Medium_Button)
|
myanimelist_title.setTextAppearance(context, R.style.TextAppearance_Medium_Button)
|
||||||
|
myanimelist_title.setText(R.string.action_edit)
|
||||||
|
myanimelist_chapters.text = ""
|
||||||
|
myanimelist_score.text = ""
|
||||||
|
myanimelist_status.text = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -123,7 +123,8 @@ class MyAnimeListPresenter : BasePresenter<MyAnimeListFragment>() {
|
|||||||
stop(GET_SEARCH_RESULTS)
|
stop(GET_SEARCH_RESULTS)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun registerManga(sync: MangaSync) {
|
fun registerManga(sync: MangaSync?) {
|
||||||
|
if (sync != null) {
|
||||||
sync.manga_id = manga.id
|
sync.manga_id = manga.id
|
||||||
add(myAnimeList.bind(sync)
|
add(myAnimeList.bind(sync)
|
||||||
.flatMap { response ->
|
.flatMap { response ->
|
||||||
@ -137,6 +138,9 @@ class MyAnimeListPresenter : BasePresenter<MyAnimeListFragment>() {
|
|||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe({ },
|
.subscribe({ },
|
||||||
{ error -> context.toast(error.message) }))
|
{ error -> context.toast(error.message) }))
|
||||||
|
} else {
|
||||||
|
db.deleteMangaSyncForManga(manga).executeAsBlocking()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getAllStatus(): List<String> {
|
fun getAllStatus(): List<String> {
|
||||||
|
@ -21,6 +21,7 @@ import eu.kanade.tachiyomi.data.database.models.Manga
|
|||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
||||||
import eu.kanade.tachiyomi.data.source.model.Page
|
import eu.kanade.tachiyomi.data.source.model.Page
|
||||||
|
import eu.kanade.tachiyomi.event.ReaderEvent
|
||||||
import eu.kanade.tachiyomi.ui.base.activity.BaseRxActivity
|
import eu.kanade.tachiyomi.ui.base.activity.BaseRxActivity
|
||||||
import eu.kanade.tachiyomi.ui.base.listener.SimpleAnimationListener
|
import eu.kanade.tachiyomi.ui.base.listener.SimpleAnimationListener
|
||||||
import eu.kanade.tachiyomi.ui.base.listener.SimpleSeekBarListener
|
import eu.kanade.tachiyomi.ui.base.listener.SimpleSeekBarListener
|
||||||
@ -30,6 +31,7 @@ import eu.kanade.tachiyomi.ui.reader.viewer.pager.horizontal.RightToLeftReader
|
|||||||
import eu.kanade.tachiyomi.ui.reader.viewer.pager.vertical.VerticalReader
|
import eu.kanade.tachiyomi.ui.reader.viewer.pager.vertical.VerticalReader
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.webtoon.WebtoonReader
|
import eu.kanade.tachiyomi.ui.reader.viewer.webtoon.WebtoonReader
|
||||||
import eu.kanade.tachiyomi.util.GLUtil
|
import eu.kanade.tachiyomi.util.GLUtil
|
||||||
|
import eu.kanade.tachiyomi.util.SharedData
|
||||||
import eu.kanade.tachiyomi.util.toast
|
import eu.kanade.tachiyomi.util.toast
|
||||||
import kotlinx.android.synthetic.main.activity_reader.*
|
import kotlinx.android.synthetic.main.activity_reader.*
|
||||||
import kotlinx.android.synthetic.main.reader_menu.*
|
import kotlinx.android.synthetic.main.reader_menu.*
|
||||||
@ -53,7 +55,8 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() {
|
|||||||
|
|
||||||
const val MENU_VISIBLE = "menu_visible"
|
const val MENU_VISIBLE = "menu_visible"
|
||||||
|
|
||||||
fun newIntent(context: Context): Intent {
|
fun newIntent(context: Context, manga: Manga, chapter: Chapter): Intent {
|
||||||
|
SharedData.put(ReaderEvent(manga, chapter))
|
||||||
return Intent(context, ReaderActivity::class.java)
|
return Intent(context, ReaderActivity::class.java)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -81,6 +84,8 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() {
|
|||||||
|
|
||||||
private var prevChapterBtn: MenuItem? = null
|
private var prevChapterBtn: MenuItem? = null
|
||||||
|
|
||||||
|
private val volumeKeysEnabled by lazy { preferences.readWithVolumeKeys().getOrDefault() }
|
||||||
|
|
||||||
val preferences: PreferencesHelper
|
val preferences: PreferencesHelper
|
||||||
get() = presenter.prefs
|
get() = presenter.prefs
|
||||||
|
|
||||||
@ -88,6 +93,11 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() {
|
|||||||
super.onCreate(savedState)
|
super.onCreate(savedState)
|
||||||
setContentView(R.layout.activity_reader)
|
setContentView(R.layout.activity_reader)
|
||||||
|
|
||||||
|
if (savedState == null && SharedData.get(ReaderEvent::class.java) == null) {
|
||||||
|
finish()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
setupToolbar(toolbar)
|
setupToolbar(toolbar)
|
||||||
subscriptions = CompositeSubscription()
|
subscriptions = CompositeSubscription()
|
||||||
|
|
||||||
@ -174,20 +184,34 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() {
|
|||||||
|
|
||||||
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
|
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
|
||||||
if (!isFinishing) {
|
if (!isFinishing) {
|
||||||
val action = event.action
|
when (event.keyCode) {
|
||||||
val keyCode = event.keyCode
|
KeyEvent.KEYCODE_VOLUME_DOWN -> {
|
||||||
when (keyCode) {
|
if (volumeKeysEnabled) {
|
||||||
KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_DPAD_RIGHT -> {
|
if (event.action == KeyEvent.ACTION_UP) {
|
||||||
if (action == KeyEvent.ACTION_UP)
|
|
||||||
viewer?.moveToNext()
|
viewer?.moveToNext()
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
KeyEvent.KEYCODE_VOLUME_UP, KeyEvent.KEYCODE_DPAD_LEFT -> {
|
}
|
||||||
if (action == KeyEvent.ACTION_UP)
|
KeyEvent.KEYCODE_VOLUME_UP -> {
|
||||||
|
if (volumeKeysEnabled) {
|
||||||
|
if (event.action == KeyEvent.ACTION_UP) {
|
||||||
viewer?.moveToPrevious()
|
viewer?.moveToPrevious()
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
KeyEvent.KEYCODE_DPAD_RIGHT -> {
|
||||||
|
if (event.action == KeyEvent.ACTION_UP) {
|
||||||
|
viewer?.moveToNext()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KeyEvent.KEYCODE_DPAD_LEFT -> {
|
||||||
|
if (event.action == KeyEvent.ACTION_UP) {
|
||||||
|
viewer?.moveToPrevious()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return super.dispatchKeyEvent(event)
|
return super.dispatchKeyEvent(event)
|
||||||
}
|
}
|
||||||
@ -258,7 +282,7 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() {
|
|||||||
|
|
||||||
|
|
||||||
private fun getOrCreateViewer(manga: Manga): BaseReader {
|
private fun getOrCreateViewer(manga: Manga): BaseReader {
|
||||||
val mangaViewer = if (manga.viewer == 0) preferences.defaultViewer else manga.viewer
|
val mangaViewer = if (manga.viewer == 0) preferences.defaultViewer() else manga.viewer
|
||||||
|
|
||||||
// Try to reuse the viewer using its tag
|
// Try to reuse the viewer using its tag
|
||||||
var fragment: BaseReader? = supportFragmentManager.findFragmentByTag(manga.viewer.toString()) as? BaseReader
|
var fragment: BaseReader? = supportFragmentManager.findFragmentByTag(manga.viewer.toString()) as? BaseReader
|
||||||
@ -479,13 +503,15 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() {
|
|||||||
private fun setCustomBrightness(enabled: Boolean) {
|
private fun setCustomBrightness(enabled: Boolean) {
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
customBrightnessSubscription = preferences.customBrightnessValue().asObservable()
|
customBrightnessSubscription = preferences.customBrightnessValue().asObservable()
|
||||||
|
.map { Math.max(0.01f, it) }
|
||||||
.subscribe { setCustomBrightnessValue(it) }
|
.subscribe { setCustomBrightnessValue(it) }
|
||||||
|
|
||||||
subscriptions.add(customBrightnessSubscription)
|
subscriptions.add(customBrightnessSubscription)
|
||||||
} else {
|
} else {
|
||||||
if (customBrightnessSubscription != null)
|
if (customBrightnessSubscription != null) {
|
||||||
subscriptions.remove(customBrightnessSubscription)
|
subscriptions.remove(customBrightnessSubscription)
|
||||||
setCustomBrightnessValue(-1f)
|
}
|
||||||
|
setCustomBrightnessValue(WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,7 +71,7 @@ class ReaderPresenter : BasePresenter<ReaderActivity>() {
|
|||||||
super.onCreate(savedState)
|
super.onCreate(savedState)
|
||||||
|
|
||||||
if (savedState == null) {
|
if (savedState == null) {
|
||||||
val event = SharedData.remove(ReaderEvent::class.java) ?: return
|
val event = SharedData.get(ReaderEvent::class.java) ?: return
|
||||||
manga = event.manga
|
manga = event.manga
|
||||||
chapter = event.chapter
|
chapter = event.chapter
|
||||||
} else {
|
} else {
|
||||||
|
@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.ui.reader.viewer.base
|
|||||||
|
|
||||||
import com.davemorrissey.labs.subscaleview.decoder.*
|
import com.davemorrissey.labs.subscaleview.decoder.*
|
||||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||||
|
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
||||||
import eu.kanade.tachiyomi.data.source.model.Page
|
import eu.kanade.tachiyomi.data.source.model.Page
|
||||||
import eu.kanade.tachiyomi.ui.base.fragment.BaseFragment
|
import eu.kanade.tachiyomi.ui.base.fragment.BaseFragment
|
||||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
||||||
@ -54,6 +55,11 @@ abstract class BaseReader : BaseFragment() {
|
|||||||
lateinit var bitmapDecoderClass: Class<out ImageDecoder>
|
lateinit var bitmapDecoderClass: Class<out ImageDecoder>
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether tap navigation is enabled or not.
|
||||||
|
*/
|
||||||
|
val tappingEnabled by lazy { readerActivity.preferences.readWithTapping().getOrDefault() }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the reader has requested to append a chapter. Used with seamless mode to avoid
|
* Whether the reader has requested to append a chapter. Used with seamless mode to avoid
|
||||||
* restarting requests when changing pages.
|
* restarting requests when changing pages.
|
||||||
@ -72,7 +78,7 @@ abstract class BaseReader : BaseFragment() {
|
|||||||
* Returns the active page.
|
* Returns the active page.
|
||||||
*/
|
*/
|
||||||
fun getActivePage(): Page {
|
fun getActivePage(): Page {
|
||||||
return pages[currentPage]
|
return pages.getOrElse(currentPage) { pages[0] }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -164,9 +164,9 @@ abstract class PagerReader : BaseReader() {
|
|||||||
val positionX = e.x
|
val positionX = e.x
|
||||||
|
|
||||||
if (positionX < pager.width * LEFT_REGION) {
|
if (positionX < pager.width * LEFT_REGION) {
|
||||||
onLeftSideTap()
|
if (tappingEnabled) onLeftSideTap()
|
||||||
} else if (positionX > pager.width * RIGHT_REGION) {
|
} else if (positionX > pager.width * RIGHT_REGION) {
|
||||||
onRightSideTap()
|
if (tappingEnabled) onRightSideTap()
|
||||||
} else {
|
} else {
|
||||||
readerActivity.onCenterSingleTap()
|
readerActivity.onCenterSingleTap()
|
||||||
}
|
}
|
||||||
|
@ -860,7 +860,7 @@ public class VerticalViewPagerImpl extends ViewGroup {
|
|||||||
final float distance = halfHeight + halfHeight *
|
final float distance = halfHeight + halfHeight *
|
||||||
distanceInfluenceForSnapDuration(distanceRatio);
|
distanceInfluenceForSnapDuration(distanceRatio);
|
||||||
|
|
||||||
int duration = 0;
|
int duration;
|
||||||
velocity = Math.abs(velocity);
|
velocity = Math.abs(velocity);
|
||||||
if (velocity > 0) {
|
if (velocity > 0) {
|
||||||
duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
|
duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
|
||||||
@ -1017,7 +1017,7 @@ public class VerticalViewPagerImpl extends ViewGroup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Locate the currently focused item or add it if needed.
|
// Locate the currently focused item or add it if needed.
|
||||||
int curIndex = -1;
|
int curIndex;
|
||||||
ItemInfo curItem = null;
|
ItemInfo curItem = null;
|
||||||
for (curIndex = 0; curIndex < mItems.size(); curIndex++) {
|
for (curIndex = 0; curIndex < mItems.size(); curIndex++) {
|
||||||
final ItemInfo ii = mItems.get(curIndex);
|
final ItemInfo ii = mItems.get(curIndex);
|
||||||
@ -1177,7 +1177,7 @@ public class VerticalViewPagerImpl extends ViewGroup {
|
|||||||
// Base offsets off of oldCurInfo.
|
// Base offsets off of oldCurInfo.
|
||||||
if (oldCurPosition < curItem.position) {
|
if (oldCurPosition < curItem.position) {
|
||||||
int itemIndex = 0;
|
int itemIndex = 0;
|
||||||
ItemInfo ii = null;
|
ItemInfo ii;
|
||||||
float offset = oldCurInfo.offset + oldCurInfo.heightFactor + marginOffset;
|
float offset = oldCurInfo.offset + oldCurInfo.heightFactor + marginOffset;
|
||||||
for (int pos = oldCurPosition + 1;
|
for (int pos = oldCurPosition + 1;
|
||||||
pos <= curItem.position && itemIndex < mItems.size(); pos++) {
|
pos <= curItem.position && itemIndex < mItems.size(); pos++) {
|
||||||
@ -1197,7 +1197,7 @@ public class VerticalViewPagerImpl extends ViewGroup {
|
|||||||
}
|
}
|
||||||
} else if (oldCurPosition > curItem.position) {
|
} else if (oldCurPosition > curItem.position) {
|
||||||
int itemIndex = mItems.size() - 1;
|
int itemIndex = mItems.size() - 1;
|
||||||
ItemInfo ii = null;
|
ItemInfo ii;
|
||||||
float offset = oldCurInfo.offset;
|
float offset = oldCurInfo.offset;
|
||||||
for (int pos = oldCurPosition - 1;
|
for (int pos = oldCurPosition - 1;
|
||||||
pos >= curItem.position && itemIndex >= 0; pos--) {
|
pos >= curItem.position && itemIndex >= 0; pos--) {
|
||||||
@ -1561,8 +1561,8 @@ public class VerticalViewPagerImpl extends ViewGroup {
|
|||||||
final View child = getChildAt(i);
|
final View child = getChildAt(i);
|
||||||
if (child.getVisibility() != GONE) {
|
if (child.getVisibility() != GONE) {
|
||||||
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
|
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
|
||||||
int childLeft = 0;
|
int childLeft;
|
||||||
int childTop = 0;
|
int childTop;
|
||||||
if (lp.isDecor) {
|
if (lp.isDecor) {
|
||||||
final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
|
final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
|
||||||
final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
|
final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
|
||||||
@ -1731,7 +1731,7 @@ public class VerticalViewPagerImpl extends ViewGroup {
|
|||||||
if (!lp.isDecor) continue;
|
if (!lp.isDecor) continue;
|
||||||
|
|
||||||
final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
|
final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
|
||||||
int childTop = 0;
|
int childTop;
|
||||||
switch (vgrav) {
|
switch (vgrav) {
|
||||||
default:
|
default:
|
||||||
childTop = paddingTop;
|
childTop = paddingTop;
|
||||||
|
@ -126,9 +126,9 @@ class WebtoonReader : BaseReader() {
|
|||||||
val positionX = e.x
|
val positionX = e.x
|
||||||
|
|
||||||
if (positionX < recycler.width * LEFT_REGION) {
|
if (positionX < recycler.width * LEFT_REGION) {
|
||||||
moveToPrevious()
|
if (tappingEnabled) moveToPrevious()
|
||||||
} else if (positionX > recycler.width * RIGHT_REGION) {
|
} else if (positionX > recycler.width * RIGHT_REGION) {
|
||||||
moveToNext()
|
if (tappingEnabled) moveToNext()
|
||||||
} else {
|
} else {
|
||||||
readerActivity.onCenterSingleTap()
|
readerActivity.onCenterSingleTap()
|
||||||
}
|
}
|
||||||
|
@ -103,21 +103,17 @@ class RecentChaptersFragment : BaseRxFragment<RecentChaptersPresenter>(), Flexib
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Open chapter in reader
|
* Open chapter in reader
|
||||||
|
*
|
||||||
* @param chapter selected chapter
|
* @param chapter selected chapter
|
||||||
*/
|
*/
|
||||||
private fun openChapter(chapter: MangaChapter) {
|
private fun openChapter(chapter: MangaChapter) {
|
||||||
// Start reader event
|
val intent = ReaderActivity.newIntent(activity, chapter.manga, chapter.chapter)
|
||||||
presenter.onOpenChapter(chapter)
|
|
||||||
|
|
||||||
//Start reader intent
|
|
||||||
val intent = ReaderActivity.newIntent(activity)
|
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Populate adapter with chapters
|
* Populate adapter with chapters
|
||||||
|
*
|
||||||
* @param chapters list of chapters
|
* @param chapters list of chapters
|
||||||
*/
|
*/
|
||||||
fun onNextMangaChapters(chapters: List<Any>) {
|
fun onNextMangaChapters(chapters: List<Any>) {
|
||||||
|
@ -10,9 +10,7 @@ import eu.kanade.tachiyomi.data.download.model.Download
|
|||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.data.source.SourceManager
|
import eu.kanade.tachiyomi.data.source.SourceManager
|
||||||
import eu.kanade.tachiyomi.event.DownloadChaptersEvent
|
import eu.kanade.tachiyomi.event.DownloadChaptersEvent
|
||||||
import eu.kanade.tachiyomi.event.ReaderEvent
|
|
||||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
||||||
import eu.kanade.tachiyomi.util.SharedData
|
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.android.schedulers.AndroidSchedulers
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
import rx.schedulers.Schedulers
|
import rx.schedulers.Schedulers
|
||||||
@ -251,14 +249,6 @@ class RecentChaptersPresenter : BasePresenter<RecentChaptersFragment>() {
|
|||||||
return cal.time
|
return cal.time
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Open chapter in reader
|
|
||||||
* @param item chapter that is opened
|
|
||||||
*/
|
|
||||||
fun onOpenChapter(item: MangaChapter) {
|
|
||||||
SharedData.put(ReaderEvent(item.manga, item.chapter))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Download selected chapter
|
* Download selected chapter
|
||||||
* @param selectedChapter chapter that is selected
|
* @param selectedChapter chapter that is selected
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package eu.kanade.tachiyomi.ui.setting
|
package eu.kanade.tachiyomi.ui.setting
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.support.v7.preference.SwitchPreferenceCompat
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import com.afollestad.materialdialogs.MaterialDialog
|
import com.afollestad.materialdialogs.MaterialDialog
|
||||||
import eu.kanade.tachiyomi.BuildConfig
|
import eu.kanade.tachiyomi.BuildConfig
|
||||||
@ -27,6 +28,10 @@ class SettingsAboutFragment : SettingsNestedFragment() {
|
|||||||
*/
|
*/
|
||||||
private var releaseSubscription: Subscription? = null
|
private var releaseSubscription: Subscription? = null
|
||||||
|
|
||||||
|
val automaticUpdateToggle by lazy {
|
||||||
|
findPreference(getString(R.string.pref_enable_automatic_updates_key)) as SwitchPreferenceCompat
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
fun newInstance(resourcePreference: Int, resourceTitle: Int): SettingsNestedFragment {
|
fun newInstance(resourcePreference: Int, resourceTitle: Int): SettingsNestedFragment {
|
||||||
@ -45,13 +50,21 @@ class SettingsAboutFragment : SettingsNestedFragment() {
|
|||||||
else
|
else
|
||||||
BuildConfig.VERSION_NAME
|
BuildConfig.VERSION_NAME
|
||||||
|
|
||||||
|
if (!BuildConfig.DEBUG && BuildConfig.INCLUDE_UPDATER) {
|
||||||
//Set onClickListener to check for new version
|
//Set onClickListener to check for new version
|
||||||
version.setOnPreferenceClickListener {
|
version.setOnPreferenceClickListener {
|
||||||
if (!BuildConfig.DEBUG && BuildConfig.INCLUDE_UPDATER)
|
|
||||||
checkVersion()
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//TODO One glorious day enable this and add the magnificent option for auto update checking.
|
||||||
|
// automaticUpdateToggle.isEnabled = true
|
||||||
|
// automaticUpdateToggle.setOnPreferenceChangeListener { preference, any ->
|
||||||
|
// val status = any as Boolean
|
||||||
|
// UpdateDownloaderAlarm.startAlarm(activity, 12, status)
|
||||||
|
// true
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
buildTime.summary = getFormattedBuildTime()
|
buildTime.summary = getFormattedBuildTime()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,12 +14,13 @@ import com.nononsenseapps.filepicker.FilePickerActivity
|
|||||||
import com.nononsenseapps.filepicker.FilePickerFragment
|
import com.nononsenseapps.filepicker.FilePickerFragment
|
||||||
import com.nononsenseapps.filepicker.LogicHandler
|
import com.nononsenseapps.filepicker.LogicHandler
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
||||||
import eu.kanade.tachiyomi.util.inflate
|
import eu.kanade.tachiyomi.util.inflate
|
||||||
|
import rx.Subscription
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
class SettingsDownloadsFragment : SettingsNestedFragment() {
|
class SettingsDownloadsFragment : SettingsNestedFragment() {
|
||||||
|
|
||||||
val downloadDirPref by lazy { findPreference(getString(R.string.pref_download_directory_key)) }
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
val DOWNLOAD_DIR_CODE = 103
|
val DOWNLOAD_DIR_CODE = 103
|
||||||
@ -31,11 +32,16 @@ class SettingsDownloadsFragment : SettingsNestedFragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val downloadDirPref by lazy { findPreference(getString(R.string.pref_download_directory_key)) }
|
||||||
|
|
||||||
|
var downloadDirSubscription: Subscription? = null
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedState: Bundle?) {
|
override fun onViewCreated(view: View, savedState: Bundle?) {
|
||||||
downloadDirPref.setOnPreferenceClickListener {
|
downloadDirPref.setOnPreferenceClickListener {
|
||||||
|
|
||||||
|
val currentDir = preferences.downloadsDirectory().getOrDefault()
|
||||||
val externalDirs = getExternalFilesDirs()
|
val externalDirs = getExternalFilesDirs()
|
||||||
val selectedIndex = externalDirs.indexOf(File(preferences.downloadsDirectory))
|
val selectedIndex = externalDirs.indexOf(File(currentDir))
|
||||||
|
|
||||||
MaterialDialog.Builder(activity)
|
MaterialDialog.Builder(activity)
|
||||||
.items(externalDirs + getString(R.string.custom_dir))
|
.items(externalDirs + getString(R.string.custom_dir))
|
||||||
@ -46,13 +52,12 @@ class SettingsDownloadsFragment : SettingsNestedFragment() {
|
|||||||
i.putExtra(FilePickerActivity.EXTRA_ALLOW_MULTIPLE, false)
|
i.putExtra(FilePickerActivity.EXTRA_ALLOW_MULTIPLE, false)
|
||||||
i.putExtra(FilePickerActivity.EXTRA_ALLOW_CREATE_DIR, true)
|
i.putExtra(FilePickerActivity.EXTRA_ALLOW_CREATE_DIR, true)
|
||||||
i.putExtra(FilePickerActivity.EXTRA_MODE, FilePickerActivity.MODE_DIR)
|
i.putExtra(FilePickerActivity.EXTRA_MODE, FilePickerActivity.MODE_DIR)
|
||||||
i.putExtra(FilePickerActivity.EXTRA_START_PATH, preferences.downloadsDirectory)
|
i.putExtra(FilePickerActivity.EXTRA_START_PATH, currentDir)
|
||||||
|
|
||||||
startActivityForResult(i, DOWNLOAD_DIR_CODE)
|
startActivityForResult(i, DOWNLOAD_DIR_CODE)
|
||||||
} else {
|
} else {
|
||||||
// One of the predefined folders was selected
|
// One of the predefined folders was selected
|
||||||
preferences.downloadsDirectory = text.toString()
|
preferences.downloadsDirectory().set(text.toString())
|
||||||
updateDownloadsDir()
|
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
})
|
})
|
||||||
@ -60,15 +65,14 @@ class SettingsDownloadsFragment : SettingsNestedFragment() {
|
|||||||
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
downloadDirSubscription = preferences.downloadsDirectory().asObservable()
|
||||||
|
.subscribe { downloadDirPref.summary = it }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onDestroyView() {
|
||||||
super.onResume()
|
downloadDirSubscription?.unsubscribe()
|
||||||
updateDownloadsDir()
|
super.onDestroyView()
|
||||||
}
|
|
||||||
|
|
||||||
fun updateDownloadsDir() {
|
|
||||||
downloadDirPref.summary = preferences.downloadsDirectory
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getExternalFilesDirs(): List<File> {
|
fun getExternalFilesDirs(): List<File> {
|
||||||
@ -81,7 +85,7 @@ class SettingsDownloadsFragment : SettingsNestedFragment() {
|
|||||||
|
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
if (data != null && requestCode == DOWNLOAD_DIR_CODE && resultCode == Activity.RESULT_OK) {
|
if (data != null && requestCode == DOWNLOAD_DIR_CODE && resultCode == Activity.RESULT_OK) {
|
||||||
preferences.downloadsDirectory = data.data.path
|
preferences.downloadsDirectory().set(data.data.path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.ui.setting
|
|||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.support.v14.preference.MultiSelectListPreference
|
||||||
import android.support.v4.app.TaskStackBuilder
|
import android.support.v4.app.TaskStackBuilder
|
||||||
import android.support.v7.preference.Preference
|
import android.support.v7.preference.Preference
|
||||||
import android.view.View
|
import android.view.View
|
||||||
@ -33,13 +34,28 @@ class SettingsGeneralFragment : SettingsNestedFragment() {
|
|||||||
findPreference(getString(R.string.pref_library_update_interval_key)) as IntListPreference
|
findPreference(getString(R.string.pref_library_update_interval_key)) as IntListPreference
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val updateRestriction by lazy {
|
||||||
|
findPreference(getString(R.string.pref_library_update_restriction_key)) as MultiSelectListPreference
|
||||||
|
}
|
||||||
|
|
||||||
val themePreference by lazy {
|
val themePreference by lazy {
|
||||||
findPreference(getString(R.string.pref_theme_key)) as IntListPreference
|
findPreference(getString(R.string.pref_theme_key)) as IntListPreference
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var updateIntervalSubscription: Subscription? = null
|
||||||
|
|
||||||
var columnsSubscription: Subscription? = null
|
var columnsSubscription: Subscription? = null
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
updateIntervalSubscription = preferences.libraryUpdateInterval().asObservable()
|
||||||
|
.subscribe { updateRestriction.isVisible = it > 0 }
|
||||||
|
|
||||||
|
columnsSubscription = Observable.combineLatest(
|
||||||
|
preferences.portraitColumns().asObservable(),
|
||||||
|
preferences.landscapeColumns().asObservable())
|
||||||
|
{ portraitColumns, landscapeColumns -> Pair(portraitColumns, landscapeColumns) }
|
||||||
|
.subscribe { updateColumnsSummary(it.first, it.second) }
|
||||||
|
|
||||||
updateInterval.setOnPreferenceChangeListener { preference, newValue ->
|
updateInterval.setOnPreferenceChangeListener { preference, newValue ->
|
||||||
LibraryUpdateAlarm.startAlarm(activity, (newValue as String).toInt())
|
LibraryUpdateAlarm.startAlarm(activity, (newValue as String).toInt())
|
||||||
true
|
true
|
||||||
@ -57,17 +73,11 @@ class SettingsGeneralFragment : SettingsNestedFragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onDestroyView() {
|
||||||
super.onResume()
|
updateIntervalSubscription?.unsubscribe()
|
||||||
columnsSubscription = Observable.combineLatest(preferences.portraitColumns().asObservable(),
|
|
||||||
preferences.landscapeColumns().asObservable(),
|
|
||||||
{ portraitColumns, landscapeColumns -> Pair(portraitColumns, landscapeColumns) })
|
|
||||||
.subscribe { updateColumnsSummary(it.first, it.second) }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPause() {
|
|
||||||
columnsSubscription?.unsubscribe()
|
columnsSubscription?.unsubscribe()
|
||||||
super.onPause()
|
super.onDestroyView()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDisplayPreferenceDialog(preference: Preference) {
|
override fun onDisplayPreferenceDialog(preference: Preference) {
|
||||||
|
@ -44,9 +44,5 @@ open class SettingsNestedFragment : PreferenceFragment() {
|
|||||||
get() = settingsActivity.preferences
|
get() = settingsActivity.preferences
|
||||||
|
|
||||||
val fragmentManagerCompat: FragmentManager
|
val fragmentManagerCompat: FragmentManager
|
||||||
get() = if (Build.VERSION.SDK_INT >= 17) {
|
get() = if (Build.VERSION.SDK_INT >= 17) childFragmentManager else fragmentManager
|
||||||
childFragmentManager
|
|
||||||
} else {
|
|
||||||
fragmentManager
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -6,7 +6,6 @@ import android.support.v14.preference.MultiSelectListPreference
|
|||||||
import android.support.v7.preference.Preference
|
import android.support.v7.preference.Preference
|
||||||
import android.support.v7.preference.PreferenceGroup
|
import android.support.v7.preference.PreferenceGroup
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
|
||||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
||||||
import eu.kanade.tachiyomi.data.source.base.Source
|
import eu.kanade.tachiyomi.data.source.base.Source
|
||||||
import eu.kanade.tachiyomi.data.source.getLanguages
|
import eu.kanade.tachiyomi.data.source.getLanguages
|
||||||
@ -65,7 +64,7 @@ class SettingsSourcesFragment : SettingsNestedFragment() {
|
|||||||
|
|
||||||
fun createSource(source: Source): Preference {
|
fun createSource(source: Source): Preference {
|
||||||
return LoginPreference(preferenceManager.context).apply {
|
return LoginPreference(preferenceManager.context).apply {
|
||||||
key = PreferencesHelper.SOURCE_ACCOUNT_USERNAME + source.id
|
key = preferences.keys.sourceUsername(source.id)
|
||||||
title = source.visibleName
|
title = source.visibleName
|
||||||
|
|
||||||
setOnPreferenceClickListener {
|
setOnPreferenceClickListener {
|
||||||
@ -80,7 +79,7 @@ class SettingsSourcesFragment : SettingsNestedFragment() {
|
|||||||
|
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
if (requestCode == SOURCE_CHANGE_REQUEST) {
|
if (requestCode == SOURCE_CHANGE_REQUEST) {
|
||||||
val pref = findPreference(PreferencesHelper.SOURCE_ACCOUNT_USERNAME + resultCode) as? LoginPreference
|
val pref = findPreference(preferences.keys.sourceUsername(resultCode)) as? LoginPreference
|
||||||
pref?.notifyChanged()
|
pref?.notifyChanged()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@ import android.content.Intent
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.support.v7.preference.PreferenceCategory
|
import android.support.v7.preference.PreferenceCategory
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
|
||||||
import eu.kanade.tachiyomi.widget.preference.LoginPreference
|
import eu.kanade.tachiyomi.widget.preference.LoginPreference
|
||||||
import eu.kanade.tachiyomi.widget.preference.MangaSyncLoginDialog
|
import eu.kanade.tachiyomi.widget.preference.MangaSyncLoginDialog
|
||||||
|
|
||||||
@ -27,7 +26,7 @@ class SettingsSyncFragment : SettingsNestedFragment() {
|
|||||||
|
|
||||||
for (sync in settingsActivity.syncManager.services) {
|
for (sync in settingsActivity.syncManager.services) {
|
||||||
val pref = LoginPreference(themedContext).apply {
|
val pref = LoginPreference(themedContext).apply {
|
||||||
key = PreferencesHelper.MANGASYNC_ACCOUNT_USERNAME + sync.id
|
key = preferences.keys.syncUsername(sync.id)
|
||||||
title = sync.name
|
title = sync.name
|
||||||
|
|
||||||
setOnPreferenceClickListener {
|
setOnPreferenceClickListener {
|
||||||
@ -44,7 +43,7 @@ class SettingsSyncFragment : SettingsNestedFragment() {
|
|||||||
|
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
if (requestCode == SYNC_CHANGE_REQUEST) {
|
if (requestCode == SYNC_CHANGE_REQUEST) {
|
||||||
val pref = findPreference(PreferencesHelper.MANGASYNC_ACCOUNT_USERNAME + resultCode) as? LoginPreference
|
val pref = findPreference(preferences.keys.syncUsername(resultCode)) as? LoginPreference
|
||||||
pref?.notifyChanged()
|
pref?.notifyChanged()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ object DeviceUtil {
|
|||||||
val intent = context.registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
|
val intent = context.registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
|
||||||
intent?.let {
|
intent?.let {
|
||||||
val plugged = it.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1)
|
val plugged = it.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1)
|
||||||
return plugged == BatteryManager.BATTERY_PLUGGED_AC || plugged == BatteryManager.BATTERY_PLUGGED_USB
|
return plugged == BatteryManager.BATTERY_PLUGGED_AC || plugged == BatteryManager.BATTERY_PLUGGED_USB || plugged == BatteryManager.BATTERY_PLUGGED_WIRELESS
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -5,10 +5,6 @@ import java.io.IOException;
|
|||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
import okio.BufferedSink;
|
|
||||||
import okio.BufferedSource;
|
|
||||||
import okio.Okio;
|
|
||||||
|
|
||||||
public final class DiskUtils {
|
public final class DiskUtils {
|
||||||
|
|
||||||
private DiskUtils() {
|
private DiskUtils() {
|
||||||
@ -39,34 +35,6 @@ public final class DiskUtils {
|
|||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static File saveBufferedSourceToDirectory(BufferedSource bufferedSource, File directory, String name) throws IOException {
|
|
||||||
createDirectory(directory);
|
|
||||||
|
|
||||||
File writeFile = new File(directory, name);
|
|
||||||
if (writeFile.exists()) {
|
|
||||||
if (writeFile.delete()) {
|
|
||||||
writeFile = new File(directory, name);
|
|
||||||
} else {
|
|
||||||
throw new IOException("Failed Deleting Existing File for Overwrite");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
BufferedSink bufferedSink = null;
|
|
||||||
try {
|
|
||||||
bufferedSink = Okio.buffer(Okio.sink(writeFile));
|
|
||||||
bufferedSink.writeAll(bufferedSource);
|
|
||||||
} catch (Exception e) {
|
|
||||||
writeFile.delete();
|
|
||||||
throw new IOException("Unable to save image");
|
|
||||||
} finally {
|
|
||||||
if (bufferedSink != null) {
|
|
||||||
bufferedSink.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return writeFile;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void deleteFiles(File inputFile) {
|
public static void deleteFiles(File inputFile) {
|
||||||
if (inputFile.isDirectory()) {
|
if (inputFile.isDirectory()) {
|
||||||
for (File childFile : inputFile.listFiles()) {
|
for (File childFile : inputFile.listFiles()) {
|
||||||
@ -74,6 +42,7 @@ public final class DiskUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//noinspection ResultOfMethodCallIgnored
|
||||||
inputFile.delete();
|
inputFile.delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
62
app/src/main/java/eu/kanade/tachiyomi/util/OkioExtensions.kt
Normal file
62
app/src/main/java/eu/kanade/tachiyomi/util/OkioExtensions.kt
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
package eu.kanade.tachiyomi.util
|
||||||
|
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
|
import okio.BufferedSource
|
||||||
|
import okio.Okio
|
||||||
|
import java.io.File
|
||||||
|
import java.io.OutputStream
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves the given source to a file and closes it. Directories will be created if needed.
|
||||||
|
*
|
||||||
|
* @param file the file where the source is copied.
|
||||||
|
*/
|
||||||
|
fun BufferedSource.saveTo(file: File) {
|
||||||
|
try {
|
||||||
|
// Create parent dirs if needed
|
||||||
|
file.parentFile.mkdirs()
|
||||||
|
|
||||||
|
// Copy to destination
|
||||||
|
saveTo(file.outputStream())
|
||||||
|
} catch (e: Exception) {
|
||||||
|
close()
|
||||||
|
file.delete()
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves the given source to an output stream and closes both resources.
|
||||||
|
*
|
||||||
|
* @param stream the stream where the source is copied.
|
||||||
|
*/
|
||||||
|
fun BufferedSource.saveTo(stream: OutputStream) {
|
||||||
|
use { input ->
|
||||||
|
Okio.buffer(Okio.sink(stream)).use {
|
||||||
|
it.writeAll(input)
|
||||||
|
it.flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves the given source to an output stream and closes both resources.
|
||||||
|
* The source is expected to be an image, and it may reencode the image.
|
||||||
|
*
|
||||||
|
* @param stream the stream where the source is copied.
|
||||||
|
* @param reencode whether to reencode the image or not.
|
||||||
|
*/
|
||||||
|
fun BufferedSource.saveImageTo(stream: OutputStream, reencode: Boolean = false) {
|
||||||
|
if (reencode) {
|
||||||
|
use {
|
||||||
|
val bitmap = BitmapFactory.decodeStream(it.inputStream())
|
||||||
|
stream.use {
|
||||||
|
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, it)
|
||||||
|
}
|
||||||
|
bitmap.recycle()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
saveTo(stream)
|
||||||
|
}
|
||||||
|
}
|
@ -14,7 +14,7 @@ object SharedData {
|
|||||||
/**
|
/**
|
||||||
* Map where the objects are saved.
|
* Map where the objects are saved.
|
||||||
*/
|
*/
|
||||||
private val map = HashMap<Class<*>, Any>()
|
val map = HashMap<Class<*>, Any>()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Publish an object to the shared data.
|
* Publish an object to the shared data.
|
||||||
@ -42,4 +42,14 @@ object SharedData {
|
|||||||
*/
|
*/
|
||||||
fun <T : Any> remove(classType: Class<T>) = get(classType)?.apply { map.remove(classType) }
|
fun <T : Any> remove(classType: Class<T>) = get(classType)?.apply { map.remove(classType) }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an object from the shared data or introduces a new one with the given function.
|
||||||
|
*
|
||||||
|
* @param classType the class of the object to retrieve.
|
||||||
|
* @param fn the function to execute if it didn't find the object.
|
||||||
|
* @return an object of type T.
|
||||||
|
*/
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
inline fun <T : Any> getOrPut(classType: Class<T>, fn: () -> T) = map.getOrPut(classType, fn) as T
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ import android.graphics.Typeface
|
|||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
class PTSansTextView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
|
class PTSansTextView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
|
||||||
@ -14,6 +15,9 @@ class PTSansTextView @JvmOverloads constructor(context: Context, attrs: Attribut
|
|||||||
companion object {
|
companion object {
|
||||||
const val PTSANS_NARROW = 0
|
const val PTSANS_NARROW = 0
|
||||||
const val PTSANS_NARROW_BOLD = 1
|
const val PTSANS_NARROW_BOLD = 1
|
||||||
|
|
||||||
|
// Map where typefaces are cached
|
||||||
|
private val typefaces = HashMap<Int, Typeface>(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@ -22,11 +26,13 @@ class PTSansTextView @JvmOverloads constructor(context: Context, attrs: Attribut
|
|||||||
|
|
||||||
val typeface = values.getInt(R.styleable.PTSansTextView_typeface, 0)
|
val typeface = values.getInt(R.styleable.PTSansTextView_typeface, 0)
|
||||||
|
|
||||||
when (typeface) {
|
setTypeface(typefaces.getOrPut(typeface) {
|
||||||
PTSANS_NARROW -> setTypeface(Typeface.createFromAsset(context.assets, "fonts/PTSans-Narrow.ttf"))
|
Typeface.createFromAsset(context.assets, when (typeface) {
|
||||||
PTSANS_NARROW_BOLD -> setTypeface(Typeface.createFromAsset(context.assets, "fonts/PTSans-NarrowBold.ttf"))
|
PTSANS_NARROW -> "fonts/PTSans-Narrow.ttf"
|
||||||
|
PTSANS_NARROW_BOLD -> "fonts/PTSans-NarrowBold.ttf"
|
||||||
else -> throw IllegalArgumentException("Font not found " + typeface)
|
else -> throw IllegalArgumentException("Font not found " + typeface)
|
||||||
}
|
})
|
||||||
|
})
|
||||||
|
|
||||||
values.recycle()
|
values.recycle()
|
||||||
}
|
}
|
||||||
|
@ -11,8 +11,8 @@ import com.afollestad.materialdialogs.MaterialDialog
|
|||||||
import com.dd.processbutton.iml.ActionProcessButton
|
import com.dd.processbutton.iml.ActionProcessButton
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.ui.setting.SettingsActivity
|
|
||||||
import eu.kanade.tachiyomi.ui.base.listener.SimpleTextWatcher
|
import eu.kanade.tachiyomi.ui.base.listener.SimpleTextWatcher
|
||||||
|
import eu.kanade.tachiyomi.ui.setting.SettingsActivity
|
||||||
import kotlinx.android.synthetic.main.pref_account_login.view.*
|
import kotlinx.android.synthetic.main.pref_account_login.view.*
|
||||||
import rx.Subscription
|
import rx.Subscription
|
||||||
|
|
||||||
@ -30,7 +30,7 @@ abstract class LoginDialogPreference : DialogFragment() {
|
|||||||
val dialog = MaterialDialog.Builder(activity)
|
val dialog = MaterialDialog.Builder(activity)
|
||||||
.customView(R.layout.pref_account_login, false)
|
.customView(R.layout.pref_account_login, false)
|
||||||
.negativeText(android.R.string.cancel)
|
.negativeText(android.R.string.cancel)
|
||||||
.build();
|
.build()
|
||||||
|
|
||||||
onViewCreated(dialog.view, savedState)
|
onViewCreated(dialog.view, savedState)
|
||||||
|
|
||||||
|
@ -34,8 +34,8 @@ class MangaSyncLoginDialog : LoginDialogPreference() {
|
|||||||
|
|
||||||
override fun setCredentialsOnView(view: View) = with(view) {
|
override fun setCredentialsOnView(view: View) = with(view) {
|
||||||
dialog_title.text = getString(R.string.login_title, sync.name)
|
dialog_title.text = getString(R.string.login_title, sync.name)
|
||||||
username.setText(preferences.getMangaSyncUsername(sync))
|
username.setText(preferences.mangaSyncUsername(sync))
|
||||||
password.setText(preferences.getMangaSyncPassword(sync))
|
password.setText(preferences.mangaSyncPassword(sync))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun checkLogin() {
|
override fun checkLogin() {
|
||||||
|
@ -34,8 +34,8 @@ class SourceLoginDialog : LoginDialogPreference() {
|
|||||||
|
|
||||||
override fun setCredentialsOnView(view: View) = with(view) {
|
override fun setCredentialsOnView(view: View) = with(view) {
|
||||||
dialog_title.text = getString(R.string.login_title, source.visibleName)
|
dialog_title.text = getString(R.string.login_title, source.visibleName)
|
||||||
username.setText(preferences.getSourceUsername(source))
|
username.setText(preferences.sourceUsername(source))
|
||||||
password.setText(preferences.getSourcePassword(source))
|
password.setText(preferences.sourcePassword(source))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun checkLogin() {
|
override fun checkLogin() {
|
||||||
|
Binary file not shown.
After Width: | Height: | Size: 717 B |
Binary file not shown.
After Width: | Height: | Size: 487 B |
Binary file not shown.
After Width: | Height: | Size: 871 B |
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
@ -27,7 +27,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_alignParentRight="true"
|
android:layout_alignParentRight="true"
|
||||||
android:layout_centerVertical="true"
|
android:layout_centerVertical="true"
|
||||||
android:text="Edit"
|
android:text="@string/action_edit"
|
||||||
style="@style/TextAppearance.Medium.Button"/>
|
style="@style/TextAppearance.Medium.Button"/>
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
@ -6,27 +6,23 @@
|
|||||||
<item
|
<item
|
||||||
android:id="@+id/action_download"
|
android:id="@+id/action_download"
|
||||||
android:title="@string/action_download"
|
android:title="@string/action_download"
|
||||||
android:icon="@drawable/ic_file_download_white_24dp"
|
|
||||||
android:visible="true"
|
android:visible="true"
|
||||||
app:showAsAction="ifRoom"/>
|
app:showAsAction="ifRoom"/>
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/action_delete"
|
android:id="@+id/action_delete"
|
||||||
android:title="@string/action_delete"
|
android:title="@string/action_delete"
|
||||||
android:icon="@drawable/ic_delete_white_24dp"
|
|
||||||
android:visible="false"
|
android:visible="false"
|
||||||
app:showAsAction="ifRoom"/>
|
app:showAsAction="ifRoom"/>
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/action_mark_as_read"
|
android:id="@+id/action_mark_as_read"
|
||||||
android:title="@string/action_mark_as_read"
|
android:title="@string/action_mark_as_read"
|
||||||
android:icon="@drawable/ic_done_all_white_24dp"
|
|
||||||
app:showAsAction="ifRoom"/>
|
app:showAsAction="ifRoom"/>
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/action_mark_as_unread"
|
android:id="@+id/action_mark_as_unread"
|
||||||
android:title="@string/action_mark_as_unread"
|
android:title="@string/action_mark_as_unread"
|
||||||
android:icon="@drawable/ic_done_all_grey_24dp"
|
|
||||||
app:showAsAction="ifRoom"/>
|
app:showAsAction="ifRoom"/>
|
||||||
|
|
||||||
</menu>
|
</menu>
|
@ -126,4 +126,14 @@
|
|||||||
<item>48</item>
|
<item>48</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="library_update_restrictions">
|
||||||
|
<item>@string/wifi</item>
|
||||||
|
<item>@string/charging</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="library_update_restrictions_values">
|
||||||
|
<item>wifi</item>
|
||||||
|
<item>ac</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
@ -13,10 +13,10 @@
|
|||||||
<string name="pref_library_columns_landscape_key">pref_library_columns_landscape_key</string>
|
<string name="pref_library_columns_landscape_key">pref_library_columns_landscape_key</string>
|
||||||
<string name="pref_library_update_interval_key">pref_library_update_interval_key</string>
|
<string name="pref_library_update_interval_key">pref_library_update_interval_key</string>
|
||||||
<string name="pref_update_only_non_completed_key">pref_update_only_non_completed_key</string>
|
<string name="pref_update_only_non_completed_key">pref_update_only_non_completed_key</string>
|
||||||
<string name="pref_update_only_when_charging_key">pref_update_only_when_charging_key</string>
|
|
||||||
<string name="pref_auto_update_manga_sync_key">pref_auto_update_manga_sync_key</string>
|
<string name="pref_auto_update_manga_sync_key">pref_auto_update_manga_sync_key</string>
|
||||||
<string name="pref_ask_update_manga_sync_key">pref_ask_update_manga_sync_key</string>
|
<string name="pref_ask_update_manga_sync_key">pref_ask_update_manga_sync_key</string>
|
||||||
<string name="pref_theme_key">pref_theme_key</string>
|
<string name="pref_theme_key">pref_theme_key</string>
|
||||||
|
<string name="pref_library_update_restriction_key">library_update_restriction</string>
|
||||||
|
|
||||||
<string name="pref_default_viewer_key">pref_default_viewer_key</string>
|
<string name="pref_default_viewer_key">pref_default_viewer_key</string>
|
||||||
<string name="pref_image_scale_type_key">pref_image_scale_type_key</string>
|
<string name="pref_image_scale_type_key">pref_image_scale_type_key</string>
|
||||||
@ -31,6 +31,9 @@
|
|||||||
<string name="pref_reader_theme_key">pref_reader_theme_key</string>
|
<string name="pref_reader_theme_key">pref_reader_theme_key</string>
|
||||||
<string name="pref_image_decoder_key">pref_image_decoder_key</string>
|
<string name="pref_image_decoder_key">pref_image_decoder_key</string>
|
||||||
<string name="pref_seamless_mode_key">pref_seamless_mode_key</string>
|
<string name="pref_seamless_mode_key">pref_seamless_mode_key</string>
|
||||||
|
<string name="pref_read_with_volume_keys_key">reader_volume_keys</string>
|
||||||
|
<string name="pref_read_with_tapping_key">reader_tap</string>
|
||||||
|
<string name="pref_reencode_key">reencode_image</string>
|
||||||
<string name="pref_filter_downloaded_key">pref_filter_downloaded_key</string>
|
<string name="pref_filter_downloaded_key">pref_filter_downloaded_key</string>
|
||||||
<string name="pref_filter_unread_key">pref_filter_unread_key</string>
|
<string name="pref_filter_unread_key">pref_filter_unread_key</string>
|
||||||
|
|
||||||
@ -49,8 +52,10 @@
|
|||||||
<string name="pref_clear_chapter_cache_key">pref_clear_chapter_cache_key</string>
|
<string name="pref_clear_chapter_cache_key">pref_clear_chapter_cache_key</string>
|
||||||
<string name="pref_clear_database_key">pref_clear_database_key</string>
|
<string name="pref_clear_database_key">pref_clear_database_key</string>
|
||||||
|
|
||||||
|
|
||||||
<string name="pref_version">pref_version</string>
|
<string name="pref_version">pref_version</string>
|
||||||
<string name="pref_build_time">pref_build_time</string>
|
<string name="pref_build_time">pref_build_time</string>
|
||||||
|
<string name="pref_enable_automatic_updates_key">pref_enable_automatic_updates_key</string>
|
||||||
|
|
||||||
<string name="pref_display_catalogue_as_list">pref_display_catalogue_as_list</string>
|
<string name="pref_display_catalogue_as_list">pref_display_catalogue_as_list</string>
|
||||||
<string name="pref_last_catalogue_source_key">pref_last_catalogue_source_key</string>
|
<string name="pref_last_catalogue_source_key">pref_last_catalogue_source_key</string>
|
||||||
|
@ -51,6 +51,7 @@
|
|||||||
<string name="action_cancel">Cancel</string>
|
<string name="action_cancel">Cancel</string>
|
||||||
<string name="action_sort">Sort</string>
|
<string name="action_sort">Sort</string>
|
||||||
<string name="action_force">Force refresh</string>
|
<string name="action_force">Force refresh</string>
|
||||||
|
<string name="action_install">Install</string>
|
||||||
|
|
||||||
<!-- Operations -->
|
<!-- Operations -->
|
||||||
<string name="deleting">Deleting…</string>
|
<string name="deleting">Deleting…</string>
|
||||||
@ -72,8 +73,6 @@
|
|||||||
<string name="landscape">Landscape</string>
|
<string name="landscape">Landscape</string>
|
||||||
<string name="default_columns">Default</string>
|
<string name="default_columns">Default</string>
|
||||||
<string name="pref_library_update_interval">Library update frequency</string>
|
<string name="pref_library_update_interval">Library update frequency</string>
|
||||||
<string name="pref_update_only_non_completed">Only update incomplete manga</string>
|
|
||||||
<string name="pref_update_only_when_charging">Only update when charging</string>
|
|
||||||
<string name="update_never">Manual</string>
|
<string name="update_never">Manual</string>
|
||||||
<string name="update_1hour">Hourly</string>
|
<string name="update_1hour">Hourly</string>
|
||||||
<string name="update_2hour">Every 2 hours</string>
|
<string name="update_2hour">Every 2 hours</string>
|
||||||
@ -82,13 +81,17 @@
|
|||||||
<string name="update_12hour">Every 12 hours</string>
|
<string name="update_12hour">Every 12 hours</string>
|
||||||
<string name="update_24hour">Daily</string>
|
<string name="update_24hour">Daily</string>
|
||||||
<string name="update_48hour">Every 2 days</string>
|
<string name="update_48hour">Every 2 days</string>
|
||||||
|
<string name="pref_library_update_restriction">Library update restrictions</string>
|
||||||
|
<string name="pref_library_update_restriction_summary">Update only when the conditions are met</string>
|
||||||
|
<string name="wifi">Wi-Fi</string>
|
||||||
|
<string name="charging">Charging</string>
|
||||||
|
<string name="pref_update_only_non_completed">Only update incomplete manga</string>
|
||||||
<string name="pref_auto_update_manga_sync">Sync chapters after reading</string>
|
<string name="pref_auto_update_manga_sync">Sync chapters after reading</string>
|
||||||
<string name="pref_ask_update_manga_sync">Confirm before updating</string>
|
<string name="pref_ask_update_manga_sync">Confirm before updating</string>
|
||||||
<string name="pref_theme">Application theme</string>
|
<string name="pref_theme">Application theme</string>
|
||||||
<string name="light_theme">Main theme</string>
|
<string name="light_theme">Main theme</string>
|
||||||
<string name="dark_theme">Dark theme</string>
|
<string name="dark_theme">Dark theme</string>
|
||||||
|
|
||||||
|
|
||||||
<!-- Reader section -->
|
<!-- Reader section -->
|
||||||
<string name="pref_hide_status_bar">Hide status bar</string>
|
<string name="pref_hide_status_bar">Hide status bar</string>
|
||||||
<string name="pref_lock_orientation">Lock orientation</string>
|
<string name="pref_lock_orientation">Lock orientation</string>
|
||||||
@ -97,6 +100,9 @@
|
|||||||
<string name="pref_custom_brightness">Use custom brightness</string>
|
<string name="pref_custom_brightness">Use custom brightness</string>
|
||||||
<string name="pref_seamless_mode">Seamless chapter transitions</string>
|
<string name="pref_seamless_mode">Seamless chapter transitions</string>
|
||||||
<string name="pref_keep_screen_on">Keep screen on</string>
|
<string name="pref_keep_screen_on">Keep screen on</string>
|
||||||
|
<string name="pref_reader_navigation">Navigation</string>
|
||||||
|
<string name="pref_read_with_volume_keys">Volume keys</string>
|
||||||
|
<string name="pref_read_with_tapping">Tapping</string>
|
||||||
<string name="pref_reader_theme">Background color</string>
|
<string name="pref_reader_theme">Background color</string>
|
||||||
<string name="white_background">White</string>
|
<string name="white_background">White</string>
|
||||||
<string name="black_background">Black</string>
|
<string name="black_background">Black</string>
|
||||||
@ -155,11 +161,16 @@
|
|||||||
<string name="pref_clear_database">Clear database</string>
|
<string name="pref_clear_database">Clear database</string>
|
||||||
<string name="pref_clear_database_summary">Delete manga and chapters that are not in your library</string>
|
<string name="pref_clear_database_summary">Delete manga and chapters that are not in your library</string>
|
||||||
<string name="clear_database_confirmation">Are you sure? Read chapters and progress of non-library manga will be lost</string>
|
<string name="clear_database_confirmation">Are you sure? Read chapters and progress of non-library manga will be lost</string>
|
||||||
|
<string name="pref_show_warning_message">Show warnings</string>
|
||||||
|
<string name="pref_show_warning_message_summary">Show warning messages during library sync </string>
|
||||||
|
<string name="pref_reencode">Reencode images</string>
|
||||||
|
<string name="pref_reencode_summary">Enable reencoding if images can\'t be decoded. Expect best results with Skia</string>
|
||||||
|
|
||||||
<!-- About section -->
|
<!-- About section -->
|
||||||
<string name="version">Version</string>
|
<string name="version">Version</string>
|
||||||
<string name="build_time">Build time</string>
|
<string name="build_time">Build time</string>
|
||||||
|
<string name="pref_enable_automatic_updates">Check for updates</string>
|
||||||
|
<string name="pref_enable_automatic_updates_summary">Automatically check for application updates</string>
|
||||||
<!-- ACRA -->
|
<!-- ACRA -->
|
||||||
<string name="pref_enable_acra">Send crash reports</string>
|
<string name="pref_enable_acra">Send crash reports</string>
|
||||||
<string name="pref_acra_summary">Helps fix any bugs. No sensitive data will be sent</string>
|
<string name="pref_acra_summary">Helps fix any bugs. No sensitive data will be sent</string>
|
||||||
@ -275,6 +286,13 @@
|
|||||||
<string name="update_check_download_started">Download started</string>
|
<string name="update_check_download_started">Download started</string>
|
||||||
<string name="update_check_look_for_updates">Looking for updates</string>
|
<string name="update_check_look_for_updates">Looking for updates</string>
|
||||||
|
|
||||||
|
<!--UpdateCheck Notifications-->
|
||||||
|
<string name="update_check_notification_file_download">Download update</string>
|
||||||
|
<string name="update_check_notification_download_in_progress">Download in progress</string>
|
||||||
|
<string name="update_check_notification_download_complete">Download complete</string>
|
||||||
|
<string name="update_check_notification_download_error">Download error</string>
|
||||||
|
<string name="update_check_notification_update_available">Update available</string>
|
||||||
|
|
||||||
<!--Content Description-->
|
<!--Content Description-->
|
||||||
<string name="description_backdrop">Backdrop image of selected manga</string>
|
<string name="description_backdrop">Backdrop image of selected manga</string>
|
||||||
<string name="description_cover">Cover of selected manga</string>
|
<string name="description_cover">Cover of selected manga</string>
|
||||||
|
@ -3,19 +3,26 @@
|
|||||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
<SwitchPreferenceCompat
|
<SwitchPreferenceCompat
|
||||||
|
android:defaultValue="true"
|
||||||
android:key="acra.enable"
|
android:key="acra.enable"
|
||||||
android:title="@string/pref_enable_acra"
|
|
||||||
android:summary="@string/pref_acra_summary"
|
android:summary="@string/pref_acra_summary"
|
||||||
android:defaultValue="true"/>
|
android:title="@string/pref_enable_acra"/>
|
||||||
|
|
||||||
|
<!--<SwitchPreferenceCompat-->
|
||||||
|
<!--android:defaultValue="false"-->
|
||||||
|
<!--android:enabled="false"-->
|
||||||
|
<!--android:key="@string/pref_enable_automatic_updates_key"-->
|
||||||
|
<!--android:summary="@string/pref_enable_automatic_updates_summary"-->
|
||||||
|
<!--android:title="@string/pref_enable_automatic_updates"/>-->
|
||||||
|
|
||||||
<Preference
|
<Preference
|
||||||
android:key="@string/pref_version"
|
android:key="@string/pref_version"
|
||||||
android:title="@string/version"
|
android:persistent="false"
|
||||||
android:persistent="false" />
|
android:title="@string/version"/>
|
||||||
|
|
||||||
<Preference
|
<Preference
|
||||||
android:key="@string/pref_build_time"
|
android:key="@string/pref_build_time"
|
||||||
android:title="@string/build_time"
|
android:persistent="false"
|
||||||
android:persistent="false" />
|
android:title="@string/build_time"/>
|
||||||
|
|
||||||
</android.support.v7.preference.PreferenceScreen>
|
</android.support.v7.preference.PreferenceScreen>
|
@ -3,12 +3,18 @@
|
|||||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
<Preference
|
<Preference
|
||||||
android:title="@string/pref_clear_chapter_cache"
|
android:key="@string/pref_clear_chapter_cache_key"
|
||||||
android:key="@string/pref_clear_chapter_cache_key" />
|
android:title="@string/pref_clear_chapter_cache"/>
|
||||||
|
|
||||||
<Preference
|
<Preference
|
||||||
android:title="@string/pref_clear_database"
|
|
||||||
android:key="@string/pref_clear_database_key"
|
android:key="@string/pref_clear_database_key"
|
||||||
android:summary="@string/pref_clear_database_summary"/>
|
android:summary="@string/pref_clear_database_summary"
|
||||||
|
android:title="@string/pref_clear_database"/>
|
||||||
|
|
||||||
|
<SwitchPreferenceCompat
|
||||||
|
android:defaultValue="false"
|
||||||
|
android:key="@string/pref_reencode_key"
|
||||||
|
android:summary="@string/pref_reencode_summary"
|
||||||
|
android:title="@string/pref_reencode"/>
|
||||||
|
|
||||||
</android.support.v7.preference.PreferenceScreen>
|
</android.support.v7.preference.PreferenceScreen>
|
@ -24,14 +24,16 @@
|
|||||||
android:summary="%s"
|
android:summary="%s"
|
||||||
android:title="@string/pref_library_update_interval"/>
|
android:title="@string/pref_library_update_interval"/>
|
||||||
|
|
||||||
|
<MultiSelectListPreference
|
||||||
|
android:entries="@array/library_update_restrictions"
|
||||||
|
android:entryValues="@array/library_update_restrictions_values"
|
||||||
|
android:key="@string/pref_library_update_restriction_key"
|
||||||
|
android:summary="@string/pref_library_update_restriction_summary"
|
||||||
|
android:title="@string/pref_library_update_restriction" />
|
||||||
|
|
||||||
<SwitchPreferenceCompat
|
<SwitchPreferenceCompat
|
||||||
android:defaultValue="false"
|
android:defaultValue="false"
|
||||||
android:key="@string/pref_update_only_non_completed_key"
|
android:key="@string/pref_update_only_non_completed_key"
|
||||||
android:title="@string/pref_update_only_non_completed"/>
|
android:title="@string/pref_update_only_non_completed"/>
|
||||||
|
|
||||||
<SwitchPreferenceCompat
|
|
||||||
android:defaultValue="false"
|
|
||||||
android:key="@string/pref_update_only_when_charging_key"
|
|
||||||
android:title="@string/pref_update_only_when_charging"/>
|
|
||||||
|
|
||||||
</android.support.v7.preference.PreferenceScreen>
|
</android.support.v7.preference.PreferenceScreen>
|
@ -2,30 +2,6 @@
|
|||||||
<android.support.v7.preference.PreferenceScreen
|
<android.support.v7.preference.PreferenceScreen
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
<SwitchPreferenceCompat android:title="@string/pref_hide_status_bar"
|
|
||||||
android:key="@string/pref_hide_status_bar_key"
|
|
||||||
android:defaultValue="true" />
|
|
||||||
|
|
||||||
<SwitchPreferenceCompat android:title="@string/pref_enable_transitions"
|
|
||||||
android:key="@string/pref_enable_transitions_key"
|
|
||||||
android:defaultValue="true" />
|
|
||||||
|
|
||||||
<SwitchPreferenceCompat android:title="@string/pref_show_page_number"
|
|
||||||
android:key="@string/pref_show_page_number_key"
|
|
||||||
android:defaultValue="true" />
|
|
||||||
|
|
||||||
<SwitchPreferenceCompat android:title="@string/pref_custom_brightness"
|
|
||||||
android:key="@string/pref_custom_brightness_key"
|
|
||||||
android:defaultValue="false" />
|
|
||||||
|
|
||||||
<SwitchPreferenceCompat android:title="@string/pref_keep_screen_on"
|
|
||||||
android:key="@string/pref_keep_screen_on_key"
|
|
||||||
android:defaultValue="true" />
|
|
||||||
|
|
||||||
<SwitchPreferenceCompat android:title="@string/pref_seamless_mode"
|
|
||||||
android:key="@string/pref_seamless_mode_key"
|
|
||||||
android:defaultValue="true" />
|
|
||||||
|
|
||||||
<eu.kanade.tachiyomi.widget.preference.IntListPreference
|
<eu.kanade.tachiyomi.widget.preference.IntListPreference
|
||||||
android:title="@string/pref_viewer_type"
|
android:title="@string/pref_viewer_type"
|
||||||
android:key="@string/pref_default_viewer_key"
|
android:key="@string/pref_default_viewer_key"
|
||||||
@ -74,4 +50,49 @@
|
|||||||
android:defaultValue="0"
|
android:defaultValue="0"
|
||||||
android:summary="%s" />
|
android:summary="%s" />
|
||||||
|
|
||||||
|
<SwitchPreferenceCompat
|
||||||
|
android:title="@string/pref_hide_status_bar"
|
||||||
|
android:key="@string/pref_hide_status_bar_key"
|
||||||
|
android:defaultValue="true" />
|
||||||
|
|
||||||
|
<SwitchPreferenceCompat
|
||||||
|
android:title="@string/pref_enable_transitions"
|
||||||
|
android:key="@string/pref_enable_transitions_key"
|
||||||
|
android:defaultValue="true" />
|
||||||
|
|
||||||
|
<SwitchPreferenceCompat
|
||||||
|
android:title="@string/pref_show_page_number"
|
||||||
|
android:key="@string/pref_show_page_number_key"
|
||||||
|
android:defaultValue="true" />
|
||||||
|
|
||||||
|
<SwitchPreferenceCompat
|
||||||
|
android:title="@string/pref_custom_brightness"
|
||||||
|
android:key="@string/pref_custom_brightness_key"
|
||||||
|
android:defaultValue="false" />
|
||||||
|
|
||||||
|
<SwitchPreferenceCompat
|
||||||
|
android:title="@string/pref_keep_screen_on"
|
||||||
|
android:key="@string/pref_keep_screen_on_key"
|
||||||
|
android:defaultValue="true" />
|
||||||
|
|
||||||
|
<SwitchPreferenceCompat
|
||||||
|
android:title="@string/pref_seamless_mode"
|
||||||
|
android:key="@string/pref_seamless_mode_key"
|
||||||
|
android:defaultValue="true" />
|
||||||
|
|
||||||
|
<PreferenceCategory
|
||||||
|
android:title="@string/pref_reader_navigation">
|
||||||
|
|
||||||
|
<SwitchPreferenceCompat
|
||||||
|
android:title="@string/pref_read_with_tapping"
|
||||||
|
android:key="@string/pref_read_with_tapping_key"
|
||||||
|
android:defaultValue="true" />
|
||||||
|
|
||||||
|
<SwitchPreferenceCompat
|
||||||
|
android:title="@string/pref_read_with_volume_keys"
|
||||||
|
android:key="@string/pref_read_with_volume_keys_key"
|
||||||
|
android:defaultValue="false" />
|
||||||
|
|
||||||
|
</PreferenceCategory>
|
||||||
|
|
||||||
</android.support.v7.preference.PreferenceScreen>
|
</android.support.v7.preference.PreferenceScreen>
|
@ -101,7 +101,7 @@ public class LibraryUpdateAlarmTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testLibraryUpdateServiceIsStartedWhenUpdateIntentIsReceived() {
|
public void testLibraryUpdateServiceIsStartedWhenUpdateIntentIsReceived() {
|
||||||
Intent intent = new Intent(context, LibraryUpdateService.class);
|
Intent intent = new Intent(context, LibraryUpdateService.class);
|
||||||
intent.putExtra("is_forced", false);
|
intent.putExtra("is_manual", false);
|
||||||
assertThat(app.getNextStartedService()).isNotEqualTo(intent);
|
assertThat(app.getNextStartedService()).isNotEqualTo(intent);
|
||||||
|
|
||||||
LibraryUpdateAlarm alarm = new LibraryUpdateAlarm();
|
LibraryUpdateAlarm alarm = new LibraryUpdateAlarm();
|
||||||
|
Reference in New Issue
Block a user