Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Added support for custom fonts
- Added "Copy number to clipboard" option inside chat overflow menu ([#651])
- Added inline audio player for MMS attachments

### Changed
- Improved multi-message copy formatting with timestamps and sender names
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import org.fossify.commons.activities.BaseSimpleActivity
import org.fossify.commons.extensions.*
import org.fossify.messages.R
import org.fossify.messages.activities.VCardViewerActivity
import org.fossify.messages.databinding.ItemAttachmentAudioPreviewBinding
import org.fossify.messages.databinding.ItemAttachmentDocumentPreviewBinding
import org.fossify.messages.databinding.ItemAttachmentMediaPreviewBinding
import org.fossify.messages.databinding.ItemAttachmentVcardPreviewBinding
Expand Down Expand Up @@ -53,6 +54,7 @@ class AttachmentsAdapter(
ATTACHMENT_DOCUMENT -> ItemAttachmentDocumentPreviewBinding.inflate(inflater, parent, false)
ATTACHMENT_VCARD -> ItemAttachmentVcardPreviewBinding.inflate(inflater, parent, false)
ATTACHMENT_MEDIA -> ItemAttachmentMediaPreviewBinding.inflate(inflater, parent, false)
ATTACHMENT_AUDIO -> ItemAttachmentAudioPreviewBinding.inflate(inflater, parent, false)
else -> throw IllegalArgumentException("Unknown view type: $viewType")
}

Expand Down Expand Up @@ -91,6 +93,13 @@ class AttachmentsAdapter(
binding = binding as ItemAttachmentMediaPreviewBinding,
attachment = attachment
)

ATTACHMENT_AUDIO -> {
(binding as ItemAttachmentAudioPreviewBinding).setupAudioPreview(
uri = attachment.uri,
onRemoveButtonClicked = { removeAttachment(attachment) }
)
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import org.fossify.commons.extensions.copyToClipboard
import org.fossify.commons.extensions.formatDateOrTime
import org.fossify.commons.extensions.getContrastColor
import org.fossify.commons.extensions.getProperPrimaryColor
import org.fossify.commons.extensions.getProperTextColor
import org.fossify.commons.extensions.getTextSize
import org.fossify.commons.extensions.getTimeFormat
import org.fossify.commons.extensions.shareTextIntent
Expand All @@ -53,6 +54,7 @@ import org.fossify.messages.activities.ThreadActivity
import org.fossify.messages.activities.VCardViewerActivity
import org.fossify.messages.databinding.ItemAttachmentDocumentBinding
import org.fossify.messages.databinding.ItemAttachmentImageBinding
import org.fossify.messages.databinding.ItemAttachmentAudioBinding
import org.fossify.messages.databinding.ItemAttachmentVcardBinding
import org.fossify.messages.databinding.ItemMessageBinding
import org.fossify.messages.databinding.ItemThreadDateTimeBinding
Expand All @@ -65,6 +67,7 @@ import org.fossify.messages.dialogs.SelectTextDialog
import org.fossify.messages.extensions.config
import org.fossify.messages.extensions.getContactFromAddress
import org.fossify.messages.extensions.isImageMimeType
import org.fossify.messages.extensions.isPlayableAudioMimeType
import org.fossify.messages.extensions.isVCardMimeType
import org.fossify.messages.extensions.isVideoMimeType
import org.fossify.messages.extensions.launchViewIntent
Expand All @@ -78,6 +81,8 @@ import org.fossify.messages.helpers.THREAD_SENT_MESSAGE_ERROR
import org.fossify.messages.helpers.THREAD_SENT_MESSAGE_SENDING
import org.fossify.messages.helpers.THREAD_SENT_MESSAGE_SENT
import org.fossify.messages.helpers.generateStableId
import org.fossify.messages.helpers.setupAudio
import org.fossify.messages.helpers.setupAudioPreview
import org.fossify.messages.helpers.setupDocumentPreview
import org.fossify.messages.helpers.setupVCardPreview
import org.fossify.messages.models.Attachment
Expand Down Expand Up @@ -393,6 +398,8 @@ class ThreadAdapter(
val mimetype = attachment.mimetype
when {
mimetype.isImageMimeType() || mimetype.isVideoMimeType() -> setupImageView(holder, binding = this, message, attachment)
mimetype.isPlayableAudioMimeType() ->
setupAudioView(binding = this, attachment)
mimetype.isVCardMimeType() -> setupVCardView(holder, threadMessageAttachmentsHolder, message, attachment)
else -> setupFileView(holder, threadMessageAttachmentsHolder, message, attachment)
}
Expand Down Expand Up @@ -538,6 +545,16 @@ class ThreadAdapter(
}
}

private fun setupAudioView(binding: ItemMessageBinding, attachment: Attachment) = binding.apply {
val uri = attachment.getUri()

val audioView = ItemAttachmentAudioBinding.inflate(layoutInflater).apply {
setupAudio(uri = uri)
}.root

threadMessageAttachmentsHolder.addView(audioView)
}

private fun setupVCardView(holder: ViewHolder, parent: LinearLayout, message: Message, attachment: Attachment) {
val uri = attachment.getUri()
val vCardView = ItemAttachmentVcardBinding.inflate(layoutInflater).apply {
Expand Down
32 changes: 28 additions & 4 deletions app/src/main/kotlin/org/fossify/messages/extensions/String.kt
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,34 @@ fun String.isAudioMimeType(): Boolean {
return lowercase().startsWith("audio")
}

private val playableAudioMimeTypes = setOf(
"audio/aac",
"audio/mp4",
"audio/mp4a-latm",
"audio/mpeg",
"audio/3gpp",
"audio/3gpp2",
"audio/amr",
"audio/amr-wb",
"audio/flac",
"audio/ogg",
"audio/opus",
"audio/wav",
"audio/x-wav",
"audio/midi",
"audio/x-midi",
"audio/vorbis",
"audio/raw",
"audio/x-m4a",
"audio/m4a",
"audio/x-aac",
"audio/aac-adts",
)

fun String.isPlayableAudioMimeType(): Boolean {
return lowercase() in playableAudioMimeTypes
}

fun String.isCalendarMimeType(): Boolean {
return lowercase().endsWith("calendar")
}
Expand All @@ -40,10 +68,6 @@ fun String.isPdfMimeType(): Boolean {
return lowercase().endsWith("pdf")
}

fun String.isZipMimeType(): Boolean {
return lowercase().endsWith("zip")
}

fun String.isPlainTextMimeType(): Boolean {
return lowercase() == "text/plain"
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@ package org.fossify.messages.helpers
import android.app.Activity
import android.net.Uri
import android.view.View
import android.widget.SeekBar
import org.fossify.commons.extensions.*
import org.fossify.commons.helpers.SimpleContactsHelper
import org.fossify.commons.helpers.ensureBackgroundThread
import org.fossify.messages.R
import org.fossify.messages.databinding.ItemAttachmentAudioBinding
import org.fossify.messages.databinding.ItemAttachmentAudioPreviewBinding
import org.fossify.messages.databinding.ItemAttachmentDocumentBinding
import org.fossify.messages.databinding.ItemAttachmentDocumentPreviewBinding
import org.fossify.messages.databinding.ItemAttachmentVcardBinding
Expand Down Expand Up @@ -79,6 +82,113 @@ fun ItemAttachmentDocumentBinding.setupDocumentPreview(
}
}

fun ItemAttachmentAudioPreviewBinding.setupAudioPreview(
uri: Uri,
onRemoveButtonClicked: (() -> Unit)? = null,
) {
audioAttachmentHolder.setupAudio(uri)
removeAttachmentButtonHolder.removeAttachmentButton.apply {
beVisible()
background.applyColorFilter(context.getProperPrimaryColor())
if (onRemoveButtonClicked != null) {
setOnClickListener {
onRemoveButtonClicked.invoke()
}
}
}
}

fun ItemAttachmentAudioBinding.setupAudio(
uri: Uri,
) {
val context = root.context

val textColor = context.getProperTextColor()
val primaryColor = context.getProperPrimaryColor()
var playIcon = org.fossify.commons.R.drawable.ic_play_vector
var pauseIcon = org.fossify.commons.R.drawable.ic_pause_vector

duration.setTextColor(textColor)
playButton.apply {
background.setTint(primaryColor)
contentDescription = context.getString(R.string.play)
}
progressBar.progressDrawable.setTint(primaryColor)
progressBar.thumb.setTint(primaryColor)

root.background.applyColorFilter(primaryColor.darkenColor())

AudioPlayerManager.getDurationMs(uri, context) { durationMs ->
duration.text = formatMs(durationMs.toInt())
}

val audioListener = object : AudioPlayerManager.AudioPlayerListener {
override fun onPlaybackStateChanged(isPlaying: Boolean) {
playButton.apply {
setImageResource(if (isPlaying) pauseIcon else playIcon)
contentDescription = context.getString(if (isPlaying) R.string.pause else R.string.play)
}
}

override fun onProgressUpdated(positionMs: Int, durationMs: Int) {
progressBar.max = durationMs
progressBar.progress = positionMs
duration.text = "${formatMs(positionMs)}"
}

override fun onPlaybackCompleted() {
playButton.apply {
setImageResource(playIcon)
contentDescription = context.getString(R.string.play)
}
progressBar.progress = 0
}

override fun onPlaybackError() {
playButton.apply {
setImageResource(playIcon)
contentDescription = context.getString(R.string.play)
}
}
}

playButton.setOnClickListener {
AudioPlayerManager.togglePlay(uri, context, audioListener)
}

progressBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
if (fromUser && AudioPlayerManager.isActive(audioListener)) {
AudioPlayerManager.seekTo(progress)
}
}

override fun onStartTrackingTouch(seekBar: SeekBar?) {
// Not required for this implementation
}

override fun onStopTrackingTouch(seekBar: SeekBar?) {
if (AudioPlayerManager.isActive(audioListener)) {
AudioPlayerManager.seekTo(progressBar.progress)
}
}
})

progressBar.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
val minBarWidth = context.resources.getDimensionPixelSize(R.dimen.audio_seekbar_min_width)
progressBar.beVisibleIf(progressBar.width >= minBarWidth)
}

root.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
override fun onViewAttachedToWindow(v: View) {
// Not required for this implementation
}
override fun onViewDetachedFromWindow(v: View) {
AudioPlayerManager.release(audioListener)
}
})
}

fun ItemAttachmentVcardPreviewBinding.setupVCardPreview(
activity: Activity,
uri: Uri,
Expand Down Expand Up @@ -169,10 +279,19 @@ fun ItemAttachmentVcardBinding.setupVCardPreview(
}
}

private const val SECONDS_PER_MINUTE = 60

private fun formatMs(ms: Int): String {
val totalSeconds = ms / 1000
val minutes = totalSeconds / SECONDS_PER_MINUTE
val seconds = totalSeconds % SECONDS_PER_MINUTE
return "%d:%02d".format(minutes, seconds)
}

private fun getIconResourceForMimeType(mimeType: String) = when {
mimeType.isAudioMimeType() -> R.drawable.ic_vector_audio_file
mimeType.isCalendarMimeType() -> R.drawable.ic_calendar_month_vector
mimeType.isPdfMimeType() -> R.drawable.ic_vector_pdf
mimeType.isZipMimeType() -> R.drawable.ic_vector_folder_zip
mimeType.endsWith("zip", ignoreCase = true) -> R.drawable.ic_vector_folder_zip
else -> R.drawable.ic_document_vector
}
Loading
Loading