|
|
|
@ -7,21 +7,126 @@ import android.app.PendingIntent
|
|
|
|
import android.content.Context
|
|
|
|
import android.content.Context
|
|
|
|
import android.os.Build
|
|
|
|
import android.os.Build
|
|
|
|
import android.text.format.DateUtils
|
|
|
|
import android.text.format.DateUtils
|
|
|
|
|
|
|
|
import android.util.SparseArray
|
|
|
|
import androidx.core.app.NotificationCompat
|
|
|
|
import androidx.core.app.NotificationCompat
|
|
|
|
import androidx.core.app.NotificationManagerCompat
|
|
|
|
import androidx.core.app.NotificationManagerCompat
|
|
|
|
import androidx.core.content.ContextCompat
|
|
|
|
import androidx.core.content.ContextCompat
|
|
|
|
import androidx.core.graphics.drawable.toBitmap
|
|
|
|
import androidx.core.graphics.drawable.toBitmap
|
|
|
|
import com.google.android.material.R as materialR
|
|
|
|
import androidx.core.text.HtmlCompat
|
|
|
|
|
|
|
|
import androidx.core.text.htmlEncode
|
|
|
|
|
|
|
|
import androidx.core.text.parseAsHtml
|
|
|
|
|
|
|
|
import androidx.core.util.forEach
|
|
|
|
|
|
|
|
import androidx.core.util.size
|
|
|
|
import org.koitharu.kotatsu.R
|
|
|
|
import org.koitharu.kotatsu.R
|
|
|
|
import org.koitharu.kotatsu.details.ui.DetailsActivity
|
|
|
|
import org.koitharu.kotatsu.details.ui.DetailsActivity
|
|
|
|
import org.koitharu.kotatsu.download.domain.DownloadState
|
|
|
|
import org.koitharu.kotatsu.download.domain.DownloadState
|
|
|
|
import org.koitharu.kotatsu.download.ui.DownloadsActivity
|
|
|
|
import org.koitharu.kotatsu.download.ui.DownloadsActivity
|
|
|
|
import org.koitharu.kotatsu.parsers.model.Manga
|
|
|
|
import org.koitharu.kotatsu.parsers.model.Manga
|
|
|
|
|
|
|
|
import org.koitharu.kotatsu.parsers.util.ellipsize
|
|
|
|
import org.koitharu.kotatsu.parsers.util.format
|
|
|
|
import org.koitharu.kotatsu.parsers.util.format
|
|
|
|
import org.koitharu.kotatsu.utils.PendingIntentCompat
|
|
|
|
import org.koitharu.kotatsu.utils.PendingIntentCompat
|
|
|
|
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
|
|
|
|
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
|
|
|
|
|
|
|
|
import com.google.android.material.R as materialR
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class DownloadNotification(private val context: Context) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private val manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
|
|
|
|
|
|
|
private val states = SparseArray<DownloadState>()
|
|
|
|
|
|
|
|
private val groupBuilder = NotificationCompat.Builder(context, CHANNEL_ID)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private val listIntent = PendingIntent.getActivity(
|
|
|
|
|
|
|
|
context,
|
|
|
|
|
|
|
|
REQUEST_LIST,
|
|
|
|
|
|
|
|
DownloadsActivity.newIntent(context),
|
|
|
|
|
|
|
|
PendingIntentCompat.FLAG_IMMUTABLE,
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
class DownloadNotification(private val context: Context, startId: Int) {
|
|
|
|
init {
|
|
|
|
|
|
|
|
groupBuilder.setOnlyAlertOnce(true)
|
|
|
|
|
|
|
|
groupBuilder.setDefaults(0)
|
|
|
|
|
|
|
|
groupBuilder.color = ContextCompat.getColor(context, R.color.blue_primary)
|
|
|
|
|
|
|
|
groupBuilder.foregroundServiceBehavior = NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE
|
|
|
|
|
|
|
|
groupBuilder.setSilent(true)
|
|
|
|
|
|
|
|
groupBuilder.setGroup(GROUP_ID)
|
|
|
|
|
|
|
|
groupBuilder.setContentIntent(listIntent)
|
|
|
|
|
|
|
|
groupBuilder.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_CHILDREN)
|
|
|
|
|
|
|
|
groupBuilder.setGroupSummary(true)
|
|
|
|
|
|
|
|
groupBuilder.setContentTitle(context.getString(R.string.downloading_manga))
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fun buildGroupNotification(): Notification {
|
|
|
|
|
|
|
|
val style = NotificationCompat.InboxStyle(groupBuilder)
|
|
|
|
|
|
|
|
var progress = 0f
|
|
|
|
|
|
|
|
var isAllDone = true
|
|
|
|
|
|
|
|
groupBuilder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
|
|
|
|
|
|
|
states.forEach { _, state ->
|
|
|
|
|
|
|
|
if (state.manga.isNsfw) {
|
|
|
|
|
|
|
|
groupBuilder.setVisibility(NotificationCompat.VISIBILITY_PRIVATE)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
val summary = when (state) {
|
|
|
|
|
|
|
|
is DownloadState.Cancelled -> {
|
|
|
|
|
|
|
|
progress++
|
|
|
|
|
|
|
|
context.getString(R.string.cancelling_)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
is DownloadState.Done -> {
|
|
|
|
|
|
|
|
progress++
|
|
|
|
|
|
|
|
context.getString(R.string.completed)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
is DownloadState.Error -> {
|
|
|
|
|
|
|
|
isAllDone = false
|
|
|
|
|
|
|
|
context.getString(R.string.error)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
is DownloadState.PostProcessing -> {
|
|
|
|
|
|
|
|
progress++
|
|
|
|
|
|
|
|
isAllDone = false
|
|
|
|
|
|
|
|
context.getString(R.string.processing_)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
is DownloadState.Preparing -> {
|
|
|
|
|
|
|
|
isAllDone = false
|
|
|
|
|
|
|
|
context.getString(R.string.preparing_)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
is DownloadState.Progress -> {
|
|
|
|
|
|
|
|
isAllDone = false
|
|
|
|
|
|
|
|
progress += state.percent
|
|
|
|
|
|
|
|
context.getString(R.string.percent_string_pattern, (state.percent * 100).format())
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
is DownloadState.Queued -> {
|
|
|
|
|
|
|
|
isAllDone = false
|
|
|
|
|
|
|
|
context.getString(R.string.queued)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
style.addLine(
|
|
|
|
|
|
|
|
context.getString(
|
|
|
|
|
|
|
|
R.string.download_summary_pattern,
|
|
|
|
|
|
|
|
state.manga.title.ellipsize(10).htmlEncode(),
|
|
|
|
|
|
|
|
summary.htmlEncode(),
|
|
|
|
|
|
|
|
).parseAsHtml(HtmlCompat.FROM_HTML_MODE_LEGACY),
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
progress /= states.size.toFloat()
|
|
|
|
|
|
|
|
style.setBigContentTitle(context.getString(R.string.downloading_manga))
|
|
|
|
|
|
|
|
groupBuilder.setContentText(context.resources.getQuantityString(R.plurals.items, states.size, states.size()))
|
|
|
|
|
|
|
|
groupBuilder.setNumber(states.size)
|
|
|
|
|
|
|
|
groupBuilder.setSmallIcon(
|
|
|
|
|
|
|
|
if (isAllDone) android.R.drawable.stat_sys_download_done else android.R.drawable.stat_sys_download,
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
when (progress) {
|
|
|
|
|
|
|
|
1f -> groupBuilder.setProgress(0, 0, false)
|
|
|
|
|
|
|
|
0f -> groupBuilder.setProgress(1, 0, true)
|
|
|
|
|
|
|
|
else -> groupBuilder.setProgress(100, (progress * 100f).toInt(), progress == 0f)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return groupBuilder.build()
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fun dismiss() {
|
|
|
|
|
|
|
|
manager.cancel(ID_GROUP)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fun newItem(startId: Int) = Item(startId)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
inner class Item(
|
|
|
|
|
|
|
|
private val startId: Int,
|
|
|
|
|
|
|
|
) {
|
|
|
|
|
|
|
|
|
|
|
|
private val builder = NotificationCompat.Builder(context, CHANNEL_ID)
|
|
|
|
private val builder = NotificationCompat.Builder(context, CHANNEL_ID)
|
|
|
|
private val cancelAction = NotificationCompat.Action(
|
|
|
|
private val cancelAction = NotificationCompat.Action(
|
|
|
|
@ -31,8 +136,8 @@ class DownloadNotification(private val context: Context, startId: Int) {
|
|
|
|
context,
|
|
|
|
context,
|
|
|
|
startId * 2,
|
|
|
|
startId * 2,
|
|
|
|
DownloadService.getCancelIntent(startId),
|
|
|
|
DownloadService.getCancelIntent(startId),
|
|
|
|
PendingIntent.FLAG_CANCEL_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE
|
|
|
|
PendingIntent.FLAG_CANCEL_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE,
|
|
|
|
)
|
|
|
|
),
|
|
|
|
)
|
|
|
|
)
|
|
|
|
private val retryAction = NotificationCompat.Action(
|
|
|
|
private val retryAction = NotificationCompat.Action(
|
|
|
|
R.drawable.ic_restart_black,
|
|
|
|
R.drawable.ic_restart_black,
|
|
|
|
@ -41,14 +146,8 @@ class DownloadNotification(private val context: Context, startId: Int) {
|
|
|
|
context,
|
|
|
|
context,
|
|
|
|
startId * 2 + 1,
|
|
|
|
startId * 2 + 1,
|
|
|
|
DownloadService.getResumeIntent(startId),
|
|
|
|
DownloadService.getResumeIntent(startId),
|
|
|
|
PendingIntent.FLAG_CANCEL_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE
|
|
|
|
PendingIntent.FLAG_CANCEL_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE,
|
|
|
|
)
|
|
|
|
),
|
|
|
|
)
|
|
|
|
|
|
|
|
private val listIntent = PendingIntent.getActivity(
|
|
|
|
|
|
|
|
context,
|
|
|
|
|
|
|
|
REQUEST_LIST,
|
|
|
|
|
|
|
|
DownloadsActivity.newIntent(context),
|
|
|
|
|
|
|
|
PendingIntentCompat.FLAG_IMMUTABLE
|
|
|
|
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
init {
|
|
|
|
init {
|
|
|
|
@ -57,9 +156,11 @@ class DownloadNotification(private val context: Context, startId: Int) {
|
|
|
|
builder.color = ContextCompat.getColor(context, R.color.blue_primary)
|
|
|
|
builder.color = ContextCompat.getColor(context, R.color.blue_primary)
|
|
|
|
builder.foregroundServiceBehavior = NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE
|
|
|
|
builder.foregroundServiceBehavior = NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE
|
|
|
|
builder.setSilent(true)
|
|
|
|
builder.setSilent(true)
|
|
|
|
|
|
|
|
builder.setGroup(GROUP_ID)
|
|
|
|
|
|
|
|
builder.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_CHILDREN)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fun create(state: DownloadState, timeLeft: Long): Notification {
|
|
|
|
fun notify(state: DownloadState, timeLeft: Long) {
|
|
|
|
builder.setContentTitle(state.manga.title)
|
|
|
|
builder.setContentTitle(state.manga.title)
|
|
|
|
builder.setContentText(context.getString(R.string.manga_downloading_))
|
|
|
|
builder.setContentText(context.getString(R.string.manga_downloading_))
|
|
|
|
builder.setProgress(1, 0, true)
|
|
|
|
builder.setProgress(1, 0, true)
|
|
|
|
@ -73,7 +174,7 @@ class DownloadNotification(private val context: Context, startId: Int) {
|
|
|
|
NotificationCompat.VISIBILITY_PRIVATE
|
|
|
|
NotificationCompat.VISIBILITY_PRIVATE
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
NotificationCompat.VISIBILITY_PUBLIC
|
|
|
|
NotificationCompat.VISIBILITY_PUBLIC
|
|
|
|
}
|
|
|
|
},
|
|
|
|
)
|
|
|
|
)
|
|
|
|
when (state) {
|
|
|
|
when (state) {
|
|
|
|
is DownloadState.Cancelled -> {
|
|
|
|
is DownloadState.Cancelled -> {
|
|
|
|
@ -82,6 +183,7 @@ class DownloadNotification(private val context: Context, startId: Int) {
|
|
|
|
builder.setContentIntent(null)
|
|
|
|
builder.setContentIntent(null)
|
|
|
|
builder.setStyle(null)
|
|
|
|
builder.setStyle(null)
|
|
|
|
builder.setOngoing(true)
|
|
|
|
builder.setOngoing(true)
|
|
|
|
|
|
|
|
builder.priority = NotificationCompat.PRIORITY_DEFAULT
|
|
|
|
}
|
|
|
|
}
|
|
|
|
is DownloadState.Done -> {
|
|
|
|
is DownloadState.Done -> {
|
|
|
|
builder.setProgress(0, 0, false)
|
|
|
|
builder.setProgress(0, 0, false)
|
|
|
|
@ -92,6 +194,7 @@ class DownloadNotification(private val context: Context, startId: Int) {
|
|
|
|
builder.setCategory(null)
|
|
|
|
builder.setCategory(null)
|
|
|
|
builder.setStyle(null)
|
|
|
|
builder.setStyle(null)
|
|
|
|
builder.setOngoing(false)
|
|
|
|
builder.setOngoing(false)
|
|
|
|
|
|
|
|
builder.priority = NotificationCompat.PRIORITY_DEFAULT
|
|
|
|
}
|
|
|
|
}
|
|
|
|
is DownloadState.Error -> {
|
|
|
|
is DownloadState.Error -> {
|
|
|
|
val message = state.error.getDisplayMessage(context.resources)
|
|
|
|
val message = state.error.getDisplayMessage(context.resources)
|
|
|
|
@ -107,12 +210,14 @@ class DownloadNotification(private val context: Context, startId: Int) {
|
|
|
|
builder.addAction(cancelAction)
|
|
|
|
builder.addAction(cancelAction)
|
|
|
|
builder.addAction(retryAction)
|
|
|
|
builder.addAction(retryAction)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
builder.priority = NotificationCompat.PRIORITY_DEFAULT
|
|
|
|
}
|
|
|
|
}
|
|
|
|
is DownloadState.PostProcessing -> {
|
|
|
|
is DownloadState.PostProcessing -> {
|
|
|
|
builder.setProgress(1, 0, true)
|
|
|
|
builder.setProgress(1, 0, true)
|
|
|
|
builder.setContentText(context.getString(R.string.processing_))
|
|
|
|
builder.setContentText(context.getString(R.string.processing_))
|
|
|
|
builder.setStyle(null)
|
|
|
|
builder.setStyle(null)
|
|
|
|
builder.setOngoing(true)
|
|
|
|
builder.setOngoing(true)
|
|
|
|
|
|
|
|
builder.priority = NotificationCompat.PRIORITY_DEFAULT
|
|
|
|
}
|
|
|
|
}
|
|
|
|
is DownloadState.Queued -> {
|
|
|
|
is DownloadState.Queued -> {
|
|
|
|
builder.setProgress(0, 0, false)
|
|
|
|
builder.setProgress(0, 0, false)
|
|
|
|
@ -120,6 +225,7 @@ class DownloadNotification(private val context: Context, startId: Int) {
|
|
|
|
builder.setStyle(null)
|
|
|
|
builder.setStyle(null)
|
|
|
|
builder.setOngoing(true)
|
|
|
|
builder.setOngoing(true)
|
|
|
|
builder.addAction(cancelAction)
|
|
|
|
builder.addAction(cancelAction)
|
|
|
|
|
|
|
|
builder.priority = NotificationCompat.PRIORITY_LOW
|
|
|
|
}
|
|
|
|
}
|
|
|
|
is DownloadState.Preparing -> {
|
|
|
|
is DownloadState.Preparing -> {
|
|
|
|
builder.setProgress(1, 0, true)
|
|
|
|
builder.setProgress(1, 0, true)
|
|
|
|
@ -127,6 +233,7 @@ class DownloadNotification(private val context: Context, startId: Int) {
|
|
|
|
builder.setStyle(null)
|
|
|
|
builder.setStyle(null)
|
|
|
|
builder.setOngoing(true)
|
|
|
|
builder.setOngoing(true)
|
|
|
|
builder.addAction(cancelAction)
|
|
|
|
builder.addAction(cancelAction)
|
|
|
|
|
|
|
|
builder.priority = NotificationCompat.PRIORITY_DEFAULT
|
|
|
|
}
|
|
|
|
}
|
|
|
|
is DownloadState.Progress -> {
|
|
|
|
is DownloadState.Progress -> {
|
|
|
|
builder.setProgress(state.max, state.progress, false)
|
|
|
|
builder.setProgress(state.max, state.progress, false)
|
|
|
|
@ -141,29 +248,41 @@ class DownloadNotification(private val context: Context, startId: Int) {
|
|
|
|
builder.setStyle(null)
|
|
|
|
builder.setStyle(null)
|
|
|
|
builder.setOngoing(true)
|
|
|
|
builder.setOngoing(true)
|
|
|
|
builder.addAction(cancelAction)
|
|
|
|
builder.addAction(cancelAction)
|
|
|
|
|
|
|
|
builder.priority = NotificationCompat.PRIORITY_DEFAULT
|
|
|
|
}
|
|
|
|
}
|
|
|
|
is DownloadState.WaitingForNetwork -> {
|
|
|
|
}
|
|
|
|
builder.setProgress(0, 0, false)
|
|
|
|
val notification = builder.build()
|
|
|
|
builder.setContentText(context.getString(R.string.waiting_for_network))
|
|
|
|
states.append(startId, state)
|
|
|
|
builder.setStyle(null)
|
|
|
|
updateGroupNotification()
|
|
|
|
builder.setOngoing(true)
|
|
|
|
manager.notify(TAG, startId, notification)
|
|
|
|
builder.addAction(cancelAction)
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fun dismiss() {
|
|
|
|
|
|
|
|
manager.cancel(TAG, startId)
|
|
|
|
|
|
|
|
states.remove(startId)
|
|
|
|
|
|
|
|
updateGroupNotification()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return builder.build()
|
|
|
|
|
|
|
|
|
|
|
|
private fun updateGroupNotification() {
|
|
|
|
|
|
|
|
val notification = buildGroupNotification()
|
|
|
|
|
|
|
|
manager.notify(ID_GROUP, notification)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private fun createMangaIntent(context: Context, manga: Manga) = PendingIntent.getActivity(
|
|
|
|
private fun createMangaIntent(context: Context, manga: Manga) = PendingIntent.getActivity(
|
|
|
|
context,
|
|
|
|
context,
|
|
|
|
manga.hashCode(),
|
|
|
|
manga.hashCode(),
|
|
|
|
DetailsActivity.newIntent(context, manga),
|
|
|
|
DetailsActivity.newIntent(context, manga),
|
|
|
|
PendingIntent.FLAG_CANCEL_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE
|
|
|
|
PendingIntent.FLAG_CANCEL_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
companion object {
|
|
|
|
companion object {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private const val TAG = "download"
|
|
|
|
private const val CHANNEL_ID = "download"
|
|
|
|
private const val CHANNEL_ID = "download"
|
|
|
|
|
|
|
|
private const val GROUP_ID = "downloads"
|
|
|
|
private const val REQUEST_LIST = 6
|
|
|
|
private const val REQUEST_LIST = 6
|
|
|
|
|
|
|
|
const val ID_GROUP = 9999
|
|
|
|
|
|
|
|
|
|
|
|
fun createChannel(context: Context) {
|
|
|
|
fun createChannel(context: Context) {
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
|
|
@ -172,7 +291,7 @@ class DownloadNotification(private val context: Context, startId: Int) {
|
|
|
|
val channel = NotificationChannel(
|
|
|
|
val channel = NotificationChannel(
|
|
|
|
CHANNEL_ID,
|
|
|
|
CHANNEL_ID,
|
|
|
|
context.getString(R.string.downloads),
|
|
|
|
context.getString(R.string.downloads),
|
|
|
|
NotificationManager.IMPORTANCE_LOW
|
|
|
|
NotificationManager.IMPORTANCE_LOW,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
channel.enableVibration(false)
|
|
|
|
channel.enableVibration(false)
|
|
|
|
channel.enableLights(false)
|
|
|
|
channel.enableLights(false)
|
|
|
|
|