Tusky-App-Android/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftHelper.kt

210 lines
6.9 KiB
Kotlin
Raw Normal View History

/* Copyright 2021 Tusky Contributors
*
* This file is a part of Tusky.
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky.components.drafts
import android.content.Context
import android.net.Uri
import android.util.Log
import android.webkit.MimeTypeMap
import androidx.core.content.FileProvider
import androidx.core.net.toUri
import com.keylesspalace.tusky.BuildConfig
import com.keylesspalace.tusky.db.AppDatabase
import com.keylesspalace.tusky.db.DraftAttachment
import com.keylesspalace.tusky.db.DraftEntity
Add UI for image-attachment "focus" (#2620) * Attempt-zero implementation of a "focus" feature for image attachments. Choose "Set focus" in the attachment menu, tap once to select focus point (no visual feedback currently), tap "OK". Works in tests. * Remove code duplication between 'update description' and 'update focus' * Fix ktlint/bitrise failures * Make updateMediaItem private * When focus is set on a post attachment the preview focuses correctly. ProgressImageView now inherits from MediaPreviewImageView. * Replace use of PointF for Focus where focus is represented, fix ktlint * Substitute 'focus' for 'focus point' in strings * First attempt draw focus point. Only updates on initial load. Modeled on code from RoundedCorners builtin from Glide * Redraw focus after each tap * Dark curtain where focus isn't (now looks like mastosoc) * Correct ktlint for FocusDialog * draft: switch to overlay for focus indicator * Draw focus circle, but ImageView and FocusIndicatorView seem to share a single canvas * Switch focus circle to path approach * Correctly scale, save and load focuses. Clamp to visible area. Focus editor looks and feels right * ktlint fixes and comments * Focus indicator drawing should use device-independent pixels * Shrink focus window when it gets unattractively tall (no linting, misbehaves on wide aspect ratio screens) * Correct max-height behavior for screens in landscape mode * Focus attachment result is are flipped on x axis; fix this * Correctly thread focus through on scheduled posts, redrafted posts, and drafts (but draft focus is lost on post) * More focus ktlint fixes * Fix specific case where a draft is given a focus, then deleted, then posted in that order * Fix accidental file change in focus PR * ktLint fix * Fix property style warnings in focus * Fix remaining style warnings from focus PR Co-authored-by: Conny Duck <k.pozniak@gmx.at>
2022-09-21 20:28:06 +02:00
import com.keylesspalace.tusky.entity.Attachment
import com.keylesspalace.tusky.entity.NewPoll
import com.keylesspalace.tusky.entity.Status
import com.keylesspalace.tusky.util.copyToFile
import java.io.File
import java.io.IOException
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import javax.inject.Inject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import okhttp3.Request
import okio.buffer
import okio.sink
class DraftHelper @Inject constructor(
2021-06-28 22:04:34 +02:00
val context: Context,
private val okHttpClient: OkHttpClient,
2021-06-28 22:04:34 +02:00
db: AppDatabase
) {
private val draftDao = db.draftDao()
suspend fun saveDraft(
2021-06-28 22:04:34 +02:00
draftId: Int,
accountId: Long,
inReplyToId: String?,
content: String?,
contentWarning: String?,
sensitive: Boolean,
visibility: Status.Visibility,
mediaUris: List<String>,
mediaDescriptions: List<String?>,
Add UI for image-attachment "focus" (#2620) * Attempt-zero implementation of a "focus" feature for image attachments. Choose "Set focus" in the attachment menu, tap once to select focus point (no visual feedback currently), tap "OK". Works in tests. * Remove code duplication between 'update description' and 'update focus' * Fix ktlint/bitrise failures * Make updateMediaItem private * When focus is set on a post attachment the preview focuses correctly. ProgressImageView now inherits from MediaPreviewImageView. * Replace use of PointF for Focus where focus is represented, fix ktlint * Substitute 'focus' for 'focus point' in strings * First attempt draw focus point. Only updates on initial load. Modeled on code from RoundedCorners builtin from Glide * Redraw focus after each tap * Dark curtain where focus isn't (now looks like mastosoc) * Correct ktlint for FocusDialog * draft: switch to overlay for focus indicator * Draw focus circle, but ImageView and FocusIndicatorView seem to share a single canvas * Switch focus circle to path approach * Correctly scale, save and load focuses. Clamp to visible area. Focus editor looks and feels right * ktlint fixes and comments * Focus indicator drawing should use device-independent pixels * Shrink focus window when it gets unattractively tall (no linting, misbehaves on wide aspect ratio screens) * Correct max-height behavior for screens in landscape mode * Focus attachment result is are flipped on x axis; fix this * Correctly thread focus through on scheduled posts, redrafted posts, and drafts (but draft focus is lost on post) * More focus ktlint fixes * Fix specific case where a draft is given a focus, then deleted, then posted in that order * Fix accidental file change in focus PR * ktLint fix * Fix property style warnings in focus * Fix remaining style warnings from focus PR Co-authored-by: Conny Duck <k.pozniak@gmx.at>
2022-09-21 20:28:06 +02:00
mediaFocus: List<Attachment.Focus?>,
2021-06-28 22:04:34 +02:00
poll: NewPoll?,
failedToSend: Boolean,
failedToSendAlert: Boolean,
scheduledAt: String?,
language: String?,
statusId: String?
) = withContext(Dispatchers.IO) {
2021-06-28 22:04:34 +02:00
val externalFilesDir = context.getExternalFilesDir("Tusky")
2021-06-28 22:04:34 +02:00
if (externalFilesDir == null || !(externalFilesDir.exists())) {
Log.e("DraftHelper", "Error obtaining directory to save media.")
throw Exception()
}
2021-06-28 22:04:34 +02:00
val draftDirectory = File(externalFilesDir, "Drafts")
2021-06-28 22:04:34 +02:00
if (!draftDirectory.exists()) {
draftDirectory.mkdir()
}
2021-06-28 22:04:34 +02:00
val uris = mediaUris.map { uriString ->
uriString.toUri()
}.mapIndexedNotNull { index, uri ->
if (uri.isInFolder(draftDirectory)) {
2021-06-28 22:04:34 +02:00
uri
} else {
uri.copyToFolder(draftDirectory, index)
}
2021-06-28 22:04:34 +02:00
}
2021-06-28 22:04:34 +02:00
val types = uris.map { uri ->
val mimeType = context.contentResolver.getType(uri)
when (mimeType?.substring(0, mimeType.indexOf('/'))) {
"video" -> DraftAttachment.Type.VIDEO
"image" -> DraftAttachment.Type.IMAGE
"audio" -> DraftAttachment.Type.AUDIO
else -> throw IllegalStateException("unknown media type")
}
2021-06-28 22:04:34 +02:00
}
2021-06-28 22:04:34 +02:00
val attachments: MutableList<DraftAttachment> = mutableListOf()
for (i in mediaUris.indices) {
attachments.add(
DraftAttachment(
uriString = uris[i].toString(),
description = mediaDescriptions[i],
Add UI for image-attachment "focus" (#2620) * Attempt-zero implementation of a "focus" feature for image attachments. Choose "Set focus" in the attachment menu, tap once to select focus point (no visual feedback currently), tap "OK". Works in tests. * Remove code duplication between 'update description' and 'update focus' * Fix ktlint/bitrise failures * Make updateMediaItem private * When focus is set on a post attachment the preview focuses correctly. ProgressImageView now inherits from MediaPreviewImageView. * Replace use of PointF for Focus where focus is represented, fix ktlint * Substitute 'focus' for 'focus point' in strings * First attempt draw focus point. Only updates on initial load. Modeled on code from RoundedCorners builtin from Glide * Redraw focus after each tap * Dark curtain where focus isn't (now looks like mastosoc) * Correct ktlint for FocusDialog * draft: switch to overlay for focus indicator * Draw focus circle, but ImageView and FocusIndicatorView seem to share a single canvas * Switch focus circle to path approach * Correctly scale, save and load focuses. Clamp to visible area. Focus editor looks and feels right * ktlint fixes and comments * Focus indicator drawing should use device-independent pixels * Shrink focus window when it gets unattractively tall (no linting, misbehaves on wide aspect ratio screens) * Correct max-height behavior for screens in landscape mode * Focus attachment result is are flipped on x axis; fix this * Correctly thread focus through on scheduled posts, redrafted posts, and drafts (but draft focus is lost on post) * More focus ktlint fixes * Fix specific case where a draft is given a focus, then deleted, then posted in that order * Fix accidental file change in focus PR * ktLint fix * Fix property style warnings in focus * Fix remaining style warnings from focus PR Co-authored-by: Conny Duck <k.pozniak@gmx.at>
2022-09-21 20:28:06 +02:00
focus = mediaFocus[i],
2021-06-28 22:04:34 +02:00
type = types[i]
)
)
2021-06-28 22:04:34 +02:00
}
2021-06-28 22:04:34 +02:00
val draft = DraftEntity(
id = draftId,
accountId = accountId,
inReplyToId = inReplyToId,
content = content,
contentWarning = contentWarning,
sensitive = sensitive,
visibility = visibility,
attachments = attachments,
poll = poll,
failedToSend = failedToSend,
failedToSendNew = failedToSendAlert,
scheduledAt = scheduledAt,
language = language,
statusId = statusId
2021-06-28 22:04:34 +02:00
)
draftDao.insertOrReplace(draft)
Log.d("DraftHelper", "saved draft to db")
}
suspend fun deleteDraftAndAttachments(draftId: Int) {
draftDao.find(draftId)?.let { draft ->
deleteDraftAndAttachments(draft)
}
}
private suspend fun deleteDraftAndAttachments(draft: DraftEntity) {
deleteAttachments(draft)
draftDao.delete(draft.id)
}
suspend fun deleteAllDraftsAndAttachmentsForAccount(accountId: Long) {
draftDao.loadDrafts(accountId).forEach { draft ->
deleteDraftAndAttachments(draft)
}
}
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.isInFolder(folder: File): Boolean {
val filePath = path ?: return true
return File(filePath).parentFile == folder
}
private fun Uri.copyToFolder(folder: File, index: Int): Uri? {
val contentResolver = context.contentResolver
val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(Date())
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_%d.%s", timeStamp, index, fileExtension)
val file = File(folder, filename)
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 {
this.copyToFile(contentResolver, file)
}
return FileProvider.getUriForFile(
context,
BuildConfig.APPLICATION_ID + ".fileprovider",
file
)
}
2021-06-28 22:04:34 +02:00
}