Fix crash on Android when saving items

This commit is contained in:
Jackson Harper
2024-07-04 12:12:45 +08:00
parent 19981645db
commit 0b80d9a37d
6 changed files with 166 additions and 18 deletions

View File

@ -1,5 +1,6 @@
*.iml
.gradle
.kotlin
/local.properties
/.idea/caches
/.idea/libraries

View File

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

View File

@ -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(),

View File

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

View File

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