Fix crash on Android when saving items
This commit is contained in:
1
android/Omnivore/.gitignore
vendored
1
android/Omnivore/.gitignore
vendored
@ -1,5 +1,6 @@
|
||||
*.iml
|
||||
.gradle
|
||||
.kotlin
|
||||
/local.properties
|
||||
/.idea/caches
|
||||
/.idea/libraries
|
||||
|
||||
@ -184,7 +184,6 @@ fun loadLibraryItemContent(context: Context, libraryItemId: String): String? {
|
||||
return try {
|
||||
return readFromInternalStorage(context = context, fileName = "${libraryItemId}.html")
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,18 +1,36 @@
|
||||
package app.omnivore.omnivore.feature.library
|
||||
|
||||
import android.app.Notification
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import androidx.compose.ui.text.intl.Locale
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.hilt.work.HiltWorker
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.ForegroundInfo
|
||||
import androidx.work.WorkerParameters
|
||||
import app.omnivore.omnivore.R
|
||||
import app.omnivore.omnivore.core.data.repository.LibraryRepository
|
||||
import app.omnivore.omnivore.core.datastore.DatastoreRepository
|
||||
import app.omnivore.omnivore.core.datastore.libraryLastSyncTimestamp
|
||||
import app.omnivore.omnivore.core.datastore.omnivoreAuthToken
|
||||
import app.omnivore.omnivore.graphql.generated.SaveUrlMutation
|
||||
import app.omnivore.omnivore.graphql.generated.type.SaveUrlInput
|
||||
import app.omnivore.omnivore.utils.Constants
|
||||
import com.apollographql.apollo3.ApolloClient
|
||||
import com.apollographql.apollo3.api.Optional
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.time.Instant
|
||||
import java.util.TimeZone
|
||||
import java.util.UUID
|
||||
import java.util.regex.Pattern
|
||||
|
||||
@HiltWorker
|
||||
class LibrarySyncWorker @AssistedInject constructor(
|
||||
@ -21,8 +39,27 @@ class LibrarySyncWorker @AssistedInject constructor(
|
||||
private val libraryRepository: LibraryRepository,
|
||||
private val datastoreRepository: DatastoreRepository,
|
||||
) : CoroutineWorker(appContext, workerParams) {
|
||||
override suspend fun getForegroundInfo(): ForegroundInfo {
|
||||
return ForegroundInfo(
|
||||
NOTIFICATION_ID,
|
||||
createNotification()
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val NOTIFICATION_CHANNEL_ID = "LIBRARY_SYNC_WORKER_CHANNEL"
|
||||
const val NOTIFICATION_CHANNEL_NAME = "Sync library"
|
||||
const val NOTIFICATION_ID = 2
|
||||
}
|
||||
|
||||
override suspend fun doWork(): Result {
|
||||
try {
|
||||
setForeground(createForegroundInfo())
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return Result.failure()
|
||||
}
|
||||
|
||||
return withContext(Dispatchers.IO) {
|
||||
try {
|
||||
performItemSync()
|
||||
@ -35,6 +72,46 @@ class LibrarySyncWorker @AssistedInject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun createForegroundInfo(): ForegroundInfo {
|
||||
val notification = createNotification()
|
||||
return ForegroundInfo(NOTIFICATION_ID, notification)
|
||||
}
|
||||
|
||||
private fun createNotification(): Notification {
|
||||
val channelId = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
createNotificationChannel()
|
||||
} else {
|
||||
""
|
||||
}
|
||||
|
||||
return NotificationCompat.Builder(applicationContext, channelId)
|
||||
.setContentTitle("Syncing library items")
|
||||
.setContentText("Your library is being synced")
|
||||
.setSmallIcon(R.drawable.ic_notification)
|
||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun createNotificationChannel(): String {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val channelName = NOTIFICATION_CHANNEL_NAME
|
||||
val channel = NotificationChannel(
|
||||
NOTIFICATION_CHANNEL_ID,
|
||||
channelName,
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
).apply {
|
||||
description = "Notification channel for library syncing"
|
||||
}
|
||||
|
||||
val notificationManager = applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
notificationManager.createNotificationChannel(channel)
|
||||
|
||||
return NOTIFICATION_CHANNEL_ID
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun performItemSync(
|
||||
cursor: String? = null,
|
||||
since: String = getLastSyncTime()?.toString() ?: Instant.MIN.toString(),
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
package app.omnivore.omnivore.feature.save
|
||||
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.WindowManager
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.compose.foundation.background
|
||||
@ -16,9 +18,12 @@ import androidx.compose.ui.Alignment.Companion.TopCenter
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.work.Constraints
|
||||
import androidx.work.Data
|
||||
import androidx.work.NetworkType
|
||||
import androidx.work.OneTimeWorkRequest
|
||||
import androidx.work.OneTimeWorkRequestBuilder
|
||||
import androidx.work.OutOfQuotaPolicy
|
||||
import androidx.work.WorkInfo
|
||||
@ -49,7 +54,7 @@ class SaveSheetActivity : AppCompatActivity() {
|
||||
if (intent.type?.startsWith("text/plain") == true) {
|
||||
intent.getStringExtra(Intent.EXTRA_TEXT)?.let {
|
||||
extractedText = it
|
||||
workManager.enqueueSaveWorker(it)
|
||||
workManager.enqueueSaveWorker(this, it)
|
||||
Log.d(ContentValues.TAG, "Extracted text: $extractedText")
|
||||
}
|
||||
}
|
||||
@ -80,8 +85,6 @@ class SaveSheetActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
val scaffoldState: ScaffoldState = rememberScaffoldState()
|
||||
|
||||
|
||||
val message = when (saveState) {
|
||||
SaveState.DEFAULT -> ""
|
||||
SaveState.SAVING -> "Saved to Omnivore"
|
||||
@ -129,25 +132,28 @@ class SaveSheetActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun WorkManager.enqueueSaveWorker(url: String) {
|
||||
private fun WorkManager.enqueueSaveWorker(context: Context, url: String) {
|
||||
val constraints = Constraints.Builder()
|
||||
.setRequiredNetworkType(NetworkType.CONNECTED)
|
||||
.build()
|
||||
|
||||
val saveWorkerRequest = OneTimeWorkRequestBuilder<SaveURLWorker>()
|
||||
.setConstraints(constraints)
|
||||
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
|
||||
.setInputData(workDataOf("url" to url))
|
||||
.addTag(url)
|
||||
// Can add other configs like setBackoffCriteria to retry sync if failed
|
||||
.build()
|
||||
val syncWorkerRequest = OneTimeWorkRequestBuilder<LibrarySyncWorker>()
|
||||
val syncWorkerRequest = OneTimeWorkRequest.Builder(LibrarySyncWorker::class.java)
|
||||
.setConstraints(constraints)
|
||||
.addTag(url)
|
||||
.setInitialDelay(5.seconds.toJavaDuration())
|
||||
.build()
|
||||
|
||||
beginWith(saveWorkerRequest)
|
||||
val inputData = Data.Builder()
|
||||
.putString("url", url)
|
||||
.build()
|
||||
|
||||
val saveURLWorkRequest = OneTimeWorkRequest.Builder(SaveURLWorker::class.java)
|
||||
.setConstraints(constraints)
|
||||
.setInputData(inputData)
|
||||
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
|
||||
.addTag(url)
|
||||
.build()
|
||||
|
||||
beginWith(saveURLWorkRequest)
|
||||
.then(syncWorkerRequest)
|
||||
.enqueue()
|
||||
}
|
||||
|
||||
@ -1,10 +1,18 @@
|
||||
package app.omnivore.omnivore.feature.save
|
||||
|
||||
import android.app.Notification
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import androidx.compose.ui.text.intl.Locale
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.hilt.work.HiltWorker
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.ForegroundInfo
|
||||
import androidx.work.WorkerParameters
|
||||
import app.omnivore.omnivore.R
|
||||
import app.omnivore.omnivore.core.datastore.DatastoreRepository
|
||||
import app.omnivore.omnivore.core.datastore.omnivoreAuthToken
|
||||
import app.omnivore.omnivore.graphql.generated.SaveUrlMutation
|
||||
@ -26,16 +34,34 @@ class SaveURLWorker @AssistedInject constructor(
|
||||
@Assisted workerParams: WorkerParameters,
|
||||
private val datastoreRepository: DatastoreRepository,
|
||||
) : CoroutineWorker(appContext, workerParams) {
|
||||
override suspend fun getForegroundInfo(): ForegroundInfo {
|
||||
return ForegroundInfo(
|
||||
NOTIFICATION_ID,
|
||||
createNotification()
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val NOTIFICATION_CHANNEL_ID = "SAVE_URL_WORKER_CHANNEL"
|
||||
const val NOTIFICATION_CHANNEL_NAME = "URL Saver"
|
||||
const val NOTIFICATION_ID = 1
|
||||
}
|
||||
|
||||
override suspend fun doWork(): Result {
|
||||
return withContext(Dispatchers.IO){
|
||||
try {
|
||||
setForeground(createForegroundInfo())
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return Result.failure()
|
||||
}
|
||||
|
||||
return withContext(Dispatchers.IO) {
|
||||
val url = inputData.getString("url") ?: return@withContext Result.failure()
|
||||
if (saveURL(url)) Result.success() else Result.failure()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun saveURL(url: String): Boolean {
|
||||
|
||||
val authToken = datastoreRepository.getString(omnivoreAuthToken) ?: return false
|
||||
|
||||
val apolloClient = ApolloClient.Builder()
|
||||
@ -60,9 +86,10 @@ class SaveURLWorker @AssistedInject constructor(
|
||||
)
|
||||
)
|
||||
).execute()
|
||||
|
||||
return (response.data?.saveUrl?.onSaveSuccess?.url != null)
|
||||
} catch (e: Exception) {
|
||||
Log.d("omnivore", "FAILED TO SAVE ITEM")
|
||||
e.printStackTrace()
|
||||
return false
|
||||
}
|
||||
}
|
||||
@ -77,5 +104,43 @@ class SaveURLWorker @AssistedInject constructor(
|
||||
return null
|
||||
}
|
||||
|
||||
private fun createForegroundInfo(): ForegroundInfo {
|
||||
val notification = createNotification()
|
||||
return ForegroundInfo(NOTIFICATION_ID, notification)
|
||||
}
|
||||
|
||||
private fun createNotification(): Notification {
|
||||
val channelId = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
createNotificationChannel()
|
||||
} else {
|
||||
""
|
||||
}
|
||||
|
||||
return NotificationCompat.Builder(applicationContext, channelId)
|
||||
.setContentTitle("Saving URL")
|
||||
.setContentText("Your URL is being saved in the background.")
|
||||
.setSmallIcon(R.drawable.ic_notification) // Ensure this icon is valid
|
||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun createNotificationChannel(): String {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val channelName = NOTIFICATION_CHANNEL_NAME
|
||||
val channel = NotificationChannel(
|
||||
NOTIFICATION_CHANNEL_ID,
|
||||
channelName,
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
).apply {
|
||||
description = "Notification channel for URL saving"
|
||||
}
|
||||
|
||||
val notificationManager = applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
notificationManager.createNotificationChannel(channel)
|
||||
|
||||
return NOTIFICATION_CHANNEL_ID
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user