Tracking manga updates

This commit is contained in:
Koitharu
2020-03-29 17:32:52 +03:00
parent 80c8344f8d
commit aad26d24ec
12 changed files with 116 additions and 22 deletions

View File

@@ -1,12 +1,19 @@
package org.koitharu.kotatsu.domain package org.koitharu.kotatsu.domain
import android.graphics.Bitmap
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.media.ThumbnailUtils
import android.util.Size import android.util.Size
import androidx.annotation.Px
import androidx.core.graphics.drawable.toBitmap
import coil.Coil
import coil.api.get
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import org.koin.core.KoinComponent import org.koin.core.KoinComponent
import org.koin.core.get import org.koin.core.get
import org.koitharu.kotatsu.BuildConfig import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.core.model.MangaPage import org.koitharu.kotatsu.core.model.MangaPage
import org.koitharu.kotatsu.core.prefs.ReaderMode import org.koitharu.kotatsu.core.prefs.ReaderMode
import org.koitharu.kotatsu.utils.ext.await import org.koitharu.kotatsu.utils.ext.await
@@ -54,4 +61,23 @@ object MangaUtils : KoinComponent {
check(imageHeight > 0 && imageWidth > 0) check(imageHeight > 0 && imageWidth > 0)
return Size(imageWidth, imageHeight) return Size(imageWidth, imageHeight)
} }
suspend fun getMangaIcon(manga: Manga, @Px width: Int, @Px height: Int): Bitmap? {
try {
val bmp = Coil.loader().get(manga.coverUrl) {
size(width, height)
}.toBitmap()
return ThumbnailUtils.extractThumbnail(
bmp,
width,
height,
ThumbnailUtils.OPTIONS_RECYCLE_INPUT
)
} catch (e: Throwable) {
if (BuildConfig.DEBUG) {
e.printStackTrace()
}
return null
}
}
} }

View File

@@ -5,10 +5,10 @@ import android.app.NotificationChannel
import android.app.NotificationManager import android.app.NotificationManager
import android.app.PendingIntent import android.app.PendingIntent
import android.content.Context import android.content.Context
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.os.Build import android.os.Build
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.graphics.drawable.toBitmap
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.ui.details.MangaDetailsActivity import org.koitharu.kotatsu.ui.details.MangaDetailsActivity
@@ -73,7 +73,7 @@ class DownloadNotification(private val context: Context) {
} }
fun setLargeIcon(icon: Drawable?) { fun setLargeIcon(icon: Drawable?) {
builder.setLargeIcon((icon as? BitmapDrawable)?.bitmap) builder.setLargeIcon(icon?.toBitmap())
} }
fun setProgress(chaptersTotal: Int, pagesTotal: Int, chapter: Int, page: Int) { fun setProgress(chaptersTotal: Int, pagesTotal: Int, chapter: Int, page: Int) {

View File

@@ -4,7 +4,6 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.ConnectivityManager import android.net.ConnectivityManager
import android.os.PowerManager import android.os.PowerManager
import android.os.WorkSource
import android.webkit.MimeTypeMap import android.webkit.MimeTypeMap
import android.widget.Toast import android.widget.Toast
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
@@ -14,7 +13,7 @@ import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import org.koin.core.inject import org.koin.android.ext.android.inject
import org.koitharu.kotatsu.BuildConfig import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.local.PagesCache import org.koitharu.kotatsu.core.local.PagesCache

View File

@@ -2,15 +2,19 @@ package org.koitharu.kotatsu.ui.tracker
import android.app.NotificationChannel import android.app.NotificationChannel
import android.app.NotificationManager import android.app.NotificationManager
import android.app.PendingIntent
import android.app.job.JobInfo import android.app.job.JobInfo
import android.app.job.JobParameters import android.app.job.JobParameters
import android.app.job.JobScheduler import android.app.job.JobScheduler
import android.content.ComponentName import android.content.ComponentName
import android.content.Context import android.content.Context
import android.os.Build import android.os.Build
import androidx.annotation.MainThread
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.toBitmap
import coil.Coil
import coil.api.get
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
@@ -19,20 +23,19 @@ import org.koitharu.kotatsu.core.model.MangaChapter
import org.koitharu.kotatsu.domain.MangaProviderFactory import org.koitharu.kotatsu.domain.MangaProviderFactory
import org.koitharu.kotatsu.domain.tracking.TrackingRepository import org.koitharu.kotatsu.domain.tracking.TrackingRepository
import org.koitharu.kotatsu.ui.common.BaseJobService import org.koitharu.kotatsu.ui.common.BaseJobService
import org.koitharu.kotatsu.ui.details.MangaDetailsActivity
import org.koitharu.kotatsu.utils.ext.safe import org.koitharu.kotatsu.utils.ext.safe
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
class TrackerJobService : BaseJobService() { class TrackerJobService : BaseJobService() {
private lateinit var repo: TrackingRepository private val notificationManager by lazy(LazyThreadSafetyMode.NONE) {
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
override fun onCreate() {
super.onCreate()
repo = TrackingRepository()
} }
override suspend fun doWork(params: JobParameters) { override suspend fun doWork(params: JobParameters) {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
val repo = TrackingRepository()
val tracks = repo.getAllTracks() val tracks = repo.getAllTracks()
if (tracks.isEmpty()) { if (tracks.isEmpty()) {
return@withContext return@withContext
@@ -53,14 +56,16 @@ class TrackerJobService : BaseJobService() {
newChapters = 0 newChapters = 0
) )
} }
track.knownChaptersCount == 0 && track.lastChapterId == 0L -> { //manga was empty on last check track.knownChaptersCount == 0 && track.lastChapterId == 0L -> { //manga was empty on last check
repo.storeTrackResult( repo.storeTrackResult(
mangaId = track.manga.id, mangaId = track.manga.id,
knownChaptersCount = track.knownChaptersCount, knownChaptersCount = track.knownChaptersCount,
lastChapterId = 0L, lastChapterId = 0L,
newChapters = chapters.size newChapters = chapters.size
) )
//TODO notify if (chapters.isNotEmpty()) {
showNotification(track.manga, chapters)
}
} }
chapters.size == track.knownChaptersCount -> { chapters.size == track.knownChaptersCount -> {
if (chapters.lastOrNull()?.id == track.lastChapterId) { if (chapters.lastOrNull()?.id == track.lastChapterId) {
@@ -78,24 +83,30 @@ class TrackerJobService : BaseJobService() {
newChapters = 0 newChapters = 0
) )
} else { } else {
val newChapters = chapters.size - knownChapter + 1
repo.storeTrackResult( repo.storeTrackResult(
mangaId = track.manga.id, mangaId = track.manga.id,
knownChaptersCount = knownChapter + 1, knownChaptersCount = knownChapter + 1,
lastChapterId = track.lastChapterId, lastChapterId = track.lastChapterId,
newChapters = chapters.size - knownChapter + 1 newChapters = newChapters
) )
//TODO notify if (newChapters != 0) {
showNotification(track.manga, chapters.takeLast(newChapters))
}
} }
} }
} }
else -> { else -> {
val newChapters = chapters.size - track.knownChaptersCount
repo.storeTrackResult( repo.storeTrackResult(
mangaId = track.manga.id, mangaId = track.manga.id,
knownChaptersCount = track.knownChaptersCount, knownChaptersCount = track.knownChaptersCount,
lastChapterId = track.lastChapterId, lastChapterId = track.lastChapterId,
newChapters = chapters.size - track.knownChaptersCount newChapters = newChapters
) )
//TODO notify if (newChapters != 0) {
showNotification(track.manga, chapters.takeLast(newChapters))
}
} }
} }
success++ success++
@@ -106,24 +117,55 @@ class TrackerJobService : BaseJobService() {
} }
} }
@MainThread private suspend fun showNotification(manga: Manga, newChapters: List<MangaChapter>) {
private fun showNotification(manga: Manga, newChapters: List<MangaChapter>) { val id = manga.url.hashCode()
//TODO val colorPrimary = ContextCompat.getColor(this@TrackerJobService, R.color.blue_primary)
val builder = NotificationCompat.Builder(this, CHANNEL_ID)
with(builder) {
setContentText(resources.getQuantityString(R.plurals.new_chapters,
newChapters.size, newChapters.size))
setContentText(manga.title)
setNumber(newChapters.size)
setLargeIcon(safe {
Coil.loader().get(manga.coverUrl).toBitmap()
})
setSmallIcon(R.drawable.ic_stat_book_plus)
val style = NotificationCompat.InboxStyle(this)
for (chapter in newChapters) {
style.addLine(chapter.name)
}
style.setSummaryText(manga.title)
setStyle(style)
val intent = MangaDetailsActivity.newIntent(this@TrackerJobService, manga)
setContentIntent(PendingIntent.getActivity(this@TrackerJobService, id,
intent, PendingIntent.FLAG_UPDATE_CURRENT))
setAutoCancel(true)
color = colorPrimary
setLights(colorPrimary, 1000, 5000)
setPriority(NotificationCompat.PRIORITY_DEFAULT)
}
withContext(Dispatchers.Main) {
notificationManager.notify(TAG, id, builder.build())
}
} }
companion object { companion object {
private const val JOB_ID = 7 private const val JOB_ID = 7
private const val CHANNEL_ID = "tracking" private const val CHANNEL_ID = "tracking"
private const val TAG = "tracking"
@RequiresApi(Build.VERSION_CODES.O) @RequiresApi(Build.VERSION_CODES.O)
private fun createNotificationChannel(context: Context) { private fun createNotificationChannel(context: Context) {
val manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager val manager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
if (manager.getNotificationChannel(CHANNEL_ID) == null) { if (manager.getNotificationChannel(CHANNEL_ID) == null) {
val channel = NotificationChannel(CHANNEL_ID, val channel = NotificationChannel(CHANNEL_ID,
context.getString(R.string.new_chapters), NotificationManager.IMPORTANCE_DEFAULT) context.getString(R.string.new_chapters),
NotificationManager.IMPORTANCE_DEFAULT)
channel.setShowBadge(true) channel.setShowBadge(true)
channel.lightColor = ContextCompat.getColor(context, R.color.blue_primary) channel.lightColor = ContextCompat.getColor(context, R.color.blue_primary)
channel.enableLights(true)
manager.createNotificationChannel(channel) manager.createNotificationChannel(channel)
} }
} }
@@ -136,7 +178,8 @@ class TrackerJobService : BaseJobService() {
// if (scheduler.allPendingJobs != null) { // if (scheduler.allPendingJobs != null) {
// return // return
// } // }
val jobInfo = JobInfo.Builder(JOB_ID, ComponentName(context, TrackerJobService::class.java)) val jobInfo =
JobInfo.Builder(JOB_ID, ComponentName(context, TrackerJobService::class.java))
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
jobInfo.setRequiredNetworkType(JobInfo.NETWORK_TYPE_NOT_ROAMING) jobInfo.setRequiredNetworkType(JobInfo.NETWORK_TYPE_NOT_ROAMING)
} else { } else {

View File

@@ -0,0 +1,17 @@
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="#FFFFFF"
android:viewportWidth="24"
android:viewportHeight="24">
<group
android:scaleX="0.92"
android:scaleY="0.92"
android:translateX="0.96"
android:translateY="0.96">
<path
android:fillColor="#FF000000"
android:pathData="M18,22H6A2,2 0,0 1,4 20V4C4,2.89 4.9,2 6,2H7V9L9.5,7.5L12,9V2H18A2,2 0,0 1,20 4V20A2,2 0,0 1,18 22M14,20H16V18H18V16H16V14H14V16H12V18H14V20Z" />
</group>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 336 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 289 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 480 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 629 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 914 B

View File

@@ -10,4 +10,9 @@
<item quantity="few">%1$d элемента</item> <item quantity="few">%1$d элемента</item>
<item quantity="many">%1$d элементов</item> <item quantity="many">%1$d элементов</item>
</plurals> </plurals>
<plurals name="new_chapters">
<item quantity="one">%1$d новая глава</item>
<item quantity="few">%1$d новых главы</item>
<item quantity="many">%1$d новых глав</item>
</plurals>
</resources> </resources>

View File

@@ -8,4 +8,8 @@
<item quantity="one">%1$d item</item> <item quantity="one">%1$d item</item>
<item quantity="other">%1$d items</item> <item quantity="other">%1$d items</item>
</plurals> </plurals>
<plurals name="new_chapters">
<item quantity="one">%1$d new chapter</item>
<item quantity="other">%1$d new chapters</item>
</plurals>
</resources> </resources>