improve media upload error messages (#2602)
This commit is contained in:
parent
ffb8ba2276
commit
62c4cfde89
|
@ -161,7 +161,7 @@ class ComposeActivity :
|
||||||
val uriNew = result.uriContent
|
val uriNew = result.uriContent
|
||||||
if (result.isSuccessful && uriNew != null) {
|
if (result.isSuccessful && uriNew != null) {
|
||||||
viewModel.cropImageItemOld?.let { itemOld ->
|
viewModel.cropImageItemOld?.let { itemOld ->
|
||||||
val size = getMediaSize(getApplicationContext().getContentResolver(), uriNew)
|
val size = getMediaSize(contentResolver, uriNew)
|
||||||
|
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
viewModel.addMediaToQueue(
|
viewModel.addMediaToQueue(
|
||||||
|
@ -407,9 +407,14 @@ class ComposeActivity :
|
||||||
enableButton(binding.composeAddMediaButton, active, active)
|
enableButton(binding.composeAddMediaButton, active, active)
|
||||||
enablePollButton(media.isNullOrEmpty())
|
enablePollButton(media.isNullOrEmpty())
|
||||||
}.subscribe()
|
}.subscribe()
|
||||||
viewModel.uploadError.observe {
|
viewModel.uploadError.observe { throwable ->
|
||||||
|
Log.w(TAG, "media upload failed", throwable)
|
||||||
|
if (throwable is UploadServerError) {
|
||||||
|
displayTransientError(throwable.errorMessage)
|
||||||
|
} else {
|
||||||
displayTransientError(R.string.error_media_upload_sending)
|
displayTransientError(R.string.error_media_upload_sending)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
viewModel.setupComplete.observe {
|
viewModel.setupComplete.observe {
|
||||||
// Focus may have changed during view model setup, ensure initial focus is on the edit field
|
// Focus may have changed during view model setup, ensure initial focus is on the edit field
|
||||||
binding.composeEditField.requestFocus()
|
binding.composeEditField.requestFocus()
|
||||||
|
@ -553,19 +558,23 @@ class ComposeActivity :
|
||||||
super.onSaveInstanceState(outState)
|
super.onSaveInstanceState(outState)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun displayTransientError(@StringRes stringId: Int) {
|
private fun displayTransientError(errorMessage: String) {
|
||||||
val bar = Snackbar.make(binding.activityCompose, stringId, Snackbar.LENGTH_LONG)
|
val bar = Snackbar.make(binding.activityCompose, errorMessage, Snackbar.LENGTH_LONG)
|
||||||
// necessary so snackbar is shown over everything
|
// necessary so snackbar is shown over everything
|
||||||
bar.view.elevation = resources.getDimension(R.dimen.compose_activity_snackbar_elevation)
|
bar.view.elevation = resources.getDimension(R.dimen.compose_activity_snackbar_elevation)
|
||||||
|
bar.setAnchorView(R.id.composeBottomBar)
|
||||||
bar.show()
|
bar.show()
|
||||||
}
|
}
|
||||||
|
private fun displayTransientError(@StringRes stringId: Int) {
|
||||||
|
displayTransientError(getString(stringId))
|
||||||
|
}
|
||||||
|
|
||||||
private fun toggleHideMedia() {
|
private fun toggleHideMedia() {
|
||||||
this.viewModel.toggleMarkSensitive()
|
this.viewModel.toggleMarkSensitive()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateSensitiveMediaToggle(markMediaSensitive: Boolean, contentWarningShown: Boolean) {
|
private fun updateSensitiveMediaToggle(markMediaSensitive: Boolean, contentWarningShown: Boolean) {
|
||||||
if (viewModel.media.value.isNullOrEmpty()) {
|
if (viewModel.media.value.isEmpty()) {
|
||||||
binding.composeHideMediaButton.hide()
|
binding.composeHideMediaButton.hide()
|
||||||
} else {
|
} else {
|
||||||
binding.composeHideMediaButton.show()
|
binding.composeHideMediaButton.show()
|
||||||
|
@ -904,11 +913,10 @@ class ComposeActivity :
|
||||||
// Currently the only supported lossless format is png.
|
// Currently the only supported lossless format is png.
|
||||||
val mimeType: String? = contentResolver.getType(item.uri)
|
val mimeType: String? = contentResolver.getType(item.uri)
|
||||||
val isPng: Boolean = mimeType != null && mimeType.endsWith("/png")
|
val isPng: Boolean = mimeType != null && mimeType.endsWith("/png")
|
||||||
val context = getApplicationContext()
|
val tempFile = createNewImageFile(this, if (isPng) ".png" else ".jpg")
|
||||||
val tempFile = createNewImageFile(context, if (isPng) ".png" else ".jpg")
|
|
||||||
|
|
||||||
// "Authority" must be the same as the android:authorities string in AndroidManifest.xml
|
// "Authority" must be the same as the android:authorities string in AndroidManifest.xml
|
||||||
val uriNew = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".fileprovider", tempFile)
|
val uriNew = FileProvider.getUriForFile(this, BuildConfig.APPLICATION_ID + ".fileprovider", tempFile)
|
||||||
|
|
||||||
viewModel.cropImageItemOld = item
|
viewModel.cropImageItemOld = item
|
||||||
|
|
||||||
|
|
|
@ -219,7 +219,7 @@ class ComposeViewModel @Inject constructor(
|
||||||
val contentWarningChanged = showContentWarning.value!! &&
|
val contentWarningChanged = showContentWarning.value!! &&
|
||||||
!contentWarning.isNullOrEmpty() &&
|
!contentWarning.isNullOrEmpty() &&
|
||||||
!startingContentWarning.startsWith(contentWarning.toString())
|
!startingContentWarning.startsWith(contentWarning.toString())
|
||||||
val mediaChanged = !media.value.isNullOrEmpty()
|
val mediaChanged = media.value.isNotEmpty()
|
||||||
val pollChanged = poll.value != null
|
val pollChanged = poll.value != null
|
||||||
|
|
||||||
return modifiedInitialState || textChanged || contentWarningChanged || mediaChanged || pollChanged
|
return modifiedInitialState || textChanged || contentWarningChanged || mediaChanged || pollChanged
|
||||||
|
|
|
@ -23,7 +23,7 @@ import android.util.Log
|
||||||
import android.webkit.MimeTypeMap
|
import android.webkit.MimeTypeMap
|
||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.FileProvider
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import at.connyduck.calladapter.networkresult.getOrThrow
|
import at.connyduck.calladapter.networkresult.fold
|
||||||
import com.keylesspalace.tusky.BuildConfig
|
import com.keylesspalace.tusky.BuildConfig
|
||||||
import com.keylesspalace.tusky.R
|
import com.keylesspalace.tusky.R
|
||||||
import com.keylesspalace.tusky.components.compose.ComposeActivity.QueuedMedia
|
import com.keylesspalace.tusky.components.compose.ComposeActivity.QueuedMedia
|
||||||
|
@ -32,6 +32,7 @@ import com.keylesspalace.tusky.network.ProgressRequestBody
|
||||||
import com.keylesspalace.tusky.util.MEDIA_SIZE_UNKNOWN
|
import com.keylesspalace.tusky.util.MEDIA_SIZE_UNKNOWN
|
||||||
import com.keylesspalace.tusky.util.getImageSquarePixels
|
import com.keylesspalace.tusky.util.getImageSquarePixels
|
||||||
import com.keylesspalace.tusky.util.getMediaSize
|
import com.keylesspalace.tusky.util.getMediaSize
|
||||||
|
import com.keylesspalace.tusky.util.getServerErrorMessage
|
||||||
import com.keylesspalace.tusky.util.randomAlphanumericString
|
import com.keylesspalace.tusky.util.randomAlphanumericString
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
@ -73,6 +74,7 @@ class AudioSizeException : Exception()
|
||||||
class VideoSizeException : Exception()
|
class VideoSizeException : Exception()
|
||||||
class MediaTypeException : Exception()
|
class MediaTypeException : Exception()
|
||||||
class CouldNotOpenFileException : Exception()
|
class CouldNotOpenFileException : Exception()
|
||||||
|
class UploadServerError(val errorMessage: String) : Exception()
|
||||||
|
|
||||||
class MediaUploader @Inject constructor(
|
class MediaUploader @Inject constructor(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
|
@ -223,8 +225,16 @@ class MediaUploader @Inject constructor(
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
val result = mediaUploadApi.uploadMedia(body, description).getOrThrow()
|
mediaUploadApi.uploadMedia(body, description).fold({ result ->
|
||||||
send(UploadEvent.FinishedEvent(result.id))
|
send(UploadEvent.FinishedEvent(result.id))
|
||||||
|
}, { throwable ->
|
||||||
|
val errorMessage = throwable.getServerErrorMessage()
|
||||||
|
if (errorMessage == null) {
|
||||||
|
throw throwable
|
||||||
|
} else {
|
||||||
|
throw UploadServerError(errorMessage)
|
||||||
|
}
|
||||||
|
})
|
||||||
awaitClose()
|
awaitClose()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -241,7 +251,7 @@ class MediaUploader @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private companion object {
|
private companion object {
|
||||||
private const val TAG = "MediaUploaderImpl"
|
private const val TAG = "MediaUploader"
|
||||||
private const val STATUS_VIDEO_SIZE_LIMIT = 41943040 // 40MiB
|
private const val STATUS_VIDEO_SIZE_LIMIT = 41943040 // 40MiB
|
||||||
private const val STATUS_AUDIO_SIZE_LIMIT = 41943040 // 40MiB
|
private const val STATUS_AUDIO_SIZE_LIMIT = 41943040 // 40MiB
|
||||||
private const val STATUS_IMAGE_SIZE_LIMIT = 8388608 // 8MiB
|
private const val STATUS_IMAGE_SIZE_LIMIT = 8388608 // 8MiB
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
package com.keylesspalace.tusky.util
|
||||||
|
|
||||||
|
import org.json.JSONException
|
||||||
|
import org.json.JSONObject
|
||||||
|
import retrofit2.HttpException
|
||||||
|
|
||||||
|
/**
|
||||||
|
* checks if this throwable indicates an error causes by a 4xx/5xx server response and
|
||||||
|
* tries to retrieve the error message the server sent
|
||||||
|
* @return the error message, or null if this is no server error or it had no error message
|
||||||
|
*/
|
||||||
|
fun Throwable.getServerErrorMessage(): String? {
|
||||||
|
if (this is HttpException) {
|
||||||
|
val errorResponse = response()?.errorBody()?.string()
|
||||||
|
return if (!errorResponse.isNullOrBlank()) {
|
||||||
|
try {
|
||||||
|
JSONObject(errorResponse).getString("error")
|
||||||
|
} catch (e: JSONException) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
|
@ -32,6 +32,7 @@ import com.keylesspalace.tusky.util.Error
|
||||||
import com.keylesspalace.tusky.util.Loading
|
import com.keylesspalace.tusky.util.Loading
|
||||||
import com.keylesspalace.tusky.util.Resource
|
import com.keylesspalace.tusky.util.Resource
|
||||||
import com.keylesspalace.tusky.util.Success
|
import com.keylesspalace.tusky.util.Success
|
||||||
|
import com.keylesspalace.tusky.util.getServerErrorMessage
|
||||||
import com.keylesspalace.tusky.util.randomAlphanumericString
|
import com.keylesspalace.tusky.util.randomAlphanumericString
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||||
|
@ -39,9 +40,6 @@ import okhttp3.MultipartBody
|
||||||
import okhttp3.RequestBody
|
import okhttp3.RequestBody
|
||||||
import okhttp3.RequestBody.Companion.asRequestBody
|
import okhttp3.RequestBody.Companion.asRequestBody
|
||||||
import okhttp3.RequestBody.Companion.toRequestBody
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
import org.json.JSONException
|
|
||||||
import org.json.JSONObject
|
|
||||||
import retrofit2.HttpException
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -156,21 +154,7 @@ class EditProfileViewModel @Inject constructor(
|
||||||
eventHub.dispatch(ProfileEditedEvent(newProfileData))
|
eventHub.dispatch(ProfileEditedEvent(newProfileData))
|
||||||
},
|
},
|
||||||
{ throwable ->
|
{ throwable ->
|
||||||
if (throwable is HttpException) {
|
saveData.postValue(Error(errorMessage = throwable.getServerErrorMessage()))
|
||||||
val errorResponse = throwable.response()?.errorBody()?.string()
|
|
||||||
val errorMsg = if (!errorResponse.isNullOrBlank()) {
|
|
||||||
try {
|
|
||||||
JSONObject(errorResponse).optString("error", "")
|
|
||||||
} catch (e: JSONException) {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
saveData.postValue(Error(errorMessage = errorMsg))
|
|
||||||
} else {
|
|
||||||
saveData.postValue(Error())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -239,6 +239,7 @@
|
||||||
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior" />
|
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
android:id="@+id/composeBottomBar"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="bottom"
|
android:layout_gravity="bottom"
|
||||||
|
|
Loading…
Reference in New Issue