diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 949f14f658..f3d14d9ecd 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -25,14 +25,10 @@ jobs:
cancel-in-progress: true
steps:
- uses: actions/checkout@v3
- - uses: actions/cache@v3
+ - name: Configure gradle
+ uses: gradle/gradle-build-action@v2
with:
- path: |
- ~/.gradle/caches
- ~/.gradle/wrapper
- key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
- restore-keys: |
- ${{ runner.os }}-gradle-
+ cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
- name: Assemble ${{ matrix.target }} debug apk
run: ./gradlew assemble${{ matrix.target }}KotlinCryptoDebug $CI_GRADLE_ARG_PROPERTIES
- name: Upload ${{ matrix.target }} debug APKs
@@ -50,14 +46,10 @@ jobs:
cancel-in-progress: ${{ github.ref != 'refs/head/main' }}
steps:
- uses: actions/checkout@v3
- - uses: actions/cache@v3
+ - name: Configure gradle
+ uses: gradle/gradle-build-action@v2
with:
- path: |
- ~/.gradle/caches
- ~/.gradle/wrapper
- key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
- restore-keys: |
- ${{ runner.os }}-gradle-
+ cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
- name: Assemble GPlay unsigned apk
run: ./gradlew clean assembleGplayKotlinCryptoRelease $CI_GRADLE_ARG_PROPERTIES
- name: Upload Gplay unsigned APKs
diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml
index fb06a2f647..62e169e49f 100644
--- a/.github/workflows/nightly.yml
+++ b/.github/workflows/nightly.yml
@@ -19,14 +19,10 @@ jobs:
uses: actions/setup-python@v4
with:
python-version: 3.8
- - uses: actions/cache@v3
+ - name: Configure gradle
+ uses: gradle/gradle-build-action@v2
with:
- path: |
- ~/.gradle/caches
- ~/.gradle/wrapper
- key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
- restore-keys: |
- ${{ runner.os }}-gradle-
+ cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
- name: Install towncrier
run: |
python3 -m pip install towncrier
diff --git a/.github/workflows/post-pr.yml b/.github/workflows/post-pr.yml
index af854bf371..0245fcdd34 100644
--- a/.github/workflows/post-pr.yml
+++ b/.github/workflows/post-pr.yml
@@ -44,14 +44,14 @@ jobs:
uses: actions/setup-python@v4
with:
python-version: 3.8
- - uses: actions/cache@v3
+ - uses: actions/setup-java@v3
with:
- path: |
- ~/.gradle/caches
- ~/.gradle/wrapper
- key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
- restore-keys: |
- ${{ runner.os }}-gradle-
+ distribution: 'adopt'
+ java-version: '11'
+ - name: Configure gradle
+ uses: gradle/gradle-build-action@v2
+ with:
+ cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
- name: Start synapse server
uses: michaelkaye/setup-matrix-synapse@v1.0.4
with:
@@ -59,10 +59,6 @@ jobs:
httpPort: 8080
disableRateLimiting: true
public_baseurl: "http://10.0.2.2:8080/"
- - uses: actions/setup-java@v3
- with:
- distribution: 'adopt'
- java-version: '11'
- name: Run sanity tests on API ${{ matrix.api-level }}
uses: reactivecircus/android-emulator-runner@v2
with:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 36f4c501bc..a430b3d4e5 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -139,14 +139,10 @@ jobs:
# with:
# distribution: 'adopt'
# java-version: 11
-# - uses: actions/cache@v3
+# - name: Configure gradle
+# uses: gradle/gradle-build-action@v2
# with:
-# path: |
-# ~/.gradle/caches
-# ~/.gradle/wrapper
-# key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
-# restore-keys: |
-# ${{ runner.os }}-gradle-
+# cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
# - name: Build Android Tests
# run: ./gradlew clean assembleAndroidTest $CI_GRADLE_ARG_PROPERTIES
diff --git a/build.gradle b/build.gradle
index 70d146e8e0..cde07bb717 100644
--- a/build.gradle
+++ b/build.gradle
@@ -25,7 +25,7 @@ buildscript {
classpath libs.gradle.kotlinPlugin
classpath libs.gradle.hiltPlugin
classpath 'com.google.firebase:firebase-appdistribution-gradle:3.1.1'
- classpath 'com.google.gms:google-services:4.3.14'
+ classpath 'com.google.gms:google-services:4.3.15'
classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.5.0.2730'
classpath 'com.google.android.gms:oss-licenses-plugin:0.10.6'
classpath "com.likethesalad.android:stem-plugin:2.3.0"
diff --git a/changelog.d/7807.misc b/changelog.d/7807.misc
new file mode 100644
index 0000000000..bc25a2a3d9
--- /dev/null
+++ b/changelog.d/7807.misc
@@ -0,0 +1 @@
+Support reactions on Voice Broadcast
diff --git a/changelog.d/7830.misc b/changelog.d/7830.misc
new file mode 100644
index 0000000000..51053ef05d
--- /dev/null
+++ b/changelog.d/7830.misc
@@ -0,0 +1 @@
+Pause voice broadcast listening on new VB recording
diff --git a/changelog.d/7895.bugfix b/changelog.d/7895.bugfix
new file mode 100644
index 0000000000..ccde7a554f
--- /dev/null
+++ b/changelog.d/7895.bugfix
@@ -0,0 +1 @@
+Send voice message should not be allowed during a voice broadcast recording
diff --git a/changelog.d/7929.misc b/changelog.d/7929.misc
new file mode 100644
index 0000000000..c0d32ad6b2
--- /dev/null
+++ b/changelog.d/7929.misc
@@ -0,0 +1 @@
+Tapping slightly left or right of the 30s buttons highlights the whole cell instead of registering as button presses
diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml
index e6556520cf..e156e55f11 100644
--- a/library/ui-strings/src/main/res/values/strings.xml
+++ b/library/ui-strings/src/main/res/values/strings.xml
@@ -3108,6 +3108,8 @@
Cannot play this voice message
Cannot record a voice message
Cannot reply or edit while voice message is active
+ Cannot start voice message
+ You can’t start a voice message as you are currently recording a live broadcast. Please end your live broadcast in order to start recording a voice message
Voice Message (%1$s)
%1$s, %2$s, %3$s
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 c1e201cfc4..13f8997452 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
@@ -151,6 +151,7 @@ class DefaultErrorFormatter @Inject constructor(
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)
+ is VoiceFailure.VoiceBroadcastInProgress -> stringProvider.getString(R.string.error_voice_message_broadcast_in_progress)
}
}
diff --git a/vector/src/main/java/im/vector/app/core/extensions/TimelineEvent.kt b/vector/src/main/java/im/vector/app/core/extensions/TimelineEvent.kt
index 89bd28fc93..16e8405887 100644
--- a/vector/src/main/java/im/vector/app/core/extensions/TimelineEvent.kt
+++ b/vector/src/main/java/im/vector/app/core/extensions/TimelineEvent.kt
@@ -18,6 +18,8 @@ package im.vector.app.core.extensions
import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants
import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent
+import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
+import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
@@ -26,8 +28,9 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent
fun TimelineEvent.canReact(): Boolean {
- // Only event of type EventType.MESSAGE, EventType.STICKER and EventType.POLL_START are supported for the moment
- return root.getClearType() in listOf(EventType.MESSAGE, EventType.STICKER) + EventType.POLL_START.values + EventType.POLL_END.values &&
+ // Only event of type EventType.MESSAGE, EventType.STICKER and EventType.POLL_START, and started voice broadcast are supported for the moment
+ return (root.getClearType() in listOf(EventType.MESSAGE, EventType.STICKER) + EventType.POLL_START.values + EventType.POLL_END.values ||
+ root.asVoiceBroadcastEvent()?.content?.voiceBroadcastState == VoiceBroadcastState.STARTED) &&
root.sendState == SendState.SYNCED &&
!root.isRedacted()
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt
index 43b22f9b64..c2755e58a3 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt
@@ -627,13 +627,17 @@ class TimelineViewModel @AssistedInject constructor(
viewModelScope.launch {
when (action) {
VoiceBroadcastAction.Recording.Start -> {
+ voiceBroadcastHelper.pausePlayback()
voiceBroadcastHelper.startVoiceBroadcast(room.roomId).fold(
{ _viewEvents.post(RoomDetailViewEvents.ActionSuccess(action)) },
{ _viewEvents.post(RoomDetailViewEvents.ActionFailure(action, it)) },
)
}
VoiceBroadcastAction.Recording.Pause -> voiceBroadcastHelper.pauseVoiceBroadcast(room.roomId)
- VoiceBroadcastAction.Recording.Resume -> voiceBroadcastHelper.resumeVoiceBroadcast(room.roomId)
+ VoiceBroadcastAction.Recording.Resume -> {
+ voiceBroadcastHelper.pausePlayback()
+ voiceBroadcastHelper.resumeVoiceBroadcast(room.roomId)
+ }
VoiceBroadcastAction.Recording.Stop -> _viewEvents.post(RoomDetailViewEvents.DisplayPromptToStopVoiceBroadcast)
VoiceBroadcastAction.Recording.StopConfirmed -> voiceBroadcastHelper.stopVoiceBroadcast(room.roomId)
is VoiceBroadcastAction.Listening.PlayOrResume -> voiceBroadcastHelper.playOrResumePlayback(action.voiceBroadcast)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt
index 4849e20b6d..28c8757e6c 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt
@@ -191,6 +191,8 @@ class MessageComposerFragment : VectorBaseFragment(), A
is MessageComposerViewEvents.VoicePlaybackOrRecordingFailure -> {
if (it.throwable is VoiceFailure.UnableToRecord) {
onCannotRecord()
+ } else if (it.throwable is VoiceFailure.VoiceBroadcastInProgress) {
+ displayErrorVoiceBroadcastInProgress()
}
showErrorInSnackbar(it.throwable)
}
@@ -526,6 +528,14 @@ class MessageComposerFragment : VectorBaseFragment(), A
messageComposerViewModel.handle(MessageComposerAction.OnVoiceRecordingUiStateChanged(VoiceMessageRecorderView.RecordingUiState.Idle))
}
+ private fun displayErrorVoiceBroadcastInProgress() {
+ MaterialAlertDialogBuilder(requireActivity())
+ .setTitle(R.string.error_voice_message_broadcast_in_progress)
+ .setMessage(getString(R.string.error_voice_message_broadcast_in_progress_message))
+ .setPositiveButton(android.R.string.ok, null)
+ .show()
+ }
+
private fun handleJoinedToAnotherRoom(action: MessageComposerViewEvents.JoinRoomCommandSuccess) {
composer.setTextIfDifferent("")
lockSendButton = false
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 56ee9ffb5a..fc79c069fe 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
@@ -19,6 +19,7 @@ package im.vector.app.features.home.room.detail.composer
import android.text.SpannableString
import androidx.lifecycle.asFlow
import com.airbnb.mvrx.MavericksViewModelFactory
+import com.airbnb.mvrx.withState
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@@ -28,6 +29,7 @@ import im.vector.app.core.di.hiltMavericksViewModelFactory
import im.vector.app.core.extensions.getVectorLastMessageContent
import im.vector.app.core.platform.VectorViewModel
import im.vector.app.core.resources.StringProvider
+import im.vector.app.core.time.Clock
import im.vector.app.features.analytics.AnalyticsTracker
import im.vector.app.features.analytics.extensions.toAnalyticsComposer
import im.vector.app.features.analytics.extensions.toAnalyticsJoinedRoom
@@ -42,12 +44,19 @@ import im.vector.app.features.home.room.detail.toMessageType
import im.vector.app.features.powerlevel.PowerLevelsFlowFactory
import im.vector.app.features.session.coroutineScope
import im.vector.app.features.settings.VectorPreferences
+import im.vector.app.features.voice.VoiceFailure
import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants
import im.vector.app.features.voicebroadcast.VoiceBroadcastHelper
+import im.vector.app.features.voicebroadcast.model.VoiceBroadcast
+import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
+import im.vector.app.features.voicebroadcast.usecase.GetVoiceBroadcastStateEventLiveUseCase
+import im.vector.app.features.voicebroadcast.voiceBroadcastId
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.mapNotNull
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.query.QueryStringValue
import org.matrix.android.sdk.api.session.Session
@@ -74,6 +83,7 @@ import org.matrix.android.sdk.api.session.room.send.UserDraft
import org.matrix.android.sdk.api.session.room.timeline.getRelationContent
import org.matrix.android.sdk.api.session.room.timeline.getTextEditableContent
import org.matrix.android.sdk.api.session.space.CreateSpaceParams
+import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.flow.flow
import org.matrix.android.sdk.flow.unwrap
import timber.log.Timber
@@ -88,6 +98,8 @@ class MessageComposerViewModel @AssistedInject constructor(
private val audioMessageHelper: AudioMessageHelper,
private val analyticsTracker: AnalyticsTracker,
private val voiceBroadcastHelper: VoiceBroadcastHelper,
+ private val clock: Clock,
+ private val getVoiceBroadcastStateEventLiveUseCase: GetVoiceBroadcastStateEventLiveUseCase,
) : VectorViewModel(initialState) {
private val room = session.getRoom(initialState.roomId)
@@ -203,8 +215,11 @@ class MessageComposerViewModel @AssistedInject constructor(
private fun observeVoiceBroadcast(room: Room) {
room.stateService().getStateEventLive(VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, QueryStringValue.Equals(session.myUserId))
.asFlow()
- .unwrap()
- .mapNotNull { it.asVoiceBroadcastEvent()?.content?.voiceBroadcastState }
+ .map { it.getOrNull()?.asVoiceBroadcastEvent()?.voiceBroadcastId }
+ .flatMapLatest { voiceBroadcastId ->
+ voiceBroadcastId?.let { getVoiceBroadcastStateEventLiveUseCase.execute(VoiceBroadcast(it, room.roomId)) } ?: flowOf(Optional.empty())
+ }
+ .map { it.getOrNull()?.content?.voiceBroadcastState }
.setOnEach {
copy(voiceBroadcastState = it)
}
@@ -916,10 +931,16 @@ class MessageComposerViewModel @AssistedInject constructor(
}
private fun handleStartRecordingVoiceMessage(room: Room) {
- try {
- audioMessageHelper.startRecording(room.roomId)
- } catch (failure: Throwable) {
- _viewEvents.post(MessageComposerViewEvents.VoicePlaybackOrRecordingFailure(failure))
+ val voiceBroadcastState = withState(this) { it.voiceBroadcastState }
+ if (voiceBroadcastState != null && voiceBroadcastState != VoiceBroadcastState.STOPPED) {
+ _viewEvents.post(MessageComposerViewEvents.VoicePlaybackOrRecordingFailure(VoiceFailure.VoiceBroadcastInProgress))
+ } else {
+ try {
+ audioMessageHelper.startRecording(room.roomId)
+ setState { copy(voiceRecordingUiState = VoiceMessageRecorderView.RecordingUiState.Recording(clock.epochMillis())) }
+ } catch (failure: Throwable) {
+ _viewEvents.post(MessageComposerViewEvents.VoicePlaybackOrRecordingFailure(failure))
+ }
}
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceRecorderFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceRecorderFragment.kt
index 25764f3654..90b813d347 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceRecorderFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceRecorderFragment.kt
@@ -125,7 +125,6 @@ class VoiceRecorderFragment : VectorBaseFragment()
if (checkPermissions(PERMISSIONS_FOR_VOICE_MESSAGE, requireActivity(), permissionVoiceMessageLauncher)) {
messageComposerViewModel.handle(MessageComposerAction.StartRecordingVoiceMessage)
vibrate(requireContext())
- updateRecordingUiState(VoiceMessageRecorderView.RecordingUiState.Recording(clock.epochMillis()))
}
}
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
index 9c4b345dc4..0a837581fd 100644
--- a/vector/src/main/java/im/vector/app/features/voice/VoiceFailure.kt
+++ b/vector/src/main/java/im/vector/app/features/voice/VoiceFailure.kt
@@ -19,4 +19,5 @@ 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)
+ object VoiceBroadcastInProgress : VoiceFailure()
}
diff --git a/vector/src/main/res/layout/item_timeline_event_voice_broadcast_listening_stub.xml b/vector/src/main/res/layout/item_timeline_event_voice_broadcast_listening_stub.xml
index deec85e2ed..98a9ccaa2d 100644
--- a/vector/src/main/res/layout/item_timeline_event_voice_broadcast_listening_stub.xml
+++ b/vector/src/main/res/layout/item_timeline_event_voice_broadcast_listening_stub.xml
@@ -105,8 +105,8 @@