diff --git a/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationActivity.kt b/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationActivity.kt index cfb792d8..9861ed35 100644 --- a/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationActivity.kt +++ b/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationActivity.kt @@ -87,7 +87,13 @@ class PostCreationActivity : BaseThemedWithoutBarActivity() { } } ?: InstanceDatabaseEntity("", "") - val _model: PostCreationViewModel by viewModels { PostCreationViewModelFactory(application, intent.clipData!!, instance) } + val _model: PostCreationViewModel by viewModels { + PostCreationViewModelFactory( + application, + intent.clipData!!, + instance + ) + } model = _model model.getPhotoData().observe(this) { newPhotoData -> @@ -116,15 +122,20 @@ class PostCreationActivity : BaseThemedWithoutBarActivity() { } binding.addPhotoButton.isEnabled = uiState.addPhotoButtonEnabled 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.uploadCompletedTextview.visibility = if(uiState.uploadCompletedTextviewVisible) VISIBLE else INVISIBLE + binding.uploadCompletedTextview.visibility = + if (uiState.uploadCompletedTextviewVisible) VISIBLE else INVISIBLE binding.removePhotoButton.isEnabled = uiState.removePhotoButtonEnabled binding.editPhotoButton.isEnabled = uiState.editPhotoButtonEnabled - binding.uploadError.visibility = if(uiState.uploadErrorVisible) VISIBLE else INVISIBLE - binding.uploadErrorTextExplanation.visibility = if(uiState.uploadErrorExplanationVisible) VISIBLE else INVISIBLE + binding.uploadError.visibility = + 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 @@ -155,6 +166,11 @@ class PostCreationActivity : BaseThemedWithoutBarActivity() { binding.newPostDescriptionInputField.doAfterTextChanged { 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.carousel.apply { diff --git a/app/src/main/java/org/pixeldroid/app/postCreation/photoEdit/PhotoEditActivity.kt b/app/src/main/java/org/pixeldroid/app/postCreation/photoEdit/PhotoEditActivity.kt index 66dbebaf..e642f490 100644 --- a/app/src/main/java/org/pixeldroid/app/postCreation/photoEdit/PhotoEditActivity.kt +++ b/app/src/main/java/org/pixeldroid/app/postCreation/photoEdit/PhotoEditActivity.kt @@ -80,6 +80,7 @@ class PhotoEditActivity : BaseThemedWithBarActivity() { companion object{ internal const val PICTURE_URI = "picture_uri" internal const val PICTURE_POSITION = "picture_position" + internal const val PICTURE_DESCRIPTION = "picture_description" private var executor: ExecutorService = newSingleThreadExecutor() private var future: Future<*>? = null diff --git a/app/src/main/java/org/pixeldroid/app/posts/StatusViewHolder.kt b/app/src/main/java/org/pixeldroid/app/posts/StatusViewHolder.kt index 2cd900a8..96cb4e59 100644 --- a/app/src/main/java/org/pixeldroid/app/posts/StatusViewHolder.kt +++ b/app/src/main/java/org/pixeldroid/app/posts/StatusViewHolder.kt @@ -3,7 +3,9 @@ package org.pixeldroid.app.posts import android.Manifest import android.app.Activity import android.app.AlertDialog +import android.content.ClipData import android.content.Intent +import android.graphics.Bitmap import android.graphics.Typeface import android.graphics.drawable.AnimatedVectorDrawable import android.graphics.drawable.Drawable @@ -17,6 +19,7 @@ import android.view.ViewGroup import android.widget.* import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat +import androidx.core.net.toUri import androidx.core.view.WindowCompat import androidx.core.view.WindowInsetsCompat 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.single.BasePermissionListener 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.databinding.AlbumImageViewBinding import org.pixeldroid.app.databinding.OpenedAlbumBinding 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.utils.BlurHashDecoder 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_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.di.PixelfedAPIHolder import org.pixeldroid.app.utils.setProfileImageFromURL import retrofit2.HttpException import java.io.File import java.io.IOException +import java.io.OutputStream import kotlin.math.roundToInt @@ -322,7 +334,7 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold //Call the api function status?.id?.let { id -> try { - if(bookmarked) { + if (bookmarked) { api.bookmarkStatus(id) } else { api.undoBookmarkStatus(id) @@ -337,7 +349,10 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold } catch (exception: HttpException) { Toast.makeText( 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 ).show() } catch (exception: IOException) { @@ -351,6 +366,14 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold 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){ var bookmarked: Boolean? = null binding.statusMore.setOnClickListener { @@ -439,30 +462,95 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold val builder = AlertDialog.Builder(binding.root.context) builder.apply { 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) { _, _ -> lifecycleScope.launch { 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 { - val api = apiHolder.api ?: apiHolder.setToCurrentUser() - api.deleteStatus(id) - binding.root.visibility = View.GONE + // Prepare image download + val imageUri = Uri.parse(status?.media_attachments?.getOrNull(binding.postPager.currentItem)?.url?: "") + 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) { Toast.makeText( - binding.root.context, - binding.root.context.getString(R.string.delete_post_failed_error, exception.code()), - Toast.LENGTH_SHORT + binding.root.context, + binding.root.context.getString(R.string.redraft_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 + binding.root.context, + binding.root.context.getString(R.string.redraft_post_failed_io_except), + Toast.LENGTH_SHORT ).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) } } + private fun ImageView.animateView() { visibility = View.VISIBLE 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 { @@ -672,7 +783,7 @@ class AlbumViewPagerAdapter( val imageUrl = if(video) preview_url else url if(opened){ Glide.with(holder.binding.root) - .download(GlideUrl(imageUrl)) + .download( GlideUrl(imageUrl)) .into(object : CustomViewTarget((holder.image as SubsamplingScaleImageView)) { override fun onResourceReady(resource: File, t: Transition?) = view.setImage(ImageSource.uri(Uri.fromFile(resource))) diff --git a/app/src/main/res/menu/post_more_menu.xml b/app/src/main/res/menu/post_more_menu.xml index b46bf28c..effe6c4c 100644 --- a/app/src/main/res/menu/post_more_menu.xml +++ b/app/src/main/res/menu/post_more_menu.xml @@ -32,6 +32,10 @@ android:id="@+id/post_more_menu_group_delete" android:visible="false"> + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c6c03d17..3c91f3e7 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -246,12 +246,16 @@ For more info about Pixelfed, you can check here: https://pixelfed.org" Discover doesn\'t load infinitely. Pull to refresh for other images. Something went wrong… This panda is not happy. Pull to refresh to try again. + Redraft + Redrafting this post will allow you to edit the photo and its description, but it will delete all current comments and likes. Continue? Delete Delete this post? Language Help translate PixelDroid to your language: Report issues or contribute to the application: Image showing a red panda, Pixelfed\'s mascot, using a phone + Could not redraft the post, error %1$d + Could not redraft the post, check your connection? Could not delete the post, error %1$d Could not delete the post, check your connection? Could not (un)bookmark the post, error %1$d diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 8b3703e8..61ceb175 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -3463,6 +3463,9 @@ + + +