-
Notifications
You must be signed in to change notification settings - Fork 739
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
Conversation
Unit Test Results110 files + 4 110 suites +4 1m 20s ⏱️ -13s 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.
♻️ This comment has been updated with latest results. |
…-files-player # Conflicts: # vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt
@daniellekirkwood @amshakal can I ask you guys to take a look at this new UI? |
Tagging @gaelledel who worked on voice messages UI. |
…-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
There was a problem hiding this 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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 ?: "" |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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)
}
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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()) { |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 🤷
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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" |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
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 |
There was a problem hiding this 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.
There was a problem hiding this 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
There was a problem hiding this 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 :)
There was a problem hiding this 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() |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this 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() | ||
} |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 --> |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added this to #5714
Type of change
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 itemMotivation and context
Closes #2525
Screenshots / GIFs
Tests
Audio Files
Tested devices
Checklist