mirror of
https://github.com/mihonapp/mihon.git
synced 2025-07-01 21:47:50 +02:00
Compare commits
13 Commits
Author | SHA1 | Date | |
---|---|---|---|
634356e72f | |||
6d3cc16ab1 | |||
6027671c09 | |||
29d0cb4a15 | |||
fe7001975a | |||
ac88f1c146 | |||
b5b86218c5 | |||
bdcc6e52e6 | |||
0eae817aa6 | |||
8994b42760 | |||
6a63ce992a | |||
9ae6285eef | |||
8f9737f567 |
BIN
.github/readme-images/app-icon.png
vendored
Normal file
BIN
.github/readme-images/app-icon.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.7 KiB |
BIN
.github/readme-images/screens.png
vendored
Normal file
BIN
.github/readme-images/screens.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 1022 KiB |
39
README.md
39
README.md
@ -1,25 +1,38 @@
|
|||||||
| Build | Download | F-Droid | Contribute | Contact |
|
| Build | Download | F-Droid | Contribute | Contact |
|
||||||
|-------|----------|---------|------------|---------|
|
|-------|----------|---------|------------|---------|
|
||||||
| [](https://travis-ci.org/inorichi/tachiyomi) | [](https://github.com/inorichi/tachiyomi/releases) [](http://tachiyomi.kanade.eu/latest) | [](https://f-droid.org/repository/browse/?fdid=eu.kanade.tachiyomi) [](//github.com/inorichi/tachiyomi/wiki/FDroid-for-dev-versions) | [](https://github.com/inorichi/tachiyomi/wiki/Translation) | [](https://discord.gg/WrBkRk4) |
|
| [](https://travis-ci.org/inorichi/tachiyomi) | [](https://github.com/inorichi/tachiyomi/releases) [](http://tachiyomi.kanade.eu/latest) | [](https://f-droid.org/repository/browse/?fdid=eu.kanade.tachiyomi) [](//github.com/inorichi/tachiyomi/wiki/FDroid-for-dev-versions) | [](https://github.com/inorichi/tachiyomi/wiki/Translation) | [](https://discord.gg/2dDQBv2) |
|
||||||
|
|
||||||
### **Contact us on [Discord](https://discord.gg/WrBkRk4)**
|
|
||||||
If you want to open an issue, please read [contributing guidelines](https://github.com/inorichi/tachiyomi/blob/master/.github/CONTRIBUTING.md). Your issue may be closed otherwise.
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
|
# Tachiyomi
|
||||||
Tachiyomi is a free and open source manga reader for Android.
|
Tachiyomi is a free and open source manga reader for Android.
|
||||||
|
|
||||||
Keep in mind it's still a beta, so expect it to crash sometimes.
|

|
||||||
|
|
||||||
# Features
|
## Features
|
||||||
|
|
||||||
* Online and offline reading
|
Features include:
|
||||||
* Configurable reader with multiple viewers and settings
|
* Online reading from sources like Batoto, KissManga, MangaFox, [and more](https://github.com/inorichi/tachiyomi-extensions)
|
||||||
* MyAnimeList support
|
* Local reading of downloaded manga
|
||||||
* Resume from the next unread chapter
|
* Configurable reader with multiple viewers, reading directions and other settings
|
||||||
* Chapter filtering
|
* MyAnimeList, AniList, and Kitsu support
|
||||||
* Schedule searching for updates
|
|
||||||
* Categories to organize your library
|
* Categories to organize your library
|
||||||
|
* Light and dark themes
|
||||||
|
* Schedule updating your library for new chapters
|
||||||
|
* Create backups locally or to your cloud service of choice
|
||||||
|
|
||||||
|
## Download
|
||||||
|
Get the app from our [releases page](https://github.com/inorichi/tachiyomi/releases) or from [f-droid](https://f-droid.org/packages/eu.kanade.tachiyomi/).
|
||||||
|
|
||||||
|
If you want to try new features before they get to the stable release, you can download the dev version [here](http://tachiyomi.kanade.eu/latest) (auto-updates not included), or add our [F-Droid repo](https://github.com/inorichi/tachiyomi/wiki/FDroid-for-dev-versions).
|
||||||
|
|
||||||
|
## Issues, Feature Requests and Contributing
|
||||||
|
|
||||||
|
If you want to open an issue, please read [contributing guidelines](https://github.com/inorichi/tachiyomi/blob/master/.github/CONTRIBUTING.md). Your issue may be closed otherwise.
|
||||||
|
|
||||||
|
## FAQ
|
||||||
|
|
||||||
|
[See our wiki.](https://github.com/inorichi/tachiyomi/wiki/FAQ)
|
||||||
|
You can also reach out to us on [Discord](https://discord.gg/WrBkRk4).
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
@ -38,8 +38,8 @@ android {
|
|||||||
minSdkVersion 16
|
minSdkVersion 16
|
||||||
targetSdkVersion 26
|
targetSdkVersion 26
|
||||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||||
versionCode 29
|
versionCode 30
|
||||||
versionName "0.6.6"
|
versionName "0.6.7"
|
||||||
|
|
||||||
buildConfigField "String", "COMMIT_COUNT", "\"${getCommitCount()}\""
|
buildConfigField "String", "COMMIT_COUNT", "\"${getCommitCount()}\""
|
||||||
buildConfigField "String", "COMMIT_SHA", "\"${getGitSha()}\""
|
buildConfigField "String", "COMMIT_SHA", "\"${getGitSha()}\""
|
||||||
|
@ -86,7 +86,7 @@
|
|||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name=".data.updater.UpdateDownloaderService"
|
android:name=".data.updater.UpdaterService"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
<service
|
<service
|
||||||
|
@ -8,7 +8,7 @@ import com.evernote.android.job.JobManager
|
|||||||
import eu.kanade.tachiyomi.data.backup.BackupCreatorJob
|
import eu.kanade.tachiyomi.data.backup.BackupCreatorJob
|
||||||
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
|
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
|
||||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||||
import eu.kanade.tachiyomi.data.updater.UpdateCheckerJob
|
import eu.kanade.tachiyomi.data.updater.UpdaterJob
|
||||||
import eu.kanade.tachiyomi.util.LocaleHelper
|
import eu.kanade.tachiyomi.util.LocaleHelper
|
||||||
import org.acra.ACRA
|
import org.acra.ACRA
|
||||||
import org.acra.annotation.ReportsCrashes
|
import org.acra.annotation.ReportsCrashes
|
||||||
@ -28,11 +28,11 @@ open class App : Application() {
|
|||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
|
if (BuildConfig.DEBUG) Timber.plant(Timber.DebugTree())
|
||||||
|
|
||||||
Injekt = InjektScope(DefaultRegistrar())
|
Injekt = InjektScope(DefaultRegistrar())
|
||||||
Injekt.importModule(AppModule(this))
|
Injekt.importModule(AppModule(this))
|
||||||
|
|
||||||
if (BuildConfig.DEBUG) Timber.plant(Timber.DebugTree())
|
|
||||||
|
|
||||||
setupAcra()
|
setupAcra()
|
||||||
setupJobManager()
|
setupJobManager()
|
||||||
setupNotificationChannels()
|
setupNotificationChannels()
|
||||||
@ -60,7 +60,7 @@ open class App : Application() {
|
|||||||
JobManager.create(this).addJobCreator { tag ->
|
JobManager.create(this).addJobCreator { tag ->
|
||||||
when (tag) {
|
when (tag) {
|
||||||
LibraryUpdateJob.TAG -> LibraryUpdateJob()
|
LibraryUpdateJob.TAG -> LibraryUpdateJob()
|
||||||
UpdateCheckerJob.TAG -> UpdateCheckerJob()
|
UpdaterJob.TAG -> UpdaterJob()
|
||||||
BackupCreatorJob.TAG -> BackupCreatorJob()
|
BackupCreatorJob.TAG -> BackupCreatorJob()
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ package eu.kanade.tachiyomi
|
|||||||
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
|
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
||||||
import eu.kanade.tachiyomi.data.updater.UpdateCheckerJob
|
import eu.kanade.tachiyomi.data.updater.UpdaterJob
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
object Migrations {
|
object Migrations {
|
||||||
@ -25,7 +25,7 @@ object Migrations {
|
|||||||
if (oldVersion < 14) {
|
if (oldVersion < 14) {
|
||||||
// Restore jobs after upgrading to evernote's job scheduler.
|
// Restore jobs after upgrading to evernote's job scheduler.
|
||||||
if (BuildConfig.INCLUDE_UPDATER && preferences.automaticUpdates()) {
|
if (BuildConfig.INCLUDE_UPDATER && preferences.automaticUpdates()) {
|
||||||
UpdateCheckerJob.setupTask()
|
UpdaterJob.setupTask()
|
||||||
}
|
}
|
||||||
LibraryUpdateJob.setupTask()
|
LibraryUpdateJob.setupTask()
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.data.notification
|
|||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||||
import eu.kanade.tachiyomi.util.getUriCompat
|
import eu.kanade.tachiyomi.util.getUriCompat
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@ -43,11 +44,10 @@ object NotificationHandler {
|
|||||||
* Returns [PendingIntent] that prompts user with apk install intent
|
* Returns [PendingIntent] that prompts user with apk install intent
|
||||||
*
|
*
|
||||||
* @param context context
|
* @param context context
|
||||||
* @param file file of apk that is installed
|
* @param uri uri of apk that is installed
|
||||||
*/
|
*/
|
||||||
fun installApkPendingActivity(context: Context, file: File): PendingIntent {
|
fun installApkPendingActivity(context: Context, uri: Uri): PendingIntent {
|
||||||
val intent = Intent(Intent.ACTION_VIEW).apply {
|
val intent = Intent(Intent.ACTION_VIEW).apply {
|
||||||
val uri = file.getUriCompat(context)
|
|
||||||
setDataAndType(uri, "application/vnd.android.package-archive")
|
setDataAndType(uri, "application/vnd.android.package-archive")
|
||||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION
|
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ import com.google.gson.annotations.SerializedName
|
|||||||
*/
|
*/
|
||||||
class GithubRelease(@SerializedName("tag_name") val version: String,
|
class GithubRelease(@SerializedName("tag_name") val version: String,
|
||||||
@SerializedName("body") val changeLog: String,
|
@SerializedName("body") val changeLog: String,
|
||||||
@SerializedName("assets") val assets: List<Assets>) {
|
@SerializedName("assets") private val assets: List<Assets>) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get download link of latest release from the assets.
|
* Get download link of latest release from the assets.
|
||||||
|
@ -3,7 +3,7 @@ package eu.kanade.tachiyomi.data.updater
|
|||||||
import eu.kanade.tachiyomi.BuildConfig
|
import eu.kanade.tachiyomi.BuildConfig
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
|
|
||||||
class GithubUpdateChecker() {
|
class GithubUpdateChecker {
|
||||||
|
|
||||||
private val service: GithubService = GithubService.create()
|
private val service: GithubService = GithubService.create()
|
||||||
|
|
||||||
|
@ -3,5 +3,5 @@ package eu.kanade.tachiyomi.data.updater
|
|||||||
sealed class GithubUpdateResult {
|
sealed class GithubUpdateResult {
|
||||||
|
|
||||||
class NewUpdate(val release: GithubRelease): GithubUpdateResult()
|
class NewUpdate(val release: GithubRelease): GithubUpdateResult()
|
||||||
class NoNewUpdate(): GithubUpdateResult()
|
class NoNewUpdate : GithubUpdateResult()
|
||||||
}
|
}
|
@ -1,147 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.updater
|
|
||||||
|
|
||||||
import android.content.BroadcastReceiver
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import android.support.v4.app.NotificationCompat
|
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.data.notification.NotificationHandler
|
|
||||||
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
|
|
||||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
|
||||||
import eu.kanade.tachiyomi.util.notificationManager
|
|
||||||
import java.io.File
|
|
||||||
import eu.kanade.tachiyomi.BuildConfig.APPLICATION_ID as ID
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Local [BroadcastReceiver] that runs on UI thread
|
|
||||||
* Notification calls from [UpdateDownloaderService] should be made from here.
|
|
||||||
*/
|
|
||||||
internal class UpdateDownloaderReceiver(val context: Context) : BroadcastReceiver() {
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val NAME = "UpdateDownloaderReceiver"
|
|
||||||
|
|
||||||
// Called to show initial notification.
|
|
||||||
internal const val NOTIFICATION_UPDATER_INITIAL = "$ID.$NAME.UPDATER_INITIAL"
|
|
||||||
|
|
||||||
// Called to show progress notification.
|
|
||||||
internal const val NOTIFICATION_UPDATER_PROGRESS = "$ID.$NAME.UPDATER_PROGRESS"
|
|
||||||
|
|
||||||
// Called to show install notification.
|
|
||||||
internal const val NOTIFICATION_UPDATER_INSTALL = "$ID.$NAME.UPDATER_INSTALL"
|
|
||||||
|
|
||||||
// Called to show error notification
|
|
||||||
internal const val NOTIFICATION_UPDATER_ERROR = "$ID.$NAME.UPDATER_ERROR"
|
|
||||||
|
|
||||||
// Value containing action of BroadcastReceiver
|
|
||||||
internal const val EXTRA_ACTION = "$ID.$NAME.ACTION"
|
|
||||||
|
|
||||||
// Value containing progress
|
|
||||||
internal const val EXTRA_PROGRESS = "$ID.$NAME.PROGRESS"
|
|
||||||
|
|
||||||
// Value containing apk path
|
|
||||||
internal const val EXTRA_APK_PATH = "$ID.$NAME.APK_PATH"
|
|
||||||
|
|
||||||
// Value containing apk url
|
|
||||||
internal const val EXTRA_APK_URL = "$ID.$NAME.APK_URL"
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Notification shown to user
|
|
||||||
*/
|
|
||||||
private val notification = NotificationCompat.Builder(context, Notifications.CHANNEL_COMMON)
|
|
||||||
|
|
||||||
override fun onReceive(context: Context, intent: Intent) {
|
|
||||||
when (intent.getStringExtra(EXTRA_ACTION)) {
|
|
||||||
NOTIFICATION_UPDATER_INITIAL -> basicNotification()
|
|
||||||
NOTIFICATION_UPDATER_PROGRESS -> updateProgress(intent.getIntExtra(EXTRA_PROGRESS, 0))
|
|
||||||
NOTIFICATION_UPDATER_INSTALL -> installNotification(intent.getStringExtra(EXTRA_APK_PATH))
|
|
||||||
NOTIFICATION_UPDATER_ERROR -> errorNotification(intent.getStringExtra(EXTRA_APK_URL))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called to show basic notification
|
|
||||||
*/
|
|
||||||
private fun basicNotification() {
|
|
||||||
// Create notification
|
|
||||||
with(notification) {
|
|
||||||
setContentTitle(context.getString(R.string.app_name))
|
|
||||||
setContentText(context.getString(R.string.update_check_notification_download_in_progress))
|
|
||||||
setSmallIcon(android.R.drawable.stat_sys_download)
|
|
||||||
setOngoing(true)
|
|
||||||
}
|
|
||||||
notification.show()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called to show progress notification
|
|
||||||
*
|
|
||||||
* @param progress progress of download
|
|
||||||
*/
|
|
||||||
private fun updateProgress(progress: Int) {
|
|
||||||
with(notification) {
|
|
||||||
setProgress(100, progress, false)
|
|
||||||
setOnlyAlertOnce(true)
|
|
||||||
}
|
|
||||||
notification.show()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called to show install notification
|
|
||||||
*
|
|
||||||
* @param path path of file
|
|
||||||
*/
|
|
||||||
private fun installNotification(path: String) {
|
|
||||||
// Prompt the user to install the new update.
|
|
||||||
with(notification) {
|
|
||||||
setContentText(context.getString(R.string.update_check_notification_download_complete))
|
|
||||||
setSmallIcon(android.R.drawable.stat_sys_download_done)
|
|
||||||
setOnlyAlertOnce(false)
|
|
||||||
setProgress(0, 0, false)
|
|
||||||
// Install action
|
|
||||||
setContentIntent(NotificationHandler.installApkPendingActivity(context, File(path)))
|
|
||||||
addAction(R.drawable.ic_system_update_grey_24dp_img,
|
|
||||||
context.getString(R.string.action_install),
|
|
||||||
NotificationHandler.installApkPendingActivity(context, File(path)))
|
|
||||||
// Cancel action
|
|
||||||
addAction(R.drawable.ic_clear_grey_24dp_img,
|
|
||||||
context.getString(R.string.action_cancel),
|
|
||||||
NotificationReceiver.dismissNotificationPendingBroadcast(context, Notifications.ID_UPDATER))
|
|
||||||
}
|
|
||||||
notification.show()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called to show error notification
|
|
||||||
*
|
|
||||||
* @param url url of apk
|
|
||||||
*/
|
|
||||||
private fun errorNotification(url: String) {
|
|
||||||
// Prompt the user to retry the download.
|
|
||||||
with(notification) {
|
|
||||||
setContentText(context.getString(R.string.update_check_notification_download_error))
|
|
||||||
setSmallIcon(android.R.drawable.stat_sys_warning)
|
|
||||||
setOnlyAlertOnce(false)
|
|
||||||
setProgress(0, 0, false)
|
|
||||||
// Retry action
|
|
||||||
addAction(R.drawable.ic_refresh_grey_24dp_img,
|
|
||||||
context.getString(R.string.action_retry),
|
|
||||||
UpdateDownloaderService.downloadApkPendingService(context, url))
|
|
||||||
// Cancel action
|
|
||||||
addAction(R.drawable.ic_clear_grey_24dp_img,
|
|
||||||
context.getString(R.string.action_cancel),
|
|
||||||
NotificationReceiver.dismissNotificationPendingBroadcast(context, Notifications.ID_UPDATER))
|
|
||||||
}
|
|
||||||
notification.show()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shows a notification from this builder.
|
|
||||||
*
|
|
||||||
* @param id the id of the notification.
|
|
||||||
*/
|
|
||||||
private fun NotificationCompat.Builder.show(id: Int = Notifications.ID_UPDATER) {
|
|
||||||
context.notificationManager.notify(id, build())
|
|
||||||
}
|
|
||||||
}
|
|
@ -10,7 +10,7 @@ import eu.kanade.tachiyomi.R
|
|||||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||||
import eu.kanade.tachiyomi.util.notificationManager
|
import eu.kanade.tachiyomi.util.notificationManager
|
||||||
|
|
||||||
class UpdateCheckerJob : Job() {
|
class UpdaterJob : Job() {
|
||||||
|
|
||||||
override fun onRunJob(params: Params): Result {
|
override fun onRunJob(params: Params): Result {
|
||||||
return GithubUpdateChecker()
|
return GithubUpdateChecker()
|
||||||
@ -19,8 +19,8 @@ class UpdateCheckerJob : Job() {
|
|||||||
if (result is GithubUpdateResult.NewUpdate) {
|
if (result is GithubUpdateResult.NewUpdate) {
|
||||||
val url = result.release.downloadLink
|
val url = result.release.downloadLink
|
||||||
|
|
||||||
val intent = Intent(context, UpdateDownloaderService::class.java).apply {
|
val intent = Intent(context, UpdaterService::class.java).apply {
|
||||||
putExtra(UpdateDownloaderService.EXTRA_DOWNLOAD_URL, url)
|
putExtra(UpdaterService.EXTRA_DOWNLOAD_URL, url)
|
||||||
}
|
}
|
||||||
|
|
||||||
NotificationCompat.Builder(context, Notifications.CHANNEL_COMMON).update {
|
NotificationCompat.Builder(context, Notifications.CHANNEL_COMMON).update {
|
@ -0,0 +1,109 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.updater
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.Uri
|
||||||
|
import android.support.v4.app.NotificationCompat
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.data.notification.NotificationHandler
|
||||||
|
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
|
||||||
|
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||||
|
import eu.kanade.tachiyomi.util.notificationManager
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DownloadNotifier is used to show notifications when downloading and update.
|
||||||
|
*
|
||||||
|
* @param context context of application.
|
||||||
|
*/
|
||||||
|
internal class UpdaterNotifier(private val context: Context) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builder to manage notifications.
|
||||||
|
*/
|
||||||
|
private val notification by lazy {
|
||||||
|
NotificationCompat.Builder(context, Notifications.CHANNEL_COMMON)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call to show notification.
|
||||||
|
*
|
||||||
|
* @param id id of the notification channel.
|
||||||
|
*/
|
||||||
|
private fun NotificationCompat.Builder.show(id: Int = Notifications.ID_UPDATER) {
|
||||||
|
context.notificationManager.notify(id, build())
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call when apk download starts.
|
||||||
|
*
|
||||||
|
* @param title tile of notification.
|
||||||
|
*/
|
||||||
|
fun onDownloadStarted(title: String) {
|
||||||
|
with(notification) {
|
||||||
|
setContentTitle(title)
|
||||||
|
setContentText(context.getString(R.string.update_check_notification_download_in_progress))
|
||||||
|
setSmallIcon(android.R.drawable.stat_sys_download)
|
||||||
|
setOngoing(true)
|
||||||
|
}
|
||||||
|
notification.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call when apk download progress changes.
|
||||||
|
*
|
||||||
|
* @param progress progress of download (xx%/100).
|
||||||
|
*/
|
||||||
|
fun onProgressChange(progress: Int) {
|
||||||
|
with(notification) {
|
||||||
|
setProgress(100, progress, false)
|
||||||
|
setOnlyAlertOnce(true)
|
||||||
|
}
|
||||||
|
notification.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call when apk download is finished.
|
||||||
|
*
|
||||||
|
* @param uri path location of apk.
|
||||||
|
*/
|
||||||
|
fun onDownloadFinished(uri: Uri) {
|
||||||
|
with(notification) {
|
||||||
|
setContentText(context.getString(R.string.update_check_notification_download_complete))
|
||||||
|
setSmallIcon(android.R.drawable.stat_sys_download_done)
|
||||||
|
setOnlyAlertOnce(false)
|
||||||
|
setProgress(0, 0, false)
|
||||||
|
// Install action
|
||||||
|
setContentIntent(NotificationHandler.installApkPendingActivity(context, uri))
|
||||||
|
addAction(R.drawable.ic_system_update_grey_24dp_img,
|
||||||
|
context.getString(R.string.action_install),
|
||||||
|
NotificationHandler.installApkPendingActivity(context, uri))
|
||||||
|
// Cancel action
|
||||||
|
addAction(R.drawable.ic_clear_grey_24dp_img,
|
||||||
|
context.getString(R.string.action_cancel),
|
||||||
|
NotificationReceiver.dismissNotificationPendingBroadcast(context, Notifications.ID_UPDATER))
|
||||||
|
}
|
||||||
|
notification.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call when apk download throws a error
|
||||||
|
*
|
||||||
|
* @param url web location of apk to download.
|
||||||
|
*/
|
||||||
|
fun onDownloadError(url: String) {
|
||||||
|
with(notification) {
|
||||||
|
setContentText(context.getString(R.string.update_check_notification_download_error))
|
||||||
|
setSmallIcon(android.R.drawable.stat_sys_warning)
|
||||||
|
setOnlyAlertOnce(false)
|
||||||
|
setProgress(0, 0, false)
|
||||||
|
// Retry action
|
||||||
|
addAction(R.drawable.ic_refresh_grey_24dp_img,
|
||||||
|
context.getString(R.string.action_retry),
|
||||||
|
UpdaterService.downloadApkPendingService(context, url))
|
||||||
|
// Cancel action
|
||||||
|
addAction(R.drawable.ic_clear_grey_24dp_img,
|
||||||
|
context.getString(R.string.action_cancel),
|
||||||
|
NotificationReceiver.dismissNotificationPendingBroadcast(context, Notifications.ID_UPDATER))
|
||||||
|
}
|
||||||
|
notification.show(Notifications.ID_UPDATER)
|
||||||
|
}
|
||||||
|
}
|
@ -2,52 +2,37 @@ package eu.kanade.tachiyomi.data.updater
|
|||||||
|
|
||||||
import android.app.IntentService
|
import android.app.IntentService
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.content.BroadcastReceiver
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.IntentFilter
|
|
||||||
import eu.kanade.tachiyomi.BuildConfig
|
import eu.kanade.tachiyomi.BuildConfig
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.network.GET
|
import eu.kanade.tachiyomi.network.GET
|
||||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||||
import eu.kanade.tachiyomi.network.ProgressListener
|
import eu.kanade.tachiyomi.network.ProgressListener
|
||||||
import eu.kanade.tachiyomi.network.newCallWithProgress
|
import eu.kanade.tachiyomi.network.newCallWithProgress
|
||||||
import eu.kanade.tachiyomi.util.registerLocalReceiver
|
import eu.kanade.tachiyomi.util.getUriCompat
|
||||||
import eu.kanade.tachiyomi.util.saveTo
|
import eu.kanade.tachiyomi.util.saveTo
|
||||||
import eu.kanade.tachiyomi.util.sendLocalBroadcastSync
|
|
||||||
import eu.kanade.tachiyomi.util.unregisterLocalReceiver
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
class UpdateDownloaderService : IntentService(UpdateDownloaderService::class.java.name) {
|
class UpdaterService : IntentService(UpdaterService::class.java.name) {
|
||||||
/**
|
/**
|
||||||
* Network helper
|
* Network helper
|
||||||
*/
|
*/
|
||||||
private val network: NetworkHelper by injectLazy()
|
private val network: NetworkHelper by injectLazy()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Local [BroadcastReceiver] that runs on UI thread
|
* Notifier for the updater state and progress.
|
||||||
*/
|
*/
|
||||||
private val updaterNotificationReceiver = UpdateDownloaderReceiver(this)
|
private val notifier by lazy { UpdaterNotifier(this) }
|
||||||
|
|
||||||
|
|
||||||
override fun onCreate() {
|
|
||||||
super.onCreate()
|
|
||||||
// Register receiver
|
|
||||||
registerLocalReceiver(updaterNotificationReceiver, IntentFilter(INTENT_FILTER_NAME))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroy() {
|
|
||||||
// Unregister receiver
|
|
||||||
unregisterLocalReceiver(updaterNotificationReceiver)
|
|
||||||
super.onDestroy()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onHandleIntent(intent: Intent?) {
|
override fun onHandleIntent(intent: Intent?) {
|
||||||
if (intent == null) return
|
if (intent == null) return
|
||||||
|
|
||||||
|
val title = intent.getStringExtra(EXTRA_DOWNLOAD_TITLE) ?: getString(R.string.app_name)
|
||||||
val url = intent.getStringExtra(EXTRA_DOWNLOAD_URL) ?: return
|
val url = intent.getStringExtra(EXTRA_DOWNLOAD_URL) ?: return
|
||||||
downloadApk(url)
|
downloadApk(title, url)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -55,9 +40,9 @@ class UpdateDownloaderService : IntentService(UpdateDownloaderService::class.jav
|
|||||||
*
|
*
|
||||||
* @param url url location of file
|
* @param url url location of file
|
||||||
*/
|
*/
|
||||||
fun downloadApk(url: String) {
|
private fun downloadApk(title: String, url: String) {
|
||||||
// Show notification download starting.
|
// Show notification download starting.
|
||||||
sendInitialBroadcast()
|
notifier.onDownloadStarted(title)
|
||||||
|
|
||||||
val progressListener = object : ProgressListener {
|
val progressListener = object : ProgressListener {
|
||||||
|
|
||||||
@ -73,7 +58,7 @@ class UpdateDownloaderService : IntentService(UpdateDownloaderService::class.jav
|
|||||||
if (progress > savedProgress && currentTime - 200 > lastTick) {
|
if (progress > savedProgress && currentTime - 200 > lastTick) {
|
||||||
savedProgress = progress
|
savedProgress = progress
|
||||||
lastTick = currentTime
|
lastTick = currentTime
|
||||||
sendProgressBroadcast(progress)
|
notifier.onProgressChange(progress)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -91,80 +76,32 @@ class UpdateDownloaderService : IntentService(UpdateDownloaderService::class.jav
|
|||||||
response.close()
|
response.close()
|
||||||
throw Exception("Unsuccessful response")
|
throw Exception("Unsuccessful response")
|
||||||
}
|
}
|
||||||
sendInstallBroadcast(apkFile.absolutePath)
|
notifier.onDownloadFinished(apkFile.getUriCompat(this))
|
||||||
} catch (error: Exception) {
|
} catch (error: Exception) {
|
||||||
Timber.e(error)
|
Timber.e(error)
|
||||||
sendErrorBroadcast(url)
|
notifier.onDownloadError(url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Show notification download starting.
|
|
||||||
*/
|
|
||||||
private fun sendInitialBroadcast() {
|
|
||||||
val intent = Intent(INTENT_FILTER_NAME).apply {
|
|
||||||
putExtra(UpdateDownloaderReceiver.EXTRA_ACTION, UpdateDownloaderReceiver.NOTIFICATION_UPDATER_INITIAL)
|
|
||||||
}
|
|
||||||
sendLocalBroadcastSync(intent)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show notification progress changed
|
|
||||||
*
|
|
||||||
* @param progress progress of download
|
|
||||||
*/
|
|
||||||
private fun sendProgressBroadcast(progress: Int) {
|
|
||||||
val intent = Intent(INTENT_FILTER_NAME).apply {
|
|
||||||
putExtra(UpdateDownloaderReceiver.EXTRA_ACTION, UpdateDownloaderReceiver.NOTIFICATION_UPDATER_PROGRESS)
|
|
||||||
putExtra(UpdateDownloaderReceiver.EXTRA_PROGRESS, progress)
|
|
||||||
}
|
|
||||||
sendLocalBroadcastSync(intent)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show install notification.
|
|
||||||
*
|
|
||||||
* @param path location of file
|
|
||||||
*/
|
|
||||||
private fun sendInstallBroadcast(path: String){
|
|
||||||
val intent = Intent(INTENT_FILTER_NAME).apply {
|
|
||||||
putExtra(UpdateDownloaderReceiver.EXTRA_ACTION, UpdateDownloaderReceiver.NOTIFICATION_UPDATER_INSTALL)
|
|
||||||
putExtra(UpdateDownloaderReceiver.EXTRA_APK_PATH, path)
|
|
||||||
}
|
|
||||||
sendLocalBroadcastSync(intent)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show error notification.
|
|
||||||
*
|
|
||||||
* @param url url of file
|
|
||||||
*/
|
|
||||||
private fun sendErrorBroadcast(url: String){
|
|
||||||
val intent = Intent(INTENT_FILTER_NAME).apply {
|
|
||||||
putExtra(UpdateDownloaderReceiver.EXTRA_ACTION, UpdateDownloaderReceiver.NOTIFICATION_UPDATER_ERROR)
|
|
||||||
putExtra(UpdateDownloaderReceiver.EXTRA_APK_URL, url)
|
|
||||||
}
|
|
||||||
sendLocalBroadcastSync(intent)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
/**
|
|
||||||
* Name of Local BroadCastReceiver.
|
|
||||||
*/
|
|
||||||
private val INTENT_FILTER_NAME = UpdateDownloaderService::class.java.name
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Download url.
|
* Download url.
|
||||||
*/
|
*/
|
||||||
internal const val EXTRA_DOWNLOAD_URL = "${BuildConfig.APPLICATION_ID}.UpdateDownloaderService.DOWNLOAD_URL"
|
internal const val EXTRA_DOWNLOAD_URL = "${BuildConfig.APPLICATION_ID}.UpdaterService.DOWNLOAD_URL"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Download title
|
||||||
|
*/
|
||||||
|
internal const val EXTRA_DOWNLOAD_TITLE = "${BuildConfig.APPLICATION_ID}.UpdaterService.DOWNLOAD_TITLE"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Downloads a new update and let the user install the new version from a notification.
|
* Downloads a new update and let the user install the new version from a notification.
|
||||||
* @param context the application context.
|
* @param context the application context.
|
||||||
* @param url the url to the new update.
|
* @param url the url to the new update.
|
||||||
*/
|
*/
|
||||||
fun downloadUpdate(context: Context, url: String) {
|
fun downloadUpdate(context: Context, url: String, title: String = context.getString(R.string.app_name)) {
|
||||||
val intent = Intent(context, UpdateDownloaderService::class.java).apply {
|
val intent = Intent(context, UpdaterService::class.java).apply {
|
||||||
|
putExtra(EXTRA_DOWNLOAD_TITLE, title)
|
||||||
putExtra(EXTRA_DOWNLOAD_URL, url)
|
putExtra(EXTRA_DOWNLOAD_URL, url)
|
||||||
}
|
}
|
||||||
context.startService(intent)
|
context.startService(intent)
|
||||||
@ -177,7 +114,7 @@ class UpdateDownloaderService : IntentService(UpdateDownloaderService::class.jav
|
|||||||
* @return [PendingIntent]
|
* @return [PendingIntent]
|
||||||
*/
|
*/
|
||||||
internal fun downloadApkPendingService(context: Context, url: String): PendingIntent {
|
internal fun downloadApkPendingService(context: Context, url: String): PendingIntent {
|
||||||
val intent = Intent(context, UpdateDownloaderService::class.java).apply {
|
val intent = Intent(context, UpdaterService::class.java).apply {
|
||||||
putExtra(EXTRA_DOWNLOAD_URL, url)
|
putExtra(EXTRA_DOWNLOAD_URL, url)
|
||||||
}
|
}
|
||||||
return PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
|
return PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
|
@ -14,12 +14,14 @@ class CloudflareInterceptor : Interceptor {
|
|||||||
|
|
||||||
private val challengePattern = Regex("""name="jschl_vc" value="(\w+)"""")
|
private val challengePattern = Regex("""name="jschl_vc" value="(\w+)"""")
|
||||||
|
|
||||||
|
private val serverCheck = arrayOf("cloudflare-nginx", "cloudflare")
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
override fun intercept(chain: Interceptor.Chain): Response {
|
override fun intercept(chain: Interceptor.Chain): Response {
|
||||||
val response = chain.proceed(chain.request())
|
val response = chain.proceed(chain.request())
|
||||||
|
|
||||||
// Check if Cloudflare anti-bot is on
|
// Check if Cloudflare anti-bot is on
|
||||||
if (response.code() == 503 && "cloudflare-nginx" == response.header("Server")) {
|
if (response.code() == 503 && serverCheck.contains(response.header("Server"))) {
|
||||||
return chain.proceed(resolveChallenge(response))
|
return chain.proceed(resolveChallenge(response))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,16 +18,6 @@ class NetworkHelper(context: Context) {
|
|||||||
.cache(Cache(cacheDir, cacheSize))
|
.cache(Cache(cacheDir, cacheSize))
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
val forceCacheClient = client.newBuilder()
|
|
||||||
.addNetworkInterceptor { chain ->
|
|
||||||
val originalResponse = chain.proceed(chain.request())
|
|
||||||
originalResponse.newBuilder()
|
|
||||||
.removeHeader("Pragma")
|
|
||||||
.header("Cache-Control", "max-age=600")
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
.build()
|
|
||||||
|
|
||||||
val cloudflareClient = client.newBuilder()
|
val cloudflareClient = client.newBuilder()
|
||||||
.addInterceptor(CloudflareInterceptor())
|
.addInterceptor(CloudflareInterceptor())
|
||||||
.build()
|
.build()
|
||||||
|
@ -17,7 +17,7 @@ class Mangafox : ParsedHttpSource() {
|
|||||||
|
|
||||||
override val name = "Mangafox"
|
override val name = "Mangafox"
|
||||||
|
|
||||||
override val baseUrl = "http://mangafox.me"
|
override val baseUrl = "http://mangafox.la"
|
||||||
|
|
||||||
override val lang = "en"
|
override val lang = "en"
|
||||||
|
|
||||||
|
@ -35,13 +35,59 @@ class Mangachan : ParsedHttpSource() {
|
|||||||
val url = if (query.isNotEmpty()) {
|
val url = if (query.isNotEmpty()) {
|
||||||
"$baseUrl/?do=search&subaction=search&story=$query&search_start=$pageNum"
|
"$baseUrl/?do=search&subaction=search&story=$query&search_start=$pageNum"
|
||||||
} else {
|
} else {
|
||||||
val filt = filters.filterIsInstance<Genre>().filter { !it.isIgnored() }
|
|
||||||
if (filt.isNotEmpty()) {
|
|
||||||
var genres = ""
|
var genres = ""
|
||||||
filt.forEach { genres += (if (it.isExcluded()) "-" else "") + it.id + '+' }
|
var order = ""
|
||||||
"$baseUrl/tags/${genres.dropLast(1)}?offset=${20 * (pageNum - 1)}"
|
var statusParam = true
|
||||||
|
var status = ""
|
||||||
|
for (filter in if (filters.isEmpty()) getFilterList() else filters) {
|
||||||
|
when (filter) {
|
||||||
|
is GenreList -> {
|
||||||
|
filter.state.forEach { f ->
|
||||||
|
if (!f.isIgnored()) {
|
||||||
|
genres += (if (f.isExcluded()) "-" else "") + f.id + '+'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is OrderBy -> { if (filter.state!!.ascending && filter.state!!.index == 0) { statusParam = false } }
|
||||||
|
is Status -> status = arrayOf("", "all_done", "end", "ongoing", "new_ch")[filter.state]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (genres.isNotEmpty()) {
|
||||||
|
for (filter in filters) {
|
||||||
|
when (filter) {
|
||||||
|
is OrderBy -> {
|
||||||
|
order = if (filter.state!!.ascending) {
|
||||||
|
arrayOf("", "&n=favasc", "&n=abcdesc", "&n=chasc")[filter.state!!.index]
|
||||||
} else {
|
} else {
|
||||||
"$baseUrl/?do=search&subaction=search&story=$query&search_start=$pageNum"
|
arrayOf("&n=dateasc", "&n=favdesc", "&n=abcasc", "&n=chdesc")[filter.state!!.index]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (statusParam) {
|
||||||
|
"$baseUrl/tags/${genres.dropLast(1)}$order?offset=${20 * (pageNum - 1)}&status=$status"
|
||||||
|
} else {
|
||||||
|
"$baseUrl/tags/$status/${genres.dropLast(1)}/$order?offset=${20 * (pageNum - 1)}"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (filter in filters) {
|
||||||
|
when (filter) {
|
||||||
|
is OrderBy -> {
|
||||||
|
order = if (filter.state!!.ascending) {
|
||||||
|
arrayOf("manga/new", "manga/new&n=favasc", "manga/new&n=abcdesc", "manga/new&n=chasc")[filter.state!!.index]
|
||||||
|
} else {
|
||||||
|
arrayOf("manga/new&n=dateasc", "mostfavorites", "catalog", "sortch")[filter.state!!.index]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (statusParam) {
|
||||||
|
"$baseUrl/$order?offset=${20 * (pageNum - 1)}&status=$status"
|
||||||
|
} else {
|
||||||
|
"$baseUrl/$order/$status?offset=${20 * (pageNum - 1)}"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return GET(url, headers)
|
return GET(url, headers)
|
||||||
@ -160,18 +206,39 @@ class Mangachan : ParsedHttpSource() {
|
|||||||
|
|
||||||
override fun imageUrlParse(document: Document) = ""
|
override fun imageUrlParse(document: Document) = ""
|
||||||
|
|
||||||
|
private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Тэги", genres)
|
||||||
private class Genre(name: String, val id: String = name.replace(' ', '_')) : Filter.TriState(name)
|
private class Genre(name: String, val id: String = name.replace(' ', '_')) : Filter.TriState(name)
|
||||||
|
private class Status : Filter.Select<String>("Статус", arrayOf("Все", "Перевод завершен", "Выпуск завершен", "Онгоинг", "Новые главы"))
|
||||||
|
private class OrderBy : Filter.Sort("Сортировка",
|
||||||
|
arrayOf("Дата", "Популярность", "Имя", "Главы"),
|
||||||
|
Filter.Sort.Selection(1, false))
|
||||||
|
|
||||||
|
|
||||||
|
override fun getFilterList() = FilterList(
|
||||||
|
Status(),
|
||||||
|
OrderBy(),
|
||||||
|
GenreList(getGenreList())
|
||||||
|
)
|
||||||
|
|
||||||
|
// private class StatusList(status: List<Status>) : Filter.Group<Status>("Статус", status)
|
||||||
|
// private class Status(name: String, val id: String) : Filter.CheckBox(name, false)
|
||||||
|
// private fun getStatusList() = listOf(
|
||||||
|
// Status("Перевод завершен", "/all_done"),
|
||||||
|
// Status("Выпуск завершен", "/end"),
|
||||||
|
// Status("Онгоинг", "/ongoing"),
|
||||||
|
// Status("Новые главы", "/new_ch")
|
||||||
|
// )
|
||||||
|
|
||||||
|
|
||||||
/* [...document.querySelectorAll("li.sidetag > a:nth-child(1)")].map((el,i) =>
|
/* [...document.querySelectorAll("li.sidetag > a:nth-child(1)")].map((el,i) =>
|
||||||
* { const link=el.getAttribute('href');const id=link.substr(6,link.length);
|
* { const link=el.getAttribute('href');const id=link.substr(6,link.length);
|
||||||
* return `Genre("${id.replace("_", " ")}")` }).join(',\n')
|
* return `Genre("${id.replace("_", " ")}")` }).join(',\n')
|
||||||
* on http://mangachan.me/
|
* on http://mangachan.me/
|
||||||
*/
|
*/
|
||||||
override fun getFilterList() = FilterList(
|
private fun getGenreList() = listOf(
|
||||||
Genre("18 плюс"),
|
Genre("18 плюс"),
|
||||||
Genre("bdsm"),
|
Genre("bdsm"),
|
||||||
Genre("арт"),
|
Genre("арт"),
|
||||||
Genre("биография"),
|
|
||||||
Genre("боевик"),
|
Genre("боевик"),
|
||||||
Genre("боевые искусства"),
|
Genre("боевые искусства"),
|
||||||
Genre("вампиры"),
|
Genre("вампиры"),
|
||||||
@ -191,7 +258,6 @@ class Mangachan : ParsedHttpSource() {
|
|||||||
Genre("кодомо"),
|
Genre("кодомо"),
|
||||||
Genre("комедия"),
|
Genre("комедия"),
|
||||||
Genre("литРПГ"),
|
Genre("литРПГ"),
|
||||||
Genre("магия"),
|
|
||||||
Genre("махо-сёдзё"),
|
Genre("махо-сёдзё"),
|
||||||
Genre("меха"),
|
Genre("меха"),
|
||||||
Genre("мистика"),
|
Genre("мистика"),
|
||||||
@ -213,6 +279,7 @@ class Mangachan : ParsedHttpSource() {
|
|||||||
Genre("сёдзё-ай"),
|
Genre("сёдзё-ай"),
|
||||||
Genre("сёнэн"),
|
Genre("сёнэн"),
|
||||||
Genre("сёнэн-ай"),
|
Genre("сёнэн-ай"),
|
||||||
|
Genre("темное фэнтези"),
|
||||||
Genre("тентакли"),
|
Genre("тентакли"),
|
||||||
Genre("трагедия"),
|
Genre("трагедия"),
|
||||||
Genre("триллер"),
|
Genre("триллер"),
|
||||||
|
@ -118,7 +118,7 @@ class Mintmanga : ParsedHttpSource() {
|
|||||||
val endIndex = html.indexOf("], 0, false);", beginIndex)
|
val endIndex = html.indexOf("], 0, false);", beginIndex)
|
||||||
val trimmedHtml = html.substring(beginIndex, endIndex)
|
val trimmedHtml = html.substring(beginIndex, endIndex)
|
||||||
|
|
||||||
val p = Pattern.compile("'.+?','.+?',\".+?\"")
|
val p = Pattern.compile("'.*?','.*?',\".*?\"")
|
||||||
val m = p.matcher(trimmedHtml)
|
val m = p.matcher(trimmedHtml)
|
||||||
|
|
||||||
val pages = mutableListOf<Page>()
|
val pages = mutableListOf<Page>()
|
||||||
|
@ -118,7 +118,7 @@ class Readmanga : ParsedHttpSource() {
|
|||||||
val endIndex = html.indexOf("], 0, false);", beginIndex)
|
val endIndex = html.indexOf("], 0, false);", beginIndex)
|
||||||
val trimmedHtml = html.substring(beginIndex, endIndex)
|
val trimmedHtml = html.substring(beginIndex, endIndex)
|
||||||
|
|
||||||
val p = Pattern.compile("'.+?','.+?',\".+?\"")
|
val p = Pattern.compile("'.*?','.*?',\".*?\"")
|
||||||
val m = p.matcher(trimmedHtml)
|
val m = p.matcher(trimmedHtml)
|
||||||
|
|
||||||
val pages = mutableListOf<Page>()
|
val pages = mutableListOf<Page>()
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package eu.kanade.tachiyomi.ui.base.presenter
|
package eu.kanade.tachiyomi.ui.base.presenter
|
||||||
|
|
||||||
import nucleus.presenter.RxPresenter
|
import nucleus.presenter.RxPresenter
|
||||||
|
import nucleus.presenter.delivery.Delivery
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
|
|
||||||
open class BasePresenter<V> : RxPresenter<V>() {
|
open class BasePresenter<V> : RxPresenter<V>() {
|
||||||
@ -35,4 +36,29 @@ open class BasePresenter<V> : RxPresenter<V>() {
|
|||||||
fun <T> Observable<T>.subscribeReplay(onNext: (V, T) -> Unit, onError: ((V, Throwable) -> Unit)? = null)
|
fun <T> Observable<T>.subscribeReplay(onNext: (V, T) -> Unit, onError: ((V, Throwable) -> Unit)? = null)
|
||||||
= compose(deliverReplay<T>()).subscribe(split(onNext, onError)).apply { add(this) }
|
= compose(deliverReplay<T>()).subscribe(split(onNext, onError)).apply { add(this) }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscribes an observable with [DeliverWithView] and adds it to the presenter's lifecycle
|
||||||
|
* subscription list.
|
||||||
|
*
|
||||||
|
* @param onNext function to execute when the observable emits an item.
|
||||||
|
* @param onError function to execute when the observable throws an error.
|
||||||
|
*/
|
||||||
|
fun <T> Observable<T>.subscribeWithView(onNext: (V, T) -> Unit, onError: ((V, Throwable) -> Unit)? = null)
|
||||||
|
= compose(DeliverWithView<V, T>(view())).subscribe(split(onNext, onError)).apply { add(this) }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A deliverable that only emits to the view if attached, otherwise the event is ignored.
|
||||||
|
*/
|
||||||
|
class DeliverWithView<View, T>(private val view: Observable<View>) : Observable.Transformer<T, Delivery<View, T>> {
|
||||||
|
|
||||||
|
override fun call(observable: Observable<T>): Observable<Delivery<View, T>> {
|
||||||
|
return observable
|
||||||
|
.materialize()
|
||||||
|
.filter { notification -> !notification.isOnCompleted }
|
||||||
|
.flatMap { notification ->
|
||||||
|
view.take(1).filter { it != null }.map { Delivery(it, notification) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ class SourceDividerItemDecoration(context: Context) : RecyclerView.ItemDecoratio
|
|||||||
private val divider: Drawable
|
private val divider: Drawable
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val a = context.obtainStyledAttributes(ATTRS)
|
val a = context.obtainStyledAttributes(intArrayOf(android.R.attr.listDivider))
|
||||||
divider = a.getDrawable(0)
|
divider = a.getDrawable(0)
|
||||||
a.recycle()
|
a.recycle()
|
||||||
}
|
}
|
||||||
@ -41,7 +41,4 @@ class SourceDividerItemDecoration(context: Context) : RecyclerView.ItemDecoratio
|
|||||||
outRect.set(0, 0, 0, divider.intrinsicHeight)
|
outRect.set(0, 0, 0, divider.intrinsicHeight)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
|
||||||
private val ATTRS = intArrayOf(android.R.attr.listDivider)
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -476,6 +476,7 @@ open class BrowseCatalogueController(bundle: Bundle) :
|
|||||||
0 -> {
|
0 -> {
|
||||||
presenter.changeMangaFavorite(manga)
|
presenter.changeMangaFavorite(manga)
|
||||||
adapter?.notifyItemChanged(position)
|
adapter?.notifyItemChanged(position)
|
||||||
|
activity?.toast(activity?.getString(R.string.manga_removed_library))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.show()
|
}.show()
|
||||||
@ -498,6 +499,7 @@ open class BrowseCatalogueController(bundle: Bundle) :
|
|||||||
ChangeMangaCategoriesDialog(this, listOf(manga), categories, preselected)
|
ChangeMangaCategoriesDialog(this, listOf(manga), categories, preselected)
|
||||||
.showDialog(router)
|
.showDialog(router)
|
||||||
}
|
}
|
||||||
|
activity?.toast(activity?.getString(R.string.manga_added_library))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -107,10 +107,15 @@ class CategoryController : NucleusController<CategoryPresenter>(),
|
|||||||
fun setCategories(categories: List<CategoryItem>) {
|
fun setCategories(categories: List<CategoryItem>) {
|
||||||
actionMode?.finish()
|
actionMode?.finish()
|
||||||
adapter?.updateDataSet(categories)
|
adapter?.updateDataSet(categories)
|
||||||
|
if (categories.isNotEmpty()) {
|
||||||
|
empty_view.hide()
|
||||||
val selected = categories.filter { it.isSelected }
|
val selected = categories.filter { it.isSelected }
|
||||||
if (selected.isNotEmpty()) {
|
if (selected.isNotEmpty()) {
|
||||||
selected.forEach { onItemLongClick(categories.indexOf(it)) }
|
selected.forEach { onItemLongClick(categories.indexOf(it)) }
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
empty_view.show(R.drawable.ic_shape_black_128dp, R.string.information_empty_category)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -242,7 +242,7 @@ class MangaInfoController : NucleusController<MangaInfoPresenter>(),
|
|||||||
fab_favorite?.setImageResource(if (isFavorite)
|
fab_favorite?.setImageResource(if (isFavorite)
|
||||||
R.drawable.ic_bookmark_white_24dp
|
R.drawable.ic_bookmark_white_24dp
|
||||||
else
|
else
|
||||||
R.drawable.ic_bookmark_border_white_24dp)
|
R.drawable.ic_add_to_library_24dp)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -301,6 +301,9 @@ class MangaInfoController : NucleusController<MangaInfoPresenter>(),
|
|||||||
.showDialog(router)
|
.showDialog(router)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
activity?.toast(activity?.getString(R.string.manga_added_library))
|
||||||
|
}else{
|
||||||
|
activity?.toast(activity?.getString(R.string.manga_removed_library))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package eu.kanade.tachiyomi.ui.setting
|
package eu.kanade.tachiyomi.ui.setting
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.support.graphics.drawable.VectorDrawableCompat
|
import android.support.graphics.drawable.VectorDrawableCompat
|
||||||
import android.support.v4.graphics.drawable.DrawableCompat
|
import android.support.v4.graphics.drawable.DrawableCompat
|
||||||
import android.support.v7.preference.*
|
import android.support.v7.preference.*
|
||||||
@ -10,7 +9,7 @@ import eu.kanade.tachiyomi.widget.preference.IntListPreference
|
|||||||
@Target(AnnotationTarget.TYPE)
|
@Target(AnnotationTarget.TYPE)
|
||||||
annotation class DSL
|
annotation class DSL
|
||||||
|
|
||||||
inline fun PreferenceManager.newScreen(context: Context, block: (@DSL PreferenceScreen).() -> Unit): PreferenceScreen {
|
inline fun PreferenceManager.newScreen(block: (@DSL PreferenceScreen).() -> Unit): PreferenceScreen {
|
||||||
return createPreferenceScreen(context).also { it.block() }
|
return createPreferenceScreen(context).also { it.block() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,8 +11,8 @@ import eu.kanade.tachiyomi.BuildConfig
|
|||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.updater.GithubUpdateChecker
|
import eu.kanade.tachiyomi.data.updater.GithubUpdateChecker
|
||||||
import eu.kanade.tachiyomi.data.updater.GithubUpdateResult
|
import eu.kanade.tachiyomi.data.updater.GithubUpdateResult
|
||||||
import eu.kanade.tachiyomi.data.updater.UpdateCheckerJob
|
import eu.kanade.tachiyomi.data.updater.UpdaterJob
|
||||||
import eu.kanade.tachiyomi.data.updater.UpdateDownloaderService
|
import eu.kanade.tachiyomi.data.updater.UpdaterService
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
||||||
import eu.kanade.tachiyomi.util.toast
|
import eu.kanade.tachiyomi.util.toast
|
||||||
import rx.Subscription
|
import rx.Subscription
|
||||||
@ -59,9 +59,9 @@ class SettingsAboutController : SettingsController() {
|
|||||||
onChange { newValue ->
|
onChange { newValue ->
|
||||||
val checked = newValue as Boolean
|
val checked = newValue as Boolean
|
||||||
if (checked) {
|
if (checked) {
|
||||||
UpdateCheckerJob.setupTask()
|
UpdaterJob.setupTask()
|
||||||
} else {
|
} else {
|
||||||
UpdateCheckerJob.cancelTask()
|
UpdaterJob.cancelTask()
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
@ -71,7 +71,7 @@ class SettingsAboutController : SettingsController() {
|
|||||||
}
|
}
|
||||||
preference {
|
preference {
|
||||||
title = "Discord"
|
title = "Discord"
|
||||||
val url = "https://discord.gg/WrBkRk4"
|
val url = "https://discord.gg/2dDQBv2"
|
||||||
summary = url
|
summary = url
|
||||||
onClick {
|
onClick {
|
||||||
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
|
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
|
||||||
@ -148,7 +148,7 @@ class SettingsAboutController : SettingsController() {
|
|||||||
if (appContext != null) {
|
if (appContext != null) {
|
||||||
// Start download
|
// Start download
|
||||||
val url = args.getString(URL_KEY)
|
val url = args.getString(URL_KEY)
|
||||||
UpdateDownloaderService.downloadUpdate(appContext, url)
|
UpdaterService.downloadUpdate(appContext, url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.build()
|
.build()
|
||||||
|
@ -10,8 +10,11 @@ import android.view.ContextThemeWrapper
|
|||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import com.bluelinelabs.conductor.ControllerChangeHandler
|
||||||
|
import com.bluelinelabs.conductor.ControllerChangeType
|
||||||
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.base.controller.BaseController
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.Subscription
|
import rx.Subscription
|
||||||
import rx.subscriptions.CompositeSubscription
|
import rx.subscriptions.CompositeSubscription
|
||||||
@ -55,9 +58,23 @@ abstract class SettingsController : PreferenceController() {
|
|||||||
return preferenceScreen?.title?.toString()
|
return preferenceScreen?.title?.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onAttach(view: View) {
|
fun setTitle() {
|
||||||
|
var parentController = parentController
|
||||||
|
while (parentController != null) {
|
||||||
|
if (parentController is BaseController && parentController.getTitle() != null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
parentController = parentController.parentController
|
||||||
|
}
|
||||||
|
|
||||||
(activity as? AppCompatActivity)?.supportActionBar?.title = getTitle()
|
(activity as? AppCompatActivity)?.supportActionBar?.title = getTitle()
|
||||||
super.onAttach(view)
|
}
|
||||||
|
|
||||||
|
override fun onChangeStarted(handler: ControllerChangeHandler, type: ControllerChangeType) {
|
||||||
|
if (type.isEnter) {
|
||||||
|
setTitle()
|
||||||
|
}
|
||||||
|
super.onChangeStarted(handler, type)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <T> Observable<T>.subscribeUntilDestroy(): Subscription {
|
fun <T> Observable<T>.subscribeUntilDestroy(): Subscription {
|
||||||
|
@ -13,9 +13,8 @@ import java.io.File
|
|||||||
* @param context context of application
|
* @param context context of application
|
||||||
*/
|
*/
|
||||||
fun File.getUriCompat(context: Context): Uri {
|
fun File.getUriCompat(context: Context): Uri {
|
||||||
val uri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
|
||||||
FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".provider", this)
|
FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".provider", this)
|
||||||
else Uri.fromFile(this)
|
else Uri.fromFile(this)
|
||||||
return uri
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
7
app/src/main/res/drawable/ic_add_to_library_24dp.xml
Normal file
7
app/src/main/res/drawable/ic_add_to_library_24dp.xml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:height="24dp"
|
||||||
|
android:width="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path android:fillColor="#FFFFFF" android:pathData="M17,18V5H7V18L12,15.82L17,18M17,3A2,2 0 0,1 19,5V21L12,18L5,21V5C5,3.89 5.9,3 7,3H17M11,7H13V9H15V11H13V13H11V11H9V9H11V7Z" />
|
||||||
|
</vector>
|
7
app/src/main/res/drawable/ic_in_library_24dp.xml
Normal file
7
app/src/main/res/drawable/ic_in_library_24dp.xml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:height="24dp"
|
||||||
|
android:width="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path android:fillColor="#FFFFFF" android:pathData="M12,8A3,3 0 0,0 15,5A3,3 0 0,0 12,2A3,3 0 0,0 9,5A3,3 0 0,0 12,8M12,11.54C9.64,9.35 6.5,8 3,8V19C6.5,19 9.64,20.35 12,22.54C14.36,20.35 17.5,19 21,19V8C17.5,8 14.36,9.35 12,11.54Z" />
|
||||||
|
</vector>
|
9
app/src/main/res/drawable/ic_shape_black_128dp.xml
Normal file
9
app/src/main/res/drawable/ic_shape_black_128dp.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="128dp"
|
||||||
|
android:height="128dp"
|
||||||
|
android:viewportWidth="24.0"
|
||||||
|
android:viewportHeight="24.0">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M11,13.5V21.5H3V13.5H11M12,2L17.5,11H6.5L12,2M17.5,13C20,13 22,15 22,17.5C22,20 20,22 17.5,22C15,22 13,20 13,17.5C13,15 15,13 17.5,13Z" />
|
||||||
|
</vector>
|
@ -10,5 +10,6 @@ android:layout_height="wrap_content">
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:paddingBottom="4dp"
|
android:paddingBottom="4dp"
|
||||||
android:paddingTop="4dp"
|
android:paddingTop="4dp"
|
||||||
|
android:clipToPadding="false"
|
||||||
tools:listitem="@layout/catalogue_global_search_controller_card" />
|
tools:listitem="@layout/catalogue_global_search_controller_card" />
|
||||||
</FrameLayout>
|
</FrameLayout>
|
@ -78,6 +78,7 @@
|
|||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:paddingEnd="4dp"
|
android:paddingEnd="4dp"
|
||||||
android:paddingStart="4dp"
|
android:paddingStart="4dp"
|
||||||
|
android:clipToPadding="false"
|
||||||
tools:listitem="@layout/catalogue_global_search_controller_card_item" />
|
tools:listitem="@layout/catalogue_global_search_controller_card_item" />
|
||||||
</android.support.v7.widget.CardView>
|
</android.support.v7.widget.CardView>
|
||||||
</android.support.constraint.ConstraintLayout>
|
</android.support.constraint.ConstraintLayout>
|
@ -7,4 +7,5 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:columnWidth="140dp"
|
android:columnWidth="140dp"
|
||||||
|
android:clipToPadding="false"
|
||||||
tools:listitem="@layout/catalogue_grid_item" />
|
tools:listitem="@layout/catalogue_grid_item" />
|
@ -19,4 +19,11 @@
|
|||||||
app:srcCompat="@drawable/ic_add_white_24dp"
|
app:srcCompat="@drawable/ic_add_white_24dp"
|
||||||
style="@style/Theme.Widget.FAB"/>
|
style="@style/Theme.Widget.FAB"/>
|
||||||
|
|
||||||
|
<eu.kanade.tachiyomi.widget.EmptyView
|
||||||
|
android:id="@+id/empty_view"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
@ -13,10 +13,12 @@
|
|||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/text_label"
|
android:id="@+id/text_label"
|
||||||
|
android:layout_margin="16dp"
|
||||||
style="@style/TextAppearance.Medium.Body2.Hint"
|
style="@style/TextAppearance.Medium.Body2.Hint"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_below="@+id/image_view"
|
android:layout_below="@+id/image_view"
|
||||||
|
android:gravity="center"
|
||||||
android:layout_centerHorizontal="true"/>
|
android:layout_centerHorizontal="true"/>
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<FrameLayout
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<eu.kanade.tachiyomi.widget.AutofitRecyclerView
|
<eu.kanade.tachiyomi.widget.AutofitRecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:id="@+id/library_grid"
|
android:id="@+id/library_grid"
|
||||||
style="@style/Theme.Widget.GridView.Catalogue"
|
style="@style/Theme.Widget.GridView.Catalogue"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:columnWidth="140dp"
|
android:columnWidth="140dp"
|
||||||
|
android:clipToPadding="false"
|
||||||
tools:listitem="@layout/catalogue_grid_item" />
|
tools:listitem="@layout/catalogue_grid_item" />
|
||||||
|
@ -58,7 +58,7 @@
|
|||||||
<android.support.design.widget.FloatingActionButton
|
<android.support.design.widget.FloatingActionButton
|
||||||
android:id="@+id/fab_favorite"
|
android:id="@+id/fab_favorite"
|
||||||
style="@style/Theme.Widget.FAB"
|
style="@style/Theme.Widget.FAB"
|
||||||
app:srcCompat="@drawable/ic_bookmark_border_white_24dp"
|
app:srcCompat="@drawable/ic_add_to_library_24dp"
|
||||||
android:layout_marginTop="0dp"
|
android:layout_marginTop="0dp"
|
||||||
android:layout_marginBottom="0dp"
|
android:layout_marginBottom="0dp"
|
||||||
android:layout_marginRight="8dp"
|
android:layout_marginRight="8dp"
|
||||||
|
@ -9,8 +9,9 @@
|
|||||||
android:id="@+id/recycler"
|
android:id="@+id/recycler"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_marginBottom="4dp"
|
android:paddingBottom="4dp"
|
||||||
android:layout_marginTop="4dp"
|
android:paddingTop="4dp"
|
||||||
|
android:clipToPadding="false"
|
||||||
tools:listitem="@layout/recently_read_item">
|
tools:listitem="@layout/recently_read_item">
|
||||||
|
|
||||||
</android.support.v7.widget.RecyclerView>
|
</android.support.v7.widget.RecyclerView>
|
||||||
|
@ -1,5 +1,16 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<changelog bulletedList="true">
|
<changelog bulletedList="true">
|
||||||
|
<changelogversion versionName="v0.6.7" changeDate="">
|
||||||
|
<changelogtext>[b]Notice to Batoto users.[/b] As you may already know, Batoto will cease to work in a few days.
|
||||||
|
We're working on a feature to help migrating the library to other sources and should be available shortly.
|
||||||
|
Please be patient.</changelogtext>
|
||||||
|
|
||||||
|
<changelogtext>Fixed http 503 errors due to Cloudflare changes.</changelogtext>
|
||||||
|
|
||||||
|
<changelogtext>Minor UI improvements.</changelogtext>
|
||||||
|
|
||||||
|
</changelogversion>
|
||||||
|
|
||||||
<changelogversion versionName="v0.6.6" changeDate="">
|
<changelogversion versionName="v0.6.6" changeDate="">
|
||||||
<changelogtext>Backups now properly restore tracking information.</changelogtext>
|
<changelogtext>Backups now properly restore tracking information.</changelogtext>
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
<string name="label_recent_updates">تحديثات المكتبة</string>
|
<string name="label_recent_updates">تحديثات المكتبة</string>
|
||||||
<string name="label_latest_updates">آخر التحديثات</string>
|
<string name="label_latest_updates">آخر التحديثات</string>
|
||||||
<string name="label_categories">الأقسام</string>
|
<string name="label_categories">الأقسام</string>
|
||||||
<string name="label_selected">المحدد</string>
|
<string name="label_selected">%1$d المحدد</string>
|
||||||
<string name="label_backup">النسخ الاحتياطي</string>
|
<string name="label_backup">النسخ الاحتياطي</string>
|
||||||
|
|
||||||
<string name="action_settings">اﻹعدادات</string>
|
<string name="action_settings">اﻹعدادات</string>
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
<string name="label_settings">Instellingen</string>
|
<string name="label_settings">Instellingen</string>
|
||||||
<string name="label_library">Mijn bibliotheek</string>
|
<string name="label_library">Mijn bibliotheek</string>
|
||||||
<string name="label_recent_manga">Onlangs gelezen</string>
|
<string name="label_recent_manga">Onlangs gelezen</string>
|
||||||
<string name="label_catalogues">Catalogen</string>
|
<string name="label_catalogues">Catalogi</string>
|
||||||
<string name="label_latest_updates">Laatste updates</string>
|
<string name="label_latest_updates">Laatste updates</string>
|
||||||
<string name="label_categories">Categorieën</string>
|
<string name="label_categories">Categorieën</string>
|
||||||
<string name="label_selected">Geselecteerd: %1$d</string>
|
<string name="label_selected">Geselecteerd: %1$d</string>
|
||||||
@ -285,7 +285,8 @@
|
|||||||
|
|
||||||
<string name="information_no_downloads">Geen downloads</string>
|
<string name="information_no_downloads">Geen downloads</string>
|
||||||
<string name="information_no_recent">Geen recente hoofdstukken</string>
|
<string name="information_no_recent">Geen recente hoofdstukken</string>
|
||||||
<string name="information_empty_library">Bibliotheek leeg</string>
|
<string name="information_empty_library">De bibliotheek is leeg, manga kunnen toegevoegd worden vanuit de catalogi.</string>
|
||||||
|
<string name="information_empty_category">Er zijn nog geen categorieën, druk op de plus knop om een categorie aan te maken.</string>
|
||||||
|
|
||||||
<string name="download_notifier_downloader_title">Downloader</string>
|
<string name="download_notifier_downloader_title">Downloader</string>
|
||||||
<string name="download_notifier_title_error">Error</string>
|
<string name="download_notifier_title_error">Error</string>
|
||||||
|
@ -299,8 +299,9 @@
|
|||||||
<string name="ongoing">Ongoing</string>
|
<string name="ongoing">Ongoing</string>
|
||||||
<string name="unknown">Unknown</string>
|
<string name="unknown">Unknown</string>
|
||||||
<string name="licensed">Licensed</string>
|
<string name="licensed">Licensed</string>
|
||||||
<string name="add_to_library">Add to library</string>
|
|
||||||
<string name="remove_from_library">Remove from library</string>
|
<string name="remove_from_library">Remove from library</string>
|
||||||
|
<string name="manga_added_library">Added to library</string>
|
||||||
|
<string name="manga_removed_library">Removed from library</string>
|
||||||
<string name="manga_info_author_label">Author</string>
|
<string name="manga_info_author_label">Author</string>
|
||||||
<string name="manga_info_artist_label">Artist</string>
|
<string name="manga_info_artist_label">Artist</string>
|
||||||
<string name="manga_info_chapters_label">Chapters</string>
|
<string name="manga_info_chapters_label">Chapters</string>
|
||||||
@ -431,7 +432,8 @@
|
|||||||
<string name="information_no_downloads">No downloads</string>
|
<string name="information_no_downloads">No downloads</string>
|
||||||
<string name="information_no_recent">No recent chapters</string>
|
<string name="information_no_recent">No recent chapters</string>
|
||||||
<string name="information_no_recent_manga">No recently read manga</string>
|
<string name="information_no_recent_manga">No recently read manga</string>
|
||||||
<string name="information_empty_library">Empty library</string>
|
<string name="information_empty_library">Your library is empty, you can add series to your library from the Catalogues.</string>
|
||||||
|
<string name="information_empty_category">You have no categories. Hit the plus button to create one for organizing your library.</string>
|
||||||
|
|
||||||
<!-- Download Notification -->
|
<!-- Download Notification -->
|
||||||
<string name="download_notifier_downloader_title">Downloader</string>
|
<string name="download_notifier_downloader_title">Downloader</string>
|
||||||
|
Reference in New Issue
Block a user