Minor cleanup
- Remove some unused StorIO queries - Clean up tall image splitting a bit (no need for creating an unscaled scaled bitmap copy, or tracking coordinates) - Clean up library updater a bit (still needs a lot of work though)
This commit is contained in:
parent
aa11902aa1
commit
a9e629aea6
9 changed files with 72 additions and 126 deletions
|
@ -72,12 +72,8 @@ interface ChapterQueries : DbProvider {
|
||||||
)
|
)
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
fun insertChapter(chapter: Chapter) = db.put().`object`(chapter).prepare()
|
|
||||||
|
|
||||||
fun insertChapters(chapters: List<Chapter>) = db.put().objects(chapters).prepare()
|
fun insertChapters(chapters: List<Chapter>) = db.put().objects(chapters).prepare()
|
||||||
|
|
||||||
fun deleteChapter(chapter: Chapter) = db.delete().`object`(chapter).prepare()
|
|
||||||
|
|
||||||
fun deleteChapters(chapters: List<Chapter>) = db.delete().objects(chapters).prepare()
|
fun deleteChapters(chapters: List<Chapter>) = db.delete().objects(chapters).prepare()
|
||||||
|
|
||||||
fun updateChaptersBackup(chapters: List<Chapter>) = db.put()
|
fun updateChaptersBackup(chapters: List<Chapter>) = db.put()
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package eu.kanade.tachiyomi.data.database.queries
|
package eu.kanade.tachiyomi.data.database.queries
|
||||||
|
|
||||||
import com.pushtorefresh.storio.sqlite.queries.DeleteQuery
|
|
||||||
import com.pushtorefresh.storio.sqlite.queries.RawQuery
|
import com.pushtorefresh.storio.sqlite.queries.RawQuery
|
||||||
import eu.kanade.tachiyomi.data.database.DbProvider
|
import eu.kanade.tachiyomi.data.database.DbProvider
|
||||||
import eu.kanade.tachiyomi.data.database.models.History
|
import eu.kanade.tachiyomi.data.database.models.History
|
||||||
|
@ -50,14 +49,4 @@ interface HistoryQueries : DbProvider {
|
||||||
.objects(historyList)
|
.objects(historyList)
|
||||||
.withPutResolver(HistoryUpsertResolver())
|
.withPutResolver(HistoryUpsertResolver())
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
fun deleteHistoryNoLastRead() = db.delete()
|
|
||||||
.byQuery(
|
|
||||||
DeleteQuery.builder()
|
|
||||||
.table(HistoryTable.TABLE)
|
|
||||||
.where("${HistoryTable.COL_LAST_READ} = ?")
|
|
||||||
.whereArgs(0)
|
|
||||||
.build(),
|
|
||||||
)
|
|
||||||
.prepare()
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -131,10 +131,6 @@ interface MangaQueries : DbProvider {
|
||||||
.withPutResolver(MangaCoverLastModifiedPutResolver())
|
.withPutResolver(MangaCoverLastModifiedPutResolver())
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
fun deleteManga(manga: Manga) = db.delete().`object`(manga).prepare()
|
|
||||||
|
|
||||||
fun deleteMangas(mangas: List<Manga>) = db.delete().objects(mangas).prepare()
|
|
||||||
|
|
||||||
fun deleteMangasNotInLibraryBySourceIds(sourceIds: List<Long>) = db.delete()
|
fun deleteMangasNotInLibraryBySourceIds(sourceIds: List<Long>) = db.delete()
|
||||||
.byQuery(
|
.byQuery(
|
||||||
DeleteQuery.builder()
|
DeleteQuery.builder()
|
||||||
|
@ -145,14 +141,6 @@ interface MangaQueries : DbProvider {
|
||||||
)
|
)
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
fun deleteMangas() = db.delete()
|
|
||||||
.byQuery(
|
|
||||||
DeleteQuery.builder()
|
|
||||||
.table(MangaTable.TABLE)
|
|
||||||
.build(),
|
|
||||||
)
|
|
||||||
.prepare()
|
|
||||||
|
|
||||||
fun getLastReadManga() = db.get()
|
fun getLastReadManga() = db.get()
|
||||||
.listOfObjects(Manga::class.java)
|
.listOfObjects(Manga::class.java)
|
||||||
.withQuery(
|
.withQuery(
|
||||||
|
|
|
@ -4,7 +4,6 @@ import android.content.Context
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
import android.webkit.MimeTypeMap
|
import android.webkit.MimeTypeMap
|
||||||
import androidx.core.graphics.BitmapCompat
|
|
||||||
import com.hippo.unifile.UniFile
|
import com.hippo.unifile.UniFile
|
||||||
import com.jakewharton.rxrelay.BehaviorRelay
|
import com.jakewharton.rxrelay.BehaviorRelay
|
||||||
import com.jakewharton.rxrelay.PublishRelay
|
import com.jakewharton.rxrelay.PublishRelay
|
||||||
|
@ -44,7 +43,6 @@ import uy.kohesive.injekt.injectLazy
|
||||||
import java.io.BufferedOutputStream
|
import java.io.BufferedOutputStream
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
import java.io.OutputStream
|
|
||||||
import java.util.zip.CRC32
|
import java.util.zip.CRC32
|
||||||
import java.util.zip.ZipEntry
|
import java.util.zip.ZipEntry
|
||||||
import java.util.zip.ZipOutputStream
|
import java.util.zip.ZipOutputStream
|
||||||
|
@ -354,7 +352,7 @@ class Downloader(
|
||||||
// Do when page is downloaded.
|
// Do when page is downloaded.
|
||||||
.doOnNext { page ->
|
.doOnNext { page ->
|
||||||
if (preferences.splitTallImages().get()) {
|
if (preferences.splitTallImages().get()) {
|
||||||
splitTallImage(page, download, tmpDir)
|
splitTallImage(page, tmpDir)
|
||||||
}
|
}
|
||||||
notifier.onProgressChange(download)
|
notifier.onProgressChange(download)
|
||||||
}
|
}
|
||||||
|
@ -560,53 +558,41 @@ class Downloader(
|
||||||
/**
|
/**
|
||||||
* Splits tall images to improve performance of reader
|
* Splits tall images to improve performance of reader
|
||||||
*/
|
*/
|
||||||
private fun splitTallImage(page: Page, download: Download, tmpDir: UniFile) {
|
private fun splitTallImage(page: Page, tmpDir: UniFile) {
|
||||||
val filename = String.format("%03d", page.number)
|
val filename = String.format("%03d", page.number)
|
||||||
val imageFile = tmpDir.listFiles()!!.find { it.name!!.startsWith("$filename.") }
|
val imageFile = tmpDir.listFiles()?.find { it.name!!.startsWith("$filename.") }
|
||||||
if (imageFile == null) {
|
?: throw Error(context.getString(R.string.download_notifier_split_page_not_found, page.number))
|
||||||
notifier.onError("Error: imageFile was not found", download.chapter.name, download.manga.title)
|
|
||||||
|
if (isAnimatedAndSupported(imageFile.openInputStream()) || !isTallImage(imageFile.openInputStream())) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isAnimatedAndSupported(imageFile.openInputStream()) && isTallImage(imageFile.openInputStream())) {
|
|
||||||
// Getting the scaled bitmap of the source image
|
|
||||||
val bitmap = BitmapFactory.decodeFile(imageFile.filePath)
|
val bitmap = BitmapFactory.decodeFile(imageFile.filePath)
|
||||||
val scaledBitmap: Bitmap =
|
val splitsCount = bitmap.height / context.resources.displayMetrics.heightPixels + 1
|
||||||
BitmapCompat.createScaledBitmap(bitmap, bitmap.width, bitmap.height, null, true)
|
val heightPerSplit = bitmap.height / splitsCount
|
||||||
|
|
||||||
val splitsCount: Int = bitmap.height / context.resources.displayMetrics.heightPixels + 1
|
|
||||||
val splitHeight = bitmap.height / splitsCount
|
|
||||||
|
|
||||||
// xCoord and yCoord are the pixel positions of the image splits
|
|
||||||
val xCoord = 0
|
|
||||||
var yCoord = 0
|
|
||||||
try {
|
try {
|
||||||
for (i in 0 until splitsCount) {
|
(0..splitsCount).forEach { split ->
|
||||||
val splitPath = imageFile.filePath!!.substringBeforeLast(".") + "__${"%03d".format(i + 1)}.jpg"
|
val splitPath = imageFile.filePath!!.substringBeforeLast(".") + "__${"%03d".format(split + 1)}.jpg"
|
||||||
// Compress the bitmap and save in jpg format
|
FileOutputStream(splitPath).use { stream ->
|
||||||
val stream: OutputStream = FileOutputStream(splitPath)
|
|
||||||
stream.use {
|
|
||||||
Bitmap.createBitmap(
|
Bitmap.createBitmap(
|
||||||
scaledBitmap,
|
bitmap,
|
||||||
xCoord,
|
0,
|
||||||
yCoord,
|
split * heightPerSplit,
|
||||||
bitmap.width,
|
bitmap.width,
|
||||||
splitHeight,
|
heightPerSplit,
|
||||||
).compress(Bitmap.CompressFormat.JPEG, 100, stream)
|
).compress(Bitmap.CompressFormat.JPEG, 100, stream)
|
||||||
}
|
}
|
||||||
yCoord += splitHeight
|
|
||||||
}
|
}
|
||||||
imageFile.delete()
|
imageFile.delete()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
// Image splits were not successfully saved so delete them and keep the original image
|
// Image splits were not successfully saved so delete them and keep the original image
|
||||||
for (i in 0 until splitsCount) {
|
(0..splitsCount)
|
||||||
val splitPath = imageFile.filePath!!.substringBeforeLast(".") + "__${"%03d".format(i + 1)}.jpg"
|
.map { imageFile.filePath!!.substringBeforeLast(".") + "__${"%03d".format(it + 1)}.jpg" }
|
||||||
File(splitPath).delete()
|
.forEach { File(it).delete() }
|
||||||
}
|
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Completes a download. This method is called in the main thread.
|
* Completes a download. This method is called in the main thread.
|
||||||
|
|
|
@ -28,6 +28,7 @@ import eu.kanade.tachiyomi.data.track.TrackService
|
||||||
import eu.kanade.tachiyomi.source.SourceManager
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
import eu.kanade.tachiyomi.source.UnmeteredSource
|
import eu.kanade.tachiyomi.source.UnmeteredSource
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
|
import eu.kanade.tachiyomi.source.model.toMangaInfo
|
||||||
import eu.kanade.tachiyomi.source.model.toSChapter
|
import eu.kanade.tachiyomi.source.model.toSChapter
|
||||||
import eu.kanade.tachiyomi.source.model.toSManga
|
import eu.kanade.tachiyomi.source.model.toSManga
|
||||||
import eu.kanade.tachiyomi.util.chapter.NoChaptersException
|
import eu.kanade.tachiyomi.util.chapter.NoChaptersException
|
||||||
|
@ -80,7 +81,7 @@ class LibraryUpdateService(
|
||||||
|
|
||||||
private lateinit var wakeLock: PowerManager.WakeLock
|
private lateinit var wakeLock: PowerManager.WakeLock
|
||||||
private lateinit var notifier: LibraryUpdateNotifier
|
private lateinit var notifier: LibraryUpdateNotifier
|
||||||
private lateinit var ioScope: CoroutineScope
|
private var ioScope: CoroutineScope? = null
|
||||||
|
|
||||||
private var mangaToUpdate: List<LibraryManga> = mutableListOf()
|
private var mangaToUpdate: List<LibraryManga> = mutableListOf()
|
||||||
private var updateJob: Job? = null
|
private var updateJob: Job? = null
|
||||||
|
@ -90,10 +91,8 @@ class LibraryUpdateService(
|
||||||
*/
|
*/
|
||||||
enum class Target {
|
enum class Target {
|
||||||
CHAPTERS, // Manga chapters
|
CHAPTERS, // Manga chapters
|
||||||
|
|
||||||
COVERS, // Manga covers
|
COVERS, // Manga covers
|
||||||
|
TRACKING, // Tracking metadata
|
||||||
TRACKING // Tracking metadata
|
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -161,7 +160,6 @@ class LibraryUpdateService(
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
|
|
||||||
ioScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
|
||||||
notifier = LibraryUpdateNotifier(this)
|
notifier = LibraryUpdateNotifier(this)
|
||||||
wakeLock = acquireWakeLock(javaClass.name)
|
wakeLock = acquireWakeLock(javaClass.name)
|
||||||
|
|
||||||
|
@ -174,8 +172,6 @@ class LibraryUpdateService(
|
||||||
*/
|
*/
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
updateJob?.cancel()
|
updateJob?.cancel()
|
||||||
// Despite what Android Studio
|
|
||||||
// states this can be null
|
|
||||||
ioScope?.cancel()
|
ioScope?.cancel()
|
||||||
if (wakeLock.isHeld) {
|
if (wakeLock.isHeld) {
|
||||||
wakeLock.release()
|
wakeLock.release()
|
||||||
|
@ -189,9 +185,7 @@ class LibraryUpdateService(
|
||||||
/**
|
/**
|
||||||
* This method needs to be implemented, but it's not used/needed.
|
* This method needs to be implemented, but it's not used/needed.
|
||||||
*/
|
*/
|
||||||
override fun onBind(intent: Intent): IBinder? {
|
override fun onBind(intent: Intent): IBinder? = null
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method called when the service receives an intent.
|
* Method called when the service receives an intent.
|
||||||
|
@ -210,6 +204,7 @@ class LibraryUpdateService(
|
||||||
|
|
||||||
// Unsubscribe from any previous subscription if needed
|
// Unsubscribe from any previous subscription if needed
|
||||||
updateJob?.cancel()
|
updateJob?.cancel()
|
||||||
|
ioScope?.cancel()
|
||||||
|
|
||||||
// Update favorite manga
|
// Update favorite manga
|
||||||
val categoryId = intent.getIntExtra(KEY_CATEGORY, -1)
|
val categoryId = intent.getIntExtra(KEY_CATEGORY, -1)
|
||||||
|
@ -220,7 +215,8 @@ class LibraryUpdateService(
|
||||||
logcat(LogPriority.ERROR, exception)
|
logcat(LogPriority.ERROR, exception)
|
||||||
stopSelf(startId)
|
stopSelf(startId)
|
||||||
}
|
}
|
||||||
updateJob = ioScope.launch(handler) {
|
ioScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
||||||
|
updateJob = ioScope?.launch(handler) {
|
||||||
when (target) {
|
when (target) {
|
||||||
Target.CHAPTERS -> updateChapterList()
|
Target.CHAPTERS -> updateChapterList()
|
||||||
Target.COVERS -> updateCovers()
|
Target.COVERS -> updateCovers()
|
||||||
|
@ -344,16 +340,10 @@ class LibraryUpdateService(
|
||||||
}
|
}
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
val errorMessage = when (e) {
|
val errorMessage = when (e) {
|
||||||
is NoChaptersException -> {
|
is NoChaptersException -> getString(R.string.no_chapters_error)
|
||||||
getString(R.string.no_chapters_error)
|
|
||||||
}
|
|
||||||
is SourceManager.SourceNotInstalledException -> {
|
|
||||||
// failedUpdates will already have the source, don't need to copy it into the message
|
// failedUpdates will already have the source, don't need to copy it into the message
|
||||||
getString(R.string.loader_not_implemented_error)
|
is SourceManager.SourceNotInstalledException -> getString(R.string.loader_not_implemented_error)
|
||||||
}
|
else -> e.message
|
||||||
else -> {
|
|
||||||
e.message
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
failedUpdates.add(mangaWithNotif to errorMessage)
|
failedUpdates.add(mangaWithNotif to errorMessage)
|
||||||
}
|
}
|
||||||
|
@ -407,11 +397,12 @@ class LibraryUpdateService(
|
||||||
private suspend fun updateManga(manga: Manga): Pair<List<Chapter>, List<Chapter>> {
|
private suspend fun updateManga(manga: Manga): Pair<List<Chapter>, List<Chapter>> {
|
||||||
val source = sourceManager.getOrStub(manga.source)
|
val source = sourceManager.getOrStub(manga.source)
|
||||||
|
|
||||||
var networkSManga: SManga? = null
|
var updatedManga: SManga = manga
|
||||||
|
|
||||||
// Update manga details metadata
|
// Update manga details metadata
|
||||||
if (preferences.autoUpdateMetadata()) {
|
if (preferences.autoUpdateMetadata()) {
|
||||||
val updatedManga = source.getMangaDetails(manga.toMangaInfo())
|
val updatedMangaDetails = source.getMangaDetails(manga.toMangaInfo())
|
||||||
val sManga = updatedManga.toSManga()
|
val sManga = updatedMangaDetails.toSManga()
|
||||||
// Avoid "losing" existing cover
|
// Avoid "losing" existing cover
|
||||||
if (!sManga.thumbnail_url.isNullOrEmpty()) {
|
if (!sManga.thumbnail_url.isNullOrEmpty()) {
|
||||||
manga.prepUpdateCover(coverCache, sManga, false)
|
manga.prepUpdateCover(coverCache, sManga, false)
|
||||||
|
@ -419,22 +410,19 @@ class LibraryUpdateService(
|
||||||
sManga.thumbnail_url = manga.thumbnail_url
|
sManga.thumbnail_url = manga.thumbnail_url
|
||||||
}
|
}
|
||||||
|
|
||||||
networkSManga = sManga
|
updatedManga = sManga
|
||||||
}
|
}
|
||||||
|
|
||||||
val chapters = source.getChapterList(manga.toMangaInfo())
|
val chapters = source.getChapterList(updatedManga.toMangaInfo())
|
||||||
.map { it.toSChapter() }
|
.map { it.toSChapter() }
|
||||||
|
|
||||||
// Get manga from database to account for if it was removed
|
// Get manga from database to account for if it was removed during the update
|
||||||
// from library or database
|
|
||||||
val dbManga = db.getManga(manga.id!!).executeAsBlocking()
|
val dbManga = db.getManga(manga.id!!).executeAsBlocking()
|
||||||
?: return Pair(emptyList(), emptyList())
|
?: return Pair(emptyList(), emptyList())
|
||||||
|
|
||||||
// Copy into [dbManga] to retain favourite value
|
// Copy into [dbManga] to retain favourite value
|
||||||
networkSManga?.let {
|
dbManga.copyFrom(updatedManga)
|
||||||
dbManga.copyFrom(it)
|
|
||||||
db.insertManga(dbManga).executeAsBlocking()
|
db.insertManga(dbManga).executeAsBlocking()
|
||||||
}
|
|
||||||
|
|
||||||
// [dbmanga] was used so that manga data doesn't get overwritten
|
// [dbmanga] was used so that manga data doesn't get overwritten
|
||||||
// in case manga gets new chapter
|
// in case manga gets new chapter
|
||||||
|
|
|
@ -247,7 +247,7 @@ class PagerPageHolder(
|
||||||
return splitInHalf(imageStream)
|
return splitInHalf(imageStream)
|
||||||
}
|
}
|
||||||
|
|
||||||
val isDoublePage = ImageUtil.isDoublePage(imageStream)
|
val isDoublePage = ImageUtil.isWideImage(imageStream)
|
||||||
if (!isDoublePage) {
|
if (!isDoublePage) {
|
||||||
return imageStream
|
return imageStream
|
||||||
}
|
}
|
||||||
|
|
|
@ -277,7 +277,7 @@ class WebtoonPageHolder(
|
||||||
return imageStream
|
return imageStream
|
||||||
}
|
}
|
||||||
|
|
||||||
val isDoublePage = ImageUtil.isDoublePage(imageStream)
|
val isDoublePage = ImageUtil.isWideImage(imageStream)
|
||||||
if (!isDoublePage) {
|
if (!isDoublePage) {
|
||||||
return imageStream
|
return imageStream
|
||||||
}
|
}
|
||||||
|
|
|
@ -99,38 +99,24 @@ object ImageUtil {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check whether the image is a double-page spread
|
* Check whether the image is wide (which we consider a double-page spread).
|
||||||
|
*
|
||||||
* @return true if the width is greater than the height
|
* @return true if the width is greater than the height
|
||||||
*/
|
*/
|
||||||
fun isDoublePage(imageStream: InputStream): Boolean {
|
fun isWideImage(imageStream: InputStream): Boolean {
|
||||||
imageStream.mark(imageStream.available() + 1)
|
val options = extractImageOptions(imageStream)
|
||||||
|
|
||||||
val imageBytes = imageStream.readBytes()
|
|
||||||
|
|
||||||
val options = BitmapFactory.Options().apply { inJustDecodeBounds = true }
|
|
||||||
BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size, options)
|
|
||||||
|
|
||||||
imageStream.reset()
|
imageStream.reset()
|
||||||
|
|
||||||
return options.outWidth > options.outHeight
|
return options.outWidth > options.outHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check whether the image is considered a tall image
|
* Check whether the image is considered a tall image.
|
||||||
* @return true if the height:width ratio is greater than the 3:!
|
*
|
||||||
|
* @return true if the height:width ratio is greater than 3.
|
||||||
*/
|
*/
|
||||||
fun isTallImage(imageStream: InputStream): Boolean {
|
fun isTallImage(imageStream: InputStream): Boolean {
|
||||||
imageStream.mark(imageStream.available() + 1)
|
val options = extractImageOptions(imageStream)
|
||||||
|
return (options.outHeight / options.outWidth) > 3
|
||||||
val imageBytes = imageStream.readBytes()
|
|
||||||
// Checking the image dimensions without loading it in the memory.
|
|
||||||
val options = BitmapFactory.Options().apply { inJustDecodeBounds = true }
|
|
||||||
BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size, options)
|
|
||||||
val width = options.outWidth
|
|
||||||
val height = options.outHeight
|
|
||||||
val ratio = height / width
|
|
||||||
|
|
||||||
return ratio > 3
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -410,4 +396,16 @@ object ImageUtil {
|
||||||
|
|
||||||
private fun Int.isWhite(): Boolean =
|
private fun Int.isWhite(): Boolean =
|
||||||
red + blue + green > 740
|
red + blue + green > 740
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to check an image's dimensions without loading it in the memory.
|
||||||
|
*/
|
||||||
|
private fun extractImageOptions(imageStream: InputStream): BitmapFactory.Options {
|
||||||
|
imageStream.mark(imageStream.available() + 1)
|
||||||
|
|
||||||
|
val imageBytes = imageStream.readBytes()
|
||||||
|
val options = BitmapFactory.Options().apply { inJustDecodeBounds = true }
|
||||||
|
BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size, options)
|
||||||
|
return options
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -814,6 +814,7 @@
|
||||||
<string name="download_notifier_no_network">No network connection available</string>
|
<string name="download_notifier_no_network">No network connection available</string>
|
||||||
<string name="download_notifier_download_paused">Download paused</string>
|
<string name="download_notifier_download_paused">Download paused</string>
|
||||||
<string name="download_notifier_download_finish">Download completed</string>
|
<string name="download_notifier_download_finish">Download completed</string>
|
||||||
|
<string name="download_notifier_split_page_not_found">Page %d not found while splitting</string>
|
||||||
|
|
||||||
<!-- Notification channels -->
|
<!-- Notification channels -->
|
||||||
<string name="channel_common">Common</string>
|
<string name="channel_common">Common</string>
|
||||||
|
|
Reference in a new issue