diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 0b9a3026c..be76705d3 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -281,6 +281,9 @@
+
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/CoroutineIntentService.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/CoroutineIntentService.kt
index 2d4ead31d..e8f167f32 100644
--- a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/CoroutineIntentService.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/CoroutineIntentService.kt
@@ -1,6 +1,8 @@
package org.koitharu.kotatsu.core.ui
import android.content.Intent
+import androidx.annotation.AnyThread
+import androidx.annotation.WorkerThread
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineExceptionHandler
@@ -39,8 +41,10 @@ abstract class CoroutineIntentService : BaseService() {
}
}
+ @WorkerThread
protected abstract suspend fun processIntent(startId: Int, intent: Intent)
+ @AnyThread
protected abstract fun onError(startId: Int, error: Throwable)
private fun errorHandler(startId: Int) = CoroutineExceptionHandler { _, throwable ->
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/local/ui/ImportDialogFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/local/ui/ImportDialogFragment.kt
index cd40efb5a..fdc21e1d8 100644
--- a/app/src/main/kotlin/org/koitharu/kotatsu/local/ui/ImportDialogFragment.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/local/ui/ImportDialogFragment.kt
@@ -66,8 +66,12 @@ class ImportDialogFragment : AlertDialogFragment(), View.On
storageManager.takePermissions(it)
}
val ctx = requireContext()
- ImportWorker.start(ctx, uris)
- Toast.makeText(ctx, R.string.import_will_start_soon, Toast.LENGTH_LONG).show()
+ val msg = if (ImportService.start(ctx, uris)) {
+ R.string.import_will_start_soon
+ } else {
+ R.string.error_occurred
+ }
+ Toast.makeText(ctx, msg, Toast.LENGTH_LONG).show()
dismiss()
}
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/local/ui/ImportWorker.kt b/app/src/main/kotlin/org/koitharu/kotatsu/local/ui/ImportService.kt
similarity index 63%
rename from app/src/main/kotlin/org/koitharu/kotatsu/local/ui/ImportWorker.kt
rename to app/src/main/kotlin/org/koitharu/kotatsu/local/ui/ImportService.kt
index 2841bbe02..45003d292 100644
--- a/app/src/main/kotlin/org/koitharu/kotatsu/local/ui/ImportWorker.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/local/ui/ImportService.kt
@@ -3,63 +3,75 @@ package org.koitharu.kotatsu.local.ui
import android.app.Notification
import android.app.PendingIntent
import android.content.Context
+import android.content.Intent
import android.content.pm.ServiceInfo
import android.net.Uri
-import android.os.Build
import androidx.core.app.NotificationChannelCompat
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.app.PendingIntentCompat
-import androidx.hilt.work.HiltWorker
-import androidx.work.Constraints
-import androidx.work.CoroutineWorker
-import androidx.work.Data
-import androidx.work.ForegroundInfo
-import androidx.work.OneTimeWorkRequestBuilder
-import androidx.work.OutOfQuotaPolicy
-import androidx.work.WorkManager
-import androidx.work.WorkerParameters
+import androidx.core.app.ServiceCompat
+import androidx.core.content.ContextCompat
import coil.ImageLoader
import coil.request.ImageRequest
-import dagger.assisted.Assisted
-import dagger.assisted.AssistedInject
+import dagger.hilt.android.AndroidEntryPoint
+import kotlinx.coroutines.runBlocking
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.ErrorReporterReceiver
+import org.koitharu.kotatsu.core.ui.CoroutineIntentService
import org.koitharu.kotatsu.core.util.ext.checkNotificationPermission
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
+import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
import org.koitharu.kotatsu.core.util.ext.toBitmapOrNull
import org.koitharu.kotatsu.core.util.ext.toUriOrNull
import org.koitharu.kotatsu.details.ui.DetailsActivity
import org.koitharu.kotatsu.local.data.importer.SingleMangaImporter
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
+import javax.inject.Inject
-@HiltWorker
-class ImportWorker @AssistedInject constructor(
- @Assisted appContext: Context,
- @Assisted params: WorkerParameters,
- private val importer: SingleMangaImporter,
- private val coil: ImageLoader
-) : CoroutineWorker(appContext, params) {
+@AndroidEntryPoint
+class ImportService : CoroutineIntentService() {
- private val notificationManager by lazy { NotificationManagerCompat.from(appContext) }
+ @Inject
+ lateinit var importer: SingleMangaImporter
- override suspend fun doWork(): Result {
- val uri = inputData.getString(DATA_URI)?.toUriOrNull() ?: return Result.failure()
- setForeground(getForegroundInfo())
- val result = runCatchingCancellable {
- importer.import(uri).manga
+ @Inject
+ lateinit var coil: ImageLoader
+
+ private lateinit var notificationManager: NotificationManagerCompat
+
+ override fun onCreate() {
+ super.onCreate()
+ notificationManager = NotificationManagerCompat.from(applicationContext)
+ }
+
+ override suspend fun processIntent(startId: Int, intent: Intent) {
+ val uri = requireNotNull(intent.getStringExtra(DATA_URI)?.toUriOrNull()) { "No unput uri" }
+ startForeground()
+ try {
+ val result = runCatchingCancellable {
+ importer.import(uri).manga
+ }
+ if (applicationContext.checkNotificationPermission(CHANNEL_ID)) {
+ val notification = buildNotification(result)
+ notificationManager.notify(TAG, startId, notification)
+ }
+ } finally {
+ ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE)
}
+ }
+
+ override fun onError(startId: Int, error: Throwable) {
if (applicationContext.checkNotificationPermission(CHANNEL_ID)) {
- val notification = buildNotification(result)
- notificationManager.notify(uri.hashCode(), notification)
+ val notification = runBlocking { buildNotification(Result.failure(error)) }
+ notificationManager.notify(TAG, startId, notification)
}
- return Result.success()
}
- override suspend fun getForegroundInfo(): ForegroundInfo {
+ private suspend fun startForeground() {
val title = applicationContext.getString(R.string.importing_manga)
- val channel = NotificationChannelCompat.Builder(CHANNEL_ID, NotificationManagerCompat.IMPORTANCE_LOW)
+ val channel = NotificationChannelCompat.Builder(CHANNEL_ID, NotificationManagerCompat.IMPORTANCE_DEFAULT)
.setName(title)
.setShowBadge(false)
.setVibrationEnabled(false)
@@ -80,14 +92,15 @@ class ImportWorker @AssistedInject constructor(
.setCategory(NotificationCompat.CATEGORY_PROGRESS)
.build()
- return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
- ForegroundInfo(FOREGROUND_NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC)
- } else {
- ForegroundInfo(FOREGROUND_NOTIFICATION_ID, notification)
- }
+ ServiceCompat.startForeground(
+ this,
+ FOREGROUND_NOTIFICATION_ID,
+ notification,
+ ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC,
+ )
}
- private suspend fun buildNotification(result: kotlin.Result): Notification {
+ private suspend fun buildNotification(result: Result): Notification {
val notification = NotificationCompat.Builder(applicationContext, CHANNEL_ID)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setDefaults(0)
@@ -135,26 +148,21 @@ class ImportWorker @AssistedInject constructor(
companion object {
- const val DATA_URI = "uri"
-
+ private const val DATA_URI = "uri"
private const val TAG = "import"
private const val CHANNEL_ID = "importing"
private const val FOREGROUND_NOTIFICATION_ID = 37
- fun start(context: Context, uris: Iterable) {
- val constraints = Constraints.Builder()
- .setRequiresStorageNotLow(true)
- .build()
- val requests = uris.map { uri ->
- OneTimeWorkRequestBuilder()
- .setConstraints(constraints)
- .addTag(TAG)
- .setInputData(Data.Builder().putString(DATA_URI, uri.toString()).build())
- .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
- .build()
+ fun start(context: Context, uris: Iterable): Boolean = try {
+ for (uri in uris) {
+ val intent = Intent(context, ImportService::class.java)
+ intent.putExtra(DATA_URI, uri.toString())
+ ContextCompat.startForegroundService(context, intent)
}
- WorkManager.getInstance(context)
- .enqueue(requests)
+ true
+ } catch (e: Exception) {
+ e.printStackTraceDebug()
+ false
}
}
}
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/local/ui/LocalStorageCleanupWorker.kt b/app/src/main/kotlin/org/koitharu/kotatsu/local/ui/LocalStorageCleanupWorker.kt
index cc6c6ccab..661a50281 100644
--- a/app/src/main/kotlin/org/koitharu/kotatsu/local/ui/LocalStorageCleanupWorker.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/local/ui/LocalStorageCleanupWorker.kt
@@ -48,7 +48,7 @@ class LocalStorageCleanupWorker @AssistedInject constructor(
val constraints = Constraints.Builder()
.setRequiresBatteryNotLow(true)
.build()
- val request = OneTimeWorkRequestBuilder()
+ val request = OneTimeWorkRequestBuilder()
.setConstraints(constraints)
.addTag(TAG)
.setBackoffCriteria(BackoffPolicy.LINEAR, 10, TimeUnit.MINUTES)
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/remotelist/ui/RemoteListViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/remotelist/ui/RemoteListViewModel.kt
index 87bb46103..fa80a5f1b 100644
--- a/app/src/main/kotlin/org/koitharu/kotatsu/remotelist/ui/RemoteListViewModel.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/remotelist/ui/RemoteListViewModel.kt
@@ -92,7 +92,7 @@ open class RemoteListViewModel @Inject constructor(
}
onBuildList(this)
}
- }.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, listOf(LoadingState))
+ }.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Lazily, listOf(LoadingState))
init {
filter.observeState()