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 000000000..521e3e928 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_stat_book_plus.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_stat_book_plus.png b/app/src/main/res/drawable-mdpi/ic_stat_book_plus.png new file mode 100644 index 000000000..19c2b7681 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_stat_book_plus.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_stat_book_plus.png b/app/src/main/res/drawable-xhdpi/ic_stat_book_plus.png new file mode 100644 index 000000000..28d25a674 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_stat_book_plus.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_stat_book_plus.png b/app/src/main/res/drawable-xxhdpi/ic_stat_book_plus.png new file mode 100644 index 000000000..a4a888163 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_stat_book_plus.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_stat_book_plus.png b/app/src/main/res/drawable-xxxhdpi/ic_stat_book_plus.png new file mode 100644 index 000000000..690da0a46 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_stat_book_plus.png differ diff --git a/app/src/main/res/values-ru/plurals.xml b/app/src/main/res/values-ru/plurals.xml index 54caaddb6..1ca8d51bf 100644 --- a/app/src/main/res/values-ru/plurals.xml +++ b/app/src/main/res/values-ru/plurals.xml @@ -10,4 +10,9 @@ %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