From 5be7b1b7280c053ea8712c4c5bbe66a06d26d9f6 Mon Sep 17 00:00:00 2001 From: dkanada Date: Sat, 9 Jan 2021 23:23:17 +0900 Subject: [PATCH 001/242] store cache for WidgetWebView between loads --- .../im/vector/app/features/widgets/webview/WidgetWebView.kt | 6 ------ 1 file changed, 6 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/widgets/webview/WidgetWebView.kt b/vector/src/main/java/im/vector/app/features/widgets/webview/WidgetWebView.kt index 446bc1663f..dc59f2694f 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/webview/WidgetWebView.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/webview/WidgetWebView.kt @@ -36,10 +36,6 @@ fun WebView.setupForWidget(webViewEventListener: WebViewEventListener) { // clear caches clearHistory() clearFormData() - clearCache(true) - - // does not cache the data - settings.cacheMode = WebSettings.LOAD_NO_CACHE // Enable Javascript settings.javaScriptEnabled = true @@ -77,8 +73,6 @@ fun WebView.clearAfterWidget() { webChromeClient = null webViewClient = null clearHistory() - // NOTE: clears RAM cache, if you pass true, it will also clear the disk cache. - clearCache(true) // Loading a blank page is optional, but will ensure that the WebView isn't doing anything when you destroy it. loadUrl("about:blank") removeAllViews() From b411e8f0093e3de47108d95c1b3227421ae40b1a Mon Sep 17 00:00:00 2001 From: dkanada Date: Tue, 12 Jan 2021 11:42:45 +0900 Subject: [PATCH 002/242] update changelog --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 1315d006e0..5e8ae6ab77 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,7 @@ Features ✨: Improvements 🙌: - Add System theme option and set as default (#904, #2387) + - Use WebView cache for widgets to avoid excessive data use (#2648) - Warn user when they are leaving a not public room (#1460) Bugfix 🐛: From bc6eb565a3b14c94f4f93d13ccf3c346b67463ac Mon Sep 17 00:00:00 2001 From: dkanada Date: Tue, 12 Jan 2021 23:19:33 +0900 Subject: [PATCH 003/242] remove unused import --- .../java/im/vector/app/features/widgets/webview/WidgetWebView.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/widgets/webview/WidgetWebView.kt b/vector/src/main/java/im/vector/app/features/widgets/webview/WidgetWebView.kt index dc59f2694f..b1908d87de 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/webview/WidgetWebView.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/webview/WidgetWebView.kt @@ -21,7 +21,6 @@ import android.view.ViewGroup import android.webkit.CookieManager import android.webkit.PermissionRequest import android.webkit.WebChromeClient -import android.webkit.WebSettings import android.webkit.WebView import im.vector.app.R import im.vector.app.features.themes.ThemeUtils From 69350ef514400a28deb0bc9ceeaf2899ad312c62 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Thu, 17 Jun 2021 16:17:38 +0300 Subject: [PATCH 004/242] Voice message UI initial implementation. --- build.gradle | 3 + vector/build.gradle | 3 + .../home/room/detail/RoomDetailAction.kt | 4 + .../home/room/detail/RoomDetailFragment.kt | 13 +++ .../home/room/detail/RoomDetailViewModel.kt | 23 +++++ .../room/detail/composer/TextComposerView.kt | 39 +++++++- .../composer/VoiceMessageRecorderView.kt | 92 +++++++++++++++++++ .../composer/VoiceMessageRecordingHelper.kt | 87 ++++++++++++++++++ .../res/drawable/bg_voice_message_lock.xml | 14 +++ .../main/res/drawable/ic_voice_lock_arrow.xml | 13 +++ .../src/main/res/drawable/ic_voice_locked.xml | 5 + .../drawable/ic_voice_message_unlocked.xml | 5 + vector/src/main/res/drawable/ic_voice_mic.xml | 12 +++ .../res/drawable/ic_voice_mic_recording.xml | 18 ++++ .../ic_voice_slide_to_cancel_arrow.xml | 8 ++ .../src/main/res/layout/composer_layout.xml | 6 ++ ...composer_layout_constraint_set_compact.xml | 8 ++ .../layout/view_voice_message_recorder.xml | 69 ++++++++++++++ vector/src/main/res/values/colors.xml | 5 + vector/src/main/res/values/strings.xml | 4 + 20 files changed, 428 insertions(+), 3 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageRecorderView.kt create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageRecordingHelper.kt create mode 100644 vector/src/main/res/drawable/bg_voice_message_lock.xml create mode 100644 vector/src/main/res/drawable/ic_voice_lock_arrow.xml create mode 100644 vector/src/main/res/drawable/ic_voice_locked.xml create mode 100644 vector/src/main/res/drawable/ic_voice_message_unlocked.xml create mode 100644 vector/src/main/res/drawable/ic_voice_mic.xml create mode 100644 vector/src/main/res/drawable/ic_voice_mic_recording.xml create mode 100644 vector/src/main/res/drawable/ic_voice_slide_to_cancel_arrow.xml create mode 100644 vector/src/main/res/layout/view_voice_message_recorder.xml diff --git a/build.gradle b/build.gradle index 881cd340f1..a7acc1c124 100644 --- a/build.gradle +++ b/build.gradle @@ -48,6 +48,9 @@ allprojects { // Chat effects includeGroupByRegex 'com\\.github\\.jetradarmobile' includeGroupByRegex 'nl\\.dionsegijn' + + // Voice RecordView + includeGroupByRegex 'com\\.github\\.3llomi' } } maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' } diff --git a/vector/build.gradle b/vector/build.gradle index a94f796a90..991e483e98 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -144,6 +144,8 @@ android { buildConfigField "im.vector.app.features.crypto.keysrequest.OutboundSessionKeySharingStrategy", "outboundSessionKeySharingStrategy", "im.vector.app.features.crypto.keysrequest.OutboundSessionKeySharingStrategy.WhenTyping" + buildConfigField "Long", "VOICE_MESSAGE_DURATION_LIMIT_MS", "120000L" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" // Keep abiFilter for the universalApk @@ -391,6 +393,7 @@ dependencies { implementation "androidx.autofill:autofill:$autofill_version" implementation 'jp.wasabeef:glide-transformations:4.3.0' implementation 'com.github.vector-im:PFLockScreen-Android:1.0.0-beta12' + implementation 'com.github.3llomi:RecordView:3.0.1' // Custom Tab implementation 'androidx.browser:browser:1.3.0' diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt index 72e614c18c..e63fc9e17b 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt @@ -109,4 +109,8 @@ sealed class RoomDetailAction : VectorViewModelAction { // Failed messages object RemoveAllFailedMessages : RoomDetailAction() + + // Voice Message + object StartRecordingVoiceMessage : RoomDetailAction() + data class EndRecordingVoiceMessage(val recordTime: Long) : RoomDetailAction() } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index 9ed4feebc4..024a37067a 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -100,6 +100,7 @@ import im.vector.app.core.ui.views.NotificationAreaView import im.vector.app.core.utils.Debouncer import im.vector.app.core.utils.DimensionConverter import im.vector.app.core.utils.KeyboardStateUtils +import im.vector.app.core.utils.PERMISSIONS_FOR_AUDIO_IP_CALL import im.vector.app.core.utils.PERMISSIONS_FOR_WRITING_FILES import im.vector.app.core.utils.checkPermissions import im.vector.app.core.utils.colorizeMatchingText @@ -1192,6 +1193,18 @@ class RoomDetailFragment @Inject constructor( override fun onTextEmptyStateChanged(isEmpty: Boolean) { // No op } + + override fun onVoiceRecordingStarted() { + roomDetailViewModel.handle(RoomDetailAction.StartRecordingVoiceMessage) + } + + override fun onVoiceRecordingEnded(recordTime: Long) { + roomDetailViewModel.handle(RoomDetailAction.EndRecordingVoiceMessage(recordTime)) + } + + override fun checkVoiceRecordingPermission(): Boolean { + return checkPermissions(PERMISSIONS_FOR_AUDIO_IP_CALL, requireActivity(), 0) + } } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index a2041c0a80..55ee63091e 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -18,6 +18,7 @@ package im.vector.app.features.home.room.detail import android.net.Uri import androidx.annotation.IdRes +import androidx.core.net.toUri import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.Async import com.airbnb.mvrx.Fail @@ -38,6 +39,7 @@ import im.vector.app.core.extensions.exhaustive import im.vector.app.core.mvrx.runCatchingToAsync import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider +import im.vector.app.features.attachments.toContentAttachmentData import im.vector.app.features.call.conference.JitsiService import im.vector.app.features.call.dialpad.DialPadLookup import im.vector.app.features.call.lookup.CallProtocolsChecker @@ -47,6 +49,7 @@ import im.vector.app.features.command.ParsedCommand import im.vector.app.features.createdirect.DirectRoomHelper import im.vector.app.features.crypto.keysrequest.OutboundSessionKeySharingStrategy import im.vector.app.features.crypto.verification.SupportedVerificationMethodsProvider +import im.vector.app.features.home.room.detail.composer.VoiceMessageRecordingHelper import im.vector.app.features.home.room.detail.composer.rainbow.RainbowGenerator import im.vector.app.features.home.room.detail.sticker.StickerPickerActionHandler import im.vector.app.features.home.room.detail.timeline.helper.RoomSummariesHolder @@ -56,6 +59,7 @@ import im.vector.app.features.home.room.typing.TypingHelper import im.vector.app.features.powerlevel.PowerLevelsObservableFactory import im.vector.app.features.session.coroutineScope import im.vector.app.features.settings.VectorPreferences +import im.vector.lib.multipicker.utils.toMultiPickerAudioType import io.reactivex.Observable import io.reactivex.rxkotlin.subscribeBy import io.reactivex.schedulers.Schedulers @@ -119,6 +123,7 @@ class RoomDetailViewModel @AssistedInject constructor( private val chatEffectManager: ChatEffectManager, private val directRoomHelper: DirectRoomHelper, private val jitsiService: JitsiService, + private val voiceMessageRecordingHelper: VoiceMessageRecordingHelper, timelineSettingsFactory: TimelineSettingsFactory ) : VectorViewModel(initialState), Timeline.Listener, ChatEffectManager.Delegate, CallProtocolsChecker.Listener { @@ -324,6 +329,8 @@ class RoomDetailViewModel @AssistedInject constructor( is RoomDetailAction.DoNotShowPreviewUrlFor -> handleDoNotShowPreviewUrlFor(action) RoomDetailAction.RemoveAllFailedMessages -> handleRemoveAllFailedMessages() RoomDetailAction.ResendAll -> handleResendAll() + RoomDetailAction.StartRecordingVoiceMessage -> handleStartRecordingVoiceMessage() + is RoomDetailAction.EndRecordingVoiceMessage -> handleEndRecordingVoiceMessage(action.recordTime) }.exhaustive } @@ -611,6 +618,22 @@ class RoomDetailViewModel @AssistedInject constructor( } } + private fun handleStartRecordingVoiceMessage() { + voiceMessageRecordingHelper.startRecording() + } + + private fun handleEndRecordingVoiceMessage(recordTime: Long) { + if (recordTime == 0L) { + voiceMessageRecordingHelper.deleteRecording() + return + } + voiceMessageRecordingHelper.stopRecording(recordTime)?.let { audioType -> + room.sendMedia(audioType.toContentAttachmentData(), false, emptySet()) + room + //voiceMessageRecordingHelper.deleteRecording() + } + } + private fun isIntegrationEnabled() = session.integrationManagerService().isIntegrationEnabled() fun isMenuItemVisible(@IdRes itemId: Int): Boolean = com.airbnb.mvrx.withState(this) { state -> diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerView.kt index d5e24dbb6b..6672027133 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerView.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerView.kt @@ -24,6 +24,7 @@ import android.view.ViewGroup import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet import androidx.core.text.toSpannable +import androidx.core.view.isInvisible import androidx.core.view.isVisible import androidx.transition.ChangeBounds import androidx.transition.Fade @@ -32,6 +33,7 @@ import androidx.transition.TransitionManager import androidx.transition.TransitionSet import im.vector.app.R import im.vector.app.databinding.ComposerLayoutBinding +import org.matrix.android.sdk.api.extensions.orFalse /** * Encapsulate the timeline composer UX. @@ -46,6 +48,9 @@ class TextComposerView @JvmOverloads constructor( fun onCloseRelatedMessage() fun onSendMessage(text: CharSequence) fun onAddAttachment() + fun onVoiceRecordingStarted() + fun onVoiceRecordingEnded(recordTime: Long) + fun checkVoiceRecordingPermission(): Boolean } val views: ComposerLayoutBinding @@ -71,7 +76,9 @@ class TextComposerView @JvmOverloads constructor( } override fun onTextEmptyStateChanged(isEmpty: Boolean) { - views.sendButton.isVisible = currentConstraintSetId == R.layout.composer_layout_constraint_set_expanded || !isEmpty + val shouldShowSendButton = currentConstraintSetId == R.layout.composer_layout_constraint_set_expanded || !isEmpty + views.sendButton.isInvisible = !shouldShowSendButton + views.voiceMessageRecorderView.isVisible = !shouldShowSendButton } } views.composerRelatedMessageCloseButton.setOnClickListener { @@ -87,6 +94,28 @@ class TextComposerView @JvmOverloads constructor( views.attachmentButton.setOnClickListener { callback?.onAddAttachment() } + + views.voiceMessageRecorderView.callback = object : VoiceMessageRecorderView.Callback { + override fun onVoiceRecordingStarted() { + views.attachmentButton.isVisible = false + views.composerEditText.isVisible = false + views.composerEmojiButton.isVisible = false + views.composerEditTextOuterBorder.isVisible = false + callback?.onVoiceRecordingStarted() + } + + override fun onVoiceRecordingEnded(recordTime: Long) { + views.attachmentButton.isVisible = true + views.composerEditText.isVisible = true + views.composerEmojiButton.isVisible = true + views.composerEditTextOuterBorder.isVisible = true + callback?.onVoiceRecordingEnded(recordTime) + } + + override fun checkVoiceRecordingPermission(): Boolean { + return callback?.checkVoiceRecordingPermission().orFalse() + } + } } fun collapse(animate: Boolean = true, transitionComplete: (() -> Unit)? = null) { @@ -96,7 +125,10 @@ class TextComposerView @JvmOverloads constructor( } currentConstraintSetId = R.layout.composer_layout_constraint_set_compact applyNewConstraintSet(animate, transitionComplete) - views.sendButton.isVisible = !views.composerEditText.text.isNullOrEmpty() + + val shouldShowSendButton = !views.composerEditText.text.isNullOrEmpty() + views.sendButton.isInvisible = !shouldShowSendButton + views.voiceMessageRecorderView.isVisible = !shouldShowSendButton } fun expand(animate: Boolean = true, transitionComplete: (() -> Unit)? = null) { @@ -106,7 +138,8 @@ class TextComposerView @JvmOverloads constructor( } currentConstraintSetId = R.layout.composer_layout_constraint_set_expanded applyNewConstraintSet(animate, transitionComplete) - views.sendButton.isVisible = true + views.sendButton.isInvisible = false + views.voiceMessageRecorderView.isVisible = false } private fun applyNewConstraintSet(animate: Boolean, transitionComplete: (() -> Unit)?) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageRecorderView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageRecorderView.kt new file mode 100644 index 0000000000..8ae32fcc69 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageRecorderView.kt @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.home.room.detail.composer + +import android.content.Context +import android.util.AttributeSet +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.content.ContextCompat +import androidx.core.view.isVisible +import com.devlomi.record_view.OnRecordListener +import im.vector.app.BuildConfig +import im.vector.app.R +import im.vector.app.databinding.ViewVoiceMessageRecorderBinding +import org.matrix.android.sdk.api.extensions.orFalse + +/** + * Encapsulates the voice message recording view and animations. + */ +class VoiceMessageRecorderView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : ConstraintLayout(context, attrs, defStyleAttr) { + + interface Callback { + fun onVoiceRecordingStarted() + fun onVoiceRecordingEnded(recordTime: Long) + fun checkVoiceRecordingPermission(): Boolean + } + + private val views: ViewVoiceMessageRecorderBinding + + var callback: Callback? = null + + init { + inflate(context, R.layout.view_voice_message_recorder, this) + views = ViewVoiceMessageRecorderBinding.bind(this) + + views.voiceMessageButton.setRecordView(views.voiceMessageRecordView) + views.voiceMessageRecordView.timeLimit = BuildConfig.VOICE_MESSAGE_DURATION_LIMIT_MS + + views.voiceMessageRecordView.setRecordPermissionHandler { callback?.checkVoiceRecordingPermission().orFalse() } + + views.voiceMessageRecordView.setOnRecordListener(object : OnRecordListener { + override fun onStart() { + onVoiceRecordingStarted() + } + + override fun onCancel() { + onVoiceRecordingEnded(0) + } + + override fun onFinish(recordTime: Long, limitReached: Boolean) { + onVoiceRecordingEnded(recordTime) + } + + override fun onLessThanSecond() { + onVoiceRecordingEnded(0) + } + }) + } + + private fun onVoiceRecordingStarted() { + views.voiceMessageLockBackground.isVisible = true + views.voiceMessageLockArrow.isVisible = true + views.voiceMessageLockImage.isVisible = true + views.voiceMessageButton.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_voice_mic_recording)) + callback?.onVoiceRecordingStarted() + } + + private fun onVoiceRecordingEnded(recordTime: Long) { + views.voiceMessageLockBackground.isVisible = false + views.voiceMessageLockArrow.isVisible = false + views.voiceMessageLockImage.isVisible = false + views.voiceMessageButton.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_voice_mic)) + callback?.onVoiceRecordingEnded(recordTime) + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageRecordingHelper.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageRecordingHelper.kt new file mode 100644 index 0000000000..63cbbe6e79 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageRecordingHelper.kt @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.home.room.detail.composer + +import android.content.Context +import android.media.MediaRecorder +import androidx.core.content.FileProvider +import androidx.core.net.toUri +import im.vector.app.BuildConfig +import im.vector.lib.multipicker.entity.MultiPickerAudioType +import im.vector.lib.multipicker.utils.toMultiPickerAudioType +import timber.log.Timber +import java.io.File +import java.io.FileOutputStream +import java.lang.RuntimeException +import java.util.UUID +import javax.inject.Inject + +/** + * Helper class to record audio for voice messages. + */ +class VoiceMessageRecordingHelper @Inject constructor( + private val context: Context +) { + + private lateinit var mediaRecorder: MediaRecorder + private val outputDirectory = File(context.cacheDir, "downloads") + private var outputFile: File? = null + + init { + if (!outputDirectory.exists()) { + outputDirectory.mkdirs() + } + } + + private fun refreshMediaRecorder() { + mediaRecorder = MediaRecorder() + mediaRecorder.setAudioSource(MediaRecorder.AudioSource.DEFAULT) + mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.OGG) + mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.OPUS) + mediaRecorder.setAudioEncodingBitRate(24000) + mediaRecorder.setAudioSamplingRate(48000) + } + + fun startRecording() { + outputFile = File(outputDirectory, UUID.randomUUID().toString() + ".ogg") + FileOutputStream(outputFile).use { fos -> + refreshMediaRecorder() + mediaRecorder.setOutputFile(fos.fd) + mediaRecorder.prepare() + mediaRecorder.start() + } + } + + fun stopRecording(recordTime: Long): MultiPickerAudioType? { + try { + mediaRecorder.stop() + mediaRecorder.reset() + mediaRecorder.release() + outputFile?.let { + val outputFileUri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".fileProvider", it) + return outputFileUri?.toMultiPickerAudioType(context) + } ?: return null + } catch (e: RuntimeException) { // Usually thrown when the record is less than 1 second. + Timber.e(e, "Voice message is not valid. Record time: %s", recordTime) + return null + } + } + + fun deleteRecording() { + outputFile?.delete() + } +} diff --git a/vector/src/main/res/drawable/bg_voice_message_lock.xml b/vector/src/main/res/drawable/bg_voice_message_lock.xml new file mode 100644 index 0000000000..672d7bf80f --- /dev/null +++ b/vector/src/main/res/drawable/bg_voice_message_lock.xml @@ -0,0 +1,14 @@ + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/drawable/ic_voice_lock_arrow.xml b/vector/src/main/res/drawable/ic_voice_lock_arrow.xml new file mode 100644 index 0000000000..7f9f2403ad --- /dev/null +++ b/vector/src/main/res/drawable/ic_voice_lock_arrow.xml @@ -0,0 +1,13 @@ + + + diff --git a/vector/src/main/res/drawable/ic_voice_locked.xml b/vector/src/main/res/drawable/ic_voice_locked.xml new file mode 100644 index 0000000000..2b92d9d5e0 --- /dev/null +++ b/vector/src/main/res/drawable/ic_voice_locked.xml @@ -0,0 +1,5 @@ + + + diff --git a/vector/src/main/res/drawable/ic_voice_message_unlocked.xml b/vector/src/main/res/drawable/ic_voice_message_unlocked.xml new file mode 100644 index 0000000000..007de349ec --- /dev/null +++ b/vector/src/main/res/drawable/ic_voice_message_unlocked.xml @@ -0,0 +1,5 @@ + + + diff --git a/vector/src/main/res/drawable/ic_voice_mic.xml b/vector/src/main/res/drawable/ic_voice_mic.xml new file mode 100644 index 0000000000..7cb091afaa --- /dev/null +++ b/vector/src/main/res/drawable/ic_voice_mic.xml @@ -0,0 +1,12 @@ + + + + diff --git a/vector/src/main/res/drawable/ic_voice_mic_recording.xml b/vector/src/main/res/drawable/ic_voice_mic_recording.xml new file mode 100644 index 0000000000..eb6cea39a5 --- /dev/null +++ b/vector/src/main/res/drawable/ic_voice_mic_recording.xml @@ -0,0 +1,18 @@ + + + + + diff --git a/vector/src/main/res/drawable/ic_voice_slide_to_cancel_arrow.xml b/vector/src/main/res/drawable/ic_voice_slide_to_cancel_arrow.xml new file mode 100644 index 0000000000..1299e82530 --- /dev/null +++ b/vector/src/main/res/drawable/ic_voice_slide_to_cancel_arrow.xml @@ -0,0 +1,8 @@ + + + diff --git a/vector/src/main/res/layout/composer_layout.xml b/vector/src/main/res/layout/composer_layout.xml index 3816c206b4..7c9c23645d 100644 --- a/vector/src/main/res/layout/composer_layout.xml +++ b/vector/src/main/res/layout/composer_layout.xml @@ -131,4 +131,10 @@ android:src="@drawable/ic_send" tools:ignore="MissingConstraints" /> + + diff --git a/vector/src/main/res/layout/composer_layout_constraint_set_compact.xml b/vector/src/main/res/layout/composer_layout_constraint_set_compact.xml index 079bc9705a..b51f69302a 100644 --- a/vector/src/main/res/layout/composer_layout_constraint_set_compact.xml +++ b/vector/src/main/res/layout/composer_layout_constraint_set_compact.xml @@ -178,4 +178,12 @@ tools:ignore="MissingPrefix" tools:visibility="visible" /> + + \ No newline at end of file diff --git a/vector/src/main/res/layout/view_voice_message_recorder.xml b/vector/src/main/res/layout/view_voice_message_recorder.xml new file mode 100644 index 0000000000..3b124ae7ef --- /dev/null +++ b/vector/src/main/res/layout/view_voice_message_recorder.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/values/colors.xml b/vector/src/main/res/values/colors.xml index 7158554833..f66476a795 100644 --- a/vector/src/main/res/values/colors.xml +++ b/vector/src/main/res/values/colors.xml @@ -132,4 +132,9 @@ @color/black_alpha @android:color/transparent + + #FFF3F8FD + #22252B + #22252B + diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 80120b51bf..b2519f60b2 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -3399,4 +3399,8 @@ Some rooms may be hidden because they’re private and you need an invite. Unnamed Room + + Start Voice Message + Slide to cancel + Voice Message Lock From cb96886568e1969fdbe43f069a08ad792951ef32 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Thu, 17 Jun 2021 16:18:20 +0300 Subject: [PATCH 005/242] Send voice message. --- .../java/org/matrix/android/sdk/rx/RxRoom.kt | 5 ++++ .../room/model/message/MessageAudioContent.kt | 13 +++++++- .../crypto/model/rest/AudioWaveformInfo.kt | 30 +++++++++++++++++++ .../room/send/LocalEchoEventFactory.kt | 9 +++++- .../multipicker/utils/ContentResolverUtil.kt | 2 +- 5 files changed, 56 insertions(+), 3 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/AudioWaveformInfo.kt diff --git a/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxRoom.kt b/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxRoom.kt index 21db4e1893..b3495c4493 100644 --- a/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxRoom.kt +++ b/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxRoom.kt @@ -23,6 +23,7 @@ import io.reactivex.Single import kotlinx.coroutines.rx2.rxCompletable import kotlinx.coroutines.rx2.rxSingle import org.matrix.android.sdk.api.query.QueryStringValue +import org.matrix.android.sdk.api.session.content.ContentAttachmentData import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.identity.ThreePid import org.matrix.android.sdk.api.session.room.Room @@ -146,6 +147,10 @@ class RxRoom(private val room: Room) { fun deleteAvatar(): Completable = rxCompletable { room.deleteAvatar() } + + fun sendMedia(attachment: ContentAttachmentData, compressBeforeSending: Boolean, roomIds: Set): Completable = rxCompletable { + room.sendMedia(attachment, compressBeforeSending, roomIds) + } } fun Room.rx(): RxRoom { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageAudioContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageAudioContent.kt index fcf3d73cbe..3ce4cd6932 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageAudioContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageAudioContent.kt @@ -20,6 +20,7 @@ import com.squareup.moshi.Json import com.squareup.moshi.JsonClass import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent +import org.matrix.android.sdk.internal.crypto.model.rest.AudioWaveformInfo import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileInfo @JsonClass(generateAdapter = true) @@ -50,7 +51,17 @@ data class MessageAudioContent( /** * Required if the file is encrypted. Information on the encrypted file, as specified in End-to-end encryption. */ - @Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null + @Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null, + + /** + * Encapsulates waveform and duration of the audio. + */ + @Json(name = "org.matrix.msc1767.audio") val audioWaveformInfo: AudioWaveformInfo? = null, + + /** + * Indicates that is a voice message. + */ + @Json(name = "org.matrix.msc2516.voice") val voiceMessageIndicator: Any? = null ) : MessageWithAttachmentContent { override val mimeType: String? diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/AudioWaveformInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/AudioWaveformInfo.kt new file mode 100644 index 0000000000..31a356e164 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/AudioWaveformInfo.kt @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.crypto.model.rest + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class AudioWaveformInfo( + @Json(name = "duration") + val duration: Long? = null, + + @Json(name = "waveform") + val waveform: List? = null + +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt index f505b13b33..03afa4beb6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt @@ -53,6 +53,7 @@ import org.matrix.android.sdk.api.session.room.model.relation.ReplyToContent import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent import org.matrix.android.sdk.api.session.room.timeline.isReply +import org.matrix.android.sdk.internal.crypto.model.rest.AudioWaveformInfo import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.content.ThumbnailExtractor import org.matrix.android.sdk.internal.session.permalinks.PermalinkFactory @@ -289,6 +290,7 @@ internal class LocalEchoEventFactory @Inject constructor( } private fun createAudioEvent(roomId: String, attachment: ContentAttachmentData): Event { + val isVoiceMessage = attachment.mimeType == "audio/ogg" val content = MessageAudioContent( msgType = MessageType.MSGTYPE_AUDIO, body = attachment.name ?: "audio", @@ -296,7 +298,12 @@ internal class LocalEchoEventFactory @Inject constructor( mimeType = attachment.getSafeMimeType()?.takeIf { it.isNotBlank() }, size = attachment.size ), - url = attachment.queryUri.toString() + url = attachment.queryUri.toString(), + audioWaveformInfo = if (!isVoiceMessage) null else AudioWaveformInfo( + duration = attachment.duration, + waveform = null // TODO. + ), + voiceMessageIndicator = if (!isVoiceMessage) null else Any() ) return createMessageEvent(roomId, content) } diff --git a/multipicker/src/main/java/im/vector/lib/multipicker/utils/ContentResolverUtil.kt b/multipicker/src/main/java/im/vector/lib/multipicker/utils/ContentResolverUtil.kt index a1982b0bbc..6cadfa6ce7 100644 --- a/multipicker/src/main/java/im/vector/lib/multipicker/utils/ContentResolverUtil.kt +++ b/multipicker/src/main/java/im/vector/lib/multipicker/utils/ContentResolverUtil.kt @@ -111,7 +111,7 @@ internal fun Uri.toMultiPickerVideoType(context: Context): MultiPickerVideoType? } } -internal fun Uri.toMultiPickerAudioType(context: Context): MultiPickerAudioType? { +fun Uri.toMultiPickerAudioType(context: Context): MultiPickerAudioType? { val projection = arrayOf( MediaStore.Audio.Media.DISPLAY_NAME, MediaStore.Audio.Media.SIZE From 5676226f4229c42a25367c57d2d60bc6b5544484 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Thu, 1 Jul 2021 10:47:41 +0300 Subject: [PATCH 006/242] Voice message recording view implementations. --- build.gradle | 2 +- vector/build.gradle | 4 +- .../room/detail/composer/TextComposerView.kt | 31 +-- .../composer/VoiceMessageRecordingHelper.kt | 87 ------- .../detail/timeline/item/MessageVoiceItem.kt | 134 +++++++++++ .../drawable/bg_voice_play_pause_button.xml | 5 + .../main/res/drawable/bg_voice_playback.xml | 12 + ...locked.xml => ic_voice_message_locked.xml} | 0 .../src/main/res/drawable/ic_voice_pause.xml | 12 + .../src/main/res/drawable/ic_voice_play.xml | 9 + .../src/main/res/layout/composer_layout.xml | 16 +- ...composer_layout_constraint_set_compact.xml | 18 +- .../main/res/layout/fragment_room_detail.xml | 8 + .../res/layout/item_timeline_event_base.xml | 7 + .../item_timeline_event_base_noinfo.xml | 2 +- .../layout/item_timeline_event_voice_stub.xml | 87 +++++++ .../layout/view_voice_message_recorder.xml | 225 +++++++++++++++--- vector/src/main/res/values/colors.xml | 13 +- vector/src/main/res/values/strings.xml | 8 + vector/src/main/res/values/theme_dark.xml | 6 + vector/src/main/res/values/theme_light.xml | 6 + 21 files changed, 523 insertions(+), 169 deletions(-) delete mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageRecordingHelper.kt create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt create mode 100644 vector/src/main/res/drawable/bg_voice_play_pause_button.xml create mode 100644 vector/src/main/res/drawable/bg_voice_playback.xml rename vector/src/main/res/drawable/{ic_voice_locked.xml => ic_voice_message_locked.xml} (100%) create mode 100644 vector/src/main/res/drawable/ic_voice_pause.xml create mode 100644 vector/src/main/res/drawable/ic_voice_play.xml create mode 100644 vector/src/main/res/layout/item_timeline_event_voice_stub.xml diff --git a/build.gradle b/build.gradle index a7acc1c124..df412d3fe8 100644 --- a/build.gradle +++ b/build.gradle @@ -50,7 +50,7 @@ allprojects { includeGroupByRegex 'nl\\.dionsegijn' // Voice RecordView - includeGroupByRegex 'com\\.github\\.3llomi' + includeGroupByRegex 'com\\.github\\.Armen101' } } maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' } diff --git a/vector/build.gradle b/vector/build.gradle index 991e483e98..2d662ea242 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -331,7 +331,7 @@ dependencies { implementation "androidx.recyclerview:recyclerview:1.2.1" implementation 'androidx.appcompat:appcompat:1.3.0' implementation "androidx.fragment:fragment-ktx:$fragment_version" - implementation 'androidx.constraintlayout:constraintlayout:2.0.4' + implementation 'androidx.constraintlayout:constraintlayout:2.1.0-beta02' implementation "androidx.sharetarget:sharetarget:1.1.0" implementation 'androidx.core:core-ktx:1.5.0' implementation "androidx.media:media:1.3.1" @@ -393,7 +393,7 @@ dependencies { implementation "androidx.autofill:autofill:$autofill_version" implementation 'jp.wasabeef:glide-transformations:4.3.0' implementation 'com.github.vector-im:PFLockScreen-Android:1.0.0-beta12' - implementation 'com.github.3llomi:RecordView:3.0.1' + implementation 'com.github.Armen101:AudioRecordView:1.0.5' // Custom Tab implementation 'androidx.browser:browser:1.3.0' diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerView.kt index 6672027133..7833864707 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerView.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerView.kt @@ -20,6 +20,7 @@ import android.content.Context import android.net.Uri import android.text.Editable import android.util.AttributeSet +import android.view.KeyEvent import android.view.ViewGroup import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet @@ -48,9 +49,7 @@ class TextComposerView @JvmOverloads constructor( fun onCloseRelatedMessage() fun onSendMessage(text: CharSequence) fun onAddAttachment() - fun onVoiceRecordingStarted() - fun onVoiceRecordingEnded(recordTime: Long) - fun checkVoiceRecordingPermission(): Boolean + fun onTouchVoiceRecording() } val views: ComposerLayoutBinding @@ -78,7 +77,7 @@ class TextComposerView @JvmOverloads constructor( override fun onTextEmptyStateChanged(isEmpty: Boolean) { val shouldShowSendButton = currentConstraintSetId == R.layout.composer_layout_constraint_set_expanded || !isEmpty views.sendButton.isInvisible = !shouldShowSendButton - views.voiceMessageRecorderView.isVisible = !shouldShowSendButton + callback?.onTextEmptyStateChanged(isEmpty) } } views.composerRelatedMessageCloseButton.setOnClickListener { @@ -94,28 +93,6 @@ class TextComposerView @JvmOverloads constructor( views.attachmentButton.setOnClickListener { callback?.onAddAttachment() } - - views.voiceMessageRecorderView.callback = object : VoiceMessageRecorderView.Callback { - override fun onVoiceRecordingStarted() { - views.attachmentButton.isVisible = false - views.composerEditText.isVisible = false - views.composerEmojiButton.isVisible = false - views.composerEditTextOuterBorder.isVisible = false - callback?.onVoiceRecordingStarted() - } - - override fun onVoiceRecordingEnded(recordTime: Long) { - views.attachmentButton.isVisible = true - views.composerEditText.isVisible = true - views.composerEmojiButton.isVisible = true - views.composerEditTextOuterBorder.isVisible = true - callback?.onVoiceRecordingEnded(recordTime) - } - - override fun checkVoiceRecordingPermission(): Boolean { - return callback?.checkVoiceRecordingPermission().orFalse() - } - } } fun collapse(animate: Boolean = true, transitionComplete: (() -> Unit)? = null) { @@ -128,7 +105,6 @@ class TextComposerView @JvmOverloads constructor( val shouldShowSendButton = !views.composerEditText.text.isNullOrEmpty() views.sendButton.isInvisible = !shouldShowSendButton - views.voiceMessageRecorderView.isVisible = !shouldShowSendButton } fun expand(animate: Boolean = true, transitionComplete: (() -> Unit)? = null) { @@ -139,7 +115,6 @@ class TextComposerView @JvmOverloads constructor( currentConstraintSetId = R.layout.composer_layout_constraint_set_expanded applyNewConstraintSet(animate, transitionComplete) views.sendButton.isInvisible = false - views.voiceMessageRecorderView.isVisible = false } private fun applyNewConstraintSet(animate: Boolean, transitionComplete: (() -> Unit)?) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageRecordingHelper.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageRecordingHelper.kt deleted file mode 100644 index 63cbbe6e79..0000000000 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageRecordingHelper.kt +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (c) 2021 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.app.features.home.room.detail.composer - -import android.content.Context -import android.media.MediaRecorder -import androidx.core.content.FileProvider -import androidx.core.net.toUri -import im.vector.app.BuildConfig -import im.vector.lib.multipicker.entity.MultiPickerAudioType -import im.vector.lib.multipicker.utils.toMultiPickerAudioType -import timber.log.Timber -import java.io.File -import java.io.FileOutputStream -import java.lang.RuntimeException -import java.util.UUID -import javax.inject.Inject - -/** - * Helper class to record audio for voice messages. - */ -class VoiceMessageRecordingHelper @Inject constructor( - private val context: Context -) { - - private lateinit var mediaRecorder: MediaRecorder - private val outputDirectory = File(context.cacheDir, "downloads") - private var outputFile: File? = null - - init { - if (!outputDirectory.exists()) { - outputDirectory.mkdirs() - } - } - - private fun refreshMediaRecorder() { - mediaRecorder = MediaRecorder() - mediaRecorder.setAudioSource(MediaRecorder.AudioSource.DEFAULT) - mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.OGG) - mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.OPUS) - mediaRecorder.setAudioEncodingBitRate(24000) - mediaRecorder.setAudioSamplingRate(48000) - } - - fun startRecording() { - outputFile = File(outputDirectory, UUID.randomUUID().toString() + ".ogg") - FileOutputStream(outputFile).use { fos -> - refreshMediaRecorder() - mediaRecorder.setOutputFile(fos.fd) - mediaRecorder.prepare() - mediaRecorder.start() - } - } - - fun stopRecording(recordTime: Long): MultiPickerAudioType? { - try { - mediaRecorder.stop() - mediaRecorder.reset() - mediaRecorder.release() - outputFile?.let { - val outputFileUri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".fileProvider", it) - return outputFileUri?.toMultiPickerAudioType(context) - } ?: return null - } catch (e: RuntimeException) { // Usually thrown when the record is less than 1 second. - Timber.e(e, "Voice message is not valid. Record time: %s", recordTime) - return null - } - } - - fun deleteRecording() { - outputFile?.delete() - } -} diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt new file mode 100644 index 0000000000..cfca70840a --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.home.room.detail.timeline.item + +import android.text.format.DateUtils +import android.view.ViewGroup +import android.widget.ImageButton +import android.widget.TextView +import androidx.core.view.isVisible +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import com.visualizer.amplitude.AudioRecordView +import im.vector.app.R +import im.vector.app.core.epoxy.ClickListener +import im.vector.app.features.home.room.detail.timeline.helper.ContentDownloadStateTrackerBinder +import im.vector.app.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder +import im.vector.app.features.home.room.detail.timeline.helper.VoiceMessagePlaybackTracker + +@EpoxyModelClass(layout = R.layout.item_timeline_event_base) +abstract class MessageVoiceItem : AbsMessageItem() { + + @EpoxyAttribute + var mxcUrl: String = "" + + @EpoxyAttribute + var duration: Int = 0 + + @EpoxyAttribute + var waveform: List = emptyList() + + @EpoxyAttribute + var izLocalFile = false + + @EpoxyAttribute + var izDownloaded = false + + @EpoxyAttribute + lateinit var contentUploadStateTrackerBinder: ContentUploadStateTrackerBinder + + @EpoxyAttribute + lateinit var contentDownloadStateTrackerBinder: ContentDownloadStateTrackerBinder + + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) + var playbackControlButtonClickListener: ClickListener? = null + + @EpoxyAttribute + lateinit var voiceMessagePlaybackTracker: VoiceMessagePlaybackTracker + + + override fun bind(holder: Holder) { + super.bind(holder) + renderSendState(holder.voiceLayout, null) + if (!attributes.informationData.sendState.hasFailed()) { + contentUploadStateTrackerBinder.bind(attributes.informationData.eventId, izLocalFile, holder.progressLayout) + } else { + holder.voicePlaybackControlButton.setImageResource(R.drawable.ic_cross) + holder.progressLayout.isVisible = false + } + + holder.voicePlaybackTime.text = formatPlaybackTime(duration) + + holder.voicePlaybackWaveform.post { + holder.voicePlaybackWaveform.recreate() + waveform.forEach { amplitude -> + holder.voicePlaybackWaveform.update(amplitude) + } + } + + holder.voicePlaybackControlButton.setOnClickListener { playbackControlButtonClickListener?.invoke(it) } + + voiceMessagePlaybackTracker.track(attributes.informationData.eventId, object : VoiceMessagePlaybackTracker.Listener { + override fun onUpdate(state: VoiceMessagePlaybackTracker.Listener.State) { + when (state) { + is VoiceMessagePlaybackTracker.Listener.State.Idle -> handleIdleState(holder, state) + is VoiceMessagePlaybackTracker.Listener.State.Playing -> handlePlayingState(holder, state) + } + } + }) + } + + private fun handleIdleState(holder: Holder, state: VoiceMessagePlaybackTracker.Listener.State.Idle) { + holder.voicePlaybackControlButton.setImageResource(R.drawable.ic_voice_play) + if (state.playbackTime > 0) { + holder.voicePlaybackTime.text = formatPlaybackTime(state.playbackTime) + } else { + holder.voicePlaybackTime.text = formatPlaybackTime(duration) + } + } + + private fun handlePlayingState(holder: Holder, state: VoiceMessagePlaybackTracker.Listener.State.Playing) { + holder.voicePlaybackControlButton.setImageResource(R.drawable.ic_voice_pause) + if (state.playbackTime > 0) { + holder.voicePlaybackTime.text = formatPlaybackTime(state.playbackTime) + } else { + holder.voicePlaybackTime.text = formatPlaybackTime(duration) + } + } + + private fun formatPlaybackTime(time: Int) = DateUtils.formatElapsedTime((time / 1000).toLong()) + + override fun unbind(holder: Holder) { + super.unbind(holder) + contentUploadStateTrackerBinder.unbind(attributes.informationData.eventId) + contentDownloadStateTrackerBinder.unbind(mxcUrl) + } + + override fun getViewType() = STUB_ID + + class Holder : AbsMessageItem.Holder(STUB_ID) { + val voiceLayout by bind(R.id.voiceLayout) + val voicePlaybackControlButton by bind(R.id.voicePlaybackControlButton) + val voicePlaybackTime by bind(R.id.voicePlaybackTime) + val voicePlaybackWaveform by bind(R.id.voicePlaybackWaveform) + val progressLayout by bind(R.id.messageFileUploadProgressLayout) + } + + companion object { + private const val STUB_ID = R.id.messageContentVoiceStub + } +} diff --git a/vector/src/main/res/drawable/bg_voice_play_pause_button.xml b/vector/src/main/res/drawable/bg_voice_play_pause_button.xml new file mode 100644 index 0000000000..c0b14c77e9 --- /dev/null +++ b/vector/src/main/res/drawable/bg_voice_play_pause_button.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/vector/src/main/res/drawable/bg_voice_playback.xml b/vector/src/main/res/drawable/bg_voice_playback.xml new file mode 100644 index 0000000000..db31e29bc7 --- /dev/null +++ b/vector/src/main/res/drawable/bg_voice_playback.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/drawable/ic_voice_locked.xml b/vector/src/main/res/drawable/ic_voice_message_locked.xml similarity index 100% rename from vector/src/main/res/drawable/ic_voice_locked.xml rename to vector/src/main/res/drawable/ic_voice_message_locked.xml diff --git a/vector/src/main/res/drawable/ic_voice_pause.xml b/vector/src/main/res/drawable/ic_voice_pause.xml new file mode 100644 index 0000000000..af74dec46c --- /dev/null +++ b/vector/src/main/res/drawable/ic_voice_pause.xml @@ -0,0 +1,12 @@ + + + + diff --git a/vector/src/main/res/drawable/ic_voice_play.xml b/vector/src/main/res/drawable/ic_voice_play.xml new file mode 100644 index 0000000000..ad90006799 --- /dev/null +++ b/vector/src/main/res/drawable/ic_voice_play.xml @@ -0,0 +1,9 @@ + + + diff --git a/vector/src/main/res/layout/composer_layout.xml b/vector/src/main/res/layout/composer_layout.xml index 7c9c23645d..5e40ab275e 100644 --- a/vector/src/main/res/layout/composer_layout.xml +++ b/vector/src/main/res/layout/composer_layout.xml @@ -131,10 +131,16 @@ android:src="@drawable/ic_send" tools:ignore="MissingConstraints" /> - + diff --git a/vector/src/main/res/layout/composer_layout_constraint_set_compact.xml b/vector/src/main/res/layout/composer_layout_constraint_set_compact.xml index b51f69302a..e429cf7d16 100644 --- a/vector/src/main/res/layout/composer_layout_constraint_set_compact.xml +++ b/vector/src/main/res/layout/composer_layout_constraint_set_compact.xml @@ -178,12 +178,18 @@ tools:ignore="MissingPrefix" tools:visibility="visible" /> - + app:layout_constraintEnd_toEndOf="parent" /> + --> \ No newline at end of file diff --git a/vector/src/main/res/layout/fragment_room_detail.xml b/vector/src/main/res/layout/fragment_room_detail.xml index ae7494894c..6a5cae4452 100644 --- a/vector/src/main/res/layout/fragment_room_detail.xml +++ b/vector/src/main/res/layout/fragment_room_detail.xml @@ -248,4 +248,12 @@ android:background="?vctr_chat_effect_snow_background" android:visibility="invisible" /> + + diff --git a/vector/src/main/res/layout/item_timeline_event_base.xml b/vector/src/main/res/layout/item_timeline_event_base.xml index e4d7aa7d9f..f753bc18a9 100644 --- a/vector/src/main/res/layout/item_timeline_event_base.xml +++ b/vector/src/main/res/layout/item_timeline_event_base.xml @@ -132,6 +132,13 @@ android:layout_marginEnd="56dp" android:layout="@layout/item_timeline_event_option_buttons_stub" /> + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/layout/view_voice_message_recorder.xml b/vector/src/main/res/layout/view_voice_message_recorder.xml index 3b124ae7ef..bf48ee1513 100644 --- a/vector/src/main/res/layout/view_voice_message_recorder.xml +++ b/vector/src/main/res/layout/view_voice_message_recorder.xml @@ -2,68 +2,217 @@ + android:layout_height="match_parent"> + app:layout_constraintTop_toBottomOf="@id/voiceMessageMicButton" /> - - - + + + + + + + + + + + tools:ignore="ContentDescription" /> - + tools:visibility="visible" + android:layout_marginBottom="4dp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toStartOf="@id/voiceMessageMicButton" + app:layout_constraintStart_toStartOf="parent" > + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/values/colors.xml b/vector/src/main/res/values/colors.xml index f66476a795..f756b4f243 100644 --- a/vector/src/main/res/values/colors.xml +++ b/vector/src/main/res/values/colors.xml @@ -135,6 +135,17 @@ #FFF3F8FD #22252B - #22252B + + + #FFE3E8F0 + #FF394049 + + + @android:color/white + #FF8E99A4 + + + #FFE3E8F0 + #FF394049 diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index b2519f60b2..0dbb65a138 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -2605,6 +2605,7 @@ Video. Image. Audio + Voice File Sticker Poll @@ -3403,4 +3404,11 @@ Start Voice Message Slide to cancel Voice Message Lock + Play Voice Message + Pause Voice Message + Recording voice message + Delete recorded voice message + Release to send + %1$ds left + Tap on the waveform to stop and playback diff --git a/vector/src/main/res/values/theme_dark.xml b/vector/src/main/res/values/theme_dark.xml index 040e73501a..385388d3d4 100644 --- a/vector/src/main/res/values/theme_dark.xml +++ b/vector/src/main/res/values/theme_dark.xml @@ -144,6 +144,12 @@ @style/WidgetButtonSocialLogin.Gitlab.Dark @style/ActionModeTheme + + + @color/vctr_voice_message_lock_background_dark + @color/vctr_voice_message_playback_background_dark + @color/vctr_voice_message_play_pause_button_background_dark + @color/vctr_voice_message_recording_playback_background_dark - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/vector/src/main/res/drawable/bg_voice_message_lock.xml b/vector/src/main/res/drawable/bg_voice_message_lock.xml index 672d7bf80f..16fcabc75f 100644 --- a/vector/src/main/res/drawable/bg_voice_message_lock.xml +++ b/vector/src/main/res/drawable/bg_voice_message_lock.xml @@ -4,7 +4,7 @@ android:width="56dp" android:height="160dp" /> - + Date: Fri, 9 Jul 2021 18:05:41 +0200 Subject: [PATCH 017/242] Fix issue with play / pause button alignment --- .../room/detail/composer/VoiceMessageRecorderView.kt | 4 ++-- .../room/detail/timeline/item/MessageVoiceItem.kt | 4 ++-- vector/src/main/res/drawable/ic_play_pause_pause.xml | 12 ++++++++++++ vector/src/main/res/drawable/ic_play_pause_play.xml | 9 +++++++++ vector/src/main/res/drawable/ic_voice_pause.xml | 12 ------------ vector/src/main/res/drawable/ic_voice_play.xml | 9 --------- .../res/layout/item_timeline_event_voice_stub.xml | 7 +++---- .../main/res/layout/view_voice_message_recorder.xml | 7 +++---- 8 files changed, 31 insertions(+), 33 deletions(-) create mode 100644 vector/src/main/res/drawable/ic_play_pause_pause.xml create mode 100644 vector/src/main/res/drawable/ic_play_pause_play.xml delete mode 100644 vector/src/main/res/drawable/ic_voice_pause.xml delete mode 100644 vector/src/main/res/drawable/ic_voice_play.xml diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageRecorderView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageRecorderView.kt index 13679da52b..25bc41e497 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageRecorderView.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageRecorderView.kt @@ -359,12 +359,12 @@ class VoiceMessageRecorderView @JvmOverloads constructor( this.amplitudeList = state.amplitudeList } is VoiceMessagePlaybackTracker.Listener.State.Playing -> { - views.voicePlaybackControlButton.setImageResource(R.drawable.ic_voice_pause) + views.voicePlaybackControlButton.setImageResource(R.drawable.ic_play_pause_pause) val formattedTimerText = DateUtils.formatElapsedTime((state.playbackTime / 1000).toLong()) views.voicePlaybackTime.setText(formattedTimerText) } is VoiceMessagePlaybackTracker.Listener.State.Idle -> { - views.voicePlaybackControlButton.setImageResource(R.drawable.ic_voice_play) + views.voicePlaybackControlButton.setImageResource(R.drawable.ic_play_pause_play) } } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt index 9202d7f72a..a369ed1df7 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt @@ -92,7 +92,7 @@ abstract class MessageVoiceItem : AbsMessageItem() { } private fun handleIdleState(holder: Holder, state: VoiceMessagePlaybackTracker.Listener.State.Idle) { - holder.voicePlaybackControlButton.setImageResource(R.drawable.ic_voice_play) + holder.voicePlaybackControlButton.setImageResource(R.drawable.ic_play_pause_play) if (state.playbackTime > 0) { holder.voicePlaybackTime.text = formatPlaybackTime(state.playbackTime) } else { @@ -101,7 +101,7 @@ abstract class MessageVoiceItem : AbsMessageItem() { } private fun handlePlayingState(holder: Holder, state: VoiceMessagePlaybackTracker.Listener.State.Playing) { - holder.voicePlaybackControlButton.setImageResource(R.drawable.ic_voice_pause) + holder.voicePlaybackControlButton.setImageResource(R.drawable.ic_play_pause_pause) if (state.playbackTime > 0) { holder.voicePlaybackTime.text = formatPlaybackTime(state.playbackTime) } else { diff --git a/vector/src/main/res/drawable/ic_play_pause_pause.xml b/vector/src/main/res/drawable/ic_play_pause_pause.xml new file mode 100644 index 0000000000..d8cfafbcf5 --- /dev/null +++ b/vector/src/main/res/drawable/ic_play_pause_pause.xml @@ -0,0 +1,12 @@ + + + + diff --git a/vector/src/main/res/drawable/ic_play_pause_play.xml b/vector/src/main/res/drawable/ic_play_pause_play.xml new file mode 100644 index 0000000000..84056219ec --- /dev/null +++ b/vector/src/main/res/drawable/ic_play_pause_play.xml @@ -0,0 +1,9 @@ + + + diff --git a/vector/src/main/res/drawable/ic_voice_pause.xml b/vector/src/main/res/drawable/ic_voice_pause.xml deleted file mode 100644 index af74dec46c..0000000000 --- a/vector/src/main/res/drawable/ic_voice_pause.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - diff --git a/vector/src/main/res/drawable/ic_voice_play.xml b/vector/src/main/res/drawable/ic_voice_play.xml deleted file mode 100644 index ad90006799..0000000000 --- a/vector/src/main/res/drawable/ic_voice_play.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/vector/src/main/res/layout/item_timeline_event_voice_stub.xml b/vector/src/main/res/layout/item_timeline_event_voice_stub.xml index eed7ae4495..2e10ad1550 100644 --- a/vector/src/main/res/layout/item_timeline_event_voice_stub.xml +++ b/vector/src/main/res/layout/item_timeline_event_voice_stub.xml @@ -27,12 +27,11 @@ android:layout_height="32dp" android:background="@drawable/bg_voice_play_pause_button" android:backgroundTint="?vctr_voice_message_play_pause_button_background" - android:paddingStart="3dp" - android:paddingEnd="0dp" - android:src="@drawable/ic_voice_play" + android:src="@drawable/ic_play_pause_play" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" - app:layout_constraintTop_toTopOf="parent" /> + app:layout_constraintTop_toTopOf="parent" + app:tint="?vctr_content_secondary" /> + app:layout_constraintTop_toTopOf="parent" + app:tint="?vctr_content_secondary" /> Date: Fri, 9 Jul 2021 18:22:52 +0200 Subject: [PATCH 018/242] Apply missing tint --- .../main/res/drawable/ic_voice_lock_arrow.xml | 14 +++++++------- .../ic_voice_slide_to_cancel_arrow.xml | 18 ++++++++++++------ .../res/layout/view_voice_message_recorder.xml | 11 +++++++---- 3 files changed, 26 insertions(+), 17 deletions(-) diff --git a/vector/src/main/res/drawable/ic_voice_lock_arrow.xml b/vector/src/main/res/drawable/ic_voice_lock_arrow.xml index 7f9f2403ad..d23dfaa932 100644 --- a/vector/src/main/res/drawable/ic_voice_lock_arrow.xml +++ b/vector/src/main/res/drawable/ic_voice_lock_arrow.xml @@ -3,11 +3,11 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/vector/src/main/res/drawable/ic_voice_slide_to_cancel_arrow.xml b/vector/src/main/res/drawable/ic_voice_slide_to_cancel_arrow.xml index 1299e82530..59beaaf52f 100644 --- a/vector/src/main/res/drawable/ic_voice_slide_to_cancel_arrow.xml +++ b/vector/src/main/res/drawable/ic_voice_slide_to_cancel_arrow.xml @@ -1,8 +1,14 @@ - - + + android:strokeWidth="2" + android:strokeColor="#F00" + android:strokeLineCap="round" + android:strokeLineJoin="round" /> diff --git a/vector/src/main/res/layout/view_voice_message_recorder.xml b/vector/src/main/res/layout/view_voice_message_recorder.xml index d1d812d0a1..7cca6e670a 100644 --- a/vector/src/main/res/layout/view_voice_message_recorder.xml +++ b/vector/src/main/res/layout/view_voice_message_recorder.xml @@ -16,7 +16,7 @@ android:visibility="gone" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@id/voiceMessageMicButton" - tools:layout_constraintBottom_toBottomOf="parent" + tools:translationY="-148dp" tools:visibility="visible" /> Date: Fri, 9 Jul 2021 18:26:58 +0200 Subject: [PATCH 019/242] Fix touchable area to delete the voice message --- vector/src/main/res/layout/view_voice_message_recorder.xml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/vector/src/main/res/layout/view_voice_message_recorder.xml b/vector/src/main/res/layout/view_voice_message_recorder.xml index 7cca6e670a..ee8dd677ac 100644 --- a/vector/src/main/res/layout/view_voice_message_recorder.xml +++ b/vector/src/main/res/layout/view_voice_message_recorder.xml @@ -121,7 +121,6 @@ android:id="@+id/voiceMessagePlaybackLayout" android:layout_width="0dp" android:layout_height="44dp" - android:layout_marginStart="16dp" android:layout_marginEnd="16dp" android:layout_marginBottom="4dp" android:visibility="gone" @@ -133,8 +132,8 @@ Date: Fri, 9 Jul 2021 20:58:56 +0200 Subject: [PATCH 020/242] Theme for Toast --- library/ui-styles/build.gradle | 2 ++ .../main/res/drawable/bg_round_corner_8dp.xml | 8 +++++ .../ui-styles/src/main/res/values/colors.xml | 4 +++ .../main/res/values/styles_voice_message.xml | 11 ++++++ .../src/main/res/values/theme_dark.xml | 1 + .../src/main/res/values/theme_light.xml | 1 + .../home/room/detail/RoomDetailFragment.kt | 8 +++-- .../composer/VoiceMessageRecorderView.kt | 35 ++++++++++++------- .../layout/view_voice_message_recorder.xml | 15 +++++++- 9 files changed, 69 insertions(+), 16 deletions(-) create mode 100644 library/ui-styles/src/main/res/drawable/bg_round_corner_8dp.xml diff --git a/library/ui-styles/build.gradle b/library/ui-styles/build.gradle index 47c4664636..3a8851784e 100644 --- a/library/ui-styles/build.gradle +++ b/library/ui-styles/build.gradle @@ -60,4 +60,6 @@ dependencies { implementation 'com.github.vector-im:PFLockScreen-Android:1.0.0-beta12' // dialpad dimen implementation 'im.dlg:android-dialer:1.2.5' + // AudioRecordView attr + implementation 'com.github.Armen101:AudioRecordView:1.0.5' } \ No newline at end of file diff --git a/library/ui-styles/src/main/res/drawable/bg_round_corner_8dp.xml b/library/ui-styles/src/main/res/drawable/bg_round_corner_8dp.xml new file mode 100644 index 0000000000..f44b146021 --- /dev/null +++ b/library/ui-styles/src/main/res/drawable/bg_round_corner_8dp.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/library/ui-styles/src/main/res/values/colors.xml b/library/ui-styles/src/main/res/values/colors.xml index bfe9a9a59c..927977431b 100644 --- a/library/ui-styles/src/main/res/values/colors.xml +++ b/library/ui-styles/src/main/res/values/colors.xml @@ -144,4 +144,8 @@ #FFE3E8F0 #FF394049 + + @color/palette_black_900 + @color/palette_gray_450 + diff --git a/library/ui-styles/src/main/res/values/styles_voice_message.xml b/library/ui-styles/src/main/res/values/styles_voice_message.xml index 29ba6e31d9..dff1d08d28 100644 --- a/library/ui-styles/src/main/res/values/styles_voice_message.xml +++ b/library/ui-styles/src/main/res/values/styles_voice_message.xml @@ -12,4 +12,15 @@ leftToRight + + \ No newline at end of file diff --git a/library/ui-styles/src/main/res/values/theme_dark.xml b/library/ui-styles/src/main/res/values/theme_dark.xml index 6dcf02139a..43693031a3 100644 --- a/library/ui-styles/src/main/res/values/theme_dark.xml +++ b/library/ui-styles/src/main/res/values/theme_dark.xml @@ -140,6 +140,7 @@ @color/vctr_voice_message_playback_background_dark @color/vctr_voice_message_play_pause_button_background_dark @color/vctr_voice_message_recording_playback_background_dark + @color/vctr_voice_message_toast_background_dark diff --git a/library/ui-styles/src/main/res/values/theme_light.xml b/library/ui-styles/src/main/res/values/theme_light.xml index 2eaba62550..974a9e0b24 100644 --- a/library/ui-styles/src/main/res/values/theme_light.xml +++ b/library/ui-styles/src/main/res/values/theme_light.xml @@ -140,7 +140,6 @@ @color/vctr_voice_message_lock_background_light @color/vctr_voice_message_playback_background_light - @color/vctr_voice_message_play_pause_button_background_light @color/vctr_voice_message_recording_playback_background_light @color/vctr_voice_message_toast_background_light diff --git a/vector/src/main/res/layout/item_timeline_event_voice_stub.xml b/vector/src/main/res/layout/item_timeline_event_voice_stub.xml index 2e10ad1550..26b8742086 100644 --- a/vector/src/main/res/layout/item_timeline_event_voice_stub.xml +++ b/vector/src/main/res/layout/item_timeline_event_voice_stub.xml @@ -26,7 +26,7 @@ android:layout_width="32dp" android:layout_height="32dp" android:background="@drawable/bg_voice_play_pause_button" - android:backgroundTint="?vctr_voice_message_play_pause_button_background" + android:backgroundTint="?android:colorBackground" android:src="@drawable/ic_play_pause_play" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" diff --git a/vector/src/main/res/layout/view_voice_message_recorder.xml b/vector/src/main/res/layout/view_voice_message_recorder.xml index 0c975aa9cb..5b48ca4a43 100644 --- a/vector/src/main/res/layout/view_voice_message_recorder.xml +++ b/vector/src/main/res/layout/view_voice_message_recorder.xml @@ -173,7 +173,7 @@ android:layout_height="32dp" android:layout_marginStart="8dp" android:background="@drawable/bg_voice_play_pause_button" - android:backgroundTint="?vctr_voice_message_play_pause_button_background" + android:backgroundTint="?android:colorBackground" android:contentDescription="@string/a11y_play_voice_message" android:src="@drawable/ic_play_pause_play" app:layout_constraintBottom_toBottomOf="parent" From 14dbbee1e3a82ed9d236eb6f98d11c150c4275eb Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 13 Jul 2021 16:10:51 +0200 Subject: [PATCH 042/242] Fix background color of voice message item --- library/ui-styles/src/main/res/values/colors.xml | 4 ---- library/ui-styles/src/main/res/values/theme_dark.xml | 1 - library/ui-styles/src/main/res/values/theme_light.xml | 1 - vector/src/main/res/layout/item_timeline_event_voice_stub.xml | 2 +- 4 files changed, 1 insertion(+), 7 deletions(-) diff --git a/library/ui-styles/src/main/res/values/colors.xml b/library/ui-styles/src/main/res/values/colors.xml index d7097b404d..1a032d94b6 100644 --- a/library/ui-styles/src/main/res/values/colors.xml +++ b/library/ui-styles/src/main/res/values/colors.xml @@ -132,10 +132,6 @@ #FFF3F8FD #22252B - - #FFE3E8F0 - #FF394049 - #FFE3E8F0 #FF394049 diff --git a/library/ui-styles/src/main/res/values/theme_dark.xml b/library/ui-styles/src/main/res/values/theme_dark.xml index 18ed21ae14..5826f5fcd3 100644 --- a/library/ui-styles/src/main/res/values/theme_dark.xml +++ b/library/ui-styles/src/main/res/values/theme_dark.xml @@ -137,7 +137,6 @@ @color/vctr_voice_message_lock_background_dark - @color/vctr_voice_message_playback_background_dark @color/vctr_voice_message_recording_playback_background_dark @color/vctr_voice_message_toast_background_dark diff --git a/library/ui-styles/src/main/res/values/theme_light.xml b/library/ui-styles/src/main/res/values/theme_light.xml index 974a9e0b24..0e0049010a 100644 --- a/library/ui-styles/src/main/res/values/theme_light.xml +++ b/library/ui-styles/src/main/res/values/theme_light.xml @@ -139,7 +139,6 @@ @color/vctr_voice_message_lock_background_light - @color/vctr_voice_message_playback_background_light @color/vctr_voice_message_recording_playback_background_light @color/vctr_voice_message_toast_background_light diff --git a/vector/src/main/res/layout/item_timeline_event_voice_stub.xml b/vector/src/main/res/layout/item_timeline_event_voice_stub.xml index 26b8742086..f9107cc595 100644 --- a/vector/src/main/res/layout/item_timeline_event_voice_stub.xml +++ b/vector/src/main/res/layout/item_timeline_event_voice_stub.xml @@ -11,7 +11,7 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:background="@drawable/bg_voice_playback" - android:backgroundTint="?vctr_voice_message_playback_background" + android:backgroundTint="?vctr_content_quinary" android:minHeight="48dp" android:paddingStart="8dp" android:paddingTop="6dp" From 6530440069b70e4a551e987d536d09eb49bb7e1d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 13 Jul 2021 16:12:54 +0200 Subject: [PATCH 043/242] Fix an issue in the color --- library/ui-styles/src/main/res/values/palette.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/ui-styles/src/main/res/values/palette.xml b/library/ui-styles/src/main/res/values/palette.xml index 16cee0c620..ed12f10af3 100644 --- a/library/ui-styles/src/main/res/values/palette.xml +++ b/library/ui-styles/src/main/res/values/palette.xml @@ -23,7 +23,7 @@ #F4F6FA - #E6E8F0 + #E3E8F0 #C1C6CD #8D97A5 #737D8C From cf4e603f0937a309872ea614c1c7840f6329e81f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 13 Jul 2021 16:14:02 +0200 Subject: [PATCH 044/242] Fix background color of voice message recorder --- library/ui-styles/src/main/res/values/colors.xml | 4 ---- library/ui-styles/src/main/res/values/theme_dark.xml | 1 - library/ui-styles/src/main/res/values/theme_light.xml | 1 - vector/src/main/res/layout/view_voice_message_recorder.xml | 2 +- 4 files changed, 1 insertion(+), 7 deletions(-) diff --git a/library/ui-styles/src/main/res/values/colors.xml b/library/ui-styles/src/main/res/values/colors.xml index 1a032d94b6..de826806e1 100644 --- a/library/ui-styles/src/main/res/values/colors.xml +++ b/library/ui-styles/src/main/res/values/colors.xml @@ -132,10 +132,6 @@ #FFF3F8FD #22252B - - #FFE3E8F0 - #FF394049 - @color/palette_black_900 @color/palette_gray_450 diff --git a/library/ui-styles/src/main/res/values/theme_dark.xml b/library/ui-styles/src/main/res/values/theme_dark.xml index 5826f5fcd3..fd37a29832 100644 --- a/library/ui-styles/src/main/res/values/theme_dark.xml +++ b/library/ui-styles/src/main/res/values/theme_dark.xml @@ -137,7 +137,6 @@ @color/vctr_voice_message_lock_background_dark - @color/vctr_voice_message_recording_playback_background_dark @color/vctr_voice_message_toast_background_dark diff --git a/library/ui-styles/src/main/res/values/theme_light.xml b/library/ui-styles/src/main/res/values/theme_light.xml index 0e0049010a..7d13c32268 100644 --- a/library/ui-styles/src/main/res/values/theme_light.xml +++ b/library/ui-styles/src/main/res/values/theme_light.xml @@ -139,7 +139,6 @@ @color/vctr_voice_message_lock_background_light - @color/vctr_voice_message_recording_playback_background_light @color/vctr_voice_message_toast_background_light diff --git a/vector/src/main/res/layout/view_voice_message_recorder.xml b/vector/src/main/res/layout/view_voice_message_recorder.xml index 5b48ca4a43..854b731d90 100644 --- a/vector/src/main/res/layout/view_voice_message_recorder.xml +++ b/vector/src/main/res/layout/view_voice_message_recorder.xml @@ -147,7 +147,7 @@ android:layout_width="0dp" android:layout_height="0dp" android:background="@drawable/bg_voice_playback" - android:backgroundTint="?vctr_voice_message_recording_playback_background" + android:backgroundTint="?vctr_content_quinary" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@id/voiceMessageDeletePlayback" From 9e0f3a151758c8a70d02010970deef5d7346d6bf Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 13 Jul 2021 16:17:22 +0200 Subject: [PATCH 045/242] Fix other color issue --- library/ui-styles/src/main/res/values/styles_voice_message.xml | 2 +- vector/src/main/res/layout/item_timeline_event_voice_stub.xml | 2 +- vector/src/main/res/layout/view_voice_message_recorder.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/library/ui-styles/src/main/res/values/styles_voice_message.xml b/library/ui-styles/src/main/res/values/styles_voice_message.xml index dff1d08d28..5d5fbef007 100644 --- a/library/ui-styles/src/main/res/values/styles_voice_message.xml +++ b/library/ui-styles/src/main/res/values/styles_voice_message.xml @@ -2,7 +2,7 @@ diff --git a/library/ui-styles/src/main/res/values/theme_light.xml b/library/ui-styles/src/main/res/values/theme_light.xml index 7d13c32268..17e0ff2938 100644 --- a/library/ui-styles/src/main/res/values/theme_light.xml +++ b/library/ui-styles/src/main/res/values/theme_light.xml @@ -138,7 +138,6 @@ @style/Widget.Vector.JumpToUnread.Light - @color/vctr_voice_message_lock_background_light @color/vctr_voice_message_toast_background_light diff --git a/vector/src/main/res/layout/view_voice_message_recorder.xml b/vector/src/main/res/layout/view_voice_message_recorder.xml index e06de6c8d7..7736d76b0e 100644 --- a/vector/src/main/res/layout/view_voice_message_recorder.xml +++ b/vector/src/main/res/layout/view_voice_message_recorder.xml @@ -12,7 +12,7 @@ android:layout_width="52dp" android:layout_height="160dp" android:background="@drawable/bg_voice_message_lock" - android:backgroundTint="?vctr_voice_message_lock_background" + android:backgroundTint="?vctr_system" android:visibility="gone" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@id/voiceMessageMicButton" From 6a0ea11e7a16c49e8053e86f128419fef15d512e Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 13 Jul 2021 17:58:14 +0200 Subject: [PATCH 049/242] Follow the spec regarding waveform content --- .../room/model/message/AudioWaveformInfo.kt | 7 + .../room/send/LocalEchoEventFactory.kt | 3 +- .../session/room/send/WaveFormSanitizer.kt | 80 ++++++++++++ .../room/send/WaveFormSanitizerTest.kt | 123 ++++++++++++++++++ 4 files changed, 212 insertions(+), 1 deletion(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/WaveFormSanitizer.kt create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/send/WaveFormSanitizerTest.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/AudioWaveformInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/AudioWaveformInfo.kt index 0e2f0f880c..d576f1057a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/AudioWaveformInfo.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/AudioWaveformInfo.kt @@ -19,11 +19,18 @@ package org.matrix.android.sdk.api.session.room.model.message import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +/** + * See https://github.com/matrix-org/matrix-doc/blob/travis/msc/audio-waveform/proposals/3246-audio-waveform.md + */ @JsonClass(generateAdapter = true) data class AudioWaveformInfo( @Json(name = "duration") val duration: Int? = null, + /** + * The array should have no less than 30 elements and no more than 120. + * List of integers between zero and 1024, inclusive. + */ @Json(name = "waveform") val waveform: List? = null ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt index fb56587ac1..c610326a94 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt @@ -75,6 +75,7 @@ internal class LocalEchoEventFactory @Inject constructor( private val markdownParser: MarkdownParser, private val textPillsUtils: TextPillsUtils, private val thumbnailExtractor: ThumbnailExtractor, + private val waveformSanitizer: WaveFormSanitizer, private val localEchoRepository: LocalEchoRepository, private val permalinkFactory: PermalinkFactory ) { @@ -302,7 +303,7 @@ internal class LocalEchoEventFactory @Inject constructor( url = attachment.queryUri.toString(), audioWaveformInfo = if (!isVoiceMessage) null else AudioWaveformInfo( duration = attachment.duration?.toInt(), - waveform = attachment.waveform + waveform = waveformSanitizer.sanitize(attachment.waveform) ), voiceMessageIndicator = if (!isVoiceMessage) null else emptyMap() ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/WaveFormSanitizer.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/WaveFormSanitizer.kt new file mode 100644 index 0000000000..2bc5cb2475 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/WaveFormSanitizer.kt @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.room.send + +import javax.inject.Inject +import kotlin.math.abs +import kotlin.math.ceil + +internal class WaveFormSanitizer @Inject constructor() { + private companion object { + const val MIN_NUMBER_OF_VALUES = 30 + const val MAX_NUMBER_OF_VALUES = 120 + + const val MAX_VALUE = 1024 + } + + /** + * The array should have no less than 30 elements and no more than 120. + * List of integers between zero and 1024, inclusive. + */ + fun sanitize(waveForm: List?): List? { + if (waveForm.isNullOrEmpty()) { + return null + } + + // Limit the number of items + val result = mutableListOf() + if (waveForm.size < MIN_NUMBER_OF_VALUES) { + // Repeat the same value to have at least 30 items + val repeatTimes = ceil(MIN_NUMBER_OF_VALUES / waveForm.size.toDouble()).toInt() + waveForm.map { value -> + repeat(repeatTimes) { + result.add(value) + } + } + } else if (waveForm.size > MAX_NUMBER_OF_VALUES) { + val keepOneOf = ceil(waveForm.size.toDouble() / MAX_NUMBER_OF_VALUES).toInt() + waveForm.mapIndexed { idx, value -> + if (idx % keepOneOf == 0) { + result.add(value) + } + } + } else { + result.addAll(waveForm) + } + + // OK, ensure all items are positive + val limited = result.map { + abs(it) + } + + // Ensure max is not above MAX_VALUE + val max = limited.maxOrNull() ?: MAX_VALUE + + val final = if (max > MAX_VALUE) { + // Reduce the range + limited.map { + it * MAX_VALUE / max + } + } else { + limited + } + + return final + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/send/WaveFormSanitizerTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/send/WaveFormSanitizerTest.kt new file mode 100644 index 0000000000..23c8aeb76b --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/send/WaveFormSanitizerTest.kt @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.room.send + +import org.amshove.kluent.shouldBe +import org.amshove.kluent.shouldBeInRange +import org.junit.Test + +class WaveFormSanitizerTest { + + private val waveFormSanitizer = WaveFormSanitizer() + + @Test + fun sanitizeNull() { + waveFormSanitizer.sanitize(null) shouldBe null + } + + @Test + fun sanitizeEmpty() { + waveFormSanitizer.sanitize(emptyList()) shouldBe null + } + + @Test + fun sanitizeSingleton() { + val result = waveFormSanitizer.sanitize(listOf(1))!! + result.size shouldBe 30 + checkResult(result) + } + + @Test + fun sanitize29() { + val list = generateSequence { 1 }.take(29).toList() + val result = waveFormSanitizer.sanitize(list)!! + checkResult(result) + } + + @Test + fun sanitize30() { + val list = generateSequence { 1 }.take(30).toList() + val result = waveFormSanitizer.sanitize(list)!! + result.size shouldBe 30 + checkResult(result) + } + + @Test + fun sanitize31() { + val list = generateSequence { 1 }.take(31).toList() + val result = waveFormSanitizer.sanitize(list)!! + checkResult(result) + } + + @Test + fun sanitize119() { + val list = generateSequence { 1 }.take(119).toList() + val result = waveFormSanitizer.sanitize(list)!! + checkResult(result) + } + + @Test + fun sanitize120() { + val list = generateSequence { 1 }.take(120).toList() + val result = waveFormSanitizer.sanitize(list)!! + result.size shouldBe 120 + checkResult(result) + } + + @Test + fun sanitize121() { + val list = generateSequence { 1 }.take(121).toList() + val result = waveFormSanitizer.sanitize(list)!! + checkResult(result) + } + + @Test + fun sanitize1024() { + val list = generateSequence { 1 }.take(1024).toList() + val result = waveFormSanitizer.sanitize(list)!! + checkResult(result) + } + + @Test + fun sanitizeNegative() { + val list = generateSequence { -1 }.take(30).toList() + val result = waveFormSanitizer.sanitize(list)!! + checkResult(result) + } + + @Test + fun sanitizeMaxValue() { + val list = generateSequence { 1025 }.take(30).toList() + val result = waveFormSanitizer.sanitize(list)!! + checkResult(result) + } + + @Test + fun sanitizeNegativeMaxValue() { + val list = generateSequence { -1025 }.take(30).toList() + val result = waveFormSanitizer.sanitize(list)!! + checkResult(result) + } + + private fun checkResult(result: List) { + result.forEach { + it shouldBeInRange 0..1024 + } + + result.size shouldBeInRange 30..120 + } +} From c938a30dd9c26bf3bc950cb587ceef3e1259b924 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 13 Jul 2021 18:12:55 +0200 Subject: [PATCH 050/242] Change filename. Later we will use the room id to save the draft. --- .../detail/composer/VoiceMessageHelper.kt | 19 +++++++++++-------- .../composer/VoiceMessageRecorderView.kt | 2 +- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageHelper.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageHelper.kt index efc0bdd82c..70f4964242 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageHelper.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageHelper.kt @@ -20,6 +20,7 @@ import android.content.Context import android.media.AudioAttributes import android.media.MediaPlayer import android.media.MediaRecorder +import android.os.Build import androidx.core.content.FileProvider import im.vector.app.BuildConfig import im.vector.app.core.utils.CountUpTimer @@ -33,7 +34,6 @@ import java.io.File import java.io.FileInputStream import java.io.FileNotFoundException import java.io.FileOutputStream -import java.util.UUID import javax.inject.Inject /** @@ -74,16 +74,19 @@ class VoiceMessageHelper @Inject constructor( stopPlayback() playbackTracker.makeAllPlaybacksIdle() - outputFile = File(outputDirectory, UUID.randomUUID().toString() + ".ogg") + outputFile = File(outputDirectory, "Voice message.ogg") lastRecordingFile = outputFile amplitudeList.clear() - FileOutputStream(outputFile).use { fos -> - refreshMediaRecorder() - mediaRecorder.setOutputFile(fos.fd) - mediaRecorder.prepare() - mediaRecorder.start() - startRecordingAmplitudes() + + refreshMediaRecorder() + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + mediaRecorder.setOutputFile(outputFile) + } else { + mediaRecorder.setOutputFile(FileOutputStream(outputFile).fd) } + mediaRecorder.prepare() + mediaRecorder.start() + startRecordingAmplitudes() } fun stopRecording(): MultiPickerAudioType? { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageRecorderView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageRecorderView.kt index cb3fc1c319..fa68afa359 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageRecorderView.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageRecorderView.kt @@ -371,7 +371,7 @@ class VoiceMessageRecorderView @JvmOverloads constructor( is VoiceMessagePlaybackTracker.Listener.State.Playing -> { views.voicePlaybackControlButton.setImageResource(R.drawable.ic_play_pause_pause) val formattedTimerText = DateUtils.formatElapsedTime((state.playbackTime / 1000).toLong()) - views.voicePlaybackTime.setText(formattedTimerText) + views.voicePlaybackTime.text = formattedTimerText } is VoiceMessagePlaybackTracker.Listener.State.Idle -> { views.voicePlaybackControlButton.setImageResource(R.drawable.ic_play_pause_play) From df795d1881f342be92b7a8576f2c128aa1c81230 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 13 Jul 2021 18:16:30 +0200 Subject: [PATCH 051/242] Cleanup --- .../session/room/send/WaveFormSanitizer.kt | 48 +++++++++++-------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/WaveFormSanitizer.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/WaveFormSanitizer.kt index 2bc5cb2475..94490529c0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/WaveFormSanitizer.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/WaveFormSanitizer.kt @@ -16,6 +16,7 @@ package org.matrix.android.sdk.internal.session.room.send +import timber.log.Timber import javax.inject.Inject import kotlin.math.abs import kotlin.math.ceil @@ -38,43 +39,48 @@ internal class WaveFormSanitizer @Inject constructor() { } // Limit the number of items - val result = mutableListOf() - if (waveForm.size < MIN_NUMBER_OF_VALUES) { - // Repeat the same value to have at least 30 items - val repeatTimes = ceil(MIN_NUMBER_OF_VALUES / waveForm.size.toDouble()).toInt() - waveForm.map { value -> - repeat(repeatTimes) { - result.add(value) + val sizeInRangeList = mutableListOf() + when { + waveForm.size < MIN_NUMBER_OF_VALUES -> { + // Repeat the same value to have at least 30 items + val repeatTimes = ceil(MIN_NUMBER_OF_VALUES / waveForm.size.toDouble()).toInt() + waveForm.map { value -> + repeat(repeatTimes) { + sizeInRangeList.add(value) + } } } - } else if (waveForm.size > MAX_NUMBER_OF_VALUES) { - val keepOneOf = ceil(waveForm.size.toDouble() / MAX_NUMBER_OF_VALUES).toInt() - waveForm.mapIndexed { idx, value -> - if (idx % keepOneOf == 0) { - result.add(value) + waveForm.size > MAX_NUMBER_OF_VALUES -> { + val keepOneOf = ceil(waveForm.size.toDouble() / MAX_NUMBER_OF_VALUES).toInt() + waveForm.mapIndexed { idx, value -> + if (idx % keepOneOf == 0) { + sizeInRangeList.add(value) + } } } - } else { - result.addAll(waveForm) + else -> { + sizeInRangeList.addAll(waveForm) + } } // OK, ensure all items are positive - val limited = result.map { + val positiveList = sizeInRangeList.map { abs(it) } // Ensure max is not above MAX_VALUE - val max = limited.maxOrNull() ?: MAX_VALUE + val max = positiveList.maxOrNull() ?: MAX_VALUE - val final = if (max > MAX_VALUE) { - // Reduce the range - limited.map { + val finalList = if (max > MAX_VALUE) { + // Reduce the values + positiveList.map { it * MAX_VALUE / max } } else { - limited + positiveList } - return final + Timber.d("Sanitize from ${waveForm.size} items to ${finalList.size} items") + return finalList } } From 0cf10b2f84c6c9658401c1e2fedb46c8201ac498 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 13 Jul 2021 18:48:59 +0200 Subject: [PATCH 052/242] Fix issue with waveform rendering --- .../sdk/internal/session/room/send/WaveFormSanitizer.kt | 2 +- .../room/detail/timeline/factory/MessageItemFactory.kt | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/WaveFormSanitizer.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/WaveFormSanitizer.kt index 94490529c0..78a03f3775 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/WaveFormSanitizer.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/WaveFormSanitizer.kt @@ -80,7 +80,7 @@ internal class WaveFormSanitizer @Inject constructor() { positiveList } - Timber.d("Sanitize from ${waveForm.size} items to ${finalList.size} items") + Timber.d("Sanitize from ${waveForm.size} items to ${finalList.size} items. Max value was $max") return finalList } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt index 6ab9f83c32..e67fa7cca0 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -257,7 +257,7 @@ class MessageItemFactory @Inject constructor( return MessageVoiceItem_() .attributes(attributes) .duration(messageContent.audioWaveformInfo?.duration ?: 0) - .waveform(messageContent.audioWaveformInfo?.waveform ?: emptyList()) + .waveform(messageContent.audioWaveformInfo?.waveform?.toFft().orEmpty()) .playbackControlButtonClickListener(playbackControlButtonClickListener) .voiceMessagePlaybackTracker(voiceMessagePlaybackTracker) .izLocalFile(localFilesHelper.isLocalFile(fileUrl)) @@ -622,6 +622,13 @@ class MessageItemFactory @Inject constructor( .highlighted(highlight) } + private fun List?.toFft(): List? { + return this?.map { + // Value comes from AudioRecordView.maxReportableAmp, and 1024 is the max value in the Matrix spec + it * 22760 / 1024 + } + } + companion object { private const val MAX_NUMBER_OF_EMOJI_FOR_BIG_FONT = 5 } From 276808c8e99583bc97fab085fb84ed3f9e5841b3 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 13 Jul 2021 20:05:50 +0200 Subject: [PATCH 053/242] Fix issue in RTL --- vector/src/main/res/drawable/ic_play_pause_play.xml | 1 + vector/src/main/res/layout/item_timeline_event_voice_stub.xml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/vector/src/main/res/drawable/ic_play_pause_play.xml b/vector/src/main/res/drawable/ic_play_pause_play.xml index 84056219ec..f632fad8ed 100644 --- a/vector/src/main/res/drawable/ic_play_pause_play.xml +++ b/vector/src/main/res/drawable/ic_play_pause_play.xml @@ -1,6 +1,7 @@ From 6ab9b462a3c15a9b8ac00f4f6a94e568147b5738 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 15 Jul 2021 13:42:40 +0200 Subject: [PATCH 054/242] Fix mic button color --- vector/src/main/res/drawable/ic_voice_mic.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vector/src/main/res/drawable/ic_voice_mic.xml b/vector/src/main/res/drawable/ic_voice_mic.xml index 7cb091afaa..fef5fb9dcf 100644 --- a/vector/src/main/res/drawable/ic_voice_mic.xml +++ b/vector/src/main/res/drawable/ic_voice_mic.xml @@ -5,8 +5,8 @@ android:viewportHeight="32"> + android:fillColor="?vctr_content_tertiary"/> + android:fillColor="?vctr_content_tertiary"/> From bb742eb483065ea014724744161dd6e7a49c9a98 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 15 Jul 2021 14:27:08 +0200 Subject: [PATCH 055/242] Handle record/play error --- .../internal/session/DefaultFileService.kt | 19 +++-- .../vector/app/core/error/ErrorFormatter.kt | 9 +++ .../home/room/detail/RoomDetailFragment.kt | 13 ++- .../home/room/detail/RoomDetailViewModel.kt | 16 +++- .../detail/composer/VoiceMessageHelper.kt | 80 +++++++++++-------- .../vector/app/features/voice/VoiceFailure.kt | 22 +++++ vector/src/main/res/values/strings.xml | 2 + 7 files changed, 120 insertions(+), 41 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/voice/VoiceFailure.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt index a284d976d0..bcc0a74258 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt @@ -25,6 +25,7 @@ import kotlinx.coroutines.completeWith import kotlinx.coroutines.withContext import okhttp3.OkHttpClient import okhttp3.Request +import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.session.content.ContentUrlResolver import org.matrix.android.sdk.api.session.file.FileService import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt @@ -120,13 +121,21 @@ internal class DefaultFileService @Inject constructor( .header(DOWNLOAD_PROGRESS_INTERCEPTOR_HEADER, url) .build() - val response = okHttpClient.newCall(request).execute() - - if (!response.isSuccessful) { - throw IOException() + val response = try { + okHttpClient.newCall(request).execute() + } catch (failure: Throwable) { + throw if (failure is IOException) { + Failure.NetworkConnection(failure) + } else { + failure + } } - val source = response.body?.source() ?: throw IOException() + if (!response.isSuccessful) { + throw Failure.NetworkConnection(IOException()) + } + + val source = response.body?.source() ?: throw Failure.NetworkConnection(IOException()) Timber.v("Response size ${response.body?.contentLength()} - Stream available: ${!source.exhausted()}") diff --git a/vector/src/main/java/im/vector/app/core/error/ErrorFormatter.kt b/vector/src/main/java/im/vector/app/core/error/ErrorFormatter.kt index e7602e5cfe..f0ba79e31c 100644 --- a/vector/src/main/java/im/vector/app/core/error/ErrorFormatter.kt +++ b/vector/src/main/java/im/vector/app/core/error/ErrorFormatter.kt @@ -19,6 +19,7 @@ package im.vector.app.core.error import im.vector.app.R import im.vector.app.core.resources.StringProvider import im.vector.app.features.call.dialpad.DialPadLookup +import im.vector.app.features.voice.VoiceFailure import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.MatrixError import org.matrix.android.sdk.api.failure.MatrixIdFailure @@ -123,11 +124,19 @@ class DefaultErrorFormatter @Inject constructor( stringProvider.getString(R.string.call_dial_pad_lookup_error) is MatrixIdFailure.InvalidMatrixId -> stringProvider.getString(R.string.login_signin_matrix_id_error_invalid_matrix_id) + is VoiceFailure -> voiceMessageError(throwable) else -> throwable.localizedMessage } ?: stringProvider.getString(R.string.unknown_error) } + private fun voiceMessageError(throwable: VoiceFailure): String { + return when (throwable) { + is VoiceFailure.UnableToPlay -> stringProvider.getString(R.string.error_voice_message_unable_to_play) + is VoiceFailure.UnableToRecord -> stringProvider.getString(R.string.error_voice_message_unable_to_record) + } + } + private fun limitExceededError(error: MatrixError): String { val delay = error.retryAfterMillis diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index fe8b40ca2e..da27c1adc8 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -168,6 +168,7 @@ import im.vector.app.features.settings.VectorSettingsActivity import im.vector.app.features.share.SharedData import im.vector.app.features.spaces.share.ShareSpaceBottomSheet import im.vector.app.features.themes.ThemeUtils +import im.vector.app.features.voice.VoiceFailure import im.vector.app.features.widgets.WidgetActivity import im.vector.app.features.widgets.WidgetArgs import im.vector.app.features.widgets.WidgetKind @@ -386,7 +387,12 @@ class RoomDetailFragment @Inject constructor( roomDetailViewModel.observeViewEvents { when (it) { - is RoomDetailViewEvents.Failure -> showErrorInSnackbar(it.throwable) + is RoomDetailViewEvents.Failure -> { + if (it.throwable is VoiceFailure.UnableToRecord) { + onCannotRecord() + } + showErrorInSnackbar(it.throwable) + } is RoomDetailViewEvents.OnNewTimelineEvents -> scrollOnNewMessageCallback.addNewTimelineEventIds(it.eventIds) is RoomDetailViewEvents.ActionSuccess -> displayRoomDetailActionSuccess(it) is RoomDetailViewEvents.ActionFailure -> displayRoomDetailActionFailure(it) @@ -428,6 +434,11 @@ class RoomDetailFragment @Inject constructor( } } + private fun onCannotRecord() { + // Update the UI, cancel the animation + views.voiceMessageRecorderView.initVoiceRecordingViews() + } + private fun acceptIncomingCall(event: RoomDetailViewEvents.DisplayAndAcceptCall) { val intent = VectorCallActivity.newIntent( context = vectorBaseActivity, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index 745a8e3019..4eb5519404 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -621,7 +621,11 @@ class RoomDetailViewModel @AssistedInject constructor( } private fun handleStartRecordingVoiceMessage() { - voiceMessageHelper.startRecording() + try { + voiceMessageHelper.startRecording() + } catch (failure: Throwable) { + _viewEvents.post(RoomDetailViewEvents.Failure(failure)) + } } private fun handleEndRecordingVoiceMessage(isCancelled: Boolean) { @@ -640,8 +644,14 @@ class RoomDetailViewModel @AssistedInject constructor( private fun handlePlayOrPauseVoicePlayback(action: RoomDetailAction.PlayOrPauseVoicePlayback) { viewModelScope.launch(Dispatchers.IO) { - val audioFile = session.fileService().downloadFile(action.messageAudioContent) - voiceMessageHelper.startOrPausePlayback(action.eventId, audioFile) + try { + // Download can fail + val audioFile = session.fileService().downloadFile(action.messageAudioContent) + // Play can fail + voiceMessageHelper.startOrPausePlayback(action.eventId, audioFile) + } catch (failure: Throwable) { + _viewEvents.post(RoomDetailViewEvents.Failure(failure)) + } } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageHelper.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageHelper.kt index 70f4964242..f202e0da56 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageHelper.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageHelper.kt @@ -25,6 +25,7 @@ import androidx.core.content.FileProvider import im.vector.app.BuildConfig import im.vector.app.core.utils.CountUpTimer import im.vector.app.features.home.room.detail.timeline.helper.VoiceMessagePlaybackTracker +import im.vector.app.features.voice.VoiceFailure import im.vector.lib.multipicker.entity.MultiPickerAudioType import im.vector.lib.multipicker.utils.toMultiPickerAudioType import org.matrix.android.sdk.api.extensions.orFalse @@ -44,7 +45,7 @@ class VoiceMessageHelper @Inject constructor( private val playbackTracker: VoiceMessagePlaybackTracker ) { private var mediaPlayer: MediaPlayer? = null - private lateinit var mediaRecorder: MediaRecorder + private var mediaRecorder: MediaRecorder? = null private val outputDirectory = File(context.cacheDir, "downloads") private var outputFile: File? = null private var lastRecordingFile: File? = null // In case of user pauses recording, plays another one in timeline @@ -60,13 +61,14 @@ class VoiceMessageHelper @Inject constructor( } } - private fun refreshMediaRecorder() { - mediaRecorder = MediaRecorder().apply { - setAudioSource(MediaRecorder.AudioSource.DEFAULT) - setOutputFormat(MediaRecorder.OutputFormat.OGG) - setAudioEncoder(MediaRecorder.AudioEncoder.OPUS) - setAudioEncodingBitRate(24000) - setAudioSamplingRate(48000) + private fun initMediaRecorder() { + MediaRecorder().let { + it.setAudioSource(MediaRecorder.AudioSource.DEFAULT) + it.setOutputFormat(MediaRecorder.OutputFormat.OGG) + it.setAudioEncoder(MediaRecorder.AudioEncoder.OPUS) + it.setAudioEncodingBitRate(24000) + it.setAudioSamplingRate(48000) + mediaRecorder = it } } @@ -78,14 +80,19 @@ class VoiceMessageHelper @Inject constructor( lastRecordingFile = outputFile amplitudeList.clear() - refreshMediaRecorder() - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - mediaRecorder.setOutputFile(outputFile) - } else { - mediaRecorder.setOutputFile(FileOutputStream(outputFile).fd) + try { + initMediaRecorder() + val mr = mediaRecorder!! + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + mr.setOutputFile(outputFile) + } else { + mr.setOutputFile(FileOutputStream(outputFile).fd) + } + mr.prepare() + mr.start() + } catch (failure: Throwable) { + throw VoiceFailure.UnableToRecord(failure) } - mediaRecorder.prepare() - mediaRecorder.start() startRecordingAmplitudes() } @@ -117,9 +124,13 @@ class VoiceMessageHelper @Inject constructor( } private fun releaseMediaRecorder() { - mediaRecorder.stop() - mediaRecorder.reset() - mediaRecorder.release() + mediaRecorder?.let { + it.stop() + it.reset() + it.release() + } + + mediaRecorder = null } fun pauseRecording() { @@ -143,27 +154,31 @@ class VoiceMessageHelper @Inject constructor( if (playbackTracker.getPlaybackState(id) is VoiceMessagePlaybackTracker.Listener.State.Playing) { playbackTracker.pausePlayback(id) } else { - playbackTracker.startPlayback(id) startPlayback(id, file) + playbackTracker.startPlayback(id) } } private fun startPlayback(id: String, file: File) { val currentPlaybackTime = playbackTracker.getPlaybackTime(id) - FileInputStream(file).use { fis -> - mediaPlayer = MediaPlayer().apply { - setAudioAttributes( - AudioAttributes.Builder() - .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) - .setUsage(AudioAttributes.USAGE_MEDIA) - .build() - ) - setDataSource(fis.fd) - prepare() - start() - seekTo(currentPlaybackTime) + try { + FileInputStream(file).use { fis -> + mediaPlayer = MediaPlayer().apply { + setAudioAttributes( + AudioAttributes.Builder() + .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) + .setUsage(AudioAttributes.USAGE_MEDIA) + .build() + ) + setDataSource(fis.fd) + prepare() + start() + seekTo(currentPlaybackTime) + } } + } catch (failure: Throwable) { + throw VoiceFailure.UnableToPlay(failure) } startPlaybackTicker(id) } @@ -186,8 +201,9 @@ class VoiceMessageHelper @Inject constructor( } private fun onAmplitudeTick() { + val mr = mediaRecorder ?: return try { - val maxAmplitude = mediaRecorder.maxAmplitude + val maxAmplitude = mr.maxAmplitude amplitudeList.add(maxAmplitude) playbackTracker.updateCurrentRecording(VoiceMessagePlaybackTracker.RECORDING_ID, amplitudeList) } catch (e: IllegalStateException) { diff --git a/vector/src/main/java/im/vector/app/features/voice/VoiceFailure.kt b/vector/src/main/java/im/vector/app/features/voice/VoiceFailure.kt new file mode 100644 index 0000000000..9c4b345dc4 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/voice/VoiceFailure.kt @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.voice + +sealed class VoiceFailure(cause: Throwable? = null) : Throwable(cause = cause) { + data class UnableToPlay(val throwable: Throwable) : VoiceFailure(throwable) + data class UnableToRecord(val throwable: Throwable) : VoiceFailure(throwable) +} diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 1fb7c3cb09..a2c255466c 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -3452,4 +3452,6 @@ Tap on the waveform to stop and playback Enable voice message Tap on the wavelength to stop and playback + Cannot play this voice message + Cannot record a voice message From 6f947e979b69cb13ddbd9b0bddb716d4ccefa076 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 15 Jul 2021 15:14:26 +0200 Subject: [PATCH 056/242] Split to sub fun --- .../composer/VoiceMessageRecorderView.kt | 55 +++++++++++-------- 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageRecorderView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageRecorderView.kt index fa68afa359..2390a86a4d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageRecorderView.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageRecorderView.kt @@ -116,42 +116,51 @@ class VoiceMessageRecorderView @JvmOverloads constructor( } views.voiceMessageMicButton.setOnTouchListener { _, event -> - return@setOnTouchListener when (event.action) { + when (event.action) { MotionEvent.ACTION_DOWN -> { - val recordingStarted = callback?.onVoiceRecordingStarted().orFalse() - if (recordingStarted) { - startRecordingTicker() - renderToast(context.getString(R.string.voice_message_release_to_send_toast)) - recordingState = RecordingState.STARTED - showRecordingViews() - - firstX = event.rawX - firstY = event.rawY - lastX = firstX - lastY = firstY - } + handleMicActionDown(event) true } MotionEvent.ACTION_UP -> { - if (recordingState != RecordingState.LOCKED) { - stopRecordingTicker() - val isCancelled = recordingState == RecordingState.NONE || recordingState == RecordingState.CANCELLED - callback?.onVoiceRecordingEnded(isCancelled) - recordingState = RecordingState.NONE - hideRecordingViews() - } + handleMicActionUp() true } MotionEvent.ACTION_MOVE -> { - handleMoveAction(event) + handleMicActionMove(event) true } - else -> false + else -> + false } } } - private fun handleMoveAction(event: MotionEvent) { + private fun handleMicActionDown(event: MotionEvent) { + val recordingStarted = callback?.onVoiceRecordingStarted().orFalse() + if (recordingStarted) { + startRecordingTicker() + renderToast(context.getString(R.string.voice_message_release_to_send_toast)) + recordingState = RecordingState.STARTED + showRecordingViews() + + firstX = event.rawX + firstY = event.rawY + lastX = firstX + lastY = firstY + } + } + + private fun handleMicActionUp() { + if (recordingState != RecordingState.LOCKED) { + stopRecordingTicker() + val isCancelled = recordingState == RecordingState.NONE || recordingState == RecordingState.CANCELLED + callback?.onVoiceRecordingEnded(isCancelled) + recordingState = RecordingState.NONE + hideRecordingViews() + } + } + + private fun handleMicActionMove(event: MotionEvent) { val currentX = event.rawX val currentY = event.rawY From bfc70be5bb7fa1841c63f07c9b7ca2dfb3562715 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 15 Jul 2021 17:04:10 +0200 Subject: [PATCH 057/242] Record voice on Android 21 --- vector/build.gradle | 5 +- .../detail/composer/VoiceMessageHelper.kt | 91 +++++------------- .../features/voice/AbstractVoiceRecorder.kt | 95 +++++++++++++++++++ .../app/features/voice/VoiceRecorder.kt | 48 ++++++++++ .../app/features/voice/VoiceRecorderL.kt | 67 +++++++++++++ .../features/voice/VoiceRecorderProvider.kt | 33 +++++++ .../app/features/voice/VoiceRecorderQ.kt | 37 ++++++++ 7 files changed, 310 insertions(+), 66 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/voice/AbstractVoiceRecorder.kt create mode 100644 vector/src/main/java/im/vector/app/features/voice/VoiceRecorder.kt create mode 100644 vector/src/main/java/im/vector/app/features/voice/VoiceRecorderL.kt create mode 100644 vector/src/main/java/im/vector/app/features/voice/VoiceRecorderProvider.kt create mode 100644 vector/src/main/java/im/vector/app/features/voice/VoiceRecorderQ.kt diff --git a/vector/build.gradle b/vector/build.gradle index f1b71741aa..9ae5ffd56e 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -144,7 +144,7 @@ android { buildConfigField "im.vector.app.features.crypto.keysrequest.OutboundSessionKeySharingStrategy", "outboundSessionKeySharingStrategy", "im.vector.app.features.crypto.keysrequest.OutboundSessionKeySharingStrategy.WhenTyping" - buildConfigField "Long", "VOICE_MESSAGE_DURATION_LIMIT_MS", "120000L" + buildConfigField "Long", "VOICE_MESSAGE_DURATION_LIMIT_MS", "120_000L" // If set, MSC3086 asserted identity messages sent on VoIP calls will cause the call to appear in the room corresponding to the asserted identity. // This *must* only be set in trusted environments. @@ -411,6 +411,9 @@ dependencies { // Passphrase strength helper implementation 'com.nulab-inc:zxcvbn:1.5.2' + // To convert voice message on old platforms + implementation 'com.arthenica:ffmpeg-kit-audio:4.4.LTS' + //Alerter implementation 'com.tapadoo.android:alerter:7.0.1' diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageHelper.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageHelper.kt index f202e0da56..4f07cf98fe 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageHelper.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageHelper.kt @@ -19,13 +19,13 @@ package im.vector.app.features.home.room.detail.composer import android.content.Context import android.media.AudioAttributes import android.media.MediaPlayer -import android.media.MediaRecorder -import android.os.Build import androidx.core.content.FileProvider import im.vector.app.BuildConfig import im.vector.app.core.utils.CountUpTimer import im.vector.app.features.home.room.detail.timeline.helper.VoiceMessagePlaybackTracker import im.vector.app.features.voice.VoiceFailure +import im.vector.app.features.voice.VoiceRecorder +import im.vector.app.features.voice.VoiceRecorderProvider import im.vector.lib.multipicker.entity.MultiPickerAudioType import im.vector.lib.multipicker.utils.toMultiPickerAudioType import org.matrix.android.sdk.api.extensions.orFalse @@ -34,7 +34,6 @@ import timber.log.Timber import java.io.File import java.io.FileInputStream import java.io.FileNotFoundException -import java.io.FileOutputStream import javax.inject.Inject /** @@ -42,54 +41,24 @@ import javax.inject.Inject */ class VoiceMessageHelper @Inject constructor( private val context: Context, - private val playbackTracker: VoiceMessagePlaybackTracker + private val playbackTracker: VoiceMessagePlaybackTracker, + voiceRecorderProvider: VoiceRecorderProvider ) { private var mediaPlayer: MediaPlayer? = null - private var mediaRecorder: MediaRecorder? = null - private val outputDirectory = File(context.cacheDir, "downloads") - private var outputFile: File? = null - private var lastRecordingFile: File? = null // In case of user pauses recording, plays another one in timeline + private var voiceRecorder: VoiceRecorder = voiceRecorderProvider.provideVoiceRecorder() private val amplitudeList = mutableListOf() private var amplitudeTicker: CountUpTimer? = null private var playbackTicker: CountUpTimer? = null - init { - if (!outputDirectory.exists()) { - outputDirectory.mkdirs() - } - } - - private fun initMediaRecorder() { - MediaRecorder().let { - it.setAudioSource(MediaRecorder.AudioSource.DEFAULT) - it.setOutputFormat(MediaRecorder.OutputFormat.OGG) - it.setAudioEncoder(MediaRecorder.AudioEncoder.OPUS) - it.setAudioEncodingBitRate(24000) - it.setAudioSamplingRate(48000) - mediaRecorder = it - } - } - fun startRecording() { stopPlayback() playbackTracker.makeAllPlaybacksIdle() - - outputFile = File(outputDirectory, "Voice message.ogg") - lastRecordingFile = outputFile amplitudeList.clear() try { - initMediaRecorder() - val mr = mediaRecorder!! - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - mr.setOutputFile(outputFile) - } else { - mr.setOutputFile(FileOutputStream(outputFile).fd) - } - mr.prepare() - mr.start() + voiceRecorder.startRecord() } catch (failure: Throwable) { throw VoiceFailure.UnableToRecord(failure) } @@ -97,9 +66,16 @@ class VoiceMessageHelper @Inject constructor( } fun stopRecording(): MultiPickerAudioType? { - internalStopRecording() + tryOrNull("Cannot stop media recording amplitude") { + stopRecordingAmplitudes() + } + val voiceMessageFile = tryOrNull("Cannot stop media recorder!") { + voiceRecorder.stopRecord() + voiceRecorder.getVoiceMessageFile() + } try { - outputFile?.let { + // TODO Improve this + voiceMessageFile?.let { val outputFileUri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".fileProvider", it) return outputFileUri ?.toMultiPickerAudioType(context) @@ -113,38 +89,24 @@ class VoiceMessageHelper @Inject constructor( } } - private fun internalStopRecording() { + /** + * When entering in playback mode actually + */ + fun pauseRecording() { + voiceRecorder.stopRecord() + } + + fun deleteRecording() { tryOrNull("Cannot stop media recording amplitude") { stopRecordingAmplitudes() } tryOrNull("Cannot stop media recorder!") { - // Usually throws when the record is less than 1 second. - releaseMediaRecorder() + voiceRecorder.cancelRecord() } } - private fun releaseMediaRecorder() { - mediaRecorder?.let { - it.stop() - it.reset() - it.release() - } - - mediaRecorder = null - } - - fun pauseRecording() { - releaseMediaRecorder() - } - - fun deleteRecording() { - internalStopRecording() - outputFile?.delete() - outputFile = null - } - fun startOrPauseRecordingPlayback() { - lastRecordingFile?.let { + voiceRecorder.getCurrentRecord()?.let { startOrPausePlayback(VoiceMessagePlaybackTracker.RECORDING_ID, it) } } @@ -201,9 +163,8 @@ class VoiceMessageHelper @Inject constructor( } private fun onAmplitudeTick() { - val mr = mediaRecorder ?: return try { - val maxAmplitude = mr.maxAmplitude + val maxAmplitude = voiceRecorder.getMaxAmplitude() amplitudeList.add(maxAmplitude) playbackTracker.updateCurrentRecording(VoiceMessagePlaybackTracker.RECORDING_ID, amplitudeList) } catch (e: IllegalStateException) { diff --git a/vector/src/main/java/im/vector/app/features/voice/AbstractVoiceRecorder.kt b/vector/src/main/java/im/vector/app/features/voice/AbstractVoiceRecorder.kt new file mode 100644 index 0000000000..8a0f829f94 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/voice/AbstractVoiceRecorder.kt @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.voice + +import android.content.Context +import android.media.MediaRecorder +import android.os.Build +import java.io.File +import java.io.FileOutputStream + +abstract class AbstractVoiceRecorder( + context: Context, + private val filenameExt: String +) : VoiceRecorder { + private val outputDirectory = File(context.cacheDir, "voice_records") + + private var mediaRecorder: MediaRecorder? = null + private var outputFile: File? = null + + init { + if (!outputDirectory.exists()) { + outputDirectory.mkdirs() + } + } + + abstract fun setOutputFormat(mediaRecorder: MediaRecorder) + abstract fun convertFile(recordedFile: File?): File? + + private fun init() { + MediaRecorder().let { + it.setAudioSource(MediaRecorder.AudioSource.DEFAULT) + setOutputFormat(it) + it.setAudioEncodingBitRate(24000) + it.setAudioSamplingRate(48000) + mediaRecorder = it + } + } + + override fun startRecord() { + init() + outputFile = File(outputDirectory, "Voice message.$filenameExt") + + val mr = mediaRecorder ?: return + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + mr.setOutputFile(outputFile) + } else { + mr.setOutputFile(FileOutputStream(outputFile).fd) + } + mr.prepare() + mr.start() + } + + override fun stopRecord() { + // Can throw when the record is less than 1 second. + mediaRecorder?.let { + it.stop() + it.reset() + it.release() + } + mediaRecorder = null + } + + override fun cancelRecord() { + stopRecord() + + outputFile?.delete() + outputFile = null + } + + override fun getMaxAmplitude(): Int { + return mediaRecorder?.maxAmplitude ?: 0 + } + + override fun getCurrentRecord(): File? { + return outputFile + } + + override fun getVoiceMessageFile(): File? { + return convertFile(outputFile) + } +} diff --git a/vector/src/main/java/im/vector/app/features/voice/VoiceRecorder.kt b/vector/src/main/java/im/vector/app/features/voice/VoiceRecorder.kt new file mode 100644 index 0000000000..17e70997b2 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/voice/VoiceRecorder.kt @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.voice + +import java.io.File + +interface VoiceRecorder { + /** + * Start the recording + */ + fun startRecord() + + /** + * Stop the recording + */ + fun stopRecord() + + /** + * Remove the file + */ + fun cancelRecord() + + fun getMaxAmplitude(): Int + + /** + * Not guaranteed to be a ogg file + */ + fun getCurrentRecord(): File? + + /** + * Guaranteed to be a ogg file + */ + fun getVoiceMessageFile(): File? +} diff --git a/vector/src/main/java/im/vector/app/features/voice/VoiceRecorderL.kt b/vector/src/main/java/im/vector/app/features/voice/VoiceRecorderL.kt new file mode 100644 index 0000000000..2d40f5f7a3 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/voice/VoiceRecorderL.kt @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.voice + +import android.content.Context +import android.media.MediaRecorder +import com.arthenica.ffmpegkit.FFmpegKit +import com.arthenica.ffmpegkit.FFmpegKitConfig +import com.arthenica.ffmpegkit.Level +import com.arthenica.ffmpegkit.ReturnCode +import im.vector.app.BuildConfig +import timber.log.Timber +import java.io.File + +class VoiceRecorderL(context: Context) : AbstractVoiceRecorder(context, "mp4") { + override fun setOutputFormat(mediaRecorder: MediaRecorder) { + // Use AAC/MP4 format here + mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4) + mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC) + } + + override fun convertFile(recordedFile: File?): File? { + if (BuildConfig.DEBUG) { + FFmpegKitConfig.setLogLevel(Level.AV_LOG_INFO) + } + recordedFile ?: return null + // Convert to OGG + val targetFile = File(recordedFile.path.removeSuffix("mp4") + "ogg") + if (targetFile.exists()) { + targetFile.delete() + } + val start = System.currentTimeMillis() + val session = FFmpegKit.execute("-i \"${recordedFile.path}\" -c:a libvorbis \"${targetFile.path}\"") + val duration = System.currentTimeMillis() - start + Timber.d("Convert to ogg in $duration ms. Size in bytes from ${recordedFile.length()} to ${targetFile.length()}") + return when { + ReturnCode.isSuccess(session.returnCode) -> { + // SUCCESS + targetFile + } + ReturnCode.isCancel(session.returnCode) -> { + // CANCEL + null + } + else -> { + // FAILURE + Timber.e("Command failed with state ${session.state} and rc ${session.returnCode}.${session.failStackTrace}") + // TODO throw? + null + } + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/voice/VoiceRecorderProvider.kt b/vector/src/main/java/im/vector/app/features/voice/VoiceRecorderProvider.kt new file mode 100644 index 0000000000..004d520a6f --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/voice/VoiceRecorderProvider.kt @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.voice + +import android.content.Context +import android.os.Build +import javax.inject.Inject + +class VoiceRecorderProvider @Inject constructor( + private val context: Context +) { + fun provideVoiceRecorder(): VoiceRecorder { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + VoiceRecorderQ(context) + } else { + VoiceRecorderL(context) + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/voice/VoiceRecorderQ.kt b/vector/src/main/java/im/vector/app/features/voice/VoiceRecorderQ.kt new file mode 100644 index 0000000000..d6f4676893 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/voice/VoiceRecorderQ.kt @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.voice + +import android.content.Context +import android.media.MediaRecorder +import android.os.Build +import androidx.annotation.RequiresApi +import java.io.File + +@RequiresApi(Build.VERSION_CODES.Q) +class VoiceRecorderQ(context: Context) : AbstractVoiceRecorder(context, "ogg") { + override fun setOutputFormat(mediaRecorder: MediaRecorder) { + // We can directly use OGG here + mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.OGG) + mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.OPUS) + } + + override fun convertFile(recordedFile: File?): File? { + // Nothing to do here + return recordedFile + } +} From 343ea42ef56a1dc8c97f993a0190a21f1a2de9b8 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 15 Jul 2021 17:30:48 +0200 Subject: [PATCH 058/242] Fix issue on Android 21 --- .../java/org/matrix/android/sdk/api/util/MimeTypes.kt | 2 ++ .../lib/multipicker/utils/ContentResolverUtil.kt | 10 +++++++++- .../home/room/detail/composer/VoiceMessageHelper.kt | 1 - 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MimeTypes.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MimeTypes.kt index 182b37f2ad..ef47775f1b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MimeTypes.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MimeTypes.kt @@ -31,6 +31,8 @@ object MimeTypes { const val Jpeg = "image/jpeg" const val Gif = "image/gif" + const val Ogg = "audio/ogg" + fun String?.normalizeMimeType() = if (this == BadJpg) Jpeg else this fun String?.isMimeTypeImage() = this?.startsWith("image/").orFalse() diff --git a/multipicker/src/main/java/im/vector/lib/multipicker/utils/ContentResolverUtil.kt b/multipicker/src/main/java/im/vector/lib/multipicker/utils/ContentResolverUtil.kt index 6cadfa6ce7..78136c274a 100644 --- a/multipicker/src/main/java/im/vector/lib/multipicker/utils/ContentResolverUtil.kt +++ b/multipicker/src/main/java/im/vector/lib/multipicker/utils/ContentResolverUtil.kt @@ -141,7 +141,7 @@ fun Uri.toMultiPickerAudioType(context: Context): MultiPickerAudioType? { MultiPickerAudioType( name, size, - context.contentResolver.getType(this), + sanitize(context.contentResolver.getType(this)), this, duration ) @@ -150,3 +150,11 @@ fun Uri.toMultiPickerAudioType(context: Context): MultiPickerAudioType? { } } } + +private fun sanitize(type: String?): String? { + if (type == "application/ogg") { + // Not supported on old system + return "audio/ogg" + } + return type +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageHelper.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageHelper.kt index 4f07cf98fe..1b78425f5e 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageHelper.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageHelper.kt @@ -74,7 +74,6 @@ class VoiceMessageHelper @Inject constructor( voiceRecorder.getVoiceMessageFile() } try { - // TODO Improve this voiceMessageFile?.let { val outputFileUri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".fileProvider", it) return outputFileUri From 13ae0ba5f14d8004b18d3c9fc0365933b5bc3b83 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 16 Jul 2021 11:00:25 +0200 Subject: [PATCH 059/242] Convert voice message to be able to play on Android 28 and below --- .../home/room/detail/RoomDetailViewModel.kt | 6 +- .../app/features/voice/VoicePlayerHelper.kt | 73 +++++++++++++++++++ 2 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 vector/src/main/java/im/vector/app/features/voice/VoicePlayerHelper.kt diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index 4eb5519404..60400ce9c6 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -57,6 +57,7 @@ import im.vector.app.features.home.room.typing.TypingHelper import im.vector.app.features.powerlevel.PowerLevelsObservableFactory import im.vector.app.features.session.coroutineScope import im.vector.app.features.settings.VectorPreferences +import im.vector.app.features.voice.VoicePlayerHelper import io.reactivex.Observable import io.reactivex.rxkotlin.subscribeBy import io.reactivex.schedulers.Schedulers @@ -121,6 +122,7 @@ class RoomDetailViewModel @AssistedInject constructor( private val directRoomHelper: DirectRoomHelper, private val jitsiService: JitsiService, private val voiceMessageHelper: VoiceMessageHelper, + private val voicePlayerHelper: VoicePlayerHelper, timelineFactory: TimelineFactory ) : VectorViewModel(initialState), Timeline.Listener, ChatEffectManager.Delegate, CallProtocolsChecker.Listener { @@ -647,8 +649,10 @@ class RoomDetailViewModel @AssistedInject constructor( try { // Download can fail val audioFile = session.fileService().downloadFile(action.messageAudioContent) + // Conversion can fail, fallback to the original file in this case and let the player fail for us + val convertedFile = voicePlayerHelper.convertFile(audioFile) ?: audioFile // Play can fail - voiceMessageHelper.startOrPausePlayback(action.eventId, audioFile) + voiceMessageHelper.startOrPausePlayback(action.eventId, convertedFile) } catch (failure: Throwable) { _viewEvents.post(RoomDetailViewEvents.Failure(failure)) } diff --git a/vector/src/main/java/im/vector/app/features/voice/VoicePlayerHelper.kt b/vector/src/main/java/im/vector/app/features/voice/VoicePlayerHelper.kt new file mode 100644 index 0000000000..f1b316c456 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/voice/VoicePlayerHelper.kt @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.voice + +import android.content.Context +import android.os.Build +import com.arthenica.ffmpegkit.FFmpegKit +import com.arthenica.ffmpegkit.ReturnCode +import timber.log.Timber +import java.io.File +import javax.inject.Inject + +class VoicePlayerHelper @Inject constructor( + context: Context +) { + private val outputDirectory = File(context.cacheDir, "voice_records") + + init { + if (!outputDirectory.exists()) { + outputDirectory.mkdirs() + } + } + + /** + * Ensure the file is encoded using aac audio codec + */ + fun convertFile(file: File): File? { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + // Nothing to do + file + } else { + // Convert to mp4 + val targetFile = File(outputDirectory, "Voice.mp4") + if (targetFile.exists()) { + targetFile.delete() + } + val start = System.currentTimeMillis() + val session = FFmpegKit.execute("-i \"${file.path}\" -c:a aac \"${targetFile.path}\"") + val duration = System.currentTimeMillis() - start + Timber.d("Convert to mp4 in $duration ms. Size in bytes from ${file.length()} to ${targetFile.length()}") + return when { + ReturnCode.isSuccess(session.returnCode) -> { + // SUCCESS + targetFile + } + ReturnCode.isCancel(session.returnCode) -> { + // CANCEL + null + } + else -> { + // FAILURE + Timber.e("Command failed with state ${session.state} and rc ${session.returnCode}.${session.failStackTrace}") + // TODO throw? + null + } + } + } + } +} From 6da4f1d84f9ce9ab92800acdd0cde0bbe7e36b0e Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 16 Jul 2021 11:19:06 +0200 Subject: [PATCH 060/242] Add comment --- .../main/java/im/vector/app/core/services/CallRingPlayer.kt | 3 +++ .../features/home/room/detail/composer/VoiceMessageHelper.kt | 1 + 2 files changed, 4 insertions(+) diff --git a/vector/src/main/java/im/vector/app/core/services/CallRingPlayer.kt b/vector/src/main/java/im/vector/app/core/services/CallRingPlayer.kt index f725742711..f23eb07424 100644 --- a/vector/src/main/java/im/vector/app/core/services/CallRingPlayer.kt +++ b/vector/src/main/java/im/vector/app/core/services/CallRingPlayer.kt @@ -136,6 +136,9 @@ class CallRingPlayerOutgoing( mediaPlayer.setAudioAttributes(AudioAttributes.Builder() .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH) .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION) + // TODO Change to ? + // .setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN) + // .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE) .build()) } else { @Suppress("DEPRECATION") diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageHelper.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageHelper.kt index 1b78425f5e..cdb789997d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageHelper.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageHelper.kt @@ -128,6 +128,7 @@ class VoiceMessageHelper @Inject constructor( mediaPlayer = MediaPlayer().apply { setAudioAttributes( AudioAttributes.Builder() + // Do not use CONTENT_TYPE_SPEECH / USAGE_VOICE_COMMUNICATION because we want to play loud here .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) .setUsage(AudioAttributes.USAGE_MEDIA) .build() From 30bb91892da5918cd2a75361ee0e12336d9e2388 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 16 Jul 2021 16:04:50 +0200 Subject: [PATCH 061/242] Fix issue about move overflow. Now use limit for distances --- .../composer/VoiceMessageRecorderView.kt | 41 +++++++++++-------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageRecorderView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageRecorderView.kt index 2390a86a4d..e473ddf3cf 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageRecorderView.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageRecorderView.kt @@ -66,10 +66,15 @@ class VoiceMessageRecorderView @JvmOverloads constructor( private var firstY: Float = 0f private var lastX: Float = 0f private var lastY: Float = 0f + private var lastDistanceX: Float = 0f + private var lastDistanceY: Float = 0f private var recordingTicker: CountUpTimer? = null private val dimensionConverter = DimensionConverter(context.resources) + private val minimumMove = dimensionConverter.dpToPx(10) + private val distanceToLock = dimensionConverter.dpToPx(34).toFloat() + private val distanceToCancel = dimensionConverter.dpToPx(120).toFloat() init { inflate(context, R.layout.view_voice_message_recorder, this) @@ -105,7 +110,7 @@ class VoiceMessageRecorderView @JvmOverloads constructor( } views.voicePlaybackWaveform.setOnClickListener { - if (recordingState !== RecordingState.PLAYBACK) { + if (recordingState != RecordingState.PLAYBACK) { recordingState = RecordingState.PLAYBACK showPlaybackViews() } @@ -147,6 +152,8 @@ class VoiceMessageRecorderView @JvmOverloads constructor( firstY = event.rawY lastX = firstX lastY = firstY + lastDistanceX = 0F + lastDistanceY = 0F } } @@ -164,11 +171,14 @@ class VoiceMessageRecorderView @JvmOverloads constructor( val currentX = event.rawX val currentY = event.rawY - val isRecordingStateChanged = updateRecordingState(currentX, currentY) + val distanceX = abs(firstX - currentX) + val distanceY = abs(firstY - currentY) + + val isRecordingStateChanged = updateRecordingState(currentX, currentY, distanceX, distanceY) when (recordingState) { RecordingState.CANCELLING -> { - val translationAmount = currentX - firstX + val translationAmount = -distanceX.coerceAtMost(distanceToCancel) views.voiceMessageMicButton.translationX = translationAmount views.voiceMessageSlideToCancel.translationX = translationAmount views.voiceMessageSlideToCancel.alpha = 1 - abs(translationAmount) / ((firstX - views.voiceMessageTimer.x) / 3) @@ -178,7 +188,7 @@ class VoiceMessageRecorderView @JvmOverloads constructor( } RecordingState.LOCKING -> { views.voiceMessageLockImage.setImageResource(R.drawable.ic_voice_message_unlocked) - val translationAmount = currentY - firstY + val translationAmount = -distanceY.coerceIn(0F, distanceToLock) views.voiceMessageMicButton.translationY = translationAmount views.voiceMessageLockArrow.translationY = translationAmount } @@ -202,12 +212,12 @@ class VoiceMessageRecorderView @JvmOverloads constructor( } lastX = currentX lastY = currentY + lastDistanceX = distanceX + lastDistanceY = distanceY } - private fun updateRecordingState(currentX: Float, currentY: Float): Boolean { + private fun updateRecordingState(currentX: Float, currentY: Float, distanceX: Float, distanceY: Float): Boolean { val previousRecordingState = recordingState - val distanceX = abs(firstX - currentX) - val distanceY = abs(firstY - currentY) if (recordingState == RecordingState.STARTED) { // Determine if cancelling or locking for the first move action. if (currentX < firstX && distanceX > distanceY) { recordingState = RecordingState.CANCELLING @@ -215,28 +225,27 @@ class VoiceMessageRecorderView @JvmOverloads constructor( recordingState = RecordingState.LOCKING } } else if (recordingState == RecordingState.CANCELLING) { // Check if cancelling conditions met, also check if it should be initial state - if (abs(currentX - firstX) < 10 && lastX < currentX) { + if (distanceX < minimumMove && distanceX < lastDistanceX) { recordingState = RecordingState.STARTED - } else if (shouldCancelRecording()) { + } else if (shouldCancelRecording(distanceX)) { recordingState = RecordingState.CANCELLED } } else if (recordingState == RecordingState.LOCKING) { // Check if locking conditions met, also check if it should be initial state - if (abs(currentY - firstY) < 10 && lastY < currentY) { + if (distanceY < minimumMove && distanceY < lastDistanceY) { recordingState = RecordingState.STARTED - } else if (shouldLockRecording()) { + } else if (shouldLockRecording(distanceY)) { recordingState = RecordingState.LOCKED } } return previousRecordingState != recordingState } - private fun shouldCancelRecording(): Boolean { - return abs(views.voiceMessageTimer.x + views.voiceMessageTimer.width - views.voiceMessageSlideToCancel.x) < 10 - || views.voiceMessageSlideToCancel.x <= views.voiceMessageTimer.x + views.voiceMessageTimer.width // To handle super fast moving + private fun shouldCancelRecording(distanceX: Float): Boolean { + return distanceX >= distanceToCancel } - private fun shouldLockRecording(): Boolean { - return abs(views.voiceMessageLockImage.y + views.voiceMessageLockImage.height - views.voiceMessageLockArrow.y) < 10 + private fun shouldLockRecording(distanceY: Float): Boolean { + return distanceY >= distanceToLock } private fun startRecordingTicker() { From 6caa2b9ae0ced8b889ba90ce12f069d18ea0bd1f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 16 Jul 2021 17:12:08 +0200 Subject: [PATCH 062/242] Fix issue with RTL Still a pb when Mic is on, margins are not correct --- .../src/main/res/values-ldrtl/integers.xml | 1 + .../src/main/res/values/integers.xml | 1 + .../composer/VoiceMessageRecorderView.kt | 53 +++++++++++++------ .../layout/view_voice_message_recorder.xml | 2 +- 4 files changed, 39 insertions(+), 18 deletions(-) diff --git a/library/ui-styles/src/main/res/values-ldrtl/integers.xml b/library/ui-styles/src/main/res/values-ldrtl/integers.xml index e8a746b687..88b587c96f 100644 --- a/library/ui-styles/src/main/res/values-ldrtl/integers.xml +++ b/library/ui-styles/src/main/res/values-ldrtl/integers.xml @@ -1,6 +1,7 @@ + -1 180 \ No newline at end of file diff --git a/library/ui-styles/src/main/res/values/integers.xml b/library/ui-styles/src/main/res/values/integers.xml index 75e8bb6f9a..2f6a1b0bc4 100644 --- a/library/ui-styles/src/main/res/values/integers.xml +++ b/library/ui-styles/src/main/res/values/integers.xml @@ -7,6 +7,7 @@ 200 + 1 0 750 diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageRecorderView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageRecorderView.kt index e473ddf3cf..c59c039abf 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageRecorderView.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageRecorderView.kt @@ -22,6 +22,7 @@ import android.util.AttributeSet import android.view.MotionEvent import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.view.isVisible +import androidx.core.view.updateLayoutParams import im.vector.app.BuildConfig import im.vector.app.R import im.vector.app.core.hardware.vibrate @@ -75,6 +76,7 @@ class VoiceMessageRecorderView @JvmOverloads constructor( private val minimumMove = dimensionConverter.dpToPx(10) private val distanceToLock = dimensionConverter.dpToPx(34).toFloat() private val distanceToCancel = dimensionConverter.dpToPx(120).toFloat() + private val rtlXMultiplier = context.resources.getInteger(R.integer.rtl_x_multiplier) init { inflate(context, R.layout.view_voice_message_recorder, this) @@ -85,7 +87,7 @@ class VoiceMessageRecorderView @JvmOverloads constructor( } fun initVoiceRecordingViews() { - hideRecordingViews(animationDuration = 0) + hideRecordingViews() stopRecordingTicker() views.voiceMessageMicButton.isVisible = true @@ -95,7 +97,7 @@ class VoiceMessageRecorderView @JvmOverloads constructor( private fun initListeners() { views.voiceMessageSendButton.setOnClickListener { stopRecordingTicker() - hideRecordingViews(animationDuration = 0) + hideRecordingViews() views.voiceMessageSendButton.isVisible = false recordingState = RecordingState.NONE callback?.onVoiceRecordingEnded(isCancelled = false) @@ -103,7 +105,7 @@ class VoiceMessageRecorderView @JvmOverloads constructor( views.voiceMessageDeletePlayback.setOnClickListener { stopRecordingTicker() - hideRecordingViews(animationDuration = 0) + hideRecordingViews() views.voiceMessageSendButton.isVisible = false recordingState = RecordingState.NONE callback?.onVoiceRecordingEnded(isCancelled = true) @@ -178,19 +180,25 @@ class VoiceMessageRecorderView @JvmOverloads constructor( when (recordingState) { RecordingState.CANCELLING -> { - val translationAmount = -distanceX.coerceAtMost(distanceToCancel) - views.voiceMessageMicButton.translationX = translationAmount - views.voiceMessageSlideToCancel.translationX = translationAmount - views.voiceMessageSlideToCancel.alpha = 1 - abs(translationAmount) / ((firstX - views.voiceMessageTimer.x) / 3) + val translationAmount = distanceX.coerceAtMost(distanceToCancel) + views.voiceMessageMicButton.translationX = -translationAmount * rtlXMultiplier + views.voiceMessageSlideToCancel.translationX = -translationAmount / 2 * rtlXMultiplier + views.voiceMessageSlideToCancel.alpha = 1 - translationAmount / distanceToCancel / 3 views.voiceMessageLockBackground.isVisible = false views.voiceMessageLockImage.isVisible = false views.voiceMessageLockArrow.isVisible = false + // Reset Y translations + views.voiceMessageMicButton.translationY = 0F + views.voiceMessageLockArrow.translationY = 0F } RecordingState.LOCKING -> { views.voiceMessageLockImage.setImageResource(R.drawable.ic_voice_message_unlocked) val translationAmount = -distanceY.coerceIn(0F, distanceToLock) views.voiceMessageMicButton.translationY = translationAmount views.voiceMessageLockArrow.translationY = translationAmount + // Reset X translations + views.voiceMessageMicButton.translationX = 0F + views.voiceMessageSlideToCancel.translationX = 0F } RecordingState.CANCELLED -> { callback?.onVoiceRecordingEnded(true) @@ -218,19 +226,23 @@ class VoiceMessageRecorderView @JvmOverloads constructor( private fun updateRecordingState(currentX: Float, currentY: Float, distanceX: Float, distanceY: Float): Boolean { val previousRecordingState = recordingState - if (recordingState == RecordingState.STARTED) { // Determine if cancelling or locking for the first move action. - if (currentX < firstX && distanceX > distanceY) { + if (recordingState == RecordingState.STARTED) { + // Determine if cancelling or locking for the first move action. + if (((currentX < firstX && rtlXMultiplier == 1) || (currentX > firstX && rtlXMultiplier == -1)) + && distanceX > distanceY) { recordingState = RecordingState.CANCELLING } else if (currentY < firstY && distanceY > distanceX) { recordingState = RecordingState.LOCKING } - } else if (recordingState == RecordingState.CANCELLING) { // Check if cancelling conditions met, also check if it should be initial state + } else if (recordingState == RecordingState.CANCELLING) { + // Check if cancelling conditions met, also check if it should be initial state if (distanceX < minimumMove && distanceX < lastDistanceX) { recordingState = RecordingState.STARTED } else if (shouldCancelRecording(distanceX)) { recordingState = RecordingState.CANCELLED } - } else if (recordingState == RecordingState.LOCKING) { // Check if locking conditions met, also check if it should be initial state + } else if (recordingState == RecordingState.LOCKING) { + // Check if locking conditions met, also check if it should be initial state if (distanceY < minimumMove && distanceY < lastDistanceY) { recordingState = RecordingState.STARTED } else if (shouldLockRecording(distanceY)) { @@ -323,7 +335,9 @@ class VoiceMessageRecorderView @JvmOverloads constructor( private fun showRecordingViews() { views.voiceMessageMicButton.setImageResource(R.drawable.ic_voice_mic_recording) - (views.voiceMessageMicButton.layoutParams as MarginLayoutParams).apply { setMargins(0, 0, 0, 0) } + views.voiceMessageMicButton.updateLayoutParams { + setMargins(0, 0, 0, 0) + } views.voiceMessageLockBackground.isVisible = true views.voiceMessageLockBackground.animate().setDuration(300).translationY(-dimensionConverter.dpToPx(148).toFloat()).start() views.voiceMessageLockImage.isVisible = true @@ -337,11 +351,16 @@ class VoiceMessageRecorderView @JvmOverloads constructor( views.voiceMessageSendButton.isVisible = false } - private fun hideRecordingViews(animationDuration: Int = 300) { + private fun hideRecordingViews() { views.voiceMessageMicButton.setImageResource(R.drawable.ic_voice_mic) - views.voiceMessageMicButton.animate().translationX(0f).translationY(0f).setDuration(animationDuration.toLong()).setDuration(0).start() - (views.voiceMessageMicButton.layoutParams as MarginLayoutParams).apply { - setMargins(0, 0, dimensionConverter.dpToPx(12), dimensionConverter.dpToPx(12)) + views.voiceMessageMicButton.animate().translationX(0f).translationY(0f).setDuration(0).start() + views.voiceMessageMicButton.updateLayoutParams { + if (rtlXMultiplier == -1) { + // RTL + setMargins(dimensionConverter.dpToPx(10), 0, 0, dimensionConverter.dpToPx(12)) + } else { + setMargins(0, 0, dimensionConverter.dpToPx(12), dimensionConverter.dpToPx(10)) + } } views.voiceMessageLockBackground.isVisible = false views.voiceMessageLockBackground.animate().translationY(0f).start() @@ -357,7 +376,7 @@ class VoiceMessageRecorderView @JvmOverloads constructor( } private fun showRecordingLockedViews() { - hideRecordingViews(animationDuration = 0) + hideRecordingViews() views.voiceMessagePlaybackLayout.isVisible = true views.voiceMessagePlaybackTimerIndicator.isVisible = true views.voicePlaybackControlButton.isVisible = false diff --git a/vector/src/main/res/layout/view_voice_message_recorder.xml b/vector/src/main/res/layout/view_voice_message_recorder.xml index 7736d76b0e..7aaa0bd05f 100644 --- a/vector/src/main/res/layout/view_voice_message_recorder.xml +++ b/vector/src/main/res/layout/view_voice_message_recorder.xml @@ -23,7 +23,7 @@ android:id="@+id/voiceMessageMicButton" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginEnd="12dp" + android:layout_marginEnd="10dp" android:layout_marginBottom="12dp" android:background="?android:attr/selectableItemBackground" android:contentDescription="@string/a11y_start_voice_message" From 49a44bd042543bde79119a833683f2b74ed3ab8d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 21 Jul 2021 11:22:35 +0200 Subject: [PATCH 063/242] Do not change txnId it in case of retry, if not provided in the params Also create txnId using UUID.randomUUID() instead of Random.nextInt(Integer.MAX_VALUE) for coherency --- .../sdk/internal/crypto/tasks/SendToDeviceTask.kt | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendToDeviceTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendToDeviceTask.kt index 41a5118be0..4b60d54238 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendToDeviceTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendToDeviceTask.kt @@ -22,8 +22,8 @@ import org.matrix.android.sdk.internal.crypto.model.rest.SendToDeviceBody import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.task.Task +import java.util.UUID import javax.inject.Inject -import kotlin.random.Random internal interface SendToDeviceTask : Task { data class Params( @@ -31,7 +31,7 @@ internal interface SendToDeviceTask : Task { val eventType: String, // the content to send. Map from user_id to device_id to content dictionary. val contentMap: MXUsersDevicesMap, - // the transactionId + // the transactionId. If not provided, a transactionId will be created by the task val transactionId: String? = null ) } @@ -46,16 +46,21 @@ internal class DefaultSendToDeviceTask @Inject constructor( messages = params.contentMap.map ) + // Create a unique txnId first, to use the same value if the request is retried + val txnId = params.transactionId ?: createUniqueTxnId() + return executeRequest( globalErrorReceiver, canRetry = true, maxRetriesCount = 3 ) { cryptoApi.sendToDevice( - params.eventType, - params.transactionId ?: Random.nextInt(Integer.MAX_VALUE).toString(), - sendToDeviceBody + eventType = params.eventType, + transactionId = txnId, + body = sendToDeviceBody ) } } } + +internal fun createUniqueTxnId() = UUID.randomUUID().toString() From 7513e972d13f72ab37de7f62241bc16f306adbdf Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 21 Jul 2021 11:43:47 +0200 Subject: [PATCH 064/242] Ensure the same txnId is reused if the Worker is started again. --- .../sdk/internal/crypto/CancelGossipRequestWorker.kt | 11 ++++++++--- .../crypto/IncomingGossipingRequestManager.kt | 8 +++++--- .../crypto/OutgoingGossipingRequestManager.kt | 7 +++++-- .../sdk/internal/crypto/SendGossipRequestWorker.kt | 10 +++++++--- .../android/sdk/internal/crypto/SendGossipWorker.kt | 10 +++++++--- 5 files changed, 32 insertions(+), 14 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CancelGossipRequestWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CancelGossipRequestWorker.kt index 7e92ff3e88..178bb4233d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CancelGossipRequestWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CancelGossipRequestWorker.kt @@ -23,12 +23,12 @@ import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.failure.shouldBeRetried import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType -import org.matrix.android.sdk.api.session.events.model.LocalEcho import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.internal.crypto.model.rest.ShareRequestCancellation import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask +import org.matrix.android.sdk.internal.crypto.tasks.createUniqueTxnId import org.matrix.android.sdk.internal.session.SessionComponent import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker import org.matrix.android.sdk.internal.worker.SessionWorkerParams @@ -43,6 +43,9 @@ internal class CancelGossipRequestWorker(context: Context, override val sessionId: String, val requestId: String, val recipients: Map>, + // The txnId for the sendToDevice request. Nullable for compatibility reason, but MUST always be provided + // to use the same value if this worker is retried. + val txnId: String? = null, override val lastFailureMessage: String? = null ) : SessionWorkerParams { companion object { @@ -51,6 +54,7 @@ internal class CancelGossipRequestWorker(context: Context, sessionId = sessionId, requestId = request.requestId, recipients = request.recipients, + txnId = createUniqueTxnId(), lastFailureMessage = null ) } @@ -66,7 +70,8 @@ internal class CancelGossipRequestWorker(context: Context, } override suspend fun doSafeWork(params: Params): Result { - val localId = LocalEcho.createLocalEchoId() + // (temporary code) + val txnId = params.txnId ?: createUniqueTxnId() val contentMap = MXUsersDevicesMap() val toDeviceContent = ShareRequestCancellation( requestingDeviceId = credentials.deviceId, @@ -92,7 +97,7 @@ internal class CancelGossipRequestWorker(context: Context, SendToDeviceTask.Params( eventType = EventType.ROOM_KEY_REQUEST, contentMap = contentMap, - transactionId = localId + transactionId = txnId ) ) cryptoStore.updateOutgoingGossipingRequestState(params.requestId, OutgoingGossipingRequestState.CANCELLED) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt index 4a0a274f4f..e8640d5011 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt @@ -35,6 +35,7 @@ import org.matrix.android.sdk.internal.crypto.model.rest.GossipingDefaultContent import org.matrix.android.sdk.internal.crypto.model.rest.GossipingToDeviceObject import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyRequestBody import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore +import org.matrix.android.sdk.internal.crypto.tasks.createUniqueTxnId import org.matrix.android.sdk.internal.di.SessionId import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers @@ -356,7 +357,8 @@ internal class IncomingGossipingRequestManager @Inject constructor( secretValue = secretValue, requestUserId = request.userId, requestDeviceId = request.deviceId, - requestId = request.requestId + requestId = request.requestId, + txnId = createUniqueTxnId() ) cryptoStore.updateGossipingRequestState(request, GossipingRequestState.ACCEPTING) @@ -376,13 +378,13 @@ internal class IncomingGossipingRequestManager @Inject constructor( } request.share = { secretValue -> - val params = SendGossipWorker.Params( sessionId = userId, secretValue = secretValue, requestUserId = request.userId, requestDeviceId = request.deviceId, - requestId = request.requestId + requestId = request.requestId, + txnId = createUniqueTxnId() ) cryptoStore.updateGossipingRequestState(request, GossipingRequestState.ACCEPTING) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingGossipingRequestManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingGossipingRequestManager.kt index c86f2be0a3..6005fae854 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingGossipingRequestManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingGossipingRequestManager.kt @@ -26,6 +26,7 @@ import org.matrix.android.sdk.internal.worker.WorkerParamsFactory import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import org.matrix.android.sdk.internal.crypto.tasks.createUniqueTxnId import timber.log.Timber import javax.inject.Inject @@ -131,7 +132,8 @@ internal class OutgoingGossipingRequestManager @Inject constructor( val params = SendGossipRequestWorker.Params( sessionId = sessionId, keyShareRequest = request as? OutgoingRoomKeyRequest, - secretShareRequest = request as? OutgoingSecretRequest + secretShareRequest = request as? OutgoingSecretRequest, + txnId = createUniqueTxnId() ) cryptoStore.updateOutgoingGossipingRequestState(request.requestId, OutgoingGossipingRequestState.SENDING) val workRequest = gossipingWorkManager.createWork(WorkerParamsFactory.toData(params), true) @@ -154,7 +156,8 @@ internal class OutgoingGossipingRequestManager @Inject constructor( if (resend) { val reSendParams = SendGossipRequestWorker.Params( sessionId = sessionId, - keyShareRequest = request.copy(requestId = LocalEcho.createLocalEchoId()) + keyShareRequest = request.copy(requestId = LocalEcho.createLocalEchoId()), + txnId = createUniqueTxnId() ) val reSendWorkRequest = gossipingWorkManager.createWork(WorkerParamsFactory.toData(reSendParams), true) gossipingWorkManager.postWork(reSendWorkRequest) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipRequestWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipRequestWorker.kt index e8d567b944..cff4002610 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipRequestWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipRequestWorker.kt @@ -23,7 +23,6 @@ import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.failure.shouldBeRetried import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType -import org.matrix.android.sdk.api.session.events.model.LocalEcho import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.internal.crypto.model.rest.GossipingToDeviceObject @@ -31,6 +30,7 @@ import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyShareRequest import org.matrix.android.sdk.internal.crypto.model.rest.SecretShareRequest import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask +import org.matrix.android.sdk.internal.crypto.tasks.createUniqueTxnId import org.matrix.android.sdk.internal.session.SessionComponent import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker import org.matrix.android.sdk.internal.worker.SessionWorkerParams @@ -46,6 +46,9 @@ internal class SendGossipRequestWorker(context: Context, override val sessionId: String, val keyShareRequest: OutgoingRoomKeyRequest? = null, val secretShareRequest: OutgoingSecretRequest? = null, + // The txnId for the sendToDevice request. Nullable for compatibility reason, but MUST always be provided + // to use the same value if this worker is retried. + val txnId: String? = null, override val lastFailureMessage: String? = null ) : SessionWorkerParams @@ -58,7 +61,8 @@ internal class SendGossipRequestWorker(context: Context, } override suspend fun doSafeWork(params: Params): Result { - val localId = LocalEcho.createLocalEchoId() + // (temporary code) + val txnId = params.txnId ?: createUniqueTxnId() val contentMap = MXUsersDevicesMap() val eventType: String val requestId: String @@ -122,7 +126,7 @@ internal class SendGossipRequestWorker(context: Context, SendToDeviceTask.Params( eventType = eventType, contentMap = contentMap, - transactionId = localId + transactionId = txnId ) ) cryptoStore.updateOutgoingGossipingRequestState(requestId, OutgoingGossipingRequestState.SENT) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipWorker.kt index 8c68057056..e6531e72ac 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipWorker.kt @@ -23,7 +23,6 @@ import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.failure.shouldBeRetried import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType -import org.matrix.android.sdk.api.session.events.model.LocalEcho import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter @@ -31,6 +30,7 @@ import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.internal.crypto.model.event.SecretSendEventContent import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask +import org.matrix.android.sdk.internal.crypto.tasks.createUniqueTxnId import org.matrix.android.sdk.internal.session.SessionComponent import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker import org.matrix.android.sdk.internal.worker.SessionWorkerParams @@ -48,6 +48,9 @@ internal class SendGossipWorker(context: Context, val requestUserId: String?, val requestDeviceId: String?, val requestId: String?, + // The txnId for the sendToDevice request. Nullable for compatibility reason, but MUST always be provided + // to use the same value if this worker is retried. + val txnId: String? = null, override val lastFailureMessage: String? = null ) : SessionWorkerParams @@ -62,7 +65,8 @@ internal class SendGossipWorker(context: Context, } override suspend fun doSafeWork(params: Params): Result { - val localId = LocalEcho.createLocalEchoId() + // (temporary code) + val txnId = params.txnId ?: createUniqueTxnId() val eventType: String = EventType.SEND_SECRET val toDeviceContent = SecretSendEventContent( @@ -127,7 +131,7 @@ internal class SendGossipWorker(context: Context, SendToDeviceTask.Params( eventType = EventType.ENCRYPTED, contentMap = sendToDeviceMap, - transactionId = localId + transactionId = txnId ) ) cryptoStore.updateGossipingRequestState( From 0d408264e0ef02ad5d7bfa7738b6c313d17f3502 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 21 Jul 2021 11:46:11 +0200 Subject: [PATCH 065/242] Bad copy paste --- .../sdk/api/session/crypto/verification/VerificationService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationService.kt index 54a1e896ae..f4b7e43f63 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationService.kt @@ -52,7 +52,7 @@ interface VerificationService { transactionId: String?): String? /** - * Request a key verification from another user using toDevice events. + * Request a key verification from another user using event in a room. */ fun requestKeyVerificationInDMs(methods: List, otherUserId: String, From a2180ec69560db6dee221bc011b91d9b452e99a3 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 21 Jul 2021 12:01:20 +0200 Subject: [PATCH 066/242] Create RequestIdHelper.createUniqueRequestId() for code clarity --- .../crypto/OutgoingGossipingRequestManager.kt | 4 ++-- .../crypto/store/db/RealmCryptoStore.kt | 6 ++--- .../internal/crypto/util/RequestIdHelper.kt | 23 +++++++++++++++++++ 3 files changed, 28 insertions(+), 5 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/util/RequestIdHelper.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingGossipingRequestManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingGossipingRequestManager.kt index 6005fae854..ccdb5ab137 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingGossipingRequestManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingGossipingRequestManager.kt @@ -16,7 +16,6 @@ package org.matrix.android.sdk.internal.crypto -import org.matrix.android.sdk.api.session.events.model.LocalEcho import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyRequestBody import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.di.SessionId @@ -27,6 +26,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch import org.matrix.android.sdk.internal.crypto.tasks.createUniqueTxnId +import org.matrix.android.sdk.internal.crypto.util.RequestIdHelper import timber.log.Timber import javax.inject.Inject @@ -156,7 +156,7 @@ internal class OutgoingGossipingRequestManager @Inject constructor( if (resend) { val reSendParams = SendGossipRequestWorker.Params( sessionId = sessionId, - keyShareRequest = request.copy(requestId = LocalEcho.createLocalEchoId()), + keyShareRequest = request.copy(requestId = RequestIdHelper.createUniqueRequestId()), txnId = createUniqueTxnId() ) val reSendWorkRequest = gossipingWorkManager.createWork(WorkerParamsFactory.toData(reSendParams), true) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt index 9ae93d61eb..cb0ae45295 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt @@ -27,7 +27,6 @@ import io.realm.Sort import io.realm.kotlin.where import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo import org.matrix.android.sdk.api.session.events.model.Event -import org.matrix.android.sdk.api.session.events.model.LocalEcho import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.toOptional @@ -89,6 +88,7 @@ import org.matrix.android.sdk.internal.crypto.store.db.query.delete import org.matrix.android.sdk.internal.crypto.store.db.query.get import org.matrix.android.sdk.internal.crypto.store.db.query.getById import org.matrix.android.sdk.internal.crypto.store.db.query.getOrCreate +import org.matrix.android.sdk.internal.crypto.util.RequestIdHelper import org.matrix.android.sdk.internal.database.mapper.ContentMapper import org.matrix.android.sdk.internal.database.tools.RealmDebugTools import org.matrix.android.sdk.internal.di.CryptoDatabase @@ -1109,7 +1109,7 @@ internal class RealmCryptoStore @Inject constructor( if (existing == null) { request = realm.createObject(OutgoingGossipingRequestEntity::class.java).apply { - this.requestId = LocalEcho.createLocalEchoId() + this.requestId = RequestIdHelper.createUniqueRequestId() this.setRecipients(recipients) this.requestState = OutgoingGossipingRequestState.UNSENT this.type = GossipRequestType.KEY @@ -1139,7 +1139,7 @@ internal class RealmCryptoStore @Inject constructor( this.type = GossipRequestType.SECRET setRecipients(recipients) this.requestState = OutgoingGossipingRequestState.UNSENT - this.requestId = LocalEcho.createLocalEchoId() + this.requestId = RequestIdHelper.createUniqueRequestId() this.requestedInfoStr = secretName }.toOutgoingGossipingRequest() as? OutgoingSecretRequest } else { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/util/RequestIdHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/util/RequestIdHelper.kt new file mode 100644 index 0000000000..9b3cb1942a --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/util/RequestIdHelper.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.crypto.util + +import java.util.UUID + +internal object RequestIdHelper { + fun createUniqueRequestId() = "req_" + UUID.randomUUID().toString() +} From bb617ffaa7d0d19fe5891d7af5efb3d73e39289f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 21 Jul 2021 13:59:19 +0200 Subject: [PATCH 067/242] Update matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CancelGossipRequestWorker.kt Co-authored-by: poljar --- .../android/sdk/internal/crypto/CancelGossipRequestWorker.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CancelGossipRequestWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CancelGossipRequestWorker.kt index 178bb4233d..26c0403b3f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CancelGossipRequestWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CancelGossipRequestWorker.kt @@ -43,7 +43,7 @@ internal class CancelGossipRequestWorker(context: Context, override val sessionId: String, val requestId: String, val recipients: Map>, - // The txnId for the sendToDevice request. Nullable for compatibility reason, but MUST always be provided + // The txnId for the sendToDevice request. Nullable for compatibility reasons, but MUST always be provided // to use the same value if this worker is retried. val txnId: String? = null, override val lastFailureMessage: String? = null From bf1ce17972a5050859e35325883542bf3ccbb4f2 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 21 Jul 2021 13:59:32 +0200 Subject: [PATCH 068/242] Update matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipRequestWorker.kt Co-authored-by: poljar --- .../android/sdk/internal/crypto/SendGossipRequestWorker.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipRequestWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipRequestWorker.kt index cff4002610..bee18d4c25 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipRequestWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipRequestWorker.kt @@ -46,7 +46,7 @@ internal class SendGossipRequestWorker(context: Context, override val sessionId: String, val keyShareRequest: OutgoingRoomKeyRequest? = null, val secretShareRequest: OutgoingSecretRequest? = null, - // The txnId for the sendToDevice request. Nullable for compatibility reason, but MUST always be provided + // The txnId for the sendToDevice request. Nullable for compatibility reasons, but MUST always be provided // to use the same value if this worker is retried. val txnId: String? = null, override val lastFailureMessage: String? = null From eded4eacd7d871c4942a0946ee49b71bd52c3743 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 21 Jul 2021 13:59:40 +0200 Subject: [PATCH 069/242] Update matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipWorker.kt Co-authored-by: poljar --- .../org/matrix/android/sdk/internal/crypto/SendGossipWorker.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipWorker.kt index e6531e72ac..8c49a6f47f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipWorker.kt @@ -48,7 +48,7 @@ internal class SendGossipWorker(context: Context, val requestUserId: String?, val requestDeviceId: String?, val requestId: String?, - // The txnId for the sendToDevice request. Nullable for compatibility reason, but MUST always be provided + // The txnId for the sendToDevice request. Nullable for compatibility reasons, but MUST always be provided // to use the same value if this worker is retried. val txnId: String? = null, override val lastFailureMessage: String? = null From ab6e0767bbc9879a92d835c81e5756b9088cbc1e Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 21 Jul 2021 14:05:51 +0200 Subject: [PATCH 070/242] Update matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationService.kt Co-authored-by: poljar --- .../sdk/api/session/crypto/verification/VerificationService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationService.kt index f4b7e43f63..b9d0c0ad2c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationService.kt @@ -52,7 +52,7 @@ interface VerificationService { transactionId: String?): String? /** - * Request a key verification from another user using event in a room. + * Request key verification with another user via room events (instead of the to-device API) */ fun requestKeyVerificationInDMs(methods: List, otherUserId: String, From 4ead39038c06ca1ed120e7bcf8da57f8e8d9e1f5 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 21 Jul 2021 14:06:47 +0200 Subject: [PATCH 071/242] Code review --- .../android/sdk/internal/crypto/tasks/SendToDeviceTask.kt | 4 +++- .../android/sdk/internal/crypto/util/RequestIdHelper.kt | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendToDeviceTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendToDeviceTask.kt index 4b60d54238..c6af9b0cd1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendToDeviceTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendToDeviceTask.kt @@ -46,7 +46,9 @@ internal class DefaultSendToDeviceTask @Inject constructor( messages = params.contentMap.map ) - // Create a unique txnId first, to use the same value if the request is retried + // If params.transactionId is not provided, we create a unique txnId. + // It's important to do that outside the requestBlock parameter of executeRequest() + // to use the same value if the request is retried val txnId = params.transactionId ?: createUniqueTxnId() return executeRequest( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/util/RequestIdHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/util/RequestIdHelper.kt index 9b3cb1942a..3106d5820c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/util/RequestIdHelper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/util/RequestIdHelper.kt @@ -19,5 +19,5 @@ package org.matrix.android.sdk.internal.crypto.util import java.util.UUID internal object RequestIdHelper { - fun createUniqueRequestId() = "req_" + UUID.randomUUID().toString() + fun createUniqueRequestId() = UUID.randomUUID().toString() } From 270ca779ffb1c76f7a0e796413dde25f100e71cd Mon Sep 17 00:00:00 2001 From: Joe Sagawa Date: Wed, 21 Jul 2021 12:14:32 +0000 Subject: [PATCH 072/242] Translated using Weblate (Japanese) Currently translated at 62.6% (1577 of 2518 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/ --- vector/src/main/res/values-ja/strings.xml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml index c948d6eddb..9028788fc3 100644 --- a/vector/src/main/res/values-ja/strings.xml +++ b/vector/src/main/res/values-ja/strings.xml @@ -391,7 +391,7 @@ メールと電話番号の同時登録はまだシステムが対応できませんが、電話番号だけの登録は可能です。 \n \n設定からプロフィールにメールアドレスを追加できます。 - 接続先サーバ側かが、機械による自動登録ではなく、あなたが人間であることを確認したいとのことです + このホームサーバはあなたがロボットではない認証を求めます。 ユーザ名はすでに使用されています 接続先サーバ: 認証サーバ: @@ -499,7 +499,7 @@ あなたはこの部屋で権限がありません。 部屋 %s は、見ることができません。 ユーザ名 - 接続先サーバ URL + ホームサーバ URL 認証サーバ URL ログイン Matrixアプリを追加 @@ -1279,7 +1279,7 @@ Matrixでのメッセージの可視性は電子メールと同様です。メ コールし直す 通話をやり直す アクティブな通話(%s) - ホームサーバーにアシスト機能を提供しない場合は、代わりに%sを使用します(IPアドレスは通話中に共有されます) + あなたのホームサーバーがアシスト機能を提供しない場合、代わりに%sを使用します(IPアドレスは通話中に共有されます) ビデオ通話が行われています… フォールバックコールアシストサーバーを許可する 有効な認証情報ではありません @@ -1724,7 +1724,7 @@ Matrixでのメッセージの可視性は電子メールと同様です。メ ウィジェットの読込に失敗しました。 \n%s ウィジェットを再読込 - + ウィジェットの追加者: **送信に失敗 - 部屋を開いてください 新しい招待 @@ -1786,4 +1786,5 @@ Matrixでのメッセージの可視性は電子メールと同様です。メ 毎回確認する 招待 おすすめの部屋 + スペース \ No newline at end of file From 0a33f2c24d36affd995159d174490224fe65adf0 Mon Sep 17 00:00:00 2001 From: metatek Date: Wed, 21 Jul 2021 12:13:50 +0000 Subject: [PATCH 073/242] Translated using Weblate (Japanese) Currently translated at 62.6% (1577 of 2518 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/ --- vector/src/main/res/values-ja/strings.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml index 9028788fc3..36b52712fb 100644 --- a/vector/src/main/res/values-ja/strings.xml +++ b/vector/src/main/res/values-ja/strings.xml @@ -393,7 +393,7 @@ \n設定からプロフィールにメールアドレスを追加できます。 このホームサーバはあなたがロボットではない認証を求めます。 ユーザ名はすでに使用されています - 接続先サーバ: + ホームサーバ: 認証サーバ: 自分用電子メールアドレスで認証をします パスワードを初期化するには, アカウントに登録されている電子メールアドレスを入力してください: @@ -1787,4 +1787,5 @@ Matrixでのメッセージの可視性は電子メールと同様です。メ 招待 おすすめの部屋 スペース + ホームサーバAPIのURL \ No newline at end of file From c5b32ea43fceb74596b96654bd1b9342b9590b97 Mon Sep 17 00:00:00 2001 From: Rintan Date: Wed, 21 Jul 2021 12:12:51 +0000 Subject: [PATCH 074/242] Translated using Weblate (Japanese) Currently translated at 62.6% (1577 of 2518 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/ --- vector/src/main/res/values-ja/strings.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml index 36b52712fb..3ffb086f48 100644 --- a/vector/src/main/res/values-ja/strings.xml +++ b/vector/src/main/res/values-ja/strings.xml @@ -1788,4 +1788,7 @@ Matrixでのメッセージの可視性は電子メールと同様です。メ おすすめの部屋 スペース ホームサーバAPIのURL + 復旧用のメールアドレスを設定します。後からオプションでメールアドレスや電話番号を使用して知人に見つけてもらえるようにできます。 + 電話番号を設定して、後からオプションで知人に見つけてもらえるようにできます。 + %s を使用してみてください \ No newline at end of file From 08ea3c0888a70709bf25e81d5d3325f64b005cb6 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 21 Jul 2021 14:40:07 +0200 Subject: [PATCH 075/242] More useful comment --- .../android/sdk/internal/crypto/CancelGossipRequestWorker.kt | 4 +++- .../android/sdk/internal/crypto/SendGossipRequestWorker.kt | 4 +++- .../matrix/android/sdk/internal/crypto/SendGossipWorker.kt | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CancelGossipRequestWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CancelGossipRequestWorker.kt index 26c0403b3f..0ec020bc46 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CancelGossipRequestWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CancelGossipRequestWorker.kt @@ -70,7 +70,9 @@ internal class CancelGossipRequestWorker(context: Context, } override suspend fun doSafeWork(params: Params): Result { - // (temporary code) + // params.txnId should be provided in all cases now. But Params can be deserialized by + // the WorkManager from data serialized in a previous version of the application, so without the txnId field. + // So if not present, we create a txnId val txnId = params.txnId ?: createUniqueTxnId() val contentMap = MXUsersDevicesMap() val toDeviceContent = ShareRequestCancellation( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipRequestWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipRequestWorker.kt index bee18d4c25..2e26720abb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipRequestWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipRequestWorker.kt @@ -61,7 +61,9 @@ internal class SendGossipRequestWorker(context: Context, } override suspend fun doSafeWork(params: Params): Result { - // (temporary code) + // params.txnId should be provided in all cases now. But Params can be deserialized by + // the WorkManager from data serialized in a previous version of the application, so without the txnId field. + // So if not present, we create a txnId val txnId = params.txnId ?: createUniqueTxnId() val contentMap = MXUsersDevicesMap() val eventType: String diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipWorker.kt index 8c49a6f47f..c5c6d26f79 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipWorker.kt @@ -65,7 +65,9 @@ internal class SendGossipWorker(context: Context, } override suspend fun doSafeWork(params: Params): Result { - // (temporary code) + // params.txnId should be provided in all cases now. But Params can be deserialized by + // the WorkManager from data serialized in a previous version of the application, so without the txnId field. + // So if not present, we create a txnId val txnId = params.txnId ?: createUniqueTxnId() val eventType: String = EventType.SEND_SECRET From 4586b7b7e64de7fd64ec09c0c856ed54100b6702 Mon Sep 17 00:00:00 2001 From: oksya8and8 Date: Wed, 21 Jul 2021 12:41:05 +0000 Subject: [PATCH 076/242] Translated using Weblate (Japanese) Currently translated at 63.1% (1591 of 2518 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/ --- vector/src/main/res/values-ja/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml index 3ffb086f48..b222798d26 100644 --- a/vector/src/main/res/values-ja/strings.xml +++ b/vector/src/main/res/values-ja/strings.xml @@ -1019,7 +1019,7 @@ Matrixでのメッセージの可視性は電子メールと同様です。メ %1$s のデフォルトユーザー %2$s のカスタム (%1$d) タイムライン - エンドツーエンド暗号化を有効にする + エンドツーエンド暗号化を有効にする… 暗号化を有効にする クロス署名 クロス署名は有効です From 66adb9b312531f1d7f9aab560e46a9007ee9307d Mon Sep 17 00:00:00 2001 From: metatek Date: Wed, 21 Jul 2021 12:39:29 +0000 Subject: [PATCH 077/242] Translated using Weblate (Japanese) Currently translated at 63.1% (1591 of 2518 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/ --- vector/src/main/res/values-ja/strings.xml | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml index b222798d26..779e40fa8e 100644 --- a/vector/src/main/res/values-ja/strings.xml +++ b/vector/src/main/res/values-ja/strings.xml @@ -220,7 +220,7 @@ 未送信の文を再送信 未送信の文を削除 ファイルが見つかりません - あなたはこの部屋で発言する権限がありません + あなたはこのルームで発言する権限がありません。 部屋の詳細 参加者 ファイル @@ -471,7 +471,7 @@ 著作権 個人情報保護方針 パスワード: - 接続先サーバ + ホームサーバ 認証サーバ メールアドレスが見つかりません。 この電話番号は既に使用されています。 @@ -642,7 +642,7 @@ ${app_name}アプリがあなたの電話帳へアクセスすることを許可 このデバイスが信頼できることを確認するために、その所有者に何らかの他の方法(直接会って、または、電話で)連絡し、以下のキーが、そのデバイスの「設定」で確認できるキーと一致するかお尋ねください: 部屋のディレクトリを選択 公開の部屋を表示するホームサーバを入力してください - ホームサーバのURL + サーバー名 %sサーバ上のすべての部屋 すべてのローカルの %s 部屋 メッセージが未送信です。今 %1$s または %2$s しますか? @@ -1279,7 +1279,7 @@ Matrixでのメッセージの可視性は電子メールと同様です。メ コールし直す 通話をやり直す アクティブな通話(%s) - あなたのホームサーバーがアシスト機能を提供しない場合、代わりに%sを使用します(IPアドレスは通話中に共有されます) + あなたのホームサーバがアシスト機能を提供しない場合、代わりに%sを使用します(IPアドレスは通話中に共有されます) ビデオ通話が行われています… フォールバックコールアシストサーバーを許可する 有効な認証情報ではありません @@ -1743,7 +1743,7 @@ Matrixでのメッセージの可視性は電子メールと同様です。メ サーバーまたはその部屋一覧が見つかりません 探検したい新しいサーバーの名前を入力してください。 新しいサーバーを追加 - あなたのサーバー + あなたのサーバ 暗号化されたメッセージの復元 セッションの公開名は対話中の相手に閲覧できます 部屋のバージョン @@ -1771,16 +1771,16 @@ Matrixでのメッセージの可視性は電子メールと同様です。メ \"%1$s\"を非公開にしますか? 公開 手動で新しいアドレスを公開 - 他の公開アドレス + 他の公開アドレス: 公開されたアドレスを通して、どのサーバーのどのユーザーでもこの部屋に参加できます。アドレスを公開するには、まずローカルアドレスとして設定する必要があります。 公開アドレス 部屋のアドレス 部屋のアドレス及び部屋一覧における可視性を管理できます。 スペースのアドレスを管理できます。 スペースのアドレス - 部屋のアドレス + ルームのアドレス ゲストの参加を許可 - 部屋へのアクセス + ルームへのアクセス 発信履歴閲覧権限の変更は今後送信されるメッセージにのみ適用されます。既存履歴の表示は影響されません。 無視して続行 毎回確認する @@ -1791,4 +1791,5 @@ Matrixでのメッセージの可視性は電子メールと同様です。メ 復旧用のメールアドレスを設定します。後からオプションでメールアドレスや電話番号を使用して知人に見つけてもらえるようにできます。 電話番号を設定して、後からオプションで知人に見つけてもらえるようにできます。 %s を使用してみてください + アクセスを取り消す \ No newline at end of file From 435ca896b4fd54babd0b3b6e7c3db7390e8554a8 Mon Sep 17 00:00:00 2001 From: Joe Sagawa Date: Wed, 21 Jul 2021 12:36:37 +0000 Subject: [PATCH 078/242] Translated using Weblate (Japanese) Currently translated at 63.1% (1591 of 2518 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/ --- vector/src/main/res/values-ja/strings.xml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml index 779e40fa8e..8213994ae0 100644 --- a/vector/src/main/res/values-ja/strings.xml +++ b/vector/src/main/res/values-ja/strings.xml @@ -173,7 +173,7 @@ はい いいえ 続ける - 最初の未読位置へ移動. + 最新の未読へ移動 部屋を退室 本当にこの部屋を退室しますか? 作成 @@ -1724,7 +1724,7 @@ Matrixでのメッセージの可視性は電子メールと同様です。メ ウィジェットの読込に失敗しました。 \n%s ウィジェットを再読込 - + クッキーを設定し%sとデータを交換する可能性があります ウィジェットの追加者: **送信に失敗 - 部屋を開いてください 新しい招待 @@ -1792,4 +1792,5 @@ Matrixでのメッセージの可視性は電子メールと同様です。メ 電話番号を設定して、後からオプションで知人に見つけてもらえるようにできます。 %s を使用してみてください アクセスを取り消す + 表示 \ No newline at end of file From 33ae09468487e8b0253ee4bacebaf3c15d53e5d3 Mon Sep 17 00:00:00 2001 From: oksya8and8 Date: Wed, 21 Jul 2021 13:00:44 +0000 Subject: [PATCH 079/242] Translated using Weblate (Japanese) Currently translated at 63.2% (1592 of 2518 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/ --- vector/src/main/res/values-ja/strings.xml | 58 ++++++++++++----------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml index 8213994ae0..8e04c6d034 100644 --- a/vector/src/main/res/values-ja/strings.xml +++ b/vector/src/main/res/values-ja/strings.xml @@ -271,8 +271,8 @@ 最終接続日 %1$s @ %2$s 認証 - この操作を行うには追加の認証が必要です. -続けるにはあなたのパスワードを入力して下さい. + この操作には追加の認証が必要です。 +\n続行するには、パスワードを入力してください。 受諾 ログイン中のアカウント 言語を選択 @@ -325,7 +325,7 @@ 暗号を有効にするためにはログアウトする必要があります. 認証された端末のみで暗号化 この部屋では, この端末から認証されていない端末への暗号送信をしません. - "新しい住所表記 (記入例 #foo:matrix.org)" + 新しいアドレス (記入例 #foo:matrix.org) この部屋はサーバ内住所表記がありません 住所表記 住所表記が正しくありません @@ -481,13 +481,14 @@ 新しいパスワードの確認 パスワードの更新に失敗しました %sのすべてのメッセージを表示しますか? - -この操作はアプリを再起動し、時間がかかることに注意してください。 +\n +\nこの操作はアプリを再起動するため時間がかかる場合があります。 電話番号の認証時にエラーが発生しました この部屋へのリンクを作成するには、部屋の住所表記が必要です。 この部屋は暗号化されています。 この部屋は暗号化されていません。 - 暗号化を有効化 (警告: 部屋の暗号を再度無効することはできません!) + 暗号化を有効にします +\n(警告: 有効後にこれを無効にすることはできません!) 部屋一覧 外観 エンドツーエンド暗号化についての情報 @@ -509,8 +510,8 @@ 通信先が通話の受取に失敗しました。 情報 ${app_name}は添付ファイルを送信および保存するために写真とビデオライブラリにアクセスするための許可が必要です。 - -あなたの携帯端末からファイルを送信できるようにするには、次のポップアップでアクセスを許可してください。 +\n +\nあなたの端末からファイルを送信できるようにするには、次のポップアップでアクセスを許可してください。 " \n \n通話をするには、次のポップアップでアクセスできるように設定してください。" @@ -518,15 +519,15 @@ " \n \n通話をするためには、次のポップアップでアクセスを許可してください。" - ビデオ通話を行うには、カメラとマイクにアクセスするための権限が${app_name}アプリに必要です。 - -通話をするには、次のポップアップでのアクセスを許可してください。 + ${app_name}はビデオ通話を行うためにカメラとマイクにアクセスする許可を必要としています。 +\n +\n次のポップアップでアクセスを許可して通話ができるようにしてください。 Matrixユーザが電子メールアドレスや電話番号を元に他のユーザを検索するためには、${app_name}アプリがあなたの端末内電話帳へアクセスする許可が必要です。 ${app_name}からあなた個人の電話帳への検索要求を許可する場合は、次のポップアップでアクセスを許可してください。 - あなた個人の端末内の電話帳に登録されている電子メールアドレス、電話番号を元に、${app_name}の利用者を検索する場合は、アプリが端末内電話帳へアクセスする許可が必要です。 - -${app_name}アプリがあなたの電話帳へアクセスすることを許可しますか? + ${app_name}はあなたの連絡先のメールアドレスや電話番号をもとに他のユーザーを見つけることができます。 +\n +\nこのアプリがあなたの連絡先へアクセスすることを許可しますか? 申し訳ありません。権限がないために操作が実行されませんでした 発言を通報する 既読 @@ -571,12 +572,13 @@ ${app_name}アプリがあなたの電話帳へアクセスすることを許可 不具合報告 開封確認メッセージのリスト ダウンロードファイルに保存しますか? - この招待は%sさんに送られましたが、このアカウントには関連づけられていません。 他のアカウントでログインするか、このメールアドレスをあなたののアカウントに追加できます。 + この招待はこのアカウントに関連付けられていない %s に送信されました。 +\n別のアカウントでログインするか、このメールを自分のアカウントに追加してください。 あなたは%sにアクセスしようとしています。話し合いに参加しますか? 部屋 これは部屋のプレビューです。部屋でのやりとりはできません。 - このユーザーにあなたと同じ権限を与える変更は取り消せません。 -よろしいですか? + このユーザーにあなたと同じ権限を与えますが、この変更は取り消せません。 +\n本当によろしいですか? 端末の連絡先 (%d) ユーザディレクトリ (%s) Matrixユーザのみ @@ -594,8 +596,8 @@ ${app_name}アプリがあなたの電話帳へアクセスすることを許可 不正な形式のIDです。メールアドレスまたは\'@localpart:domain\'のようなMatrix IDを入力してください このコンテンツを報告する理由 このユーザによるすべてのメッセージを非表示にしますか? - -この操作はアプリを再起動し、時間がかかることに注意してください。 +\n +\nこの操作はアプリを再起動するため時間がかかる場合があります。 アップロードをキャンセルする ダウンロードをキャンセルする 検索 @@ -686,8 +688,8 @@ ${app_name}アプリがあなたの電話帳へアクセスすることを許可 無効なコミュニティID \'%s\' は有効なコミュニティIDではありません 部屋のエンドツーエンド暗号鍵は \'%s\' に保存されました。 - -警告:このファイルは、アプリケーションがアンインストールされた場合に削除されることがあります。 +\n +\n警告: このファイルは、アプリケーションをアンインストールすると削除されることがあります。 暗号鍵を要求している新しい端末 \'%s\' を追加しました。 未認証の端末 \'%s\' が暗号鍵を要求しています。 作成 @@ -785,7 +787,7 @@ ${app_name}アプリがあなたの電話帳へアクセスすることを許可 低プライバシー • 通知中のメッセージの内容は Matrixのホームサーバから直接安全に入手しています - "• "メタデータとメッセージのデータ を含む通知 + ・通知は メタデータとメッセージのデータ を含みます • 通知は メッセージの内容を表示しません ユーザの名前をあげるときバイブレーションで通知 送信の前にメディアをプレビュー @@ -914,7 +916,7 @@ Matrixでのメッセージの可視性は電子メールと同様です。メ 開封確認メッセージを表示 開封確認メッセージをクリックすると詳細なリストを確認できます。 エンター入力でメッセージを送信 - ソフトウェアキーボードのエンターボタンを押した際に、改行を追加する代わりにメッセージを送信します。 + ソフトウェアキーボードのEnterボタンを押した際に改行せずにメッセージを送信します パスワード パスワードを更新 パスワードが無効です @@ -949,9 +951,9 @@ Matrixでのメッセージの可視性は電子メールと同様です。メ アルゴリズム 署名 通知に関する問題の解決 - システム設定 - アカウント設定 - カスタム設定 + システム設定。 + アカウント設定。 + カスタム設定。 起動時の実行 バックグラウンド制限の確認 とどまる @@ -1023,7 +1025,7 @@ Matrixでのメッセージの可視性は電子メールと同様です。メ 暗号化を有効にする クロス署名 クロス署名は有効です -\n秘密鍵は端末内です +\n秘密鍵は端末内にあります。 クロス署名は有効です \n鍵は信頼されています \n秘密鍵は不明です @@ -1133,7 +1135,7 @@ Matrixでのメッセージの可視性は電子メールと同様です。メ %s からの招待 このセッションは正常に検証されました。 概ね完了しました。%s の画面にも同じシールドアイコンが表示されていますか? - 相手のユーザーのデバイスのコードをスキャンし、相互に安全性を検証します。 + 相手ユーザーのデバイスのコードをスキャンし、相互に安全性を検証します 相手のコードをスキャン スキャンできません 断る From b15f04dce7db4c356a8012d4e61d9db90905b4aa Mon Sep 17 00:00:00 2001 From: metatek Date: Wed, 21 Jul 2021 12:52:25 +0000 Subject: [PATCH 080/242] Translated using Weblate (Japanese) Currently translated at 63.2% (1592 of 2518 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/ --- vector/src/main/res/values-ja/strings.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml index 8e04c6d034..eba535c156 100644 --- a/vector/src/main/res/values-ja/strings.xml +++ b/vector/src/main/res/values-ja/strings.xml @@ -391,7 +391,7 @@ メールと電話番号の同時登録はまだシステムが対応できませんが、電話番号だけの登録は可能です。 \n \n設定からプロフィールにメールアドレスを追加できます。 - このホームサーバはあなたがロボットではない認証を求めます。 + このホームサーバはあなたがロボットではない認証を求めます ユーザ名はすでに使用されています ホームサーバ: 認証サーバ: @@ -854,7 +854,7 @@ Matrixでのメッセージの可視性は電子メールと同様です。メ 続けるには、パスワードを入力してください: アカウントを停止 パスワードを入力してください。 - この部屋は置き換えられており、アクティブではありません + このルームは置き換えられており、アクティブではありません。 ここで会話が続いています この部屋は別の会話の続きです より古いメッセージを見るには、ここをクリックしてください @@ -1657,7 +1657,7 @@ Matrixでのメッセージの可視性は電子メールと同様です。メ 復元鍵を喪失しましたか?設定で新しいのを設定できます。 メッセージの復元 バックアップのバージョンを取得中… - 復元パスフレーズを使用して暗号化されたメッセージ履歴書をアンロックします。 + 暗号化されたメッセージ履歴のロックを解除するには、復元パスフレーズを使用してください 復元パスフレーズをご存知でなければ、%sができます。 復元鍵を使用して暗号化されたメッセージ履歴をアンロックします 復元鍵を入力 @@ -1726,7 +1726,7 @@ Matrixでのメッセージの可視性は電子メールと同様です。メ ウィジェットの読込に失敗しました。 \n%s ウィジェットを再読込 - クッキーを設定し%sとデータを交換する可能性があります + クッキーを設定し%sとデータを交換する可能性があります: ウィジェットの追加者: **送信に失敗 - 部屋を開いてください 新しい招待 From 154e70cea2323da84f42a7d275b08404df78780f Mon Sep 17 00:00:00 2001 From: oksya8and8 Date: Wed, 21 Jul 2021 13:01:49 +0000 Subject: [PATCH 081/242] Translated using Weblate (Japanese) Currently translated at 63.2% (1592 of 2518 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/ --- vector/src/main/res/values-ja/strings.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml index eba535c156..6e5b432bdc 100644 --- a/vector/src/main/res/values-ja/strings.xml +++ b/vector/src/main/res/values-ja/strings.xml @@ -544,11 +544,11 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場 ウィジェットの作成に失敗しました ウィジェットをこの部屋から削除してもよろしいですか? サーバーが利用できないか、オーバーロードしている可能性があります - この部屋は検証されていない不明なデバイスが含まれています。 -つまり、デバイスが主張するユーザーに属しているという保証はありません。 -続行する前に、各デバイスの検証プロセスを進めることをおすすめしますが、検証することなくメッセージを再送信することができます。 - -不明なデバイス: + この部屋は検証されていない不明なセッションが含まれています。 +\nこれは、そのセッションが主張するユーザーのものであるという保証がないことを意味します。 +\n続行する前に、各セッションの検証プロセスを進めることをおすすめしますが、検証せずにメッセージを再送信することもできます。 +\n +\n不明なセッション: 部屋に不明なデバイスが含まれています キーが一致していることを確認 一致する場合は、下の確認ボタンを押します。そうでない場合は、他の誰かがこのデバイスを盗聴しているので、代わりにブロックボタンを押すことをおすすめします。将来この検証プロセスはより洗練されたものになるでしょう。 From 520d4ac73708f984ad8b180aa79a354012031521 Mon Sep 17 00:00:00 2001 From: metatek Date: Wed, 21 Jul 2021 13:01:11 +0000 Subject: [PATCH 082/242] Translated using Weblate (Japanese) Currently translated at 63.2% (1592 of 2518 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/ --- vector/src/main/res/values-ja/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml index 6e5b432bdc..c93846cfa1 100644 --- a/vector/src/main/res/values-ja/strings.xml +++ b/vector/src/main/res/values-ja/strings.xml @@ -687,7 +687,7 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場 新しいコミュニティID (記入例 +foo:matrix.org) 無効なコミュニティID \'%s\' は有効なコミュニティIDではありません - 部屋のエンドツーエンド暗号鍵は \'%s\' に保存されました。 + ルームのエンドツーエンド暗号鍵は \'%s\' に保存されました。 \n \n警告: このファイルは、アプリケーションをアンインストールすると削除されることがあります。 暗号鍵を要求している新しい端末 \'%s\' を追加しました。 From c1cbbe2044e435e32ecc4166187a212507559538 Mon Sep 17 00:00:00 2001 From: oksya8and8 Date: Wed, 21 Jul 2021 13:03:32 +0000 Subject: [PATCH 083/242] Translated using Weblate (Japanese) Currently translated at 63.2% (1592 of 2518 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/ --- vector/src/main/res/values-ja/strings.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml index c93846cfa1..8ef33b71bd 100644 --- a/vector/src/main/res/values-ja/strings.xml +++ b/vector/src/main/res/values-ja/strings.xml @@ -845,11 +845,11 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場 アバターに通知を表示 今すぐ見る アカウントを停止 - この操作によりあなたのアカウントは永久に使えなくなります。あなたはログインできなくなり、誰も同じユーザIDを登録できなくなります。アカウントが参加しているすべての部屋を去り、アイデンティティサーバからアカウントの詳細を削除することになります。 この操作は取り消しできません - -あなたのアカウントを停止することによって デフォルトではあなたが送信したメッセージを削除しません。メッセージを削除したいときは、下のボックスにチェックを入れてください。 - -Matrixでのメッセージの可視性は電子メールと同様です。メッセージを削除すると、新規ユーザや登録していないユーザは読むことができませんが、すでにそれらのメッセージにアクセスできる登録済みのユーザはメッセージのコピーにアクセスできます。 + この操作によりあなたのアカウントは永久に使えなくなります。あなたはログインできなくなり、誰も同じユーザIDを再登録できなくなります。アカウントが参加しているすべての部屋を退出し、IDサーバからアカウントの詳細を削除することになります。 この操作は取り消しできません +\n +\nあなたのアカウントを停止することによって デフォルトではあなたが送信したメッセージの削除はされません。メッセージを削除を望む場合は、以下のボックスにチェックを入れてください。 +\n +\nMatrixでのメッセージの可視性は電子メールと同様のものです。メッセージを削除すると、あなたが送信したメッセージが新規または未登録のユーザーと共有されないことを意味しますが、すでにこれらのメッセージにアクセスをしている登録ユーザーはそのコピーにアクセスできます。 アカウントを停止したとき自分の送信した全てのメッセージを削除 (警告: この操作により、将来的なユーザが会話を不完全な形で見ることになります) 続けるには、パスワードを入力してください: アカウントを停止 From c0430dac5cb16b628791b6e25d5e3bf3e34918d3 Mon Sep 17 00:00:00 2001 From: metatek Date: Wed, 21 Jul 2021 13:02:12 +0000 Subject: [PATCH 084/242] Translated using Weblate (Japanese) Currently translated at 63.2% (1592 of 2518 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/ --- vector/src/main/res/values-ja/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml index 8ef33b71bd..7c37c14fad 100644 --- a/vector/src/main/res/values-ja/strings.xml +++ b/vector/src/main/res/values-ja/strings.xml @@ -544,7 +544,7 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場 ウィジェットの作成に失敗しました ウィジェットをこの部屋から削除してもよろしいですか? サーバーが利用できないか、オーバーロードしている可能性があります - この部屋は検証されていない不明なセッションが含まれています。 + このルームは検証されていない不明なセッションが含まれています。 \nこれは、そのセッションが主張するユーザーのものであるという保証がないことを意味します。 \n続行する前に、各セッションの検証プロセスを進めることをおすすめしますが、検証せずにメッセージを再送信することもできます。 \n From ff727c52dcc56361d0969e311b4648110472aa00 Mon Sep 17 00:00:00 2001 From: oksya8and8 Date: Wed, 21 Jul 2021 14:28:30 +0000 Subject: [PATCH 085/242] Translated using Weblate (Japanese) Currently translated at 63.7% (1606 of 2518 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/ --- vector/src/main/res/values-ja/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml index 7c37c14fad..af7aa93ade 100644 --- a/vector/src/main/res/values-ja/strings.xml +++ b/vector/src/main/res/values-ja/strings.xml @@ -1086,7 +1086,7 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場 コンテンツの報告 このコンテンツを報告する理由 報告する - ユーザーをブロック + ユーザーを無視する ユーザーをブロック 警告: @@ -1374,7 +1374,7 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場 %sがこの部屋をアップグレードしました。 エンドツーエンド暗号化をオンにしました (%1$s) 将来の部屋のメッセージを %1$sに見えるようにしました - 将来の部屋の履歴を %1$sに見えるようにしました + 今後の部屋履歴を%1$sに表示させました %1$s は将来のメッセージを %2$sに見えるようにしました %sが通話を設定するためのデータを送信しました。 通話をしました。 From a8aa1299b15b675a934f3a8e727f31c18923ec75 Mon Sep 17 00:00:00 2001 From: metatek Date: Wed, 21 Jul 2021 14:28:06 +0000 Subject: [PATCH 086/242] Translated using Weblate (Japanese) Currently translated at 63.7% (1606 of 2518 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/ --- vector/src/main/res/values-ja/strings.xml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml index af7aa93ade..8f9aad210a 100644 --- a/vector/src/main/res/values-ja/strings.xml +++ b/vector/src/main/res/values-ja/strings.xml @@ -18,7 +18,7 @@ %1$sが表示名を%2$sから%3$sに変更しました %1$sが表示名 (%2$s) を削除しました %1$sがテーマを%2$sに変更しました - %1$sが部屋名を%2$sに変更しました + %1$sがルーム名を%2$sに変更しました %sがビデオ通話を開始しました。 %sが音声通話を開始しました。 %sが電話に出ました。 @@ -30,7 +30,7 @@ %1$sと他%2$d名 - %1$sは、今後の部屋履歴を%2$sに表示させました + %1$sは、今後のルーム履歴を%2$sに表示させました 部屋のメンバー全員、招待された時点から。 部屋のメンバー全員、参加した時点から。 部屋のメンバー全員。 @@ -845,7 +845,7 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場 アバターに通知を表示 今すぐ見る アカウントを停止 - この操作によりあなたのアカウントは永久に使えなくなります。あなたはログインできなくなり、誰も同じユーザIDを再登録できなくなります。アカウントが参加しているすべての部屋を退出し、IDサーバからアカウントの詳細を削除することになります。 この操作は取り消しできません + この操作によりあなたのアカウントは永久に使えなくなります。あなたはログインできなくなり、誰も同じユーザIDを再登録できなくなります。アカウントが参加しているすべてのルームを退出し、IDサーバからアカウントの詳細を削除することになります。 この操作は取り消しできません \n \nあなたのアカウントを停止することによって デフォルトではあなたが送信したメッセージの削除はされません。メッセージを削除を望む場合は、以下のボックスにチェックを入れてください。 \n @@ -1087,7 +1087,7 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場 このコンテンツを報告する理由 報告する ユーザーを無視する - ユーザーをブロック + ユーザーを無視する 警告: 元の大きさのまま画像を送信する @@ -1450,7 +1450,7 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場 あなたは%1$sウィジェットを追加しました あなたは %1$sの招待を受けました %1$sが %2$sウィジェットを追加しました - 部屋名を変更しました:%1$s + ルーム名を変更しました:%1$s あなたは%1$sの招待を取り消しました %1$sが%2$sの招待を取り消しました あなたは%1$sを招待しました @@ -1674,7 +1674,7 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場 中止 上書き 違うセッションにより設定された暗号鍵バックアップが既に存在します。既存のバックアップを上書きしますか? - ホームサーバーには既存のバックアップがあります + ホームサーバには既存のバックアップがあります 復元鍵が保存されました。 復号鍵が%sに保存されました。 \n @@ -1687,11 +1687,11 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場 暗号鍵がバックアップ中です。 (高度)復元鍵を使用して設定 または、復元鍵を使用してバックアップを保護しバックアップを保護できます。復元鍵は安全な場所で保管してください。 - 暗号鍵のコピーを暗号化してホームサーバーに保存します。バックアップを保護するためにパスフレーズを設定してください。 + 暗号鍵のコピーを暗号化してホームサーバに保存します。バックアップを保護するためにパスフレーズを設定してください。 \n \n最高度のセキュリティのために、アカウントのパスワードと異なることが大切です。 パスフレーズを使用してバックアップを保護します。 - 暗号化が有効な部屋で送信されたメッセージはEnd-to-end暗号化によって保護されます。メッセージを読むための暗号鍵を持っているのは送受信者のみです。 + 暗号化が有効な部屋で送信されたメッセージはエンドツーエンド暗号化によって保護されます。メッセージを読むための暗号鍵を持っているのは送受信者のみです。 \n \n暗号鍵を失わないように保護されたバックアップをしてください。 (高度) From c75554ebb90b05f207f3180609c9b7694654de20 Mon Sep 17 00:00:00 2001 From: oksya8and8 Date: Wed, 21 Jul 2021 14:31:06 +0000 Subject: [PATCH 087/242] Translated using Weblate (Japanese) Currently translated at 63.7% (1606 of 2518 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/ --- vector/src/main/res/values-ja/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml index 8f9aad210a..ab3da107cd 100644 --- a/vector/src/main/res/values-ja/strings.xml +++ b/vector/src/main/res/values-ja/strings.xml @@ -1333,7 +1333,7 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場 この部屋のサーバのACLを変更しました。 この部屋のサーバACLを設定しました。 ここをアップグレードしました。 - この部屋をアップグレードしました。 + あなたはこの部屋をアップグレードしました。 通話を終えました。 通話に応えました。 通話を設定するためのデータを送信しました。 @@ -1373,7 +1373,7 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場 %sがここをアップグレードしました。 %sがこの部屋をアップグレードしました。 エンドツーエンド暗号化をオンにしました (%1$s) - 将来の部屋のメッセージを %1$sに見えるようにしました + あなたは今後のメッセージを%1$sに見えるようにしました 今後の部屋履歴を%1$sに表示させました %1$s は将来のメッセージを %2$sに見えるようにしました %sが通話を設定するためのデータを送信しました。 From 5b96d80b064c611e2aa6abf7f94fd77229397cad Mon Sep 17 00:00:00 2001 From: metatek Date: Wed, 21 Jul 2021 14:30:43 +0000 Subject: [PATCH 088/242] Translated using Weblate (Japanese) Currently translated at 63.7% (1606 of 2518 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/ --- vector/src/main/res/values-ja/strings.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml index ab3da107cd..13c4bc7db1 100644 --- a/vector/src/main/res/values-ja/strings.xml +++ b/vector/src/main/res/values-ja/strings.xml @@ -31,9 +31,9 @@ %1$sと他%2$d名 %1$sは、今後のルーム履歴を%2$sに表示させました - 部屋のメンバー全員、招待された時点から。 - 部屋のメンバー全員、参加した時点から。 - 部屋のメンバー全員。 + ルームのメンバー全員、招待された時点から。 + ルームのメンバー全員、参加した時点から。 + ルームのメンバー全員。 誰でも。 不明 (%s)。 %1$s がエンドツーエンド暗号化を有効にしました (%2$s) @@ -1371,10 +1371,10 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場 ・%sに一致するサーバは禁止されています。 %sがこの部屋のサーバACLを設定しました。 %sがここをアップグレードしました。 - %sがこの部屋をアップグレードしました。 + %s がこのルームをアップグレードしました。 エンドツーエンド暗号化をオンにしました (%1$s) あなたは今後のメッセージを%1$sに見えるようにしました - 今後の部屋履歴を%1$sに表示させました + 今後のルーム履歴を%1$sに表示させました %1$s は将来のメッセージを %2$sに見えるようにしました %sが通話を設定するためのデータを送信しました。 通話をしました。 From b72e6edd0a533bac77ce43e6d5614bf36971bef8 Mon Sep 17 00:00:00 2001 From: oksya8and8 Date: Thu, 22 Jul 2021 14:37:55 +0000 Subject: [PATCH 089/242] Translated using Weblate (Japanese) Currently translated at 65.6% (1652 of 2518 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/ --- vector/src/main/res/values-ja/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml index 13c4bc7db1..cd6c6bf4fe 100644 --- a/vector/src/main/res/values-ja/strings.xml +++ b/vector/src/main/res/values-ja/strings.xml @@ -1795,4 +1795,5 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場 %s を使用してみてください アクセスを取り消す 表示 + この部屋内のメッセージはエンドツーエンド暗号化されています。 \ No newline at end of file From 91053616af8b3cae9453f7d5f52a79a6943dc3b8 Mon Sep 17 00:00:00 2001 From: metatek Date: Thu, 22 Jul 2021 14:37:29 +0000 Subject: [PATCH 090/242] Translated using Weblate (Japanese) Currently translated at 65.6% (1652 of 2518 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/ --- vector/src/main/res/values-ja/strings.xml | 110 +++++++++++++++------- 1 file changed, 77 insertions(+), 33 deletions(-) diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml index cd6c6bf4fe..4cf3ceda78 100644 --- a/vector/src/main/res/values-ja/strings.xml +++ b/vector/src/main/res/values-ja/strings.xml @@ -24,9 +24,9 @@ %sが電話に出ました。 %sが通話を終了しました。 %sさんからの招待 - 部屋への招待 + ルームへの招待 %1$sと%2$s - 空の部屋 + 空のルーム %1$sと他%2$d名 @@ -41,10 +41,10 @@ VoIP会議が開始されました VoIP会議が終了しました (アバターも変更された) - %1$s が部屋名を削除しました + %1$s がルーム名を削除しました %1$s がルームトピックを削除しました %1$s がプロフィール %2$s を更新しました - %1$s は %2$s に部屋に参加するよう招待状を送りました + %1$s は %2$s にルームに参加するよう招待状を送りました %1$sは%2$sの招待を受け入れました ** 解読できません: %s ** 送信者の端末からこのメッセージのキーが送信されていません。 @@ -53,7 +53,7 @@ 画像のアップロードに失敗しました ネットワークエラー Matrixエラー - 現在空の部屋に再参加することはできません。 + 現在空のルームに再参加することはできません。 メールアドレス 電話番号 ルームのアバターを変更しました @@ -428,7 +428,7 @@ 拒否 管理者権限操作 通話 - 対話 + ダイレクトメッセージ 部屋から退室させる 再入室禁止 再入室禁止解除 @@ -968,7 +968,7 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場 ネットワークを変更 全てのコミュニティ 部屋 - 対話 + ダイレクトメッセージ 新しい部屋 作成 部屋名 @@ -979,7 +979,7 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場 一般 セキュリティとプライバシー ヘルプと概要 - 対話 + ダイレクトメッセージ (編集済み) 会話を検索… Matrix ID から追加 @@ -1330,10 +1330,10 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場 %1$sの部屋への招待を取り消しました。理由:%2$s %1$sにルームへの招待状を送りました。理由:%2$s VoIPカンファレンスをリクエストしました - この部屋のサーバのACLを変更しました。 - この部屋のサーバACLを設定しました。 + このルームのサーバのACLを変更しました。 + このルームのサーバACLを設定しました。 ここをアップグレードしました。 - あなたはこの部屋をアップグレードしました。 + あなたはこのルームをアップグレードしました。 通話を終えました。 通話に応えました。 通話を設定するためのデータを送信しました。 @@ -1357,19 +1357,19 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場 この部屋のアドレスとして%1$sが追加されました。 %1$sが%2$s にルームへの招待を送りました。理由:%3$s - %1$sが%2$sの部屋への招待を取り消しました。理由:%3$s + %1$sが%2$sのルームへの招待を取り消しました。理由:%3$s %1$sが %2$sの招待を承諾しました。理由:%3$s %1$sは%2$sの招待を取り下げました。理由:%3$s %1$sはこの部屋のアドレスとして%2$sを追加しました。 あなたのプロフィールが更新されました %1$s - %sがこの部屋のサーバのACLを変更しました。 + %sがこのルームのサーバのACLを変更しました。 ・サーバーにマッチするIPリテラルを禁止されています。 ・サーバーにマッチするIPリテラルを許可されています。 ・%sに一致するサーバは許可されています。 ・%sに一致するサーバは禁止されています。 - %sがこの部屋のサーバACLを設定しました。 + %sがこのルームのサーバACLを設定しました。 %sがここをアップグレードしました。 %s がこのルームをアップグレードしました。 エンドツーエンド暗号化をオンにしました (%1$s) @@ -1389,18 +1389,18 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場 %1$s は招待を拒否しました。理由: %2$s 退出しました。理由: %1$s %1$sが退出しました。理由: %2$s - あなたがこの部屋を退出しました。理由: %1$s + あなたがこのルームを退出しました。理由: %1$s 初期同期: -\n退出した部屋のインポート +\n退出したルームのインポート 初期同期: -\n招待された部屋のインポート +\n招待されたルームのインポート 初期同期: -\n参加した部屋のインポート - %1$sがこの部屋を退出しました。理由: %2$s - あなたがこの部屋に参加しました。理由: %1$s - %1$sがこの部屋に参加しました。理由: %2$s - あなたがこの部屋に参加しました。理由: %1$s - %1$sがこの部屋に参加しました。理由: %2$s +\n参加したルームのインポート + %1$sがこのルームを退出しました。理由: %2$s + あなたがこのルームに参加しました。理由: %1$s + %1$sがこのルームに参加しました。理由: %2$s + あなたがこのルームに参加しました。理由: %1$s + %1$sがこのルームに参加しました。理由: %2$s %1$sがあなたを招待しました。 理由: %2$s あなたが%1$sを招待しました。 理由: %2$s %1$sが%2$sを招待しました。 理由: %3$s @@ -1414,7 +1414,7 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場 初期同期: \nコミュニティをインポート 初期同期: -\n部屋をインポート +\nルームをインポート 初期同期: \n暗号をインポート 初期同期: @@ -1423,7 +1423,7 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場 \nデータをダウンロードしています… 初期同期: \nサーバーからの応答を待っています… - 空の部屋(%sでした) + 空のルーム(%sでした) %1$sと%2$sと%3$sそれに%4$dその他 @@ -1454,18 +1454,18 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場 あなたは%1$sの招待を取り消しました %1$sが%2$sの招待を取り消しました あなたは%1$sを招待しました - あなたは%1$sの部屋への招待を取り消しました - %1$sが%2$sの部屋への招待を取り消しました + あなたは%1$sのルームへの招待を取り消しました + %1$sが%2$sのルームへの招待を取り消しました %1$sが%2$sを招待しました - あなたは%1$sに部屋への招待を送りました + あなたは%1$sにルームへの招待を送りました メッセージが%1$sによって削除されました [理由: %2$s] メッセージが削除されました [理由: %1$s] %1$sがメッセージを削除しました メッセージを削除 - 部屋のアバターを削除しました - %1$s が部屋のアバターを削除しました - 部屋のトピックを削除しました - 部屋名を削除しました + ルームのアバターを削除しました + %1$s がルームのアバターを削除しました + ルームのトピックを削除しました + ルーム名を削除しました 許可を与える ${app_name}は、信頼できる通知を受け取るために、影響の少ないバックグラウンド接続を維持する必要があります。 \n次の画面で、${app_name}を常にバックグラウンドで実行することを許可するように求められます。同意してください。 @@ -1544,7 +1544,7 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場 \n%1$s FCMトークンの取得に失敗しました: \n%1$s - 🎉すべてのサーバーの参加を禁止されています!この部屋は使用できなくなりました。 + 🎉すべてのサーバの参加を禁止されています!このルームは使用できなくなりました。 変化がありません。 • サーバーにマッチするIPリテラルが禁止されるようになりました。 • サーバーにマッチするIPリテラルが許可されるようになりました。 @@ -1796,4 +1796,48 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場 アクセスを取り消す 表示 この部屋内のメッセージはエンドツーエンド暗号化されています。 + ダイレクトメッセージ + 新しいダイレクトメッセージを送信する + メールアドレス(任意) + メールアドレス + アカウントを回復するためのメールを設定します。 後で、オプションで、あなたが知人にあなたのメールに見つけてもらえるようにできます。 + メールアドレスを設定する + メールアドレスを確認しました + 検出可能なメールアドレス + 続行するには条件に同意してください + ホームサーバーの利用規約に同意したら、再試行してください。 + 次に + 次に + 次に + 次に + 次に + ユーザー名で招待 + ユーザー名を選択してください。 + ユーザー名やパスワードが正しくありません。 入力したパスワードは、スペースで開始または終了しますので、ご確認ください。 + そのユーザー名は既に使用されています + ユーザー名 + ユーザー名または電子メール + %sでサインイン + %sでサインアップ + %sで続ける + または + SSOを続行します + サインイン + サインアップ + Element Matrix Servicesに接続する + Matrix IDでサインイン + Matrix IDでサインイン + もっと詳しく知る + その他 + カスタムと高度な設定 + 組織向けのプレミアムホスティング + 組織向けのプレミアムホスティング + 最大のパブリックサーバーで数百万人に無料で参加 + メールと同じように、アカウントには1つのホームがありますが、誰とでも話すことができます + サーバーを選択 + 始めましょう + エクスペリエンスを拡張およびカスタマイズする + 暗号化して会話をプライベートに保つ + 直接またはグループで人々とチャットする + あなたの会話。 それを所有する。 \ No newline at end of file From 53dbd7da9687e9157a8ec0b8f18a17461e7b3d89 Mon Sep 17 00:00:00 2001 From: Rintan Date: Thu, 22 Jul 2021 14:18:06 +0000 Subject: [PATCH 091/242] Translated using Weblate (Japanese) Currently translated at 65.6% (1652 of 2518 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/ --- vector/src/main/res/values-ja/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml index 4cf3ceda78..d3a392a5b9 100644 --- a/vector/src/main/res/values-ja/strings.xml +++ b/vector/src/main/res/values-ja/strings.xml @@ -1840,4 +1840,5 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場 暗号化して会話をプライベートに保つ 直接またはグループで人々とチャットする あなたの会話。 それを所有する。 + アカウントの復旧用のメールアドレスを設定して、後からオプションで知人に見つけてもらえるようにできます。 \ No newline at end of file From 0916dba0fa4203178c800f566e87e22b3d49c618 Mon Sep 17 00:00:00 2001 From: metatek Date: Thu, 22 Jul 2021 14:38:55 +0000 Subject: [PATCH 092/242] Translated using Weblate (Japanese) Currently translated at 65.6% (1653 of 2518 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/ --- vector/src/main/res/values-ja/strings.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml index d3a392a5b9..0314e322db 100644 --- a/vector/src/main/res/values-ja/strings.xml +++ b/vector/src/main/res/values-ja/strings.xml @@ -1795,7 +1795,7 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場 %s を使用してみてください アクセスを取り消す 表示 - この部屋内のメッセージはエンドツーエンド暗号化されています。 + このルーム内のメッセージはエンドツーエンド暗号化されています。 ダイレクトメッセージ 新しいダイレクトメッセージを送信する メールアドレス(任意) @@ -1841,4 +1841,5 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場 直接またはグループで人々とチャットする あなたの会話。 それを所有する。 アカウントの復旧用のメールアドレスを設定して、後からオプションで知人に見つけてもらえるようにできます。 + これは、%sを使用したダイレクトメッセージ履歴の始まりです。 \ No newline at end of file From 9ca940eba15910e496b766b09260a76d2dbad4d7 Mon Sep 17 00:00:00 2001 From: Alireza Date: Mon, 19 Jul 2021 17:53:26 +0000 Subject: [PATCH 093/242] Translated using Weblate (Persian) Currently translated at 98.6% (2484 of 2518 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/fa/ --- vector/src/main/res/values-fa/strings.xml | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/vector/src/main/res/values-fa/strings.xml b/vector/src/main/res/values-fa/strings.xml index 628a6f8379..ebf2575ed5 100644 --- a/vector/src/main/res/values-fa/strings.xml +++ b/vector/src/main/res/values-fa/strings.xml @@ -398,7 +398,7 @@ نَه در دانلودها ذخیره شود؟ ادامه - زُدودَن + حذف پیوستن پیش‌نمایش رد کردن @@ -802,10 +802,10 @@ المنت برای گرفتن عکس و تماس‌های ویدیویی نیاز به اجازه دارد. بازکردن سرتیتر در حال هم‌گام‌سازی… - پریدن به نخستین پیام خوانده نشده. + پریدن به پیام خوانده نشده شما برای پیوستن به این اتاق توسط %s دعوت شدید یک اتاق - گَپِ نو + گفت و گو جدید افزودن عضو %d عضو فعّال @@ -925,7 +925,7 @@ بازفرستادن پیام‌های فرستاده‌نشده حذف پیام‌های فرستاده‌نشده پرونده پیدا نشد - اجازهٔ فرستادن در این اتاق را ندارید + شما اجازهٔ ارسال به این اتاق را ندارید. %d پیام جدید %d پیام جدید @@ -2244,7 +2244,7 @@ سرور از یکی از محدودیت های منابع خود فراتر رفته است بنابراین برخی از کاربران نمی توانند وارد سیستم شوند. این اتاق ادامه‌ی گفتگوی دیگر است گفتگو در اینجا ادامه دارد - این اتاق جایگزین شده و دیگر فعال نیست + این اتاق جایگزین شده و دیگر فعال نیست. لطفاً تمام پیام‌های ارسال شده‌ی من را حدف کن (هشدار: این امر باعث می شود کاربران آینده مکالمات را به صورت ناقص ببینند) این کار باعث می شود حساب شما برای همیشه غیرقابل استفاده شود. شما قادر به ورود به سیستم نخواهید بود و هیچ کس نمی تواند شناسه کاربری مشابه را دوباره ثبت کند. این باعث می شود که حساب شما از همه اتاق هایی که در آن شرکت می کند خارج شود و جزئیات حساب شما از سرور هویت حذف می شود. این اقدام برگشت ناپذیر است . \n @@ -2783,7 +2783,7 @@ متاسفم، یک خطا زمانی که در تلاش برای اضافه شدن به کنفرانس بود ، اتفاق افتاد این کارساز در حال حاضر در فهرست موجود است نمی‌توانم این کارساز یا فهرست اتاقش را بیابم - نام کارساز جدیدی که می‌خواهید در آن به جستجو بپردازید را وارد کنید + نام کارساز جدیدی که می‌خواهید در آن به جستجو بپردازید را وارد کنید. اضافه کردن یک کارساز(سرور) جدید کارساز شما اتاق نامگذاری نشده @@ -2797,4 +2797,10 @@ شما مدیر این اسپیس هستید،مطمئن شوید قبل از خروج، حق مدیریت را به عضوی دیگر منتقل کرده اید. این اسپیس عمومی نیست. شما نمیتوانید بدون یک دعوتنامه دوباره ملحق شوید. شما تنها فرد اینجا هستید. اگر خارج شوید، هیچ کس از جمله شما قادر نخواهد بود در آینده ملحق شود. + به هر روش ادامه بده + در صورت داشتن هرگونه سوالی میتوانید با من تماس بگیرید + بازخورد فضاها + مجوز های از دست رفته + برای انجام این عمل، لطفاً از تنظیمات سیستم به دوربین اجازه دهید. + برخی از مجوزها برای انجام این عمل وجود ندارد، لطفاً از تنظیمات سیستم مجوزها را بدهید. \ No newline at end of file From a11941714d8604aff98d58dec544bb182ff7e4ca Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Fri, 23 Jul 2021 15:19:50 +0300 Subject: [PATCH 094/242] Code review fixes. --- .../src/main/assets/open_source_licenses.html | 13 ++++++++ .../home/room/detail/RoomDetailFragment.kt | 33 ++++++++++++++----- .../composer/VoiceMessageRecorderView.kt | 5 +++ vector/src/main/res/values/strings.xml | 1 + 4 files changed, 44 insertions(+), 8 deletions(-) diff --git a/vector/src/main/assets/open_source_licenses.html b/vector/src/main/assets/open_source_licenses.html index 107e8d362d..490de1f23a 100755 --- a/vector/src/main/assets/open_source_licenses.html +++ b/vector/src/main/assets/open_source_licenses.html @@ -595,5 +595,18 @@ Apache License +

+ GNU GENERAL PUBLIC LICENSE +
+ Version 3, 29 June 2007 +

+
    +
  • + ffmpeg-kit +
    + Copyright (c) 2021 Taner Sener +
  • +
+ diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index da27c1adc8..d05d968fd9 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -1291,7 +1291,14 @@ class RoomDetailFragment @Inject constructor( } override fun onTextBlankStateChanged(isBlank: Boolean) { - views.voiceMessageRecorderView.isVisible = !views.composerLayout.views.sendButton.isVisible && vectorPreferences.labsUseVoiceMessage() + if (!views.composerLayout.views.sendButton.isVisible && vectorPreferences.labsUseVoiceMessage()) { + // Animate alpha to prevent overlapping with the animation of the send button + views.voiceMessageRecorderView.alpha = 0f + views.voiceMessageRecorderView.isVisible = true + views.voiceMessageRecorderView.animate().alpha(1f).setDuration(300).start() + } else { + views.voiceMessageRecorderView.isVisible = false + } } } } @@ -1356,11 +1363,13 @@ class RoomDetailFragment @Inject constructor( views.inviteView.isVisible = false if (state.tombstoneEvent == null) { if (state.canSendMessage) { - views.composerLayout.isVisible = true - views.voiceMessageRecorderView.isVisible = vectorPreferences.labsUseVoiceMessage() - views.composerLayout.setRoomEncrypted(summary.isEncrypted) - views.notificationAreaView.render(NotificationAreaView.State.Hidden) - views.composerLayout.alwaysShowSendButton = !vectorPreferences.labsUseVoiceMessage() + if (!views.voiceMessageRecorderView.isActive()) { + views.composerLayout.isVisible = true + views.voiceMessageRecorderView.isVisible = vectorPreferences.labsUseVoiceMessage() + views.composerLayout.setRoomEncrypted(summary.isEncrypted) + views.notificationAreaView.render(NotificationAreaView.State.Hidden) + views.composerLayout.alwaysShowSendButton = !vectorPreferences.labsUseVoiceMessage() + } } else { views.composerLayout.isVisible = false views.voiceMessageRecorderView.isVisible = false @@ -1904,13 +1913,21 @@ class RoomDetailFragment @Inject constructor( roomDetailViewModel.handle(RoomDetailAction.UpdateQuickReactAction(action.eventId, action.clickedOn, action.add)) } is EventSharedAction.Edit -> { - roomDetailViewModel.handle(RoomDetailAction.EnterEditMode(action.eventId, views.composerLayout.text.toString())) + if (!views.voiceMessageRecorderView.isActive()) { + roomDetailViewModel.handle(RoomDetailAction.EnterEditMode(action.eventId, views.composerLayout.text.toString())) + } else { + requireActivity().toast(R.string.error_voice_message_cannot_reply_or_edit) + } } is EventSharedAction.Quote -> { roomDetailViewModel.handle(RoomDetailAction.EnterQuoteMode(action.eventId, views.composerLayout.text.toString())) } is EventSharedAction.Reply -> { - roomDetailViewModel.handle(RoomDetailAction.EnterReplyMode(action.eventId, views.composerLayout.text.toString())) + if (!views.voiceMessageRecorderView.isActive()) { + roomDetailViewModel.handle(RoomDetailAction.EnterReplyMode(action.eventId, views.composerLayout.text.toString())) + } else { + requireActivity().toast(R.string.error_voice_message_cannot_reply_or_edit) + } } is EventSharedAction.CopyPermalink -> { val permalink = session.permalinkService().createPermalink(roomDetailArgs.roomId, action.eventId) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageRecorderView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageRecorderView.kt index c59c039abf..dc7c0f3a03 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageRecorderView.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageRecorderView.kt @@ -400,6 +400,11 @@ class VoiceMessageRecorderView @JvmOverloads constructor( PLAYBACK } + /** + * Returns true if the voice message is recording or is in playback mode + */ + fun isActive() = recordingState !in listOf(RecordingState.NONE, RecordingState.CANCELLED) + override fun onUpdate(state: VoiceMessagePlaybackTracker.Listener.State) { when (state) { is VoiceMessagePlaybackTracker.Listener.State.Recording -> { diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index a2c255466c..63944fcb02 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -3454,4 +3454,5 @@ Tap on the wavelength to stop and playback Cannot play this voice message Cannot record a voice message + Cannot reply or edit while voice message is active From bd2ed4c58ac5414aed5ef107bd6d8e97d17e9022 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Fri, 23 Jul 2021 16:53:34 +0300 Subject: [PATCH 095/242] Stop playback when deleting record on locked mode. --- .../app/features/home/room/detail/RoomDetailViewModel.kt | 1 + .../features/home/room/detail/composer/VoiceMessageHelper.kt | 4 +++- .../home/room/detail/composer/VoiceMessageRecorderView.kt | 1 + 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index 60400ce9c6..0ac638034d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -631,6 +631,7 @@ class RoomDetailViewModel @AssistedInject constructor( } private fun handleEndRecordingVoiceMessage(isCancelled: Boolean) { + voiceMessageHelper.stopPlayback() if (isCancelled) { voiceMessageHelper.deleteRecording() } else { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageHelper.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageHelper.kt index cdb789997d..dc1115cbda 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageHelper.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageHelper.kt @@ -93,6 +93,7 @@ class VoiceMessageHelper @Inject constructor( */ fun pauseRecording() { voiceRecorder.stopRecord() + stopRecordingAmplitudes() } fun deleteRecording() { @@ -112,6 +113,7 @@ class VoiceMessageHelper @Inject constructor( fun startOrPausePlayback(id: String, file: File) { stopPlayback() + stopRecordingAmplitudes() if (playbackTracker.getPlaybackState(id) is VoiceMessagePlaybackTracker.Listener.State.Playing) { playbackTracker.pausePlayback(id) } else { @@ -145,7 +147,7 @@ class VoiceMessageHelper @Inject constructor( startPlaybackTicker(id) } - private fun stopPlayback() { + fun stopPlayback() { mediaPlayer?.stop() stopPlaybackTicker() } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageRecorderView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageRecorderView.kt index dc7c0f3a03..0dc3d198db 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageRecorderView.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageRecorderView.kt @@ -415,6 +415,7 @@ class VoiceMessageRecorderView @JvmOverloads constructor( val formattedTimerText = DateUtils.formatElapsedTime((state.playbackTime / 1000).toLong()) views.voicePlaybackTime.text = formattedTimerText } + is VoiceMessagePlaybackTracker.Listener.State.Paused, is VoiceMessagePlaybackTracker.Listener.State.Idle -> { views.voicePlaybackControlButton.setImageResource(R.drawable.ic_play_pause_play) } From be660fbbf31e262113ba41c282c9f73646c1927a Mon Sep 17 00:00:00 2001 From: zeritti Date: Fri, 23 Jul 2021 20:33:48 +0000 Subject: [PATCH 096/242] Translated using Weblate (Czech) Currently translated at 100.0% (2518 of 2518 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/cs/ --- vector/src/main/res/values-cs/strings.xml | 29 +++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/vector/src/main/res/values-cs/strings.xml b/vector/src/main/res/values-cs/strings.xml index 7a91dd1901..b2c3c292f4 100644 --- a/vector/src/main/res/values-cs/strings.xml +++ b/vector/src/main/res/values-cs/strings.xml @@ -639,7 +639,7 @@ Znovu odeslat neodeslané zprávy Smazat neodeslané zprávy Soubor nenalezen - Nemáte právo odesílat v této místnosti + Nemáte právo odesílat v této místnosti. %d nová zpráva %d nové zprávy @@ -1356,7 +1356,7 @@ Deaktivovat účet Zadejte, prosím, uživatelské jméno. Prosím, zadejte své heslo. - Tato místnost byla nahrazena a není již aktivní + Tato místnost byla nahrazena a není již aktivní. Konverzace pokračuje tady Tato místnost je pokračováním jiné konverzace Po kliknutí zde uvidíte starší zprávy @@ -2868,4 +2868,29 @@ Adresa prostoru Prohlédnout a spravovat adresy tohoto prostoru. Adresy prostorů + Aktualizujte na doporučenou verzi místnosti + Tato místnost používá místnost verze %s, kterou homeserver označil za nestabilní. + K aktualizaci místnosti potřebujete oprávnění + Automaticky aktualizovat mateřský prostor + Automaticky pozvat uživatele + Budete aktualizovat tuto místnost z %s na %s. + Aktualizace místnosti je pokročilá akce a obvykle se doporučuje tehdy, je-li místnost nestabilní kvůli chybám, chybějícím funkcím nebo slabým místům v zabezpečení. +\nObvykle má vliv pouze na to, jak server místnost zpracovává. + Aktualizovat soukromou místnost + Aktualizovat veřejnou místnost + Aktualizace + Buďte, prosím, trpěliví, může to chvíli trvat. + Vstupte do náhradní místnosti + Aktualizuje místnost na novou verzi + nestabilní + stabilní + Výchozí verze + Verze místností 👓 + Raději ověřit porovnáním emoji + Oskenovat tímto zařízením + Oskenujte kód svým dalším zařízením nebo přepněte a oskenujte tímto zařízením + URL API Homeserveru + Chybějící oprávnění + Pro provedení této akce udělte, prosím, oprávnění Fotoaparát v systémových nastaveních. + Některá z oprávnění potřebných k provedení akce chybí, prosím, udělte oprávnění v systémových nastaveních. \ No newline at end of file From f1da60e582a1eddac7bc3bcd1f1598834723a0ab Mon Sep 17 00:00:00 2001 From: libexus Date: Thu, 22 Jul 2021 13:01:28 +0000 Subject: [PATCH 097/242] Translated using Weblate (German) Currently translated at 99.0% (2493 of 2518 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/de/ --- vector/src/main/res/values-de/strings.xml | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/vector/src/main/res/values-de/strings.xml b/vector/src/main/res/values-de/strings.xml index 3f3697f4b8..a7869e82f0 100644 --- a/vector/src/main/res/values-de/strings.xml +++ b/vector/src/main/res/values-de/strings.xml @@ -8,7 +8,7 @@ %1$s hat den Raum betreten %1$s hat den Raum verlassen %1$s hat die Einladung abgelehnt - %1$s hat %2$s gekickt + %1$s hat %2$s entfernt %1$s hat den Bann von %2$s aufgehoben %1$s hat %2$s gebannt %1$s hat die Einladung für %2$s zurückgezogen @@ -95,7 +95,7 @@ %1$s ist dem Raum beigetreten. Grund: %2$s %1$s hat den Raum verlassen. Grund: %2$s %1$s hat die Einladung abgelehnt. Grund: %2$s - %1$s hat %2$s gekickt. Grund: %3$s + %1$s hat %2$s entfernt. Grund: %3$s %1$s hat Sperre von %2$s aufgehoben. Grund: %3$s %1$s hat %2$s verbannt. Grund: %3$s %1$s hat eine Einladung an %2$s gesandt um diesem Raum beizutreten. Grund: %3$s @@ -952,7 +952,7 @@ Filter Gruppen-Mitglieder Filter Gruppen-Räume Der Community-Administrator hat keine lange Beschreibung für diese Community zur Verfügung gestellt. - Du wurdest von %2$s aus %1$s gekickt + Du wurdest von %2$s aus %1$s entfernt Du wurdest von %2$s aus %1$s verbannt Grund: %1$s Erneut beitreten @@ -1075,7 +1075,7 @@ Tritt dem Raum mit angegebenen Alias bei Verlasse Raum Raumthema ändern - Kickt Benutzer mit angegebener ID + Entfernt die Person angegebener ID Ändert deinen Anzeigenamen (De-)Aktiviert Markdown Um das Matrix-App-Management zu reparieren @@ -2628,7 +2628,7 @@ Mit %s weitermachen Knopf zum Nachrichteneditor hinzufügen, der die Emoji-Tastatur öffnet Emoji-Tastatur anzeigen - Nutze /confetti Kommando oder sende Nachrichten, die ❄️ oder 🎉 enthalten + Nutze /confetti oder sende Nachrichten mit ❄️ oder 🎉 Chateffekte Thema ändern Raum aktualisieren @@ -2877,4 +2877,11 @@ Adressen des Spaces anzeigen und verwalten. Space-Adressen Beim Versuch %s beizutreten, ist leider ein Fehler aufgetreten + Zur empfohlenen Raumversion upgraden + Ersatzraum betreten + Raum zu neuer Version upgraden + stabil + instabil + Raumversionen 👓 + Fehlende Berechtigungen \ No newline at end of file From ad270a4e2b0b466025f620485876d2f86dfda2bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Fri, 23 Jul 2021 07:28:11 +0000 Subject: [PATCH 098/242] Translated using Weblate (Estonian) Currently translated at 100.0% (2518 of 2518 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/et/ --- vector/src/main/res/values-et/strings.xml | 29 +++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/vector/src/main/res/values-et/strings.xml b/vector/src/main/res/values-et/strings.xml index 4420763a1e..fd46b38b30 100644 --- a/vector/src/main/res/values-et/strings.xml +++ b/vector/src/main/res/values-et/strings.xml @@ -1197,7 +1197,7 @@ Saada saatmata sõnumid uuesti Kustuta saatmata sõnumid Faili ei leidunud - Sul ei ole õigusi siia jututuppa kirjutamiseks + Sul ei ole õigusi siia jututuppa kirjutamiseks. %d uus sõnum %d uut sõnumit @@ -1608,7 +1608,7 @@ Deaktiveeri konto Palun sisesta kasutajanimi. Palun sisesta oma salasõna. - See jututuba on asendatud teise jututoaga ning ei ole enam kasutusel + See jututuba on asendatud teise jututoaga ning ei ole enam kasutusel. Vestlus jätkub siin See jututuba on järg varasemale vestlusele Vanemate sõnumite nägemiseks klõpsi siin @@ -2815,4 +2815,29 @@ Kogukonnakeskuse aadressid Selle kogukonnakeskuse hallatud ja nähtavad aadressid. Kogukonnakeskuse aadressid + ebapüsiv + stabiilne + Uuenda see jututoa versioon soovitatud versioonini + Selle jututoa versioon on %s ning see koduserver on tema märkinud ebastabiilseks. + Jututoa versiooni uuendamiseks on sul vaja õigusi + Uuenda automaatselt ka kogukonnakeskust, kus jututuba osaleb + Kutsu automaatselt kasutajaid + Sa oled uuendamas jututoa versiooni: %s -> %s. + Jututoa versiooni uuendamine on erandlik tegevus ning soovitame seda vaid siis kui ta on vigade tõttu raskestikasutatav, tal puuduvad uued funktsionaalsused või temas leidub turvavigu. +\nTavaliselt versiooniuuendus mõjutab vaid seda viisi kuidas andmeid töödeldakse serveris. + Uuenda omavaheline jututuba + Uuenda avalik jututuba + Uuenda + Palun oota rahulikult. Selleks võib kuluda natuke aega. + Liitu asenduseks mõeldud jututoaga + Uuendab jututoa uue versioonini + Vaikimisi versioon + Jututubade versioonid 👓 + Selle asemel verifitseeri võrreldes emoji\'sid + Skaneeri selle seadmega + Skaneeri koodi oma teise seadmega või vaheta pooli ning skaneeri selle seadmega + Koduserveri API aadress + Õigused on puudu + Selle tegevuse jaoks palun luba seadistustes sellele rakendusele kaamera kasutamine. + Selle tegevuse jaoks puuduvad sul õigused. Palun jaga vajalikud õigused süsteemi seadistustest. \ No newline at end of file From 56dd0541e5194bb83d1547e9fdd76eb04d119332 Mon Sep 17 00:00:00 2001 From: Danial Behzadi Date: Fri, 23 Jul 2021 09:40:17 +0000 Subject: [PATCH 099/242] Translated using Weblate (Persian) Currently translated at 100.0% (2518 of 2518 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/fa/ --- vector/src/main/res/values-fa/strings.xml | 324 ++++++++++++---------- 1 file changed, 180 insertions(+), 144 deletions(-) diff --git a/vector/src/main/res/values-fa/strings.xml b/vector/src/main/res/values-fa/strings.xml index ebf2575ed5..7e7ccb209b 100644 --- a/vector/src/main/res/values-fa/strings.xml +++ b/vector/src/main/res/values-fa/strings.xml @@ -223,7 +223,7 @@ در حال گوش دادن به رویدادها پیام‌ها اتاق - ساماندهی + تنظیمات جزئیات اعضا گزارش اشکال در حال بارگذاری… @@ -333,7 +333,7 @@ کنش‌ها تایید خطا - پسندیده‌ها + محبوب‌ها افراد انجمن‌ها پالایش نام‌های اتاق @@ -355,7 +355,7 @@ ارسال رخدادنگارها ارسال رخدادنگارهای خطا ارسال تصویر صفحه - لطفاً اشکال پیش آمده را توصیف کنید. شما چه کاری انجام دادید؟ انتظار داشتید چه اتفاقی بیفتد؟ چه اتفاقی رخ داد؟ + لطفاً اشکال را شرح دهید. چه‌کار کردید؟ انتظار داشتید چه شود؟ چه شد؟ ترجیحاً توضیحات را به زبان انگلیسی بنویسید. مشکل خود را اینجا شرح دهید برای کمک به اشکال‌یابی، رخدادنگارهای مربوط به این دستگاه به همراه گزارش اشکال ارسال خواهند شد. البته گزارش اشکال شما و پیوست‌های آن به صورت عمومی منتشر نمی‌شوند. می‌توانید با برداشتن علامت گزینه‌های زیر، اطلاعات کمتری را ارسال نمایید: @@ -394,11 +394,11 @@ تماس‌ها تماس ذخیره شد - بَلِہ - نَه + بله + نه در دانلودها ذخیره شود؟ ادامه - حذف + برداشتن پیوستن پیش‌نمایش رد کردن @@ -449,12 +449,12 @@ ابزارهای مدیر تماس گپ‌های مستقیم - نِشَست‌ھا + نشست‌ها دعوت ترک این اتاق حذف از این اتاق ماندن - حذف + برداشتن بارگیری کنفرانس در حال برگذاری است. \nبه صورت %1$s یا %2$s به آن بپیوندید @@ -466,7 +466,7 @@ انجام شد انصراف نادیده‌گرفتن - آیا می‌خواهید از حساب کاربری خود خارج شوید؟ + مطمئنید که می‌خواهید از حسابتان خارج شوید؟ علامت‌گذاری به عنوان خوانده شده ورود با سامانه‌های احراز هویت مرکزی در حال راه‌اندازی سرویس @@ -478,8 +478,8 @@ پشتیبان‌گیری کلید استفاده از پشتیبان کلید پشتیبان‌گیری از کلید هنوز به پایان نرسیده است، لطفاً شکیبا باشید… - در صورتی که اکنون از حساب خود خارج شوید، پیام‌های رمزنگاری شده خود را از دست خواهید داد - پشتیبان‌گیری کلید در جریان است. در صورتی که اکنون از حساب خود خارج شوید، پیام‌های رمزنگاری شده خود را از دست خواهید داد. + اگر اکنون از حسابتان خارج شوید، پیام‌های رمزنگاشته‌تان را از دست خواهید داد + پشتیبان‌گیری کلید در جریان است. اگر اکنون از حسابتان خارج شوید، پیام‌های رمزنگاشته‌تان را از دست خواهید داد. برای از دست ندادن دسترسی به پیام‌های رمزنگاری شده، باید پشتیبان کلید امن روی تمام نشست‌هایتان فعّال باشد. پیام‌های رمزنگاری شده خود را نمی‌خواهم در حال پشتیبان‌گیری از کلیدها… @@ -545,7 +545,7 @@ تأیید گذرواژه‌تان نمی‌توانید با المنت همراه، این کار را بکنید نیاز به تأیید هویت است - ساماندھیِ پیشرَفتِہ‌یِ آگَہداد + تنظمیات پیش‌رفتهٔ آگاهی آگاهی‌های رفع‌اشکال آگاهی‌ها در تنظیمات سامانه به کار افتاده‌اند. آگاهی‌ّا در تنظیمات سامانه از کار افتاده‌اند. @@ -775,9 +775,9 @@ گذرواژه جدیدی باید وارد شود. رایانامه‌ای به %s فرستاده شد. هنگامی که پیوند داخلش را دنبال کردید، پایین را کلیک کنید. شکست در تأیید نشانی رایانامه: مطمئن شوید که پیوند درون رایانامه را کلیک کرده‌اید - گذرواژه بازنشانی شد. + گذرواژه‌تان بازنشانی شد. \n -\nشما از همه نشست‌ها خارج شدید و دیگر آگاهی ها را دریافت نخواهید کرد. برای فعال‌سازی دوباره آگاهی‌ها، در هر دستگاه دوباره وارد شوید. +\nاز تمامی نشست‌ها خارج شدید و دیگر آگاهی‌ها را دریافت نخواهید کرد. برای به کار انداختن دوبارهٔ آگاهی‌ها، دوباره در هر دستگاه وارد شوید. لطفاً سیاست‌های این کارساز خانگی را بررسی کرده و بپذیرید: نشانی باید با http[s]:// آغاز شود نمی‌توان ثبت‌نام کرد: خطای شبکه @@ -802,10 +802,10 @@ المنت برای گرفتن عکس و تماس‌های ویدیویی نیاز به اجازه دارد. بازکردن سرتیتر در حال هم‌گام‌سازی… - پریدن به پیام خوانده نشده + پرش به ناخوانده شما برای پیوستن به این اتاق توسط %s دعوت شدید یک اتاق - گفت و گو جدید + گپ جدید افزودن عضو %d عضو فعّال @@ -852,7 +852,7 @@ توقف رونوشت موفقیت - آگَہداد + آگاهی‌ها خاتمه اجازهٔ شروع تماس کنفرانسی در این اتاق را ندارید اجازهٔ شروع تماس در این اتاق را ندارید @@ -913,10 +913,10 @@ دعوت کاربر با شناسه لطفاً یک یا چند نشانی رایانامه یا شناسهٔ ماتریکس را وارد کنید رایانامه یا شناسهٔ ماتریکس - جُستُجو - %s دارد می‌نِویسَد… - %1$s و %2$s دارَند می‌نِویسَند… - %1$s و %2$s و دیگَران دارَند می‌نِویسَند… + جست‌وجو + %s دارد می‌نویسد… + %1$s و %2$s دارند می‌نویسند… + %1$s و %2$s و دیگران دارند می‌نویسند… فرستادن پیام رمزشده… فرستادن پیام (رمز نشده)… اتّصال به کارساز از دست رفت. @@ -925,7 +925,7 @@ بازفرستادن پیام‌های فرستاده‌نشده حذف پیام‌های فرستاده‌نشده پرونده پیدا نشد - شما اجازهٔ ارسال به این اتاق را ندارید. + اجازهٔ فرستادن به این اتاق را ندارید. %d پیام جدید %d پیام جدید @@ -1019,7 +1019,7 @@ \nمدیرهای یکپارچگی، داده‌های پیکربندی را دریافت کرده و می‌توانند از طرف شما ابزارک‌ها را تغییر داده، دعوت‌های اتاق فرستاده و سطوح قدرت را تنظیم کنند. نمایش برچسب زمانی برای تمامی پیام‌ها نمایش برچسب‌های زمانی در قالب ۱۲ساعته - شامل رویدادهای دعوت/پیوستن/خروج/اخراج/تحریم و تغییرهای آواتار/نام نمایشی. + شامل رویدادهای دعوت/پیوستن/ترک/اخراج/تحریم و تغییرهای چهرک/نام نمایشی. پشتیبان امن مدیریت برپایی پشتیبان امن @@ -1275,7 +1275,7 @@ مخاطب دوربین صدا - گالری + جُنگ برچسب رسانه هیچ رسانه‌ای در این اتاق نیست @@ -1732,11 +1732,11 @@ ثبت توکن افزودن حساب کاربری [%1$s] -\nاین خطا از کنترل المنت خارج است. هیچ حساب Google‌ای روی تلفن وجود ندارد. لطفاً مدیریت حساب را در تنظیمات گوشی باز کرده و یک حساب Google اضافه کنید. +\nاین خطا خارج از مهار ${app_name} است. هیچ حساب گوگلی روی تلفن نیست. لطفاً مدیر حساب را گشوده و حساب گوگلی بیفزایید.
[%1$s] -\nاین خطا از کنترل المنت خارج است و به چند دلیل ممکن است رخ داده باشد. امکان دارد در صورت تلاش مجدد مشکل رفع شود، همچنین می‌توانید بررسی کنید که سرویس Google Play در استفاده از اینترنت در تنظیمات گوشی محدودیتی نداشته باشد یا ساعت دستگاه شما درست باشد. همچنین ممکن است به علت استفاده از ROM سفارشی‌شده این خطا رخ داده باشد. +\nاین خطا خارج از مهار ${app_name} است و ممکن است به دلایل مختلفی رخ داده باشد. ممکن است اگر بعداً دوباره تلاش کنید، کار کند. می‌توانید بررسی کنید که مصرف دادهٔ خدمات پلی گوگل در تنظیمات سامانه محدود نشده باشد یا ساعت افزاره‌تان درست باشد. همچنین ممکن است روی رام‌های سفارشی اتفاق بیفتد. [%1$s] -\nاین خطا از کنترل المنت خارج است و طبق گفته گوگل ، این خطا نشان می دهد که دستگاه بیش از حد مجاز، برنامه های ثبت شده در FCM دارد. این خطا فقط در مواردی رخ می دهد که تعداد زیادی برنامه وجود دارد، بنابراین نباید بر کاربر عادی رخ دهد. +\nاین خطا، خارج از مهار ${app_name} است و طبق گفتهٔ گوگل، نشانگر ثبت بیش‌از حد کاره‌ها در FCM است. خطا فقط در مواردی که تعداد خیلی زیادی کاره وجود داشته باشد رخ می‌دهد، پس کاربران معمولی نباید تحت تأثیر قرار گیرند. بازیابی توکن FCM با مشکل مواجه شد: \n%1$s توکن FCM با موفقیت بازیابی شد: @@ -1845,9 +1845,9 @@ محاسبه‌ی کلید curve نام‌کاربری یا گذواژه‌ی اشتباه. ابتدا یا انتهای گذرواژه‌ی وارد شده کاراکتر space قرار دارد، لطفا بررسی کنید. پیام‌ها را بدون در نظرگرفتن markdown ارسال کن - اگر اکنون لغو کنید و از حساب خود خارج شوید، دسترسی به پیام‌های رمزشده را از دست می‌دهید. + اگر اکنون لغو کنید، ممکن است در صورت قطع دسترسی به ورودهایتان، داده‌ها و پیام‌های رمزنگاشته را از دست بدهد. \n -\nالبته شما همچنان قادر هستید که پشتیبان امن و کلید‌های ‌آن را در تنظیمات، مدیریت کنید. +\nهمچنین می‌توانید در تنظیمات، پشتیبان امن برپا کرده و کلیدهایتان را مدیریت کنید. تنظیم کلید امنیتی به شما اماکن امن‌کردن پیام‌ها و دسترسی به پیام‌های رمزشده را می‌دهد. تنظیم کلید امنیتی به شما اجازه دسترسی به پیام‌های رمز شده را می‌دهد. \n @@ -1939,7 +1939,7 @@ \n \nاگر کارتان با این افزاره تمام شده یا می‌خواهید به حساب دیگری وارد شوید، پاکشان کنید. برای بازیابی کلیدهای رمزگذاری ذخیره شده در این دستگاه، وارد حساب خود شوید. شما برای خواندن همه پیام‌های رمزشده‌ی خود در هر دستگاهی به این کلید‌ها نیاز دارید. - ادمین سرور (%1$s) شما را از حسابتان خارج کرده‌است %2$s (%3$s). + مدیر کارساز خانگیتان (%1$s) شما (%2$s) را از حسابتان (%3$s) خارج کرد. این می تواند به دلایل مختلف باشد: \n \n• شما گذرواژه خود را در نشست دیگری تغییر داده‌اید. @@ -2034,11 +2034,11 @@ این محتوا گزارش شده‌است. \n \nاگر نمی‌خواهید محتوای بیشتری از این کاربر مشاهده کنید، می توانید او را نادیده بگیرید تا پیام‌های او را مشاهده نکنید. - امکان به اشتراک‌گذاری این محتوا وجود ندارد + نتوانست هم‌رسانی داده را مدیریت کند چرخش و برش - اضافه کردن تصویر از + افزودن تصویر از هنگام دریافت پیوست خطایی رخ داد. - پرونده \'%1$s\' (%2$s) برای آپلود بسیار بزرگ است. محدودیت آپلود %3$s است. + پروندهٔ «%1$s» (%2$s) برای بارگذاری بسیار بزرگ است. کران %3$s است. %d کاربر خواند %d کاربر خواندند @@ -2098,7 +2098,7 @@ توضیح در مورد اتاق (اختیاری) نام اتاق پیام پاک شد - به نظر می‌رسد تلاش می‌کنید تا به کارساز خانگی دیگری وصل شوید. می‌خواهید خارج شوید؟ + به نظر در تلاش برای وصل شدن به کارساز خانگی دیگری هستید. می‌خواهید خارج شوید؟ برای بازنشانی گذرواژه‌ی خود نیاز به پیکربندی سرور هویت‌سنجی دارید. شما از سرور هویت‌سنجی استفاده نمی‌کنید خطای نامشخص @@ -2138,7 +2138,7 @@ شروع فرآیند تایید کردن برای امنیت بیشتر پیشنهاد می‌کنیم که این بخش را یا به صورت حضوری یا با استفاده از یک راهکار ارتباطی امن دیگر تکمیل کنید. با مقایسه‌کردن یک رشته‌ی متنی کوتاه تایید کنید. - شما به دلیل اطلاعات نادرست حساب کاربری یا انقضای نشست از حساب خارج شدید. + به خاطر ورود منقضی یا نامعتبر، از حسابتان خارج شدید. از پیکربندی استفاده کنید المنت یک پیکربندی اختصاصی سرور برای دامنه‌ی شناسه‌ی کاربری شما \"%1$s\" تشخیص داده است: \n%2$s @@ -2196,7 +2196,7 @@ از کلید بازیابی برای رمزگشایی پیام‌های رمزشده‌ی قبلی خود استفاده کنید کلید امنیتی خود را نمی‌دانید؟ شما می‌توانید %s. برای قفل‌گشایی تاریخچهٔ پیام‌های رمزشده‌تان از عبارت عبور بازیابیتان استفاده کنید - اگر از دستگاه خارج شوید یا دستگاه خود را از دست دهید، ممکن است امکان دسترسی به پیام های خود را نداشته باشید. + ممکن است در صورت خروج از حساب یا از دست دادن این افزاره، دسترسی به پیام‌هایتان را از دست بدهید. کلیدهای رمزگذاری شما اکنون در پس زمینه در حال پشتیبان‌گیری بر روی سرور است. تهیه نسخه‌ی پشتیبان اولیه ممکن است چند دقیقه طول بکشد. در حال تولید کلید پشتیبان با استفاده از کلید امنیتی، این ممکن است چند ثانیه زمان ببرد. به نظر می‌رسد شما در یک نشست دیگر کلید پشتیبان تهیه کرده‌اید. آیا می‌خواهید آن را با موردی که ایجاد می‌کنید جایگزین کنید؟ @@ -2244,13 +2244,13 @@ سرور از یکی از محدودیت های منابع خود فراتر رفته است بنابراین برخی از کاربران نمی توانند وارد سیستم شوند. این اتاق ادامه‌ی گفتگوی دیگر است گفتگو در اینجا ادامه دارد - این اتاق جایگزین شده و دیگر فعال نیست. + این اتاق جایگزین شده و دیگر فعّال نیست. لطفاً تمام پیام‌های ارسال شده‌ی من را حدف کن (هشدار: این امر باعث می شود کاربران آینده مکالمات را به صورت ناقص ببینند) - این کار باعث می شود حساب شما برای همیشه غیرقابل استفاده شود. شما قادر به ورود به سیستم نخواهید بود و هیچ کس نمی تواند شناسه کاربری مشابه را دوباره ثبت کند. این باعث می شود که حساب شما از همه اتاق هایی که در آن شرکت می کند خارج شود و جزئیات حساب شما از سرور هویت حذف می شود. این اقدام برگشت ناپذیر است . + این کار حسابتان را برای همیشه غیر قابل استفاده خواهد کرد. قادر به ورود نخواهید بود و هیچ‌کس نخواهد توانست دوباره این شناسهٔ کاربری را ثبت کند. این کار موجب ترک حسابتان از تمامی اتاق‌ها شده و جزییات حسابتان را از کارساز هویتتان برخواهد داشت. این عمل بازگشت‌پذیر نیست. \n -\nغیرفعال کردن حساب شما به طور پیش فرض باعث نمی شود پیام های ارسالی شما را حذف کنیم . اگر می خواهید پیام های شما را فراموش کنیم ، لطفاً کادر زیر را علامت بزنید. +\nغیرفعّال‌سازی حسابتان به صورت پیش‌گزیده منجر به قراموشی پیام‌هایی که فرستاده‌اید نخواهد شد. اگر می‌خواهید پیام‌هایتان را فراموش کنیم، تیک کادر زیر را بزنید. \n -\nقابلیت مشاهده پیام در المنت مانند ایمیل است. حذف کردن پیام‌های شما به این معنی است که پیام‌هایی که ارسال کرده‌اید به هیچ کاربر جدید یا ثبت نشده‌ای نمایش داده نمی شود، اما کاربران ثبت‌نام شده که از قبل به این پیام ها دسترسی داشتند همچنان به نسخه خود دسترسی خواهند داشت. +\nپدیداری پیام در ماتریکس، مانند رایانامه است. فراموشی پیام‌هایتان به معنی هم‌رسانی نشدن آن‌ها با کاربران جدید یا ثبت‌نشده است. کاربران ثبت‌شده‌ای که پیش‌تر به این پیام‌ها دسترسی داشته‌اند، همچنان به رونوشتشان دسترسی خواهند داشت. برای ادامهٔ استفاده از کارساز خانگی %1$s باید شرایط و ضوابط را خوانده و بپذیرید. Markdown غیرفعال شده است. Markdown فعال شده است. @@ -2441,7 +2441,7 @@ دسترسی به مخاطبانتان را مجاز کنید. برای پویش یک رمز QR نیاز است دسترسی به دوربین را مجاز کنید. آغاز به گپ - خروجی گرفتن + برون‌ریزی بازرسی اگر اتاق فقط برای تعامل با افراد داخل سرور خانه شما می‌باشد، این قابلیت را فعال کنید. این تنظیم را بعدا نمی‌توانید تغییر دهید. آیا می‌خواهید جهت کشف مخاطبینی که می شناسید، داده های مخاطب خود را (شماره تلفن و ایمیل) به سرور هویت‌سنجی(%1$s) ارسال کنید؟ \n @@ -2528,9 +2528,9 @@ فرستادن پیام داده شده با بارش برف فرستادن کاغذ رنگی 🎉 فرستادن برف ❄️ - احراز هویت انجام نشد - المنت برای انجام این عمل نیاز دارد که گذواژه‌ی خود را وارد کنید. - احراز هویت مجدد مورد نیاز است + تأیید هویت شکست خورد + ${app_name} برای انجام این کار، نیاز به ورود گذواژه‌‌تان دارد. + نیاز به تأیید هویت دوباره است کاربران هنگام انتقال تماس خطایی روی داد انتقال @@ -2560,43 +2560,43 @@ از سرگیری غیر مجاز، اطلاعات هویت‌سنجی موجود نمی‌باشد بازگشت - محتوای رخداد - رخداد وضعیتی ارسال شد! - رخداد ارسال شد! - رخداد معیوب - نوع پیام فراموش شده‌است + محتوای رویداد + رویداد وضعیت فرستاده شد! + رویداد فرستاده شد! + رویداد بدشکل + بدون گونهٔ پیام بدون محتوا - محتوای رخداد + محتوای رویداد کلید وضعیت - نوع - ارسال رخداد وضعیت سفارشی + گونه + فرستادن رویداد وضعیت سفارشی ویرایش محتوا - رخدادهای وضعیتی - ارسال رخداد وضعیتی - ارسال رخداد سفارشی + رویدادهای وضعیت + فرستادن رویداد وضعیت + فرستادن رویداد سفارشی کاوش وضعیت اتاق ابزارهای توسعه - مشاهده رسیدهای خوانده‌شده - خبر نده - بدون صدا خبر بده - با صدا خبر بده - پیام به دلیل رخداد خطا ارسال نشد + دیدن رسیدهای خواندن + بدون آگاهی + آگاهی بدون صدا + آگاهی با صدا + پیام به دلیل خطا فرستاده نشد تیک‌خورده - بستن پنجره ی شکلک‌ها - بازکردن پنجره‌ی شکلک‌ها + بستن اموجی‌بردار + گشودن اموجی‌بردار سطح اعتماد کامل - سطح اعتماد هشداری - سطح اعتماد پیش‌فرض - انتخاب‌شده - عکس - ویدئو - این اتاق حاوی پیش‌نویس‌هایی است که هنوز ارسال نشده‌اند - بعضی از پیام‌ها هنوز ارسال نشده‌اند - حذف نمایه - تغییر نمایه - واردکردن کلید از فایل - ابزارک‌های باز - اسکرین‌شات + سطح اعتماد هشدار + سطح اعتماد پیش‌گزیده + گزیده + تصویر + ویدیو + این اتاق پیش‌نویس‌هایی نفرستاده دارد + برخی پیام‌ها ارسال نشده‌اند + حذف چهرک + تغییر چهرک + درون‌ریزی کلید از پرونده + گشودن ابزارک‌ها + نماگرفت ابتدا مشاوره بگیرید %d ورودی @@ -2661,26 +2661,26 @@ تطبیق سرور %s اجازه داده شده‌است. تطبیق سرور %s ممنوع شده‌است. شما ACL های سرور را برای این اتاق تنظیم کردید. - آیا مطمئن هستید که می خواهید همه پیام های ارسال نشده در این اتاق را حذف کنید؟ - حذف پیام‌های ارسال نشده - پیام ارسال نشد - آیا می خواهید ارسال پیام را لغو کنید؟ - حذف تمامی پیام‌های ناموفق - ناموفق - ارسال شد - در حال ارسال + مطمئنید که می‌خواهید تمام پیام‌های فرستاده نشده در این اتاق را حذف کنید؟ + حذف پیام‌های فرستاده نشده + فرستادن پیام‌ها شکست خورد + می‌خواهید فرستادن پیام را لغو کنید؟ + حذف تمام پیام‌های شکست‌خورده + شکست خورد + فرستاده شد + فرستادن نمایش همه‌ی اتاق‌های داخل فهرست. نمایش‌ها همه‌ی اتاق‌ها فهرست اتاق‌ها - پَیام فِرِستاده شُد - هشدار به پشتیبانی سرور و نسخه اتاق آزمایشی نیاز دارد - فضای کاری آزمایشی - اتاق محدود. - شما دعوت شده‌اید - اسپیس ها یک شیوه جدید برای دسته بندی اتاق ها و افراد هستند. - به فضای‌های کاری خوش‌آمدید! - اتاق‌ها و فضای کاری موجود را اضافه کنید - آیا مطمئن هستید که می خواهید فضای کاری را ترک کنید؟ - خروج از فضای کاری + پیام فرستاده شد + هشدار: نیاز به پشتیبانی کارساز و نگارش اتاق آزمایشی + فضای آزمایشی - اتاق محدود. + دعوت شده‌اید + فضاها شیوه‌ای جدید برای گروه‌بندی اتاق‌ها و افراد است. + به فضاها خوش آمدید! + افزودن فضا و اتاق‌های موجود + مطمئنید که می‌خواهید فضا را ترک کنید؟ + ترک فضا افزودن اتاق کاوش در اتاق‌ها @@ -2692,10 +2692,10 @@ این نام مستعار در حال حاضر قابل دسترسی نیست. \nبعداً دوباره امتحان کنید، یا از مدیر اتاق بخواهید که دسترسی شما به آن را بررسی کند. در هر صورت بپیوند - پیوستن به فضای کاری - ساخت فضای کاری + پیوستن به فضا + ایجاد فضا فعلا رد شوید - به فضای کاری %1$s من بپیوند.%2$s + به فضایم %1$s بپیوند.%2$s آن‌ها بخشی از %s نخواهند شد فقط به این اتاق آنها قادر به کاوش در %s خواهند بود @@ -2705,9 +2705,9 @@ دعوت با ایمیل در حال حاضر فقط شما هستید. %s با دیگران حتی بهتر خواهد بود. دعوت افراد - افراد را به فضای کاری خود دعوت کنید + دعوت افراد به فضایتان توضیحات - در حال ساخت فضای کاری … + ساختن فضا… تصادفی عمومی بیایید برای هر یک از آنها یک اتاق درست کنیم. بعداً می توانید موارد دیگر را اضافه کنید، از جمله موارد موجود. @@ -2717,30 +2717,30 @@ برای ادامه یک اسم برای آن بگذارید. توضیحاتی را برای کمک به افراد در شناسایی آن اضافه کنید. همواره می توانید این توضیحات را تغییر دهید. توضیحاتی را برای مشخص کردن آن اضافه کنید. همواره می توانید این توضیحات را تغییر دهید. - ساخت فضای کاری - فقط با دعوت، مناسب برای خودتان و یا تیم‌ها + ایجاد یک فضا + فقط با دعوت. بهترین برای خودتان یا گروه‌ها خصوصی - برای همه آزاد است، بهترین حالت برای انجمن‌ها می‌باشد + آزاد برای همه. بهترین برای اجتماع‌ها عمومی - یک فضای کاری خصوصی برای شما و همکارانتان - من و همکارانم - یک فضای کاری خصوصی برای نظم بخشیدن به اتاق‌های شما - فقط خودم - اطمینان حاصل کنید که افراد مناسب به %s دسترسی دارند. شما می‌توانید این را بعدا تغییر دهید. - ساخت فضای کاری - هر کسی در فضای کاری این اتاق وجود داشته باشد، می‌تواند اتاق را پیدا کرده و به آن بپیوندد. فقط مدیران این اتاق می توانند آن را به یک فضای کاری اضافه کنند. - فضا‌های کاری - فضا‌های کاری + فضایی خصوصی برای شما و همگروهی‌هایتان + من و همگروهی‌هایم + یک فضای خصوصی برای نظم بخشی به اتاق‌هایتان + فقط من + مطمئن شوید که افراد درست به %s دسترسی دارند. می‌توانید بعدها این را تغییر دهید. + ساخت یک فضا + هر کسی در فضای این اتاق، می‌تواند اتاق را یافته و به آن بپیوندد. فقط مدیران این اتاق می توانند آن را به فضایی بیفزایند. + فضا‌ها + فضا‌ها با چه کسانی کار می‌کنی؟ - برای پیوستن به یک فضای کاری موجود، نیاز به دعوت دارید. - می‌توانید این را بعدا تغییر دهید - می خواهید چه نوع فضای‌ کاری‌ای ایجاد کنید؟ - فضاهای کاری راهی جدید برای ایجاد دسته‌هایی از اتاق‌ها و افراد است - فضای کاری خصوصی شما - فضای کاری عمومی شما - افزودن فضای کاری - ترک اتاق با شناسه داده شده (یا اگر اتاق خالی باشد اتاق فعلی) - پیوستن به فضای کاری با شناسه + برای پیوستن به یک فضای موجود، نیاز به دعوت دارید. + می‌توانید بعداً این را تغییر دهید + می‌خواهید چه نوع فضایی ایجاد کنید؟ + فضاها راهی جدید برای گروه‌بندی اتاق‌ها و افراد است + فضای خصوصیتان + فضا عمومیتان + افزودن فضا + ترک اتاق با شناسهٔ داده‌شده (در صورت null بودن، اتاق کنونی) + پیوستن به فضا با شناسهٔ داده‌شده علامت زده نشده جستجو با نام اتاق هرکسی می تواند اتاق را پیدا کرده و به آن بپیوندد @@ -2752,14 +2752,14 @@ مجوز پیوستن مهمان به اتاق دعوت‌ها اتاق‌های پیشنهادی - مدیریت اتاق‌ها و فضاهای کاری + مدیریت اتاق‌ها و فضاها نشانه‌گذاری به عنوان غیر پیشنهادی نشانه‌گذاری به عنوان پیشنهادی پیشنهادی - عمومی کردن این فضای کاری + عمومی کردن این فضا مدیریت اتاق‌ها - به دنبال کسی هستید که در %s نباشد؟ - %s شما را دعوت کرده است + دنبال کسی هستید که در %s نیست؟ + %s دعوتتان می‌کند این اتاق عمومی است رسانه را با اندازه اصلی ارسال کن @@ -2772,35 +2772,71 @@ به عنوان پیش فرض استفاده کن و دیگر سوال نپرس همواره بپرس دعوت به %s - شخص ناشناس + فرد ناشناس انتقال به %1$s - این قابلیت در وضعیت بتا است - به ما بازخورد دهید - ارسال بازخورد با شکست مواجه شد(%s) - سپاس! بازخورد شما با موفقیت ارسال شد - شما در حال استفاده از نسخه بتای اسپیس هستید. بازخورد شما به آگاه کردن نسخه های بعدی کمک خواهد کرد. سکو و نام کاربری شما ثبت خواهد شد تا به ما در استفاده هرچه بیشتر از بازخورد شما کمک کند. + این ویژگی در حالت آزمایشی است + دادن بازخورد + فرستادن بازخورد شکست خورد (%s) + سپاس! بازخوردتان با موفّقیت فرستاده شد + دارید از نگارشی آزمایشی از فضاها استفاده می‌کنید. بازخوردتان در شکل‌دهی به نگارش‌های آتی کمک خواهد کرد. برای کمک به استفادهٔ هرچه بیش‌تر از بازخوردتان، بن‌سازه و نام کاربریتان یادداشت خواهد شد. بازخورد متاسفم، یک خطا زمانی که در تلاش برای اضافه شدن به کنفرانس بود ، اتفاق افتاد این کارساز در حال حاضر در فهرست موجود است نمی‌توانم این کارساز یا فهرست اتاقش را بیابم - نام کارساز جدیدی که می‌خواهید در آن به جستجو بپردازید را وارد کنید. + نام کارسازی جدید که می‌خواهید کشف کنید را وارد کنید. اضافه کردن یک کارساز(سرور) جدید کارساز شما - اتاق نامگذاری نشده - بعضی اتاق ها ممکن است پنهان باشند زیرا خصوصی هستند و شما به یک دعوتنامه نیاز دارید. - بعضی اتاق ها ممکن است پنهان باشند زیرا خصوصی هستند و شما به یک دعوتنامه نیاز دارید. -\nشما اجازه اضافه کردن اتاق را ندارید. - این اسپیس هیچ اتاقی ندارد - لطفا برای آگاهی بیشتر با مدیر کارساز خود تماس بگیرید - به نظر می رسد کارساز خانگی شما هنوز از اسپیس ها پشتیبانی نمی کند - افزودن اتاق ها - شما مدیر این اسپیس هستید،مطمئن شوید قبل از خروج، حق مدیریت را به عضوی دیگر منتقل کرده اید. - این اسپیس عمومی نیست. شما نمیتوانید بدون یک دعوتنامه دوباره ملحق شوید. - شما تنها فرد اینجا هستید. اگر خارج شوید، هیچ کس از جمله شما قادر نخواهد بود در آینده ملحق شود. - به هر روش ادامه بده - در صورت داشتن هرگونه سوالی میتوانید با من تماس بگیرید + اتاق بی‌نام + ممکن است برخی اتاق‌ها پنهان باشند، زیرا خصوصی بوده و نیاز به دعوت دارید. + ممکن است برخی اتاق‌ها پنهان باشند، زیرا خصوصی بوده و نیاز به دعوت دارید. +\nاجازهٔ افزودن اتاق‌ها را ندارید. + این فضا هیچ اتاقی ندارد + لطفاً برای اطّلاعات بیش‌تر، با مدیر کارسازتان تماس بگیرید + به نظر کارساز خانگیتان هنوز از فضاها پشتیبانی نمی‌کند + افزودن اتاق‌ها + شما مدیر این فضایید. پیش از ترک، مطمئن شوید حق مدیریت را به عضو دیگری منتقل کرده‌اید. + این فضا عمومی نیست. بدون دعوت نخواهید توانست دوباره بپیوندید. + شما تنها فرد حاضرید. اگر خارج شوید، هیچ‌کس از جمله خوتان قادر به پیوستن در آینده نخواهد بود. + به هر حال ادامه بده + اگر پرسش دیگری دارید، می‌توانید با من در تماس باشید بازخورد فضاها - مجوز های از دست رفته - برای انجام این عمل، لطفاً از تنظیمات سیستم به دوربین اجازه دهید. - برخی از مجوزها برای انجام این عمل وجود ندارد، لطفاً از تنظیمات سیستم مجوزها را بدهید. + اجازه‌های نداشته + برای این کار، لطفاً اجازهٔ دوربین را از تنظیمات سامانه اعطا کنید. + برخی اجازه‌ها برای این کار داده نشده. لطفاً اجازه‌ها را از تنظیمات سامانه اعطا کنید. + متأسفانه هنگام تلاش برای پیوستن، خطایی رخ داد: %s + ارتقا به نگارش اتاق پیشنهادی + این اتاق در حال اجرای نگارش %s اتاق‌هاست که این کارساز خانگی به عنوان ناپایدار علامت زده است. + برای ارتقای اتاق نیاز به اجازه دارید + به‌روز رسانی خودکار والد فضا + دعوت خودکار کاربران + این اتاق را از %s به %s ارتقا خواهید داد. + ارتقای اتاق، عملی پیش‌رفته است و معمولاً هنگامی که اتاقی به خاطر اشکال‌ها، کمبود ویژگی‌ها یا آسیب‌پذیری‌های امنیتی ناپایدار است، پیشنهاد می‌شود. +\nاین عمل معمولاً فقط روی چگونگی فراوری اتاق روی کارساز اثر می‌گذارد. + ارتقای اتاق خصوصی + ارتقای اتاق عمومی + ارتقا + لطفاً شکیبا باشید. ممکن است کمی زمان ببرد. + پیوستن به اتاق جایگزینی + در حال حاضر ممکن است افراد نتوانند به هیچ اتاق خصوصی‌ای که می‌سازید بپیوندند. +\n +\nاین مورد را به عنوان بخشی از حالت آزمایشی بهیود خواهیم داد، فقط می‌خواستیم بدانید. + فضاهای همگروهی هنوز کاملاً آماده نیستند، ولی می‌توانید بیازماییدشان + ناپایدار + پایدار + نگارش پیش‌گزیده + نگارش‌های اتاق 👓 + جایگزینی تأیید با مقایسهٔ اموجی‌ها + پویش با این افزاره + با افزارهٔ دیگرتان پوییده یا جابه‌جا کرده و با این افزاره بپویید + نشانی فضا + دیدن و مدیریت نشانی‌های این فضا. + نشانی‌های فضا + نشانی API کارساز خانگی + فضای خصوصی + فضای عمومی + اتاقی را به نگارشی جدید ارتقا می‌دهد + این فضا عمومی است + حس آزمایش دارید؟ +\nمی‌توانید فضاهای موجود را به فضایی بیفزایید. + فضای آزمایشی - فقط نمایش یتسم‌ها در خانه \ No newline at end of file From d0bb1bcf59a57fe43bb94d36a523ef6c9e70e97f Mon Sep 17 00:00:00 2001 From: 1kiarash <1kiarash.gm@gmail.com> Date: Fri, 23 Jul 2021 08:32:34 +0000 Subject: [PATCH 100/242] Translated using Weblate (Persian) Currently translated at 100.0% (2518 of 2518 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/fa/ --- vector/src/main/res/values-fa/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/vector/src/main/res/values-fa/strings.xml b/vector/src/main/res/values-fa/strings.xml index 7e7ccb209b..0e3a186ff6 100644 --- a/vector/src/main/res/values-fa/strings.xml +++ b/vector/src/main/res/values-fa/strings.xml @@ -2839,4 +2839,5 @@ حس آزمایش دارید؟ \nمی‌توانید فضاهای موجود را به فضایی بیفزایید. فضای آزمایشی - فقط نمایش یتسم‌ها در خانه + مشاوره با %1$s \ No newline at end of file From ff37ed0e1f8774a3ced6901025fec349808438e2 Mon Sep 17 00:00:00 2001 From: Vancha Date: Wed, 21 Jul 2021 12:32:52 +0000 Subject: [PATCH 101/242] Translated using Weblate (Frisian) Currently translated at 39.3% (990 of 2518 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/fy/ --- vector/src/main/res/values-fy/strings.xml | 375 +++++++++++++++++++++- 1 file changed, 373 insertions(+), 2 deletions(-) diff --git a/vector/src/main/res/values-fy/strings.xml b/vector/src/main/res/values-fy/strings.xml index 5bdcc26d1f..e60844a237 100644 --- a/vector/src/main/res/values-fy/strings.xml +++ b/vector/src/main/res/values-fy/strings.xml @@ -464,7 +464,7 @@ Klik hjir om âldere berjochten te besjen Disse keamer is in trochsetting fan in oar petear It petear giet hjir troch - Disse keamer is ferfong en is net mear aktyf + Disse keamer is ferfong en is net mear aktyf. Graach dyn wachtwurd ynfiere. Graach in brûkersnamme ynfiere. Akkount Útskeakelje @@ -586,7 +586,7 @@ Feiligje dyn reservekopy mei in Wachtwurdssin. Kaaien mei de hân eksportearje (Avanseare) - Reitsje dyn fersifere gegevens nea kwyt + Reitsje jo fersifere gegevens nea kwyt Gjin Matrix sesje beskikber Graach de wachtwurdssin fuortsmite ast wolst dot ${app_name} in nije herstel kaai foar dy oanmakket. Graach in wachtwurdssin ynfiere @@ -665,4 +665,375 @@ Do hast de tsjinner ACLs foar dizze keamer feroare. %s hat de tsjinner ACLs foar dizze keamer feroare. Do hast dyn profyl ôfbylding feroare + Koe de suggestje net ferstjoere (%s) + Dankewol, it ferstjoeren fan de suggestje is slagge + Skriuw jo suggestje hjir + Skriuw jo suggestje hjir ûnder. + Formaat: + Url: + session_name: + app_display_name: + push_key: + app_id: + Jo binne dizze keamer al oan it besjen! + Dizze keamer yn it keamer oersjoch publisearre + Reaksjes Besjen + Reaksje Tafoege + Keamers + Wolkom thús! + Útnûge troch %s + Hat jo in ûtnoeging stjoerd + Gean in keamer binnen om de app te brûken. + Opnij probearje + Feroarje + Brûkers komme net oerien + Kaaien komme net oerien + Der is en ûnjildich berjocht ûntfong + De sesje hat ûnferwachts in berjocht ûntfong + De SAS kaam net oerien + De sesje wit neat fan dy transaksje + It ferifikaasje proses hat te lang duorre + De brûker hat de ferifikaasje ôfbrutsen + Ik begryp it + Konfiguraasje brûke + Ik wie it sels + Back-up Fuortsmite + Koe de back-up net fuortsmite (%s) + Back-up oan it fuortsmiten… + Back-up werom sette: + Binne jo der wis fan\? + Ferfange + As bestân opslaan + Diele + Wachtwurdssin is net sterk genôch + Slagge! + Backup Oanmeitsje + Wachtwurdssin ynstelle + Keamers + Gjin brûkers + Keamers + Minsken + Thús + foarbyld + Foarbyld + Oanmeitsje + Fersifere berjocht + Lûd + Stil + Út + Brûker mei it id wat opjûn is der út skoppe + Keamer ferlitte + Diele + Diele sûnder te ferifiearren + Ferifiearre + Ferifikaasje starte + Alles Blokkearre + Troch DRM beskerme Media lêze + De mikrofoan brûke + De kamera brûke + BESJEN + Hiel grut + It grutst + Grutter + Grut + Lyts + Gewoan + Hiel lyts + Grutte fan it lettertype + Ik + Nije Útnoeging + Nij Berjocht + Keamer + Nij Evenemint + %1$s en %2$s + %1$s yn %2$s en %3$s + %1$s yn %2$s + + %d notifikaasje + %d notifikaasjes + + + %1$s: %2$d berjocht + %1$s: %2$d berjochten + + + %d útnûging + %d útnoegingen + + + %d keamer + %d keamers + + Typ hjir… + De tsjinner stiet al yn de list + Kin dizze tsjinner, as de rômte list fan dizze tsjinner net fine + Fier de namme yn fan de tsjinner dy jo ûntdekke wolle. + In nije tsjinner tafoegje + Jo tsjinner + Alle keamers op %s tsjinner + Tsjinner namme + Keamer hat ûnbekend sesjes + Ik befêstigje dat de kaaien oerien komme + As dit net oerienkomt, kin de feiligens fan jo kommunikaasje kompromittearre wêze. + Befêstigje troch dit te fergelykjen mei Brûkers Ynstellingen fan jo oare sesje: + Fan de swarte list ôf helje + Sesje ferifiearre + Op de swarte list set + Ferifiearre + Net Ferifiearre + Ûntsiferings flater + Sesje ID + Algoritme + Tema + Oersjoch + As haad adres ynstelle + Nij adres (bygelyks #foo:matrix.org) + Jo moatte útlogge om fersifering ynskeakelje te kinnen. + Ein-oan-Ein fersifering is ynskeakele + Ein-oan-Ein Fersifering + Adressen + + %d ferballe brûker + %d ferballe brûkers + + Ferballe brûkers + Publisearje + Keamer tagong + Wa hat tagong ta dizze keamer\? + Keamer Skiednis Lêsberheid + Keamer Tagong + Dizze keamer yn it keamer oersjoch sjen litte + Tagong en sichtberens + Lege prioriteit + Standert komprimearing + Media + In ativaasje koade ynfiere + Telefoan nûmer + Binne jo der wis fan dat jo de %1$s %2$s fuort smite wolle\? + E-mail adressen en telefoan nûmers dy oan jo Matrix account keppele binne behearre + Der die harren in flater foar wylst jo e-mail adres ferifiearre waard. + Hjoeddeistich wachtwurd + %1$s @ %2$s + Myn account deaktivearje + Account deaktivearje + Behearre + Berjochten mei enter ferstjoere + Trilje wannear jo neamd wurde + Account eveneminten sjen litte + Sjen litte wannear oft minsken de keamer yn kaam binne, as der út gien binne + Brûk it /confetti kommando as ferstjoer in berjocht mei ❄️ as 🎉 + Chat effekten sjen litte + Sjen kinne dat berjochten lêzen binne + Lit oare minsken witte dat jo oan it typen binne. + Typ notifikaasjes ferstjoere + Tiid stimpels foar alle berjochten sjen litte + Fersifering Kaai Behear + Fersifering + Oare ynstellingen + Notifikaasjes + Brûkers ynstellingen + olm ferzje + Ferzje + Eftergrûn Syngronisaasje Modus + Optimalisearje foar batterij gebrûk + Wannear ik útnûge wurd foar in keamer + Stille Notifikaasjes Ynstelle + • Notifikaasjes befetsje allinich metadata + • Notifikaasjes wurde mei Firebase Cloud Messaging ferstjoerd + Dizze applikaasje hat tastimming nedich om op de eftergrûn te starten + Restriksjes útskeakelje + • Notifikaasje sil de ynhâld fan it berjocht net sjen litte + LED kleur útsykje, triljen, lûd… + Lûde Notifikaasjes Ynstelle + It skerm foar 3 sekonden ynskeakelje + Notifikaasjes foar dizze sesje ynskeakelje + Notifikaasjes foar dit account ynskeakelje + Notifikaasje lûd + Gewoan + Tsjinst Starte + Notifikaasje Tsjinst + Ynstellingen Kontrolearje + Sesje Ynstellingen. + Oan it útfieren... (%1$d of %2$d) + Profyl Ôfbylding + Ferzje %s + Ferzje + ÚTNOEGINGEN + LEGE PRIORITEIT + KEAMERS + FAVORITEN + OERSJOCH + KEAMERS + Gjin resultaten + Delheljen Ôfbrekke + Minsken + Keamer Details + Bestannen + Sykje + ÚTNÛGE + Permisjes feroarje + Keamer namme feroarje + Skiednis sichtberens feroarje + Keamer fersifering ynskeakelje + Haad adres fan de keamer feroarje + Keamer ôfbylding feroarje + Elkenien op de hichte bringe + Berjochten dy troch oaren ferstjoerd binne fuort smite + Brûkers ferbalje + Brûkers der út skoppe + Brûkers útnûgje + Berjochten ferstjoere + Standert rol + Permisjes + Negearre + Útlogge + Net fertrouwe + Fertrouwe + Bestân net fûn + Net ferstjoerde berjochten fuortsmite + Net ferstjoerde berjochten opnij ferstjoere + Alles ôfbrekke + Alles opnij ferstjoere + Berjocht net ferstjoerd. %1$s as %2$s no\? + Ferbining mei de tsjinner is ferlern. + In reaksje ferstjoere (net fersifere)… + In fersifere reaksje ferstjoere… + In berjocht ferstjoere (net fersifere)… + In fersifere berjocht ferstjoere… + Allinich Matrix brûkers + Útnoeging ôfbrekke + Net mear negearre + Brûker negearre + Brûker net mear negearre + Negearre + Degradearje + Jo sels degradearje\? + Sesje list sjen litte + Neame + In administrator meitsje + In moderator meitsje + Werom sette nei gewoane brûker + Der út skoppe + Ferbalje + Útnoeging ôfbrekke + Útnûgje + SESJES + Direkte Berjochten + SKILJE + ADMINISTRAASJE ARK + %1$s %2$s lyn + %1$s no + Ôfwêzich + Offline + Online + Binne jo der wis fan dat jo de keamer ferlitte wolle\? + Keamer ferlitte + 1 lid + Syngronisearje… + Dochs Trochgean + Yn downloads opslaan\? + Opslein + In foto as fideo meitsje + Kin gjin fideo opnimme + + %1$dm %2$ds + %d s + It delheljen ôfbrekke\? + Opnij de fersifering kaaien fan jo oare sesjes opfreegje. + Kin net registrearre : flater mei it befêstigjen fan eigendom fan e-mail adres + Kin net ynlogge: Netwurk flater + URL moat mei http[s]:// begjinne + Jo nije wachtwurd befêstigje + E-mail adres (opsjoneel) + E-mail adres + E-mailadres as brûkers namme + Wachtwurd + Oerslaan + As standert ynstelle en net wer freegje + Altyd freegje + Triedleaze Koptelefoan + Koptelefoan + Lûdsprekker + Probearje ris %s te brûken + Stimlûd ferstjoere + Begjinne mei Fideo Beljen + Begjinne mei Beljen + Identiteits Tsjinner URL + Thús tsjinner API URL + Thús tsjinner URL + It oanjaan fan in flater yn dizze applikaasje is net slagge (%s) + It oanjaan fan in flater yn dizze applikaasje is slagge + Keamer binnen gean + Avesaasje (%s%%) + Dizze applikaasje is de lêste kear fêst rûn. Wolle jo miskien it skerm iepenje om in flater yn dizze applikaasje oan te jaan\? + It liker der op dat jo út lilkens mei jo telefoan skodzje. Wolle jo miskien it skerm iepenje om in flater yn dizze applikaasje oan te jaan\? + Rômtes + Útnûgingen + Alle keamers yn it keamer oersjoch sjen litte, ek keamers mei ynhâld foar folwoeksenen. + Keamers mei ynhâld foar folwoeksenen sjen litte + Keamer oersjoch + Gjin resultaten mear + Oanrekommandearre Keamers + Nije wearde + Omskeakelje + Kopiearre + Jo kinne josels net belje + Jo meie gjin konferinsje starte + Jo meie gjin konferinsje petear yn dizze keamer starte + Start jo Petearen + Om dizze aksje út te fieren, skeakelje dan graach de Kamera permisje yn fanút de systeem ynstellingen. + Jo hawwe ein-oan-ein fersifering ynskeakele. + %1$s hat ein-oan-ein fersifering ynskeakele. + %1$s hat foarkaam dat gasten dizze keamer binnen gean kinne. + Jo hawwe foarkaam dat gasten dizze keamer binnen gean kinne. + %1$s hat foarkaam dat gasten dizze keamer binnen gean kinne. + Jo hawwe foarkaam dat gasten dizze keamer binnen gean kinne. + Jo hawwe gasten tastien om hjir binnen te gean. + %1$s hat gasten tastien om hjir binnen te gean. + Jo hawwe gasten tastien om dizze keamer binnen te gean. + %1$s hat gasten tastien om dizze keamer binnen te gean. + Systeem Standert + Jo hawwe ein-oan-ein fersifering ynskeakele (net erkend algoritme %1$s). + + %1$s en 1 oar + %1$s en %2$d oaren + + + %1$s, %2$s, %3$s en %4$d oar + %1$s, %2$s, %3$s en %4$d oaren + + %1$s, %2$s, %3$s en %4$s + %1$s, %2$s en %3$s + %1$s en %2$s + Keamer Útnoeging + It is no net mooglik om in lege keamer wer binnen te gean. + Jo hawwe de útnoeging nei %1$s oannaam + %1$s hat de útnoeging nei %2$s oannaam + Jo hawwe de útnoeging nei %1$s ynlutsen + %1$s hat de útnoeging nei %2$s wer ynlutsen + Jo hawwe de útnoeging nei %1$s om de keamer binnen te gean wer ynlutsen + %1$s hat de útnoeging nei %2$s om de keamer binnen te gean wer ynlutsen + Jo hawwe %1$s útnoege + %1$s hat %2$s útnoege + Jo hawwe in útnoeging om de keamer binnen te gean nei %1$s stjoerd + %1$s hat in útnoeging om de keamer binnen te gean nei %2$s stjoerd + %1$s hat de keamer ôfbylding fuortsmiten + VoIP konferinsje ôfrûn + VoIP konferinsje begûn + Jo hawwe in VoIP konferinsje oanfrege + %1$s hat in VoIP konferinsje oanfrege + 🎉 Alle tsjinners bin ferballe fan it meidwaan! Dizze keamer kin net mear brûkt wurde. + • Tsjinners dy oerien komme mei IP adressen binne no ferballe. + • Tsjinners dy oerien komme mei IP adressen binne no tastien. + • Tsjinners dy oerien komme mei %s binne út de list mei tastiene tsjinners wei helle. + • Tsjinners dy oerien komme mei %s binne no tastien. + • Tsjinners dy oerien komme mei %s binne út de list mei ferballe tsjinners wei helle. + • Tsjinners dy oerien komme mei %s binne no ferballe. + • Tsjinners dy oerien komme mei IP adressen binne ferballe. + • Tsjinners dy oerien komme mei IP adressen binne tastien. + • Tsjinners dy oerien komme mei %s binne ferballe. + Jo hawwe de keamer ôfbylding fuortsmiten + Jo hawwe de keamer ôfbylding feroare + %1$s hat de keamer ôfbylding feroare + %1$s hat syn profylfoto feroare \ No newline at end of file From c5dfcc9c093f6b0c16d44fa4702cc0ec7809db9d Mon Sep 17 00:00:00 2001 From: random Date: Fri, 23 Jul 2021 09:48:03 +0000 Subject: [PATCH 102/242] Translated using Weblate (Italian) Currently translated at 99.8% (2515 of 2518 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/it/ --- vector/src/main/res/values-it/strings.xml | 29 +++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/vector/src/main/res/values-it/strings.xml b/vector/src/main/res/values-it/strings.xml index c6dedd6bdf..e9dc787928 100644 --- a/vector/src/main/res/values-it/strings.xml +++ b/vector/src/main/res/values-it/strings.xml @@ -573,7 +573,7 @@ Rispedisci messaggi non inviati Elimina i messaggi non inviati File non trovato - Non hai il permesso di pubblicare in questa stanza + Non hai il permesso di pubblicare in questa stanza. Fidati Non fidarti @@ -1078,7 +1078,7 @@ Cambia il tuo nome visualizzato Markdown attivo / spento Per correggere la gestione dei widget - Questa stanza è stata sostituita da un\'altra e non è più attiva + Questa stanza è stata sostituita da un\'altra e non è più attiva. La conversazione continua qui Questa stanza contiene una conversazione cominciata altrove Clicca qui per vedere i messaggi precedenti @@ -2869,4 +2869,29 @@ Indirizzo dello spazio Vedi e gestisci gli indirizzi di questo spazio. Indirizzi dello spazio + Aggiorna la stanza alla versione consigliata + La versione di questa stanza è %s, che questo homeserver ha segnalato come instabile. + Ti serve il permesso per aggiornare una stanza + Aggiorna il padre dello spazio automaticamente + Invita utenti automaticamente + Aggiornerai questa stanza dalla %s alla %s. + L\'aggiornamento di una stanza è un\'azione avanzata e solitamente è consigliata quando una stanza è instabile a causa di errori, funzionalità mancanti o vulnerabilità di sicurezza. +\nDi solito influisce solo su come la stanza viene elaborata sul server. + Aggiorna stanza privata + Aggiorna stanza pubblica + Aggiorna + Abbi pazienza, potrebbe volerci un po\' di tempo. + Entra nella stanza sostitutiva + Aggiorna una stanza ad una nuova versione + instabile + stabile + Versione predefinita + Versioni stanza 👓 + Verifica confrontando gli emoji + Scansiona con questo dispositivo + Scansiona il codice con l\'altro dispositivo o scambia e scansiona con questo dispositivo + URL delle API dell\'homeserver + Autorizzazioni mancanti + Per eseguire questa azione, concedi l\'autorizzazione Fotocamera dalle impostazioni di sistema. + Mancano alcune autorizzazioni per eseguire questa azione, concedile dalle impostazioni di sistema. \ No newline at end of file From 936ca950d948e1d69104c65901929d00d2475955 Mon Sep 17 00:00:00 2001 From: oksya8and8 Date: Thu, 22 Jul 2021 14:40:18 +0000 Subject: [PATCH 103/242] Translated using Weblate (Japanese) Currently translated at 65.6% (1653 of 2518 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/ --- vector/src/main/res/values-ja/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml index 0314e322db..7412b950a5 100644 --- a/vector/src/main/res/values-ja/strings.xml +++ b/vector/src/main/res/values-ja/strings.xml @@ -1841,5 +1841,5 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場 直接またはグループで人々とチャットする あなたの会話。 それを所有する。 アカウントの復旧用のメールアドレスを設定して、後からオプションで知人に見つけてもらえるようにできます。 - これは、%sを使用したダイレクトメッセージ履歴の始まりです。 + ここが%sとのダイレクトメッセージのスタート地点です。 \ No newline at end of file From fe8a9c8753214e23cc39291805f028e4d9d9b0c8 Mon Sep 17 00:00:00 2001 From: lvre <7uu3qrbvm@relay.firefox.com> Date: Mon, 19 Jul 2021 13:27:18 +0000 Subject: [PATCH 104/242] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (2518 of 2518 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/pt_BR/ --- vector/src/main/res/values-pt-rBR/strings.xml | 39 +++++++++++++++---- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/vector/src/main/res/values-pt-rBR/strings.xml b/vector/src/main/res/values-pt-rBR/strings.xml index d187980d54..1339ce1c00 100644 --- a/vector/src/main/res/values-pt-rBR/strings.xml +++ b/vector/src/main/res/values-pt-rBR/strings.xml @@ -577,7 +577,7 @@ Reenviar mensagens não-enviadas Deletar mensagens não-enviadas Arquivo não encontrado - Você não tem permissão para postar nesta sala + Você não tem permissão para postar nesta sala. Confiar Não confiar @@ -1121,7 +1121,7 @@ Para continuar, por favor entre sua senha: Desativar Conta Por favor entre sua senha. - Esta sala tem sido substituída e não está mais ativa + Esta sala tem sido substituída e não está mais ativa. A conversa continua aqui Esta sala é uma continuação de uma outra conversa Clique aqui para ver mensagens mais antigas @@ -2099,7 +2099,7 @@ Fazer signout desta sessão Nenhuma informação criptográfica disponível Esta sessão é confiada para mensageria segura porque você a verificou: - Verifique esta sessão para marcá-la como confiada & garantir-lhe acesso a mensagens encriptadas. Se você não fez signin a esta sessão sua conta pode estar comprometida: + Verifique esta sessão para marcá-la como confiada & conceder-lhe acesso a mensagens encriptadas. Se você não fez signin a esta sessão sua conta pode estar comprometida: %d sessão ativa %d sessões ativas @@ -2107,7 +2107,7 @@ Verificar este login Outras(os) usuárias(os) podem não confiar nela Completar Segurança - Use uma sessão existente para verificar esta aqui, garantindo-lhe acesso a mensagens encriptadas. + Use uma sessão existente para verificar esta aqui, concedendo-lhe acesso a mensagens encriptadas. Verificar Verificada(o) Aviso @@ -2164,7 +2164,7 @@ Recarregar Novo login. Foi você\? Toque para revisar & verificar - Use esta sessão para verificar sua nova, garantindo-lhe acesso a mensagens encriptadas. + Use esta sessão para verificar sua nova, concedendo-lhe acesso a mensagens encriptadas. Não foi eu Sua conta pode estar comprometida Se você cancelar, você não vai ser capaz de ler mensagens encriptadas neste dispositivo, e outras(os) usuárias(os) não vão confiar nele @@ -2289,7 +2289,7 @@ Verificar Manualmente por Texto Verificar login Verificar Interativamente por Emoji - Confirme sua identidade ao verificar este login desde uma de suas outras sessões, garantindo-lhe acesso a mensagens encriptadas. + Confirme sua identidade ao verificar este login desde uma de suas outras sessões, concedendo-lhe acesso a mensagens encriptadas. Marcar como Confiada Por favor escolha um nome de usuária(o). Por favor escolha uma senha. @@ -2435,7 +2435,7 @@ Gerenciar emails e números de telefone linkados a sua conta Matrix Código Por favor use o formato internacional (número de telefone deve começar com \'+\') - Confirme sua identidade ao verificar este login, garantindo-lhe acesso a mensagens encriptadas. + Confirme sua identidade ao verificar este login, concedendo-lhe acesso a mensagens encriptadas. Não dá para abrir uma sala de onde você foi banida(o). Não dá para encontrar esta sala. Assegure-se que ela existe. O link foi malformado @@ -2885,4 +2885,29 @@ Endereço de espaço Ver e gerenciar endereços deste espaço. Endereços de espaço + Fazer upgrade para a versão de sala recomendada + Esta sala está rodando versão de sala %s, que este servidorcasa tem marcado como instável. + Você precisa de permissão para fazer upgrade de uma sala + Fazer update de pai de espaço automaticamente + Convidar usuárias(os) automaticamente + Você vai fazer upgrade desta sala de %s para %s. + Fazer upgrade de uma sala é uma ação avançada e é geralmente recomendado quando uma sala está instável devido a bugs, funcionalidades faltando ou vulnerabilidades de segurança. +\nIsto geralmente só afeta como a sala é processada no servidor. + Fazer upgrade de sala privada + Fazer upgrade de sala pública + Fazer upgrade + Por favor seja paciente, pode levar algum tempo. + Juntar-se a sala de substituição + Faz upgrade de uma sala para uma nova versão + instável + estável + Versão Default + Versões de Sala 👓 + Verificar ao comparar emoji em vez disso + Scannar com este dispositivo + Scanne o código com seu outro dispositivo ou troque e scanne com este dispositivo + URL de API de Servidorcasa + Permissões faltando + Para performar esta ação, por favor conceda a permissão Câmera a partir das configurações de sistema. + Algumas permissões estão faltando para performar esta ação, por favor conceda as permissões a partir das configurações de sistema. \ No newline at end of file From ba52336a6053b4e89bdae3f6c809fa08bd9ee320 Mon Sep 17 00:00:00 2001 From: Michael Mihai Date: Thu, 22 Jul 2021 11:03:12 +0000 Subject: [PATCH 105/242] Translated using Weblate (Romanian) Currently translated at 10.4% (264 of 2518 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ro/ --- vector/src/main/res/values-ro/strings.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/vector/src/main/res/values-ro/strings.xml b/vector/src/main/res/values-ro/strings.xml index c5bae614bb..c9aaf56899 100644 --- a/vector/src/main/res/values-ro/strings.xml +++ b/vector/src/main/res/values-ro/strings.xml @@ -290,4 +290,11 @@ Conectare Deconectare Deconectare + • Serverele care se potrivesc %s au fost eliminate din lista. + Tu ai schimbat lista de acces in aceasta cameră. + %s a schimbat lista de acces pentru această cameră. + • Serverele care se potrivesc %s sunt permise. + • Serverele care se potrivesc %s sunt interzise. + Tu ai setat lista de acces in aceasta cameră. + %1$s porniți criptarea de la un capat la altul (%2$s) \ No newline at end of file From df75c9375f1b81e5dc74337597cb165e3c4659eb Mon Sep 17 00:00:00 2001 From: tanmatsu Date: Thu, 22 Jul 2021 10:55:16 +0000 Subject: [PATCH 106/242] Translated using Weblate (Romanian) Currently translated at 10.4% (264 of 2518 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ro/ --- vector/src/main/res/values-ro/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vector/src/main/res/values-ro/strings.xml b/vector/src/main/res/values-ro/strings.xml index c9aaf56899..ef390de982 100644 --- a/vector/src/main/res/values-ro/strings.xml +++ b/vector/src/main/res/values-ro/strings.xml @@ -297,4 +297,6 @@ • Serverele care se potrivesc %s sunt interzise. Tu ai setat lista de acces in aceasta cameră. %1$s porniți criptarea de la un capat la altul (%2$s) + Ați făcut istoria camerei viitoare vizibilă pentru %1$s + %1$s a făcut istoria camerei viitoare vizibilă pentru %2$s \ No newline at end of file From 1c75c8f6f26d04880ef20e79d1a67ec95a039398 Mon Sep 17 00:00:00 2001 From: Besnik Bleta Date: Tue, 20 Jul 2021 11:16:39 +0000 Subject: [PATCH 107/242] Translated using Weblate (Albanian) Currently translated at 99.5% (2507 of 2518 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/sq/ --- vector/src/main/res/values-sq/strings.xml | 31 ++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/vector/src/main/res/values-sq/strings.xml b/vector/src/main/res/values-sq/strings.xml index 553761ce26..85a074ab86 100644 --- a/vector/src/main/res/values-sq/strings.xml +++ b/vector/src/main/res/values-sq/strings.xml @@ -452,7 +452,7 @@ Hidheni tej Anëtarë liste Po njëkohësohet… - Hidhu te të palexuarit. + Hidhu te të palexuarit Jeni ftuar të merrni pjesë në këtë dhomë nga %s një dhomë Fjalosje e Re @@ -526,7 +526,7 @@ Ridërgo mesazhet e padërguara Fshi mesazhet e padërguar S’u gjet kartelë - S’keni leje të postoni në këtë dhomë + S’keni leje të postoni në këtë dhomë. Besoje Mos i zër besë Dalje @@ -923,7 +923,7 @@ Sesioni juaj i paverifikuar \'%s\' po kërkon kyçe fshehtëzimi. I heq cilësinë e operatorit përdoruesit me ID-në e dhënë Që të vazhdohet të përdoret shërbyesi home %1$s, duhet të shqyrtoni dhe pajtoheni me termat dhe kushtet. - Kjo dhomë është zëvendësuar dhe s’është më aktive + Kjo dhomë është zëvendësuar dhe s’është më aktive. Kjo dhomë është një vazhdim i një bisede tjetër Ky shërbyes home ka tejkaluar një nga kufijtë mbi burimet. Ky shërbyes home ka tejkaluar kufirin Përdorues Aktivë Mujorë. @@ -2805,4 +2805,29 @@ Adresë hapësire Adresa hapësire Shihni dhe administroni adresa në këtë hapësirë. + Përmirësoje me versionin e rekomanduar të dhomës + Kjo dhomë po xhiron nën versionin %s, të cilin ky shërbyes Home e konsideron si të paqëndrueshëm. + Ju duhen leje për të përmirësua një dhomë + Përditëso automatikisht mëmë hapësire + Fto përdorues automatikisht + Do ta përmirësoni këtë dhomë nga %s në %s. + Përmirësimi i një dhome është një veprim i thelluar dhe zakonisht i rekomanduar kur një dhomë është e paqëndrueshme për shkak të metash, veçorish që mungojnë ose cenueshmëri sigurie. +\nZakonisht kjo prek vetëm mënyrën se si trajtohet dhoma te shërbyesi. + Përmirësoni dhomë private + Përmirësoni dhomë publike + Përmirësoje + Ju lutemi, bëni durim, mund të zgjasë ca. + Hyni te dhoma zëvendësuese + E përmirëson një dhomë me një version të ri + i paqëndrueshëm + i qëndrueshëm + Version Parazgjedhje + Versione Dhome 👓 + Verifikoji duke krahasuar emoji + Skanoni me këtë pajisje + Skanojeni kodin me pajisjen tuaj tjetër, ose kaloni në të dhe skanoni me këtë pajisje + URL API Shërbyesi Home + Mungojnë leje + Për kryerjen e këtij veprimi, ju lutemi, akordoni leje Kamerës që nga rregullimet e sistemit. + Për kryerjen e këtij veprimi mungojnë disa leje, ju lutemi, akordojini lejet që nga rregullimet e sistemit. \ No newline at end of file From ad8f42a9c05a4f7c6281b23ea010ff18c344320d Mon Sep 17 00:00:00 2001 From: Ihor Hordiichuk Date: Mon, 19 Jul 2021 12:25:24 +0000 Subject: [PATCH 108/242] Translated using Weblate (Ukrainian) Currently translated at 70.3% (1772 of 2518 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/uk/ --- vector/src/main/res/values-uk/strings.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vector/src/main/res/values-uk/strings.xml b/vector/src/main/res/values-uk/strings.xml index 5c9bf9595e..9941e387db 100644 --- a/vector/src/main/res/values-uk/strings.xml +++ b/vector/src/main/res/values-uk/strings.xml @@ -413,7 +413,7 @@ Попередній перегляд Відхилити - До першого непрочитаного повідомлення. + Перейти до непрочитаного %s запросив(ла) вас приєднатися до цієї кімнати Запрошення надіслано на адресу %s, не пов\'язану з цим обліковим записом.\nВи можете увійти під іншим обліковим записом чи приєднати email до цього. @@ -474,7 +474,7 @@ Надіслати ненадіслані повідомлення знову Видалити ненадіслані повідомлення Файл не знайдено - Ви не маєте права писати повідомлення в цій кімнаті + Ви не маєте дозволу писати повідомлення у цій кімнаті. Довіряти Не довіряти @@ -1047,7 +1047,7 @@ Для продовження, будь ласка, введіть Ваш пароль: Деактивувати обліковий запис Будь ласка, введіть Ваш пароль. - Ця кімната була замінена та більше не активна + Ця кімната була замінена та більше не активна. Продовження розмови тут Ця кімната є продовженням іншої розмови Натисніть сюди, щоб побачити старіші повідомлення From 739bd7fd846c55bda550b1116e8e12bebc982cfd Mon Sep 17 00:00:00 2001 From: sr093906 Date: Mon, 19 Jul 2021 14:15:38 +0000 Subject: [PATCH 109/242] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (2518 of 2518 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/zh_Hans/ --- vector/src/main/res/values-zh-rCN/strings.xml | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/vector/src/main/res/values-zh-rCN/strings.xml b/vector/src/main/res/values-zh-rCN/strings.xml index cee86a356b..f3e6219bf0 100644 --- a/vector/src/main/res/values-zh-rCN/strings.xml +++ b/vector/src/main/res/values-zh-rCN/strings.xml @@ -431,7 +431,7 @@ 重新发送未发送的消息 删除未发送的消息 文件未找到 - 你没有发送到这个聊天室的权限 + 你没有在这个聊天室发帖的权限。 信任 不信任 注销 @@ -955,7 +955,7 @@ 更改你显示的昵称 打开/关闭 markdown 修复 Matrix Apps 管理 - 这个聊天室已经被替换并且不再活跃 + 这个聊天室已经被替换并且不再活跃。 对话在此继续 这个聊天室是另一个对话的延续 点击此处查看更早的消息 @@ -2772,4 +2772,29 @@ 空间地址 查看和管理这个空间的地址。 空间地址 + 升级到推荐的聊天室版本 + 这个聊天室运行聊天室版本 %s,此主服务器已将其标记为不稳定。 + 您需要权限才能升级聊天室 + 自动更新空间父级 + 自动邀请用户 + 你将把此聊天室从 %s 升级到 %s。 + 升级聊天室是一项高级操作,通常建议在由于错误、缺少功能或安全漏洞造成聊天室不稳定时进行此操作。 +\n这通常只会影响此服务器如何处理该聊天室。 + 升级私人聊天室 + 升级公共聊天室 + 升级 + 请耐心等待,这可能需要一些时间。 + 加入替换聊天室 + 将聊天室升级到新版本 + 不稳定 + 稳定 + 默认版本 + 聊天室版本 👓 + 通过比较表情符号来验证 + 使用此设备扫描 + 使用您的其他设备扫描代码或切换并使用此设备扫描 + 主服务器 API 网址 + 缺少权限 + 要执行此操作,请从系统设置中授予相机权限。 + 缺少执行此操作的某些权限,请从系统设置中授予权限。 \ No newline at end of file From ebbd9af7a6dc1c214295e989327f703883cde54a Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Tue, 20 Jul 2021 02:11:42 +0000 Subject: [PATCH 110/242] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (2518 of 2518 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/zh_Hant/ --- vector/src/main/res/values-zh-rTW/strings.xml | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/vector/src/main/res/values-zh-rTW/strings.xml b/vector/src/main/res/values-zh-rTW/strings.xml index 21ff01b42c..dd32aaf460 100644 --- a/vector/src/main/res/values-zh-rTW/strings.xml +++ b/vector/src/main/res/values-zh-rTW/strings.xml @@ -567,7 +567,7 @@ 重送沒送出的訊息 刪除沒送出的訊息 沒找到檔案 - 您沒有權限在此聊天室發言 + 您沒有權限在此聊天室發言。 %d 則新訊息 @@ -995,7 +995,7 @@ 變更您的顯示暱稱 開啟/關閉 markdown 為了修復 Matrix 應用程式管理 - 這個聊天室已被取代,且不再活躍 + 這個聊天室已被取代,且不再活躍。 對話在此繼續 這個聊天示是其他對話的延續 點選這裡以檢視更舊的訊息 @@ -2762,4 +2762,29 @@ 空間地址 檢視與管理此空間的地址。 空間地址 + 升級到建議的聊天室版本 + 此聊天室正在執行聊天室版本 %s,此家伺服器已被標記為不穩定。 + 您需要升級聊天室的權限 + 自動更新空間上層 + 自動邀請使用者 + 您將要把此聊天室從 %s 升級到 %s。 + 升級聊天是是一項進階動作,通常建議在聊天室因臭蟲、缺少功能或安全漏洞而不穩定時使用。 +\n這通常只會影響聊天是在伺服器上的處理方式。 + 升級私人聊天室 + 升級公開聊天室 + 升級 + 請耐心等待,這可能會需要一些時間。 + 加入取代的聊天室 + 升級聊天室到新版本 + 不穩定 + 穩定 + 預設版本 + 聊天室版本 👓 + 透過比較表情符號來驗證 + 使用此裝置掃描 + 使用您的其他裝置掃描條碼或切換並使用此裝置掃描 + 家伺服器 API URL + 缺少權限 + 要執行此動作,請從系統設定中授予「相機」權限。 + 缺少執行此動作的部份權限,請從系統設定中授予權限。 \ No newline at end of file From f123db398ed2795281b9b84438cdbac4e798bf6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Sun, 25 Jul 2021 19:28:45 +0000 Subject: [PATCH 111/242] Translated using Weblate (Estonian) Currently translated at 100.0% (2518 of 2518 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/et/ --- vector/src/main/res/values-et/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/res/values-et/strings.xml b/vector/src/main/res/values-et/strings.xml index fd46b38b30..edd187daa6 100644 --- a/vector/src/main/res/values-et/strings.xml +++ b/vector/src/main/res/values-et/strings.xml @@ -533,7 +533,7 @@ Küsi oma muudest sessioonidest krüptimisvõtmed uuesti. Võtmete jagamise päring on saadetud. Päring on saadetud - Palun käivita ${app_name} mõnes muus seadmes, mis suudab neid sõnumeid dekrüptoda ja seega saata krüptovõtmeid siia sessiooni. + Palun käivita ${app_name} mõnes muus seadmes, mis suudab neid sõnumeid dekrüptida ja seega saata krüptovõtmeid siia sessiooni. Lugemisteatiste loend Gruppide loend From 07ad907db4de85337e757de404ea1ac198827c9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Mon, 26 Jul 2021 16:00:20 +0000 Subject: [PATCH 112/242] Translated using Weblate (Estonian) Currently translated at 100.0% (24 of 24 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/et/ --- fastlane/metadata/android/et/changelogs/40101120.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/et/changelogs/40101120.txt diff --git a/fastlane/metadata/android/et/changelogs/40101120.txt b/fastlane/metadata/android/et/changelogs/40101120.txt new file mode 100644 index 0000000000..c43e8f47b5 --- /dev/null +++ b/fastlane/metadata/android/et/changelogs/40101120.txt @@ -0,0 +1,2 @@ +Põhilised muutused selles versioonis: teemade ja välimuse uuendused ning videokõne-järgse kokkujooksmise parandused +Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases/tag/v1.1.12 From 40c951d114ccd1033791ce9d262c38fb7c1cec19 Mon Sep 17 00:00:00 2001 From: Lukas Novotny Date: Mon, 26 Jul 2021 14:20:15 +0000 Subject: [PATCH 113/242] Translated using Weblate (Czech) Currently translated at 100.0% (24 of 24 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/cs/ --- fastlane/metadata/android/cs-CZ/changelogs/40101120.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/cs-CZ/changelogs/40101120.txt diff --git a/fastlane/metadata/android/cs-CZ/changelogs/40101120.txt b/fastlane/metadata/android/cs-CZ/changelogs/40101120.txt new file mode 100644 index 0000000000..3a10c20c0a --- /dev/null +++ b/fastlane/metadata/android/cs-CZ/changelogs/40101120.txt @@ -0,0 +1,2 @@ +Hlavní změny v této verzi: aktualizace motivu a stylu a oprava pádu aplikace po videohovoru +Úplný seznam změn: https://github.com/vector-im/element-android/releases/tag/v1.1.12 From 5f148eb47891fbf5708ef44b148e2dc75ab97f09 Mon Sep 17 00:00:00 2001 From: lvre <7uu3qrbvm@relay.firefox.com> Date: Mon, 26 Jul 2021 17:51:42 +0000 Subject: [PATCH 114/242] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (24 of 24 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/pt_BR/ --- fastlane/metadata/android/pt-BR/changelogs/40101120.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/pt-BR/changelogs/40101120.txt diff --git a/fastlane/metadata/android/pt-BR/changelogs/40101120.txt b/fastlane/metadata/android/pt-BR/changelogs/40101120.txt new file mode 100644 index 0000000000..4e8b0a0901 --- /dev/null +++ b/fastlane/metadata/android/pt-BR/changelogs/40101120.txt @@ -0,0 +1,2 @@ +Principais mudanças nesta versão: atualização de tema e estilo e consertar um crash depois de chamada de vídeo +Changelog completo: https://github.com/vector-im/element-android/releases/tag/v1.1.12 From 4caf333769c94118b20ffddb30254feadb56d156 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 27 Jul 2021 09:59:01 +0200 Subject: [PATCH 115/242] Do not check the baseURL to override if it is the same than the one previously known and used --- .../java/org/matrix/android/sdk/internal/auth/SessionCreator.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/SessionCreator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/SessionCreator.kt index 160fd2d556..cc00c963ea 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/SessionCreator.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/SessionCreator.kt @@ -49,6 +49,8 @@ internal class DefaultSessionCreator @Inject constructor( // remove trailing "/" ?.trim { it == '/' } ?.takeIf { it.isNotBlank() } + // It can be the same value, so in this case, do not check again the validity + ?.takeIf { it != homeServerConnectionConfig.homeServerUriBase.toString() } ?.also { Timber.d("Overriding homeserver url to $it (will check if valid)") } ?.let { Uri.parse(it) } ?.takeIf { From 4c8a8d8cfb675e1fc550b9d67c5353852e2752d9 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Tue, 27 Jul 2021 15:32:35 +0300 Subject: [PATCH 116/242] Design review fixes. --- library/ui-styles/src/main/res/values/colors.xml | 2 +- .../src/main/res/values/styles_voice_message.xml | 2 +- .../app/features/home/room/detail/RoomDetailFragment.kt | 8 +++++++- .../home/room/detail/composer/VoiceMessageHelper.kt | 8 ++++++-- .../home/room/detail/composer/VoiceMessageRecorderView.kt | 7 +++++-- .../home/room/detail/timeline/item/MessageVoiceItem.kt | 2 ++ vector/src/main/res/values/strings.xml | 3 ++- 7 files changed, 24 insertions(+), 8 deletions(-) diff --git a/library/ui-styles/src/main/res/values/colors.xml b/library/ui-styles/src/main/res/values/colors.xml index e31bfb4ab8..9d5f15cbde 100644 --- a/library/ui-styles/src/main/res/values/colors.xml +++ b/library/ui-styles/src/main/res/values/colors.xml @@ -130,6 +130,6 @@ @color/palette_black_900 - @color/palette_gray_450 + @color/palette_gray_400 diff --git a/library/ui-styles/src/main/res/values/styles_voice_message.xml b/library/ui-styles/src/main/res/values/styles_voice_message.xml index 5d5fbef007..59fea75074 100644 --- a/library/ui-styles/src/main/res/values/styles_voice_message.xml +++ b/library/ui-styles/src/main/res/values/styles_voice_message.xml @@ -9,7 +9,7 @@ true 2dp 2dp - leftToRight + rightToLeft