Fix saving files to Nextcloud

pull/3336/head
Alex Baker 10 months ago
parent 1f659c3dc6
commit 3ada78b0f1

@ -6,12 +6,12 @@ import android.net.Uri
import android.os.Bundle import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import org.tasks.data.entity.Task
import com.todoroo.astrid.service.TaskCreator import com.todoroo.astrid.service.TaskCreator
import com.todoroo.astrid.utility.Constants import com.todoroo.astrid.utility.Constants
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.tasks.analytics.Firebase import org.tasks.analytics.Firebase
import org.tasks.data.entity.Task
import org.tasks.data.entity.TaskAttachment import org.tasks.data.entity.TaskAttachment
import org.tasks.files.FileHelper import org.tasks.files.FileHelper
import org.tasks.intents.TaskIntents import org.tasks.intents.TaskIntents
@ -87,17 +87,17 @@ class ShareLinkActivity : AppCompatActivity() {
startActivity(intent) startActivity(intent)
} }
private fun copyAttachment(intent: Intent): ArrayList<Uri> = private suspend fun copyAttachment(intent: Intent): ArrayList<Uri> =
intent.getParcelableExtra<Uri>(Intent.EXTRA_STREAM) intent.getParcelableExtra<Uri>(Intent.EXTRA_STREAM)
?.let { copyAttachments(listOf(it)) } ?.let { copyAttachments(listOf(it)) }
?: arrayListOf() ?: arrayListOf()
private fun copyMultipleAttachments(intent: Intent): ArrayList<Uri> = private suspend fun copyMultipleAttachments(intent: Intent): ArrayList<Uri> =
intent.getParcelableArrayListExtra<Uri>(Intent.EXTRA_STREAM) intent.getParcelableArrayListExtra<Uri>(Intent.EXTRA_STREAM)
?.let { copyAttachments(it) } ?.let { copyAttachments(it) }
?: arrayListOf() ?: arrayListOf()
private fun copyAttachments(uris: List<Uri>) = private suspend fun copyAttachments(uris: List<Uri>) =
uris uris
.filter { .filter {
it.scheme == ContentResolver.SCHEME_CONTENT it.scheme == ContentResolver.SCHEME_CONTENT

@ -22,9 +22,11 @@ import org.tasks.compose.edit.AttachmentRow
import org.tasks.data.dao.TaskAttachmentDao import org.tasks.data.dao.TaskAttachmentDao
import org.tasks.data.entity.TaskAttachment import org.tasks.data.entity.TaskAttachment
import org.tasks.dialogs.AddAttachmentDialog import org.tasks.dialogs.AddAttachmentDialog
import org.tasks.extensions.Context.takePersistableUriPermission
import org.tasks.files.FileHelper import org.tasks.files.FileHelper
import org.tasks.preferences.Preferences import org.tasks.preferences.Preferences
import org.tasks.ui.TaskEditControlFragment import org.tasks.ui.TaskEditControlFragment
import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
@ -50,13 +52,18 @@ class FilesControlSet : TaskEditControlFragment() {
AttachmentRow( AttachmentRow(
attachments = viewState.attachments, attachments = viewState.attachments,
openAttachment = { openAttachment = {
Timber.d("Clicked open $it")
FileHelper.startActionView( FileHelper.startActionView(
requireActivity(), context,
if (Strings.isNullOrEmpty(it.uri)) null else Uri.parse(it.uri) if (Strings.isNullOrEmpty(it.uri)) null else Uri.parse(it.uri)
) )
}, },
deleteAttachment = { viewModel.setAttachments(viewState.attachments - it) }, deleteAttachment = {
Timber.d("Clicked delete $it")
viewModel.setAttachments(viewState.attachments - it)
},
addAttachment = { addAttachment = {
Timber.d("Add attachment clicked")
AddAttachmentDialog.newAddAttachmentDialog(this@FilesControlSet) AddAttachmentDialog.newAddAttachmentDialog(this@FilesControlSet)
.show(parentFragmentManager, FRAG_TAG_ADD_ATTACHMENT_DIALOG) .show(parentFragmentManager, FRAG_TAG_ADD_ATTACHMENT_DIALOG)
}, },
@ -69,29 +76,44 @@ class FilesControlSet : TaskEditControlFragment() {
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == AddAttachmentDialog.REQUEST_CAMERA || requestCode == AddAttachmentDialog.REQUEST_AUDIO) { if (requestCode == AddAttachmentDialog.REQUEST_CAMERA || requestCode == AddAttachmentDialog.REQUEST_AUDIO) {
if (resultCode == Activity.RESULT_OK) { if (resultCode == Activity.RESULT_OK) {
lifecycleScope.launch {
val uri = data!!.data val uri = data!!.data
copyToAttachmentDirectory(uri) copyToAttachmentDirectory(uri)
FileHelper.delete(requireContext(), uri) FileHelper.delete(requireContext(), uri)
} }
}
} else if (requestCode == AddAttachmentDialog.REQUEST_STORAGE || requestCode == AddAttachmentDialog.REQUEST_GALLERY) { } else if (requestCode == AddAttachmentDialog.REQUEST_STORAGE || requestCode == AddAttachmentDialog.REQUEST_GALLERY) {
if (resultCode == Activity.RESULT_OK) { if (resultCode == Activity.RESULT_OK) {
lifecycleScope.launch {
val clip = data!!.clipData val clip = data!!.clipData
if (clip != null) { if (clip != null) {
for (i in 0 until clip.itemCount) { for (i in 0 until clip.itemCount) {
val item = clip.getItemAt(i) val item = clip.getItemAt(i)
requireContext().takePersistableUriPermission(item.uri)
copyToAttachmentDirectory(item.uri) copyToAttachmentDirectory(item.uri)
} }
} else { } else {
requireContext().takePersistableUriPermission(data.data!!)
copyToAttachmentDirectory(data.data) copyToAttachmentDirectory(data.data)
} }
} }
}
} else { } else {
super.onActivityResult(requestCode, resultCode, data) super.onActivityResult(requestCode, resultCode, data)
} }
} }
private fun copyToAttachmentDirectory(input: Uri?) { private suspend fun copyToAttachmentDirectory(input: Uri?) {
newAttachment(FileHelper.copyToUri(requireContext(), preferences.attachmentsDirectory!!, input!!)) val destination = preferences.attachmentsDirectory ?: return
newAttachment(
if (FileHelper.isInTree(requireContext(), destination, input!!)) {
Timber.d("$input already exists in $destination")
input
} else {
Timber.d("Copying $input to $destination")
FileHelper.copyToUri(requireContext(), destination, input)
}
)
} }
private fun newAttachment(output: Uri) { private fun newAttachment(output: Uri) {

@ -1,80 +0,0 @@
package org.tasks.activities;
import android.annotation.SuppressLint;
import android.content.ContentResolver;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.FileProvider;
import com.todoroo.astrid.utility.Constants;
import org.tasks.files.FileHelper;
import org.tasks.time.DateTime;
import java.io.File;
import java.io.IOException;
public class CameraActivity extends AppCompatActivity {
private static final int REQUEST_CODE_CAMERA = 75;
private static final String EXTRA_URI = "extra_output";
private Uri uri;
@SuppressLint("NewApi")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
uri = savedInstanceState.getParcelable(EXTRA_URI);
} else {
try {
uri =
FileHelper.newFile(
this,
Uri.fromFile(getCacheDir()),
"image/jpeg",
new DateTime().toString("yyyyMMddHHmm"),
".jpeg");
} catch (IOException e) {
throw new RuntimeException(e);
}
if (!uri.getScheme().equals(ContentResolver.SCHEME_FILE)) {
throw new RuntimeException("Invalid Uri");
}
final Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
final Uri shared =
FileProvider.getUriForFile(
this, Constants.FILE_PROVIDER_AUTHORITY, new File(uri.getPath()));
intent.putExtra(MediaStore.EXTRA_OUTPUT, shared);
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
startActivityForResult(intent, REQUEST_CODE_CAMERA);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_CODE_CAMERA) {
if (resultCode == RESULT_OK) {
final Intent intent = new Intent();
intent.setData(uri);
setResult(RESULT_OK, intent);
}
finish();
} else {
super.onActivityResult(requestCode, resultCode, data);
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelable(EXTRA_URI, uri);
}
}

@ -0,0 +1,83 @@
package org.tasks.activities
import android.annotation.SuppressLint
import android.app.Activity
import android.content.ContentResolver
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.provider.MediaStore
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.FileProvider
import androidx.lifecycle.lifecycleScope
import com.todoroo.astrid.utility.Constants
import kotlinx.coroutines.launch
import org.tasks.files.FileHelper.newFile
import org.tasks.time.DateTime
import java.io.File
import java.io.IOException
class CameraActivity : AppCompatActivity() {
private var uri: Uri? = null
@SuppressLint("NewApi")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (savedInstanceState != null) {
uri = savedInstanceState.getParcelable(EXTRA_URI)
} else {
lifecycleScope.launch {
try {
uri =
newFile(
this@CameraActivity,
Uri.fromFile(cacheDir),
"image/jpeg",
DateTime().toString("yyyyMMddHHmm"),
".jpeg"
)
} catch (e: IOException) {
throw RuntimeException(e)
}
if (uri!!.scheme != ContentResolver.SCHEME_FILE) {
throw RuntimeException("Invalid Uri")
}
val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
val shared =
FileProvider.getUriForFile(
this@CameraActivity, Constants.FILE_PROVIDER_AUTHORITY, File(
uri!!.path!!
)
)
intent.putExtra(MediaStore.EXTRA_OUTPUT, shared)
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
startActivityForResult(intent, REQUEST_CODE_CAMERA)
}
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == REQUEST_CODE_CAMERA) {
if (resultCode == Activity.RESULT_OK) {
val intent = Intent()
intent.setData(uri)
setResult(Activity.RESULT_OK, intent)
}
finish()
} else {
super.onActivityResult(requestCode, resultCode, data)
}
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putParcelable(EXTRA_URI, uri)
}
companion object {
private const val REQUEST_CODE_CAMERA = 75
private const val EXTRA_URI = "extra_output"
}
}

@ -30,7 +30,6 @@ import com.todoroo.astrid.activity.TaskEditFragment.Companion.gesturesDisabled
import org.tasks.R import org.tasks.R
import org.tasks.compose.BeastModeBanner import org.tasks.compose.BeastModeBanner
import org.tasks.data.entity.UserActivity import org.tasks.data.entity.UserActivity
import org.tasks.extensions.Context.findActivity
import org.tasks.files.FileHelper import org.tasks.files.FileHelper
import org.tasks.fragments.CommentBarFragment import org.tasks.fragments.CommentBarFragment
import org.tasks.themes.TasksTheme import org.tasks.themes.TasksTheme
@ -123,14 +122,9 @@ fun TaskEditScreen(
val context = LocalContext.current val context = LocalContext.current
CommentsRow( CommentsRow(
comments = comments, comments = comments,
copyCommentToClipboard = { copyCommentToClipboard = { copyToClipboard(context, R.string.comment, it) },
copyToClipboard(context, R.string.comment, it)
},
deleteComment = deleteComment, deleteComment = deleteComment,
openImage = { openImage = { FileHelper.startActionView(context, it) },
val activity = context.findActivity() ?: return@CommentsRow
FileHelper.startActionView(activity, it)
}
) )
} }
BeastModeBanner( BeastModeBanner(

@ -72,6 +72,7 @@ class AddAttachmentDialog : DialogFragment() {
activity = activity, activity = activity,
initial = null, initial = null,
allowMultiple = true, allowMultiple = true,
persistPermissions = true,
), ),
REQUEST_STORAGE REQUEST_STORAGE
) )

@ -26,6 +26,7 @@ import com.todoroo.andlib.utility.AndroidUtilities.atLeastS
import org.tasks.BuildConfig import org.tasks.BuildConfig
import org.tasks.R import org.tasks.R
import org.tasks.notifications.NotificationManager.Companion.NOTIFICATION_CHANNEL_DEFAULT import org.tasks.notifications.NotificationManager.Companion.NOTIFICATION_CHANNEL_DEFAULT
import timber.log.Timber
object Context { object Context {
private const val HTTP = "http" private const val HTTP = "http"
@ -149,4 +150,15 @@ object Context {
) )
} }
} }
fun Context.takePersistableUriPermission(
uri: Uri,
mode: Int = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
) {
try {
contentResolver.takePersistableUriPermission(uri, mode)
} catch (e: SecurityException) {
Timber.e(e)
}
}
} }

@ -17,12 +17,15 @@ import com.google.common.collect.Iterables
import com.google.common.io.ByteStreams import com.google.common.io.ByteStreams
import com.google.common.io.Files import com.google.common.io.Files
import com.todoroo.astrid.utility.Constants import com.todoroo.astrid.utility.Constants
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.tasks.Strings.isNullOrEmpty import org.tasks.Strings.isNullOrEmpty
import org.tasks.extensions.Context.safeStartActivity import org.tasks.extensions.Context.safeStartActivity
import timber.log.Timber import timber.log.Timber
import java.io.File import java.io.File
import java.io.FileNotFoundException import java.io.FileNotFoundException
import java.io.IOException import java.io.IOException
import java.io.InputStream
import java.util.* import java.util.*
object FileHelper { object FileHelper {
@ -30,9 +33,18 @@ object FileHelper {
activity: Activity?, activity: Activity?,
initial: Uri?, initial: Uri?,
allowMultiple: Boolean = false, allowMultiple: Boolean = false,
persistPermissions: Boolean = false,
vararg mimeTypes: String?, vararg mimeTypes: String?,
): Intent = ): Intent =
Intent(Intent.ACTION_OPEN_DOCUMENT).apply { Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
if (persistPermissions) {
addFlags(
Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
or Intent.FLAG_GRANT_READ_URI_PERMISSION
or Intent.FLAG_GRANT_PREFIX_URI_PERMISSION
)
}
putExtra("android.content.extra.SHOW_ADVANCED", true) putExtra("android.content.extra.SHOW_ADVANCED", true)
putExtra("android.content.extra.FANCY", true) putExtra("android.content.extra.FANCY", true)
putExtra("android.content.extra.SHOW_FILESIZE", true) putExtra("android.content.extra.SHOW_FILESIZE", true)
@ -130,6 +142,24 @@ object FileHelper {
Files.getFileExtension(getFilename(context, uri)!!) Files.getFileExtension(getFilename(context, uri)!!)
} }
suspend fun fileExists(context: Context, uri: Uri): Boolean = withContext(Dispatchers.IO) {
when (uri.scheme) {
ContentResolver.SCHEME_FILE -> {
File(uri.path!!).exists()
}
ContentResolver.SCHEME_CONTENT -> {
try {
val documentFile = DocumentFile.fromSingleUri(context, uri)
documentFile?.exists() == true && documentFile.length() > 0
} catch (e: Exception) {
Timber.e(e)
false
}
}
else -> false
}
}
fun getMimeType(context: Context, uri: Uri): String? { fun getMimeType(context: Context, uri: Uri): String? {
val mimeType = context.contentResolver.getType(uri) val mimeType = context.contentResolver.getType(uri)
if (!isNullOrEmpty(mimeType)) { if (!isNullOrEmpty(mimeType)) {
@ -139,28 +169,33 @@ object FileHelper {
return MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension) return MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension)
} }
fun startActionView(context: Activity, uri: Uri?) { fun startActionView(context: Context, uri: Uri?) {
var uri = uri ?: return var uri = uri ?: return
val mimeType = getMimeType(context, uri) val mimeType = getMimeType(context, uri)
val intent = Intent(Intent.ACTION_VIEW) val intent = Intent(Intent.ACTION_VIEW)
if (uri.scheme == ContentResolver.SCHEME_CONTENT) { if (uri.scheme == ContentResolver.SCHEME_CONTENT) {
uri = copyToUri(context, Uri.fromFile(context.cacheDir), uri) intent.setDataAndType(uri, mimeType)
} } else {
val share = FileProvider.getUriForFile(context, Constants.FILE_PROVIDER_AUTHORITY, File(uri.path)) val share = FileProvider.getUriForFile(context, Constants.FILE_PROVIDER_AUTHORITY, File(uri.path))
intent.setDataAndType(share, mimeType) intent.setDataAndType(share, mimeType)
}
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
context.safeStartActivity(intent) context.safeStartActivity(intent)
} }
@JvmStatic @JvmStatic
@Throws(IOException::class) @Throws(IOException::class)
fun newFile( suspend fun newFile(
context: Context, destination: Uri, mimeType: String?, baseName: String, extension: String?): Uri { context: Context, destination: Uri, mimeType: String?, baseName: String, extension: String?): Uri = withContext(Dispatchers.IO) {
val filename = getNonCollidingFileName(context, destination, baseName, extension) val filename = getNonCollidingFileName(context, destination, baseName, extension)
return when (destination.scheme) { when (destination.scheme) {
"content" -> { "content" -> {
val tree = DocumentFile.fromTreeUri(context, destination) val tree = DocumentFile.fromTreeUri(context, destination)
val f1 = tree!!.createFile(mimeType!!, filename) ?: throw IOException("Failed to access directory: $destination")
if (!tree.canWrite()) {
throw IOException("No write permission for directory: $destination")
}
val f1 = tree.createFile(mimeType ?: "application/octet-stream", filename)
?: throw FileNotFoundException("Failed to create $filename") ?: throw FileNotFoundException("Failed to create $filename")
f1.uri f1.uri
} }
@ -171,15 +206,16 @@ object FileHelper {
} }
val f2 = File(dir.absolutePath + File.separator + filename) val f2 = File(dir.absolutePath + File.separator + filename)
if (f2.createNewFile()) { if (f2.createNewFile()) {
return Uri.fromFile(f2) Uri.fromFile(f2)
} } else {
throw FileNotFoundException("Failed to create $filename") throw FileNotFoundException("Failed to create $filename")
} }
}
else -> throw IllegalArgumentException("Unknown URI scheme: " + destination.scheme) else -> throw IllegalArgumentException("Unknown URI scheme: " + destination.scheme)
} }
} }
fun copyToUri(context: Context, destination: Uri, input: Uri): Uri { suspend fun copyToUri(context: Context, destination: Uri, input: Uri): Uri {
val filename = getFilename(context, input) val filename = getFilename(context, input)
val basename = Files.getNameWithoutExtension(filename!!) val basename = Files.getNameWithoutExtension(filename!!)
try { try {
@ -197,7 +233,53 @@ object FileHelper {
} }
} }
fun copyStream(context: Context, input: Uri?, output: Uri?) { suspend fun isInTree(context: Context, treeUri: Uri, documentUri: Uri): Boolean = withContext(Dispatchers.IO) {
if (treeUri.authority != documentUri.authority) {
return@withContext false
}
try {
val tree = DocumentFile.fromTreeUri(context, treeUri)
?: return@withContext false
val documentFile = DocumentFile.fromSingleUri(context, documentUri)
?: return@withContext false
val name = documentFile.name ?: return@withContext false
val treeFile = tree.findFile(name) ?: return@withContext false
val contentResolver = context.contentResolver
contentResolver.openInputStream(documentUri)?.use { input1 ->
contentResolver.openInputStream(treeFile.uri)?.use { input2 ->
compareStreams(input1, input2)
}
} ?: false
} catch (e: Exception) {
Timber.e(e)
false
}
}
private fun compareStreams(input1: InputStream, input2: InputStream): Boolean {
val buffer1 = ByteArray(8192)
val buffer2 = ByteArray(8192)
while (true) {
val count1 = input1.read(buffer1)
val count2 = input2.read(buffer2)
if (count1 != count2) {
return false
}
if (count1 == -1) {
return true
}
if (!buffer1.contentEquals(buffer2)) {
return false
}
}
}
suspend fun copyStream(context: Context, input: Uri?, output: Uri?) = withContext(Dispatchers.IO) {
val contentResolver = context.contentResolver val contentResolver = context.contentResolver
try { try {
val inputStream = contentResolver.openInputStream(input!!) val inputStream = contentResolver.openInputStream(input!!)

@ -13,6 +13,7 @@ import org.tasks.calendars.CalendarEventProvider
import org.tasks.data.dao.TaskDao import org.tasks.data.dao.TaskDao
import org.tasks.data.db.Database import org.tasks.data.db.Database
import org.tasks.etebase.EtebaseLocalCache import org.tasks.etebase.EtebaseLocalCache
import org.tasks.extensions.Context.takePersistableUriPermission
import org.tasks.extensions.Context.toast import org.tasks.extensions.Context.toast
import org.tasks.files.FileHelper import org.tasks.files.FileHelper
import org.tasks.injection.InjectingPreferenceFragment import org.tasks.injection.InjectingPreferenceFragment
@ -73,11 +74,7 @@ class Advanced : InjectingPreferenceFragment() {
if (requestCode == REQUEST_CODE_FILES_DIR) { if (requestCode == REQUEST_CODE_FILES_DIR) {
if (resultCode == Activity.RESULT_OK) { if (resultCode == Activity.RESULT_OK) {
val uri = data!!.data!! val uri = data!!.data!!
requireContext().contentResolver requireContext().takePersistableUriPermission(uri)
.takePersistableUriPermission(
uri,
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
)
preferences.setUri(R.string.p_attachment_dir, uri) preferences.setUri(R.string.p_attachment_dir, uri)
updateAttachmentDirectory() updateAttachmentDirectory()
} }

@ -12,6 +12,7 @@ import org.tasks.R
import org.tasks.dialogs.ExportTasksDialog import org.tasks.dialogs.ExportTasksDialog
import org.tasks.dialogs.ImportTasksDialog import org.tasks.dialogs.ImportTasksDialog
import org.tasks.drive.DriveLoginActivity import org.tasks.drive.DriveLoginActivity
import org.tasks.extensions.Context.takePersistableUriPermission
import org.tasks.extensions.Context.toast import org.tasks.extensions.Context.toast
import org.tasks.files.FileHelper import org.tasks.files.FileHelper
import org.tasks.injection.InjectingPreferenceFragment import org.tasks.injection.InjectingPreferenceFragment
@ -176,11 +177,7 @@ class Backups : InjectingPreferenceFragment() {
if (requestCode == REQUEST_CODE_BACKUP_DIR) { if (requestCode == REQUEST_CODE_BACKUP_DIR) {
if (resultCode == RESULT_OK && data != null) { if (resultCode == RESULT_OK && data != null) {
val uri = data.data!! val uri = data.data!!
context?.contentResolver context?.takePersistableUriPermission(uri)
?.takePersistableUriPermission(
uri,
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
)
preferences.setUri(R.string.p_backup_dir, uri) preferences.setUri(R.string.p_backup_dir, uri)
updateBackupDirectory() updateBackupDirectory()
viewModel.updateLocalBackup() viewModel.updateLocalBackup()

@ -524,7 +524,7 @@ class TaskEditViewModel @Inject constructor(
} }
} }
fun addComment(message: String?, picture: Uri?) { fun addComment(message: String?, picture: Uri?) = viewModelScope.launch(NonCancellable) {
val userActivity = UserActivity() val userActivity = UserActivity()
if (picture != null) { if (picture != null) {
val output = FileHelper.copyToUri(context, preferences.attachmentsDirectory!!, picture) val output = FileHelper.copyToUri(context, preferences.attachmentsDirectory!!, picture)
@ -533,12 +533,8 @@ class TaskEditViewModel @Inject constructor(
userActivity.message = message userActivity.message = message
userActivity.targetId = task.uuid userActivity.targetId = task.uuid
userActivity.created = currentTimeMillis() userActivity.created = currentTimeMillis()
viewModelScope.launch {
withContext(NonCancellable) {
userActivityDao.createNew(userActivity) userActivityDao.createNew(userActivity)
} }
}
}
fun hideBeastModeHint(click: Boolean) { fun hideBeastModeHint(click: Boolean) {
_viewState.update { _viewState.update {
@ -621,7 +617,11 @@ class TaskEditViewModel @Inject constructor(
init { init {
viewModelScope.launch { viewModelScope.launch {
taskAttachmentDao.getAttachments(task.id).toPersistentSet().let { attachments -> taskAttachmentDao
.getAttachments(task.id)
.filter { FileHelper.fileExists(context, Uri.parse(it.uri)) }
.toPersistentSet()
.let { attachments ->
_originalState.update { it.copy(attachments = attachments) } _originalState.update { it.copy(attachments = attachments) }
_viewState.update { it.copy(attachments = attachments) } _viewState.update { it.copy(attachments = attachments) }
} }

Loading…
Cancel
Save