Implement first version of redraft feature

This commit is contained in:
Frédéric Gerber 2022-10-19 23:03:32 +02:00 committed by fgerber
parent 650e7b248b
commit 746242eed8
6 changed files with 160 additions and 21 deletions

View File

@ -87,7 +87,13 @@ class PostCreationActivity : BaseThemedWithoutBarActivity() {
} }
} ?: InstanceDatabaseEntity("", "") } ?: InstanceDatabaseEntity("", "")
val _model: PostCreationViewModel by viewModels { PostCreationViewModelFactory(application, intent.clipData!!, instance) } val _model: PostCreationViewModel by viewModels {
PostCreationViewModelFactory(
application,
intent.clipData!!,
instance
)
}
model = _model model = _model
model.getPhotoData().observe(this) { newPhotoData -> model.getPhotoData().observe(this) { newPhotoData ->
@ -116,15 +122,20 @@ class PostCreationActivity : BaseThemedWithoutBarActivity() {
} }
binding.addPhotoButton.isEnabled = uiState.addPhotoButtonEnabled binding.addPhotoButton.isEnabled = uiState.addPhotoButtonEnabled
enableButton(uiState.postCreationSendButtonEnabled) enableButton(uiState.postCreationSendButtonEnabled)
binding.uploadProgressBar.visibility = if(uiState.uploadProgressBarVisible) VISIBLE else INVISIBLE binding.uploadProgressBar.visibility =
if (uiState.uploadProgressBarVisible) VISIBLE else INVISIBLE
binding.uploadProgressBar.progress = uiState.uploadProgress binding.uploadProgressBar.progress = uiState.uploadProgress
binding.uploadCompletedTextview.visibility = if(uiState.uploadCompletedTextviewVisible) VISIBLE else INVISIBLE binding.uploadCompletedTextview.visibility =
if (uiState.uploadCompletedTextviewVisible) VISIBLE else INVISIBLE
binding.removePhotoButton.isEnabled = uiState.removePhotoButtonEnabled binding.removePhotoButton.isEnabled = uiState.removePhotoButtonEnabled
binding.editPhotoButton.isEnabled = uiState.editPhotoButtonEnabled binding.editPhotoButton.isEnabled = uiState.editPhotoButtonEnabled
binding.uploadError.visibility = if(uiState.uploadErrorVisible) VISIBLE else INVISIBLE binding.uploadError.visibility =
binding.uploadErrorTextExplanation.visibility = if(uiState.uploadErrorExplanationVisible) VISIBLE else INVISIBLE if (uiState.uploadErrorVisible) VISIBLE else INVISIBLE
binding.uploadErrorTextExplanation.visibility =
if (uiState.uploadErrorExplanationVisible) VISIBLE else INVISIBLE
binding.toolbarPostCreation.visibility = if(uiState.isCarousel) VISIBLE else INVISIBLE binding.toolbarPostCreation.visibility =
if (uiState.isCarousel) VISIBLE else INVISIBLE
binding.carousel.layoutCarousel = uiState.isCarousel binding.carousel.layoutCarousel = uiState.isCarousel
@ -155,6 +166,11 @@ class PostCreationActivity : BaseThemedWithoutBarActivity() {
binding.newPostDescriptionInputField.doAfterTextChanged { binding.newPostDescriptionInputField.doAfterTextChanged {
model.newPostDescriptionChanged(binding.newPostDescriptionInputField.text) model.newPostDescriptionChanged(binding.newPostDescriptionInputField.text)
} }
// Fetch existing description passed through putExtra (if any)
val existingDescription = intent.getStringExtra(PhotoEditActivity.PICTURE_DESCRIPTION)
binding.newPostDescriptionInputField.setText(existingDescription)
binding.postTextInputLayout.counterMaxLength = instance.maxStatusChars binding.postTextInputLayout.counterMaxLength = instance.maxStatusChars
binding.carousel.apply { binding.carousel.apply {

View File

@ -80,6 +80,7 @@ class PhotoEditActivity : BaseThemedWithBarActivity() {
companion object{ companion object{
internal const val PICTURE_URI = "picture_uri" internal const val PICTURE_URI = "picture_uri"
internal const val PICTURE_POSITION = "picture_position" internal const val PICTURE_POSITION = "picture_position"
internal const val PICTURE_DESCRIPTION = "picture_description"
private var executor: ExecutorService = newSingleThreadExecutor() private var executor: ExecutorService = newSingleThreadExecutor()
private var future: Future<*>? = null private var future: Future<*>? = null

View File

@ -3,7 +3,9 @@ package org.pixeldroid.app.posts
import android.Manifest import android.Manifest
import android.app.Activity import android.app.Activity
import android.app.AlertDialog import android.app.AlertDialog
import android.content.ClipData
import android.content.Intent import android.content.Intent
import android.graphics.Bitmap
import android.graphics.Typeface import android.graphics.Typeface
import android.graphics.drawable.AnimatedVectorDrawable import android.graphics.drawable.AnimatedVectorDrawable
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
@ -17,6 +19,7 @@ import android.view.ViewGroup
import android.widget.* import android.widget.*
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.net.toUri
import androidx.core.view.WindowCompat import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat import androidx.core.view.WindowInsetsControllerCompat
@ -37,10 +40,17 @@ import com.karumi.dexter.listener.PermissionDeniedResponse
import com.karumi.dexter.listener.PermissionGrantedResponse import com.karumi.dexter.listener.PermissionGrantedResponse
import com.karumi.dexter.listener.single.BasePermissionListener import com.karumi.dexter.listener.single.BasePermissionListener
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import okhttp3.*
import okio.BufferedSink
import okio.Okio
import okio.buffer
import okio.sink
import org.pixeldroid.app.R import org.pixeldroid.app.R
import org.pixeldroid.app.databinding.AlbumImageViewBinding import org.pixeldroid.app.databinding.AlbumImageViewBinding
import org.pixeldroid.app.databinding.OpenedAlbumBinding import org.pixeldroid.app.databinding.OpenedAlbumBinding
import org.pixeldroid.app.databinding.PostFragmentBinding import org.pixeldroid.app.databinding.PostFragmentBinding
import org.pixeldroid.app.postCreation.PostCreationActivity
import org.pixeldroid.app.postCreation.photoEdit.PhotoEditActivity
import org.pixeldroid.app.posts.MediaViewerActivity.Companion.openActivity import org.pixeldroid.app.posts.MediaViewerActivity.Companion.openActivity
import org.pixeldroid.app.utils.BlurHashDecoder import org.pixeldroid.app.utils.BlurHashDecoder
import org.pixeldroid.app.utils.api.PixelfedAPI import org.pixeldroid.app.utils.api.PixelfedAPI
@ -49,12 +59,14 @@ import org.pixeldroid.app.utils.api.objects.Status
import org.pixeldroid.app.utils.api.objects.Status.Companion.POST_COMMENT_TAG import org.pixeldroid.app.utils.api.objects.Status.Companion.POST_COMMENT_TAG
import org.pixeldroid.app.utils.api.objects.Status.Companion.POST_TAG import org.pixeldroid.app.utils.api.objects.Status.Companion.POST_TAG
import org.pixeldroid.app.utils.api.objects.Status.Companion.VIEW_COMMENTS_TAG import org.pixeldroid.app.utils.api.objects.Status.Companion.VIEW_COMMENTS_TAG
import org.pixeldroid.app.posts.fromHtml
import org.pixeldroid.app.utils.db.AppDatabase import org.pixeldroid.app.utils.db.AppDatabase
import org.pixeldroid.app.utils.di.PixelfedAPIHolder import org.pixeldroid.app.utils.di.PixelfedAPIHolder
import org.pixeldroid.app.utils.setProfileImageFromURL import org.pixeldroid.app.utils.setProfileImageFromURL
import retrofit2.HttpException import retrofit2.HttpException
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
import java.io.OutputStream
import kotlin.math.roundToInt import kotlin.math.roundToInt
@ -322,7 +334,7 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
//Call the api function //Call the api function
status?.id?.let { id -> status?.id?.let { id ->
try { try {
if(bookmarked) { if (bookmarked) {
api.bookmarkStatus(id) api.bookmarkStatus(id)
} else { } else {
api.undoBookmarkStatus(id) api.undoBookmarkStatus(id)
@ -337,7 +349,10 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
} catch (exception: HttpException) { } catch (exception: HttpException) {
Toast.makeText( Toast.makeText(
binding.root.context, binding.root.context,
binding.root.context.getString(R.string.bookmark_post_failed_error, exception.code()), binding.root.context.getString(
R.string.bookmark_post_failed_error,
exception.code()
),
Toast.LENGTH_SHORT Toast.LENGTH_SHORT
).show() ).show()
} catch (exception: IOException) { } catch (exception: IOException) {
@ -351,6 +366,14 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
return null return null
} }
private fun OutputStream.writeBitmap(bitmap: Bitmap) {
use { out ->
//(quality is ignored for PNG)
bitmap.compress(Bitmap.CompressFormat.PNG, 85, out)
out.flush()
}
}
private fun activateMoreButton(apiHolder: PixelfedAPIHolder, db: AppDatabase, lifecycleScope: LifecycleCoroutineScope){ private fun activateMoreButton(apiHolder: PixelfedAPIHolder, db: AppDatabase, lifecycleScope: LifecycleCoroutineScope){
var bookmarked: Boolean? = null var bookmarked: Boolean? = null
binding.statusMore.setOnClickListener { binding.statusMore.setOnClickListener {
@ -439,30 +462,95 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
val builder = AlertDialog.Builder(binding.root.context) val builder = AlertDialog.Builder(binding.root.context)
builder.apply { builder.apply {
setMessage(R.string.delete_dialog) setMessage(R.string.delete_dialog)
setPositiveButton(android.R.string.ok) { _, _ ->
lifecycleScope.launch {
deletePost(apiHolder.api ?: apiHolder.setToCurrentUser(), db)
}
}
setNegativeButton(android.R.string.cancel) { _, _ -> }
show()
}
true
}
R.id.post_more_menu_redraft -> {
val builder = AlertDialog.Builder(binding.root.context)
builder.apply {
setMessage(R.string.redraft_dialog)
setPositiveButton(android.R.string.ok) { _, _ -> setPositiveButton(android.R.string.ok) { _, _ ->
lifecycleScope.launch { lifecycleScope.launch {
val user = db.userDao().getActiveUser()!! val user = db.userDao().getActiveUser()!!
status?.id?.let { id -> status?.id?.let { id ->
db.homePostDao().delete(id, user.user_id, user.instance_uri)
db.publicPostDao().delete(id, user.user_id, user.instance_uri)
try { try {
val api = apiHolder.api ?: apiHolder.setToCurrentUser() // Prepare image download
api.deleteStatus(id) val imageUri = Uri.parse(status?.media_attachments?.getOrNull(binding.postPager.currentItem)?.url?: "")
binding.root.visibility = View.GONE val request: Request = Request.Builder().url(imageUri.toString()).build()
// TODO: check whether image is in cache directory already (e.g. using Glide)?
OkHttpClient().newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {}
@Throws(IOException::class)
override fun onResponse(call: Call, response: Response) {
// Download existing image
val downloadedFile = File(context.cacheDir, "temp_redraft_img.png")
val sink: BufferedSink =
downloadedFile.sink().buffer()
sink.writeAll(response.body!!.source())
sink.close()
val downloadedUri = Uri.fromFile(downloadedFile)
// Create new post creation activity
val intent = Intent(context, PostCreationActivity::class.java)
// Pass downloaded image to post creation activity
intent.apply {
if (clipData == null) {
clipData = ClipData("", emptyArray(), ClipData.Item(downloadedUri))
} else {
clipData!!.addItem(ClipData.Item(downloadedUri))
}
addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
val imageDescription = status?.content
// Pass description of existing image to post creation activity
if (imageDescription != null) {
intent.putExtra(PhotoEditActivity.PICTURE_DESCRIPTION, fromHtml(imageDescription!!).toString())
}
// Launch post creation activity
binding.root.context.startActivity(intent)
// TODO: find better way to clean up temporary file in cacheDir?
downloadedFile.deleteOnExit()
}
})
} catch (exception: HttpException) { } catch (exception: HttpException) {
Toast.makeText( Toast.makeText(
binding.root.context, binding.root.context,
binding.root.context.getString(R.string.delete_post_failed_error, exception.code()), binding.root.context.getString(R.string.redraft_post_failed_error, exception.code()),
Toast.LENGTH_SHORT Toast.LENGTH_SHORT
).show() ).show()
} catch (exception: IOException) { } catch (exception: IOException) {
Toast.makeText( Toast.makeText(
binding.root.context, binding.root.context,
binding.root.context.getString(R.string.delete_post_failed_io_except), binding.root.context.getString(R.string.redraft_post_failed_io_except),
Toast.LENGTH_SHORT Toast.LENGTH_SHORT
).show() ).show()
} }
// Delete original post
deletePost(apiHolder.api ?: apiHolder.setToCurrentUser(), db)
} }
} }
} }
@ -546,6 +634,7 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
status?.media_attachments?.let { binding.postPagerHost.images = ArrayList(it) } status?.media_attachments?.let { binding.postPagerHost.images = ArrayList(it) }
} }
private fun ImageView.animateView() { private fun ImageView.animateView() {
visibility = View.VISIBLE visibility = View.VISIBLE
when (val drawable = drawable) { when (val drawable = drawable) {
@ -627,7 +716,29 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
} }
} }
private suspend fun deletePost(api: PixelfedAPI, db: AppDatabase) {
val user = db.userDao().getActiveUser()!!
status?.id?.let { id ->
db.homePostDao().delete(id, user.user_id, user.instance_uri)
db.publicPostDao().delete(id, user.user_id, user.instance_uri)
try {
api.deleteStatus(id)
binding.root.visibility = View.GONE
} catch (exception: HttpException) {
Toast.makeText(
binding.root.context,
binding.root.context.getString(R.string.delete_post_failed_error, exception.code()),
Toast.LENGTH_SHORT
).show()
} catch (exception: IOException) {
Toast.makeText(
binding.root.context,
binding.root.context.getString(R.string.delete_post_failed_io_except),
Toast.LENGTH_SHORT
).show()
}
}
}
companion object { companion object {
@ -672,7 +783,7 @@ class AlbumViewPagerAdapter(
val imageUrl = if(video) preview_url else url val imageUrl = if(video) preview_url else url
if(opened){ if(opened){
Glide.with(holder.binding.root) Glide.with(holder.binding.root)
.download(GlideUrl(imageUrl)) .download( GlideUrl(imageUrl))
.into(object : CustomViewTarget<SubsamplingScaleImageView, File>((holder.image as SubsamplingScaleImageView)) { .into(object : CustomViewTarget<SubsamplingScaleImageView, File>((holder.image as SubsamplingScaleImageView)) {
override fun onResourceReady(resource: File, t: Transition<in File>?) = override fun onResourceReady(resource: File, t: Transition<in File>?) =
view.setImage(ImageSource.uri(Uri.fromFile(resource))) view.setImage(ImageSource.uri(Uri.fromFile(resource)))

View File

@ -32,6 +32,10 @@
android:id="@+id/post_more_menu_group_delete" android:id="@+id/post_more_menu_group_delete"
android:visible="false"> android:visible="false">
<item android:id="@+id/post_more_menu_redraft"
android:title="@string/redraft" />
<item android:id="@+id/post_more_menu_delete" <item android:id="@+id/post_more_menu_delete"
android:title="@string/delete"/> android:title="@string/delete"/>

View File

@ -246,12 +246,16 @@ For more info about Pixelfed, you can check here: https://pixelfed.org"</string>
<string name="discover_no_infinite_load">Discover doesn\'t load infinitely. Pull to refresh for other images.</string> <string name="discover_no_infinite_load">Discover doesn\'t load infinitely. Pull to refresh for other images.</string>
<string name="something_went_wrong">Something went wrong…</string> <string name="something_went_wrong">Something went wrong…</string>
<string name="panda_pull_to_refresh_to_try_again">This panda is not happy. Pull to refresh to try again.</string> <string name="panda_pull_to_refresh_to_try_again">This panda is not happy. Pull to refresh to try again.</string>
<string name="redraft">Redraft</string>
<string name="redraft_dialog">Redrafting this post will allow you to edit the photo and its description, but it will delete all current comments and likes. Continue?</string>
<string name="delete">Delete</string> <string name="delete">Delete</string>
<string name="delete_dialog">Delete this post?</string> <string name="delete_dialog">Delete this post?</string>
<string name="language">Language</string> <string name="language">Language</string>
<string name="help_translate">Help translate PixelDroid to your language:</string> <string name="help_translate">Help translate PixelDroid to your language:</string>
<string name="issues_contribute">Report issues or contribute to the application:</string> <string name="issues_contribute">Report issues or contribute to the application:</string>
<string name="mascot_description">Image showing a red panda, Pixelfed\'s mascot, using a phone</string> <string name="mascot_description">Image showing a red panda, Pixelfed\'s mascot, using a phone</string>
<string name="redraft_post_failed_error">Could not redraft the post, error %1$d</string>
<string name="redraft_post_failed_io_except">Could not redraft the post, check your connection?</string>
<string name="delete_post_failed_error">Could not delete the post, error %1$d</string> <string name="delete_post_failed_error">Could not delete the post, error %1$d</string>
<string name="delete_post_failed_io_except">Could not delete the post, check your connection?</string> <string name="delete_post_failed_io_except">Could not delete the post, check your connection?</string>
<string name="bookmark_post_failed_error">Could not (un)bookmark the post, error %1$d</string> <string name="bookmark_post_failed_error">Could not (un)bookmark the post, error %1$d</string>

View File

@ -3463,6 +3463,9 @@
<artifact name="aapt2-7.3.0-rc01-8691043-linux.jar"> <artifact name="aapt2-7.3.0-rc01-8691043-linux.jar">
<sha256 value="28b7445d32544169c8c31fba72fb63e067c3608e1c61ea78f2fa89cde38794fa" origin="Generated by Gradle"/> <sha256 value="28b7445d32544169c8c31fba72fb63e067c3608e1c61ea78f2fa89cde38794fa" origin="Generated by Gradle"/>
</artifact> </artifact>
<artifact name="aapt2-7.3.0-rc01-8691043-osx.jar">
<sha256 value="c1aa071d7284bb771c93037494547f772aefae18da4ad7af1523a5b7ab4d915c" origin="Generated by Gradle"/>
</artifact>
<artifact name="aapt2-7.3.0-rc01-8691043.pom"> <artifact name="aapt2-7.3.0-rc01-8691043.pom">
<sha256 value="360cf64368acc9f9b1c0d0c9a82488a045e2780a5f7bfc16a57bfcc0d40cf200" origin="Generated by Gradle"/> <sha256 value="360cf64368acc9f9b1c0d0c9a82488a045e2780a5f7bfc16a57bfcc0d40cf200" origin="Generated by Gradle"/>
</artifact> </artifact>