Tracking manga updates

remotes/weblate/feature/pagecrop
Koitharu 6 years ago
parent 80c8344f8d
commit aad26d24ec

@ -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
}
}
} }

@ -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) {

@ -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

@ -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 {

@ -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

@ -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>

@ -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>
Loading…
Cancel
Save