From 46b997a64223280daecaefb378713eb9c12162d4 Mon Sep 17 00:00:00 2001 From: Vivianne Date: Thu, 16 Jan 2020 10:01:02 -0800 Subject: [PATCH 1/6] Small changes to the media player (#1572) * Resolving some issues with media playback. * Changing ViewVideoFragment so that it fully handles autohiding the media controller, allowing consistency between it and the toolbar * Fixed an issue where the toolbar and description were not fading in * Do not hide the toolbar/media player if the video is loading or paused * Created "ExposedPlayPausedVideoView" to allow hooking play/pause * Fix back button when viewing videos * Back button did not work if video controls were visible. * Tweak audio player * Always show the controls when audio begins playing * Do not auto-hide the controls if the player is playing audio * Address PR comments * Use overloaded constructor * Remove m prefix * Fix subtle media player issues * Fix audio player incorrectly auto-hiding after hiding/showing toolbar * Only subscribe touch listener once content is ready - Prevents top toolbar visibility from getting out of phase with audio player visibility if hidden during load --- .../keylesspalace/tusky/ViewMediaActivity.kt | 6 ++ .../tusky/fragment/ViewVideoFragment.kt | 60 ++++++++++++++++--- .../tusky/view/ExposedPlayPauseVideoView.kt | 33 ++++++++++ .../main/res/layout/fragment_view_video.xml | 2 +- 4 files changed, 93 insertions(+), 8 deletions(-) create mode 100644 app/src/main/java/com/keylesspalace/tusky/view/ExposedPlayPauseVideoView.kt diff --git a/app/src/main/java/com/keylesspalace/tusky/ViewMediaActivity.kt b/app/src/main/java/com/keylesspalace/tusky/ViewMediaActivity.kt index 66bf8e8d..ca57b446 100644 --- a/app/src/main/java/com/keylesspalace/tusky/ViewMediaActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/ViewMediaActivity.kt @@ -186,8 +186,14 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener for (listener in toolbarVisibilityListeners) { listener(isToolbarVisible) } + val visibility = if (isToolbarVisible) View.VISIBLE else View.INVISIBLE val alpha = if (isToolbarVisible) 1.0f else 0.0f + if (isToolbarVisible) { + // If to be visible, need to make visible immediately and animate alpha + toolbar.alpha = 0.0f + toolbar.visibility = visibility + } toolbar.animate().alpha(alpha) .setListener(object : AnimatorListenerAdapter() { 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/layout/fragment_view_video.xml b/app/src/main/res/layout/fragment_view_video.xml index 083bdb20..180481c4 100644 --- a/app/src/main/res/layout/fragment_view_video.xml +++ b/app/src/main/res/layout/fragment_view_video.xml @@ -23,7 +23,7 @@ app:layout_constraintTop_toTopOf="parent" tools:text="Some media description" /> - Date: Thu, 16 Jan 2020 19:05:52 +0100 Subject: [PATCH 2/6] Add support for uploading audio attachments (#1630) * Add support for audio attachments. Partially addresses #1337 * Register Tusky as a target for audio sharing * Use icon with textColorTertiary for audio preview --- app/src/main/AndroidManifest.xml | 7 +++++++ .../tusky/components/compose/ComposeActivity.kt | 13 ++++++++----- .../tusky/components/compose/ComposeViewModel.kt | 3 ++- .../components/compose/MediaPreviewAdapter.kt | 15 ++++++++++----- .../tusky/components/compose/MediaUploader.kt | 8 ++++++++ .../res/drawable/ic_music_box_preview_24dp.xml | 8 ++++++++ app/src/main/res/values/strings.xml | 1 + 7 files changed, 44 insertions(+), 11 deletions(-) create mode 100644 app/src/main/res/drawable/ic_music_box_preview_24dp.xml 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 @@ + + + + + + + () if (intent.action != null) { when (intent.action) { @@ -323,7 +323,7 @@ class ComposeActivity : BaseActivity(), combineOptionalLiveData(viewModel.media, viewModel.poll) { media, poll -> val active = poll == null && media!!.size != 4 - && media.firstOrNull()?.type != QueuedMedia.Type.VIDEO + && (media.isEmpty() || media.first().type == QueuedMedia.Type.IMAGE) enableButton(composeAddMediaButton, active, active) enablePollButton(media.isNullOrEmpty()) }.subscribe() @@ -813,7 +813,7 @@ class ComposeActivity : BaseActivity(), val intent = Intent(Intent.ACTION_GET_CONTENT) intent.addCategory(Intent.CATEGORY_OPENABLE) - val mimeTypes = arrayOf("image/*", "video/*") + val mimeTypes = arrayOf("image/*", "video/*", "audio/*") intent.type = "*/*" intent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes) startActivityForResult(intent, MEDIA_PICK_RESULT) @@ -856,6 +856,9 @@ class ComposeActivity : BaseActivity(), is VideoSizeException -> { R.string.error_video_upload_size } + is AudioSizeException -> { + R.string.error_audio_upload_size + } is VideoOrImageException -> { R.string.error_media_upload_image_or_video } @@ -980,7 +983,7 @@ class ComposeActivity : BaseActivity(), val description: String? = null ) { enum class Type { - IMAGE, VIDEO; + IMAGE, VIDEO, AUDIO; } } @@ -1036,7 +1039,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 15973853..0e71596f 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 @@ -127,7 +127,7 @@ class ComposeViewModel mediaUploader.prepareMedia(uri) .map { (type, uri, size) -> val mediaItems = media.value!! - if (type == QueuedMedia.Type.VIDEO + if (type != QueuedMedia.Type.IMAGE && mediaItems.isNotEmpty() && mediaItems[0].type == QueuedMedia.Type.IMAGE) { throw VideoOrImageException() @@ -388,6 +388,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 babb0a39..093e860f 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 @@ -69,11 +69,16 @@ class MediaPreviewAdapter( val item = differ.currentList[position] holder.progressImageView.setChecked(!item.description.isNullOrEmpty()) holder.progressImageView.setProgress(item.uploadPercent) - Glide.with(holder.itemView.context) - .load(item.uri) - .diskCacheStrategy(DiskCacheStrategy.NONE) - .dontAnimate() - .into(holder.progressImageView) + if (item.type == ComposeActivity.QueuedMedia.Type.AUDIO) { + // TODO: Fancy waveform display? + holder.progressImageView.setImageResource(R.drawable.ic_music_box_preview_24dp) + } else { + Glide.with(holder.itemView.context) + .load(item.uri) + .diskCacheStrategy(DiskCacheStrategy.NONE) + .dontAnimate() + .into(holder.progressImageView) + } } private val differ = AsyncListDiffer(this, object : DiffUtil.ItemCallback() { 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 af41f4bb..73fa6d11 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): Observable } +class AudioSizeException : Exception() class VideoSizeException : Exception() class MediaTypeException : Exception() class CouldNotOpenFileException : Exception() @@ -128,6 +129,12 @@ class MediaUploaderImpl( "image" -> { PreparedMedia(QueuedMedia.Type.IMAGE, uri, mediaSize) } + "audio" -> { + if (mediaSize > STATUS_AUDIO_SIZE_LIMIT) { + throw AudioSizeException() + } + PreparedMedia(QueuedMedia.Type.AUDIO, uri, mediaSize) + } else -> { throw MediaTypeException() } @@ -196,6 +203,7 @@ class MediaUploaderImpl( private companion object { private const val TAG = "MediaUploaderImpl" private const val STATUS_VIDEO_SIZE_LIMIT = 41943040 // 40MiB + private const val STATUS_AUDIO_SIZE_LIMIT = 41943040 // 40MiB private const val STATUS_IMAGE_SIZE_LIMIT = 8388608 // 8MiB private const val STATUS_IMAGE_PIXEL_SIZE_LIMIT = 16777216 // 4096^2 Pixels 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/values/strings.xml b/app/src/main/res/values/strings.xml index 21bd1e3e..7311b17b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -12,6 +12,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. From bd66a9e6ae7fe429a31232880ee1e0d8cbb3959c Mon Sep 17 00:00:00 2001 From: Levi Bard Date: Thu, 16 Jan 2020 19:06:13 +0100 Subject: [PATCH 3/6] Add support for sharing audio attachments (#1629) --- .../main/java/com/keylesspalace/tusky/ViewMediaActivity.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/ViewMediaActivity.kt b/app/src/main/java/com/keylesspalace/tusky/ViewMediaActivity.kt index ca57b446..447c0590 100644 --- a/app/src/main/java/com/keylesspalace/tusky/ViewMediaActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/ViewMediaActivity.kt @@ -254,8 +254,9 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener val attachment = attachments!![viewPager.currentItem].attachment when (attachment.type) { Attachment.Type.IMAGE -> 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.") } } @@ -319,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) From 701e1dafe34260d8289ba90d0aee046770a63dbe Mon Sep 17 00:00:00 2001 From: Ivan Kupalov Date: Thu, 16 Jan 2020 19:08:19 +0100 Subject: [PATCH 4/6] Fix setting text to CW button in fav/reblog notification. Fix #1641 (#1642) This is a regression from e1e9268ef5051a96375f810372560f2da9516ad5 --- .../keylesspalace/tusky/adapter/NotificationsAdapter.java | 5 +++++ 1 file changed, 5 insertions(+) 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 6a5bc073..e018235e 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java @@ -479,6 +479,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) { From 584fc544103a59e64fecb959c7c2bebef00a1e6e Mon Sep 17 00:00:00 2001 From: Ivan Kupalov Date: Sat, 18 Jan 2020 12:12:37 +0100 Subject: [PATCH 5/6] Enable zooming and panning in the CaptionDialog (#1643) --- .../tusky/components/compose/dialog/CaptionDialog.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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) From e23c234f7750e61750ef5a84709ab14dc6b99ee9 Mon Sep 17 00:00:00 2001 From: Ivan Kupalov Date: Sat, 18 Jan 2020 12:13:10 +0100 Subject: [PATCH 6/6] Make radio buttons in compose screen sheets full width, fix #1593 (#1645) --- app/src/main/res/layout/activity_compose.xml | 6 +++--- app/src/main/res/layout/view_compose_options.xml | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/src/main/res/layout/activity_compose.xml b/app/src/main/res/layout/activity_compose.xml index e7f93639..d592eff6 100644 --- a/app/src/main/res/layout/activity_compose.xml +++ b/app/src/main/res/layout/activity_compose.xml @@ -167,7 +167,7 @@