migrate drafts to paging 3 (#2206)

* migrate drafts to paging 3

* migrate DraftHelper to coroutines
This commit is contained in:
Konrad Pozniak 2021-06-24 21:23:29 +02:00 committed by GitHub
commit f6dd131b95
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 357 additions and 337 deletions

View file

@ -28,13 +28,12 @@ import com.keylesspalace.tusky.db.DraftEntity
import com.keylesspalace.tusky.entity.NewPoll
import com.keylesspalace.tusky.entity.Status
import com.keylesspalace.tusky.util.IOUtils
import io.reactivex.rxjava3.core.Completable
import io.reactivex.rxjava3.core.Observable
import io.reactivex.rxjava3.core.Single
import io.reactivex.rxjava3.schedulers.Schedulers
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.File
import java.text.SimpleDateFormat
import java.util.*
import java.util.Date
import java.util.Locale
import javax.inject.Inject
class DraftHelper @Inject constructor(
@ -44,7 +43,7 @@ class DraftHelper @Inject constructor(
private val draftDao = db.draftDao()
fun saveDraft(
suspend fun saveDraft(
draftId: Int,
accountId: Long,
inReplyToId: String?,
@ -56,9 +55,7 @@ class DraftHelper @Inject constructor(
mediaDescriptions: List<String?>,
poll: NewPoll?,
failedToSend: Boolean
): Completable {
return Single.fromCallable {
) = withContext(Dispatchers.IO) {
val externalFilesDir = context.getExternalFilesDir("Tusky")
if (externalFilesDir == null || !(externalFilesDir.exists())) {
@ -103,7 +100,7 @@ class DraftHelper @Inject constructor(
)
}
DraftEntity(
val draft = DraftEntity(
id = draftId,
accountId = accountId,
inReplyToId = inReplyToId,
@ -116,42 +113,34 @@ class DraftHelper @Inject constructor(
failedToSend = failedToSend
)
}.flatMapCompletable { draft ->
draftDao.insertOrReplace(draft)
}.subscribeOn(Schedulers.io())
}
fun deleteDraftAndAttachments(draftId: Int): Completable {
return draftDao.find(draftId)
.flatMapCompletable { draft ->
draft?.let {
deleteDraftAndAttachments(it)
}
}
suspend fun deleteDraftAndAttachments(draftId: Int) {
draftDao.find(draftId)?.let { draft ->
deleteDraftAndAttachments(draft)
}
}
fun deleteDraftAndAttachments(draft: DraftEntity): Completable {
return deleteAttachments(draft)
.andThen(draftDao.delete(draft.id))
suspend fun deleteDraftAndAttachments(draft: DraftEntity) {
deleteAttachments(draft)
draftDao.delete(draft.id)
}
fun deleteAllDraftsAndAttachmentsForAccount(accountId: Long) {
draftDao.loadDraftsSingle(accountId)
.flatMapObservable { Observable.fromIterable(it) }
.flatMapCompletable { draft ->
deleteDraftAndAttachments(draft)
}.subscribeOn(Schedulers.io())
.subscribe()
suspend fun deleteAllDraftsAndAttachmentsForAccount(accountId: Long) {
draftDao.loadDrafts(accountId).forEach { draft ->
deleteDraftAndAttachments(draft)
}
}
fun deleteAttachments(draft: DraftEntity): Completable {
return Completable.fromCallable {
suspend fun deleteAttachments(draft: DraftEntity) {
withContext(Dispatchers.IO) {
draft.attachments.forEach { attachment ->
if (context.contentResolver.delete(attachment.uri, null, null) == 0) {
Log.e("DraftHelper", "Did not delete file ${attachment.uriString}")
}
}
}.subscribeOn(Schedulers.io())
}
}
private fun Uri.isNotInFolder(folder: File): Boolean {

View file

@ -22,6 +22,7 @@ import android.util.Log
import android.widget.LinearLayout
import android.widget.Toast
import androidx.activity.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import autodispose2.androidx.lifecycle.AndroidLifecycleScopeProvider.from
@ -34,9 +35,10 @@ import com.keylesspalace.tusky.components.compose.ComposeActivity
import com.keylesspalace.tusky.databinding.ActivityDraftsBinding
import com.keylesspalace.tusky.db.DraftEntity
import com.keylesspalace.tusky.di.ViewModelFactory
import com.keylesspalace.tusky.util.hide
import com.keylesspalace.tusky.util.show
import com.keylesspalace.tusky.util.visible
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import retrofit2.HttpException
import javax.inject.Inject
@ -51,7 +53,6 @@ class DraftsActivity : BaseActivity(), DraftActionListener {
private lateinit var bottomSheet: BottomSheetBehavior<LinearLayout>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityDraftsBinding.inflate(layoutInflater)
@ -74,16 +75,15 @@ class DraftsActivity : BaseActivity(), DraftActionListener {
bottomSheet = BottomSheetBehavior.from(binding.bottomSheet.root)
viewModel.drafts.observe(this) { draftList ->
if (draftList.isEmpty()) {
binding.draftsRecyclerView.hide()
binding.draftsErrorMessageView.show()
} else {
binding.draftsRecyclerView.show()
binding.draftsErrorMessageView.hide()
adapter.submitList(draftList)
lifecycleScope.launch {
viewModel.drafts.collectLatest { draftData ->
adapter.submitData(draftData)
}
}
adapter.addLoadStateListener {
binding.draftsErrorMessageView.visible(adapter.itemCount == 0)
}
}
override fun onOpenDraft(draft: DraftEntity) {

View file

@ -17,7 +17,7 @@ package com.keylesspalace.tusky.components.drafts
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.paging.PagedListAdapter
import androidx.paging.PagingDataAdapter
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
@ -35,7 +35,7 @@ interface DraftActionListener {
class DraftsAdapter(
private val listener: DraftActionListener
) : PagedListAdapter<DraftEntity, BindingHolder<ItemDraftBinding>>(
) : PagingDataAdapter<DraftEntity, BindingHolder<ItemDraftBinding>>(
object : DiffUtil.ItemCallback<DraftEntity>() {
override fun areItemsTheSame(oldItem: DraftEntity, newItem: DraftEntity): Boolean {
return oldItem.id == newItem.id
@ -87,6 +87,5 @@ class DraftsAdapter(
holder.binding.draftPoll.hide()
}
}
}
}
}

View file

@ -16,13 +16,17 @@
package com.keylesspalace.tusky.components.drafts
import androidx.lifecycle.ViewModel
import androidx.paging.toLiveData
import androidx.lifecycle.viewModelScope
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.cachedIn
import com.keylesspalace.tusky.db.AccountManager
import com.keylesspalace.tusky.db.AppDatabase
import com.keylesspalace.tusky.db.DraftEntity
import com.keylesspalace.tusky.entity.Status
import com.keylesspalace.tusky.network.MastodonApi
import io.reactivex.rxjava3.core.Single
import kotlinx.coroutines.launch
import javax.inject.Inject
class DraftsViewModel @Inject constructor(
@ -32,22 +36,28 @@ class DraftsViewModel @Inject constructor(
val draftHelper: DraftHelper
) : ViewModel() {
val drafts = database.draftDao().loadDrafts(accountManager.activeAccount?.id!!).toLiveData(pageSize = 20)
val drafts = Pager(
config = PagingConfig(pageSize = 20),
pagingSourceFactory = { database.draftDao().draftsPagingSource(accountManager.activeAccount?.id!!) }
).flow
.cachedIn(viewModelScope)
private val deletedDrafts: MutableList<DraftEntity> = mutableListOf()
fun deleteDraft(draft: DraftEntity) {
// this does not immediately delete media files to avoid unnecessary file operations
// in case the user decides to restore the draft
database.draftDao().delete(draft.id)
.subscribe()
deletedDrafts.add(draft)
viewModelScope.launch {
database.draftDao().delete(draft.id)
deletedDrafts.add(draft)
}
}
fun restoreDraft(draft: DraftEntity) {
database.draftDao().insertOrReplace(draft)
.subscribe()
deletedDrafts.remove(draft)
viewModelScope.launch {
database.draftDao().insertOrReplace(draft)
deletedDrafts.remove(draft)
}
}
fun getToot(tootId: String): Single<Status> {
@ -55,9 +65,10 @@ class DraftsViewModel @Inject constructor(
}
override fun onCleared() {
deletedDrafts.forEach {
draftHelper.deleteAttachments(it).subscribe()
viewModelScope.launch {
deletedDrafts.forEach {
draftHelper.deleteAttachments(it)
}
}
}
}