Merge with upstream
This commit is contained in:
commit
565f7f5788
|
@ -89,6 +89,13 @@
|
|||
|
||||
<data android:mimeType="video/*" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
|
||||
<data android:mimeType="audio/*" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.service.chooser.chooser_target_service"
|
||||
|
|
|
@ -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() {
|
||||
|
@ -248,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.")
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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<Uri>()
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -63,6 +63,7 @@ interface MediaUploader {
|
|||
fun uploadMedia(media: QueuedMedia, videoLimit: Int, imageLimit: Int): Observable<UploadEvent>
|
||||
}
|
||||
|
||||
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()
|
||||
|
|
|
@ -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> 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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="24dp"
|
||||
android:width="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path android:fillColor="?android:textColorTertiary" android:pathData="M16,9H13V14.5A2.5,2.5 0 0,1 10.5,17A2.5,2.5 0 0,1 8,14.5A2.5,2.5 0 0,1 10.5,12C11.07,12 11.58,12.19 12,12.5V7H16M19,3H5A2,2 0 0,0 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3Z" />
|
||||
</vector>
|
|
@ -225,7 +225,7 @@
|
|||
|
||||
<TextView
|
||||
android:id="@+id/actionPhotoTake"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:drawablePadding="8dp"
|
||||
android:padding="8dp"
|
||||
|
@ -234,7 +234,7 @@
|
|||
|
||||
<TextView
|
||||
android:id="@+id/actionPhotoPick"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:drawablePadding="8dp"
|
||||
android:padding="8dp"
|
||||
|
@ -243,7 +243,7 @@
|
|||
|
||||
<TextView
|
||||
android:id="@+id/addPollTextActionTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:drawablePadding="8dp"
|
||||
android:padding="8dp"
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="Some media description" />
|
||||
|
||||
<VideoView
|
||||
<com.keylesspalace.tusky.view.ExposedPlayPauseVideoView
|
||||
android:id="@+id/videoView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
|
||||
<RadioButton
|
||||
android:id="@+id/publicRadioButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:layout_weight="1"
|
||||
|
@ -27,7 +27,7 @@
|
|||
|
||||
<RadioButton
|
||||
android:id="@+id/unlistedRadioButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:layout_marginTop="4dp"
|
||||
|
@ -40,7 +40,7 @@
|
|||
|
||||
<RadioButton
|
||||
android:id="@+id/privateRadioButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:layout_marginTop="4dp"
|
||||
|
@ -53,7 +53,7 @@
|
|||
|
||||
<RadioButton
|
||||
android:id="@+id/directRadioButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_weight="1"
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
<string name="error_compose_character_limit">The status is too long!</string>
|
||||
<string name="error_image_upload_size">The file must be less than 8MB.</string>
|
||||
<string name="error_video_upload_size">Video files must be less than 40MB.</string>
|
||||
<string name="error_audio_upload_size">Audio files must be less than 40MB.</string>
|
||||
<string name="error_media_upload_type">That type of file cannot be uploaded.</string>
|
||||
<string name="error_media_upload_opening">That file could not be opened.</string>
|
||||
<string name="error_media_upload_permission">Permission to read media is required.</string>
|
||||
|
|
Loading…
Reference in New Issue