From 5644a22d38a2c049d00f3652378800f58d6df619 Mon Sep 17 00:00:00 2001 From: Matthieu <24-artectrex@users.noreply.shinice.net> Date: Sun, 30 Oct 2022 23:34:26 +0100 Subject: [PATCH] Refactor working --- .../app/postCreation/PostCreationActivity.kt | 24 +- .../app/postCreation/PostCreationViewModel.kt | 255 ++---------------- .../postCreation/PostSubmissionActivity.kt | 86 +++--- .../postCreation/PostSubmissionViewModel.kt | 177 +++--------- .../pixeldroid/app/utils/api/PixelfedAPI.kt | 24 +- .../res/layout/activity_post_creation.xml | 26 +- .../res/layout/activity_post_submission.xml | 59 ++-- .../res/menu/post_submission_account_menu.xml | 11 + app/src/main/res/values/strings.xml | 5 +- 9 files changed, 169 insertions(+), 498 deletions(-) create mode 100644 app/src/main/res/menu/post_submission_account_menu.xml 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 021ae03a..72f7bbdc 100644 --- a/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationActivity.kt +++ b/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationActivity.kt @@ -13,16 +13,13 @@ import android.util.Log import android.view.View import android.view.View.INVISIBLE import android.view.View.VISIBLE -import android.view.View.GONE import android.widget.Toast import androidx.activity.result.ActivityResult import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.viewModels -import androidx.core.content.ContextCompat import androidx.core.net.toFile import androidx.core.net.toUri -import androidx.core.widget.doAfterTextChanged import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle @@ -33,7 +30,6 @@ import org.pixeldroid.app.R import org.pixeldroid.app.databinding.ActivityPostCreationBinding import org.pixeldroid.app.postCreation.camera.CameraActivity import org.pixeldroid.app.postCreation.carousel.CarouselItem -import org.pixeldroid.app.searchDiscover.TrendingActivity import org.pixeldroid.app.utils.BaseThemedWithoutBarActivity import org.pixeldroid.app.utils.db.entities.InstanceDatabaseEntity import org.pixeldroid.app.utils.db.entities.UserDatabaseEntity @@ -110,10 +106,6 @@ class PostCreationActivity : BaseThemedWithoutBarActivity() { model.userMessageShown() } binding.addPhotoButton.isEnabled = uiState.addPhotoButtonEnabled - enableButton(uiState.postCreationSendButtonEnabled) - binding.uploadProgressBar.visibility = - if (uiState.uploadProgressBarVisible) VISIBLE else INVISIBLE - binding.uploadProgressBar.progress = uiState.uploadProgress binding.removePhotoButton.isEnabled = uiState.removePhotoButtonEnabled binding.editPhotoButton.isEnabled = uiState.editPhotoButtonEnabled binding.toolbarPostCreation.visibility = @@ -136,7 +128,7 @@ class PostCreationActivity : BaseThemedWithoutBarActivity() { // get the description and send the post binding.postCreationSendButton.setOnClickListener { if (validatePost() && model.isNotEmpty()) { - model.upload(it.context, binding.root.context) + model.nextStep(binding.root.context) } } @@ -274,7 +266,7 @@ class PostCreationActivity : BaseThemedWithoutBarActivity() { private fun validatePost(): Boolean { - if(model.getPhotoData().value?.all { it.videoEncodeProgress == null } == false){ + if(model.getPhotoData().value?.all { !it.video || it.videoEncodeComplete } == false){ AlertDialog.Builder(this).apply { setMessage(R.string.still_encoding) setNegativeButton(android.R.string.ok) { _, _ -> } @@ -284,18 +276,6 @@ class PostCreationActivity : BaseThemedWithoutBarActivity() { return true } - private fun enableButton(enable: Boolean = true){ - binding.postCreationSendButton.isEnabled = enable - if(enable){ - binding.submittingProgressBar.visibility = GONE - binding.postCreationSendButton.visibility = VISIBLE - } else { - binding.submittingProgressBar.visibility = VISIBLE - binding.postCreationSendButton.visibility = GONE - } - - } - private val editResultContract: ActivityResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()){ result: ActivityResult? -> if (result?.resultCode == Activity.RESULT_OK && result.data != null) { 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 f1e35b3d..47d54d9c 100644 --- a/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationViewModel.kt +++ b/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationViewModel.kt @@ -7,43 +7,43 @@ import android.content.Intent import android.net.Uri import android.os.Parcelable import android.provider.OpenableColumns -import android.text.Editable -import android.util.Log -import android.widget.Toast import androidx.core.content.ContextCompat import androidx.core.net.toFile import androidx.core.net.toUri -import androidx.exifinterface.media.ExifInterface -import androidx.lifecycle.* +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +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.media_editor.photoEdit.VideoEditActivity -import org.pixeldroid.media_editor.photoEdit.VideoEditActivity.RelativeCropPosition 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.di.PixelfedAPIHolder -import org.pixeldroid.app.utils.fileExtension import org.pixeldroid.app.utils.getMimeType -import retrofit2.HttpException +import org.pixeldroid.media_editor.photoEdit.VideoEditActivity import java.io.File -import java.io.FileNotFoundException -import java.io.IOException -import java.io.Serializable -import java.net.URI import javax.inject.Inject +import kotlin.collections.ArrayList +import kotlin.collections.MutableList +import kotlin.collections.MutableMap +import kotlin.collections.arrayListOf +import kotlin.collections.forEach +import kotlin.collections.get +import kotlin.collections.getOrNull +import kotlin.collections.indexOfFirst +import kotlin.collections.isNotEmpty +import kotlin.collections.mutableListOf +import kotlin.collections.mutableMapOf +import kotlin.collections.plus +import kotlin.collections.set +import kotlin.collections.toMutableList import kotlin.math.ceil @@ -54,18 +54,10 @@ data class PostCreationActivityUiState( val addPhotoButtonEnabled: Boolean = true, val editPhotoButtonEnabled: Boolean = true, val removePhotoButtonEnabled: Boolean = true, - val postCreationSendButtonEnabled: Boolean = true, val isCarousel: Boolean = true, val newPostDescriptionText: String = "", - - val uploadProgressBarVisible: Boolean = false, - val uploadProgress: Int = 0, - val uploadCompletedTextviewVisible: Boolean = false, - val uploadErrorVisible: Boolean = false, - val uploadErrorExplanationText: String = "", - val uploadErrorExplanationVisible: Boolean = false, ) @Parcelize @@ -201,33 +193,6 @@ class PostCreationViewModel(application: Application, clipdata: ClipData? = null photoData.value = photoData.value } - fun resetUploadStatus() { - photoData.value = photoData.value?.map { it.copy(uploadId = null, progress = null) }?.toMutableList() - } - - fun setVideoEncodeAtPosition(uri: Uri, progress: Int?, stabilizationFirstPass: Boolean = false, error: Boolean = false) { - photoData.value?.indexOfFirst { it.imageUri == uri }?.let { position -> - photoData.value?.set(position, - photoData.value!![position].copy( - videoEncodeProgress = progress, - videoEncodeStabilizationFirstPass = stabilizationFirstPass, - videoEncodeError = error, - ) - ) - photoData.value = photoData.value - } - } - - fun setUriAtPosition(uri: Uri, position: Int) { - photoData.value?.set(position, photoData.value!![position].copy(imageUri = uri)) - photoData.value = photoData.value - } - - fun setSizeAtPosition(imageSize: Long, position: Int) { - photoData.value?.set(position, photoData.value!![position].copy(size = imageSize)) - photoData.value = photoData.value - } - fun removeAt(currentPosition: Int) { photoData.value?.removeAt(currentPosition) _uiState.update { @@ -239,183 +204,12 @@ class PostCreationViewModel(application: Application, clipdata: ClipData? = null } /** - * 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). + * Next step */ - @OptIn(ExperimentalUnsignedTypes::class) - fun upload(context: Context, bindingContext: Context) { - _uiState.update { currentUiState -> - currentUiState.copy( - postCreationSendButtonEnabled = false, - addPhotoButtonEnabled = false, - editPhotoButtonEnabled = false, - removePhotoButtonEnabled = false, - uploadCompletedTextviewVisible = false, - uploadErrorVisible = false, - uploadProgressBarVisible = true - ) - } - + fun nextStep(context: Context) { val intent = Intent(context, PostSubmissionActivity::class.java) intent.putExtra(PostSubmissionActivity.PHOTO_DATA, getPhotoData().value?.let { ArrayList(it) }) - ContextCompat.startActivity(bindingContext, intent, null) - - 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) } - - val api = apiHolder.api ?: apiHolder.setToCurrentUser() - val inter = api.mediaUpload(description, requestBody.parts[0]) - - 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 - _uiState.update { currentUiState -> - currentUiState.copy( - postCreationSendButtonEnabled = false - ) - } - viewModelScope.launch { - try { - val api = apiHolder.api ?: apiHolder.setToCurrentUser() - - api.postStatus( - statusText = description, - media_ids = getPhotoData().value!!.mapNotNull { it.uploadId }.toList() - ) - 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 - ) - } - } - } + ContextCompat.startActivity(context, intent, null) } fun modifyAt(position: Int, data: Intent): Unit? { @@ -512,7 +306,6 @@ class PostCreationViewModel(application: Application, clipdata: ClipData? = null tempFiles.forEach { it.delete() } - } fun registerNewFFmpegSession(position: Uri, sessionId: Long) { 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 4b0fddd6..6ead56d7 100644 --- a/app/src/main/java/org/pixeldroid/app/postCreation/PostSubmissionActivity.kt +++ b/app/src/main/java/org/pixeldroid/app/postCreation/PostSubmissionActivity.kt @@ -1,60 +1,39 @@ 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.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 android.view.View.GONE -import android.widget.Toast -import androidx.activity.result.ActivityResult -import androidx.activity.result.ActivityResultLauncher -import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.viewModels -import androidx.core.net.toFile -import androidx.core.net.toUri import androidx.core.widget.doAfterTextChanged import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle -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.ActivityPostCreationBinding import org.pixeldroid.app.databinding.ActivityPostSubmissionBinding -import org.pixeldroid.app.postCreation.camera.CameraActivity -import org.pixeldroid.app.postCreation.carousel.CarouselItem +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.fileExtension -import org.pixeldroid.app.utils.getMimeType import org.pixeldroid.app.utils.setSquareImageFromURL import java.io.File -import java.io.OutputStream -import java.text.SimpleDateFormat -import java.util.* -import kotlin.collections.ArrayList class PostSubmissionActivity : BaseThemedWithoutBarActivity() { companion object { internal const val PICTURE_DESCRIPTION = "picture_description" - internal const val TEMP_FILES = "temp_files" - internal const val POST_REDRAFT = "post_redraft" internal const val PHOTO_DATA = "photo_data" } + 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 @@ -69,8 +48,10 @@ class PostSubmissionActivity : BaseThemedWithoutBarActivity() { 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 -> @@ -83,8 +64,7 @@ class PostSubmissionActivity : BaseThemedWithoutBarActivity() { val _model: PostSubmissionViewModel by viewModels { PostSubmissionViewModelFactory( application, - photoData!!, - instance + photoData!! ) } model = _model @@ -112,6 +92,8 @@ class PostSubmissionActivity : BaseThemedWithoutBarActivity() { binding.uploadErrorTextExplanation.visibility = if (uiState.uploadErrorExplanationVisible) VISIBLE else INVISIBLE + selectedAccount = accounts.indexOf(uiState.chosenAccount) + binding.uploadErrorTextExplanation.text = uiState.uploadErrorExplanationText } } @@ -120,6 +102,10 @@ class PostSubmissionActivity : BaseThemedWithoutBarActivity() { model.newPostDescriptionChanged(binding.newPostDescriptionInputField.text) } + binding.nsfwSwitch.setOnCheckedChangeListener { _, isChecked -> + model.updateNSFW(isChecked) + } + val existingDescription: String? = intent.getStringExtra(PICTURE_DESCRIPTION) binding.newPostDescriptionInputField.setText( @@ -130,8 +116,7 @@ class PostSubmissionActivity : BaseThemedWithoutBarActivity() { binding.postTextInputLayout.counterMaxLength = instance.maxStatusChars - val firstPostImage = photoData!![0] - setSquareImageFromURL(View(applicationContext), firstPostImage.imageUri.toString(), binding.postPreview) + setSquareImageFromURL(View(applicationContext), photoData!![0].imageUri.toString(), binding.postPreview) // get the description and send the post binding.postCreationSendButton.setOnClickListener { if (validatePost()) model.upload() @@ -151,21 +136,30 @@ class PostSubmissionActivity : BaseThemedWithoutBarActivity() { } } - 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() - } - setNegativeButton(android.R.string.cancel) { _, _ -> } - show() + 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 } - } else { - super.onBackPressed() } + return super.onOptionsItemSelected(item) } private fun validatePost(): Boolean { 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 91e9e7be..816046f1 100644 --- a/app/src/main/java/org/pixeldroid/app/postCreation/PostSubmissionViewModel.kt +++ b/app/src/main/java/org/pixeldroid/app/postCreation/PostSubmissionViewModel.kt @@ -1,15 +1,11 @@ package org.pixeldroid.app.postCreation import android.app.Application -import android.content.ClipData import android.content.Intent import android.net.Uri -import android.provider.OpenableColumns 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.* import androidx.preference.PreferenceManager @@ -25,11 +21,9 @@ import kotlinx.coroutines.launch import okhttp3.MultipartBody import org.pixeldroid.app.MainActivity import org.pixeldroid.app.R -import org.pixeldroid.media_editor.photoEdit.VideoEditActivity -import org.pixeldroid.media_editor.photoEdit.VideoEditActivity.RelativeCropPosition 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 @@ -39,7 +33,6 @@ import java.io.FileNotFoundException import java.io.IOException import java.net.URI import javax.inject.Inject -import kotlin.math.ceil // Models the UI state for the PostCreationActivity @@ -49,6 +42,9 @@ data class PostSubmissionActivityUiState( val postCreationSendButtonEnabled: Boolean = true, val newPostDescriptionText: String = "", + val nsfw: Boolean = false, + + val chosenAccount: UserDatabaseEntity? = null, val uploadProgressBarVisible: Boolean = false, val uploadProgress: Int = 0, @@ -56,19 +52,21 @@ data class PostSubmissionActivityUiState( val uploadErrorVisible: Boolean = false, val uploadErrorExplanationText: String = "", val uploadErrorExplanationVisible: Boolean = false, - ) +) -class PostSubmissionViewModel(application: Application, clipdata: ClipData? = null, val instance: InstanceDatabaseEntity? = null) : AndroidViewModel(application) { +class PostSubmissionViewModel(application: Application, photodata: ArrayList? = null) : AndroidViewModel(application) { private val photoData: MutableLiveData> by lazy { MutableLiveData>().also { - it.value = clipdata?.let { it1 -> addPossibleImages(it1, mutableListOf()) } + if (photodata != null) { + it.value = photodata.toMutableList() + } } } @Inject lateinit var apiHolder: PixelfedAPIHolder - private val _uiState: MutableStateFlow + private val _uiState: MutableStateFlow init { (application as PixelDroidApplication).getAppComponent().inject(this) @@ -76,10 +74,10 @@ class PostSubmissionViewModel(application: Application, clipdata: ClipData? = nu PreferenceManager.getDefaultSharedPreferences(application) val initialDescription = sharedPreferences.getString("prefill_description", "") ?: "" - _uiState = MutableStateFlow(PostCreationActivityUiState(newPostDescriptionText = initialDescription)) + _uiState = MutableStateFlow(PostSubmissionActivityUiState(newPostDescriptionText = initialDescription)) } - val uiState: StateFlow = _uiState + val uiState: StateFlow = _uiState // Map photoData indexes to FFmpeg Session IDs private val sessionMap: MutableMap = mutableMapOf() @@ -94,126 +92,10 @@ class PostSubmissionViewModel(application: Application, clipdata: ClipData? = nu fun getPhotoData(): LiveData> = photoData - /** - * Will add as many images as possible to [photoData], from the [clipData], and if - * ([photoData].size + [clipData].itemCount) > [InstanceDatabaseEntity.albumLimit] then it will only add as many images - * as are legal (if any) and a dialog will be shown to the user alerting them of this fact. - */ - fun addPossibleImages(clipData: ClipData, previousList: MutableList? = photoData.value): MutableList { - val dataToAdd: ArrayList = arrayListOf() - var count = clipData.itemCount - if(count + (previousList?.size ?: 0) > instance!!.albumLimit){ - _uiState.update { currentUiState -> - currentUiState.copy(userMessage = getApplication().getString(R.string.total_exceeds_album_limit).format(instance.albumLimit)) - } - count = count.coerceAtMost(instance.albumLimit - (previousList?.size ?: 0)) - } - if (count + (previousList?.size ?: 0) >= instance.albumLimit) { - // Disable buttons to add more images - _uiState.update { currentUiState -> - currentUiState.copy(addPhotoButtonEnabled = false) - } - } - for (i in 0 until count) { - clipData.getItemAt(i).let { - val sizeAndVideoPair: Pair = - getSizeAndVideoValidate(it.uri, (previousList?.size ?: 0) + dataToAdd.size + 1) - dataToAdd.add(PhotoData(imageUri = it.uri, size = sizeAndVideoPair.first, video = sizeAndVideoPair.second, imageDescription = it.text?.toString())) - } - } - return previousList?.plus(dataToAdd)?.toMutableList() ?: mutableListOf() - } - - fun setImages(addPossibleImages: MutableList) { - photoData.value = addPossibleImages - } - - /** - * Returns the size of the file of the Uri, and whether it is a video, - * and opens a dialog in case it is too big or in case the file is unsupported. - */ - fun getSizeAndVideoValidate(uri: Uri, editPosition: Int): Pair { - val size: Long = - if (uri.scheme =="content") { - getApplication().contentResolver.query(uri, null, null, null, null) - ?.use { cursor -> - /* Get the column indexes of the data in the Cursor, - * move to the first row in the Cursor, get the data, - * and display it. - */ - val sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE) - cursor.moveToFirst() - cursor.getLong(sizeIndex) - } ?: 0 - } else { - uri.toFile().length() - } - - val sizeInkBytes = ceil(size.toDouble() / 1000).toLong() - val type = uri.getMimeType(getApplication().contentResolver) - val isVideo = type.startsWith("video/") - - if(isVideo && !instance!!.videoEnabled){ - _uiState.update { currentUiState -> - currentUiState.copy(userMessage = getApplication().getString(R.string.video_not_supported)) - } - } - - if (sizeInkBytes > instance!!.maxPhotoSize || sizeInkBytes > instance.maxVideoSize) { - val maxSize = if (isVideo) instance.maxVideoSize else instance.maxPhotoSize - _uiState.update { currentUiState -> - currentUiState.copy( - userMessage = getApplication().getString(R.string.size_exceeds_instance_limit, editPosition, sizeInkBytes, maxSize) - ) - } - } - return Pair(size, isVideo) - } - - fun isNotEmpty(): Boolean = photoData.value?.isNotEmpty() ?: false - - fun updateDescription(position: Int, description: String) { - photoData.value?.getOrNull(position)?.imageDescription = description - photoData.value = photoData.value - } - fun resetUploadStatus() { photoData.value = photoData.value?.map { it.copy(uploadId = null, progress = null) }?.toMutableList() } - fun setVideoEncodeAtPosition(uri: Uri, progress: Int?, stabilizationFirstPass: Boolean = false, error: Boolean = false) { - photoData.value?.indexOfFirst { it.imageUri == uri }?.let { position -> - photoData.value?.set(position, - photoData.value!![position].copy( - videoEncodeProgress = progress, - videoEncodeStabilizationFirstPass = stabilizationFirstPass, - videoEncodeError = error, - ) - ) - photoData.value = photoData.value - } - } - - fun setUriAtPosition(uri: Uri, position: Int) { - photoData.value?.set(position, photoData.value!![position].copy(imageUri = uri)) - photoData.value = photoData.value - } - - fun setSizeAtPosition(imageSize: Long, position: Int) { - photoData.value?.set(position, photoData.value!![position].copy(size = imageSize)) - photoData.value = photoData.value - } - - fun removeAt(currentPosition: Int) { - photoData.value?.removeAt(currentPosition) - _uiState.update { - it.copy( - addPhotoButtonEnabled = true - ) - } - photoData.value = photoData.value - } - /** * Uploads the images that are in the [photoData] array. * Keeps track of them in the [PhotoData.progress] (for the upload progress), and the @@ -224,9 +106,6 @@ class PostSubmissionViewModel(application: Application, clipdata: ClipData? = nu _uiState.update { currentUiState -> currentUiState.copy( postCreationSendButtonEnabled = false, - addPhotoButtonEnabled = false, - editPhotoButtonEnabled = false, - removePhotoButtonEnabled = false, uploadCompletedTextviewVisible = false, uploadErrorVisible = false, uploadProgressBarVisible = true @@ -300,9 +179,14 @@ class PostSubmissionViewModel(application: Application, clipdata: ClipData? = nu val description = data.imageDescription?.let { MultipartBody.Part.createFormData("description", it) } - val api = apiHolder.api ?: apiHolder.setToCurrentUser() + //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()) @@ -349,6 +233,10 @@ class PostSubmissionViewModel(application: Application, clipdata: ClipData? = nu 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 @@ -356,11 +244,15 @@ class PostSubmissionViewModel(application: Application, clipdata: ClipData? = nu } viewModelScope.launch { try { - val api = apiHolder.api ?: apiHolder.setToCurrentUser() + //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() + media_ids = getPhotoData().value!!.mapNotNull { it.uploadId }.toList(), + sensitive = nsfw ) Toast.makeText(getApplication(), getApplication().getString(R.string.upload_post_success), Toast.LENGTH_SHORT).show() @@ -386,6 +278,8 @@ class PostSubmissionViewModel(application: Application, clipdata: ClipData? = nu postCreationSendButtonEnabled = true ) } + } finally { + apiHolder.api = null } } } @@ -400,16 +294,21 @@ class PostSubmissionViewModel(application: Application, clipdata: ClipData? = nu override fun onCleared() { super.onCleared() - VideoEditActivity.cancelEncoding() tempFiles.forEach { it.delete() } } + + fun updateNSFW(checked: Boolean) { _uiState.update { it.copy(nsfw = checked) } } + + fun chooseAccount(which: UserDatabaseEntity) { + _uiState.update { it.copy(chosenAccount = which) } + } } -class PostSubmissionViewModelFactory(val application: Application, val photoData: ArrayList, val instance: InstanceDatabaseEntity) : ViewModelProvider.Factory { +class PostSubmissionViewModelFactory(val application: Application, val photoData: ArrayList) : ViewModelProvider.Factory { override fun create(modelClass: Class): T { - return modelClass.getConstructor(Application::class.java, ArrayList::class.java, InstanceDatabaseEntity::class.java).newInstance(application, photoData, instance) + return modelClass.getConstructor(Application::class.java, ArrayList::class.java).newInstance(application, photoData) } } \ 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 14b39e8c..5313f130 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 @@ -153,18 +153,18 @@ interface PixelfedAPI { @FormUrlEncoded @POST("/api/v1/statuses") suspend fun postStatus( - @Field("status") statusText : String, - @Field("in_reply_to_id") in_reply_to_id : String? = null, - @Field("media_ids[]") media_ids : List = emptyList(), - @Field("poll[options][]") poll_options : List? = null, - @Field("poll[expires_in]") poll_expires : List? = null, - @Field("poll[multiple]") poll_multiple : List? = null, - @Field("poll[hide_totals]") poll_hideTotals : List? = null, - @Field("sensitive") sensitive : Boolean? = null, - @Field("spoiler_text") spoiler_text : String? = null, - @Field("visibility") visibility : String = "public", - @Field("scheduled_at") scheduled_at : String? = null, - @Field("language") language : String? = null + @Field("status") statusText: String, + @Field("in_reply_to_id") in_reply_to_id: String? = null, + @Field("media_ids[]") media_ids: List = emptyList(), + @Field("poll[options][]") poll_options: List? = null, + @Field("poll[expires_in]") poll_expires: List? = null, + @Field("poll[multiple]") poll_multiple: List? = null, + @Field("poll[hide_totals]") poll_hideTotals: List? = null, + @Field("sensitive") sensitive: Int? = null, + @Field("spoiler_text") spoiler_text: String? = null, + @Field("visibility") visibility: String = "public", + @Field("scheduled_at") scheduled_at: String? = null, + @Field("language") language: String? = null ) : Status @DELETE("/api/v1/statuses/{id}") diff --git a/app/src/main/res/layout/activity_post_creation.xml b/app/src/main/res/layout/activity_post_creation.xml index 05ed23a0..57d5def9 100644 --- a/app/src/main/res/layout/activity_post_creation.xml +++ b/app/src/main/res/layout/activity_post_creation.xml @@ -11,27 +11,16 @@ android:layout_width="match_parent" android:layout_height="0dp" app:showCaption="true" - app:layout_constraintBottom_toBottomOf="@+id/uploadProgressBar" + app:layout_constraintBottom_toTopOf="@+id/buttonConstraints" app:layout_constraintTop_toTopOf="parent"/> - - + app:layout_constraintEnd_toEndOf="parent">