From aad26d24eca660e87dcbf1e10da805252e3dd9f7 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Sun, 29 Mar 2020 17:32:52 +0300 Subject: [PATCH] Tracking manga updates --- .../org/koitharu/kotatsu/domain/MangaUtils.kt | 26 ++++++ .../ui/download/DownloadNotification.kt | 4 +- .../kotatsu/ui/download/DownloadService.kt | 3 +- .../kotatsu/ui/tracker/TrackerJobService.kt | 79 ++++++++++++++---- .../drawable-anydpi-v24/ic_stat_book_plus.xml | 17 ++++ .../res/drawable-hdpi/ic_stat_book_plus.png | Bin 0 -> 336 bytes .../res/drawable-mdpi/ic_stat_book_plus.png | Bin 0 -> 289 bytes .../res/drawable-xhdpi/ic_stat_book_plus.png | Bin 0 -> 480 bytes .../res/drawable-xxhdpi/ic_stat_book_plus.png | Bin 0 -> 629 bytes .../drawable-xxxhdpi/ic_stat_book_plus.png | Bin 0 -> 914 bytes app/src/main/res/values-ru/plurals.xml | 5 ++ app/src/main/res/values/plurals.xml | 4 + 12 files changed, 116 insertions(+), 22 deletions(-) create mode 100644 app/src/main/res/drawable-anydpi-v24/ic_stat_book_plus.xml create mode 100644 app/src/main/res/drawable-hdpi/ic_stat_book_plus.png create mode 100644 app/src/main/res/drawable-mdpi/ic_stat_book_plus.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_stat_book_plus.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_stat_book_plus.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_stat_book_plus.png diff --git a/app/src/main/java/org/koitharu/kotatsu/domain/MangaUtils.kt b/app/src/main/java/org/koitharu/kotatsu/domain/MangaUtils.kt index 7a79c011a..86089ae6f 100644 --- a/app/src/main/java/org/koitharu/kotatsu/domain/MangaUtils.kt +++ b/app/src/main/java/org/koitharu/kotatsu/domain/MangaUtils.kt @@ -1,12 +1,19 @@ package org.koitharu.kotatsu.domain +import android.graphics.Bitmap import android.graphics.BitmapFactory +import android.media.ThumbnailUtils 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.Request import org.koin.core.KoinComponent import org.koin.core.get import org.koitharu.kotatsu.BuildConfig +import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.MangaPage import org.koitharu.kotatsu.core.prefs.ReaderMode import org.koitharu.kotatsu.utils.ext.await @@ -54,4 +61,23 @@ object MangaUtils : KoinComponent { check(imageHeight > 0 && imageWidth > 0) 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 + } + } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/download/DownloadNotification.kt b/app/src/main/java/org/koitharu/kotatsu/ui/download/DownloadNotification.kt index c75b7674c..825b4e5a2 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/download/DownloadNotification.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/download/DownloadNotification.kt @@ -5,10 +5,10 @@ import android.app.NotificationChannel import android.app.NotificationManager import android.app.PendingIntent import android.content.Context -import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.Drawable import android.os.Build import androidx.core.app.NotificationCompat +import androidx.core.graphics.drawable.toBitmap import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.ui.details.MangaDetailsActivity @@ -73,7 +73,7 @@ class DownloadNotification(private val context: Context) { } fun setLargeIcon(icon: Drawable?) { - builder.setLargeIcon((icon as? BitmapDrawable)?.bitmap) + builder.setLargeIcon(icon?.toBitmap()) } fun setProgress(chaptersTotal: Int, pagesTotal: Int, chapter: Int, page: Int) { diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/download/DownloadService.kt b/app/src/main/java/org/koitharu/kotatsu/ui/download/DownloadService.kt index a3de5540b..7ed72e49d 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/download/DownloadService.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/download/DownloadService.kt @@ -4,7 +4,6 @@ import android.content.Context import android.content.Intent import android.net.ConnectivityManager import android.os.PowerManager -import android.os.WorkSource import android.webkit.MimeTypeMap import android.widget.Toast import androidx.core.content.ContextCompat @@ -14,7 +13,7 @@ import kotlinx.coroutines.* import kotlinx.coroutines.sync.Mutex import okhttp3.OkHttpClient import okhttp3.Request -import org.koin.core.inject +import org.koin.android.ext.android.inject import org.koitharu.kotatsu.BuildConfig import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.local.PagesCache diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/tracker/TrackerJobService.kt b/app/src/main/java/org/koitharu/kotatsu/ui/tracker/TrackerJobService.kt index cdff3d53d..b091034cc 100644 --- a/app/src/main/java/org/koitharu/kotatsu/ui/tracker/TrackerJobService.kt +++ b/app/src/main/java/org/koitharu/kotatsu/ui/tracker/TrackerJobService.kt @@ -2,15 +2,19 @@ package org.koitharu.kotatsu.ui.tracker import android.app.NotificationChannel import android.app.NotificationManager +import android.app.PendingIntent import android.app.job.JobInfo import android.app.job.JobParameters import android.app.job.JobScheduler import android.content.ComponentName import android.content.Context import android.os.Build -import androidx.annotation.MainThread import androidx.annotation.RequiresApi +import androidx.core.app.NotificationCompat 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.withContext 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.tracking.TrackingRepository import org.koitharu.kotatsu.ui.common.BaseJobService +import org.koitharu.kotatsu.ui.details.MangaDetailsActivity import org.koitharu.kotatsu.utils.ext.safe import java.util.concurrent.TimeUnit class TrackerJobService : BaseJobService() { - private lateinit var repo: TrackingRepository - - override fun onCreate() { - super.onCreate() - repo = TrackingRepository() + private val notificationManager by lazy(LazyThreadSafetyMode.NONE) { + getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager } override suspend fun doWork(params: JobParameters) { withContext(Dispatchers.IO) { + val repo = TrackingRepository() val tracks = repo.getAllTracks() if (tracks.isEmpty()) { return@withContext @@ -53,14 +56,16 @@ class TrackerJobService : BaseJobService() { 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( mangaId = track.manga.id, knownChaptersCount = track.knownChaptersCount, lastChapterId = 0L, newChapters = chapters.size ) - //TODO notify + if (chapters.isNotEmpty()) { + showNotification(track.manga, chapters) + } } chapters.size == track.knownChaptersCount -> { if (chapters.lastOrNull()?.id == track.lastChapterId) { @@ -78,24 +83,30 @@ class TrackerJobService : BaseJobService() { newChapters = 0 ) } else { + val newChapters = chapters.size - knownChapter + 1 repo.storeTrackResult( mangaId = track.manga.id, knownChaptersCount = knownChapter + 1, lastChapterId = track.lastChapterId, - newChapters = chapters.size - knownChapter + 1 + newChapters = newChapters ) - //TODO notify + if (newChapters != 0) { + showNotification(track.manga, chapters.takeLast(newChapters)) + } } } } else -> { + val newChapters = chapters.size - track.knownChaptersCount repo.storeTrackResult( mangaId = track.manga.id, knownChaptersCount = track.knownChaptersCount, lastChapterId = track.lastChapterId, - newChapters = chapters.size - track.knownChaptersCount + newChapters = newChapters ) - //TODO notify + if (newChapters != 0) { + showNotification(track.manga, chapters.takeLast(newChapters)) + } } } success++ @@ -106,24 +117,55 @@ class TrackerJobService : BaseJobService() { } } - @MainThread - private fun showNotification(manga: Manga, newChapters: List) { - //TODO + private suspend fun showNotification(manga: Manga, newChapters: List) { + val id = manga.url.hashCode() + 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 { private const val JOB_ID = 7 private const val CHANNEL_ID = "tracking" + private const val TAG = "tracking" @RequiresApi(Build.VERSION_CODES.O) 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) { 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.lightColor = ContextCompat.getColor(context, R.color.blue_primary) + channel.enableLights(true) manager.createNotificationChannel(channel) } } @@ -136,7 +178,8 @@ class TrackerJobService : BaseJobService() { // if (scheduler.allPendingJobs != null) { // 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) { jobInfo.setRequiredNetworkType(JobInfo.NETWORK_TYPE_NOT_ROAMING) } else { diff --git a/app/src/main/res/drawable-anydpi-v24/ic_stat_book_plus.xml b/app/src/main/res/drawable-anydpi-v24/ic_stat_book_plus.xml new file mode 100644 index 000000000..73fd841ea --- /dev/null +++ b/app/src/main/res/drawable-anydpi-v24/ic_stat_book_plus.xml @@ -0,0 +1,17 @@ + + + + + diff --git a/app/src/main/res/drawable-hdpi/ic_stat_book_plus.png b/app/src/main/res/drawable-hdpi/ic_stat_book_plus.png new file mode 100644 index 0000000000000000000000000000000000000000..521e3e9281a511efd6563e7b71098f6a8887a838 GIT binary patch literal 336 zcmV-W0k8gvP)YRMoLf#${mDZ?Bfbcd4gszkC!j|W7P5hJ)!kO z`7_?K2Bn=6RDw#-p8_3VJDKYufaZd(qY{+l%*W58f$^YgCD@0(JqOK3^iKS1&@R@K zsRvz)!G0%8P?$nI>OzB-F`CS28lbITf?{%u!B|iYuRX-Hun~m`Pz$*00j7yf^mPOZ iU{wsIIQ?Bg^2$3Xj=yqi`usfr0000@_E8!37Gx1xd{L} zC7>Mu9F%~C0O0U%0hquZhN6K7tm8S=A6Ua#6woyPK>vVOyx_f8z&4g~+bduy^;|{+ z=h(n->S?F?A`0kadN-qhYn);%^=zg3CJOjUqdSXKrT&=S<(kW02CZVp0=t7}k8QDY*G5-kzV&WX_Opj&HLl!A-31?~k6 z4ZY!~;oL)~hie_ry`GnNzVMqK{^$REFXx>5-2d(Ci!=ZXVie=;oI647NTx1tE}+$` z&7a3@Y~V#Z_b`;RemPT@H`{oGsa^;?!6$#RinBTE*A?RjUgK&vo1bf6D5MMlIKrE5 z1fCsp;j#o$E*BMeSrdVoq5^w05txkvQ6LILf#U@*fNPkfhh2I_9esGH1k1^gLzffK;J0POM$17F@2g#L60lJ z`dr-vKH@NwTS1Te!TMp{1VS438vXAC7BQB|%4rI8`cmnZ50XFc(sa7#L#`JUSULrP zlk#28L2@U7F}#(G46ER?;F&2RF^x@pDRJXJ@g5Ixrdxr2T*M8`mz0~BQ1sS`l*liG WJZ_s(|Fk3k00007sZ&VWG#0jEp@M~t5-@^=q!0@U zu?a##A_NkW{8MDaVck1tcIS>WvwIHQZtpYa`Blrp&d`wKI8K8DJZd`=1?S!CFtf3wznY^!-GL5($yg@o;+KGp90lqQ1TY)RC0+aq@H5B5n*j7ImP@+$D6pI3 z;Y|Qqi{+9oZXi+IK%%&TL~#R&;sz4M4J3*iNEA1aC~hE8+(4puAs}D`m<2uq_km#p z{k=N{>;WgqXO4k4Kx?3ZfEMta@Gp?tz!GqEpn!le;3;sJv+xEmJt#nnz*djN*MaXn z-t8up0yQB95;2g7fkX@>VjvL%i5N)4Kq3YbF_4IXL<}TiAQ1zJ^d-=5U@dTtx}4RQ zK*6PeU$JHxNX#;jm}MX_%Rpk5fyAsP06hk7b~xi1-CBV=9|F&4G|*JU&hHJB-Pu5* zvw=is1BuRk3ACOp)_ztpy1fkC3H5C>P$>Q$)az0hD0?Xkl)V%N%3cZs0q_-g5IEiT zDg|f+_z`j8DzjV$&_&==)tg1gDexpZP=YjH^>z`m4csUNC_#Ev^@as<1k9C&lK_na zA36U!L6fo%ER>3qLK^1$)id8JT{(YV0JkXXogD(}cFoVV|Bm7~PTrgWTj=tcTDV0w P00000NkvXXu0mjf@RV2<>3aSW-L^LDO(hi{-5=Ng z6i%}fd(L_JPW8Rr-|u{%UHI9G%3on>Dki8kRR2A85N^%f+~ck*%@+fXK@)Q#O>T zChs+pV88dkWlQ_J$?42i4bcbAOC36{@PI$~0Bb_!3%{><2TC8f_byBpV(>fU0OU`3 z&#d;ELuNKZLA>W~=kKew{%N;5d`yGkLEVeEts?iQDQB!0?=c;3?3*GpRmRfk z@*JjyuMYMGEI#Qr=Bzdh-xzX*0sG=8Q(Wv77DO$-tqp6fDdzj!Tv34Go=_eDls1OXfPB?=wXpiCZ3sMz{&IYoZQm0 z6$edK6q*z7)aK_(eqQrHN@&7Cfz5uG&t81$36fz(^SswHt_{Y?+ZeiSW6i4V9yh2T zZW1fl!Wb*;&&H{-GyBm=1xD3!UmHo41y9y>syDMtxnuelAgaV4LADNu! z$o=AYZosWI%wm1{_uqxq&n<3ksgLk#3 zn0?*XmuBtzCQci81{PafBrjj7qmBcAL!+HZgau=l%1$d элемента %1$d элементов + + %1$d новая глава + %1$d новых главы + %1$d новых глав + \ No newline at end of file diff --git a/app/src/main/res/values/plurals.xml b/app/src/main/res/values/plurals.xml index 4ae01551a..521d57dfd 100644 --- a/app/src/main/res/values/plurals.xml +++ b/app/src/main/res/values/plurals.xml @@ -8,4 +8,8 @@ %1$d item %1$d items + + %1$d new chapter + %1$d new chapters + \ No newline at end of file