From beaed6b8751e547564e5e40c6cb8f93c7a7df88f Mon Sep 17 00:00:00 2001 From: Konrad Pozniak Date: Mon, 9 May 2022 19:39:43 +0200 Subject: [PATCH] Fix crash when saving redrafted media to drafts (#2502) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix crash when saving draft from redraft * fix crash when saving draft from redraft * replace ... with … --- .../components/compose/ComposeActivity.kt | 15 ++++- .../components/compose/ComposeViewModel.kt | 49 ++++++++------- .../tusky/components/drafts/DraftHelper.kt | 63 ++++++++++++++----- app/src/main/res/values/strings.xml | 1 + 4 files changed, 87 insertions(+), 41 deletions(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt index 32162614..a6e8d677 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt @@ -967,8 +967,19 @@ class ComposeActivity : } private fun saveDraftAndFinish(contentText: String, contentWarning: String) { - viewModel.saveDraft(contentText, contentWarning) - finishWithoutSlideOutAnimation() + lifecycleScope.launch { + val dialog = if (viewModel.shouldShowSaveDraftDialog()) { + ProgressDialog.show( + this@ComposeActivity, null, + getString(R.string.saving_draft), true, false + ) + } else { + null + } + viewModel.saveDraft(contentText, contentWarning) + dialog?.cancel() + finishWithoutSlideOutAnimation() + } } override fun search(token: String): List { diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt index 7b180532..abf2ff42 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt @@ -220,31 +220,36 @@ class ComposeViewModel @Inject constructor( } } - fun saveDraft(content: String, contentWarning: String) { - viewModelScope.launch { - val mediaUris: MutableList = mutableListOf() - val mediaDescriptions: MutableList = mutableListOf() - media.value.forEach { item -> - mediaUris.add(item.uri.toString()) - mediaDescriptions.add(item.description) - } - - draftHelper.saveDraft( - draftId = draftId, - accountId = accountManager.activeAccount?.id!!, - inReplyToId = inReplyToId, - content = content, - contentWarning = contentWarning, - sensitive = markMediaAsSensitive.value!!, - visibility = statusVisibility.value!!, - mediaUris = mediaUris, - mediaDescriptions = mediaDescriptions, - poll = poll.value, - failedToSend = false - ) + fun shouldShowSaveDraftDialog(): Boolean { + // if any of the media files need to be downloaded first it could take a while, so show a loading dialog + return media.value.any { mediaValue -> + mediaValue.uri.scheme == "https" } } + suspend fun saveDraft(content: String, contentWarning: String) { + val mediaUris: MutableList = mutableListOf() + val mediaDescriptions: MutableList = mutableListOf() + media.value.forEach { item -> + mediaUris.add(item.uri.toString()) + mediaDescriptions.add(item.description) + } + + draftHelper.saveDraft( + draftId = draftId, + accountId = accountManager.activeAccount?.id!!, + inReplyToId = inReplyToId, + content = content, + contentWarning = contentWarning, + sensitive = markMediaAsSensitive.value!!, + visibility = statusVisibility.value!!, + mediaUris = mediaUris, + mediaDescriptions = mediaDescriptions, + poll = poll.value, + failedToSend = false + ) + } + /** * Send status to the server. * Uses current state plus provided arguments. diff --git a/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftHelper.kt b/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftHelper.kt index 7511dc3c..a6cd3fcd 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftHelper.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftHelper.kt @@ -30,7 +30,12 @@ import com.keylesspalace.tusky.entity.Status import com.keylesspalace.tusky.util.IOUtils import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext +import okhttp3.OkHttpClient +import okhttp3.Request +import okio.buffer +import okio.sink import java.io.File +import java.io.IOException import java.text.SimpleDateFormat import java.util.Date import java.util.Locale @@ -38,6 +43,7 @@ import javax.inject.Inject class DraftHelper @Inject constructor( val context: Context, + val okHttpClient: OkHttpClient, db: AppDatabase ) { @@ -71,11 +77,11 @@ class DraftHelper @Inject constructor( val uris = mediaUris.map { uriString -> uriString.toUri() - }.map { uri -> - if (uri.isNotInFolder(draftDirectory)) { - uri.copyToFolder(draftDirectory) - } else { + }.mapNotNull { uri -> + if (uri.isInFolder(draftDirectory)) { uri + } else { + uri.copyToFolder(draftDirectory) } } @@ -114,6 +120,7 @@ class DraftHelper @Inject constructor( ) draftDao.insertOrReplace(draft) + Log.d("DraftHelper", "saved draft to db") } suspend fun deleteDraftAndAttachments(draftId: Int) { @@ -133,33 +140,55 @@ class DraftHelper @Inject constructor( } } - suspend fun deleteAttachments(draft: DraftEntity) { - withContext(Dispatchers.IO) { - draft.attachments.forEach { attachment -> - if (context.contentResolver.delete(attachment.uri, null, null) == 0) { - Log.e("DraftHelper", "Did not delete file ${attachment.uriString}") - } + suspend fun deleteAttachments(draft: DraftEntity) = withContext(Dispatchers.IO) { + draft.attachments.forEach { attachment -> + if (context.contentResolver.delete(attachment.uri, null, null) == 0) { + Log.e("DraftHelper", "Did not delete file ${attachment.uriString}") } } } - private fun Uri.isNotInFolder(folder: File): Boolean { + private fun Uri.isInFolder(folder: File): Boolean { val filePath = path ?: return true return File(filePath).parentFile == folder } - private fun Uri.copyToFolder(folder: File): Uri { + private fun Uri.copyToFolder(folder: File): Uri? { val contentResolver = context.contentResolver - val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(Date()) - val mimeType = contentResolver.getType(this) - val map = MimeTypeMap.getSingleton() - val fileExtension = map.getExtensionFromMimeType(mimeType) + val fileExtension = if (scheme == "https") { + lastPathSegment?.substringAfterLast('.', "tmp") + } else { + val mimeType = contentResolver.getType(this) + val map = MimeTypeMap.getSingleton() + map.getExtensionFromMimeType(mimeType) + } val filename = String.format("Tusky_Draft_Media_%s.%s", timeStamp, fileExtension) val file = File(folder, filename) - IOUtils.copyToFile(contentResolver, this, file) + + if (scheme == "https") { + // saving redrafted media + try { + val request = Request.Builder().url(toString()).build() + + val response = okHttpClient.newCall(request).execute() + + val sink = file.sink().buffer() + + response.body?.source()?.use { input -> + sink.use { output -> + output.writeAll(input) + } + } + } catch (ex: IOException) { + Log.w("DraftHelper", "failed to save media", ex) + return null + } + } else { + IOUtils.copyToFile(contentResolver, this, file) + } return FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".fileprovider", file) } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index fc1ed743..d8ce8394 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -640,5 +640,6 @@ Unsubscribe Compose Post + Saving draft…