From e40a774b1f148f7813c503bd3e4ad5339cee624c Mon Sep 17 00:00:00 2001 From: fgerber Date: Wed, 2 Nov 2022 17:38:55 +0100 Subject: [PATCH 01/21] Pass existing description through to post submission activity --- .../pixeldroid/app/postCreation/PostCreationActivity.kt | 2 ++ .../pixeldroid/app/postCreation/PostCreationViewModel.kt | 9 ++++++++- .../app/postCreation/PostSubmissionActivity.kt | 2 ++ .../app/postCreation/PostSubmissionViewModel.kt | 7 ++++++- 4 files changed, 18 insertions(+), 2 deletions(-) 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 72f7bbdc..9a4f50bc 100644 --- a/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationActivity.kt +++ b/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationActivity.kt @@ -93,6 +93,8 @@ class PostCreationActivity : BaseThemedWithoutBarActivity() { ) } + model.setExistingDescription(intent.getStringExtra(PICTURE_DESCRIPTION)) + lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { model.uiState.collect { uiState -> diff --git a/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationViewModel.kt b/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationViewModel.kt index 47d54d9c..1fbcbeb3 100644 --- a/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationViewModel.kt +++ b/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationViewModel.kt @@ -23,6 +23,7 @@ import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.parcelize.Parcelize import org.pixeldroid.app.R +import org.pixeldroid.app.posts.fromHtml import org.pixeldroid.app.utils.PixelDroidApplication import org.pixeldroid.app.utils.db.entities.InstanceDatabaseEntity import org.pixeldroid.app.utils.di.PixelfedAPIHolder @@ -80,6 +81,7 @@ class PostCreationViewModel(application: Application, clipdata: ClipData? = null it.value = clipdata?.let { it1 -> addPossibleImages(it1, mutableListOf()) } } } + private var existingDescription: String? = null @Inject lateinit var apiHolder: PixelfedAPIHolder @@ -92,7 +94,7 @@ class PostCreationViewModel(application: Application, clipdata: ClipData? = null PreferenceManager.getDefaultSharedPreferences(application) val initialDescription = sharedPreferences.getString("prefill_description", "") ?: "" - _uiState = MutableStateFlow(PostCreationActivityUiState(newPostDescriptionText = initialDescription)) + _uiState = MutableStateFlow(PostCreationActivityUiState(newPostDescriptionText = existingDescription ?: initialDescription)) } val uiState: StateFlow = _uiState @@ -193,6 +195,10 @@ class PostCreationViewModel(application: Application, clipdata: ClipData? = null photoData.value = photoData.value } + fun setExistingDescription(description: String?) { + existingDescription = description + } + fun removeAt(currentPosition: Int) { photoData.value?.removeAt(currentPosition) _uiState.update { @@ -209,6 +215,7 @@ class PostCreationViewModel(application: Application, clipdata: ClipData? = null fun nextStep(context: Context) { val intent = Intent(context, PostSubmissionActivity::class.java) intent.putExtra(PostSubmissionActivity.PHOTO_DATA, getPhotoData().value?.let { ArrayList(it) }) + intent.putExtra(PostSubmissionActivity.PICTURE_DESCRIPTION, existingDescription) ContextCompat.startActivity(context, intent, null) } diff --git a/app/src/main/java/org/pixeldroid/app/postCreation/PostSubmissionActivity.kt b/app/src/main/java/org/pixeldroid/app/postCreation/PostSubmissionActivity.kt index 6ead56d7..adf0cafc 100644 --- a/app/src/main/java/org/pixeldroid/app/postCreation/PostSubmissionActivity.kt +++ b/app/src/main/java/org/pixeldroid/app/postCreation/PostSubmissionActivity.kt @@ -69,6 +69,8 @@ class PostSubmissionActivity : BaseThemedWithoutBarActivity() { } model = _model + model.setExistingDescription(intent.getStringExtra(PICTURE_DESCRIPTION)) + lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { model.uiState.collect { uiState -> diff --git a/app/src/main/java/org/pixeldroid/app/postCreation/PostSubmissionViewModel.kt b/app/src/main/java/org/pixeldroid/app/postCreation/PostSubmissionViewModel.kt index 816046f1..02e8c677 100644 --- a/app/src/main/java/org/pixeldroid/app/postCreation/PostSubmissionViewModel.kt +++ b/app/src/main/java/org/pixeldroid/app/postCreation/PostSubmissionViewModel.kt @@ -62,6 +62,7 @@ class PostSubmissionViewModel(application: Application, photodata: ArrayList = _uiState @@ -288,6 +289,10 @@ class PostSubmissionViewModel(application: Application, photodata: ArrayList Date: Wed, 2 Nov 2022 21:52:25 +0100 Subject: [PATCH 02/21] Move post deletion to ensure download is complete before --- .../pixeldroid/app/posts/StatusViewHolder.kt | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) 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 8fbc370a..4a098d58 100644 --- a/app/src/main/java/org/pixeldroid/app/posts/StatusViewHolder.kt +++ b/app/src/main/java/org/pixeldroid/app/posts/StatusViewHolder.kt @@ -10,6 +10,7 @@ import android.graphics.drawable.AnimatedVectorDrawable import android.graphics.drawable.Drawable import android.location.GnssAntennaInfo.Listener import android.net.Uri +import android.os.Handler import android.os.Looper import android.text.method.LinkMovementMethod import android.util.Log @@ -18,6 +19,7 @@ import android.view.Menu import android.view.View import android.view.ViewGroup import android.widget.* +import androidx.annotation.UiThread import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat import androidx.core.view.WindowCompat @@ -40,6 +42,7 @@ import com.karumi.dexter.listener.PermissionDeniedResponse import com.karumi.dexter.listener.PermissionGrantedResponse import com.karumi.dexter.listener.single.BasePermissionListener import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import okhttp3.* import okio.BufferedSink import okio.buffer @@ -506,6 +509,13 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold // Wait for all outstanding downloads to finish if (counter.incrementAndGet() == imageUris.size) { if (allFilesExist(imageNames)) { + // Delete original post + Handler(Looper.getMainLooper()).post { + runBlocking { + deletePost(apiHolder.api ?: apiHolder.setToCurrentUser(), db) + } + } + val counterInt = counter.get() Toast.makeText( binding.root.context, @@ -574,6 +584,7 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold Toast.LENGTH_SHORT ).show() } + val okHttpClient = OkHttpClient() // Iterate through all pictures of the original post for (currentAttachment in postAttachments) { @@ -587,7 +598,7 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold // Check whether image is in cache directory already (maybe rather do so using Glide in the future?) if (!downloadedFile.exists()) { - OkHttpClient().newCall(downloadRequest) + okHttpClient.newCall(downloadRequest) .enqueue(object : Callback { override fun onFailure( call: Call, @@ -615,6 +626,7 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold continuation() } }) + } else { continuation() } @@ -636,13 +648,6 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold Toast.LENGTH_SHORT ).show() } - - // Delete original post - deletePost( - apiHolder.api ?: apiHolder.setToCurrentUser(), - db - ) - } } setNegativeButton(android.R.string.cancel) { _, _ -> } From e5ff66a83eae436217e7c8bd31dd645ad9aae61d Mon Sep 17 00:00:00 2001 From: fgerber Date: Thu, 3 Nov 2022 13:22:18 +0100 Subject: [PATCH 03/21] Account for NSFW status in redraft --- .../app/postCreation/PostCreationActivity.kt | 2 ++ .../app/postCreation/PostCreationViewModel.kt | 6 ++++++ .../app/postCreation/PostSubmissionActivity.kt | 8 ++++++-- .../app/postCreation/PostSubmissionViewModel.kt | 2 +- .../java/org/pixeldroid/app/posts/StatusViewHolder.kt | 11 ++++++----- 5 files changed, 21 insertions(+), 8 deletions(-) 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 9a4f50bc..2c932f99 100644 --- a/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationActivity.kt +++ b/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationActivity.kt @@ -48,6 +48,7 @@ class PostCreationActivity : BaseThemedWithoutBarActivity() { internal const val PICTURE_DESCRIPTION = "picture_description" internal const val TEMP_FILES = "temp_files" internal const val POST_REDRAFT = "post_redraft" + internal const val POST_NSFW = "post_nsfw" } private var user: UserDatabaseEntity? = null @@ -94,6 +95,7 @@ class PostCreationActivity : BaseThemedWithoutBarActivity() { } model.setExistingDescription(intent.getStringExtra(PICTURE_DESCRIPTION)) + model.setExistingNSFW(intent.getBooleanExtra(POST_NSFW, false)) lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { diff --git a/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationViewModel.kt b/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationViewModel.kt index 1fbcbeb3..86cc42a6 100644 --- a/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationViewModel.kt +++ b/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationViewModel.kt @@ -82,6 +82,7 @@ class PostCreationViewModel(application: Application, clipdata: ClipData? = null } } private var existingDescription: String? = null + private var existingNSFW: Boolean = false @Inject lateinit var apiHolder: PixelfedAPIHolder @@ -199,6 +200,10 @@ class PostCreationViewModel(application: Application, clipdata: ClipData? = null existingDescription = description } + fun setExistingNSFW(sensitive: Boolean) { + existingNSFW = sensitive + } + fun removeAt(currentPosition: Int) { photoData.value?.removeAt(currentPosition) _uiState.update { @@ -216,6 +221,7 @@ class PostCreationViewModel(application: Application, clipdata: ClipData? = null val intent = Intent(context, PostSubmissionActivity::class.java) intent.putExtra(PostSubmissionActivity.PHOTO_DATA, getPhotoData().value?.let { ArrayList(it) }) intent.putExtra(PostSubmissionActivity.PICTURE_DESCRIPTION, existingDescription) + intent.putExtra(PostSubmissionActivity.POST_NSFW, existingNSFW) ContextCompat.startActivity(context, intent, null) } diff --git a/app/src/main/java/org/pixeldroid/app/postCreation/PostSubmissionActivity.kt b/app/src/main/java/org/pixeldroid/app/postCreation/PostSubmissionActivity.kt index adf0cafc..878e5541 100644 --- a/app/src/main/java/org/pixeldroid/app/postCreation/PostSubmissionActivity.kt +++ b/app/src/main/java/org/pixeldroid/app/postCreation/PostSubmissionActivity.kt @@ -29,7 +29,8 @@ class PostSubmissionActivity : BaseThemedWithoutBarActivity() { companion object { internal const val PICTURE_DESCRIPTION = "picture_description" internal const val PHOTO_DATA = "photo_data" - } + internal const val POST_NSFW = "post_nsfw" +} private lateinit var accounts: List private var selectedAccount: Int = -1 @@ -71,6 +72,10 @@ class PostSubmissionActivity : BaseThemedWithoutBarActivity() { model.setExistingDescription(intent.getStringExtra(PICTURE_DESCRIPTION)) + val sensitive = intent.getBooleanExtra(POST_NSFW, false) + model.updateNSFW(sensitive) + binding.nsfwSwitch.isChecked = sensitive + lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { model.uiState.collect { uiState -> @@ -115,7 +120,6 @@ class PostSubmissionActivity : BaseThemedWithoutBarActivity() { existingDescription ?: model.uiState.value.newPostDescriptionText ) - binding.postTextInputLayout.counterMaxLength = instance.maxStatusChars setSquareImageFromURL(View(applicationContext), photoData!![0].imageUri.toString(), binding.postPreview) diff --git a/app/src/main/java/org/pixeldroid/app/postCreation/PostSubmissionViewModel.kt b/app/src/main/java/org/pixeldroid/app/postCreation/PostSubmissionViewModel.kt index 02e8c677..42b4e089 100644 --- a/app/src/main/java/org/pixeldroid/app/postCreation/PostSubmissionViewModel.kt +++ b/app/src/main/java/org/pixeldroid/app/postCreation/PostSubmissionViewModel.kt @@ -236,7 +236,7 @@ class PostSubmissionViewModel(application: Application, photodata: ArrayList currentUiState.copy( 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 4a098d58..ff8e7054 100644 --- a/app/src/main/java/org/pixeldroid/app/posts/StatusViewHolder.kt +++ b/app/src/main/java/org/pixeldroid/app/posts/StatusViewHolder.kt @@ -8,7 +8,6 @@ import android.content.Intent import android.graphics.Typeface import android.graphics.drawable.AnimatedVectorDrawable import android.graphics.drawable.Drawable -import android.location.GnssAntennaInfo.Listener import android.net.Uri import android.os.Handler import android.os.Looper @@ -19,7 +18,6 @@ import android.view.Menu import android.view.View import android.view.ViewGroup import android.widget.* -import androidx.annotation.UiThread import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat import androidx.core.view.WindowCompat @@ -486,6 +484,7 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold val imageNames: MutableList = mutableListOf() val imageDescriptions: MutableList = mutableListOf() + val postNSFW = status?.sensitive for (currentAttachment in postAttachments) { val imageUri = currentAttachment.url ?: "" @@ -569,6 +568,10 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold PostCreationActivity.POST_REDRAFT, true ) + intent.putExtra( + PostCreationActivity.POST_NSFW, + postNSFW + ) // Launch post creation activity binding.root.context.startActivity(intent) @@ -584,7 +587,6 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold Toast.LENGTH_SHORT ).show() } - val okHttpClient = OkHttpClient() // Iterate through all pictures of the original post for (currentAttachment in postAttachments) { @@ -598,7 +600,7 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold // Check whether image is in cache directory already (maybe rather do so using Glide in the future?) if (!downloadedFile.exists()) { - okHttpClient.newCall(downloadRequest) + OkHttpClient().newCall(downloadRequest) .enqueue(object : Callback { override fun onFailure( call: Call, @@ -626,7 +628,6 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold continuation() } }) - } else { continuation() } From c726bbb44842d5d5c03f73dff878a52be27b613e Mon Sep 17 00:00:00 2001 From: fgerber Date: Thu, 3 Nov 2022 13:52:41 +0100 Subject: [PATCH 04/21] Remove useless import --- .../org/pixeldroid/app/postCreation/PostCreationViewModel.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationViewModel.kt b/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationViewModel.kt index 86cc42a6..f5f552c0 100644 --- a/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationViewModel.kt +++ b/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationViewModel.kt @@ -23,7 +23,6 @@ import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.parcelize.Parcelize import org.pixeldroid.app.R -import org.pixeldroid.app.posts.fromHtml import org.pixeldroid.app.utils.PixelDroidApplication import org.pixeldroid.app.utils.db.entities.InstanceDatabaseEntity import org.pixeldroid.app.utils.di.PixelfedAPIHolder From 1c8a7d8b7de9971f1d33d6d6c26c32d5ea7e69d4 Mon Sep 17 00:00:00 2001 From: fgerber Date: Thu, 3 Nov 2022 23:12:54 +0100 Subject: [PATCH 05/21] Refactor redraft as functional code --- .../pixeldroid/app/posts/StatusViewHolder.kt | 62 +++++-------------- 1 file changed, 16 insertions(+), 46 deletions(-) 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 ff8e7054..75a08e0d 100644 --- a/app/src/main/java/org/pixeldroid/app/posts/StatusViewHolder.kt +++ b/app/src/main/java/org/pixeldroid/app/posts/StatusViewHolder.kt @@ -9,7 +9,6 @@ import android.graphics.Typeface import android.graphics.drawable.AnimatedVectorDrawable import android.graphics.drawable.Drawable import android.net.Uri -import android.os.Handler import android.os.Looper import android.text.method.LinkMovementMethod import android.util.Log @@ -40,7 +39,6 @@ import com.karumi.dexter.listener.PermissionDeniedResponse import com.karumi.dexter.listener.PermissionGrantedResponse import com.karumi.dexter.listener.single.BasePermissionListener import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking import okhttp3.* import okio.BufferedSink import okio.buffer @@ -480,25 +478,16 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold val postDescription = status?.content ?: "" val postAttachments = status?.media_attachments!! // Catch possible exception from !! (?) - val imageUris: MutableList = mutableListOf() - val imageNames: MutableList = mutableListOf() - val imageDescriptions: MutableList = - mutableListOf() val postNSFW = status?.sensitive - for (currentAttachment in postAttachments) { - val imageUri = currentAttachment.url ?: "" - val imageName = - Uri.parse(imageUri).lastPathSegment.toString() - val imageDescription = - currentAttachment.description ?: "" - val downloadedFile = - File(context.cacheDir, imageName) - val downloadedUri = Uri.fromFile(downloadedFile) - - imageUris.add(downloadedUri) - imageNames.add(imageName) - imageDescriptions.add(imageDescription) + val imageNames = postAttachments.map { postAttachment -> + Uri.parse(postAttachment.url ?: "").lastPathSegment.toString() + } + val imageUris = imageNames.map { imageName -> + Uri.fromFile(File(context.cacheDir, imageName)) + } + val imageDescriptions = postAttachments.map { postAttachment -> + fromHtml(postAttachment.description ?: "").toString() } val counter = AtomicInteger(0) @@ -509,10 +498,8 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold if (counter.incrementAndGet() == imageUris.size) { if (allFilesExist(imageNames)) { // Delete original post - Handler(Looper.getMainLooper()).post { - runBlocking { - deletePost(apiHolder.api ?: apiHolder.setToCurrentUser(), db) - } + lifecycleScope.launch { + deletePost(apiHolder.api ?: apiHolder.setToCurrentUser(), db) } val counterInt = counter.get() @@ -527,23 +514,10 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold ).show() // Pass downloaded images to new post creation activity intent.apply { - assert(imageUris.size == imageDescriptions.size) - - for (i in 0 until imageUris.size) { - val imageUri = imageUris[i] - val imageDescription = - fromHtml(imageDescriptions[i]).toString() - val imageItem = ClipData.Item( - imageDescription, - null, - imageUri - ) + imageUris.zip(imageDescriptions).map { (imageUri, imageDescription) -> + val imageItem = ClipData.Item(imageDescription, null, imageUri) if (clipData == null) { - clipData = ClipData( - "", - emptyArray(), - imageItem - ) + clipData = ClipData("", emptyArray(), imageItem) } else { clipData!!.addItem(imageItem) } @@ -837,14 +811,10 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold } } - private fun allFilesExist(listOfNames: MutableList): Boolean { - for (name in listOfNames) { - val file = File(binding.root.context.cacheDir, name) - if (!file.exists()) { - return false - } + private fun allFilesExist(listOfNames: List): Boolean { + return listOfNames.all { + File(binding.root.context.cacheDir, it).exists() } - return true } companion object { From 20d38d3fa83bd04730b7eed14e500216b13af066 Mon Sep 17 00:00:00 2001 From: fgerber Date: Wed, 2 Nov 2022 17:38:55 +0100 Subject: [PATCH 06/21] Pass existing description through to post submission activity --- .../pixeldroid/app/postCreation/PostCreationActivity.kt | 2 ++ .../pixeldroid/app/postCreation/PostCreationViewModel.kt | 9 ++++++++- .../app/postCreation/PostSubmissionActivity.kt | 2 ++ .../app/postCreation/PostSubmissionViewModel.kt | 7 ++++++- 4 files changed, 18 insertions(+), 2 deletions(-) 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 4deca72f..ec2c81b6 100644 --- a/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationActivity.kt +++ b/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationActivity.kt @@ -93,6 +93,8 @@ class PostCreationActivity : BaseThemedWithoutBarActivity() { ) } + model.setExistingDescription(intent.getStringExtra(PICTURE_DESCRIPTION)) + lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { model.uiState.collect { uiState -> diff --git a/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationViewModel.kt b/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationViewModel.kt index 47d54d9c..1fbcbeb3 100644 --- a/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationViewModel.kt +++ b/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationViewModel.kt @@ -23,6 +23,7 @@ import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.parcelize.Parcelize import org.pixeldroid.app.R +import org.pixeldroid.app.posts.fromHtml import org.pixeldroid.app.utils.PixelDroidApplication import org.pixeldroid.app.utils.db.entities.InstanceDatabaseEntity import org.pixeldroid.app.utils.di.PixelfedAPIHolder @@ -80,6 +81,7 @@ class PostCreationViewModel(application: Application, clipdata: ClipData? = null it.value = clipdata?.let { it1 -> addPossibleImages(it1, mutableListOf()) } } } + private var existingDescription: String? = null @Inject lateinit var apiHolder: PixelfedAPIHolder @@ -92,7 +94,7 @@ class PostCreationViewModel(application: Application, clipdata: ClipData? = null PreferenceManager.getDefaultSharedPreferences(application) val initialDescription = sharedPreferences.getString("prefill_description", "") ?: "" - _uiState = MutableStateFlow(PostCreationActivityUiState(newPostDescriptionText = initialDescription)) + _uiState = MutableStateFlow(PostCreationActivityUiState(newPostDescriptionText = existingDescription ?: initialDescription)) } val uiState: StateFlow = _uiState @@ -193,6 +195,10 @@ class PostCreationViewModel(application: Application, clipdata: ClipData? = null photoData.value = photoData.value } + fun setExistingDescription(description: String?) { + existingDescription = description + } + fun removeAt(currentPosition: Int) { photoData.value?.removeAt(currentPosition) _uiState.update { @@ -209,6 +215,7 @@ class PostCreationViewModel(application: Application, clipdata: ClipData? = null fun nextStep(context: Context) { val intent = Intent(context, PostSubmissionActivity::class.java) intent.putExtra(PostSubmissionActivity.PHOTO_DATA, getPhotoData().value?.let { ArrayList(it) }) + intent.putExtra(PostSubmissionActivity.PICTURE_DESCRIPTION, existingDescription) ContextCompat.startActivity(context, intent, null) } diff --git a/app/src/main/java/org/pixeldroid/app/postCreation/PostSubmissionActivity.kt b/app/src/main/java/org/pixeldroid/app/postCreation/PostSubmissionActivity.kt index 6ead56d7..adf0cafc 100644 --- a/app/src/main/java/org/pixeldroid/app/postCreation/PostSubmissionActivity.kt +++ b/app/src/main/java/org/pixeldroid/app/postCreation/PostSubmissionActivity.kt @@ -69,6 +69,8 @@ class PostSubmissionActivity : BaseThemedWithoutBarActivity() { } model = _model + model.setExistingDescription(intent.getStringExtra(PICTURE_DESCRIPTION)) + lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { model.uiState.collect { uiState -> diff --git a/app/src/main/java/org/pixeldroid/app/postCreation/PostSubmissionViewModel.kt b/app/src/main/java/org/pixeldroid/app/postCreation/PostSubmissionViewModel.kt index 816046f1..02e8c677 100644 --- a/app/src/main/java/org/pixeldroid/app/postCreation/PostSubmissionViewModel.kt +++ b/app/src/main/java/org/pixeldroid/app/postCreation/PostSubmissionViewModel.kt @@ -62,6 +62,7 @@ class PostSubmissionViewModel(application: Application, photodata: ArrayList = _uiState @@ -288,6 +289,10 @@ class PostSubmissionViewModel(application: Application, photodata: ArrayList Date: Wed, 2 Nov 2022 21:52:25 +0100 Subject: [PATCH 07/21] Move post deletion to ensure download is complete before --- .../pixeldroid/app/posts/StatusViewHolder.kt | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) 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 8fbc370a..4a098d58 100644 --- a/app/src/main/java/org/pixeldroid/app/posts/StatusViewHolder.kt +++ b/app/src/main/java/org/pixeldroid/app/posts/StatusViewHolder.kt @@ -10,6 +10,7 @@ import android.graphics.drawable.AnimatedVectorDrawable import android.graphics.drawable.Drawable import android.location.GnssAntennaInfo.Listener import android.net.Uri +import android.os.Handler import android.os.Looper import android.text.method.LinkMovementMethod import android.util.Log @@ -18,6 +19,7 @@ import android.view.Menu import android.view.View import android.view.ViewGroup import android.widget.* +import androidx.annotation.UiThread import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat import androidx.core.view.WindowCompat @@ -40,6 +42,7 @@ import com.karumi.dexter.listener.PermissionDeniedResponse import com.karumi.dexter.listener.PermissionGrantedResponse import com.karumi.dexter.listener.single.BasePermissionListener import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import okhttp3.* import okio.BufferedSink import okio.buffer @@ -506,6 +509,13 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold // Wait for all outstanding downloads to finish if (counter.incrementAndGet() == imageUris.size) { if (allFilesExist(imageNames)) { + // Delete original post + Handler(Looper.getMainLooper()).post { + runBlocking { + deletePost(apiHolder.api ?: apiHolder.setToCurrentUser(), db) + } + } + val counterInt = counter.get() Toast.makeText( binding.root.context, @@ -574,6 +584,7 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold Toast.LENGTH_SHORT ).show() } + val okHttpClient = OkHttpClient() // Iterate through all pictures of the original post for (currentAttachment in postAttachments) { @@ -587,7 +598,7 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold // Check whether image is in cache directory already (maybe rather do so using Glide in the future?) if (!downloadedFile.exists()) { - OkHttpClient().newCall(downloadRequest) + okHttpClient.newCall(downloadRequest) .enqueue(object : Callback { override fun onFailure( call: Call, @@ -615,6 +626,7 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold continuation() } }) + } else { continuation() } @@ -636,13 +648,6 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold Toast.LENGTH_SHORT ).show() } - - // Delete original post - deletePost( - apiHolder.api ?: apiHolder.setToCurrentUser(), - db - ) - } } setNegativeButton(android.R.string.cancel) { _, _ -> } From 2e558017f2f0f34d85b15c4e529a4257e3a3ec43 Mon Sep 17 00:00:00 2001 From: fgerber Date: Thu, 3 Nov 2022 13:22:18 +0100 Subject: [PATCH 08/21] Account for NSFW status in redraft --- .../app/postCreation/PostCreationActivity.kt | 2 ++ .../app/postCreation/PostCreationViewModel.kt | 6 ++++++ .../app/postCreation/PostSubmissionActivity.kt | 8 ++++++-- .../app/postCreation/PostSubmissionViewModel.kt | 2 +- .../java/org/pixeldroid/app/posts/StatusViewHolder.kt | 11 ++++++----- 5 files changed, 21 insertions(+), 8 deletions(-) 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 ec2c81b6..08a56312 100644 --- a/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationActivity.kt +++ b/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationActivity.kt @@ -48,6 +48,7 @@ class PostCreationActivity : BaseThemedWithoutBarActivity() { internal const val PICTURE_DESCRIPTION = "picture_description" internal const val TEMP_FILES = "temp_files" internal const val POST_REDRAFT = "post_redraft" + internal const val POST_NSFW = "post_nsfw" } private var user: UserDatabaseEntity? = null @@ -94,6 +95,7 @@ class PostCreationActivity : BaseThemedWithoutBarActivity() { } model.setExistingDescription(intent.getStringExtra(PICTURE_DESCRIPTION)) + model.setExistingNSFW(intent.getBooleanExtra(POST_NSFW, false)) lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { diff --git a/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationViewModel.kt b/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationViewModel.kt index 1fbcbeb3..86cc42a6 100644 --- a/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationViewModel.kt +++ b/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationViewModel.kt @@ -82,6 +82,7 @@ class PostCreationViewModel(application: Application, clipdata: ClipData? = null } } private var existingDescription: String? = null + private var existingNSFW: Boolean = false @Inject lateinit var apiHolder: PixelfedAPIHolder @@ -199,6 +200,10 @@ class PostCreationViewModel(application: Application, clipdata: ClipData? = null existingDescription = description } + fun setExistingNSFW(sensitive: Boolean) { + existingNSFW = sensitive + } + fun removeAt(currentPosition: Int) { photoData.value?.removeAt(currentPosition) _uiState.update { @@ -216,6 +221,7 @@ class PostCreationViewModel(application: Application, clipdata: ClipData? = null val intent = Intent(context, PostSubmissionActivity::class.java) intent.putExtra(PostSubmissionActivity.PHOTO_DATA, getPhotoData().value?.let { ArrayList(it) }) intent.putExtra(PostSubmissionActivity.PICTURE_DESCRIPTION, existingDescription) + intent.putExtra(PostSubmissionActivity.POST_NSFW, existingNSFW) ContextCompat.startActivity(context, intent, null) } diff --git a/app/src/main/java/org/pixeldroid/app/postCreation/PostSubmissionActivity.kt b/app/src/main/java/org/pixeldroid/app/postCreation/PostSubmissionActivity.kt index adf0cafc..878e5541 100644 --- a/app/src/main/java/org/pixeldroid/app/postCreation/PostSubmissionActivity.kt +++ b/app/src/main/java/org/pixeldroid/app/postCreation/PostSubmissionActivity.kt @@ -29,7 +29,8 @@ class PostSubmissionActivity : BaseThemedWithoutBarActivity() { companion object { internal const val PICTURE_DESCRIPTION = "picture_description" internal const val PHOTO_DATA = "photo_data" - } + internal const val POST_NSFW = "post_nsfw" +} private lateinit var accounts: List private var selectedAccount: Int = -1 @@ -71,6 +72,10 @@ class PostSubmissionActivity : BaseThemedWithoutBarActivity() { model.setExistingDescription(intent.getStringExtra(PICTURE_DESCRIPTION)) + val sensitive = intent.getBooleanExtra(POST_NSFW, false) + model.updateNSFW(sensitive) + binding.nsfwSwitch.isChecked = sensitive + lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { model.uiState.collect { uiState -> @@ -115,7 +120,6 @@ class PostSubmissionActivity : BaseThemedWithoutBarActivity() { existingDescription ?: model.uiState.value.newPostDescriptionText ) - binding.postTextInputLayout.counterMaxLength = instance.maxStatusChars setSquareImageFromURL(View(applicationContext), photoData!![0].imageUri.toString(), binding.postPreview) diff --git a/app/src/main/java/org/pixeldroid/app/postCreation/PostSubmissionViewModel.kt b/app/src/main/java/org/pixeldroid/app/postCreation/PostSubmissionViewModel.kt index 02e8c677..42b4e089 100644 --- a/app/src/main/java/org/pixeldroid/app/postCreation/PostSubmissionViewModel.kt +++ b/app/src/main/java/org/pixeldroid/app/postCreation/PostSubmissionViewModel.kt @@ -236,7 +236,7 @@ class PostSubmissionViewModel(application: Application, photodata: ArrayList currentUiState.copy( 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 4a098d58..ff8e7054 100644 --- a/app/src/main/java/org/pixeldroid/app/posts/StatusViewHolder.kt +++ b/app/src/main/java/org/pixeldroid/app/posts/StatusViewHolder.kt @@ -8,7 +8,6 @@ import android.content.Intent import android.graphics.Typeface import android.graphics.drawable.AnimatedVectorDrawable import android.graphics.drawable.Drawable -import android.location.GnssAntennaInfo.Listener import android.net.Uri import android.os.Handler import android.os.Looper @@ -19,7 +18,6 @@ import android.view.Menu import android.view.View import android.view.ViewGroup import android.widget.* -import androidx.annotation.UiThread import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat import androidx.core.view.WindowCompat @@ -486,6 +484,7 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold val imageNames: MutableList = mutableListOf() val imageDescriptions: MutableList = mutableListOf() + val postNSFW = status?.sensitive for (currentAttachment in postAttachments) { val imageUri = currentAttachment.url ?: "" @@ -569,6 +568,10 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold PostCreationActivity.POST_REDRAFT, true ) + intent.putExtra( + PostCreationActivity.POST_NSFW, + postNSFW + ) // Launch post creation activity binding.root.context.startActivity(intent) @@ -584,7 +587,6 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold Toast.LENGTH_SHORT ).show() } - val okHttpClient = OkHttpClient() // Iterate through all pictures of the original post for (currentAttachment in postAttachments) { @@ -598,7 +600,7 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold // Check whether image is in cache directory already (maybe rather do so using Glide in the future?) if (!downloadedFile.exists()) { - okHttpClient.newCall(downloadRequest) + OkHttpClient().newCall(downloadRequest) .enqueue(object : Callback { override fun onFailure( call: Call, @@ -626,7 +628,6 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold continuation() } }) - } else { continuation() } From 8325067566d3134aa947ddb26187269bc6d8f399 Mon Sep 17 00:00:00 2001 From: fgerber Date: Thu, 3 Nov 2022 13:52:41 +0100 Subject: [PATCH 09/21] Remove useless import --- .../org/pixeldroid/app/postCreation/PostCreationViewModel.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationViewModel.kt b/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationViewModel.kt index 86cc42a6..f5f552c0 100644 --- a/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationViewModel.kt +++ b/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationViewModel.kt @@ -23,7 +23,6 @@ import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.parcelize.Parcelize import org.pixeldroid.app.R -import org.pixeldroid.app.posts.fromHtml import org.pixeldroid.app.utils.PixelDroidApplication import org.pixeldroid.app.utils.db.entities.InstanceDatabaseEntity import org.pixeldroid.app.utils.di.PixelfedAPIHolder From 553c65f7bc581f12b669a3dd30015816ab1f6151 Mon Sep 17 00:00:00 2001 From: fgerber Date: Thu, 3 Nov 2022 23:12:54 +0100 Subject: [PATCH 10/21] Refactor redraft as functional code --- .../pixeldroid/app/posts/StatusViewHolder.kt | 62 +++++-------------- 1 file changed, 16 insertions(+), 46 deletions(-) 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 ff8e7054..75a08e0d 100644 --- a/app/src/main/java/org/pixeldroid/app/posts/StatusViewHolder.kt +++ b/app/src/main/java/org/pixeldroid/app/posts/StatusViewHolder.kt @@ -9,7 +9,6 @@ import android.graphics.Typeface import android.graphics.drawable.AnimatedVectorDrawable import android.graphics.drawable.Drawable import android.net.Uri -import android.os.Handler import android.os.Looper import android.text.method.LinkMovementMethod import android.util.Log @@ -40,7 +39,6 @@ import com.karumi.dexter.listener.PermissionDeniedResponse import com.karumi.dexter.listener.PermissionGrantedResponse import com.karumi.dexter.listener.single.BasePermissionListener import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking import okhttp3.* import okio.BufferedSink import okio.buffer @@ -480,25 +478,16 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold val postDescription = status?.content ?: "" val postAttachments = status?.media_attachments!! // Catch possible exception from !! (?) - val imageUris: MutableList = mutableListOf() - val imageNames: MutableList = mutableListOf() - val imageDescriptions: MutableList = - mutableListOf() val postNSFW = status?.sensitive - for (currentAttachment in postAttachments) { - val imageUri = currentAttachment.url ?: "" - val imageName = - Uri.parse(imageUri).lastPathSegment.toString() - val imageDescription = - currentAttachment.description ?: "" - val downloadedFile = - File(context.cacheDir, imageName) - val downloadedUri = Uri.fromFile(downloadedFile) - - imageUris.add(downloadedUri) - imageNames.add(imageName) - imageDescriptions.add(imageDescription) + val imageNames = postAttachments.map { postAttachment -> + Uri.parse(postAttachment.url ?: "").lastPathSegment.toString() + } + val imageUris = imageNames.map { imageName -> + Uri.fromFile(File(context.cacheDir, imageName)) + } + val imageDescriptions = postAttachments.map { postAttachment -> + fromHtml(postAttachment.description ?: "").toString() } val counter = AtomicInteger(0) @@ -509,10 +498,8 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold if (counter.incrementAndGet() == imageUris.size) { if (allFilesExist(imageNames)) { // Delete original post - Handler(Looper.getMainLooper()).post { - runBlocking { - deletePost(apiHolder.api ?: apiHolder.setToCurrentUser(), db) - } + lifecycleScope.launch { + deletePost(apiHolder.api ?: apiHolder.setToCurrentUser(), db) } val counterInt = counter.get() @@ -527,23 +514,10 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold ).show() // Pass downloaded images to new post creation activity intent.apply { - assert(imageUris.size == imageDescriptions.size) - - for (i in 0 until imageUris.size) { - val imageUri = imageUris[i] - val imageDescription = - fromHtml(imageDescriptions[i]).toString() - val imageItem = ClipData.Item( - imageDescription, - null, - imageUri - ) + imageUris.zip(imageDescriptions).map { (imageUri, imageDescription) -> + val imageItem = ClipData.Item(imageDescription, null, imageUri) if (clipData == null) { - clipData = ClipData( - "", - emptyArray(), - imageItem - ) + clipData = ClipData("", emptyArray(), imageItem) } else { clipData!!.addItem(imageItem) } @@ -837,14 +811,10 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold } } - private fun allFilesExist(listOfNames: MutableList): Boolean { - for (name in listOfNames) { - val file = File(binding.root.context.cacheDir, name) - if (!file.exists()) { - return false - } + private fun allFilesExist(listOfNames: List): Boolean { + return listOfNames.all { + File(binding.root.context.cacheDir, it).exists() } - return true } companion object { From c8794861537360eaee97c1044b2a80981fa221ce Mon Sep 17 00:00:00 2001 From: fgerber Date: Fri, 4 Nov 2022 10:16:25 +0100 Subject: [PATCH 11/21] Rebase and update dependencies --- gradle/verification-metadata.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index aeb39044..819d5bf6 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -1899,6 +1899,9 @@ + + + From ba26871572b068b8b95b4b21dd6d9d2683600091 Mon Sep 17 00:00:00 2001 From: fgerber Date: Fri, 4 Nov 2022 16:18:01 +0100 Subject: [PATCH 12/21] Make redraft code more functional, adapt gradle and remove deprecated onBackPressed() --- app/build.gradle | 2 +- .../app/postCreation/PostCreationActivity.kt | 38 +- .../app/postCreation/PostCreationViewModel.kt | 16 +- .../postCreation/PostSubmissionActivity.kt | 5 +- .../postCreation/PostSubmissionViewModel.kt | 11 +- .../pixeldroid/app/posts/StatusViewHolder.kt | 38 +- build.gradle | 2 +- gradle/verification-metadata.xml | 679 ++++++++++++++++++ 8 files changed, 730 insertions(+), 61 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index ea65893e..428d36be 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -125,7 +125,7 @@ android { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.0' + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.2.0' /** * AndroidX dependencies: 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 08a56312..c847e917 100644 --- a/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationActivity.kt +++ b/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationActivity.kt @@ -14,6 +14,7 @@ import android.view.View import android.view.View.INVISIBLE import android.view.View.VISIBLE import android.widget.Toast +import androidx.activity.OnBackPressedCallback import androidx.activity.result.ActivityResult import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts @@ -76,7 +77,9 @@ class PostCreationActivity : BaseThemedWithoutBarActivity() { PostCreationViewModelFactory( application, intent.clipData!!, - instance + instance, + intent.getStringExtra(PICTURE_DESCRIPTION), + intent.getBooleanExtra(POST_NSFW, false) ) } model = _model @@ -94,9 +97,6 @@ class PostCreationActivity : BaseThemedWithoutBarActivity() { ) } - model.setExistingDescription(intent.getStringExtra(PICTURE_DESCRIPTION)) - model.setExistingNSFW(intent.getBooleanExtra(POST_NSFW, false)) - lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { model.uiState.collect { uiState -> @@ -165,23 +165,25 @@ class PostCreationActivity : BaseThemedWithoutBarActivity() { val file = File(binding.root.context.cacheDir, it) model.trackTempFile(file) } - } - override fun onBackPressed() { - val redraft = intent.getBooleanExtra(POST_REDRAFT, false) - if (redraft) { - val builder = AlertDialog.Builder(binding.root.context) - builder.apply { - setMessage(R.string.redraft_dialog_cancel) - setPositiveButton(android.R.string.ok) { _, _ -> - super.onBackPressed() + onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + val redraft = intent.getBooleanExtra(POST_REDRAFT, false) + if (redraft) { + val builder = AlertDialog.Builder(binding.root.context) + builder.apply { + setMessage(R.string.redraft_dialog_cancel) + setPositiveButton(android.R.string.ok) { _, _ -> + finish() + } + setNegativeButton(android.R.string.cancel) { _, _ -> } + show() + } + } else { + finish() } - setNegativeButton(android.R.string.cancel) { _, _ -> } - show() } - } else { - super.onBackPressed() - } + }) } private val addPhotoResultContract = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> diff --git a/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationViewModel.kt b/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationViewModel.kt index f5f552c0..dcb5c6a7 100644 --- a/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationViewModel.kt +++ b/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationViewModel.kt @@ -74,14 +74,12 @@ data class PhotoData( var videoEncodeError: Boolean = false, ) : Parcelable -class PostCreationViewModel(application: Application, clipdata: ClipData? = null, val instance: InstanceDatabaseEntity? = null) : AndroidViewModel(application) { +class PostCreationViewModel(application: Application, clipdata: ClipData? = null, val instance: InstanceDatabaseEntity? = null, val existingDescription: String? = null, val existingNSFW: Boolean = false) : AndroidViewModel(application) { private val photoData: MutableLiveData> by lazy { MutableLiveData>().also { it.value = clipdata?.let { it1 -> addPossibleImages(it1, mutableListOf()) } } } - private var existingDescription: String? = null - private var existingNSFW: Boolean = false @Inject lateinit var apiHolder: PixelfedAPIHolder @@ -195,14 +193,6 @@ class PostCreationViewModel(application: Application, clipdata: ClipData? = null photoData.value = photoData.value } - fun setExistingDescription(description: String?) { - existingDescription = description - } - - fun setExistingNSFW(sensitive: Boolean) { - existingNSFW = sensitive - } - fun removeAt(currentPosition: Int) { photoData.value?.removeAt(currentPosition) _uiState.update { @@ -333,8 +323,8 @@ class PostCreationViewModel(application: Application, clipdata: ClipData? = null } } -class PostCreationViewModelFactory(val application: Application, val clipdata: ClipData, val instance: InstanceDatabaseEntity) : ViewModelProvider.Factory { +class PostCreationViewModelFactory(val application: Application, val clipdata: ClipData, val instance: InstanceDatabaseEntity, val existingDescription: String?, val existingNSFW: Boolean) : ViewModelProvider.Factory { override fun create(modelClass: Class): T { - return modelClass.getConstructor(Application::class.java, ClipData::class.java, InstanceDatabaseEntity::class.java).newInstance(application, clipdata, instance) + return modelClass.getConstructor(Application::class.java, ClipData::class.java, InstanceDatabaseEntity::class.java, String::class.java, Boolean::class.java).newInstance(application, clipdata, instance, existingDescription, existingNSFW) } } diff --git a/app/src/main/java/org/pixeldroid/app/postCreation/PostSubmissionActivity.kt b/app/src/main/java/org/pixeldroid/app/postCreation/PostSubmissionActivity.kt index 878e5541..76ce02f3 100644 --- a/app/src/main/java/org/pixeldroid/app/postCreation/PostSubmissionActivity.kt +++ b/app/src/main/java/org/pixeldroid/app/postCreation/PostSubmissionActivity.kt @@ -65,13 +65,12 @@ class PostSubmissionActivity : BaseThemedWithoutBarActivity() { val _model: PostSubmissionViewModel by viewModels { PostSubmissionViewModelFactory( application, - photoData!! + photoData!!, + intent.getStringExtra(PICTURE_DESCRIPTION) ) } model = _model - model.setExistingDescription(intent.getStringExtra(PICTURE_DESCRIPTION)) - val sensitive = intent.getBooleanExtra(POST_NSFW, false) model.updateNSFW(sensitive) binding.nsfwSwitch.isChecked = sensitive diff --git a/app/src/main/java/org/pixeldroid/app/postCreation/PostSubmissionViewModel.kt b/app/src/main/java/org/pixeldroid/app/postCreation/PostSubmissionViewModel.kt index 42b4e089..aab0f2f2 100644 --- a/app/src/main/java/org/pixeldroid/app/postCreation/PostSubmissionViewModel.kt +++ b/app/src/main/java/org/pixeldroid/app/postCreation/PostSubmissionViewModel.kt @@ -54,7 +54,7 @@ data class PostSubmissionActivityUiState( val uploadErrorExplanationVisible: Boolean = false, ) -class PostSubmissionViewModel(application: Application, photodata: ArrayList? = null) : AndroidViewModel(application) { +class PostSubmissionViewModel(application: Application, photodata: ArrayList? = null, val existingDescription: String? = null) : AndroidViewModel(application) { private val photoData: MutableLiveData> by lazy { MutableLiveData>().also { if (photodata != null) { @@ -62,7 +62,6 @@ class PostSubmissionViewModel(application: Application, photodata: ArrayList) : ViewModelProvider.Factory { +class PostSubmissionViewModelFactory(val application: Application, val photoData: ArrayList, val existingDescription: String?) : ViewModelProvider.Factory { override fun create(modelClass: Class): T { - return modelClass.getConstructor(Application::class.java, ArrayList::class.java).newInstance(application, photoData) + return modelClass.getConstructor(Application::class.java, ArrayList::class.java, String::class.java).newInstance(application, photoData, existingDescription) } } \ No newline at end of file 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 75a08e0d..1476b124 100644 --- a/app/src/main/java/org/pixeldroid/app/posts/StatusViewHolder.kt +++ b/app/src/main/java/org/pixeldroid/app/posts/StatusViewHolder.kt @@ -480,15 +480,24 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold status?.media_attachments!! // Catch possible exception from !! (?) val postNSFW = status?.sensitive - val imageNames = postAttachments.map { postAttachment -> - Uri.parse(postAttachment.url ?: "").lastPathSegment.toString() + val imageUriStrings = postAttachments.map { postAttachment -> + postAttachment.url ?: "" } - val imageUris = imageNames.map { imageName -> - Uri.fromFile(File(context.cacheDir, imageName)) + val imageNames = imageUriStrings.map { imageUriString -> + Uri.parse(imageUriString).lastPathSegment.toString() + } + val downloadedFiles = imageNames.map { imageName -> + File(context.cacheDir, imageName) + } + val imageUris = downloadedFiles.map { downloadedFile -> + Uri.fromFile(downloadedFile) } val imageDescriptions = postAttachments.map { postAttachment -> fromHtml(postAttachment.description ?: "").toString() } + val downloadRequests: List = imageUriStrings.map { imageUriString -> + Request.Builder().url(imageUriString).build() + } val counter = AtomicInteger(0) @@ -515,14 +524,18 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold // Pass downloaded images to new post creation activity intent.apply { imageUris.zip(imageDescriptions).map { (imageUri, imageDescription) -> - val imageItem = ClipData.Item(imageDescription, null, imageUri) + ClipData.Item(imageDescription, null, imageUri) + }.forEach { imageItem -> if (clipData == null) { - clipData = ClipData("", emptyArray(), imageItem) + clipData = ClipData( + "", + emptyArray(), + imageItem + ) } else { clipData!!.addItem(imageItem) } } - addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT) addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) } @@ -563,15 +576,7 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold } // Iterate through all pictures of the original post - for (currentAttachment in postAttachments) { - val imageUri = currentAttachment.url ?: "" - val imageName = - Uri.parse(imageUri).lastPathSegment.toString() - val downloadedFile = - File(context.cacheDir, imageName) - val downloadRequest: Request = - Request.Builder().url(imageUri).build() - + downloadRequests.zip(downloadedFiles).forEach { (downloadRequest, downloadedFile) -> // Check whether image is in cache directory already (maybe rather do so using Glide in the future?) if (!downloadedFile.exists()) { OkHttpClient().newCall(downloadRequest) @@ -606,7 +611,6 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold continuation() } } - } catch (exception: HttpException) { Toast.makeText( binding.root.context, diff --git a/build.gradle b/build.gradle index a828ac4b..14d775d6 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:8.0.0-alpha07' + classpath 'com.android.tools.build:gradle:7.3.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 819d5bf6..bc80582f 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -403,6 +403,14 @@ + + + + + + + + @@ -414,6 +422,14 @@ + + + + + + + + @@ -422,6 +438,14 @@ + + + + + + + + @@ -430,6 +454,14 @@ + + + + + + + + @@ -438,6 +470,14 @@ + + + + + + + + @@ -449,6 +489,14 @@ + + + + + + + + @@ -460,6 +508,14 @@ + + + + + + + + @@ -1687,6 +1743,14 @@ + + + + + + + + @@ -1695,6 +1759,14 @@ + + + + + + + + @@ -1703,6 +1775,14 @@ + + + + + + + + @@ -1791,6 +1871,14 @@ + + + + + + + + @@ -1799,6 +1887,14 @@ + + + + + + + + @@ -1807,6 +1903,14 @@ + + + + + + + + @@ -1815,6 +1919,14 @@ + + + + + + + + @@ -1823,6 +1935,14 @@ + + + + + + + + @@ -1839,6 +1959,14 @@ + + + + + + + + @@ -1847,6 +1975,14 @@ + + + + + + + + @@ -1855,6 +1991,14 @@ + + + + + + + + @@ -1863,6 +2007,14 @@ + + + + + + + + @@ -1871,6 +2023,14 @@ + + + + + + + + @@ -1879,6 +2039,14 @@ + + + + + + + + @@ -1887,6 +2055,14 @@ + + + + + + + + @@ -1895,6 +2071,14 @@ + + + + + + + + @@ -1906,6 +2090,14 @@ + + + + + + + + @@ -1914,6 +2106,14 @@ + + + + + + + + @@ -1922,6 +2122,14 @@ + + + + + + + + @@ -1930,6 +2138,14 @@ + + + + + + + + @@ -1938,6 +2154,14 @@ + + + + + + + + @@ -1946,6 +2170,14 @@ + + + + + + + + @@ -1954,6 +2186,14 @@ + + + + + + + + @@ -1970,6 +2210,22 @@ + + + + + + + + + + + + + + + + @@ -1978,6 +2234,14 @@ + + + + + + + + @@ -1994,6 +2258,14 @@ + + + + + + + + @@ -2026,6 +2298,14 @@ + + + + + + + + @@ -2058,6 +2338,14 @@ + + + + + + + + @@ -2098,6 +2386,14 @@ + + + + + + + + @@ -2106,6 +2402,14 @@ + + + + + + + + @@ -2114,6 +2418,14 @@ + + + + + + + + @@ -2122,6 +2434,14 @@ + + + + + + + + @@ -2130,6 +2450,14 @@ + + + + + + + + @@ -2138,6 +2466,14 @@ + + + + + + + + @@ -2146,6 +2482,14 @@ + + + + + + + + @@ -2154,6 +2498,14 @@ + + + + + + + + @@ -2500,6 +2852,9 @@ + + + @@ -2636,6 +2991,22 @@ + + + + + + + + + + + + + + + + @@ -2664,6 +3035,16 @@ + + + + + + + + + + @@ -2711,6 +3092,14 @@ + + + + + + + + @@ -2732,6 +3121,11 @@ + + + + + @@ -2779,11 +3173,24 @@ + + + + + + + + + + + + + @@ -2792,6 +3199,14 @@ + + + + + + + + @@ -2800,11 +3215,24 @@ + + + + + + + + + + + + + @@ -2813,6 +3241,14 @@ + + + + + + + + @@ -3284,6 +3720,14 @@ + + + + + + + + @@ -3292,6 +3736,14 @@ + + + + + + + + @@ -3300,6 +3752,14 @@ + + + + + + + + @@ -3308,6 +3768,14 @@ + + + + + + + + @@ -3316,6 +3784,14 @@ + + + + + + + + @@ -3324,6 +3800,14 @@ + + + + + + + + @@ -3332,6 +3816,14 @@ + + + + + + + + @@ -3340,6 +3832,14 @@ + + + + + + + + @@ -3348,6 +3848,14 @@ + + + + + + + + @@ -3356,6 +3864,14 @@ + + + + + + + + @@ -3364,6 +3880,14 @@ + + + + + + + + @@ -3372,6 +3896,14 @@ + + + + + + + + @@ -3380,6 +3912,14 @@ + + + + + + + + @@ -3388,6 +3928,14 @@ + + + + + + + + @@ -3396,6 +3944,14 @@ + + + + + + + + @@ -3404,11 +3960,24 @@ + + + + + + + + + + + + + @@ -3430,6 +3999,14 @@ + + + + + + + + @@ -3465,6 +4042,14 @@ + + + + + + + + @@ -3778,6 +4363,11 @@ + + + + + @@ -3798,6 +4388,14 @@ + + + + + + + + @@ -3893,6 +4491,14 @@ + + + + + + + + @@ -4202,6 +4808,9 @@ + + + @@ -4315,6 +4924,11 @@ + + + + + @@ -4486,6 +5100,14 @@ + + + + + + + + @@ -4563,7 +5185,18 @@ + + + + + + + + + + + @@ -4600,12 +5233,23 @@ + + + + + + + + + + + @@ -4639,6 +5283,14 @@ + + + + + + + + @@ -4647,6 +5299,14 @@ + + + + + + + + @@ -4676,6 +5336,14 @@ + + + + + + + + @@ -4685,6 +5353,9 @@ + + + @@ -4966,6 +5637,14 @@ + + + + + + + + From 2fe334d32ec61d645e910065f53b3f91919bd6cb Mon Sep 17 00:00:00 2001 From: MagT Date: Mon, 14 Nov 2022 17:02:48 +0000 Subject: [PATCH 13/21] Translated using Weblate (Polish) Currently translated at 87.3% (208 of 238 strings) Co-authored-by: MagT Translate-URL: https://weblate.pixeldroid.org/projects/pixeldroid/app/pl/ Translation: PixelDroid/pixeldroid --- app/src/main/res/values-pl/strings.xml | 35 ++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 8d07bab9..fb32e226 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -197,7 +197,7 @@ %1$s udostępnił post Miniatura zdjęcia w powiadomieniu o tym poście - ODKRYJ + Odkrywanie Możesz być zdezorientowany polem tekstowym pytającym o domenę twojej „instancji”. \n \nPixelfed to sfederowana platforma będąca częścią „Fediwersum”, co oznacza, że może ona komunikować się z innymi platformami tego „świata”, na przykład z Mastodonem (zobacz https://joinmastodon.org). @@ -252,4 +252,35 @@ Z %1$s Błąd podczas dodawania zdjęcia Podgląd posta - + Następny krok + + Dodanie elementu %d zakończone sukcesem + Dodanie %d elementów zakończone sukcesem + Ładowanie zakończone + Ładowanie zakończone + + Wzorzec opisu + Hasztagi zyskujące popularność + Wybierz co chcesz usunąć + Użyj dynamicznych kolorów z systemu + Dodaj szczegóły + Dodaj do zakładek + Usuń z zakładek + Szukaj popularnych kont na tej instancji + Popularne konta + Widok w siatce + Zakładki + Kolekcje + Usuń kolekcję + Dodaj wpis + Usuń wpis + Czy na pewno chcesz usunąć kolekcję\? + Wybierz co chcesz dodać + Dodany do kolekcji + Błąd dodawania do kolekcji + Błąd usuwania z kolekcji + Usunięto z kolekcji + Zapisz + Więcej ustawień profilu + Konto prywatne + \ No newline at end of file From 651832d35ea2c0581def3335e235c1e1dd3c83ef Mon Sep 17 00:00:00 2001 From: Matthieu <24-artectrex@users.noreply.shinice.net> Date: Sat, 19 Nov 2022 00:57:03 +0100 Subject: [PATCH 14/21] Refactor trending activity --- .../ProfilePostsRecyclerViewAdapter.kt | 22 +- .../app/searchDiscover/TrendingActivity.kt | 169 +- .../pixeldroid/app/utils/api/PixelfedAPI.kt | 3 +- app/src/main/res/layout/activity_trending.xml | 4 +- gradle.properties | 1 + gradle/verification-metadata.xml | 1439 +++++++++++++++++ 6 files changed, 1534 insertions(+), 104 deletions(-) diff --git a/app/src/main/java/org/pixeldroid/app/profile/ProfilePostsRecyclerViewAdapter.kt b/app/src/main/java/org/pixeldroid/app/profile/ProfilePostsRecyclerViewAdapter.kt index 5a33a222..9e449652 100644 --- a/app/src/main/java/org/pixeldroid/app/profile/ProfilePostsRecyclerViewAdapter.kt +++ b/app/src/main/java/org/pixeldroid/app/profile/ProfilePostsRecyclerViewAdapter.kt @@ -1,12 +1,22 @@ package org.pixeldroid.app.profile -import android.view.View +import android.view.LayoutInflater +import android.view.ViewGroup import android.widget.ImageView import androidx.recyclerview.widget.RecyclerView -import org.pixeldroid.app.R +import org.pixeldroid.app.databinding.FragmentProfilePostsBinding -class ProfilePostViewHolder(val postView: View) : RecyclerView.ViewHolder(postView) { - val postPreview: ImageView = postView.findViewById(R.id.postPreview) - val albumIcon: ImageView = postView.findViewById(R.id.albumIcon) - val videoIcon: ImageView = postView.findViewById(R.id.albumIcon) +class ProfilePostViewHolder(val postView: FragmentProfilePostsBinding) : RecyclerView.ViewHolder(postView.root) { + val postPreview: ImageView = postView.postPreview + val albumIcon: ImageView = postView.albumIcon + val videoIcon: ImageView = postView.videoIcon + + companion object { + fun create(parent: ViewGroup): ProfilePostViewHolder { + val itemBinding = FragmentProfilePostsBinding.inflate( + LayoutInflater.from(parent.context), parent, false + ) + return ProfilePostViewHolder(itemBinding) + } + } } \ No newline at end of file diff --git a/app/src/main/java/org/pixeldroid/app/searchDiscover/TrendingActivity.kt b/app/src/main/java/org/pixeldroid/app/searchDiscover/TrendingActivity.kt index e05d9eeb..44f474ff 100644 --- a/app/src/main/java/org/pixeldroid/app/searchDiscover/TrendingActivity.kt +++ b/app/src/main/java/org/pixeldroid/app/searchDiscover/TrendingActivity.kt @@ -1,8 +1,8 @@ package org.pixeldroid.app.searchDiscover +import android.annotation.SuppressLint import android.content.Intent import android.os.Bundle -import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.annotation.StringRes @@ -19,6 +19,7 @@ import org.pixeldroid.app.utils.BaseThemedWithBarActivity import org.pixeldroid.app.utils.api.PixelfedAPI import org.pixeldroid.app.utils.api.objects.Account import org.pixeldroid.app.utils.api.objects.Attachment +import org.pixeldroid.app.utils.api.objects.FeedContent import org.pixeldroid.app.utils.api.objects.Status import org.pixeldroid.app.utils.api.objects.Tag import org.pixeldroid.app.utils.setSquareImageFromURL @@ -27,44 +28,41 @@ import java.io.IOException class TrendingActivity : BaseThemedWithBarActivity() { - private lateinit var api: PixelfedAPI private lateinit var binding: ActivityTrendingBinding - private lateinit var recycler : RecyclerView - private lateinit var discoverAdapter : DiscoverRecyclerViewAdapter - private lateinit var hashtagsAdapter : HashtagsRecyclerViewAdapter - private lateinit var accountsAdapter : AccountsRecyclerViewAdapter + private lateinit var trendingAdapter : TrendingRecyclerViewAdapter override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityTrendingBinding.inflate(layoutInflater) setContentView(binding.root) - api = apiHolder.api ?: apiHolder.setToCurrentUser() - recycler = binding.list + val recycler = binding.list supportActionBar?.setDisplayHomeAsUpEnabled(true) val type = intent.getSerializableExtra(TRENDING_TAG) as TrendingType? ?: TrendingType.POSTS - if(type == TrendingType.POSTS || type == TrendingType.DISCOVER) { - // Set posts RecyclerView as a grid with 3 columns - recycler.layoutManager = GridLayoutManager(this, 3) - discoverAdapter = DiscoverRecyclerViewAdapter() - recycler.adapter = discoverAdapter - if(type == TrendingType.POSTS) { - supportActionBar?.setTitle(R.string.trending_posts) - } else { - supportActionBar?.setTitle(R.string.discover) + when (type) { + TrendingType.POSTS, TrendingType.DISCOVER -> { + // Set posts RecyclerView as a grid with 3 columns + recycler.layoutManager = GridLayoutManager(this, 3) + supportActionBar?.setTitle( + if (type == TrendingType.POSTS) { + R.string.trending_posts + } else { + R.string.discover + } + ) + this.trendingAdapter = DiscoverRecyclerViewAdapter() + } + TrendingType.HASHTAGS -> { + supportActionBar?.setTitle(R.string.trending_hashtags) + this.trendingAdapter = HashtagsRecyclerViewAdapter() + } + TrendingType.ACCOUNTS -> { + supportActionBar?.setTitle(R.string.popular_accounts) + this.trendingAdapter = AccountsRecyclerViewAdapter() } } - if(type == TrendingType.HASHTAGS) { - supportActionBar?.setTitle(R.string.trending_hashtags) - hashtagsAdapter = HashtagsRecyclerViewAdapter() - recycler.adapter = hashtagsAdapter - } - if(type == TrendingType.ACCOUNTS) { - supportActionBar?.setTitle(R.string.popular_accounts) - accountsAdapter = AccountsRecyclerViewAdapter() - recycler.adapter = accountsAdapter - } + recycler.adapter = this.trendingAdapter getTrending(type) binding.refreshLayout.setOnRefreshListener { @@ -76,6 +74,7 @@ class TrendingActivity : BaseThemedWithBarActivity() { binding.motionLayout.apply { if(show){ transitionToEnd() + binding.errorLayout.errorText.setText(errorText) } else { transitionToStart() } @@ -87,25 +86,14 @@ class TrendingActivity : BaseThemedWithBarActivity() { private fun getTrending(type: TrendingType) { lifecycleScope.launchWhenCreated { try { - when(type) { - TrendingType.POSTS -> { - val trendingPosts = api.trendingPosts("daily") - discoverAdapter.addPosts(trendingPosts) - } - TrendingType.HASHTAGS -> { - val trendingTags = api.trendingHashtags() - .map { it.copy(name = it.name.removePrefix("#")) } - hashtagsAdapter.addHashtags(trendingTags) - } - TrendingType.ACCOUNTS -> { - val trendingAccounts = api.popularAccounts() - accountsAdapter.addAccounts(trendingAccounts) - } - TrendingType.DISCOVER -> { - val posts = api.discover().posts - discoverAdapter.addPosts(posts) - } + val api: PixelfedAPI = apiHolder.api ?: apiHolder.setToCurrentUser() + val content: List = when(type) { + TrendingType.POSTS -> api.trendingPosts(Range.daily) + TrendingType.HASHTAGS -> api.trendingHashtags().map { it.copy(name = it.name.removePrefix("#")) } + TrendingType.ACCOUNTS -> api.popularAccounts() + TrendingType.DISCOVER -> api.discover().posts } + trendingAdapter.addPosts(content) showError(show = false) } catch (exception: IOException) { showError() @@ -116,25 +104,32 @@ class TrendingActivity : BaseThemedWithBarActivity() { } /** - * [RecyclerView.Adapter] that can display a list of [Status]s' thumbnails for the discover view + * Abstract class for the different RecyclerViewAdapters used in this activity */ - class DiscoverRecyclerViewAdapter: RecyclerView.Adapter() { - private val posts: ArrayList = ArrayList() + abstract class TrendingRecyclerViewAdapter: RecyclerView.Adapter(){ + val data: ArrayList = ArrayList() - fun addPosts(newPosts : List) { - posts.clear() - posts.addAll(newPosts) + @SuppressLint("NotifyDataSetChanged") + fun addPosts(newPosts: List){ + data.clear() + data.addAll(newPosts) notifyDataSetChanged() } - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProfilePostViewHolder { - val view = LayoutInflater.from(parent.context) - .inflate(R.layout.fragment_profile_posts, parent, false) - return ProfilePostViewHolder(view) - } + override fun getItemCount(): Int = data.size + } - override fun onBindViewHolder(holder: ProfilePostViewHolder, position: Int) { - val post = posts[position] + /** + * [RecyclerView.Adapter] that can display a list of [Status]s' thumbnails for the discover view + */ + class DiscoverRecyclerViewAdapter: TrendingRecyclerViewAdapter() { + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProfilePostViewHolder = + ProfilePostViewHolder.create(parent) + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + if (holder !is ProfilePostViewHolder) return + + val post = data[position] as? Status if((post?.media_attachments?.size ?: 0) > 1) { holder.albumIcon.visibility = View.VISIBLE } else { @@ -144,15 +139,14 @@ class TrendingActivity : BaseThemedWithBarActivity() { } else holder.videoIcon.visibility = View.GONE } - setSquareImageFromURL(holder.postView, post?.getPostPreviewURL(), holder.postPreview, post?.media_attachments?.firstOrNull()?.blurhash) + setSquareImageFromURL(holder.postView.root, post?.getPostPreviewURL(), holder.postPreview, post?.media_attachments?.firstOrNull()?.blurhash) holder.postPreview.setOnClickListener { - val intent = Intent(holder.postView.context, PostActivity::class.java) + val intent = Intent(holder.postView.root.context, PostActivity::class.java) intent.putExtra(Status.POST_TAG, post) - holder.postView.context.startActivity(intent) + holder.postView.root.context.startActivity(intent) } } - override fun getItemCount(): Int = posts.size } companion object { @@ -161,55 +155,38 @@ class TrendingActivity : BaseThemedWithBarActivity() { enum class TrendingType { POSTS, HASHTAGS, ACCOUNTS, DISCOVER } + + @Suppress("EnumEntryName", "unused") + enum class Range { + daily, monthly, yearly + } } /** * [RecyclerView.Adapter] that can display a list of [Tag]s for the trending view */ - class HashtagsRecyclerViewAdapter: RecyclerView.Adapter() { - private val tags: ArrayList = ArrayList() + class HashtagsRecyclerViewAdapter: TrendingRecyclerViewAdapter() { + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HashTagViewHolder = + HashTagViewHolder.create(parent) - fun addHashtags(newTags : List) { - tags.clear() - tags.addAll(newTags) - notifyDataSetChanged() + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + val tag = data[position] as Tag + (holder as HashTagViewHolder).bind(tag) } - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HashTagViewHolder { - return HashTagViewHolder.create(parent) - } - - override fun onBindViewHolder(holder: HashTagViewHolder, position: Int) { - val tag = tags[position] - holder.bind(tag) - } - - override fun getItemCount(): Int = tags.size } /** * [RecyclerView.Adapter] that can display a list of [Account]s for the popular view */ - class AccountsRecyclerViewAdapter: RecyclerView.Adapter() { - private val accounts: ArrayList = ArrayList() + class AccountsRecyclerViewAdapter: TrendingRecyclerViewAdapter() { + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AccountViewHolder = + AccountViewHolder.create(parent) - fun addAccounts(newAccounts : List) { - accounts.clear() - accounts.addAll(newAccounts) - notifyDataSetChanged() + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + val account = data[position] as? Account + (holder as AccountViewHolder).bind(account) } - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AccountViewHolder { - return AccountViewHolder.create(parent) - } - - override fun onBindViewHolder(holder: AccountViewHolder, position: Int) { - val account = accounts[position] - holder.bind(account) - } - - override fun getItemCount(): Int = accounts.size } } \ No newline at end of file diff --git a/app/src/main/java/org/pixeldroid/app/utils/api/PixelfedAPI.kt b/app/src/main/java/org/pixeldroid/app/utils/api/PixelfedAPI.kt index 5313f130..4e8db5fa 100644 --- a/app/src/main/java/org/pixeldroid/app/utils/api/PixelfedAPI.kt +++ b/app/src/main/java/org/pixeldroid/app/utils/api/PixelfedAPI.kt @@ -7,6 +7,7 @@ import okhttp3.Interceptor import org.pixeldroid.app.utils.api.objects.* import okhttp3.MultipartBody import okhttp3.OkHttpClient +import org.pixeldroid.app.searchDiscover.TrendingActivity import org.pixeldroid.app.utils.api.objects.Collection import org.pixeldroid.app.utils.api.objects.Tag import org.pixeldroid.app.utils.db.AppDatabase @@ -367,7 +368,7 @@ interface PixelfedAPI { @GET("/api/v1.1/discover/posts/trending") suspend fun trendingPosts( - @Query("range") range: String + @Query("range") range: TrendingActivity.Companion.Range ) : List @GET("/api/v1.1/discover/posts/hashtags") diff --git a/app/src/main/res/layout/activity_trending.xml b/app/src/main/res/layout/activity_trending.xml index b38b6bd4..784b355e 100644 --- a/app/src/main/res/layout/activity_trending.xml +++ b/app/src/main/res/layout/activity_trending.xml @@ -38,7 +38,9 @@ android:layout_height="match_parent" app:layoutDescription="@xml/error_layout_xml_error_scene"> - + false + + + + + + + + @@ -59,6 +67,14 @@ + + + + + + + + @@ -79,6 +95,9 @@ + + + @@ -102,6 +121,14 @@ + + + + + + + + @@ -113,6 +140,14 @@ + + + + + + + + @@ -142,6 +177,14 @@ + + + + + + + + @@ -282,6 +325,22 @@ + + + + + + + + + + + + + + + + @@ -304,6 +363,14 @@ + + + + + + + + @@ -329,6 +396,9 @@ + + + @@ -344,6 +414,14 @@ + + + + + + + + @@ -403,6 +481,14 @@ + + + + + + + + @@ -414,6 +500,22 @@ + + + + + + + + + + + + + + + + @@ -422,6 +524,22 @@ + + + + + + + + + + + + + + + + @@ -430,6 +548,22 @@ + + + + + + + + + + + + + + + + @@ -438,6 +572,22 @@ + + + + + + + + + + + + + + + + @@ -449,6 +599,22 @@ + + + + + + + + + + + + + + + + @@ -460,6 +626,22 @@ + + + + + + + + + + + + + + + + @@ -471,6 +653,14 @@ + + + + + + + + @@ -516,6 +706,14 @@ + + + + + + + + @@ -527,6 +725,14 @@ + + + + + + + + @@ -568,6 +774,9 @@ + + + @@ -718,6 +927,14 @@ + + + + + + + + @@ -761,6 +978,14 @@ + + + + + + + + @@ -827,6 +1052,11 @@ + + + + + @@ -909,6 +1139,14 @@ + + + + + + + + @@ -942,6 +1180,14 @@ + + + + + + + + @@ -1239,6 +1485,11 @@ + + + + + @@ -1247,6 +1498,14 @@ + + + + + + + + @@ -1258,6 +1517,14 @@ + + + + + + + + @@ -1345,6 +1612,14 @@ + + + + + + + + @@ -1446,6 +1721,14 @@ + + + + + + + + @@ -1454,6 +1737,14 @@ + + + + + + + + @@ -1462,6 +1753,14 @@ + + + + + + + + @@ -1470,6 +1769,14 @@ + + + + + + + + @@ -1478,6 +1785,14 @@ + + + + + + + + @@ -1494,6 +1809,14 @@ + + + + + + + + @@ -1507,6 +1830,14 @@ + + + + + + + + @@ -1523,6 +1854,14 @@ + + + + + + + + @@ -1531,6 +1870,14 @@ + + + + + + + + @@ -1687,6 +2034,14 @@ + + + + + + + + @@ -1695,6 +2050,22 @@ + + + + + + + + + + + + + + + + @@ -1703,6 +2074,22 @@ + + + + + + + + + + + + + + + + @@ -1711,6 +2098,14 @@ + + + + + + + + @@ -1791,6 +2186,14 @@ + + + + + + + + @@ -1799,6 +2202,22 @@ + + + + + + + + + + + + + + + + @@ -1807,6 +2226,22 @@ + + + + + + + + + + + + + + + + @@ -1815,6 +2250,14 @@ + + + + + + + + @@ -1823,6 +2266,14 @@ + + + + + + + + @@ -1831,6 +2282,14 @@ + + + + + + + + @@ -1839,6 +2298,22 @@ + + + + + + + + + + + + + + + + @@ -1847,6 +2322,22 @@ + + + + + + + + + + + + + + + + @@ -1855,6 +2346,22 @@ + + + + + + + + + + + + + + + + @@ -1863,6 +2370,22 @@ + + + + + + + + + + + + + + + + @@ -1871,6 +2394,22 @@ + + + + + + + + + + + + + + + + @@ -1879,6 +2418,22 @@ + + + + + + + + + + + + + + + + @@ -1887,6 +2442,22 @@ + + + + + + + + + + + + + + + + @@ -1895,6 +2466,22 @@ + + + + + + + + + + + + + + + + @@ -1903,6 +2490,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + @@ -1911,6 +2522,22 @@ + + + + + + + + + + + + + + + + @@ -1919,6 +2546,22 @@ + + + + + + + + + + + + + + + + @@ -1927,6 +2570,22 @@ + + + + + + + + + + + + + + + + @@ -1935,6 +2594,22 @@ + + + + + + + + + + + + + + + + @@ -1943,6 +2618,22 @@ + + + + + + + + + + + + + + + + @@ -1951,6 +2642,22 @@ + + + + + + + + + + + + + + + + @@ -1959,6 +2666,14 @@ + + + + + + + + @@ -1967,6 +2682,22 @@ + + + + + + + + + + + + + + + + @@ -1975,6 +2706,22 @@ + + + + + + + + + + + + + + + + @@ -1983,6 +2730,14 @@ + + + + + + + + @@ -1991,6 +2746,22 @@ + + + + + + + + + + + + + + + + @@ -1999,6 +2770,14 @@ + + + + + + + + @@ -2023,6 +2802,14 @@ + + + + + + + + @@ -2031,6 +2818,22 @@ + + + + + + + + + + + + + + + + @@ -2039,6 +2842,22 @@ + + + + + + + + + + + + + + + + @@ -2047,6 +2866,22 @@ + + + + + + + + + + + + + + + + @@ -2055,6 +2890,22 @@ + + + + + + + + + + + + + + + + @@ -2063,6 +2914,22 @@ + + + + + + + + + + + + + + + + @@ -2071,6 +2938,22 @@ + + + + + + + + + + + + + + + + @@ -2079,6 +2962,22 @@ + + + + + + + + + + + + + + + + @@ -2087,6 +2986,22 @@ + + + + + + + + + + + + + + + + @@ -2095,6 +3010,22 @@ + + + + + + + + + + + + + + + + @@ -2103,6 +3034,22 @@ + + + + + + + + + + + + + + + + @@ -2111,6 +3058,22 @@ + + + + + + + + + + + + + + + + @@ -2119,6 +3082,22 @@ + + + + + + + + + + + + + + + + @@ -2127,6 +3106,22 @@ + + + + + + + + + + + + + + + + @@ -2135,6 +3130,22 @@ + + + + + + + + + + + + + + + + @@ -2143,6 +3154,22 @@ + + + + + + + + + + + + + + + + @@ -2151,6 +3178,22 @@ + + + + + + + + + + + + + + + + @@ -2159,6 +3202,14 @@ + + + + + + + + @@ -2401,6 +3452,14 @@ + + + + + + + + @@ -2480,6 +3539,14 @@ + + + + + + + + @@ -2497,6 +3564,9 @@ + + + @@ -2532,6 +3602,14 @@ + + + + + + + + @@ -2633,6 +3711,22 @@ + + + + + + + + + + + + + + + + @@ -2661,6 +3755,16 @@ + + + + + + + + + + @@ -2708,6 +3812,14 @@ + + + + + + + + @@ -2729,6 +3841,11 @@ + + + + + @@ -2776,11 +3893,29 @@ + + + + + + + + + + + + + + + + + + @@ -2789,6 +3924,22 @@ + + + + + + + + + + + + + + + + @@ -2797,11 +3948,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -2810,6 +3987,14 @@ + + + + + + + + @@ -3281,6 +4466,14 @@ + + + + + + + + @@ -3289,6 +4482,14 @@ + + + + + + + + @@ -3297,6 +4498,14 @@ + + + + + + + + @@ -3305,6 +4514,14 @@ + + + + + + + + @@ -3313,6 +4530,14 @@ + + + + + + + + @@ -3321,6 +4546,14 @@ + + + + + + + + @@ -3329,6 +4562,14 @@ + + + + + + + + @@ -3337,6 +4578,14 @@ + + + + + + + + @@ -3345,6 +4594,14 @@ + + + + + + + + @@ -3353,6 +4610,14 @@ + + + + + + + + @@ -3361,6 +4626,14 @@ + + + + + + + + @@ -3369,6 +4642,14 @@ + + + + + + + + @@ -3377,6 +4658,14 @@ + + + + + + + + @@ -3385,6 +4674,14 @@ + + + + + + + + @@ -3393,6 +4690,14 @@ + + + + + + + + @@ -3401,11 +4706,24 @@ + + + + + + + + + + + + + @@ -3427,6 +4745,14 @@ + + + + + + + + @@ -3462,6 +4788,14 @@ + + + + + + + + @@ -3775,6 +5109,11 @@ + + + + + @@ -3795,6 +5134,14 @@ + + + + + + + + @@ -3890,6 +5237,14 @@ + + + + + + + + @@ -3898,6 +5253,14 @@ + + + + + + + + @@ -4199,6 +5562,9 @@ + + + @@ -4312,6 +5678,14 @@ + + + + + + + + @@ -4483,6 +5857,14 @@ + + + + + + + + @@ -4560,7 +5942,18 @@ + + + + + + + + + + + @@ -4597,12 +5990,23 @@ + + + + + + + + + + + @@ -4636,6 +6040,14 @@ + + + + + + + + @@ -4644,6 +6056,14 @@ + + + + + + + + @@ -4673,6 +6093,14 @@ + + + + + + + + @@ -4682,6 +6110,9 @@ + + + @@ -4963,6 +6394,14 @@ + + + + + + + + From 4f3020e0bed4461e5507fea97bd0a28d3ba0b57e Mon Sep 17 00:00:00 2001 From: fgerber Date: Tue, 15 Nov 2022 12:31:04 +0100 Subject: [PATCH 15/21] Restructure post creation activity into two fragments --- app/src/main/AndroidManifest.xml | 3 - .../app/postCreation/PostCreationActivity.kt | 278 +--------------- .../app/postCreation/PostCreationFragment.kt | 311 ++++++++++++++++++ .../app/postCreation/PostCreationViewModel.kt | 257 ++++++++++++++- .../postCreation/PostSubmissionActivity.kt | 193 ----------- .../postCreation/PostSubmissionFragment.kt | 194 +++++++++++ .../app/postCreation/camera/CameraFragment.kt | 1 + app/src/main/res/drawable/switch_account.xml | 5 + .../res/layout/activity_post_creation.xml | 98 +----- .../res/layout/fragment_post_creation.xml | 102 ++++++ ...ssion.xml => fragment_post_submission.xml} | 28 +- .../res/menu/post_submission_account_menu.xml | 2 +- .../res/navigation/post_creation_graph.xml | 25 ++ 13 files changed, 919 insertions(+), 578 deletions(-) create mode 100644 app/src/main/java/org/pixeldroid/app/postCreation/PostCreationFragment.kt delete mode 100644 app/src/main/java/org/pixeldroid/app/postCreation/PostSubmissionActivity.kt create mode 100644 app/src/main/java/org/pixeldroid/app/postCreation/PostSubmissionFragment.kt create mode 100644 app/src/main/res/drawable/switch_account.xml create mode 100644 app/src/main/res/layout/fragment_post_creation.xml rename app/src/main/res/layout/{activity_post_submission.xml => fragment_post_submission.xml} (89%) create mode 100644 app/src/main/res/navigation/post_creation_graph.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 8d057857..915bc2c3 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -67,9 +67,6 @@ - - - // update UI - binding.carousel.addData( - newPhotoData.map { - CarouselItem( - it.imageUri, it.imageDescription, it.video, - it.videoEncodeProgress, it.videoEncodeStabilizationFirstPass, - it.videoEncodeComplete, it.videoEncodeError, - ) - } - ) - } - - lifecycleScope.launch { - repeatOnLifecycle(Lifecycle.State.STARTED) { - model.uiState.collect { uiState -> - uiState.userMessage?.let { - AlertDialog.Builder(binding.root.context).apply { - setMessage(it) - setNegativeButton(android.R.string.ok) { _, _ -> } - }.show() - - // Notify the ViewModel the message is displayed - model.userMessageShown() - } - binding.addPhotoButton.isEnabled = uiState.addPhotoButtonEnabled - binding.removePhotoButton.isEnabled = uiState.removePhotoButtonEnabled - binding.editPhotoButton.isEnabled = uiState.editPhotoButtonEnabled - binding.toolbarPostCreation.visibility = - if (uiState.isCarousel) VISIBLE else INVISIBLE - binding.carousel.layoutCarousel = uiState.isCarousel - } - } - } - - binding.carousel.apply { - layoutCarouselCallback = { model.becameCarousel(it)} - maxEntries = instance.albumLimit - addPhotoButtonCallback = { - addPhoto() - } - updateDescriptionCallback = { position: Int, description: String -> - model.updateDescription(position, description) - } - } - // get the description and send the post - binding.postCreationSendButton.setOnClickListener { - if (validatePost() && model.isNotEmpty()) { - model.nextStep(binding.root.context) - } - } - - binding.editPhotoButton.setOnClickListener { - binding.carousel.currentPosition.takeIf { it != RecyclerView.NO_POSITION }?.let { currentPosition -> - edit(currentPosition) - } - } - - binding.addPhotoButton.setOnClickListener { - addPhoto() - } - - binding.savePhotoButton.setOnClickListener { - binding.carousel.currentPosition.takeIf { it != RecyclerView.NO_POSITION }?.let { currentPosition -> - savePicture(it, currentPosition) - } - } - - binding.removePhotoButton.setOnClickListener { - binding.carousel.currentPosition.takeIf { it != RecyclerView.NO_POSITION }?.let { currentPosition -> - model.removeAt(currentPosition) - model.cancelEncode(currentPosition) - } - } - - // Clean up temporary files, if any - val tempFiles = intent.getStringArrayExtra(TEMP_FILES) - tempFiles?.asList()?.forEach { - val file = File(binding.root.context.cacheDir, it) - model.trackTempFile(file) - } - - onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) { - override fun handleOnBackPressed() { - val redraft = intent.getBooleanExtra(POST_REDRAFT, false) - if (redraft) { - val builder = AlertDialog.Builder(binding.root.context) - builder.apply { - setMessage(R.string.redraft_dialog_cancel) - setPositiveButton(android.R.string.ok) { _, _ -> - finish() - } - setNegativeButton(android.R.string.cancel) { _, _ -> } - show() - } - } else { - finish() - } - } - }) + binding = ActivityPostCreationBinding.inflate(layoutInflater) + setContentView(binding.root) + val navHostFragment = + supportFragmentManager.findFragmentById(R.id.postCreationContainer) as NavHostFragment + navController = navHostFragment.navController + navController.setGraph(R.navigation.post_creation_graph) } - private val addPhotoResultContract = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> - if (result.resultCode == Activity.RESULT_OK && result.data?.clipData != null) { - result.data?.clipData?.let { - model.setImages(model.addPossibleImages(it)) - } - } else if (result.resultCode != Activity.RESULT_CANCELED) { - Toast.makeText(applicationContext, R.string.add_images_error, Toast.LENGTH_SHORT).show() - } + override fun onSupportNavigateUp(): Boolean { + return navController.navigateUp() || super.onSupportNavigateUp() } - private fun addPhoto(){ - addPhotoResultContract.launch( - Intent(this, CameraActivity::class.java) - ) - } - - private fun savePicture(button: View, currentPosition: Int) { - val originalUri = model.getPhotoData().value!![currentPosition].imageUri - - val pair = getOutputFile(originalUri) - val outputStream: OutputStream = pair.first - val path: String = pair.second - - contentResolver.openInputStream(originalUri)!!.use { input -> - outputStream.use { output -> - input.copyTo(output) - } - } - - if(path.startsWith("file")) { - MediaScannerConnection.scanFile( - this, - arrayOf(path.toUri().toFile().absolutePath), - null - ) { path, uri -> - if (uri == null) { - Log.e( - "NEW IMAGE SCAN FAILED", - "Tried to scan $path, but it failed" - ) - } - } - } - Snackbar.make( - button, getString(R.string.save_image_success), - Snackbar.LENGTH_LONG - ).show() - } - - private fun getOutputFile(uri: Uri): Pair { - val extension = uri.fileExtension(contentResolver) - - val name = SimpleDateFormat("yyyy-MM-dd-HH-mm-ss-SSS", Locale.US) - .format(System.currentTimeMillis()) + ".$extension" - - val outputStream: OutputStream - val path: String - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - val resolver: ContentResolver = contentResolver - val type = uri.getMimeType(contentResolver) - val contentValues = ContentValues() - contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, name) - contentValues.put(MediaStore.MediaColumns.MIME_TYPE, type) - contentValues.put( - MediaStore.MediaColumns.RELATIVE_PATH, - Environment.DIRECTORY_PICTURES - ) - val store = - if (type.startsWith("video")) MediaStore.Video.Media.EXTERNAL_CONTENT_URI - else MediaStore.Images.Media.EXTERNAL_CONTENT_URI - val imageUri: Uri = resolver.insert(store, contentValues)!! - path = imageUri.toString() - outputStream = resolver.openOutputStream(imageUri)!! - } else { - @Suppress("DEPRECATION") val imagesDir = - Environment.getExternalStoragePublicDirectory(getString(R.string.app_name)) - imagesDir.mkdir() - val file = File(imagesDir, name) - path = Uri.fromFile(file).toString() - outputStream = file.outputStream() - } - return Pair(outputStream, path) - } - - - private fun validatePost(): Boolean { - if(model.getPhotoData().value?.all { !it.video || it.videoEncodeComplete } == false){ - AlertDialog.Builder(this).apply { - setMessage(R.string.still_encoding) - setNegativeButton(android.R.string.ok) { _, _ -> } - }.show() - return false - } - return true - } - - private val editResultContract: ActivityResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()){ - result: ActivityResult? -> - if (result?.resultCode == Activity.RESULT_OK && result.data != null) { - val position: Int = result.data!!.getIntExtra(org.pixeldroid.media_editor.photoEdit.PhotoEditActivity.PICTURE_POSITION, 0) - model.modifyAt(position, result.data!!) - ?: Toast.makeText(applicationContext, R.string.error_editing, Toast.LENGTH_SHORT).show() - } else if(result?.resultCode != Activity.RESULT_CANCELED){ - Toast.makeText(applicationContext, R.string.error_editing, Toast.LENGTH_SHORT).show() - } - } - - private fun edit(position: Int) { - val intent = Intent( - this, - if(model.getPhotoData().value!![position].video) org.pixeldroid.media_editor.photoEdit.VideoEditActivity::class.java else org.pixeldroid.media_editor.photoEdit.PhotoEditActivity::class.java - ) - .putExtra(org.pixeldroid.media_editor.photoEdit.PhotoEditActivity.PICTURE_URI, model.getPhotoData().value!![position].imageUri) - .putExtra(org.pixeldroid.media_editor.photoEdit.PhotoEditActivity.PICTURE_POSITION, position) - - editResultContract.launch(intent) - - } } \ No newline at end of file diff --git a/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationFragment.kt b/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationFragment.kt new file mode 100644 index 00000000..e0d722b8 --- /dev/null +++ b/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationFragment.kt @@ -0,0 +1,311 @@ +package org.pixeldroid.app.postCreation + +import android.app.Activity +import android.app.AlertDialog +import android.content.ContentResolver +import android.content.ContentValues +import android.content.Intent +import android.media.MediaScannerConnection +import android.net.Uri +import android.os.* +import android.provider.MediaStore +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Toast +import androidx.activity.OnBackPressedCallback +import androidx.activity.result.ActivityResult +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts +import androidx.core.net.toFile +import androidx.core.net.toUri +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import androidx.navigation.fragment.findNavController +import androidx.recyclerview.widget.RecyclerView +import com.google.android.material.snackbar.Snackbar +import kotlinx.coroutines.launch +import org.pixeldroid.app.R +import org.pixeldroid.app.databinding.FragmentPostCreationBinding +import org.pixeldroid.app.postCreation.camera.CameraActivity +import org.pixeldroid.app.postCreation.carousel.CarouselItem +import org.pixeldroid.app.utils.BaseFragment +import org.pixeldroid.app.utils.db.entities.InstanceDatabaseEntity +import org.pixeldroid.app.utils.db.entities.UserDatabaseEntity +import org.pixeldroid.app.utils.fileExtension +import org.pixeldroid.app.utils.getMimeType +import org.pixeldroid.media_editor.photoEdit.PhotoEditActivity +import org.pixeldroid.media_editor.photoEdit.VideoEditActivity +import java.io.File +import java.io.OutputStream +import java.text.SimpleDateFormat +import java.util.* + + +class PostCreationFragment : BaseFragment() { + + private var user: UserDatabaseEntity? = null + private var instance: InstanceDatabaseEntity = InstanceDatabaseEntity("", "") + + private lateinit var binding: FragmentPostCreationBinding + private lateinit var model: PostCreationViewModel + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + super.onCreateView(inflater, container, savedInstanceState) + + // Inflate the layout for this fragment + binding = FragmentPostCreationBinding.inflate(layoutInflater) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + user = db.userDao().getActiveUser() + + instance = user?.run { + db.instanceDao().getAll().first { instanceDatabaseEntity -> + instanceDatabaseEntity.uri.contains(instance_uri) + } + } ?: InstanceDatabaseEntity("", "") + + val _model: PostCreationViewModel by activityViewModels { + PostCreationViewModelFactory( + requireActivity().application, + requireActivity().intent.clipData!!, + instance, + requireActivity().intent.getStringExtra(PostCreationActivity.PICTURE_DESCRIPTION), + requireActivity().intent.getBooleanExtra(PostCreationActivity.POST_NSFW, false) + ) + } + model = _model + + model.getPhotoData().observe(viewLifecycleOwner) { newPhotoData -> + // update UI + binding.carousel.addData( + newPhotoData.map { + CarouselItem( + it.imageUri, it.imageDescription, it.video, + it.videoEncodeProgress, it.videoEncodeStabilizationFirstPass, + it.videoEncodeComplete, it.videoEncodeError, + ) + } + ) + } + + lifecycleScope.launch { + viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { + model.uiState.collect { uiState -> + uiState.userMessage?.let { + AlertDialog.Builder(binding.root.context).apply { + setMessage(it) + setNegativeButton(android.R.string.ok) { _, _ -> } + }.show() + + // Notify the ViewModel the message is displayed + model.userMessageShown() + } + binding.addPhotoButton.isEnabled = uiState.addPhotoButtonEnabled + binding.removePhotoButton.isEnabled = uiState.removePhotoButtonEnabled + binding.editPhotoButton.isEnabled = uiState.editPhotoButtonEnabled + binding.toolbarPostCreation.visibility = + if (uiState.isCarousel) View.VISIBLE else View.INVISIBLE + binding.carousel.layoutCarousel = uiState.isCarousel + } + } + } + + binding.carousel.apply { + layoutCarouselCallback = { model.becameCarousel(it)} + maxEntries = instance.albumLimit + addPhotoButtonCallback = { + addPhoto() + } + updateDescriptionCallback = { position: Int, description: String -> + model.updateDescription(position, description) + } + } + // get the description and send the post + binding.postCreationSendButton.setOnClickListener { + if (validatePost() && model.isNotEmpty()) { + findNavController().navigate(R.id.action_postCreationFragment_to_postSubmissionFragment) + } + } + + binding.editPhotoButton.setOnClickListener { + binding.carousel.currentPosition.takeIf { it != RecyclerView.NO_POSITION }?.let { currentPosition -> + edit(currentPosition) + } + } + + binding.addPhotoButton.setOnClickListener { + addPhoto() + } + + binding.savePhotoButton.setOnClickListener { + binding.carousel.currentPosition.takeIf { it != RecyclerView.NO_POSITION }?.let { currentPosition -> + savePicture(it, currentPosition) + } + } + + binding.removePhotoButton.setOnClickListener { + binding.carousel.currentPosition.takeIf { it != RecyclerView.NO_POSITION }?.let { currentPosition -> + model.removeAt(currentPosition) + model.cancelEncode(currentPosition) + } + } + + // Clean up temporary files, if any + val tempFiles = requireActivity().intent.getStringArrayExtra(PostCreationActivity.TEMP_FILES) + tempFiles?.asList()?.forEach { + val file = File(binding.root.context.cacheDir, it) + model.trackTempFile(file) + } + + // Handle back pressed button + requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + val redraft = requireActivity().intent.getBooleanExtra(PostCreationActivity.POST_REDRAFT, false) + if (redraft) { + val builder = AlertDialog.Builder(binding.root.context) + builder.apply { + setMessage(R.string.redraft_dialog_cancel) + setPositiveButton(android.R.string.ok) { _, _ -> + requireActivity().finish() + } + setNegativeButton(android.R.string.cancel) { _, _ -> } + show() + } + } else { + requireActivity().finish() + } + } + }) + } + + private val addPhotoResultContract = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> + if (result.resultCode == Activity.RESULT_OK && result.data?.clipData != null) { + result.data?.clipData?.let { + model.setImages(model.addPossibleImages(it)) + } + } else if (result.resultCode != Activity.RESULT_CANCELED) { + Toast.makeText(requireActivity(), R.string.add_images_error, Toast.LENGTH_SHORT).show() + } + } + + private fun addPhoto(){ + addPhotoResultContract.launch( + Intent(requireActivity(), CameraActivity::class.java) + ) + } + + private fun savePicture(button: View, currentPosition: Int) { + val originalUri = model.getPhotoData().value!![currentPosition].imageUri + + val pair = getOutputFile(originalUri) + val outputStream: OutputStream = pair.first + val path: String = pair.second + + requireActivity().contentResolver.openInputStream(originalUri)!!.use { input -> + outputStream.use { output -> + input.copyTo(output) + } + } + + if(path.startsWith("file")) { + MediaScannerConnection.scanFile( + requireActivity(), + arrayOf(path.toUri().toFile().absolutePath), + null + ) { path, uri -> + if (uri == null) { + Log.e( + "NEW IMAGE SCAN FAILED", + "Tried to scan $path, but it failed" + ) + } + } + } + Snackbar.make( + button, getString(R.string.save_image_success), + Snackbar.LENGTH_LONG + ).show() + } + + private fun getOutputFile(uri: Uri): Pair { + val extension = uri.fileExtension(requireActivity().contentResolver) + + val name = SimpleDateFormat("yyyy-MM-dd-HH-mm-ss-SSS", Locale.US) + .format(System.currentTimeMillis()) + ".$extension" + + val outputStream: OutputStream + val path: String + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + val resolver: ContentResolver = requireActivity().contentResolver + val type = uri.getMimeType(requireActivity().contentResolver) + val contentValues = ContentValues() + contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, name) + contentValues.put(MediaStore.MediaColumns.MIME_TYPE, type) + contentValues.put( + MediaStore.MediaColumns.RELATIVE_PATH, + Environment.DIRECTORY_PICTURES + ) + val store = + if (type.startsWith("video")) MediaStore.Video.Media.EXTERNAL_CONTENT_URI + else MediaStore.Images.Media.EXTERNAL_CONTENT_URI + val imageUri: Uri = resolver.insert(store, contentValues)!! + path = imageUri.toString() + outputStream = resolver.openOutputStream(imageUri)!! + } else { + @Suppress("DEPRECATION") val imagesDir = + Environment.getExternalStoragePublicDirectory(getString(R.string.app_name)) + imagesDir.mkdir() + val file = File(imagesDir, name) + path = Uri.fromFile(file).toString() + outputStream = file.outputStream() + } + return Pair(outputStream, path) + } + + + private fun validatePost(): Boolean { + if (model.getPhotoData().value?.all { !it.video || it.videoEncodeComplete } == false) { + AlertDialog.Builder(requireActivity()).apply { + setMessage(R.string.still_encoding) + setNegativeButton(android.R.string.ok) { _, _ -> } + }.show() + return false + } + return true + } + + private val editResultContract: ActivityResultLauncher = registerForActivityResult( + ActivityResultContracts.StartActivityForResult()){ + result: ActivityResult? -> + if (result?.resultCode == Activity.RESULT_OK && result.data != null) { + val position: Int = result.data!!.getIntExtra(org.pixeldroid.media_editor.photoEdit.PhotoEditActivity.PICTURE_POSITION, 0) + model.modifyAt(position, result.data!!) + ?: Toast.makeText(requireActivity(), R.string.error_editing, Toast.LENGTH_SHORT).show() + } else if(result?.resultCode != Activity.RESULT_CANCELED){ + Toast.makeText(requireActivity(), R.string.error_editing, Toast.LENGTH_SHORT).show() + } + } + + private fun edit(position: Int) { + val intent = Intent( + requireActivity(), + if (model.getPhotoData().value!![position].video) VideoEditActivity::class.java else PhotoEditActivity::class.java + ) + .putExtra(PhotoEditActivity.PICTURE_URI, model.getPhotoData().value!![position].imageUri) + .putExtra(PhotoEditActivity.PICTURE_POSITION, position) + + editResultContract.launch(intent) + } +} + diff --git a/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationViewModel.kt b/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationViewModel.kt index dcb5c6a7..4f9c214f 100644 --- a/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationViewModel.kt +++ b/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationViewModel.kt @@ -2,14 +2,16 @@ package org.pixeldroid.app.postCreation import android.app.Application import android.content.ClipData -import android.content.Context import android.content.Intent import android.net.Uri import android.os.Parcelable import android.provider.OpenableColumns -import androidx.core.content.ContextCompat +import android.text.Editable +import android.util.Log +import android.widget.Toast import androidx.core.net.toFile import androidx.core.net.toUri +import androidx.exifinterface.media.ExifInterface import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData @@ -17,18 +19,32 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope import androidx.preference.PreferenceManager +import com.jarsilio.android.scrambler.exceptions.UnsupportedFileFormatException +import com.jarsilio.android.scrambler.stripMetadata +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers +import io.reactivex.rxjava3.disposables.Disposable +import io.reactivex.rxjava3.schedulers.Schedulers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.parcelize.Parcelize +import okhttp3.MultipartBody +import org.pixeldroid.app.MainActivity import org.pixeldroid.app.R import org.pixeldroid.app.utils.PixelDroidApplication +import org.pixeldroid.app.utils.api.objects.Attachment import org.pixeldroid.app.utils.db.entities.InstanceDatabaseEntity +import org.pixeldroid.app.utils.db.entities.UserDatabaseEntity import org.pixeldroid.app.utils.di.PixelfedAPIHolder +import org.pixeldroid.app.utils.fileExtension import org.pixeldroid.app.utils.getMimeType import org.pixeldroid.media_editor.photoEdit.VideoEditActivity +import retrofit2.HttpException import java.io.File +import java.io.FileNotFoundException +import java.io.IOException +import java.net.URI import javax.inject.Inject import kotlin.collections.ArrayList import kotlin.collections.MutableList @@ -57,7 +73,19 @@ data class PostCreationActivityUiState( val isCarousel: Boolean = true, + val postCreationSendButtonEnabled: Boolean = true, + val newPostDescriptionText: String = "", + val nsfw: Boolean = false, + + val chosenAccount: UserDatabaseEntity? = null, + + val uploadProgressBarVisible: Boolean = false, + val uploadProgress: Int = 0, + val uploadCompletedTextviewVisible: Boolean = false, + val uploadErrorVisible: Boolean = false, + val uploadErrorExplanationText: String = "", + val uploadErrorExplanationVisible: Boolean = false, ) @Parcelize @@ -92,7 +120,10 @@ class PostCreationViewModel(application: Application, clipdata: ClipData? = null PreferenceManager.getDefaultSharedPreferences(application) val initialDescription = sharedPreferences.getString("prefill_description", "") ?: "" - _uiState = MutableStateFlow(PostCreationActivityUiState(newPostDescriptionText = existingDescription ?: initialDescription)) + _uiState = MutableStateFlow(PostCreationActivityUiState( + newPostDescriptionText = existingDescription ?: initialDescription, + nsfw = existingNSFW + )) } val uiState: StateFlow = _uiState @@ -169,7 +200,7 @@ class PostCreationViewModel(application: Application, clipdata: ClipData? = null val type = uri.getMimeType(getApplication().contentResolver) val isVideo = type.startsWith("video/") - if(isVideo && !instance!!.videoEnabled){ + if (isVideo && !instance!!.videoEnabled) { _uiState.update { currentUiState -> currentUiState.copy(userMessage = getApplication().getString(R.string.video_not_supported)) } @@ -203,17 +234,6 @@ class PostCreationViewModel(application: Application, clipdata: ClipData? = null photoData.value = photoData.value } - /** - * Next step - */ - fun nextStep(context: Context) { - val intent = Intent(context, PostSubmissionActivity::class.java) - intent.putExtra(PostSubmissionActivity.PHOTO_DATA, getPhotoData().value?.let { ArrayList(it) }) - intent.putExtra(PostSubmissionActivity.PICTURE_DESCRIPTION, existingDescription) - intent.putExtra(PostSubmissionActivity.POST_NSFW, existingNSFW) - ContextCompat.startActivity(context, intent, null) - } - fun modifyAt(position: Int, data: Intent): Unit? { val result: PhotoData = photoData.value?.getOrNull(position)?.run { if (video) { @@ -262,7 +282,7 @@ class PostCreationViewModel(application: Application, clipdata: ClipData? = null private fun videoEncodeProgress(originalUri: Uri, progress: Int, firstPass: Boolean, outputVideoPath: Uri?, error: Boolean){ photoData.value?.indexOfFirst { it.imageUri == originalUri }?.let { position -> - if(outputVideoPath != null){ + if (outputVideoPath != null) { // If outputVideoPath is not null, it means the video is done and we can change Uris val (size, _) = getSizeAndVideoValidate(outputVideoPath, position) @@ -310,7 +330,7 @@ class PostCreationViewModel(application: Application, clipdata: ClipData? = null } } - fun registerNewFFmpegSession(position: Uri, sessionId: Long) { + private fun registerNewFFmpegSession(position: Uri, sessionId: Long) { sessionMap[position] = sessionId } @@ -321,6 +341,209 @@ class PostCreationViewModel(application: Application, clipdata: ClipData? = null ) } } + + fun resetUploadStatus() { + photoData.value = photoData.value?.map { it.copy(uploadId = null, progress = null) }?.toMutableList() + } + + /** + * Uploads the images that are in the [photoData] array. + * Keeps track of them in the [PhotoData.progress] (for the upload progress), and the + * [PhotoData.uploadId] (for the list of ids of the uploads). + */ + @OptIn(ExperimentalUnsignedTypes::class) + fun upload() { + _uiState.update { currentUiState -> + currentUiState.copy( + postCreationSendButtonEnabled = false, + uploadCompletedTextviewVisible = false, + uploadErrorVisible = false, + uploadProgressBarVisible = true + ) + } + + for (data: PhotoData in getPhotoData().value ?: emptyList()) { + val extension = data.imageUri.fileExtension(getApplication().contentResolver) + + val strippedImage = File.createTempFile("temp_img", ".$extension", getApplication().cacheDir) + + val imageUri = data.imageUri + + val (strippedOrNot, size) = try { + val orientation = ExifInterface(getApplication().contentResolver.openInputStream(imageUri)!!).getAttributeInt( + ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL) + + stripMetadata(imageUri, strippedImage, getApplication().contentResolver) + + // Restore EXIF orientation + val exifInterface = ExifInterface(strippedImage) + exifInterface.setAttribute(ExifInterface.TAG_ORIENTATION, orientation.toString()) + exifInterface.saveAttributes() + + Pair(strippedImage.inputStream(), strippedImage.length()) + } catch (e: UnsupportedFileFormatException){ + strippedImage.delete() + if(imageUri != data.imageUri) File(URI(imageUri.toString())).delete() + val imageInputStream = try { + getApplication().contentResolver.openInputStream(imageUri)!! + } catch (e: FileNotFoundException){ + _uiState.update { currentUiState -> + currentUiState.copy( + userMessage = getApplication().getString(R.string.file_not_found, + data.imageUri) + ) + } + return + } + Pair(imageInputStream, data.size) + } catch (e: IOException){ + strippedImage.delete() + if(imageUri != data.imageUri) File(URI(imageUri.toString())).delete() + _uiState.update { currentUiState -> + currentUiState.copy( + userMessage = getApplication().getString(R.string.file_not_found, + data.imageUri) + ) + } + return + } + + val type = data.imageUri.getMimeType(getApplication().contentResolver) + val imagePart = ProgressRequestBody(strippedOrNot, size, type) + val requestBody = MultipartBody.Builder() + .setType(MultipartBody.FORM) + .addFormDataPart("file", System.currentTimeMillis().toString(), imagePart) + .build() + + val sub = imagePart.progressSubject + .subscribeOn(Schedulers.io()) + .subscribe { percentage -> + data.progress = percentage.toInt() + _uiState.update { currentUiState -> + currentUiState.copy( + uploadProgress = getPhotoData().value!!.sumOf { it.progress ?: 0 } / getPhotoData().value!!.size + ) + } + } + + var postSub: Disposable? = null + + val description = data.imageDescription?.let { MultipartBody.Part.createFormData("description", it) } + + // Ugly temporary account switching, but it works well enough for now + val api = uiState.value.chosenAccount?.let { + apiHolder.setToCurrentUser(it) + } ?: apiHolder.api ?: apiHolder.setToCurrentUser() + + val inter = api.mediaUpload(description, requestBody.parts[0]) + + apiHolder.api = null + postSub = inter + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + { attachment: Attachment -> + data.progress = 0 + data.uploadId = attachment.id!! + }, + { e: Throwable -> + _uiState.update { currentUiState -> + currentUiState.copy( + uploadErrorVisible = true, + uploadErrorExplanationText = if(e is HttpException){ + getApplication().getString(R.string.upload_error, e.code()) + } else "", + uploadErrorExplanationVisible = e is HttpException, + ) + } + strippedImage.delete() + if(imageUri != data.imageUri) File(URI(imageUri.toString())).delete() + e.printStackTrace() + postSub?.dispose() + sub.dispose() + }, + { + strippedImage.delete() + if(imageUri != data.imageUri) File(URI(imageUri.toString())).delete() + data.progress = 100 + if (getPhotoData().value!!.all { it.progress == 100 && it.uploadId != null }) { + _uiState.update { currentUiState -> + currentUiState.copy( + uploadProgressBarVisible = false, + uploadCompletedTextviewVisible = true + ) + } + post() + } + postSub?.dispose() + sub.dispose() + } + ) + } + } + + private fun post() { + val description = uiState.value.newPostDescriptionText + + // TODO: investigate why this works but booleans don't + val nsfw = if (uiState.value.nsfw) 1 else 0 + + _uiState.update { currentUiState -> + currentUiState.copy( + postCreationSendButtonEnabled = false + ) + } + viewModelScope.launch { + try { + //Ugly temporary account switching, but it works well enough for now + val api = uiState.value.chosenAccount?.let { + apiHolder.setToCurrentUser(it) + } ?: apiHolder.api ?: apiHolder.setToCurrentUser() + + api.postStatus( + statusText = description, + media_ids = getPhotoData().value!!.mapNotNull { it.uploadId }.toList(), + sensitive = nsfw + ) + Toast.makeText(getApplication(), getApplication().getString(R.string.upload_post_success), + Toast.LENGTH_SHORT).show() + val intent = Intent(getApplication(), MainActivity::class.java) + intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + //TODO make the activity launch this instead (and surrounding toasts too) + getApplication().startActivity(intent) + } catch (exception: IOException) { + Toast.makeText(getApplication(), getApplication().getString(R.string.upload_post_error), + Toast.LENGTH_SHORT).show() + Log.e(TAG, exception.toString()) + _uiState.update { currentUiState -> + currentUiState.copy( + postCreationSendButtonEnabled = true + ) + } + } catch (exception: HttpException) { + Toast.makeText(getApplication(), getApplication().getString(R.string.upload_post_failed), + Toast.LENGTH_SHORT).show() + Log.e(TAG, exception.response().toString() + exception.message().toString()) + _uiState.update { currentUiState -> + currentUiState.copy( + postCreationSendButtonEnabled = true + ) + } + } finally { + apiHolder.api = null + } + } + } + + fun newPostDescriptionChanged(text: Editable?) { + _uiState.update { it.copy(newPostDescriptionText = text.toString()) } + } + + fun updateNSFW(checked: Boolean) { _uiState.update { it.copy(nsfw = checked) } } + + fun chooseAccount(which: UserDatabaseEntity) { + _uiState.update { it.copy(chosenAccount = which) } + } } class PostCreationViewModelFactory(val application: Application, val clipdata: ClipData, val instance: InstanceDatabaseEntity, val existingDescription: String?, val existingNSFW: Boolean) : ViewModelProvider.Factory { diff --git a/app/src/main/java/org/pixeldroid/app/postCreation/PostSubmissionActivity.kt b/app/src/main/java/org/pixeldroid/app/postCreation/PostSubmissionActivity.kt deleted file mode 100644 index 76ce02f3..00000000 --- a/app/src/main/java/org/pixeldroid/app/postCreation/PostSubmissionActivity.kt +++ /dev/null @@ -1,193 +0,0 @@ -package org.pixeldroid.app.postCreation - -import android.app.AlertDialog -import android.os.Bundle -import android.view.Menu -import android.view.MenuItem -import android.view.View -import android.view.View.GONE -import android.view.View.INVISIBLE -import android.view.View.VISIBLE -import androidx.activity.viewModels -import androidx.core.widget.doAfterTextChanged -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle -import kotlinx.coroutines.launch -import org.pixeldroid.app.R -import org.pixeldroid.app.databinding.ActivityPostSubmissionBinding -import org.pixeldroid.app.postCreation.PostCreationActivity.Companion.TEMP_FILES -import org.pixeldroid.app.utils.BaseThemedWithoutBarActivity -import org.pixeldroid.app.utils.db.entities.InstanceDatabaseEntity -import org.pixeldroid.app.utils.db.entities.UserDatabaseEntity -import org.pixeldroid.app.utils.setSquareImageFromURL -import java.io.File - - -class PostSubmissionActivity : BaseThemedWithoutBarActivity() { - - companion object { - internal const val PICTURE_DESCRIPTION = "picture_description" - internal const val PHOTO_DATA = "photo_data" - internal const val POST_NSFW = "post_nsfw" -} - - private lateinit var accounts: List - private var selectedAccount: Int = -1 - private lateinit var menu: Menu - private var user: UserDatabaseEntity? = null - private lateinit var instance: InstanceDatabaseEntity - - private lateinit var binding: ActivityPostSubmissionBinding - - private lateinit var model: PostSubmissionViewModel - - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - binding = ActivityPostSubmissionBinding.inflate(layoutInflater) - setContentView(binding.root) - - supportActionBar?.setDisplayHomeAsUpEnabled(true) - supportActionBar?.setTitle(R.string.add_details) - - user = db.userDao().getActiveUser() - accounts = db.userDao().getAll() - - instance = user?.run { - db.instanceDao().getAll().first { instanceDatabaseEntity -> - instanceDatabaseEntity.uri.contains(instance_uri) - } - } ?: InstanceDatabaseEntity("", "") - - val photoData = intent.getParcelableArrayListExtra(PHOTO_DATA) as ArrayList? - - val _model: PostSubmissionViewModel by viewModels { - PostSubmissionViewModelFactory( - application, - photoData!!, - intent.getStringExtra(PICTURE_DESCRIPTION) - ) - } - model = _model - - val sensitive = intent.getBooleanExtra(POST_NSFW, false) - model.updateNSFW(sensitive) - binding.nsfwSwitch.isChecked = sensitive - - lifecycleScope.launch { - repeatOnLifecycle(Lifecycle.State.STARTED) { - model.uiState.collect { uiState -> - uiState.userMessage?.let { - AlertDialog.Builder(binding.root.context).apply { - setMessage(it) - setNegativeButton(android.R.string.ok) { _, _ -> } - }.show() - - // Notify the ViewModel the message is displayed - model.userMessageShown() - } - enableButton(uiState.postCreationSendButtonEnabled) - binding.uploadProgressBar.visibility = - if (uiState.uploadProgressBarVisible) VISIBLE else INVISIBLE - binding.uploadProgressBar.progress = uiState.uploadProgress - binding.uploadCompletedTextview.visibility = - if (uiState.uploadCompletedTextviewVisible) VISIBLE else INVISIBLE - binding.uploadError.visibility = - if (uiState.uploadErrorVisible) VISIBLE else INVISIBLE - binding.uploadErrorTextExplanation.visibility = - if (uiState.uploadErrorExplanationVisible) VISIBLE else INVISIBLE - - selectedAccount = accounts.indexOf(uiState.chosenAccount) - - binding.uploadErrorTextExplanation.text = uiState.uploadErrorExplanationText - } - } - } - binding.newPostDescriptionInputField.doAfterTextChanged { - model.newPostDescriptionChanged(binding.newPostDescriptionInputField.text) - } - - binding.nsfwSwitch.setOnCheckedChangeListener { _, isChecked -> - model.updateNSFW(isChecked) - } - - val existingDescription: String? = intent.getStringExtra(PICTURE_DESCRIPTION) - - binding.newPostDescriptionInputField.setText( - // Set description from redraft if any, otherwise from the template - existingDescription ?: model.uiState.value.newPostDescriptionText - ) - - binding.postTextInputLayout.counterMaxLength = instance.maxStatusChars - - setSquareImageFromURL(View(applicationContext), photoData!![0].imageUri.toString(), binding.postPreview) - // get the description and send the post - binding.postCreationSendButton.setOnClickListener { - if (validatePost()) model.upload() - } - - // Button to retry image upload when it fails - binding.retryUploadButton.setOnClickListener { - model.resetUploadStatus() - model.upload() - } - - // Clean up temporary files, if any - val tempFiles = intent.getStringArrayExtra(TEMP_FILES) - tempFiles?.asList()?.forEach { - val file = File(binding.root.context.cacheDir, it) - model.trackTempFile(file) - } - } - - override fun onCreateOptionsMenu(newMenu: Menu): Boolean { - menuInflater.inflate(R.menu.post_submission_account_menu, newMenu) - menu = newMenu - return true - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - when (item.itemId){ - R.id.action_switch_accounts -> { - AlertDialog.Builder(this).apply { - setIcon(R.drawable.material_drawer_ico_account) - setTitle(R.string.switch_accounts) - setSingleChoiceItems(accounts.map { it.username + " (${it.fullHandle})" }.toTypedArray(), selectedAccount) { dialog, which -> - if(selectedAccount != which){ - model.chooseAccount(accounts[which]) - } - dialog.dismiss() - } - setNegativeButton(android.R.string.cancel) { _, _ -> } - }.show() - return true - } - } - return super.onOptionsItemSelected(item) - } - - private fun validatePost(): Boolean { - binding.postTextInputLayout.run { - val content = editText?.length() ?: 0 - if (content > counterMaxLength) { - // error, too many characters - error = resources.getQuantityString(R.plurals.description_max_characters, counterMaxLength, counterMaxLength) - return false - } - } - return true - } - - private fun enableButton(enable: Boolean = true){ - binding.postCreationSendButton.isEnabled = enable - if(enable){ - binding.postingProgressBar.visibility = GONE - binding.postCreationSendButton.visibility = VISIBLE - } else { - binding.postingProgressBar.visibility = VISIBLE - binding.postCreationSendButton.visibility = GONE - } - - } -} \ No newline at end of file diff --git a/app/src/main/java/org/pixeldroid/app/postCreation/PostSubmissionFragment.kt b/app/src/main/java/org/pixeldroid/app/postCreation/PostSubmissionFragment.kt new file mode 100644 index 00000000..371f41e3 --- /dev/null +++ b/app/src/main/java/org/pixeldroid/app/postCreation/PostSubmissionFragment.kt @@ -0,0 +1,194 @@ +package org.pixeldroid.app.postCreation + +import android.app.AlertDialog +import android.os.Bundle +import android.view.LayoutInflater +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem +import android.view.View +import android.view.ViewGroup +import androidx.activity.OnBackPressedCallback +import androidx.core.view.MenuProvider +import androidx.core.widget.doAfterTextChanged +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import androidx.navigation.fragment.findNavController +import androidx.navigation.ui.setupWithNavController +import kotlinx.coroutines.launch +import org.pixeldroid.app.R +import org.pixeldroid.app.databinding.FragmentPostSubmissionBinding +import org.pixeldroid.app.utils.BaseFragment +import org.pixeldroid.app.utils.db.entities.InstanceDatabaseEntity +import org.pixeldroid.app.utils.db.entities.UserDatabaseEntity +import org.pixeldroid.app.utils.setSquareImageFromURL + + +class PostSubmissionFragment : BaseFragment() { + + private lateinit var accounts: List + private var selectedAccount: Int = -1 +// private lateinit var menu: Menu + + private var user: UserDatabaseEntity? = null + private lateinit var instance: InstanceDatabaseEntity + + private lateinit var binding: FragmentPostSubmissionBinding + private lateinit var model: PostCreationViewModel + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + super.onCreateView(inflater, container, savedInstanceState) + + // Inflate the layout for this fragment + binding = FragmentPostSubmissionBinding.inflate(layoutInflater) +// setHasOptionsMenu(true) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.topBar.setupWithNavController(findNavController()) + + user = db.userDao().getActiveUser() + accounts = db.userDao().getAll() + + instance = user?.run { + db.instanceDao().getAll().first { instanceDatabaseEntity -> + instanceDatabaseEntity.uri.contains(instance_uri) + } + } ?: InstanceDatabaseEntity("", "") + + val _model: PostCreationViewModel by activityViewModels { + PostCreationViewModelFactory( + requireActivity().application, + requireActivity().intent.clipData!!, + instance, + requireActivity().intent.getStringExtra(PostCreationActivity.PICTURE_DESCRIPTION), + requireActivity().intent.getBooleanExtra(PostCreationActivity.POST_NSFW, false) + ) + } + model = _model + + // Display the values from the view model + binding.nsfwSwitch.isChecked = model.uiState.value.nsfw + binding.newPostDescriptionInputField.setText(model.uiState.value.newPostDescriptionText) + + lifecycleScope.launch { + viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { + model.uiState.collect { uiState -> + uiState.userMessage?.let { + AlertDialog.Builder(binding.root.context).apply { + setMessage(it) + setNegativeButton(android.R.string.ok) { _, _ -> } + }.show() + + // Notify the ViewModel the message is displayed + model.userMessageShown() + } + enableButton(uiState.postCreationSendButtonEnabled) + binding.uploadProgressBar.visibility = + if (uiState.uploadProgressBarVisible) View.VISIBLE else View.INVISIBLE + binding.uploadProgressBar.progress = uiState.uploadProgress + binding.uploadCompletedTextview.visibility = + if (uiState.uploadCompletedTextviewVisible) View.VISIBLE else View.INVISIBLE + binding.uploadError.visibility = + if (uiState.uploadErrorVisible) View.VISIBLE else View.INVISIBLE + binding.uploadErrorTextExplanation.visibility = + if (uiState.uploadErrorExplanationVisible) View.VISIBLE else View.INVISIBLE + + selectedAccount = accounts.indexOf(uiState.chosenAccount) + + binding.uploadErrorTextExplanation.text = uiState.uploadErrorExplanationText + } + } + } + + binding.newPostDescriptionInputField.doAfterTextChanged { + model.newPostDescriptionChanged(binding.newPostDescriptionInputField.text) + } + + binding.nsfwSwitch.setOnCheckedChangeListener { _, isChecked -> + model.updateNSFW(isChecked) + } + + binding.postTextInputLayout.counterMaxLength = instance.maxStatusChars + + setSquareImageFromURL(View(requireActivity()), model.getPhotoData()!!.value?.get(0)?.imageUri.toString(), binding.postPreview) + + // Get the description and send the post + binding.postCreationSendButton.setOnClickListener { + if (validatePost()) model.upload() + } + + // Button to retry image upload when it fails + binding.retryUploadButton.setOnClickListener { + model.resetUploadStatus() + model.upload() + } + + // Handle back pressed button + requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + findNavController().navigate(R.id.action_postSubmissionFragment_to_postCreationFragment) + } + }) + + binding.topBar.addMenuProvider(object: MenuProvider { + override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { + // Add menu items here + menuInflater.inflate(R.menu.post_submission_account_menu, menu) + } + + override fun onMenuItemSelected(menuItem: MenuItem): Boolean { + // Handle the menu selection + return when (menuItem.itemId) { + R.id.action_switch_accounts -> { + AlertDialog.Builder(requireActivity()).apply { + setIcon(R.drawable.switch_account) + setTitle(R.string.switch_accounts) + setSingleChoiceItems(accounts.map { it.username + " (${it.fullHandle})" }.toTypedArray(), selectedAccount) { dialog, which -> + if (selectedAccount != which) { + model.chooseAccount(accounts[which]) + } + dialog.dismiss() + } + setNegativeButton(android.R.string.cancel) { _, _ -> } + }.show() + return true + } + else -> false + } + } + }, viewLifecycleOwner, Lifecycle.State.RESUMED) + } + + private fun validatePost(): Boolean { + binding.postTextInputLayout.run { + val content = editText?.length() ?: 0 + if (content > counterMaxLength) { + // error, too many characters + error = resources.getQuantityString(R.plurals.description_max_characters, counterMaxLength, counterMaxLength) + return false + } + } + return true + } + + private fun enableButton(enable: Boolean = true){ + binding.postCreationSendButton.isEnabled = enable + if(enable){ + binding.postingProgressBar.visibility = View.GONE + binding.postCreationSendButton.visibility = View.VISIBLE + } else { + binding.postingProgressBar.visibility = View.VISIBLE + binding.postCreationSendButton.visibility = View.GONE + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/pixeldroid/app/postCreation/camera/CameraFragment.kt b/app/src/main/java/org/pixeldroid/app/postCreation/camera/CameraFragment.kt index 51bf07b0..715c4ea1 100644 --- a/app/src/main/java/org/pixeldroid/app/postCreation/camera/CameraFragment.kt +++ b/app/src/main/java/org/pixeldroid/app/postCreation/camera/CameraFragment.kt @@ -81,6 +81,7 @@ class CameraFragment : BaseFragment() { container: ViewGroup?, savedInstanceState: Bundle? ): View { + super.onCreateView(inflater, container, savedInstanceState) inActivity = arguments?.getBoolean("CameraActivity") ?: false binding = FragmentCameraBinding.inflate(layoutInflater) diff --git a/app/src/main/res/drawable/switch_account.xml b/app/src/main/res/drawable/switch_account.xml new file mode 100644 index 00000000..ace366b2 --- /dev/null +++ b/app/src/main/res/drawable/switch_account.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/layout/activity_post_creation.xml b/app/src/main/res/layout/activity_post_creation.xml index 57d5def9..98f621cc 100644 --- a/app/src/main/res/layout/activity_post_creation.xml +++ b/app/src/main/res/layout/activity_post_creation.xml @@ -6,96 +6,16 @@ android:layout_height="match_parent" tools:context=".postCreation.PostCreationActivity"> - - - - -