diff --git a/CHANGELOG.md b/CHANGELOG.md index 55455ece5..286c7d889 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ Group changes to describe their impact on the project, as follows: - If next message is also a voice recording, playback will automatically start after the currently playing one ends. - Chat while in call: a shortcut to a conversation screen with the remote. - Chat while in a conference: if the conference has a text stream enabled, you can chat with the other participants of the conference while it lasts. At the end, you'll find the messages history in the call history (and not in the list of conversations). +- Auto export of media to native gallery even when auto download is enabled (but still not if VFS is enabled nor for ephemeral messages). - Notification showing upload/download of files shared through chat will let user know the progress and keep the app alive during that process. - Screen sharing in conference: only desktop app starting with 6.0 version is able to start it, but on mobiles you'll be able to see it. - You can choose whatever ringtone you'd like for incoming calls (in Android notification channel settings). diff --git a/app/src/main/java/org/linphone/core/CoreContext.kt b/app/src/main/java/org/linphone/core/CoreContext.kt index 3395fdf04..9ff99de9e 100644 --- a/app/src/main/java/org/linphone/core/CoreContext.kt +++ b/app/src/main/java/org/linphone/core/CoreContext.kt @@ -46,6 +46,7 @@ import org.linphone.ui.call.CallActivity import org.linphone.utils.ActivityMonitor import org.linphone.utils.AppUtils import org.linphone.utils.Event +import org.linphone.utils.FileUtils import org.linphone.utils.LinphoneUtils class CoreContext @@ -112,6 +113,11 @@ class CoreContext MutableLiveData>() } + private var filesToExportToNativeMediaGallery = arrayListOf() + val filesToExportToNativeMediaGalleryEvent: MutableLiveData>> by lazy { + MutableLiveData>>() + } + @SuppressLint("HandlerLeak") private lateinit var coreThread: Handler @@ -155,6 +161,42 @@ class CoreContext private var previousCallState = Call.State.Idle private val coreListener = object : CoreListenerStub() { + @WorkerThread + override fun onMessagesReceived( + core: Core, + chatRoom: ChatRoom, + messages: Array + ) { + if (corePreferences.makePublicMediaFilesDownloaded && core.maxSizeForAutoDownloadIncomingFiles >= 0) { + for (message in messages) { + // Never do auto media export for ephemeral messages! + if (message?.isEphemeral == true) continue + + for (content in message?.contents.orEmpty()) { + if (content.isFile) { + val path = content.filePath + if (path.isNullOrEmpty()) continue + + val mime = "${content.type}/${content.subtype}" + val mimeType = FileUtils.getMimeType(mime) + when (mimeType) { + FileUtils.MimeType.Image, FileUtils.MimeType.Video, FileUtils.MimeType.Audio -> { + Log.i("$TAG Added file path [$path] to the list of media to export to native media gallery") + filesToExportToNativeMediaGallery.add(path) + } + else -> {} + } + } + } + } + } + + if (filesToExportToNativeMediaGallery.isNotEmpty()) { + Log.i("$TAG Creating event with [${filesToExportToNativeMediaGallery.size}] files to export to native media gallery") + filesToExportToNativeMediaGalleryEvent.postValue(Event(filesToExportToNativeMediaGallery)) + } + } + @WorkerThread override fun onGlobalStateChanged(core: Core, state: GlobalState, message: String) { Log.i("$TAG Global state changed [$state]") @@ -628,6 +670,11 @@ class CoreContext return found != null } + @WorkerThread + fun clearFilesToExportToNativeGallery() { + filesToExportToNativeMediaGallery.clear() + } + @WorkerThread fun startAudioCall( address: Address, diff --git a/app/src/main/java/org/linphone/core/CorePreferences.kt b/app/src/main/java/org/linphone/core/CorePreferences.kt index 7a9fac7c2..72bd1e4ca 100644 --- a/app/src/main/java/org/linphone/core/CorePreferences.kt +++ b/app/src/main/java/org/linphone/core/CorePreferences.kt @@ -149,6 +149,13 @@ class CorePreferences config.setBool("app", "mark_as_read_notif_dismissal", value) } + var makePublicMediaFilesDownloaded: Boolean + // Keep old name for backward compatibility + get() = config.getBool("app", "make_downloaded_images_public_in_gallery", false) + set(value) { + config.setBool("app", "make_downloaded_images_public_in_gallery", value) + } + // Conference related @get:WorkerThread @set:WorkerThread diff --git a/app/src/main/java/org/linphone/core/VFS.kt b/app/src/main/java/org/linphone/core/VFS.kt index 135aed367..837cf30d0 100644 --- a/app/src/main/java/org/linphone/core/VFS.kt +++ b/app/src/main/java/org/linphone/core/VFS.kt @@ -27,6 +27,7 @@ import android.util.Base64 import android.util.Pair import androidx.security.crypto.EncryptedSharedPreferences import androidx.security.crypto.MasterKey +import org.linphone.LinphoneApplication.Companion.corePreferences import java.math.BigInteger import java.nio.charset.StandardCharsets import java.security.KeyStore @@ -73,6 +74,12 @@ class VFS { } preferences.edit().putBoolean("vfs_enabled", true).apply() + + if (corePreferences.makePublicMediaFilesDownloaded) { + Log.w("$TAG VFS is now enabled, disabling auto export of media files to native gallery") + corePreferences.makePublicMediaFilesDownloaded = false + } + return true } diff --git a/app/src/main/java/org/linphone/ui/fileviewer/MediaViewerActivity.kt b/app/src/main/java/org/linphone/ui/fileviewer/MediaViewerActivity.kt index 95b831db0..e110a956e 100644 --- a/app/src/main/java/org/linphone/ui/fileviewer/MediaViewerActivity.kt +++ b/app/src/main/java/org/linphone/ui/fileviewer/MediaViewerActivity.kt @@ -99,7 +99,8 @@ class MediaViewerActivity : GenericActivity() { val timestamp = args.getLong("timestamp", -1) val isEncrypted = args.getBoolean("isEncrypted", false) val originalPath = args.getString("originalPath", "") - viewModel.initTempModel(path, timestamp, isEncrypted, originalPath) + val isFromEphemeralMessage = args.getBoolean("isFromEphemeralMessage", false) + viewModel.initTempModel(path, timestamp, isEncrypted, originalPath, isFromEphemeralMessage) val conversationId = args.getString("conversationId").orEmpty() Log.i( @@ -183,6 +184,12 @@ class MediaViewerActivity : GenericActivity() { val currentItem = binding.mediaViewPager.currentItem val model = if (currentItem >= 0 && currentItem < list.size) list[currentItem] else null if (model != null) { + // Never do auto media export for ephemeral messages! + if (model.isFromEphemeralMessage) { + Log.e("$TAG Do not export media from ephemeral message!") + return + } + val filePath = model.path lifecycleScope.launch { withContext(Dispatchers.IO) { diff --git a/app/src/main/java/org/linphone/ui/fileviewer/viewmodel/MediaListViewModel.kt b/app/src/main/java/org/linphone/ui/fileviewer/viewmodel/MediaListViewModel.kt index 364c498a9..9562cdd3a 100644 --- a/app/src/main/java/org/linphone/ui/fileviewer/viewmodel/MediaListViewModel.kt +++ b/app/src/main/java/org/linphone/ui/fileviewer/viewmodel/MediaListViewModel.kt @@ -57,9 +57,9 @@ class MediaListViewModel } @UiThread - fun initTempModel(path: String, timestamp: Long, isEncrypted: Boolean, originalPath: String) { + fun initTempModel(path: String, timestamp: Long, isEncrypted: Boolean, originalPath: String, isFromEphemeralMessage: Boolean) { val name = FileUtils.getNameFromFilePath(path) - val model = FileModel(path, name, 0, timestamp, isEncrypted, originalPath) + val model = FileModel(path, name, 0, timestamp, isEncrypted, originalPath, isFromEphemeralMessage) temporaryModel = model Log.i("$TAG Temporary model for file [$name] created, use it while other media for conversation are being loaded") mediaList.postValue(arrayListOf(model)) @@ -98,7 +98,10 @@ class MediaListViewModel val size = mediaContent.size.toLong() val timestamp = mediaContent.creationTimestamp if (path.isNotEmpty() && name.isNotEmpty()) { - val model = FileModel(path, name, size, timestamp, isEncrypted, originalPath) + // TODO FIXME: we don't have the ephemeral info at Content level, using the chatRoom info even if content ephemeral status may or may not be different... + val ephemeral = chatRoom.isEphemeralEnabled + + val model = FileModel(path, name, size, timestamp, isEncrypted, originalPath, ephemeral) list.add(model) } diff --git a/app/src/main/java/org/linphone/ui/main/MainActivity.kt b/app/src/main/java/org/linphone/ui/main/MainActivity.kt index 891de4c80..21e33b655 100644 --- a/app/src/main/java/org/linphone/ui/main/MainActivity.kt +++ b/app/src/main/java/org/linphone/ui/main/MainActivity.kt @@ -52,9 +52,11 @@ import androidx.navigation.NavOptions import androidx.navigation.findNavController import kotlin.math.max import kotlinx.coroutines.Deferred +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.LinphoneApplication.Companion.corePreferences import org.linphone.R @@ -313,6 +315,19 @@ class MainActivity : GenericActivity() { } } + coreContext.filesToExportToNativeMediaGalleryEvent.observe(this) { + it.consume { files -> + Log.i("$TAG Found [${files.size}] files to export to native media gallery") + for (file in files) { + exportFileToNativeMediaGallery(file) + } + + coreContext.postOnCoreThread { + coreContext.clearFilesToExportToNativeGallery() + } + } + } + CarConnection(this).type.observe(this) { val asString = when (it) { CarConnection.CONNECTION_TYPE_NOT_CONNECTED -> "NOT CONNECTED" @@ -777,4 +792,18 @@ class MainActivity : GenericActivity() { dialog.show() currentlyDisplayedAuthDialog = dialog } + + private fun exportFileToNativeMediaGallery(filePath: String) { + lifecycleScope.launch { + withContext(Dispatchers.IO) { + Log.i("$TAG Export file [$filePath] to Android's MediaStore") + val mediaStorePath = FileUtils.addContentToMediaStore(filePath) + if (mediaStorePath.isNotEmpty()) { + Log.i("$TAG File [$filePath] has been successfully exported to MediaStore") + } else { + Log.e("$TAG Failed to export file [$filePath] to MediaStore!") + } + } + } + } } diff --git a/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationDocumentsListFragment.kt b/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationDocumentsListFragment.kt index 9b410ca77..80b334fdd 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationDocumentsListFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationDocumentsListFragment.kt @@ -145,6 +145,7 @@ class ConversationDocumentsListFragment : SlidingPaneChildFragment() { putBoolean("isEncrypted", fileModel.isEncrypted) putLong("timestamp", fileModel.fileCreationTimestamp) putString("originalPath", fileModel.originalPath) + putBoolean("isFromEphemeralMessage", fileModel.isFromEphemeralMessage) putBoolean("isMedia", false) } when (FileUtils.getMimeType(mime)) { diff --git a/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationFragment.kt b/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationFragment.kt index f8dfae277..dd44bcd17 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationFragment.kt @@ -1114,6 +1114,7 @@ open class ConversationFragment : SlidingPaneChildFragment() { putBoolean("isEncrypted", fileModel.isEncrypted) putLong("timestamp", fileModel.fileCreationTimestamp) putString("originalPath", fileModel.originalPath) + putBoolean("isFromEphemeralMessage", fileModel.isFromEphemeralMessage) } when (mimeType) { FileUtils.MimeType.Image, FileUtils.MimeType.Video, FileUtils.MimeType.Audio -> { diff --git a/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationMediaListFragment.kt b/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationMediaListFragment.kt index abece7630..76a9c5e06 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationMediaListFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationMediaListFragment.kt @@ -174,6 +174,7 @@ class ConversationMediaListFragment : SlidingPaneChildFragment() { putBoolean("isEncrypted", fileModel.isEncrypted) putLong("timestamp", fileModel.fileCreationTimestamp) putString("originalPath", fileModel.originalPath) + putBoolean("isFromEphemeralMessage", fileModel.isFromEphemeralMessage) putBoolean("isMedia", true) } when (FileUtils.getMimeType(mime)) { diff --git a/app/src/main/java/org/linphone/ui/main/chat/model/EventLogModel.kt b/app/src/main/java/org/linphone/ui/main/chat/model/EventLogModel.kt index 948ae8de8..257c50357 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/model/EventLogModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/model/EventLogModel.kt @@ -38,7 +38,8 @@ class EventLogModel onWebUrlClicked: ((url: String) -> Unit)? = null, onContactClicked: ((friendRefKey: String) -> Unit)? = null, onRedToastToShow: ((pair: Pair) -> Unit)? = null, - onVoiceRecordingPlaybackEnded: ((id: String) -> Unit)? = null + onVoiceRecordingPlaybackEnded: ((id: String) -> Unit)? = null, + onFileToExportToNativeGallery: ((path: String) -> Unit)? = null ) { companion object { private const val TAG = "[Event Log Model]" @@ -89,7 +90,8 @@ class EventLogModel onWebUrlClicked, onContactClicked, onRedToastToShow, - onVoiceRecordingPlaybackEnded + onVoiceRecordingPlaybackEnded, + onFileToExportToNativeGallery ) } diff --git a/app/src/main/java/org/linphone/ui/main/chat/model/FileModel.kt b/app/src/main/java/org/linphone/ui/main/chat/model/FileModel.kt index c8cc3c868..f02346c08 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/model/FileModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/model/FileModel.kt @@ -45,6 +45,7 @@ class FileModel val fileCreationTimestamp: Long, val isEncrypted: Boolean, val originalPath: String, + val isFromEphemeralMessage: Boolean, val isWaitingToBeDownloaded: Boolean = false, val flexboxLayoutWrapBefore: Boolean = false, private val onClicked: ((model: FileModel) -> Unit)? = null diff --git a/app/src/main/java/org/linphone/ui/main/chat/model/MessageModel.kt b/app/src/main/java/org/linphone/ui/main/chat/model/MessageModel.kt index 7c0a9b5de..de4c8cc1a 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/model/MessageModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/model/MessageModel.kt @@ -42,6 +42,7 @@ import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import org.linphone.LinphoneApplication.Companion.coreContext +import org.linphone.LinphoneApplication.Companion.corePreferences import org.linphone.R import org.linphone.core.Address import org.linphone.core.ChatMessage @@ -82,7 +83,8 @@ class MessageModel private val onWebUrlClicked: ((url: String) -> Unit)? = null, private val onContactClicked: ((friendRefKey: String) -> Unit)? = null, private val onRedToastToShow: ((pair: Pair) -> Unit)? = null, - private val onVoiceRecordingPlaybackEnded: ((id: String) -> Unit)? = null + private val onVoiceRecordingPlaybackEnded: ((id: String) -> Unit)? = null, + private val onFileToExportToNativeGallery: ((path: String) -> Unit)? = null ) { companion object { private const val TAG = "[Message Model]" @@ -224,6 +226,27 @@ class MessageModel } } + @WorkerThread + override fun onFileTransferTerminated(message: ChatMessage, content: Content) { + Log.i("$TAG File [${content.name}] from message [${message.messageId}] transfer terminated") + + // Never do auto media export for ephemeral messages! + if (corePreferences.makePublicMediaFilesDownloaded && !message.isEphemeral) { + val path = content.filePath + if (path.isNullOrEmpty()) return + + val mime = "${content.type}/${content.subtype}" + val mimeType = FileUtils.getMimeType(mime) + when (mimeType) { + FileUtils.MimeType.Image, FileUtils.MimeType.Video, FileUtils.MimeType.Audio -> { + Log.i("$TAG Exporting file path [$path] to the native media gallery") + onFileToExportToNativeGallery?.invoke(path) + } + else -> {} + } + } + } + @WorkerThread override fun onNewMessageReaction(message: ChatMessage, reaction: ChatMessageReaction) { Log.i( @@ -443,6 +466,7 @@ class MessageModel timestamp, isFileEncrypted, originalPath, + chatMessage.isEphemeral, flexboxLayoutWrapBefore = wrapBefore ) { model -> onContentClicked?.invoke(model) @@ -470,7 +494,8 @@ class MessageModel content.fileSize.toLong(), timestamp, isFileEncrypted, - path + path, + chatMessage.isEphemeral ) { model -> onContentClicked?.invoke(model) } @@ -482,6 +507,7 @@ class MessageModel timestamp, isFileEncrypted, name, + chatMessage.isEphemeral, isWaitingToBeDownloaded = true ) { model -> downloadContent(model, content) diff --git a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationDocumentsListViewModel.kt b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationDocumentsListViewModel.kt index ecf157930..502f145f0 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationDocumentsListViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationDocumentsListViewModel.kt @@ -79,7 +79,11 @@ class ConversationDocumentsListViewModel val size = documentContent.size.toLong() val timestamp = documentContent.creationTimestamp if (path.isNotEmpty() && name.isNotEmpty()) { - val model = FileModel(path, name, size, timestamp, isEncrypted, originalPath) { + // TODO FIXME: we don't have the ephemeral info at Content level, using the chatRoom info even if content ephemeral status may or may not be different... + val ephemeral = chatRoom.isEphemeralEnabled + + val model = + FileModel(path, name, size, timestamp, isEncrypted, originalPath, ephemeral) { openDocumentEvent.postValue(Event(it)) } list.add(model) diff --git a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationMediaListViewModel.kt b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationMediaListViewModel.kt index edf9d4884..f05ff724f 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationMediaListViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationMediaListViewModel.kt @@ -78,7 +78,8 @@ class ConversationMediaListViewModel val size = mediaContent.size.toLong() val timestamp = mediaContent.creationTimestamp if (path.isNotEmpty() && name.isNotEmpty()) { - val model = FileModel(path, name, size, timestamp, isEncrypted, originalPath) { + val model = + FileModel(path, name, size, timestamp, isEncrypted, originalPath, chatRoom.isEphemeralEnabled) { openMediaEvent.postValue(Event(it)) } list.add(model) diff --git a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationViewModel.kt b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationViewModel.kt index d592a9495..6ddd58e44 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationViewModel.kt @@ -761,6 +761,19 @@ class ConversationViewModel }, { id -> voiceRecordPlaybackEndedEvent.postValue(Event(id)) + }, + { filePath -> + viewModelScope.launch { + withContext(Dispatchers.IO) { + Log.i("$TAG Export file [$filePath] to Android's MediaStore") + val mediaStorePath = FileUtils.addContentToMediaStore(filePath) + if (mediaStorePath.isNotEmpty()) { + Log.i("$TAG File [$filePath] has been successfully exported to MediaStore") + } else { + Log.e("$TAG Failed to export file [$filePath] to MediaStore!") + } + } + } } ) eventsList.add(model) diff --git a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/SendMessageInConversationViewModel.kt b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/SendMessageInConversationViewModel.kt index 230a78b56..0c711eba4 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/SendMessageInConversationViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/SendMessageInConversationViewModel.kt @@ -397,7 +397,7 @@ class SendMessageInConversationViewModel val fileName = FileUtils.getNameFromFilePath(file) val timestamp = System.currentTimeMillis() / 1000 - val model = FileModel(file, fileName, 0, timestamp, false, file) { model -> + val model = FileModel(file, fileName, 0, timestamp, false, file, chatRoom.isEphemeralEnabled) { model -> removeAttachment(model.path) } diff --git a/app/src/main/java/org/linphone/ui/main/settings/viewmodel/SettingsViewModel.kt b/app/src/main/java/org/linphone/ui/main/settings/viewmodel/SettingsViewModel.kt index 9c90b3894..eaef19b0c 100644 --- a/app/src/main/java/org/linphone/ui/main/settings/viewmodel/SettingsViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/settings/viewmodel/SettingsViewModel.kt @@ -90,6 +90,8 @@ class SettingsViewModel val autoDownloadEnabled = MutableLiveData() + val autoExportMediaToNativeGallery = MutableLiveData() + val markAsReadWhenDismissingNotification = MutableLiveData() // Contacts settings @@ -246,7 +248,8 @@ class SettingsViewModel expandAudioCodecs.value = false expandVideoCodecs.value = false - isVfsEnabled.value = VFS.isEnabled(coreContext.context) + val vfsEnabled = VFS.isEnabled(coreContext.context) + isVfsEnabled.value = vfsEnabled val vibrator = coreContext.context.getSystemService(Vibrator::class.java) isVibrationAvailable.value = vibrator.hasVibrator() @@ -285,6 +288,7 @@ class SettingsViewModel allowIpv6.postValue(core.isIpv6Enabled) autoDownloadEnabled.postValue(core.maxSizeForAutoDownloadIncomingFiles == 0) + autoExportMediaToNativeGallery.postValue(corePreferences.makePublicMediaFilesDownloaded && !vfsEnabled) markAsReadWhenDismissingNotification.postValue( corePreferences.markConversationAsReadWhenDismissingMessageNotification ) @@ -439,6 +443,15 @@ class SettingsViewModel } } + @UiThread + fun toggleAutoExportMediaFilesToNativeGallery() { + val newValue = autoExportMediaToNativeGallery.value == false + coreContext.postOnCoreThread { core -> + corePreferences.makePublicMediaFilesDownloaded = newValue + autoExportMediaToNativeGallery.postValue(newValue) + } + } + @UiThread fun toggleMarkConversationAsReadWhenDismissingNotification() { val newValue = markAsReadWhenDismissingNotification.value == false diff --git a/app/src/main/res/layout/settings_chat.xml b/app/src/main/res/layout/settings_chat.xml index d118eada6..0e9522744 100644 --- a/app/src/main/res/layout/settings_chat.xml +++ b/app/src/main/res/layout/settings_chat.xml @@ -40,10 +40,39 @@ android:layout_height="wrap_content" android:layout_marginTop="20dp" android:layout_marginEnd="16dp" + android:enabled="@{!viewModel.isVfsEnabled}" android:checked="@{viewModel.autoDownloadEnabled}" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" /> + + + + + app:layout_constraintTop_toBottomOf="@id/auto_export_media_to_native_gallery_switch" /> diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 8c533f513..d7557d089 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -201,6 +201,7 @@ Changer de sonnerie Conversations Télécharger automatiquement les fichiers + Rendre visible dans la galerie les médias téléchargés Marquer la conversation comme lue lorsqu\'une notification de message est supprimée Contacts Ajouter un serveur LDAP diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index aa82e83a6..f66b4bbed 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -240,6 +240,7 @@ Change ringtone Conversations Auto-download files + Make downloaded media public Mark conversation as read when dismissing message notification Contacts Add LDAP server