From 755e3fe932cfef14e1543595aeb76e3bd8fc281b Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Wed, 24 Nov 2021 12:00:36 +0000 Subject: [PATCH 1/7] switching the player tracker to a singleton to avoid losing state on rotation - this means we need to be extra careful about releasing any listeners --- .../app/core/di/MavericksViewModelModule.kt | 6 ++++++ .../home/room/detail/RoomDetailFragment.kt | 8 +++---- .../composer/MessageComposerViewModel.kt | 21 +++++-------------- .../helper/VoiceMessagePlaybackTracker.kt | 3 ++- 4 files changed, 16 insertions(+), 22 deletions(-) diff --git a/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt index 69257f1f05..cac694e84e 100644 --- a/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt @@ -42,6 +42,7 @@ import im.vector.app.features.home.UnknownDeviceDetectorSharedViewModel import im.vector.app.features.home.UnreadMessagesSharedViewModel import im.vector.app.features.home.room.breadcrumbs.BreadcrumbsViewModel import im.vector.app.features.home.room.detail.RoomDetailViewModel +import im.vector.app.features.home.room.detail.composer.MessageComposerViewModel import im.vector.app.features.home.room.detail.search.SearchViewModel import im.vector.app.features.home.room.detail.timeline.action.MessageActionsViewModel import im.vector.app.features.home.room.detail.timeline.edithistory.ViewEditHistoryViewModel @@ -508,6 +509,11 @@ interface MavericksViewModelModule { @MavericksViewModelKey(RoomDetailViewModel::class) fun roomDetailViewModelFactory(factory: RoomDetailViewModel.Factory): MavericksAssistedViewModelFactory<*, *> + @Binds + @IntoMap + @MavericksViewModelKey(MessageComposerViewModel::class) + fun messageComposerViewModelFactory(factory: MessageComposerViewModel.Factory): MavericksAssistedViewModelFactory<*, *> + @Binds @IntoMap @MavericksViewModelKey(SetIdentityServerViewModel::class) 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 d8f1846d62..5f96eaf304 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 @@ -240,7 +240,6 @@ class RoomDetailFragment @Inject constructor( autoCompleterFactory: AutoCompleter.Factory, private val permalinkHandler: PermalinkHandler, private val notificationDrawerManager: NotificationDrawerManager, - val messageComposerViewModelFactory: MessageComposerViewModel.Factory, private val eventHtmlRenderer: EventHtmlRenderer, private val vectorPreferences: VectorPreferences, private val colorProvider: ColorProvider, @@ -393,8 +392,8 @@ class RoomDetailFragment @Inject constructor( when (mode) { is SendMode.Regular -> renderRegularMode(mode.text) is SendMode.Edit -> renderSpecialMode(mode.timelineEvent, R.drawable.ic_edit, R.string.edit, mode.text) - is SendMode.Quote -> renderSpecialMode(mode.timelineEvent, R.drawable.ic_quote, R.string.quote, mode.text) - is SendMode.Reply -> renderSpecialMode(mode.timelineEvent, R.drawable.ic_reply, R.string.reply, mode.text) + is SendMode.Quote -> renderSpecialMode(mode.timelineEvent, R.drawable.ic_quote, R.string.quote, mode.text) + is SendMode.Reply -> renderSpecialMode(mode.timelineEvent, R.drawable.ic_reply, R.string.reply, mode.text) } } @@ -1130,9 +1129,8 @@ class RoomDetailFragment @Inject constructor( override fun onPause() { super.onPause() - notificationDrawerManager.setCurrentRoom(null) - + voiceMessagePlaybackTracker.unTrack(VoiceMessagePlaybackTracker.RECORDING_ID) messageComposerViewModel.handle(MessageComposerAction.SaveDraft(views.composerLayout.text.toString())) // We should improve the UX to support going into playback mode when paused and delete the media when the view is destroyed. diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt index f4bfb32fce..c3c828252c 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt @@ -23,6 +23,8 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import im.vector.app.R +import im.vector.app.core.di.MavericksAssistedViewModelFactory +import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider @@ -764,23 +766,10 @@ class MessageComposerViewModel @AssistedInject constructor( } @AssistedFactory - interface Factory { - fun create(initialState: MessageComposerViewState): MessageComposerViewModel + interface Factory : MavericksAssistedViewModelFactory { + override fun create(initialState: MessageComposerViewState): MessageComposerViewModel } - /** - * We're unable to create this ViewModel with `by hiltMavericksViewModelFactory()` due to the - * VoiceMessagePlaybackTracker being ActivityScoped - * - * This factory allows us to provide the ViewModel instance from the Fragment directly - * bypassing the Singleton scope requirement - */ - companion object : MavericksViewModelFactory { + companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() - @JvmStatic - override fun create(viewModelContext: ViewModelContext, state: MessageComposerViewState): MessageComposerViewModel { - val fragment: RoomDetailFragment = (viewModelContext as FragmentViewModelContext).fragment() - return fragment.messageComposerViewModelFactory.create(state) - } - } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/VoiceMessagePlaybackTracker.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/VoiceMessagePlaybackTracker.kt index 2e8f6d9336..feadac6e1b 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/VoiceMessagePlaybackTracker.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/VoiceMessagePlaybackTracker.kt @@ -20,8 +20,9 @@ import android.os.Handler import android.os.Looper import dagger.hilt.android.scopes.ActivityScoped import javax.inject.Inject +import javax.inject.Singleton -@ActivityScoped +@Singleton class VoiceMessagePlaybackTracker @Inject constructor() { private val mainHandler = Handler(Looper.getMainLooper()) From bbb3a6139f1b63a9ff65b76d04d6ad3143009b88 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Wed, 24 Nov 2021 14:22:12 +0000 Subject: [PATCH 2/7] avoiding duplicated drag event updates --- .../home/room/detail/composer/voice/VoiceMessageRecorderView.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageRecorderView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageRecorderView.kt index 14d5a58279..2923af140d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageRecorderView.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageRecorderView.kt @@ -140,6 +140,7 @@ class VoiceMessageRecorderView @JvmOverloads constructor( } private fun onDrag(currentDragState: DraggingState, newDragState: DraggingState) { + if (currentDragState == newDragState) return when (newDragState) { is DraggingState.Cancelling -> voiceMessageViews.renderCancelling(newDragState.distanceX) is DraggingState.Locking -> { From 4a5e21ad2198e4c9cc1d448a2dc8c75f21b78d4f Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Wed, 24 Nov 2021 14:23:33 +0000 Subject: [PATCH 3/7] avoiding stopping any active recording if we're rotating - had to keep track of the recording start time in order to maintain the current length counter --- .../home/room/detail/RoomDetailFragment.kt | 18 ++++++---- .../composer/MessageComposerViewState.kt | 4 +-- .../voice/VoiceMessageRecorderView.kt | 34 +++++++++++-------- .../composer/voice/VoiceMessageViews.kt | 8 ++--- 4 files changed, 37 insertions(+), 27 deletions(-) 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 5f96eaf304..8b506e93c4 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 @@ -699,7 +699,7 @@ class RoomDetailFragment @Inject constructor( if (checkPermissions(PERMISSIONS_FOR_VOICE_MESSAGE, requireActivity(), permissionVoiceMessageLauncher)) { messageComposerViewModel.handle(MessageComposerAction.StartRecordingVoiceMessage) vibrate(requireContext()) - updateRecordingUiState(RecordingUiState.Started) + updateRecordingUiState(RecordingUiState.Started(System.currentTimeMillis())) } } @@ -713,7 +713,9 @@ class RoomDetailFragment @Inject constructor( } override fun onVoiceRecordingLocked() { - updateRecordingUiState(RecordingUiState.Locked) + val startedState = withState(messageComposerViewModel) { it.voiceRecordingUiState as? RecordingUiState.Started } + val startTime = startedState?.recordingStartTimestamp ?: System.currentTimeMillis() + updateRecordingUiState(RecordingUiState.Locked(startTime)) } override fun onVoiceRecordingEnded() { @@ -1131,11 +1133,15 @@ class RoomDetailFragment @Inject constructor( super.onPause() notificationDrawerManager.setCurrentRoom(null) voiceMessagePlaybackTracker.unTrack(VoiceMessagePlaybackTracker.RECORDING_ID) - messageComposerViewModel.handle(MessageComposerAction.SaveDraft(views.composerLayout.text.toString())) - // We should improve the UX to support going into playback mode when paused and delete the media when the view is destroyed. - messageComposerViewModel.handle(MessageComposerAction.EndAllVoiceActions(deleteRecord = false)) - views.voiceMessageRecorderView.render(RecordingUiState.None) + if (withState(messageComposerViewModel) { it.isVoiceRecording } && requireActivity().isChangingConfigurations) { + // we're rotating, maintain any active recordings + } else { + messageComposerViewModel.handle(MessageComposerAction.SaveDraft(views.composerLayout.text.toString())) + // We should improve the UX to support going into playback mode when paused and delete the media when the view is destroyed. + messageComposerViewModel.handle(MessageComposerAction.EndAllVoiceActions(deleteRecord = false)) + views.voiceMessageRecorderView.render(RecordingUiState.None) + } } private val attachmentFileActivityResultLauncher = registerStartForActivityResult { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewState.kt index 0df093c661..f9c32d3194 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewState.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewState.kt @@ -54,8 +54,8 @@ data class MessageComposerViewState( VoiceMessageRecorderView.RecordingUiState.None, VoiceMessageRecorderView.RecordingUiState.Cancelled, VoiceMessageRecorderView.RecordingUiState.Playback -> false - VoiceMessageRecorderView.RecordingUiState.Locked, - VoiceMessageRecorderView.RecordingUiState.Started -> true + is VoiceMessageRecorderView.RecordingUiState.Locked, + is VoiceMessageRecorderView.RecordingUiState.Started -> true } val isVoiceMessageIdle = !isVoiceRecording diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageRecorderView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageRecorderView.kt index 2923af140d..3b4c8a58c4 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageRecorderView.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageRecorderView.kt @@ -105,32 +105,35 @@ class VoiceMessageRecorderView @JvmOverloads constructor( fun render(recordingState: RecordingUiState) { if (lastKnownState == recordingState) return - lastKnownState = recordingState when (recordingState) { - RecordingUiState.None -> { + RecordingUiState.None -> { reset() } - RecordingUiState.Started -> { - startRecordingTicker() + is RecordingUiState.Started -> { + startRecordingTicker(startFromLocked = false, startAt = recordingState.recordingStartTimestamp) voiceMessageViews.renderToast(context.getString(R.string.voice_message_release_to_send_toast)) voiceMessageViews.showRecordingViews() dragState = DraggingState.Ready } - RecordingUiState.Cancelled -> { + RecordingUiState.Cancelled -> { reset() vibrate(context) } - RecordingUiState.Locked -> { + is RecordingUiState.Locked -> { + if (lastKnownState == null) { + startRecordingTicker(startFromLocked = true, startAt = recordingState.recordingStartTimestamp) + } voiceMessageViews.renderLocked() postDelayed({ voiceMessageViews.showRecordingLockedViews(recordingState) }, 500) } - RecordingUiState.Playback -> { + RecordingUiState.Playback -> { stopRecordingTicker() voiceMessageViews.showPlaybackViews() } } + lastKnownState = recordingState } private fun reset() { @@ -159,22 +162,23 @@ class VoiceMessageRecorderView @JvmOverloads constructor( dragState = newDragState } - private fun startRecordingTicker() { + private fun startRecordingTicker(startFromLocked: Boolean, startAt: Long) { + val startMs = ((System.currentTimeMillis() - startAt)).coerceAtLeast(0) recordingTicker?.stop() recordingTicker = CountUpTimer().apply { tickListener = object : CountUpTimer.TickListener { override fun onTick(milliseconds: Long) { - onRecordingTick(milliseconds) + val isLocked = startFromLocked || lastKnownState is RecordingUiState.Locked + onRecordingTick(isLocked, milliseconds + startMs) } } resume() } - onRecordingTick(0L) + onRecordingTick(startFromLocked, milliseconds = startMs) } - private fun onRecordingTick(milliseconds: Long) { - val currentState = lastKnownState ?: return - voiceMessageViews.renderRecordingTimer(currentState, milliseconds / 1_000) + private fun onRecordingTick(isLocked: Boolean, milliseconds: Long) { + voiceMessageViews.renderRecordingTimer(isLocked, milliseconds / 1_000) val timeDiffToRecordingLimit = BuildConfig.VOICE_MESSAGE_DURATION_LIMIT_MS - milliseconds if (timeDiffToRecordingLimit <= 0) { post { @@ -211,9 +215,9 @@ class VoiceMessageRecorderView @JvmOverloads constructor( sealed interface RecordingUiState { object None : RecordingUiState - object Started : RecordingUiState + data class Started(val recordingStartTimestamp: Long) : RecordingUiState object Cancelled : RecordingUiState - object Locked : RecordingUiState + data class Locked(val recordingStartTimestamp: Long) : RecordingUiState object Playback : RecordingUiState } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageViews.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageViews.kt index 32f21a3177..e138e14261 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageViews.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageViews.kt @@ -154,7 +154,7 @@ class VoiceMessageViews( fun hideRecordingViews(recordingState: RecordingUiState) { // We need to animate the lock image first - if (recordingState != RecordingUiState.Locked) { + if (recordingState !is RecordingUiState.Locked) { views.voiceMessageLockImage.isVisible = false views.voiceMessageLockImage.animate().translationY(0f).start() views.voiceMessageLockBackground.isVisible = false @@ -171,7 +171,7 @@ class VoiceMessageViews( views.voiceMessageTimerIndicator.isVisible = false views.voiceMessageTimer.isVisible = false - if (recordingState != RecordingUiState.Locked) { + if (recordingState !is RecordingUiState.Locked) { views.voiceMessageMicButton .animate() .scaleX(1f) @@ -304,9 +304,9 @@ class VoiceMessageViews( views.voiceMessageToast.isVisible = false } - fun renderRecordingTimer(recordingState: RecordingUiState, recordingTimeMillis: Long) { + fun renderRecordingTimer(isLocked: Boolean, recordingTimeMillis: Long) { val formattedTimerText = DateUtils.formatElapsedTime(recordingTimeMillis) - if (recordingState == RecordingUiState.Locked) { + if (isLocked) { views.voicePlaybackTime.apply { post { text = formattedTimerText From 5ed765999a3f075a2f7cb1f6c82485612c4bcdc6 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Wed, 24 Nov 2021 14:25:04 +0000 Subject: [PATCH 4/7] adding changelog entry --- changelog.d/4067.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/4067.bugfix diff --git a/changelog.d/4067.bugfix b/changelog.d/4067.bugfix new file mode 100644 index 0000000000..63d62df840 --- /dev/null +++ b/changelog.d/4067.bugfix @@ -0,0 +1 @@ +Allow voice messages to continue recording during device rotation \ No newline at end of file From 8fa264589a131aa1c7ea212ceef18abd8b4cbb0d Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Wed, 24 Nov 2021 15:18:39 +0000 Subject: [PATCH 5/7] removing unused imports --- .../home/room/detail/composer/MessageComposerViewModel.kt | 4 ---- .../detail/timeline/helper/VoiceMessagePlaybackTracker.kt | 1 - 2 files changed, 5 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt index c3c828252c..16a2d16a50 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt @@ -16,9 +16,7 @@ package im.vector.app.features.home.room.detail.composer -import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MavericksViewModelFactory -import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -32,7 +30,6 @@ import im.vector.app.features.attachments.toContentAttachmentData import im.vector.app.features.command.CommandParser import im.vector.app.features.command.ParsedCommand import im.vector.app.features.home.room.detail.ChatEffect -import im.vector.app.features.home.room.detail.RoomDetailFragment import im.vector.app.features.home.room.detail.composer.rainbow.RainbowGenerator import im.vector.app.features.home.room.detail.toMessageType import im.vector.app.features.powerlevel.PowerLevelsFlowFactory @@ -771,5 +768,4 @@ class MessageComposerViewModel @AssistedInject constructor( } companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() - } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/VoiceMessagePlaybackTracker.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/VoiceMessagePlaybackTracker.kt index feadac6e1b..86cc792e7b 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/VoiceMessagePlaybackTracker.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/VoiceMessagePlaybackTracker.kt @@ -18,7 +18,6 @@ package im.vector.app.features.home.room.detail.timeline.helper import android.os.Handler import android.os.Looper -import dagger.hilt.android.scopes.ActivityScoped import javax.inject.Inject import javax.inject.Singleton From 5d1812008d1ac1d81d6210c7ec8b1cf66f68d29a Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Wed, 24 Nov 2021 15:53:59 +0000 Subject: [PATCH 6/7] adding clock abstraction for avoiding directly using the System.currentTimeMillis --- .../im/vector/app/core/di/SingletonModule.kt | 5 ++++ .../java/im/vector/app/core/time/Clock.kt | 29 +++++++++++++++++++ .../home/room/detail/RoomDetailFragment.kt | 8 +++-- .../voice/VoiceMessageRecorderView.kt | 8 ++++- 4 files changed, 46 insertions(+), 4 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/core/time/Clock.kt diff --git a/vector/src/main/java/im/vector/app/core/di/SingletonModule.kt b/vector/src/main/java/im/vector/app/core/di/SingletonModule.kt index 429b88be69..350e1f6b7a 100644 --- a/vector/src/main/java/im/vector/app/core/di/SingletonModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/SingletonModule.kt @@ -29,6 +29,8 @@ import dagger.hilt.components.SingletonComponent import im.vector.app.core.dispatchers.CoroutineDispatchers import im.vector.app.core.error.DefaultErrorFormatter import im.vector.app.core.error.ErrorFormatter +import im.vector.app.core.time.Clock +import im.vector.app.core.time.DefaultClock import im.vector.app.features.invite.AutoAcceptInvites import im.vector.app.features.invite.CompileTimeAutoAcceptInvites import im.vector.app.features.navigation.DefaultNavigator @@ -66,6 +68,9 @@ abstract class VectorBindModule { @Binds abstract fun bindAutoAcceptInvites(autoAcceptInvites: CompileTimeAutoAcceptInvites): AutoAcceptInvites + + @Binds + abstract fun bindDefaultClock(clock: DefaultClock): Clock } @InstallIn(SingletonComponent::class) diff --git a/vector/src/main/java/im/vector/app/core/time/Clock.kt b/vector/src/main/java/im/vector/app/core/time/Clock.kt new file mode 100644 index 0000000000..ebde7f8fb4 --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/time/Clock.kt @@ -0,0 +1,29 @@ +/* + * 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.core.time + +import javax.inject.Inject + +interface Clock { + fun epochMillis(): Long +} + +class DefaultClock @Inject constructor() : Clock { + override fun epochMillis(): Long { + return System.currentTimeMillis() + } +} 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 8b506e93c4..08a2e6cd9c 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 @@ -87,6 +87,7 @@ import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.lifecycleAwareLazy import im.vector.app.core.platform.showOptimizedSnackbar import im.vector.app.core.resources.ColorProvider +import im.vector.app.core.time.Clock import im.vector.app.core.ui.views.CurrentCallsView import im.vector.app.core.ui.views.CurrentCallsViewPresenter import im.vector.app.core.ui.views.FailedMessagesWarningView @@ -250,7 +251,8 @@ class RoomDetailFragment @Inject constructor( private val roomDetailPendingActionStore: RoomDetailPendingActionStore, private val pillsPostProcessorFactory: PillsPostProcessor.Factory, private val callManager: WebRtcCallManager, - private val voiceMessagePlaybackTracker: VoiceMessagePlaybackTracker + private val voiceMessagePlaybackTracker: VoiceMessagePlaybackTracker, + private val clock: Clock ) : VectorBaseFragment(), TimelineEventController.Callback, @@ -699,7 +701,7 @@ class RoomDetailFragment @Inject constructor( if (checkPermissions(PERMISSIONS_FOR_VOICE_MESSAGE, requireActivity(), permissionVoiceMessageLauncher)) { messageComposerViewModel.handle(MessageComposerAction.StartRecordingVoiceMessage) vibrate(requireContext()) - updateRecordingUiState(RecordingUiState.Started(System.currentTimeMillis())) + updateRecordingUiState(RecordingUiState.Started(clock.epochMillis())) } } @@ -714,7 +716,7 @@ class RoomDetailFragment @Inject constructor( override fun onVoiceRecordingLocked() { val startedState = withState(messageComposerViewModel) { it.voiceRecordingUiState as? RecordingUiState.Started } - val startTime = startedState?.recordingStartTimestamp ?: System.currentTimeMillis() + val startTime = startedState?.recordingStartTimestamp ?: clock.epochMillis() updateRecordingUiState(RecordingUiState.Locked(startTime)) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageRecorderView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageRecorderView.kt index 3b4c8a58c4..0989337264 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageRecorderView.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageRecorderView.kt @@ -20,19 +20,23 @@ import android.content.Context import android.util.AttributeSet import android.view.View import androidx.constraintlayout.widget.ConstraintLayout +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.BuildConfig import im.vector.app.R import im.vector.app.core.extensions.exhaustive import im.vector.app.core.hardware.vibrate +import im.vector.app.core.time.Clock import im.vector.app.core.utils.CountUpTimer import im.vector.app.core.utils.DimensionConverter import im.vector.app.databinding.ViewVoiceMessageRecorderBinding import im.vector.app.features.home.room.detail.timeline.helper.VoiceMessagePlaybackTracker +import javax.inject.Inject import kotlin.math.floor /** * Encapsulates the voice message recording view and animations. */ +@AndroidEntryPoint class VoiceMessageRecorderView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, @@ -51,6 +55,8 @@ class VoiceMessageRecorderView @JvmOverloads constructor( fun onRecordingWaveformClicked() } + @Inject lateinit var clock: Clock + // We need to define views as lateinit var to be able to check if initialized for the bug fix on api 21 and 22. @Suppress("UNNECESSARY_LATEINIT") private lateinit var voiceMessageViews: VoiceMessageViews @@ -163,7 +169,7 @@ class VoiceMessageRecorderView @JvmOverloads constructor( } private fun startRecordingTicker(startFromLocked: Boolean, startAt: Long) { - val startMs = ((System.currentTimeMillis() - startAt)).coerceAtLeast(0) + val startMs = ((clock.epochMillis() - startAt)).coerceAtLeast(0) recordingTicker?.stop() recordingTicker = CountUpTimer().apply { tickListener = object : CountUpTimer.TickListener { From fdfac8d20acad014090c90413476e3d7328ce1c7 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Thu, 25 Nov 2021 10:03:09 +0000 Subject: [PATCH 7/7] adding doc for the default clock time provision --- vector/src/main/java/im/vector/app/core/time/Clock.kt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/vector/src/main/java/im/vector/app/core/time/Clock.kt b/vector/src/main/java/im/vector/app/core/time/Clock.kt index ebde7f8fb4..b7b6e88f8d 100644 --- a/vector/src/main/java/im/vector/app/core/time/Clock.kt +++ b/vector/src/main/java/im/vector/app/core/time/Clock.kt @@ -23,6 +23,13 @@ interface Clock { } class DefaultClock @Inject constructor() : Clock { + + /** + * Provides a UTC epoch in milliseconds + * + * This value is not guaranteed to be correct with reality + * as a User can override the system time and date to any values. + */ override fun epochMillis(): Long { return System.currentTimeMillis() }