Drafts v2 (#2032)
* cleanup warnings, reorganize some code * move ComposeAutoCompleteAdapter to compose package * composeOptions doesn't need to be a class member * add DraftsActivity and DraftsViewModel * drafts * remove unnecessary Unit in ComposeViewModel * add schema/25.json * fix db migration * drafts * cleanup code * fix compose activity rotation bug * fix media descriptions getting lost when restoring a draft * improve deleting drafts * fix ComposeActivityTest * improve draft layout for almost empty drafts * reformat code * show toast when opening reply to deleted toot * improve item_draft layout
This commit is contained in:
parent
baa915a0a3
commit
940d6d395a
85 changed files with 2032 additions and 381 deletions
|
|
@ -0,0 +1,159 @@
|
|||
/* Copyright 2021 Tusky Contributors
|
||||
*
|
||||
* This file is a part of Tusky.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky.components.drafts
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import android.webkit.MimeTypeMap
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.core.net.toUri
|
||||
import com.keylesspalace.tusky.BuildConfig
|
||||
import com.keylesspalace.tusky.db.AppDatabase
|
||||
import com.keylesspalace.tusky.db.DraftAttachment
|
||||
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.Completable
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import java.io.File
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
|
||||
class DraftHelper @Inject constructor(
|
||||
val context: Context,
|
||||
db: AppDatabase
|
||||
) {
|
||||
|
||||
private val draftDao = db.draftDao()
|
||||
|
||||
fun saveDraft(
|
||||
draftId: Int,
|
||||
accountId: Long,
|
||||
inReplyToId: String?,
|
||||
content: String?,
|
||||
contentWarning: String?,
|
||||
sensitive: Boolean,
|
||||
visibility: Status.Visibility,
|
||||
mediaUris: List<String>,
|
||||
mediaDescriptions: List<String?>,
|
||||
poll: NewPoll?,
|
||||
failedToSend: Boolean
|
||||
): Completable {
|
||||
return Single.fromCallable {
|
||||
|
||||
val draftDirectory = context.getExternalFilesDir("Tusky")
|
||||
|
||||
if (draftDirectory == null || !(draftDirectory.exists())) {
|
||||
Log.e("DraftHelper", "Error obtaining directory to save media.")
|
||||
throw Exception()
|
||||
}
|
||||
|
||||
val uris = mediaUris.map { uriString ->
|
||||
uriString.toUri()
|
||||
}.map { uri ->
|
||||
if (uri.isNotInFolder(draftDirectory)) {
|
||||
uri.copyToFolder(draftDirectory)
|
||||
} else {
|
||||
uri
|
||||
}
|
||||
}
|
||||
|
||||
val types = uris.map { uri ->
|
||||
val mimeType = context.contentResolver.getType(uri)
|
||||
when (mimeType?.substring(0, mimeType.indexOf('/'))) {
|
||||
"video" -> DraftAttachment.Type.VIDEO
|
||||
"image" -> DraftAttachment.Type.IMAGE
|
||||
"audio" -> DraftAttachment.Type.AUDIO
|
||||
else -> throw IllegalStateException("unknown media type")
|
||||
}
|
||||
}
|
||||
|
||||
val attachments: MutableList<DraftAttachment> = mutableListOf()
|
||||
for (i in mediaUris.indices) {
|
||||
attachments.add(
|
||||
DraftAttachment(
|
||||
uriString = uris[i].toString(),
|
||||
description = mediaDescriptions[i],
|
||||
type = types[i]
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
DraftEntity(
|
||||
id = draftId,
|
||||
accountId = accountId,
|
||||
inReplyToId = inReplyToId,
|
||||
content = content,
|
||||
contentWarning = contentWarning,
|
||||
sensitive = sensitive,
|
||||
visibility = visibility,
|
||||
attachments = attachments,
|
||||
poll = poll,
|
||||
failedToSend = failedToSend
|
||||
)
|
||||
|
||||
}.flatMapCompletable { draft ->
|
||||
draftDao.insertOrReplace(draft)
|
||||
}.subscribeOn(Schedulers.io())
|
||||
}
|
||||
|
||||
fun deleteDraftAndAttachments(draftId: Int): Completable {
|
||||
return draftDao.find(draftId)
|
||||
.flatMapCompletable { draft ->
|
||||
deleteDraftAndAttachments(draft)
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteDraftAndAttachments(draft: DraftEntity): Completable {
|
||||
return deleteAttachments(draft)
|
||||
.andThen(draftDao.delete(draft.id))
|
||||
}
|
||||
|
||||
fun deleteAttachments(draft: DraftEntity): Completable {
|
||||
return Completable.fromCallable {
|
||||
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 {
|
||||
val filePath = path ?: return true
|
||||
return File(filePath).parentFile == folder
|
||||
}
|
||||
|
||||
private fun Uri.copyToFolder(folder: File): Uri {
|
||||
val contentResolver = context.contentResolver
|
||||
|
||||
val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(Date())
|
||||
|
||||
val mimeType = contentResolver.getType(this)
|
||||
val map = MimeTypeMap.getSingleton()
|
||||
val fileExtension = map.getExtensionFromMimeType(mimeType)
|
||||
|
||||
val filename = String.format("Tusky_Draft_Media_%s.%s", timeStamp, fileExtension)
|
||||
val file = File(folder, filename)
|
||||
IOUtils.copyToFile(contentResolver, this, file)
|
||||
return FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".fileprovider", file)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
/* Copyright 2020 Tusky Contributors
|
||||
*
|
||||
* This file is a part of Tusky.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky.components.drafts
|
||||
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import androidx.appcompat.widget.AppCompatImageView
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.db.DraftAttachment
|
||||
|
||||
class DraftMediaAdapter(
|
||||
private val attachmentClick: () -> Unit
|
||||
) : ListAdapter<DraftAttachment, DraftMediaAdapter.DraftMediaViewHolder>(
|
||||
object: DiffUtil.ItemCallback<DraftAttachment>() {
|
||||
override fun areItemsTheSame(oldItem: DraftAttachment, newItem: DraftAttachment): Boolean {
|
||||
return oldItem == newItem
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: DraftAttachment, newItem: DraftAttachment): Boolean {
|
||||
return oldItem == newItem
|
||||
}
|
||||
|
||||
}
|
||||
) {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DraftMediaViewHolder {
|
||||
return DraftMediaViewHolder(AppCompatImageView(parent.context))
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: DraftMediaViewHolder, position: Int) {
|
||||
getItem(position)?.let { attachment ->
|
||||
if (attachment.type == DraftAttachment.Type.AUDIO) {
|
||||
holder.imageView.setImageResource(R.drawable.ic_music_box_preview_24dp)
|
||||
} else {
|
||||
Glide.with(holder.itemView.context)
|
||||
.load(attachment.uri)
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.dontAnimate()
|
||||
.into(holder.imageView)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inner class DraftMediaViewHolder(val imageView: ImageView)
|
||||
: RecyclerView.ViewHolder(imageView) {
|
||||
init {
|
||||
val thumbnailViewSize =
|
||||
imageView.context.resources.getDimensionPixelSize(R.dimen.compose_media_preview_size)
|
||||
val layoutParams = ConstraintLayout.LayoutParams(thumbnailViewSize, thumbnailViewSize)
|
||||
val margin = itemView.context.resources
|
||||
.getDimensionPixelSize(R.dimen.compose_media_preview_margin)
|
||||
val marginBottom = itemView.context.resources
|
||||
.getDimensionPixelSize(R.dimen.compose_media_preview_margin_bottom)
|
||||
layoutParams.setMargins(margin, 0, margin, marginBottom)
|
||||
imageView.layoutParams = layoutParams
|
||||
imageView.scaleType = ImageView.ScaleType.CENTER_CROP
|
||||
imageView.setOnClickListener {
|
||||
attachmentClick()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,197 @@
|
|||
/* Copyright 2020 Tusky Contributors
|
||||
*
|
||||
* This file is a part of Tusky.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky.components.drafts
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.Toast
|
||||
import androidx.activity.viewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.recyclerview.widget.DividerItemDecoration
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.keylesspalace.tusky.BaseActivity
|
||||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.SavedTootActivity
|
||||
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.uber.autodispose.android.lifecycle.autoDispose
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import retrofit2.HttpException
|
||||
import javax.inject.Inject
|
||||
|
||||
class DraftsActivity : BaseActivity(), DraftActionListener {
|
||||
|
||||
@Inject
|
||||
lateinit var viewModelFactory: ViewModelFactory
|
||||
|
||||
private val viewModel: DraftsViewModel by viewModels { viewModelFactory }
|
||||
|
||||
private lateinit var binding: ActivityDraftsBinding
|
||||
private lateinit var bottomSheet: BottomSheetBehavior<LinearLayout>
|
||||
|
||||
private var oldDraftsButton: MenuItem? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
binding = ActivityDraftsBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
setSupportActionBar(binding.includedToolbar.toolbar)
|
||||
supportActionBar?.apply {
|
||||
title = getString(R.string.title_drafts)
|
||||
setDisplayHomeAsUpEnabled(true)
|
||||
setDisplayShowHomeEnabled(true)
|
||||
}
|
||||
|
||||
binding.draftsErrorMessageView.setup(R.drawable.elephant_friend_empty, R.string.no_saved_status)
|
||||
|
||||
val adapter = DraftsAdapter(this)
|
||||
|
||||
binding.draftsRecyclerView.adapter = adapter
|
||||
binding.draftsRecyclerView.layoutManager = LinearLayoutManager(this)
|
||||
binding.draftsRecyclerView.addItemDecoration(DividerItemDecoration(this, DividerItemDecoration.VERTICAL))
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
menuInflater.inflate(R.menu.drafts, menu)
|
||||
oldDraftsButton = menu.findItem(R.id.action_old_drafts)
|
||||
viewModel.showOldDraftsButton()
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.autoDispose(this, Lifecycle.Event.ON_DESTROY)
|
||||
.subscribe { showOldDraftsButton ->
|
||||
oldDraftsButton?.isVisible = showOldDraftsButton
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
android.R.id.home -> {
|
||||
onBackPressed()
|
||||
return true
|
||||
}
|
||||
R.id.action_old_drafts -> {
|
||||
val intent = Intent(this, SavedTootActivity::class.java)
|
||||
startActivityWithSlideInAnimation(intent)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
override fun onOpenDraft(draft: DraftEntity) {
|
||||
|
||||
if (draft.inReplyToId != null) {
|
||||
bottomSheet.state = BottomSheetBehavior.STATE_COLLAPSED
|
||||
viewModel.getToot(draft.inReplyToId)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.autoDispose(this)
|
||||
.subscribe({ status ->
|
||||
val composeOptions = ComposeActivity.ComposeOptions(
|
||||
draftId = draft.id,
|
||||
tootText = draft.content,
|
||||
contentWarning = draft.contentWarning,
|
||||
inReplyToId = draft.inReplyToId,
|
||||
replyingStatusContent = status.content.toString(),
|
||||
replyingStatusAuthor = status.account.localUsername,
|
||||
draftAttachments = draft.attachments,
|
||||
poll = draft.poll,
|
||||
sensitive = draft.sensitive,
|
||||
visibility = draft.visibility
|
||||
)
|
||||
|
||||
bottomSheet.state = BottomSheetBehavior.STATE_HIDDEN
|
||||
|
||||
startActivity(ComposeActivity.startIntent(this, composeOptions))
|
||||
|
||||
}, { throwable ->
|
||||
|
||||
bottomSheet.state = BottomSheetBehavior.STATE_HIDDEN
|
||||
|
||||
Log.w(TAG, "failed loading reply information", throwable)
|
||||
|
||||
if (throwable is HttpException && throwable.code() == 404) {
|
||||
// the original status to which a reply was drafted has been deleted
|
||||
// let's open the ComposeActivity without reply information
|
||||
Toast.makeText(this, getString(R.string.drafts_toot_reply_removed), Toast.LENGTH_LONG).show()
|
||||
openDraftWithoutReply(draft)
|
||||
} else {
|
||||
Snackbar.make(binding.root, getString(R.string.drafts_failed_loading_reply), Snackbar.LENGTH_SHORT)
|
||||
.show()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
openDraftWithoutReply(draft)
|
||||
}
|
||||
}
|
||||
|
||||
private fun openDraftWithoutReply(draft: DraftEntity) {
|
||||
val composeOptions = ComposeActivity.ComposeOptions(
|
||||
draftId = draft.id,
|
||||
tootText = draft.content,
|
||||
contentWarning = draft.contentWarning,
|
||||
draftAttachments = draft.attachments,
|
||||
poll = draft.poll,
|
||||
sensitive = draft.sensitive,
|
||||
visibility = draft.visibility
|
||||
)
|
||||
|
||||
startActivity(ComposeActivity.startIntent(this, composeOptions))
|
||||
}
|
||||
|
||||
override fun onDeleteDraft(draft: DraftEntity) {
|
||||
viewModel.deleteDraft(draft)
|
||||
Snackbar.make(binding.root, getString(R.string.draft_deleted), Snackbar.LENGTH_LONG)
|
||||
.setAction(R.string.action_undo) {
|
||||
viewModel.restoreDraft(draft)
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TAG = "DraftsActivity"
|
||||
|
||||
fun newIntent(context: Context) = Intent(context, DraftsActivity::class.java)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
/* Copyright 2021 Tusky Contributors
|
||||
*
|
||||
* This file is a part of Tusky.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky.components.drafts
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.paging.PagedListAdapter
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.keylesspalace.tusky.databinding.ItemDraftBinding
|
||||
import com.keylesspalace.tusky.db.DraftEntity
|
||||
import com.keylesspalace.tusky.util.BindingViewHolder
|
||||
import com.keylesspalace.tusky.util.hide
|
||||
import com.keylesspalace.tusky.util.show
|
||||
import com.keylesspalace.tusky.util.visible
|
||||
|
||||
interface DraftActionListener {
|
||||
fun onOpenDraft(draft: DraftEntity)
|
||||
fun onDeleteDraft(draft: DraftEntity)
|
||||
}
|
||||
|
||||
class DraftsAdapter(
|
||||
private val listener: DraftActionListener
|
||||
) : PagedListAdapter<DraftEntity, BindingViewHolder<ItemDraftBinding>>(
|
||||
object : DiffUtil.ItemCallback<DraftEntity>() {
|
||||
override fun areItemsTheSame(oldItem: DraftEntity, newItem: DraftEntity): Boolean {
|
||||
return oldItem.id == newItem.id
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: DraftEntity, newItem: DraftEntity): Boolean {
|
||||
return oldItem == newItem
|
||||
}
|
||||
}
|
||||
) {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingViewHolder<ItemDraftBinding> {
|
||||
|
||||
val binding = ItemDraftBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
|
||||
val viewHolder = BindingViewHolder(binding)
|
||||
|
||||
binding.draftMediaPreview.layoutManager = LinearLayoutManager(binding.root.context, RecyclerView.HORIZONTAL, false)
|
||||
binding.draftMediaPreview.adapter = DraftMediaAdapter {
|
||||
getItem(viewHolder.adapterPosition)?.let { draft ->
|
||||
listener.onOpenDraft(draft)
|
||||
}
|
||||
}
|
||||
|
||||
return viewHolder
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: BindingViewHolder<ItemDraftBinding>, position: Int) {
|
||||
getItem(position)?.let { draft ->
|
||||
holder.binding.root.setOnClickListener {
|
||||
listener.onOpenDraft(draft)
|
||||
}
|
||||
holder.binding.deleteButton.setOnClickListener {
|
||||
listener.onDeleteDraft(draft)
|
||||
}
|
||||
holder.binding.draftSendingInfo.visible(draft.failedToSend)
|
||||
|
||||
holder.binding.contentWarning.visible(!draft.contentWarning.isNullOrEmpty())
|
||||
holder.binding.contentWarning.text = draft.contentWarning
|
||||
holder.binding.content.text = draft.content
|
||||
|
||||
holder.binding.draftMediaPreview.visible(draft.attachments.isNotEmpty())
|
||||
(holder.binding.draftMediaPreview.adapter as DraftMediaAdapter).submitList(draft.attachments)
|
||||
|
||||
if (draft.poll != null) {
|
||||
holder.binding.draftPoll.show()
|
||||
holder.binding.draftPoll.setPoll(draft.poll)
|
||||
} else {
|
||||
holder.binding.draftPoll.hide()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
/* Copyright 2020 Tusky Contributors
|
||||
*
|
||||
* This file is a part of Tusky.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky.components.drafts
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.paging.toLiveData
|
||||
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.Observable
|
||||
import io.reactivex.Single
|
||||
import javax.inject.Inject
|
||||
|
||||
class DraftsViewModel @Inject constructor(
|
||||
val database: AppDatabase,
|
||||
val accountManager: AccountManager,
|
||||
val api: MastodonApi,
|
||||
val draftHelper: DraftHelper
|
||||
) : ViewModel() {
|
||||
|
||||
val drafts = database.draftDao().loadDrafts(accountManager.activeAccount?.id!!).toLiveData(pageSize = 20)
|
||||
|
||||
private val deletedDrafts: MutableList<DraftEntity> = mutableListOf()
|
||||
|
||||
fun showOldDraftsButton(): Observable<Boolean> {
|
||||
return database.tootDao().savedTootCount()
|
||||
.map { count -> count > 0 }
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
fun restoreDraft(draft: DraftEntity) {
|
||||
database.draftDao().insertOrReplace(draft)
|
||||
.subscribe()
|
||||
deletedDrafts.remove(draft)
|
||||
}
|
||||
|
||||
fun getToot(tootId: String): Single<Status> {
|
||||
return api.statusSingle(tootId)
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
deletedDrafts.forEach {
|
||||
draftHelper.deleteAttachments(it).subscribe()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue