Estimate remeaning download time
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -9,6 +9,7 @@
|
|||||||
/.idea/workspace.xml
|
/.idea/workspace.xml
|
||||||
/.idea/navEditor.xml
|
/.idea/navEditor.xml
|
||||||
/.idea/assetWizardSettings.xml
|
/.idea/assetWizardSettings.xml
|
||||||
|
/.idea/kotlinScripting.xml
|
||||||
.DS_Store
|
.DS_Store
|
||||||
/build
|
/build
|
||||||
/captures
|
/captures
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import android.app.NotificationManager
|
|||||||
import android.app.PendingIntent
|
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 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
|
||||||
@@ -49,7 +50,7 @@ class DownloadNotification(private val context: Context, startId: Int) {
|
|||||||
builder.setSilent(true)
|
builder.setSilent(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun create(state: DownloadState): Notification {
|
fun create(state: DownloadState, timeLeft: Long): Notification {
|
||||||
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)
|
||||||
@@ -117,7 +118,13 @@ class DownloadNotification(private val context: Context, startId: Int) {
|
|||||||
}
|
}
|
||||||
is DownloadState.Progress -> {
|
is DownloadState.Progress -> {
|
||||||
builder.setProgress(state.max, state.progress, false)
|
builder.setProgress(state.max, state.progress, false)
|
||||||
builder.setContentText((state.percent * 100).format() + "%")
|
if (timeLeft > 0L) {
|
||||||
|
val eta = DateUtils.getRelativeTimeSpanString(timeLeft, 0L, DateUtils.SECOND_IN_MILLIS)
|
||||||
|
builder.setContentText(eta)
|
||||||
|
} else {
|
||||||
|
val percent = (state.percent * 100).format()
|
||||||
|
builder.setContentText(context.getString(R.string.percent_string_pattern, percent))
|
||||||
|
}
|
||||||
builder.setCategory(NotificationCompat.CATEGORY_PROGRESS)
|
builder.setCategory(NotificationCompat.CATEGORY_PROGRESS)
|
||||||
builder.setStyle(null)
|
builder.setStyle(null)
|
||||||
builder.setOngoing(true)
|
builder.setOngoing(true)
|
||||||
|
|||||||
@@ -11,10 +11,7 @@ import android.widget.Toast
|
|||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.*
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
|
||||||
import kotlinx.coroutines.flow.mapLatest
|
|
||||||
import kotlinx.coroutines.flow.transformWhile
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.plus
|
import kotlinx.coroutines.plus
|
||||||
import org.koin.android.ext.android.get
|
import org.koin.android.ext.android.get
|
||||||
@@ -32,6 +29,7 @@ import org.koitharu.kotatsu.parsers.model.Manga
|
|||||||
import org.koitharu.kotatsu.utils.ext.connectivityManager
|
import org.koitharu.kotatsu.utils.ext.connectivityManager
|
||||||
import org.koitharu.kotatsu.utils.ext.throttle
|
import org.koitharu.kotatsu.utils.ext.throttle
|
||||||
import org.koitharu.kotatsu.utils.progress.ProgressJob
|
import org.koitharu.kotatsu.utils.progress.ProgressJob
|
||||||
|
import org.koitharu.kotatsu.utils.progress.TimeLeftEstimator
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
class DownloadService : BaseService() {
|
class DownloadService : BaseService() {
|
||||||
@@ -99,13 +97,22 @@ class DownloadService : BaseService() {
|
|||||||
private fun listenJob(job: ProgressJob<DownloadState>) {
|
private fun listenJob(job: ProgressJob<DownloadState>) {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
val startId = job.progressValue.startId
|
val startId = job.progressValue.startId
|
||||||
|
val timeLeftEstimator = TimeLeftEstimator()
|
||||||
val notification = DownloadNotification(this@DownloadService, startId)
|
val notification = DownloadNotification(this@DownloadService, startId)
|
||||||
notificationSwitcher.notify(startId, notification.create(job.progressValue))
|
notificationSwitcher.notify(startId, notification.create(job.progressValue, -1L))
|
||||||
job.progressAsFlow()
|
job.progressAsFlow()
|
||||||
|
.onEach { state ->
|
||||||
|
if (state is DownloadState.Progress) {
|
||||||
|
timeLeftEstimator.tick(value = state.progress, total = state.max)
|
||||||
|
} else {
|
||||||
|
timeLeftEstimator.emptyTick()
|
||||||
|
}
|
||||||
|
}
|
||||||
.throttle { state -> if (state is DownloadState.Progress) 400L else 0L }
|
.throttle { state -> if (state is DownloadState.Progress) 400L else 0L }
|
||||||
.whileActive()
|
.whileActive()
|
||||||
.collect { state ->
|
.collect { state ->
|
||||||
notificationSwitcher.notify(startId, notification.create(state))
|
val timeLeft = timeLeftEstimator.getEstimatedTimeLeft()
|
||||||
|
notificationSwitcher.notify(startId, notification.create(state, timeLeft))
|
||||||
}
|
}
|
||||||
job.join()
|
job.join()
|
||||||
(job.progressValue as? DownloadState.Done)?.let {
|
(job.progressValue as? DownloadState.Done)?.let {
|
||||||
@@ -119,7 +126,7 @@ class DownloadService : BaseService() {
|
|||||||
if (job.isCancelled) {
|
if (job.isCancelled) {
|
||||||
null
|
null
|
||||||
} else {
|
} else {
|
||||||
notification.create(job.progressValue)
|
notification.create(job.progressValue, -1L)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
stopSelf(startId)
|
stopSelf(startId)
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import kotlinx.parcelize.Parcelize
|
|||||||
@Parcelize
|
@Parcelize
|
||||||
data class Progress(
|
data class Progress(
|
||||||
val value: Int,
|
val value: Int,
|
||||||
val total: Int
|
val total: Int,
|
||||||
) : Parcelable, Comparable<Progress> {
|
) : Parcelable, Comparable<Progress> {
|
||||||
|
|
||||||
override fun compareTo(other: Progress): Int {
|
override fun compareTo(other: Progress): Int {
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package org.koitharu.kotatsu.utils.progress
|
||||||
|
|
||||||
|
import android.os.SystemClock
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
import kotlin.math.roundToLong
|
||||||
|
|
||||||
|
private const val MIN_ESTIMATE_TICKS = 4
|
||||||
|
private const val NO_TIME = -1L
|
||||||
|
|
||||||
|
class TimeLeftEstimator {
|
||||||
|
|
||||||
|
private var times = ArrayList<Int>()
|
||||||
|
private var lastTick: Tick? = null
|
||||||
|
|
||||||
|
fun tick(value: Int, total: Int) {
|
||||||
|
if (total < 0) {
|
||||||
|
emptyTick()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val tick = Tick(value, total, SystemClock.elapsedRealtime())
|
||||||
|
lastTick?.let {
|
||||||
|
val ticksCount = value - it.value
|
||||||
|
times.add(((tick.time - it.time) / ticksCount.toDouble()).roundToInt())
|
||||||
|
}
|
||||||
|
lastTick = tick
|
||||||
|
}
|
||||||
|
|
||||||
|
fun emptyTick() {
|
||||||
|
lastTick = null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getEstimatedTimeLeft(): Long {
|
||||||
|
val progress = lastTick ?: return NO_TIME
|
||||||
|
if (times.size < MIN_ESTIMATE_TICKS) {
|
||||||
|
return NO_TIME
|
||||||
|
}
|
||||||
|
val timePerTick = times.average()
|
||||||
|
val ticksLeft = progress.total - progress.value
|
||||||
|
return (ticksLeft * timePerTick).roundToLong()
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Tick(
|
||||||
|
val value: Int,
|
||||||
|
val total: Int,
|
||||||
|
val time: Long,
|
||||||
|
)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user