diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 0f77db16..3535d9f5 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -89,6 +89,13 @@
+
+
+
+
+
+
+
shareImage(directory, attachment.url)
+ Attachment.Type.AUDIO,
Attachment.Type.VIDEO,
- Attachment.Type.GIFV -> shareVideo(directory, attachment.url)
+ Attachment.Type.GIFV -> shareMediaFile(directory, attachment.url)
else -> Log.e(TAG, "Unknown media format for sharing.")
}
}
@@ -313,7 +320,7 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
}
- private fun shareVideo(directory: File, url: String) {
+ private fun shareMediaFile(directory: File, url: String) {
val uri = Uri.parse(url)
val mimeTypeMap = MimeTypeMap.getSingleton()
val extension = MimeTypeMap.getFileExtensionFromUrl(url)
diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java b/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java
index dd9503b1..9f7fe665 100644
--- a/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java
+++ b/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java
@@ -495,6 +495,11 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
boolean hasSpoiler = !TextUtils.isEmpty(statusViewData.getSpoilerText());
contentWarningDescriptionTextView.setVisibility(hasSpoiler ? View.VISIBLE : View.GONE);
contentWarningButton.setVisibility(hasSpoiler ? View.VISIBLE : View.GONE);
+ if (statusViewData.isExpanded()) {
+ contentWarningButton.setText(R.string.status_content_warning_show_less);
+ } else {
+ contentWarningButton.setText(R.string.status_content_warning_show_more);
+ }
contentWarningButton.setOnClickListener(view -> {
if (getAdapterPosition() != RecyclerView.NO_POSITION) {
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt
index ca6111d3..8abecbd1 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt
@@ -209,7 +209,7 @@ class ComposeActivity : BaseActivity(),
* instance state will be re-queued. */
val type = intent.type
if (type != null) {
- if (type.startsWith("image/") || type.startsWith("video/")) {
+ if (type.startsWith("image/") || type.startsWith("video/") || type.startsWith("audio/")) {
val uriList = ArrayList()
if (intent.action != null) {
when (intent.action) {
@@ -368,8 +368,8 @@ class ComposeActivity : BaseActivity(),
}
combineOptionalLiveData(viewModel.media, viewModel.poll) { media, poll ->
if(!viewModel.hasNoAttachmentLimits) {
- val active = (poll == null && media!!.size != 4
- && media.firstOrNull()?.type != QueuedMedia.Type.VIDEO)
+ val active = poll == null && media!!.size != 4
+ && (media.isEmpty() || media.first().type == QueuedMedia.Type.IMAGE)
enableButton(composeAddMediaButton, active, active)
enablePollButton(media.isNullOrEmpty())
}
@@ -913,7 +913,7 @@ class ComposeActivity : BaseActivity(),
intent.addCategory(Intent.CATEGORY_OPENABLE)
if(!viewModel.hasNoAttachmentLimits) {
- val mimeTypes = arrayOf("image/*", "video/*")
+ val mimeTypes = arrayOf("image/*", "video/*", "audio/*")
intent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes)
}
intent.type = "*/*"
@@ -960,6 +960,9 @@ class ComposeActivity : BaseActivity(),
is MediaSizeException -> {
R.string.error_media_upload_size
}
+ is AudioSizeException -> {
+ R.string.error_audio_upload_size
+ }
is VideoOrImageException -> {
R.string.error_media_upload_image_or_video
}
@@ -1088,7 +1091,8 @@ class ComposeActivity : BaseActivity(),
companion object Type {
public const val IMAGE: Int = 0
public const val VIDEO: Int = 1
- public const val UNKNOWN: Int = 2
+ public const val AUDIO: Int = 2
+ public const val UNKNOWN: Int = 3
}
}
@@ -1145,7 +1149,7 @@ class ComposeActivity : BaseActivity(),
@JvmStatic
fun canHandleMimeType(mimeType: String?): Boolean {
- return mimeType != null && (mimeType.startsWith("image/") || mimeType.startsWith("video/") || mimeType == "text/plain")
+ return mimeType != null && (mimeType.startsWith("image/") || mimeType.startsWith("video/") || mimeType.startsWith("audio/") || mimeType == "text/plain")
}
}
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt
index 5acca508..b2e7bf5e 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt
@@ -181,7 +181,7 @@ class ComposeViewModel
.map { (type, uri, size) ->
val mediaItems = media.value!!
if (!hasNoAttachmentLimits
- && type == QueuedMedia.Type.VIDEO
+ && type != QueuedMedia.Type.IMAGE
&& mediaItems.isNotEmpty()
&& mediaItems[0].type == QueuedMedia.Type.IMAGE) {
throw VideoOrImageException()
@@ -449,6 +449,7 @@ class ComposeViewModel
val mediaType = when (a.type) {
Attachment.Type.VIDEO, Attachment.Type.GIFV -> QueuedMedia.Type.VIDEO
Attachment.Type.UNKNOWN, Attachment.Type.IMAGE -> QueuedMedia.Type.IMAGE
+ Attachment.Type.AUDIO -> QueuedMedia.Type.AUDIO
else -> QueuedMedia.Type.IMAGE
}
addUploadedMedia(a.id, mediaType, a.url.toUri(), a.description)
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/MediaPreviewAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/MediaPreviewAdapter.kt
index 54156c65..bde5f581 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/compose/MediaPreviewAdapter.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/MediaPreviewAdapter.kt
@@ -89,6 +89,11 @@ class MediaPreviewAdapter(
holder.view.setChecked(!item.description.isNullOrEmpty())
holder.view.setProgress(item.uploadPercent)
}
+ ComposeActivity.QueuedMedia.Type.AUDIO -> {
+ (holder as PreviewViewHolder).view.setChecked(!item.description.isNullOrEmpty())
+ holder.view.setProgress(item.uploadPercent)
+ holder.view.setImageResource(R.drawable.ic_music_box_preview_24dp)
+ }
else -> {
(holder as PreviewViewHolder).view.setChecked(!item.description.isNullOrEmpty())
holder.view.setProgress(item.uploadPercent)
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/MediaUploader.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/MediaUploader.kt
index e07f2460..bc5dd858 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/compose/MediaUploader.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/MediaUploader.kt
@@ -63,6 +63,7 @@ interface MediaUploader {
fun uploadMedia(media: QueuedMedia, videoLimit: Int, imageLimit: Int): Observable
}
+class AudioSizeException : Exception()
class VideoSizeException : Exception()
class MediaSizeException : Exception()
class MediaTypeException : Exception()
@@ -129,6 +130,12 @@ class MediaUploaderImpl(
"image" -> {
PreparedMedia(QueuedMedia.Type.IMAGE, uri, mediaSize)
}
+ "audio" -> {
+ if (mediaSize > videoLimit) { // TODO: CHANGE!!11
+ throw AudioSizeException()
+ }
+ PreparedMedia(QueuedMedia.Type.AUDIO, uri, mediaSize)
+ }
else -> {
if (mediaSize > videoLimit) {
throw MediaSizeException()
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/dialog/CaptionDialog.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/dialog/CaptionDialog.kt
index e7cc36cb..d594601c 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/compose/dialog/CaptionDialog.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/dialog/CaptionDialog.kt
@@ -24,7 +24,6 @@ import android.text.InputType
import android.util.DisplayMetrics
import android.view.WindowManager
import android.widget.EditText
-import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
@@ -34,6 +33,7 @@ import at.connyduck.sparkbutton.helpers.Utils
import com.bumptech.glide.Glide
import com.bumptech.glide.request.target.CustomTarget
import com.bumptech.glide.request.transition.Transition
+import com.github.chrisbanes.photoview.PhotoView
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.util.withLifecycleContext
@@ -50,7 +50,7 @@ fun T.makeCaptionDialog(existingDescription: String?,
dialogLayout.setPadding(padding, padding, padding, padding)
dialogLayout.orientation = LinearLayout.VERTICAL
- val imageView = ImageView(this)
+ val imageView = PhotoView(this)
val displayMetrics = DisplayMetrics()
windowManager.defaultDisplay.getMetrics(displayMetrics)
diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewVideoFragment.kt b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewVideoFragment.kt
index 34036107..0b3a23a7 100644
--- a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewVideoFragment.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewVideoFragment.kt
@@ -21,6 +21,7 @@ import android.annotation.SuppressLint
import android.os.Bundle
import android.os.Handler
import android.os.Looper
+import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@@ -31,6 +32,7 @@ import com.keylesspalace.tusky.ViewMediaActivity
import com.keylesspalace.tusky.entity.Attachment
import com.keylesspalace.tusky.util.hide
import com.keylesspalace.tusky.util.visible
+import com.keylesspalace.tusky.view.ExposedPlayPauseVideoView
import kotlinx.android.synthetic.main.activity_view_media.*
import kotlinx.android.synthetic.main.fragment_view_video.*
@@ -41,11 +43,13 @@ class ViewVideoFragment : ViewMediaFragment() {
// Hoist toolbar hiding to activity so it can track state across different fragments
// This is explicitly stored as runnable so that we pass it to the handler later for cancellation
mediaActivity.onPhotoTap()
+ mediaController.hide()
}
private lateinit var mediaActivity: ViewMediaActivity
private val TOOLBAR_HIDE_DELAY_MS = 3000L
override lateinit var descriptionView : TextView
private lateinit var mediaController : MediaController
+ private var isAudio = false
override fun setUserVisibleHint(isVisibleToUser: Boolean) {
// Start/pause/resume video playback as fragment is shown/hidden
@@ -72,14 +76,43 @@ class ViewVideoFragment : ViewMediaFragment() {
videoView.transitionName = url
videoView.setVideoPath(url)
- mediaController = MediaController(mediaActivity)
+ mediaController = object : MediaController(mediaActivity) {
+ override fun show(timeout: Int) {
+ // We're doing manual auto-close management.
+ // Also, take focus back from the pause button so we can use the back button.
+ super.show(0)
+ mediaController.requestFocus()
+ }
+
+ override fun dispatchKeyEvent(event: KeyEvent?): Boolean {
+ if (event?.keyCode == KeyEvent.KEYCODE_BACK) {
+ if (event.action == KeyEvent.ACTION_UP) {
+ hide()
+ activity?.supportFinishAfterTransition()
+ }
+ return true
+ }
+ return super.dispatchKeyEvent(event)
+ }
+ }
+
mediaController.setMediaPlayer(videoView)
videoView.setMediaController(mediaController)
videoView.requestFocus()
- videoView.setOnTouchListener { _, _ ->
- mediaActivity.onPhotoTap()
- false
- }
+ videoView.setPlayPauseListener(object: ExposedPlayPauseVideoView.PlayPauseListener {
+ override fun onPause() {
+ handler.removeCallbacks(hideToolbar)
+ }
+ override fun onPlay() {
+ // Audio doesn't cause the controller to show automatically,
+ // and we only want to hide the toolbar if it's a video.
+ if (isAudio) {
+ mediaController.show()
+ } else {
+ hideToolbarAfterDelay(TOOLBAR_HIDE_DELAY_MS)
+ }
+ }
+ })
videoView.setOnPreparedListener { mp ->
val containerWidth = videoContainer.measuredWidth.toFloat()
val containerHeight = videoContainer.measuredHeight.toFloat()
@@ -94,10 +127,16 @@ class ViewVideoFragment : ViewMediaFragment() {
videoView.layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT
}
+ // Wait until the media is loaded before accepting taps as we don't want toolbar to
+ // be hidden until then.
+ videoView.setOnTouchListener { _, _ ->
+ mediaActivity.onPhotoTap()
+ false
+ }
+
progressBar.hide()
mp.isLooping = true
if (arguments!!.getBoolean(ARG_START_POSTPONED_TRANSITION)) {
- hideToolbarAfterDelay(TOOLBAR_HIDE_DELAY_MS)
videoView.start()
}
}
@@ -126,6 +165,7 @@ class ViewVideoFragment : ViewMediaFragment() {
throw IllegalArgumentException("attachment has to be set")
}
url = attachment.url
+ isAudio = attachment.type == Attachment.Type.AUDIO
finalizeViewSetup(url, attachment.previewUrl, attachment.description)
}
@@ -136,6 +176,12 @@ class ViewVideoFragment : ViewMediaFragment() {
isDescriptionVisible = showingDescription && visible
val alpha = if (isDescriptionVisible) 1.0f else 0.0f
+ if (isDescriptionVisible) {
+ // If to be visible, need to make visible immediately and animate alpha
+ descriptionView.alpha = 0.0f
+ descriptionView.visible(isDescriptionVisible)
+ }
+
descriptionView.animate().alpha(alpha)
.setListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
@@ -145,7 +191,7 @@ class ViewVideoFragment : ViewMediaFragment() {
})
.start()
- if (visible) {
+ if (visible && videoView.isPlaying && !isAudio) {
hideToolbarAfterDelay(TOOLBAR_HIDE_DELAY_MS)
} else {
handler.removeCallbacks(hideToolbar)
diff --git a/app/src/main/java/com/keylesspalace/tusky/view/ExposedPlayPauseVideoView.kt b/app/src/main/java/com/keylesspalace/tusky/view/ExposedPlayPauseVideoView.kt
new file mode 100644
index 00000000..ec748e04
--- /dev/null
+++ b/app/src/main/java/com/keylesspalace/tusky/view/ExposedPlayPauseVideoView.kt
@@ -0,0 +1,33 @@
+package com.keylesspalace.tusky.view
+
+import android.content.Context
+import android.util.AttributeSet
+import android.widget.VideoView
+
+class ExposedPlayPauseVideoView @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0)
+ : VideoView(context, attrs, defStyleAttr) {
+
+ private var listener: PlayPauseListener? = null
+
+ fun setPlayPauseListener(listener: PlayPauseListener) {
+ this.listener = listener
+ }
+
+ override fun start() {
+ super.start()
+ listener?.onPlay()
+ }
+
+ override fun pause() {
+ super.pause()
+ listener?.onPause()
+ }
+
+ interface PlayPauseListener {
+ fun onPlay()
+ fun onPause()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_music_box_preview_24dp.xml b/app/src/main/res/drawable/ic_music_box_preview_24dp.xml
new file mode 100644
index 00000000..67901791
--- /dev/null
+++ b/app/src/main/res/drawable/ic_music_box_preview_24dp.xml
@@ -0,0 +1,8 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_compose.xml b/app/src/main/res/layout/activity_compose.xml
index 014ee181..27ea1d65 100644
--- a/app/src/main/res/layout/activity_compose.xml
+++ b/app/src/main/res/layout/activity_compose.xml
@@ -225,7 +225,7 @@
- The status is too long!
The file must be less than 8MB.
Video files must be less than 40MB.
+ Audio files must be less than 40MB.
That type of file cannot be uploaded.
That file could not be opened.
Permission to read media is required.