Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Audio files in the timeline now appear with the audio player #5586

Merged
merged 30 commits into from
Apr 7, 2022

Conversation

ericdecanini
Copy link
Contributor

@ericdecanini ericdecanini commented Mar 19, 2022

Type of change

  • Feature
  • Bugfix
  • Technical
  • Other :

Content

Instead of appearing as a simple downloadable file, audio files uploaded in the timeline will now be displayed with the audio player so they are playable from the timeline

I took this opportunity to improve our UX with the way the timeline handles playing multiple audio files (by that I mean playing one while another one is playing so the other pauses, etc)

The seekbar is a little fiddly though. Clicking on it works nicely to seek and so does swiping right, but swiping left doesn't work (not before already being in a seeking state) because it conflicts with the ItemTouchHelper of the recycler view which already has a swipe left action being handled. Due to this being on the level of the recycler view, I couldn't find a way to handle this from within the context of the audio item

Motivation and context

Closes #2525

Screenshots / GIFs

Light Mode Dark Mode
image image

Tests

Audio Files

  • Upload an audio file in the timeline
  • See that it displays differently from a regular file attachment and plays the audio
  • See that the seekbar works
  • Try uploading a second audio file and see that they interact nicely with each other
  • Try backgrounding and foregrounding the app and see that no strange behaviour is found

Tested devices

  • Physical
  • Emulator
  • OS version(s): Android 10, 12

Checklist

@github-actions
Copy link

github-actions bot commented Mar 19, 2022

Unit Test Results

110 files  +  4  110 suites  +4   1m 20s ⏱️ -13s
195 tests +  7  195 ✔️ +  7  0 💤 ±0  0 ±0 
650 runs  +28  650 ✔️ +28  0 💤 ±0  0 ±0 

Results for commit d9f2033. ± Comparison against base commit 1097436.

This pull request removes 2 and adds 9 tests. Note that renamed tests count towards both.
im.vector.app.features.onboarding.OnboardingViewModelTest ‑ given registration has started and has dummy step to do, when handling action, then ignores other steps and executes dummy
im.vector.app.features.onboarding.OnboardingViewModelTest ‑ when registering account, then updates state and emits account created event
im.vector.app.features.onboarding.DirectLoginUseCaseTest ‑ given direct authentication throws, when logging in directly, then returns failure result with original cause
im.vector.app.features.onboarding.DirectLoginUseCaseTest ‑ given wellknown fails with content, when logging in directly, then returns success with direct session result
im.vector.app.features.onboarding.DirectLoginUseCaseTest ‑ given wellknown fails without content, when logging in directly, then returns well known error
im.vector.app.features.onboarding.DirectLoginUseCaseTest ‑ given wellknown throws, when logging in directly, then returns failure result with original cause
im.vector.app.features.onboarding.DirectLoginUseCaseTest ‑ when logging in directly, then returns success with direct session result
im.vector.app.features.onboarding.OnboardingViewModelTest ‑ given has sign in with matrix id sign mode, when handling login or register action fails, then emits error
im.vector.app.features.onboarding.OnboardingViewModelTest ‑ given has sign in with matrix id sign mode, when handling login or register action, then logs in directly
im.vector.app.features.onboarding.OnboardingViewModelTest ‑ given personalisation enabled and registration has started and has dummy step to do, when handling action, then ignores other steps and executes dummy
im.vector.app.features.onboarding.OnboardingViewModelTest ‑ given personalisation enabled, when registering account, then updates state and emits account created event

♻️ This comment has been updated with latest results.

@ericdecanini ericdecanini marked this pull request as ready for review March 21, 2022 19:05
@ericdecanini ericdecanini requested review from a team and mnaturel and removed request for a team March 21, 2022 19:06
@ericdecanini
Copy link
Contributor Author

@daniellekirkwood @amshakal can I ask you guys to take a look at this new UI?

@ericdecanini ericdecanini added the X-Needs-Design May require input from the design team label Mar 22, 2022
@amshakal
Copy link

Tagging @gaelledel who worked on voice messages UI.

@amshakal amshakal requested a review from gaelledel March 24, 2022 13:06
…-files-player

# Conflicts:
#	vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageHelper.kt
#	vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageViews.kt
#	vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt
#	vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt
…-files-player

# Conflicts:
#	vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt
Copy link
Contributor

@mnaturel mnaturel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good work! I have only small remarks. When testing, I have seen something strange. I play an audio file in a room. Then I press back which leads me to previous screen. It pauses the audio but does not reset it to the start. It may appear strange to users since they have no way to restart at the beginning of the audio except restarting the app. It may be a side effect of the bug you fixed when going in background.

I have also a question about UI: Could we indicate somewhere near the filename the total duration of the audio track? When we start playing we loose that information and since there is currently no seekbar we don't really know where we are in the audio track.

@@ -0,0 +1 @@
Adds the ability for audio attachments to be played in the timeline
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we use the Github issue number instead for the Changelog entry? I know there were some discussions about it, but I don't know what was decided. I think all the team should use use the same pattern: either Github issue number or PR number.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I learned about this process, I heard it's very ambiguous and we go either way, but I agree that one way or the other we should choose one and be consistent with it!

val nonFormattedBody = when (messageContent) {
is MessageAudioContent -> getAudioContentBodyText(messageContent)
is MessagePollContent -> messageContent.getBestPollCreationInfo()?.question?.getBestQuestion()
else -> messageContent?.body ?: ""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is the orEmpty extension we can use here to replace Elvis operator. I found it easier to read:
messageContent?.body.orEmpty(). What do you think?

informationData: MessageInformationData,
highlight: Boolean,
attributes: AbsMessageItem.Attributes,
) = if (messageContent.voiceMessageIndicator != null) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we could write the if/else in reverse to avoid negative condition which tends to be more difficult to read? Like this:

if (messageContent.voiceMessageIndicator == null) {
    buildAudioMessageItem(params, messageContent, informationData, highlight, attributes)
} else {
    buildVoiceMessageItem(params, messageContent, informationData, highlight, attributes)
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can see the Uncle Bob here ✨ Clean Code never mentions this in the case of null checks (and I can't find anything online about it), but imo I think != null is actually positive, because it's akin to "if this data exists"

messageContent.encryptedFileInfo?.toElementToDecrypt())
)
.audioMessagePlaybackTracker(audioMessagePlaybackTracker)
.isLocalFile(localFilesHelper.isLocalFile(fileUrl))
.mxcUrl(fileUrl)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see we no more set the isDownloaded attribute. Was it intended? I am wondering since it still exists in the MessageVoiceItem class. And if it was not intended, should we have the same attribute in MessageAudioItem?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good spot! isDownloaded in MessageVoiceItem isn't actually being used. I'll push changes to remove it from that class.

It's used only in MessageFileItem to determine the icon that's shown, but in the case of voice and audio items this is replaced by the play button

}

private fun bindUploadState(holder: Holder) {
if (!attributes.informationData.sendState.hasFailed()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same remark about the condition, it is better to use positive condition when possible, in that case, we would have:

if (attributes.informationData.sendState.hasFailed()) {
    holder.audioPlaybackControlButton.setImageResource(R.drawable.ic_cross)
    holder.audioPlaybackControlButton.contentDescription =
    holder.view.context.getString(R.string.error_audio_message_unable_to_play, filename)
    holder.progressLayout.isVisible = false
} else {
     contentUploadStateTrackerBinder.bind(attributes.informationData.eventId, isLocalFile, holder.progressLayout)
}

@@ -55,10 +55,12 @@ abstract class MessageVoiceItem : AbsMessageItem<MessageVoiceItem.Holder>() {
var waveform: List<Int> = emptyList()

@EpoxyAttribute
var izLocalFile = false
@JvmField
var isLocalFile = false
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we really keep these fields which you annotated with @JvmFields. We could use private const instead or directly the boolean value where it is called. Same remark for the field isLocalFile in MessageAudioItem. What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These can't be const as they're set via epoxy, but the reason for adding @JvmField here is Epoxy returns a compile error without it.

What I find weird is that this only happened after renaming it to fix the spelling. The reasoning for that is something that completely baffles me 🤷

Copy link
Contributor

@mnaturel mnaturel Mar 29, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. Here is the explanation: airbnb/epoxy#459. The question is now, should we rename the fields with the @JvmField annotation or keep using the iz prefix. It seems iz prefix is used for booleans in Epoxy model in the project. @bmarty Can you confirm?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh this explains it! Imo it is pretty ugly to mispell our own variables just to avoid this. @JvmField isn't great but I think it's a better compromise

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. I prefer good naming as well.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK

@@ -0,0 +1,69 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
Copy link
Contributor

@mnaturel mnaturel Mar 28, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't we reuse the existing item_timeline_event_voice_stub or are there any differences?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pretty major differences. This stub is the screenshot you see above in the pr description

@ericdecanini
Copy link
Contributor Author

Good work! I have only small remarks. When testing, I have seen something strange. I play an audio file in a room. Then I press back which leads me to previous screen. It pauses the audio but does not reset it to the start. It may appear strange to users since they have no way to restart at the beginning of the audio except restarting the app. It may be a side effect of the bug you fixed when going in background.

I have also a question about UI: Could we indicate somewhere near the filename the total duration of the audio track? When we start playing we loose that information and since there is currently no seekbar we don't really know where we are in the audio track.

Good catch with that bug! It should be an easy fix so I'll push that in a bit.

With that UI suggestion, that is a good point. I would like to know what design thinks about it

@ericdecanini ericdecanini requested a review from mnaturel March 29, 2022 17:56
Copy link
Contributor

@mnaturel mnaturel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good for me. I will let you see with design team if you want to add total duration of the audio track somewhere near the title.

Copy link

@gaelledel gaelledel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool! Just need to follow the specs for audio files available in compound here https://www.figma.com/file/X4XTH9iS2KGJ2wFKDqkyed/Compound?node-id=2039%3A26421

@ericdecanini ericdecanini removed the X-Needs-Design May require input from the design team label Apr 1, 2022
Copy link

@gaelledel gaelledel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes except we need to remove the underline from the file title please :)

Copy link
Contributor

@mnaturel mnaturel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work! We now have a real audio player, hope it will be appreciated by users! I have just a question about the place of some ViewActions.

@@ -42,4 +42,5 @@ sealed class MessageComposerAction : VectorViewModelAction {
data class EndAllVoiceActions(val deleteRecord: Boolean = true) : MessageComposerAction()
data class VoiceWaveformTouchedUp(val eventId: String, val duration: Int, val percentage: Float) : MessageComposerAction()
data class VoiceWaveformMovedTo(val eventId: String, val duration: Int, val percentage: Float) : MessageComposerAction()
data class AudioSeekBarMovedTo(val eventId: String, val duration: Int, val percentage: Float) : MessageComposerAction()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am wondering if this is really an action of the MessageComposerView? Does it handle the move of the seek bar inside the timeline or when composing a new message? The question is the same for VoiceWaveformTouchedUp and VoiceWaveformMovedTo. And maybe others but I don't have all the understanding of the feature.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You make a strong point! It doesn't sound like this and some of the other voice actinos like the ones you mentioned and ones concerning play/pause functionality should belong here. Perhaps I could work it in another PR to separate these out a little bit

@ericdecanini ericdecanini merged commit 45104f8 into develop Apr 7, 2022
@ericdecanini ericdecanini deleted the feature/eric/audio-files-player branch April 7, 2022 09:25
Copy link
Member

@bmarty bmarty left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My 2 cents

mediaPlayer?.pause()
playbackTracker.updatePausedAtPlaybackTime(id, toMillisecond, percentage)
stopPlaybackTicker()
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👀 thanks!

@@ -55,10 +55,12 @@ abstract class MessageVoiceItem : AbsMessageItem<MessageVoiceItem.Holder>() {
var waveform: List<Int> = emptyList()

@EpoxyAttribute
var izLocalFile = false
@JvmField
var isLocalFile = false
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK

@@ -2860,6 +2860,14 @@
<string name="error_voice_message_cannot_reply_or_edit">Cannot reply or edit while voice message is active</string>
<string name="voice_message_reply_content">Voice Message (%1$s)</string>

<string name="a11y_audio_message_item">%1$s, %2$s, %3$s</string> <!-- filename, duration, file size -->
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comments have to be above the string for Weblate to handle it properly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added this to #5714

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add an embedded player for Audio files
5 participants