Fixes + API 16 support
@ -88,7 +88,7 @@
|
|||||||
|
|
||||||
<receiver android:name=".data.library.LibraryUpdateService$CancelUpdateReceiver" />
|
<receiver android:name=".data.library.LibraryUpdateService$CancelUpdateReceiver" />
|
||||||
|
|
||||||
<receiver android:name=".data.download.ImageNotificationReceiver" />
|
<receiver android:name=".ui.reader.notification.ImageNotificationReceiver" />
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="eu.kanade.tachiyomi.data.glide.AppGlideModule"
|
android:name="eu.kanade.tachiyomi.data.glide.AppGlideModule"
|
||||||
|
@ -41,11 +41,6 @@ class DownloadNotifier(private val context: Context) {
|
|||||||
*/
|
*/
|
||||||
internal var multipleDownloadThreads = false
|
internal var multipleDownloadThreads = false
|
||||||
|
|
||||||
/**
|
|
||||||
* Value determining if notification should be shown
|
|
||||||
*/
|
|
||||||
internal var showNotification = true
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when download progress changes.
|
* Called when download progress changes.
|
||||||
* Note: Only accepted when multi download active.
|
* Note: Only accepted when multi download active.
|
||||||
@ -53,7 +48,7 @@ class DownloadNotifier(private val context: Context) {
|
|||||||
* @param queue the queue containing downloads.
|
* @param queue the queue containing downloads.
|
||||||
*/
|
*/
|
||||||
internal fun onProgressChange(queue: DownloadQueue) {
|
internal fun onProgressChange(queue: DownloadQueue) {
|
||||||
if (multipleDownloadThreads && showNotification)
|
if (multipleDownloadThreads)
|
||||||
doOnProgressChange(null, queue)
|
doOnProgressChange(null, queue)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,7 +60,7 @@ class DownloadNotifier(private val context: Context) {
|
|||||||
* @param queue the queue containing downloads
|
* @param queue the queue containing downloads
|
||||||
*/
|
*/
|
||||||
internal fun onProgressChange(download: Download, queue: DownloadQueue) {
|
internal fun onProgressChange(download: Download, queue: DownloadQueue) {
|
||||||
if (!multipleDownloadThreads && showNotification)
|
if (!multipleDownloadThreads)
|
||||||
doOnProgressChange(download, queue)
|
doOnProgressChange(download, queue)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,7 +126,6 @@ class DownloadNotifier(private val context: Context) {
|
|||||||
* @param download download object containing download information
|
* @param download download object containing download information
|
||||||
*/
|
*/
|
||||||
private fun onComplete(download: Download?) {
|
private fun onComplete(download: Download?) {
|
||||||
if (showNotification) {
|
|
||||||
// Create notification.
|
// Create notification.
|
||||||
with(notificationBuilder) {
|
with(notificationBuilder) {
|
||||||
setContentTitle(download?.chapter?.name ?: context.getString(R.string.app_name))
|
setContentTitle(download?.chapter?.name ?: context.getString(R.string.app_name))
|
||||||
@ -142,7 +136,7 @@ class DownloadNotifier(private val context: Context) {
|
|||||||
|
|
||||||
// Show notification.
|
// Show notification.
|
||||||
context.notificationManager.notify(notificationId, notificationBuilder.build())
|
context.notificationManager.notify(notificationId, notificationBuilder.build())
|
||||||
}
|
|
||||||
// Reset initial values
|
// Reset initial values
|
||||||
isDownloading = false
|
isDownloading = false
|
||||||
initialQueueSize = 0
|
initialQueueSize = 0
|
||||||
@ -163,7 +157,6 @@ class DownloadNotifier(private val context: Context) {
|
|||||||
*/
|
*/
|
||||||
internal fun onError(error: String? = null, chapter: String? = null) {
|
internal fun onError(error: String? = null, chapter: String? = null) {
|
||||||
// Create notification
|
// Create notification
|
||||||
if (showNotification) {
|
|
||||||
with(notificationBuilder) {
|
with(notificationBuilder) {
|
||||||
setContentTitle(chapter ?: context.getString(R.string.download_notifier_title_error))
|
setContentTitle(chapter ?: context.getString(R.string.download_notifier_title_error))
|
||||||
setContentText(error ?: context.getString(R.string.download_notifier_unkown_error))
|
setContentText(error ?: context.getString(R.string.download_notifier_unkown_error))
|
||||||
@ -171,9 +164,6 @@ class DownloadNotifier(private val context: Context) {
|
|||||||
setProgress(0, 0, false)
|
setProgress(0, 0, false)
|
||||||
}
|
}
|
||||||
context.notificationManager.notify(Constants.NOTIFICATION_DOWNLOAD_CHAPTER_ERROR_ID, notificationBuilder.build())
|
context.notificationManager.notify(Constants.NOTIFICATION_DOWNLOAD_CHAPTER_ERROR_ID, notificationBuilder.build())
|
||||||
} else {
|
|
||||||
context.toast(error ?: context.getString(R.string.download_notifier_unkown_error))
|
|
||||||
}
|
|
||||||
// Reset download information
|
// Reset download information
|
||||||
onClear()
|
onClear()
|
||||||
isDownloading = false
|
isDownloading = false
|
||||||
|
@ -1,132 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.download
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.graphics.Bitmap
|
|
||||||
import android.support.v4.app.NotificationCompat
|
|
||||||
import com.bumptech.glide.Glide
|
|
||||||
import com.bumptech.glide.request.animation.GlideAnimation
|
|
||||||
import com.bumptech.glide.request.target.SimpleTarget
|
|
||||||
import eu.kanade.tachiyomi.Constants
|
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.util.notificationManager
|
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
|
|
||||||
class ImageNotifier(private val context: Context) {
|
|
||||||
/**
|
|
||||||
* Notification builder.
|
|
||||||
*/
|
|
||||||
private val notificationBuilder = NotificationCompat.Builder(context)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Id of the notification.
|
|
||||||
*/
|
|
||||||
private val notificationId: Int
|
|
||||||
get() = Constants.NOTIFICATION_DOWNLOAD_IMAGE_ID
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Status of download. Used for correct notification icon.
|
|
||||||
*/
|
|
||||||
private var isDownloading = false
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when download progress changes.
|
|
||||||
* @param progress progress value in range [0,100]
|
|
||||||
*/
|
|
||||||
fun onProgressChange(progress: Int) {
|
|
||||||
with(notificationBuilder) {
|
|
||||||
if (!isDownloading) {
|
|
||||||
setContentTitle(context.getString(R.string.saving_picture))
|
|
||||||
setSmallIcon(android.R.drawable.stat_sys_download)
|
|
||||||
setLargeIcon(null)
|
|
||||||
setStyle(null)
|
|
||||||
// Clear old actions if they exist
|
|
||||||
if (!mActions.isEmpty())
|
|
||||||
mActions.clear()
|
|
||||||
isDownloading = true
|
|
||||||
}
|
|
||||||
|
|
||||||
setProgress(100, progress, false)
|
|
||||||
}
|
|
||||||
// Displays the progress bar on notification
|
|
||||||
context.notificationManager.notify(notificationId, notificationBuilder.build())
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when image download is complete
|
|
||||||
* @param file image file containing downloaded page image
|
|
||||||
*/
|
|
||||||
fun onComplete(file: File) {
|
|
||||||
with(notificationBuilder) {
|
|
||||||
if (isDownloading) {
|
|
||||||
setProgress(0, 0, false)
|
|
||||||
isDownloading = false
|
|
||||||
}
|
|
||||||
setContentTitle(context.getString(R.string.picture_saved))
|
|
||||||
setSmallIcon(R.drawable.ic_insert_photo_black_24dp)
|
|
||||||
Glide.with(context).load(file).asBitmap().into(object : SimpleTarget<Bitmap>(96, 96) {
|
|
||||||
/**
|
|
||||||
* The method that will be called when the resource load has finished.
|
|
||||||
* @param resource the loaded resource.
|
|
||||||
*/
|
|
||||||
override fun onResourceReady(resource: Bitmap?, glideAnimation: GlideAnimation<in Bitmap>?) {
|
|
||||||
setLargeIcon(resource)
|
|
||||||
context.notificationManager.notify(notificationId, notificationBuilder.build())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
Glide.with(context).load(file).asBitmap().into(object : SimpleTarget<Bitmap>(720, 1280) {
|
|
||||||
/**
|
|
||||||
* The method that will be called when the resource load has finished.
|
|
||||||
* @param resource the loaded resource.
|
|
||||||
*/
|
|
||||||
override fun onResourceReady(resource: Bitmap?, glideAnimation: GlideAnimation<in Bitmap>?) {
|
|
||||||
setStyle(NotificationCompat.BigPictureStyle().bigPicture(resource))
|
|
||||||
context.notificationManager.notify(notificationId, notificationBuilder.build())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
setAutoCancel(true)
|
|
||||||
|
|
||||||
// Clear old actions if they exist
|
|
||||||
if (!mActions.isEmpty())
|
|
||||||
mActions.clear()
|
|
||||||
|
|
||||||
setContentIntent(ImageNotificationReceiver.showImageIntent(context, file.absolutePath))
|
|
||||||
// Share action
|
|
||||||
addAction(R.drawable.ic_share_white_24dp,
|
|
||||||
context.getString(R.string.action_share),
|
|
||||||
ImageNotificationReceiver.shareImageIntent(context, file.absolutePath, notificationId))
|
|
||||||
// Delete action
|
|
||||||
addAction(R.drawable.ic_delete_white_24dp,
|
|
||||||
context.getString(R.string.action_delete),
|
|
||||||
ImageNotificationReceiver.deleteImageIntent(context, file.absolutePath, notificationId))
|
|
||||||
}
|
|
||||||
// Displays the progress bar on notification
|
|
||||||
context.notificationManager.notify(notificationId, notificationBuilder.build())
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clears the notification message
|
|
||||||
*/
|
|
||||||
internal fun onClear() {
|
|
||||||
context.notificationManager.cancel(notificationId)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called on error while downloading image
|
|
||||||
* @param error string containing error information
|
|
||||||
*/
|
|
||||||
internal fun onError(error: String?) {
|
|
||||||
// Create notification
|
|
||||||
with(notificationBuilder) {
|
|
||||||
setContentTitle(context.getString(R.string.download_notifier_title_error))
|
|
||||||
setContentText(error ?: context.getString(R.string.download_notifier_unkown_error))
|
|
||||||
setSmallIcon(android.R.drawable.ic_menu_report_image)
|
|
||||||
setProgress(0, 0, false)
|
|
||||||
}
|
|
||||||
context.notificationManager.notify(notificationId, notificationBuilder.build())
|
|
||||||
isDownloading = false
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -5,6 +5,7 @@ import android.content.Intent
|
|||||||
import android.content.pm.ActivityInfo
|
import android.content.pm.ActivityInfo
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Build.VERSION_CODES.KITKAT
|
import android.os.Build.VERSION_CODES.KITKAT
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
@ -229,20 +230,18 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun onLongPress(page: Page) {
|
fun onLongPress(page: Page) {
|
||||||
MaterialDialog.Builder(this).apply {
|
MaterialDialog.Builder(this)
|
||||||
title = "Choose"
|
.title(getString(R.string.options))
|
||||||
items(R.array.reader_image_options)
|
.items(R.array.reader_image_options)
|
||||||
.itemsIds(R.array.reader_image_options_values)
|
.itemsIds(R.array.reader_image_options_values)
|
||||||
itemsCallback { materialDialog, view, i, charSequence ->
|
.itemsCallback { materialDialog, view, i, charSequence ->
|
||||||
when (i) {
|
when (i) {
|
||||||
0 -> presenter.setCover(page)
|
0 -> presenter.setCover(page)
|
||||||
1 -> presenter.shareImage(page)
|
1 -> shareImage(page)
|
||||||
2 -> presenter.savePage(page)
|
2 -> presenter.savePage(page)
|
||||||
}
|
}
|
||||||
|
|
||||||
}.show()
|
}.show()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called from the presenter at startup, allowing to prepare the selected reader.
|
* Called from the presenter at startup, allowing to prepare the selected reader.
|
||||||
@ -471,6 +470,21 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start a share intent that lets user share image
|
||||||
|
*
|
||||||
|
* @param page page object containing image information.
|
||||||
|
*/
|
||||||
|
fun shareImage(page: Page) {
|
||||||
|
val shareIntent = Intent().apply {
|
||||||
|
action = Intent.ACTION_SEND
|
||||||
|
putExtra(Intent.EXTRA_STREAM, Uri.parse(page.imagePath))
|
||||||
|
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||||
|
type = "image/jpeg"
|
||||||
|
}
|
||||||
|
startActivity(Intent.createChooser(shareIntent, resources.getText(R.string.action_share)))
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the brightness of the screen. Range is [-75, 100].
|
* Sets the brightness of the screen. Range is [-75, 100].
|
||||||
* From -75 to -1 a semi-transparent black view is shown at the top with the minimum brightness.
|
* From -75 to -1 a semi-transparent black view is shown at the top with the minimum brightness.
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
package eu.kanade.tachiyomi.ui.reader
|
package eu.kanade.tachiyomi.ui.reader
|
||||||
|
|
||||||
import android.content.Intent
|
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
@ -13,15 +11,14 @@ import eu.kanade.tachiyomi.data.database.models.History
|
|||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.data.database.models.MangaSync
|
import eu.kanade.tachiyomi.data.database.models.MangaSync
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||||
import eu.kanade.tachiyomi.data.download.ImageNotifier
|
|
||||||
import eu.kanade.tachiyomi.data.mangasync.MangaSyncManager
|
import eu.kanade.tachiyomi.data.mangasync.MangaSyncManager
|
||||||
import eu.kanade.tachiyomi.data.mangasync.UpdateMangaSyncService
|
import eu.kanade.tachiyomi.data.mangasync.UpdateMangaSyncService
|
||||||
import eu.kanade.tachiyomi.data.network.NetworkHelper
|
|
||||||
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.data.source.model.Page
|
import eu.kanade.tachiyomi.data.source.model.Page
|
||||||
import eu.kanade.tachiyomi.data.source.online.OnlineSource
|
import eu.kanade.tachiyomi.data.source.online.OnlineSource
|
||||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
||||||
|
import eu.kanade.tachiyomi.ui.reader.notification.ImageNotifier
|
||||||
import eu.kanade.tachiyomi.util.RetryWithDelay
|
import eu.kanade.tachiyomi.util.RetryWithDelay
|
||||||
import eu.kanade.tachiyomi.util.SharedData
|
import eu.kanade.tachiyomi.util.SharedData
|
||||||
import eu.kanade.tachiyomi.util.toast
|
import eu.kanade.tachiyomi.util.toast
|
||||||
@ -33,19 +30,12 @@ import timber.log.Timber
|
|||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.InputStream
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Presenter of [ReaderActivity].
|
* Presenter of [ReaderActivity].
|
||||||
*/
|
*/
|
||||||
class ReaderPresenter : BasePresenter<ReaderActivity>() {
|
class ReaderPresenter : BasePresenter<ReaderActivity>() {
|
||||||
|
|
||||||
/**
|
|
||||||
* Network helper
|
|
||||||
*/
|
|
||||||
private val network: NetworkHelper by injectLazy()
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Preferences.
|
* Preferences.
|
||||||
*/
|
*/
|
||||||
@ -108,11 +98,6 @@ class ReaderPresenter : BasePresenter<ReaderActivity>() {
|
|||||||
*/
|
*/
|
||||||
private val source by lazy { sourceManager.get(manga.source)!! }
|
private val source by lazy { sourceManager.get(manga.source)!! }
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
val imageNotifier by lazy { ImageNotifier(context) }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Directory of pictures
|
* Directory of pictures
|
||||||
*/
|
*/
|
||||||
@ -548,43 +533,21 @@ class ReaderPresenter : BasePresenter<ReaderActivity>() {
|
|||||||
* Update cover with page file.
|
* Update cover with page file.
|
||||||
*/
|
*/
|
||||||
internal fun setCover(page: Page) {
|
internal fun setCover(page: Page) {
|
||||||
// Update cover to selected file, show error if something went wrong
|
|
||||||
try {
|
try {
|
||||||
if (editCoverWithStream(File(page.imagePath).inputStream(), manga)) {
|
if (manga.favorite) {
|
||||||
|
if (manga.thumbnail_url != null) {
|
||||||
|
coverCache.copyToCache(manga.thumbnail_url!!, File(page.imagePath).inputStream())
|
||||||
context.toast(R.string.cover_updated)
|
context.toast(R.string.cover_updated)
|
||||||
} else {
|
} else {
|
||||||
throw Exception("Stream copy failed")
|
throw Exception("Image url not found")
|
||||||
}
|
}
|
||||||
} catch(e: Exception) {
|
} else {
|
||||||
context.toast(R.string.notification_manga_update_failed)
|
context.toast(R.string.notification_first_add_to_library)
|
||||||
Timber.e(e.message)
|
|
||||||
}
|
}
|
||||||
|
} catch (error: Exception) {
|
||||||
|
context.toast(R.string.notification_cover_update_failed)
|
||||||
|
Timber.e(error)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Called to copy image to cache
|
|
||||||
* @param inputStream the new cover.
|
|
||||||
* @param manga the manga edited.
|
|
||||||
* @return true if the cover is updated, false otherwise
|
|
||||||
*/
|
|
||||||
@Throws(IOException::class)
|
|
||||||
private fun editCoverWithStream(inputStream: InputStream, manga: Manga): Boolean {
|
|
||||||
if (manga.thumbnail_url != null && manga.favorite) {
|
|
||||||
coverCache.copyToCache(manga.thumbnail_url!!, inputStream)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
fun shareImage(page: Page) {
|
|
||||||
val shareIntent = Intent().apply {
|
|
||||||
action = Intent.ACTION_SEND
|
|
||||||
putExtra(Intent.EXTRA_STREAM, Uri.parse(page.imagePath))
|
|
||||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
|
||||||
type = "image/jpeg"
|
|
||||||
}
|
|
||||||
context.startActivity(Intent.createChooser(shareIntent, context.resources.getText(R.string.action_share))
|
|
||||||
.apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -593,6 +556,9 @@ class ReaderPresenter : BasePresenter<ReaderActivity>() {
|
|||||||
*/
|
*/
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
internal fun savePage(page: Page) {
|
internal fun savePage(page: Page) {
|
||||||
|
// Used to show image notification
|
||||||
|
val imageNotifier = ImageNotifier(context)
|
||||||
|
|
||||||
// Location of image file.
|
// Location of image file.
|
||||||
val inputFile = File(page.imagePath)
|
val inputFile = File(page.imagePath)
|
||||||
|
|
||||||
@ -602,23 +568,20 @@ class ReaderPresenter : BasePresenter<ReaderActivity>() {
|
|||||||
|
|
||||||
//Remove the notification if already exist (user feedback)
|
//Remove the notification if already exist (user feedback)
|
||||||
imageNotifier.onClear()
|
imageNotifier.onClear()
|
||||||
|
|
||||||
//Check if file doesn't already exist
|
|
||||||
if (destFile.exists()) {
|
|
||||||
imageNotifier.onComplete(destFile)
|
|
||||||
} else {
|
|
||||||
if (inputFile.exists()) {
|
if (inputFile.exists()) {
|
||||||
// Copy file
|
// Copy file
|
||||||
Observable.fromCallable { inputFile.copyTo(destFile) }
|
Observable.fromCallable { inputFile.copyTo(destFile, true) }
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe(
|
.subscribe(
|
||||||
{ imageNotifier.onComplete(it) },
|
{
|
||||||
|
// Show notification
|
||||||
|
imageNotifier.onComplete(it)
|
||||||
|
},
|
||||||
{ error ->
|
{ error ->
|
||||||
Timber.e(error.message)
|
Timber.e(error)
|
||||||
imageNotifier.onError(error.message)
|
imageNotifier.onError(error.message)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package eu.kanade.tachiyomi.data.download
|
package eu.kanade.tachiyomi.ui.reader.notification
|
||||||
|
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.content.BroadcastReceiver
|
import android.content.BroadcastReceiver
|
||||||
@ -9,6 +9,10 @@ import eu.kanade.tachiyomi.R
|
|||||||
import eu.kanade.tachiyomi.util.notificationManager
|
import eu.kanade.tachiyomi.util.notificationManager
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The BroadcastReceiver of [ImageNotifier]
|
||||||
|
* Intent calls should be made from this class.
|
||||||
|
*/
|
||||||
class ImageNotificationReceiver : BroadcastReceiver() {
|
class ImageNotificationReceiver : BroadcastReceiver() {
|
||||||
override fun onReceive(context: Context, intent: Intent) {
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
when (intent.action) {
|
when (intent.action) {
|
||||||
@ -25,23 +29,36 @@ class ImageNotificationReceiver : BroadcastReceiver() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteImage(path: String) {
|
/**
|
||||||
|
* Called to delete image
|
||||||
|
* @param path path of file
|
||||||
|
*/
|
||||||
|
private fun deleteImage(path: String) {
|
||||||
val file = File(path)
|
val file = File(path)
|
||||||
if (file.exists()) file.delete()
|
if (file.exists()) file.delete()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun shareImage(context: Context, path: String) {
|
/**
|
||||||
|
* Called to start share intent to share image
|
||||||
|
* @param context context of application
|
||||||
|
* @param path path of file
|
||||||
|
*/
|
||||||
|
private fun shareImage(context: Context, path: String) {
|
||||||
val shareIntent = Intent().apply {
|
val shareIntent = Intent().apply {
|
||||||
action = Intent.ACTION_SEND
|
action = Intent.ACTION_SEND
|
||||||
putExtra(Intent.EXTRA_STREAM, Uri.parse(path))
|
putExtra(Intent.EXTRA_STREAM, Uri.parse(path))
|
||||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
|
||||||
type = "image/jpeg"
|
type = "image/jpeg"
|
||||||
}
|
}
|
||||||
context.startActivity(Intent.createChooser(shareIntent, context.resources.getText(R.string.action_share))
|
context.startActivity(Intent.createChooser(shareIntent, context.resources.getText(R.string.action_share))
|
||||||
.apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK })
|
.apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK })
|
||||||
}
|
}
|
||||||
|
|
||||||
fun showImage(context: Context, path: String) {
|
/**
|
||||||
|
* Called to show image in gallery application
|
||||||
|
* @param context context of application
|
||||||
|
* @param path path of file
|
||||||
|
*/
|
||||||
|
private fun showImage(context: Context, path: String) {
|
||||||
val intent = Intent().apply {
|
val intent = Intent().apply {
|
||||||
action = Intent.ACTION_VIEW
|
action = Intent.ACTION_VIEW
|
||||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK
|
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK
|
||||||
@ -51,17 +68,17 @@ class ImageNotificationReceiver : BroadcastReceiver() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val ACTION_SHARE_IMAGE = "eu.kanade.SHARE_IMAGE"
|
private const val ACTION_SHARE_IMAGE = "eu.kanade.SHARE_IMAGE"
|
||||||
|
|
||||||
const val ACTION_SHOW_IMAGE = "eu.kanade.SHOW_IMAGE"
|
private const val ACTION_SHOW_IMAGE = "eu.kanade.SHOW_IMAGE"
|
||||||
|
|
||||||
const val ACTION_DELETE_IMAGE = "eu.kanade.DELETE_IMAGE"
|
private const val ACTION_DELETE_IMAGE = "eu.kanade.DELETE_IMAGE"
|
||||||
|
|
||||||
const val EXTRA_FILE_LOCATION = "file_location"
|
private const val EXTRA_FILE_LOCATION = "file_location"
|
||||||
|
|
||||||
const val NOTIFICATION_ID = "notification_id"
|
private const val NOTIFICATION_ID = "notification_id"
|
||||||
|
|
||||||
fun shareImageIntent(context: Context, path: String, notificationId: Int): PendingIntent {
|
internal fun shareImageIntent(context: Context, path: String, notificationId: Int): PendingIntent {
|
||||||
val intent = Intent(context, ImageNotificationReceiver::class.java).apply {
|
val intent = Intent(context, ImageNotificationReceiver::class.java).apply {
|
||||||
action = ACTION_SHARE_IMAGE
|
action = ACTION_SHARE_IMAGE
|
||||||
putExtra(EXTRA_FILE_LOCATION, path)
|
putExtra(EXTRA_FILE_LOCATION, path)
|
||||||
@ -70,7 +87,7 @@ class ImageNotificationReceiver : BroadcastReceiver() {
|
|||||||
return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
|
return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun showImageIntent(context: Context, path: String): PendingIntent {
|
internal fun showImageIntent(context: Context, path: String): PendingIntent {
|
||||||
val intent = Intent(context, ImageNotificationReceiver::class.java).apply {
|
val intent = Intent(context, ImageNotificationReceiver::class.java).apply {
|
||||||
action = ACTION_SHOW_IMAGE
|
action = ACTION_SHOW_IMAGE
|
||||||
putExtra(EXTRA_FILE_LOCATION, path)
|
putExtra(EXTRA_FILE_LOCATION, path)
|
||||||
@ -78,7 +95,7 @@ class ImageNotificationReceiver : BroadcastReceiver() {
|
|||||||
return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
|
return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteImageIntent(context: Context, path: String, notificationId: Int): PendingIntent {
|
internal fun deleteImageIntent(context: Context, path: String, notificationId: Int): PendingIntent {
|
||||||
val intent = Intent(context, ImageNotificationReceiver::class.java).apply {
|
val intent = Intent(context, ImageNotificationReceiver::class.java).apply {
|
||||||
action = ACTION_DELETE_IMAGE
|
action = ACTION_DELETE_IMAGE
|
||||||
putExtra(EXTRA_FILE_LOCATION, path)
|
putExtra(EXTRA_FILE_LOCATION, path)
|
@ -0,0 +1,93 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.reader.notification
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.support.v4.app.NotificationCompat
|
||||||
|
import com.bumptech.glide.Glide
|
||||||
|
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||||
|
import com.bumptech.glide.request.animation.GlideAnimation
|
||||||
|
import com.bumptech.glide.request.target.SimpleTarget
|
||||||
|
import eu.kanade.tachiyomi.Constants
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.util.notificationManager
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class used to show BigPictureStyle notifications
|
||||||
|
*/
|
||||||
|
class ImageNotifier(private val context: Context) {
|
||||||
|
/**
|
||||||
|
* Notification builder.
|
||||||
|
*/
|
||||||
|
private val notificationBuilder = NotificationCompat.Builder(context)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Id of the notification.
|
||||||
|
*/
|
||||||
|
private val notificationId: Int
|
||||||
|
get() = Constants.NOTIFICATION_DOWNLOAD_IMAGE_ID
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when image download/copy is complete
|
||||||
|
* @param file image file containing downloaded page image
|
||||||
|
*/
|
||||||
|
fun onComplete(file: File) {
|
||||||
|
with(notificationBuilder) {
|
||||||
|
Glide.with(context).load(file).asBitmap().diskCacheStrategy(DiskCacheStrategy.NONE).skipMemoryCache(true).into(object : SimpleTarget<Bitmap>(720, 1280) {
|
||||||
|
/**
|
||||||
|
* The method that will be called when the resource load has finished.
|
||||||
|
* @param resource the loaded resource.
|
||||||
|
*/
|
||||||
|
override fun onResourceReady(resource: Bitmap?, glideAnimation: GlideAnimation<in Bitmap>?) {
|
||||||
|
setContentTitle(context.getString(R.string.picture_saved))
|
||||||
|
setSmallIcon(R.drawable.ic_insert_photo_white_24dp)
|
||||||
|
setStyle(NotificationCompat.BigPictureStyle().bigPicture(resource))
|
||||||
|
setLargeIcon(resource)
|
||||||
|
setAutoCancel(true)
|
||||||
|
// Clear old actions if they exist
|
||||||
|
if (!mActions.isEmpty())
|
||||||
|
mActions.clear()
|
||||||
|
|
||||||
|
setContentIntent(ImageNotificationReceiver.showImageIntent(context, file.absolutePath))
|
||||||
|
// Share action
|
||||||
|
addAction(R.drawable.ic_share_grey_24dp,
|
||||||
|
context.getString(R.string.action_share),
|
||||||
|
ImageNotificationReceiver.shareImageIntent(context, file.absolutePath, notificationId))
|
||||||
|
// Delete action
|
||||||
|
addAction(R.drawable.ic_delete_grey_24dp,
|
||||||
|
context.getString(R.string.action_delete),
|
||||||
|
ImageNotificationReceiver.deleteImageIntent(context, file.absolutePath, notificationId))
|
||||||
|
updateNotification()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the notification message
|
||||||
|
*/
|
||||||
|
fun onClear() {
|
||||||
|
context.notificationManager.cancel(notificationId)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateNotification() {
|
||||||
|
// Displays the progress bar on notification
|
||||||
|
context.notificationManager.notify(notificationId, notificationBuilder.build())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called on error while downloading image
|
||||||
|
* @param error string containing error information
|
||||||
|
*/
|
||||||
|
fun onError(error: String?) {
|
||||||
|
// Create notification
|
||||||
|
with(notificationBuilder) {
|
||||||
|
setContentTitle(context.getString(R.string.download_notifier_title_error))
|
||||||
|
setContentText(error ?: context.getString(R.string.unknown_error))
|
||||||
|
setSmallIcon(android.R.drawable.ic_menu_report_image)
|
||||||
|
}
|
||||||
|
updateNotification()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -11,6 +11,7 @@ import eu.kanade.tachiyomi.ui.reader.ReaderChapter
|
|||||||
import eu.kanade.tachiyomi.ui.reader.viewer.base.BaseReader
|
import eu.kanade.tachiyomi.ui.reader.viewer.base.BaseReader
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.pager.horizontal.LeftToRightReader
|
import eu.kanade.tachiyomi.ui.reader.viewer.pager.horizontal.LeftToRightReader
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.pager.horizontal.RightToLeftReader
|
import eu.kanade.tachiyomi.ui.reader.viewer.pager.horizontal.RightToLeftReader
|
||||||
|
import eu.kanade.tachiyomi.util.toast
|
||||||
import rx.subscriptions.CompositeSubscription
|
import rx.subscriptions.CompositeSubscription
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -187,8 +188,13 @@ abstract class PagerReader : BaseReader() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onLongPress(e: MotionEvent?) {
|
override fun onLongPress(e: MotionEvent?) {
|
||||||
super.onLongPress(e)
|
if (isAdded) {
|
||||||
readerActivity.onLongPress(adapter.pages!![pager.currentItem])
|
val page = adapter.pages.getOrNull(pager.currentItem)
|
||||||
|
if (page != null)
|
||||||
|
readerActivity.onLongPress(page)
|
||||||
|
else
|
||||||
|
context.toast(getString(R.string.unknown_error))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -6,9 +6,11 @@ import android.view.*
|
|||||||
import android.view.GestureDetector.SimpleOnGestureListener
|
import android.view.GestureDetector.SimpleOnGestureListener
|
||||||
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
|
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.source.model.Page
|
import eu.kanade.tachiyomi.data.source.model.Page
|
||||||
import eu.kanade.tachiyomi.ui.reader.ReaderChapter
|
import eu.kanade.tachiyomi.ui.reader.ReaderChapter
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.base.BaseReader
|
import eu.kanade.tachiyomi.ui.reader.viewer.base.BaseReader
|
||||||
|
import eu.kanade.tachiyomi.util.toast
|
||||||
import eu.kanade.tachiyomi.widget.PreCachingLayoutManager
|
import eu.kanade.tachiyomi.widget.PreCachingLayoutManager
|
||||||
import rx.subscriptions.CompositeSubscription
|
import rx.subscriptions.CompositeSubscription
|
||||||
|
|
||||||
@ -142,17 +144,21 @@ class WebtoonReader : BaseReader() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onLongPress(e: MotionEvent) {
|
override fun onLongPress(e: MotionEvent) {
|
||||||
super.onLongPress(e)
|
if (isAdded) {
|
||||||
val a = recycler.findChildViewUnder(e.rawX, e.rawY)
|
val child = recycler.findChildViewUnder(e.rawX, e.rawY)
|
||||||
val i = recycler.getChildAdapterPosition(a)
|
val position = recycler.getChildAdapterPosition(child)
|
||||||
readerActivity.onLongPress(adapter.getItem(i))
|
val page = adapter.pages?.getOrNull(position)
|
||||||
|
if (page != null)
|
||||||
|
readerActivity.onLongPress(page)
|
||||||
|
else
|
||||||
|
context.toast(getString(R.string.unknown_error))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when a new chapter is set in [BaseReader].
|
* Called when a new chapter is set in [BaseReader].
|
||||||
*
|
|
||||||
* @param chapter the chapter set.
|
* @param chapter the chapter set.
|
||||||
* @param currentPage the initial page to display.
|
* @param currentPage the initial page to display.
|
||||||
*/
|
*/
|
||||||
@ -167,7 +173,6 @@ class WebtoonReader : BaseReader() {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when a chapter is appended in [BaseReader].
|
* Called when a chapter is appended in [BaseReader].
|
||||||
*
|
|
||||||
* @param chapter the chapter appended.
|
* @param chapter the chapter appended.
|
||||||
*/
|
*/
|
||||||
override fun onChapterAppended(chapter: ReaderChapter) {
|
override fun onChapterAppended(chapter: ReaderChapter) {
|
||||||
@ -191,7 +196,6 @@ class WebtoonReader : BaseReader() {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the active page.
|
* Sets the active page.
|
||||||
*
|
|
||||||
* @param pageNumber the index of the page from [pages].
|
* @param pageNumber the index of the page from [pages].
|
||||||
*/
|
*/
|
||||||
override fun setActivePage(pageNumber: Int) {
|
override fun setActivePage(pageNumber: Int) {
|
||||||
|
BIN
app/src/main/res/drawable-hdpi/ic_delete_grey_24dp.png
Normal file
After Width: | Height: | Size: 485 B |
BIN
app/src/main/res/drawable-hdpi/ic_insert_photo_white_24dp.png
Normal file
After Width: | Height: | Size: 247 B |
BIN
app/src/main/res/drawable-hdpi/ic_share_grey_24dp.png
Normal file
After Width: | Height: | Size: 928 B |
BIN
app/src/main/res/drawable-mdpi/ic_delete_grey_24dp.png
Normal file
After Width: | Height: | Size: 367 B |
BIN
app/src/main/res/drawable-mdpi/ic_insert_photo_white_24dp.png
Normal file
After Width: | Height: | Size: 185 B |
BIN
app/src/main/res/drawable-mdpi/ic_share_grey_24dp.png
Normal file
After Width: | Height: | Size: 554 B |
BIN
app/src/main/res/drawable-xhdpi/ic_delete_grey_24dp.png
Normal file
After Width: | Height: | Size: 594 B |
BIN
app/src/main/res/drawable-xhdpi/ic_insert_photo_white_24dp.png
Normal file
After Width: | Height: | Size: 304 B |
BIN
app/src/main/res/drawable-xhdpi/ic_share_grey_24dp.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
app/src/main/res/drawable-xxhdpi/ic_delete_grey_24dp.png
Normal file
After Width: | Height: | Size: 695 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_insert_photo_white_24dp.png
Normal file
After Width: | Height: | Size: 450 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_share_grey_24dp.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
app/src/main/res/drawable-xxxhdpi/ic_delete_grey_24dp.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
app/src/main/res/drawable-xxxhdpi/ic_insert_photo_white_24dp.png
Normal file
After Width: | Height: | Size: 570 B |
BIN
app/src/main/res/drawable-xxxhdpi/ic_share_grey_24dp.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
@ -1,9 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportHeight="24.0"
|
|
||||||
android:viewportWidth="24.0">
|
|
||||||
<path
|
|
||||||
android:fillColor="#FF000000"
|
|
||||||
android:pathData="M21,19V5c0,-1.1 -0.9,-2 -2,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2zM8.5,13.5l2.5,3.01L14.5,12l4.5,6H5l3.5,-4.5z" />
|
|
||||||
</vector>
|
|
BIN
app/src/main/res/drawable/ic_insert_photo_white_24dp.png
Normal file
After Width: | Height: | Size: 185 B |
@ -48,8 +48,6 @@
|
|||||||
<string name="pref_download_only_over_wifi_key">pref_download_only_over_wifi_key</string>
|
<string name="pref_download_only_over_wifi_key">pref_download_only_over_wifi_key</string>
|
||||||
<string name="pref_remove_after_marked_as_read_key">pref_remove_after_marked_as_read_key</string>
|
<string name="pref_remove_after_marked_as_read_key">pref_remove_after_marked_as_read_key</string>
|
||||||
<string name="pref_category_remove_after_read_key">pref_category_remove_after_read_key</string>
|
<string name="pref_category_remove_after_read_key">pref_category_remove_after_read_key</string>
|
||||||
<string name="pref_notifications_single_page_key">notifications_single_page</string>
|
|
||||||
<string name="pref_notifications_manga_download_key">notifications_manga_download</string>
|
|
||||||
<string name="pref_last_used_category_key">last_used_category</string>
|
<string name="pref_last_used_category_key">last_used_category</string>
|
||||||
|
|
||||||
<string name="pref_source_languages">pref_source_languages</string>
|
<string name="pref_source_languages">pref_source_languages</string>
|
||||||
|
@ -267,11 +267,6 @@
|
|||||||
<string name="status">Status</string>
|
<string name="status">Status</string>
|
||||||
<string name="chapters">Chapters</string>
|
<string name="chapters">Chapters</string>
|
||||||
|
|
||||||
<!-- Reader Activity -->
|
|
||||||
<string name="custom_filter">Custom filter</string>
|
|
||||||
<string name="save_page">Download page</string>
|
|
||||||
<string name="set_as_cover">Set as cover</string>
|
|
||||||
<string name="cover_updated">Cover updated</string>
|
|
||||||
<!-- Dialog remove recently view -->
|
<!-- Dialog remove recently view -->
|
||||||
<string name="dialog_remove_recently_description">This will remove the read date of this chapter. Are you sure?</string>
|
<string name="dialog_remove_recently_description">This will remove the read date of this chapter. Are you sure?</string>
|
||||||
<string name="dialog_remove_recently_reset">Reset all chapters for this manga</string>
|
<string name="dialog_remove_recently_reset">Reset all chapters for this manga</string>
|
||||||
@ -279,6 +274,7 @@
|
|||||||
<!-- Image notifier -->
|
<!-- Image notifier -->
|
||||||
<string name="picture_saved">Picture saved</string>
|
<string name="picture_saved">Picture saved</string>
|
||||||
<string name="saving_picture">Saving picture</string>
|
<string name="saving_picture">Saving picture</string>
|
||||||
|
<string name="options">Options</string>
|
||||||
|
|
||||||
<!-- Reader activity -->
|
<!-- Reader activity -->
|
||||||
<string name="custom_filter">Custom filter</string>
|
<string name="custom_filter">Custom filter</string>
|
||||||
@ -315,6 +311,7 @@
|
|||||||
<string name="notification_no_new_chapters">No new chapters found</string>
|
<string name="notification_no_new_chapters">No new chapters found</string>
|
||||||
<string name="notification_new_chapters">New chapters found for:</string>
|
<string name="notification_new_chapters">New chapters found for:</string>
|
||||||
<string name="notification_manga_update_failed">Failed to update manga:</string>
|
<string name="notification_manga_update_failed">Failed to update manga:</string>
|
||||||
|
<string name="notification_cover_update_failed">Failed to update cover</string>
|
||||||
<string name="notification_first_add_to_library">Please add the manga to your library before doing this</string>
|
<string name="notification_first_add_to_library">Please add the manga to your library before doing this</string>
|
||||||
<string name="notification_not_connected_to_ac_title">Sync canceled</string>
|
<string name="notification_not_connected_to_ac_title">Sync canceled</string>
|
||||||
<string name="notification_not_connected_to_ac_body">Not connected to AC power</string>
|
<string name="notification_not_connected_to_ac_body">Not connected to AC power</string>
|
||||||
|