Fixed video player
This commit fixes the issue n. 122 of the list of issues reported in the previous repository (https://git.mentality.rip/FWGS/Husky/issues/122).
This commit is contained in:
parent
c7163dbaff
commit
41b96beb1c
8 changed files with 329 additions and 254 deletions
|
@ -172,7 +172,8 @@ dependencies {
|
|||
implementation(ApplicationLibs.Glide.glideOkhttp)
|
||||
kapt(ApplicationLibs.Glide.glideCompiler)
|
||||
|
||||
implementation(ApplicationLibs.Google.flexBox)
|
||||
implementation(ApplicationLibs.Google.flexbox)
|
||||
implementation(ApplicationLibs.Google.exoplayer)
|
||||
implementation(ApplicationLibs.Google.materialDesign)
|
||||
|
||||
implementation(ApplicationLibs.RxJava.rxAndroid)
|
||||
|
|
|
@ -39,6 +39,8 @@ import io.reactivex.plugins.RxJavaPlugins
|
|||
import org.conscrypt.Conscrypt
|
||||
import java.security.Security
|
||||
import javax.inject.Inject
|
||||
import timber.log.Timber
|
||||
import timber.log.Timber.DebugTree
|
||||
|
||||
class TuskyApplication : Application(), HasAndroidInjector {
|
||||
|
||||
|
@ -93,6 +95,8 @@ class TuskyApplication : Application(), HasAndroidInjector {
|
|||
.setWorkerFactory(notificationWorkerFactory)
|
||||
.build()
|
||||
)
|
||||
|
||||
Timber.plant(DebugTree())
|
||||
}
|
||||
|
||||
override fun attachBaseContext(base: Context) {
|
||||
|
|
|
@ -109,7 +109,7 @@ class ComposeActivity : BaseActivity(),
|
|||
|
||||
@Inject
|
||||
lateinit var viewModelFactory: ViewModelFactory
|
||||
|
||||
|
||||
@Inject
|
||||
lateinit var eventHub: EventHub
|
||||
|
||||
|
@ -177,12 +177,12 @@ class ComposeActivity : BaseActivity(),
|
|||
|
||||
val composeOptions = intent.getParcelableExtra<ComposeOptions?>(COMPOSE_OPTIONS_EXTRA)
|
||||
|
||||
if (!composeOptions?.formattingSyntax.isNullOrEmpty()) {
|
||||
suggestFormattingSyntax = composeOptions?.formattingSyntax!!
|
||||
suggestFormattingSyntax = if (!composeOptions?.formattingSyntax.isNullOrEmpty()) {
|
||||
composeOptions?.formattingSyntax!!
|
||||
} else {
|
||||
suggestFormattingSyntax = activeAccount.defaultFormattingSyntax
|
||||
activeAccount.defaultFormattingSyntax
|
||||
}
|
||||
|
||||
|
||||
viewModel.setup(composeOptions)
|
||||
setupReplyViews(composeOptions?.replyingStatusAuthor, composeOptions?.replyingStatusContent)
|
||||
val tootText = composeOptions?.tootText
|
||||
|
@ -210,7 +210,7 @@ class ComposeActivity : BaseActivity(),
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun applyShareIntent(intent: Intent, savedInstanceState: Bundle?) {
|
||||
if (savedInstanceState == null) {
|
||||
/* Get incoming images being sent through a share action from another app. Only do this
|
||||
|
@ -314,7 +314,7 @@ class ComposeActivity : BaseActivity(),
|
|||
composeEditField.setLayerType(View.LAYER_TYPE_SOFTWARE, null)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun reenableAttachments() {
|
||||
// in case of we already had disabled attachments
|
||||
// but got information about extension later
|
||||
|
@ -336,20 +336,20 @@ class ComposeActivity : BaseActivity(),
|
|||
if(instanceData.supportsMarkdown) {
|
||||
supportedFormattingSyntax.add("text/markdown")
|
||||
}
|
||||
|
||||
|
||||
if(instanceData.supportsBBcode) {
|
||||
supportedFormattingSyntax.add("text/bbcode")
|
||||
}
|
||||
|
||||
|
||||
if(instanceData.supportsHTML) {
|
||||
supportedFormattingSyntax.add("text/html")
|
||||
}
|
||||
|
||||
|
||||
if(supportedFormattingSyntax.size != 0) {
|
||||
composeFormattingSyntax.visible(true)
|
||||
|
||||
|
||||
val supportsPrefferedSyntax = supportedFormattingSyntax.contains(viewModel.formattingSyntax.value!!)
|
||||
|
||||
|
||||
if(!supportsPrefferedSyntax) {
|
||||
suggestFormattingSyntax = if(supportedFormattingSyntax.contains(activeAccount.defaultFormattingSyntax))
|
||||
activeAccount.defaultFormattingSyntax
|
||||
|
@ -358,7 +358,7 @@ class ComposeActivity : BaseActivity(),
|
|||
viewModel.formattingSyntax.value = ""
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if(instanceData.software == "pleroma") {
|
||||
composePreviewButton.visibility = View.VISIBLE
|
||||
reenableAttachments()
|
||||
|
@ -528,37 +528,37 @@ class ComposeActivity : BaseActivity(),
|
|||
// Set the cursor after the inserted text
|
||||
composeEditField.setSelection(start + text.length)
|
||||
}
|
||||
|
||||
|
||||
private fun enableFormattingSyntaxButton(syntax: String, enable: Boolean) {
|
||||
val stringId = when(syntax) {
|
||||
"text/html" -> R.string.action_html
|
||||
"text/bbcode" -> R.string.action_bbcode
|
||||
else -> R.string.action_markdown
|
||||
}
|
||||
|
||||
|
||||
val actionStringId = if(enable) R.string.action_disable_formatting_syntax else R.string.action_enable_formatting_syntax
|
||||
val tooltipText = getString(actionStringId).format(stringId)
|
||||
|
||||
|
||||
composeFormattingSyntax.contentDescription = tooltipText
|
||||
|
||||
|
||||
@ColorInt val color = ThemeUtils.getColor(this, if(enable) R.attr.colorPrimary else android.R.attr.textColorTertiary);
|
||||
composeFormattingSyntax.drawable.colorFilter = PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN);
|
||||
|
||||
|
||||
enableMarkdownWYSIWYGButtons(enable);
|
||||
}
|
||||
|
||||
|
||||
private fun setIconForSyntax(syntax: String, enable: Boolean) {
|
||||
val drawableId = when(syntax) {
|
||||
"text/html" -> R.drawable.ic_html_24dp
|
||||
"text/bbcode" -> R.drawable.ic_bbcode_24dp
|
||||
else -> R.drawable.ic_markdown
|
||||
}
|
||||
|
||||
|
||||
suggestFormattingSyntax = if(drawableId == R.drawable.ic_markdown) "text/markdown" else syntax
|
||||
composeFormattingSyntax.setImageResource(drawableId)
|
||||
enableFormattingSyntaxButton(syntax, enable)
|
||||
}
|
||||
|
||||
|
||||
private fun toggleFormattingMode() {
|
||||
if(viewModel.formattingSyntax.value!! == suggestFormattingSyntax) {
|
||||
viewModel.formattingSyntax.value = ""
|
||||
|
@ -566,7 +566,7 @@ class ComposeActivity : BaseActivity(),
|
|||
viewModel.formattingSyntax.value = suggestFormattingSyntax
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun selectFormattingSyntax() : Boolean {
|
||||
val menu = PopupMenu(this, composeFormattingSyntax)
|
||||
val plaintextId = 0
|
||||
|
@ -582,7 +582,7 @@ class ComposeActivity : BaseActivity(),
|
|||
|
||||
if(viewModel.instanceMetadata.value?.supportsHTML ?: false)
|
||||
menu.menu.add(0, htmlId, 0, R.string.action_html)
|
||||
|
||||
|
||||
menu.setOnMenuItemClickListener { menuItem ->
|
||||
val choose = when (menuItem.itemId) {
|
||||
markdownId -> "text/markdown"
|
||||
|
@ -595,10 +595,10 @@ class ComposeActivity : BaseActivity(),
|
|||
true
|
||||
}
|
||||
menu.show()
|
||||
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
private fun enableMarkdownWYSIWYGButtons(visible: Boolean) {
|
||||
val visibility = if(visible) View.VISIBLE else View.GONE
|
||||
codeButton.visibility = visibility
|
||||
|
@ -656,7 +656,7 @@ class ComposeActivity : BaseActivity(),
|
|||
private fun hashButtonClicked() {
|
||||
prependSelectedWordsWith("#")
|
||||
}
|
||||
|
||||
|
||||
private fun codeButtonClicked() {
|
||||
when(viewModel.formattingSyntax.value!!) {
|
||||
"text/markdown" -> MarkdownEdit.addCode(composeEditField)
|
||||
|
@ -664,7 +664,7 @@ class ComposeActivity : BaseActivity(),
|
|||
"text/html" -> HTMLEdit.addCode(composeEditField)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun linkButtonClicked() {
|
||||
when(viewModel.formattingSyntax.value!!) {
|
||||
"text/markdown" -> MarkdownEdit.addLink(composeEditField)
|
||||
|
@ -672,7 +672,7 @@ class ComposeActivity : BaseActivity(),
|
|||
"text/html" -> HTMLEdit.addLink(composeEditField)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun strikethroughButtonClicked() {
|
||||
when(viewModel.formattingSyntax.value!!) {
|
||||
"text/markdown" -> MarkdownEdit.addStrikeThrough(composeEditField)
|
||||
|
@ -680,7 +680,7 @@ class ComposeActivity : BaseActivity(),
|
|||
"text/html" -> HTMLEdit.addStrikeThrough(composeEditField)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun italicButtonClicked() {
|
||||
when(viewModel.formattingSyntax.value!!) {
|
||||
"text/markdown" -> MarkdownEdit.addItalic(composeEditField)
|
||||
|
@ -688,7 +688,7 @@ class ComposeActivity : BaseActivity(),
|
|||
"text/html" -> HTMLEdit.addItalic(composeEditField)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun boldButtonClicked() {
|
||||
when(viewModel.formattingSyntax.value!!) {
|
||||
"text/markdown" -> MarkdownEdit.addBold(composeEditField)
|
||||
|
@ -945,14 +945,14 @@ class ComposeActivity : BaseActivity(),
|
|||
if(preview && previewBehavior.state != BottomSheetBehavior.STATE_HIDDEN) {
|
||||
previewBehavior.state = BottomSheetBehavior.STATE_HIDDEN
|
||||
}
|
||||
|
||||
|
||||
if (verifyScheduledTime()) {
|
||||
sendStatus(preview)
|
||||
} else {
|
||||
showScheduleView()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun onStatusPreviewReady(status: Status) {
|
||||
enableButtons(true)
|
||||
previewView.setupWithStatus(status)
|
||||
|
@ -1060,7 +1060,6 @@ class ComposeActivity : BaseActivity(),
|
|||
|
||||
private fun initiateMediaPicking() {
|
||||
val intent = Intent(Intent.ACTION_GET_CONTENT)
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE)
|
||||
|
||||
if(!viewModel.hasNoAttachmentLimits) {
|
||||
val mimeTypes = arrayOf("image/*", "video/*", "audio/*")
|
||||
|
@ -1068,6 +1067,7 @@ class ComposeActivity : BaseActivity(),
|
|||
}
|
||||
intent.type = "*/*"
|
||||
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE)
|
||||
startActivityForResult(intent, MEDIA_PICK_RESULT)
|
||||
}
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ import com.keylesspalace.tusky.ViewMediaActivity
|
|||
import com.keylesspalace.tusky.entity.Attachment
|
||||
|
||||
abstract class ViewMediaFragment : BaseFragment() {
|
||||
|
||||
private var toolbarVisibiltyDisposable: Function0<Boolean>? = null
|
||||
|
||||
abstract fun setupMediaView(
|
||||
|
|
|
@ -18,6 +18,7 @@ package com.keylesspalace.tusky.fragment
|
|||
import android.animation.Animator
|
||||
import android.animation.AnimatorListenerAdapter
|
||||
import android.annotation.SuppressLint
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
|
@ -26,182 +27,219 @@ import android.view.LayoutInflater
|
|||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.MediaController
|
||||
import com.google.android.exoplayer2.MediaItem
|
||||
import com.google.android.exoplayer2.PlaybackException
|
||||
import com.google.android.exoplayer2.Player
|
||||
import com.google.android.exoplayer2.SimpleExoPlayer
|
||||
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector
|
||||
import com.keylesspalace.tusky.R
|
||||
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.*
|
||||
import kotlinx.android.synthetic.main.activity_view_media.toolbar
|
||||
import kotlinx.android.synthetic.main.fragment_view_video.mediaDescription
|
||||
import kotlinx.android.synthetic.main.fragment_view_video.progressBar
|
||||
import kotlinx.android.synthetic.main.fragment_view_video.videoView
|
||||
import timber.log.Timber
|
||||
|
||||
class ViewVideoFragment : ViewMediaFragment() {
|
||||
private lateinit var toolbar: View
|
||||
private val handler = Handler(Looper.getMainLooper())
|
||||
private val hideToolbar = Runnable {
|
||||
// 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
|
||||
private lateinit var mediaController : MediaController
|
||||
private var isAudio = false
|
||||
|
||||
override fun setUserVisibleHint(isVisibleToUser: Boolean) {
|
||||
// Start/pause/resume video playback as fragment is shown/hidden
|
||||
super.setUserVisibleHint(isVisibleToUser)
|
||||
if (videoView == null) {
|
||||
return
|
||||
}
|
||||
private lateinit var toolbar: View
|
||||
private val handler = Handler(Looper.getMainLooper())
|
||||
private val hideToolbar = Runnable {
|
||||
// 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
|
||||
private lateinit var mediaController: MediaController
|
||||
private var isAudio = false
|
||||
|
||||
if (isVisibleToUser) {
|
||||
if (mediaActivity.isToolbarVisible) {
|
||||
handler.postDelayed(hideToolbar, TOOLBAR_HIDE_DELAY_MS)
|
||||
}
|
||||
videoView.start()
|
||||
} else {
|
||||
handler.removeCallbacks(hideToolbar)
|
||||
videoView.pause()
|
||||
mediaController.hide()
|
||||
}
|
||||
}
|
||||
private var exoPlayer: SimpleExoPlayer? = null
|
||||
private val playbackStateListener: Player.Listener = playbackStateListener()
|
||||
private var playWhenReady = true
|
||||
private var currentWindow = 0
|
||||
private var playbackPosition = 0L
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
override fun setupMediaView(
|
||||
url: String,
|
||||
previewUrl: String?,
|
||||
description: String?,
|
||||
showingDescription: Boolean
|
||||
) {
|
||||
mediaDescription.text = description
|
||||
mediaDescription.visible(showingDescription)
|
||||
override fun setUserVisibleHint(isVisibleToUser: Boolean) {
|
||||
// Start/pause/resume video playback as fragment is shown/hidden
|
||||
super.setUserVisibleHint(isVisibleToUser)
|
||||
|
||||
videoView.transitionName = url
|
||||
videoView.setVideoPath(url)
|
||||
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()
|
||||
}
|
||||
if(videoView == null) {
|
||||
return
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
if(isVisibleToUser) {
|
||||
if(mediaActivity.isToolbarVisible) {
|
||||
handler.postDelayed(hideToolbar, TOOLBAR_HIDE_DELAY_MS)
|
||||
}
|
||||
exoPlayer?.play()
|
||||
} else {
|
||||
handler.removeCallbacks(hideToolbar)
|
||||
exoPlayer?.pause()
|
||||
mediaController.hide()
|
||||
}
|
||||
}
|
||||
|
||||
mediaController.setMediaPlayer(videoView)
|
||||
videoView.setMediaController(mediaController)
|
||||
videoView.requestFocus()
|
||||
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()
|
||||
val videoWidth = mp.videoWidth.toFloat()
|
||||
val videoHeight = mp.videoHeight.toFloat()
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
override fun setupMediaView(
|
||||
url: String,
|
||||
previewUrl: String?,
|
||||
description: String?,
|
||||
showingDescription: Boolean
|
||||
) {
|
||||
mediaDescription.text = description
|
||||
mediaDescription.visible(showingDescription)
|
||||
|
||||
if(containerWidth/containerHeight > videoWidth/videoHeight) {
|
||||
videoView.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
|
||||
videoView.layoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
} else {
|
||||
videoView.layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
videoView.layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT
|
||||
}
|
||||
videoView.transitionName = url
|
||||
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()
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
progressBar.hide()
|
||||
mp.isLooping = true
|
||||
if (arguments!!.getBoolean(ARG_START_POSTPONED_TRANSITION)) {
|
||||
videoView.start()
|
||||
}
|
||||
}
|
||||
val trackSelector = DefaultTrackSelector(requireActivity()).apply {
|
||||
setParameters(buildUponParameters().setMaxVideoSizeSd())
|
||||
}
|
||||
|
||||
if (arguments!!.getBoolean(ARG_START_POSTPONED_TRANSITION)) {
|
||||
mediaActivity.onBringUp()
|
||||
}
|
||||
}
|
||||
exoPlayer = SimpleExoPlayer.Builder(requireActivity())
|
||||
.setTrackSelector(trackSelector)
|
||||
.build()
|
||||
.also { player ->
|
||||
videoView.player = player
|
||||
|
||||
private fun hideToolbarAfterDelay(delayMilliseconds: Long) {
|
||||
handler.postDelayed(hideToolbar, delayMilliseconds)
|
||||
}
|
||||
val mediaItem = MediaItem.Builder()
|
||||
.setUri(Uri.parse(url))
|
||||
.build()
|
||||
player.setMediaItem(mediaItem)
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
toolbar = activity!!.toolbar
|
||||
mediaActivity = activity as ViewMediaActivity
|
||||
return inflater.inflate(R.layout.fragment_view_video, container, false)
|
||||
}
|
||||
player.addListener(playbackStateListener)
|
||||
player.seekTo(currentWindow, playbackPosition)
|
||||
player.playWhenReady = playWhenReady
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
val attachment = arguments?.getParcelable<Attachment>(ARG_ATTACHMENT)
|
||||
val url: String
|
||||
player.prepare()
|
||||
}
|
||||
|
||||
if (attachment == null) {
|
||||
throw IllegalArgumentException("attachment has to be set")
|
||||
}
|
||||
url = attachment.url
|
||||
isAudio = attachment.type == Attachment.Type.AUDIO
|
||||
finalizeViewSetup(url, attachment.previewUrl, attachment.description)
|
||||
}
|
||||
videoView.requestFocus()
|
||||
|
||||
override fun onToolbarVisibilityChange(visible: Boolean) {
|
||||
if (videoView == null || mediaDescription == null || !userVisibleHint) {
|
||||
return
|
||||
}
|
||||
if(arguments!!.getBoolean(ARG_START_POSTPONED_TRANSITION)) {
|
||||
mediaActivity.onBringUp()
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
mediaDescription.alpha = 0.0f
|
||||
mediaDescription.visible(isDescriptionVisible)
|
||||
}
|
||||
private fun hideToolbarAfterDelay(delayMilliseconds: Long) {
|
||||
handler.postDelayed(hideToolbar, delayMilliseconds)
|
||||
}
|
||||
|
||||
mediaDescription.animate().alpha(alpha)
|
||||
.setListener(object : AnimatorListenerAdapter() {
|
||||
override fun onAnimationEnd(animation: Animator) {
|
||||
mediaDescription?.visible(isDescriptionVisible)
|
||||
animation.removeListener(this)
|
||||
}
|
||||
})
|
||||
.start()
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
toolbar = activity!!.toolbar
|
||||
mediaActivity = activity as ViewMediaActivity
|
||||
return inflater.inflate(R.layout.fragment_view_video, container, false)
|
||||
}
|
||||
|
||||
if (visible && videoView.isPlaying && !isAudio) {
|
||||
hideToolbarAfterDelay(TOOLBAR_HIDE_DELAY_MS)
|
||||
} else {
|
||||
handler.removeCallbacks(hideToolbar)
|
||||
}
|
||||
}
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
|
||||
override fun onTransitionEnd() {
|
||||
}
|
||||
val attachment = arguments?.getParcelable<Attachment>(ARG_ATTACHMENT)
|
||||
?: throw IllegalArgumentException("attachment has to be set")
|
||||
|
||||
isAudio = (attachment.type == Attachment.Type.AUDIO)
|
||||
finalizeViewSetup(attachment.url, attachment.previewUrl, attachment.description)
|
||||
}
|
||||
|
||||
override fun onToolbarVisibilityChange(visible: Boolean) {
|
||||
if(videoView == null || mediaDescription == null || !userVisibleHint) {
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
mediaDescription.alpha = 0.0f
|
||||
mediaDescription.visible(isDescriptionVisible)
|
||||
}
|
||||
|
||||
mediaDescription.animate().alpha(alpha)
|
||||
.setListener(object : AnimatorListenerAdapter() {
|
||||
override fun onAnimationEnd(animation: Animator) {
|
||||
mediaDescription?.visible(isDescriptionVisible)
|
||||
animation.removeListener(this)
|
||||
}
|
||||
})
|
||||
.start()
|
||||
|
||||
if(visible && (videoView.player?.isPlaying == true) && !isAudio) {
|
||||
hideToolbarAfterDelay(TOOLBAR_HIDE_DELAY_MS)
|
||||
} else {
|
||||
handler.removeCallbacks(hideToolbar)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTransitionEnd() {
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
|
||||
releasePlayer()
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
|
||||
releasePlayer()
|
||||
}
|
||||
|
||||
private fun releasePlayer() {
|
||||
exoPlayer?.run {
|
||||
playbackPosition = this.currentPosition
|
||||
currentWindow = this.currentWindowIndex
|
||||
playWhenReady = this.playWhenReady
|
||||
removeListener(playbackStateListener)
|
||||
release()
|
||||
}
|
||||
exoPlayer = null
|
||||
}
|
||||
|
||||
private fun playbackStateListener() = object : Player.Listener {
|
||||
|
||||
override fun onPlaybackStateChanged(playbackState: Int) {
|
||||
when(playbackState) {
|
||||
Player.STATE_BUFFERING -> {
|
||||
progressBar.visibility = View.VISIBLE
|
||||
}
|
||||
Player.STATE_READY,
|
||||
Player.STATE_ENDED -> {
|
||||
progressBar.visibility = View.GONE
|
||||
}
|
||||
else -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPlayerError(error: PlaybackException) {
|
||||
Timber.e(error.errorCodeName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,44 +1,44 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/videoContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clickable="true"
|
||||
android:focusable="true">
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/videoContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clickable="true"
|
||||
android:focusable="true">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/mediaDescription"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="?attr/actionBarSize"
|
||||
android:background="#60000000"
|
||||
android:hyphenationFrequency="full"
|
||||
android:lineSpacingMultiplier="1.1"
|
||||
android:padding="8dp"
|
||||
android:textAlignment="center"
|
||||
android:textColor="#eee"
|
||||
android:textSize="?attr/status_text_medium"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="Some media description" />
|
||||
<TextView
|
||||
android:id="@+id/mediaDescription"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="?attr/actionBarSize"
|
||||
android:background="#60000000"
|
||||
android:hyphenationFrequency="full"
|
||||
android:lineSpacingMultiplier="1.1"
|
||||
android:padding="8dp"
|
||||
android:textAlignment="center"
|
||||
android:textColor="#eee"
|
||||
android:textSize="?attr/status_text_medium"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="Some media description" />
|
||||
|
||||
<com.keylesspalace.tusky.view.ExposedPlayPauseVideoView
|
||||
android:id="@+id/videoView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
<com.google.android.exoplayer2.ui.PlayerView
|
||||
android:id="@+id/videoView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
|
@ -1,44 +1,72 @@
|
|||
object ApplicationLibs {
|
||||
|
||||
private object Versions {
|
||||
const val androidImageCropper = "2.8.0"
|
||||
const val appcompat = "1.2.0"
|
||||
const val autodispose = "1.4.0"
|
||||
const val bigImageViewer = "1.7.0"
|
||||
const val browser = "1.3.0"
|
||||
const val cardView = "1.0.0"
|
||||
const val conscryptAndroid = "2.5.1"
|
||||
const val constraintlayout = "2.1.0"
|
||||
const val coreKtx = "1.3.2"
|
||||
const val dagger = "2.30.1"
|
||||
const val emoji = "1.1.0"
|
||||
const val exifInterface = "1.3.2"
|
||||
const val exoplayer = "2.15.0"
|
||||
const val filemojiCompat = "1.0.17"
|
||||
const val flexbox = "2.0.1"
|
||||
const val fragmentKtx = "1.2.5"
|
||||
const val glide = "4.11.0"
|
||||
const val glideImage = "1.8.0"
|
||||
const val lifecycle = "2.2.0"
|
||||
const val markdownEdit = "1.0.0"
|
||||
const val materialDesign = "1.4.0"
|
||||
const val materialDrawer = "8.2.0"
|
||||
const val materialDrawerTypeface = "3.0.1.4.original-kotlin@aar"
|
||||
const val pagingRuntimeKtx = "2.1.2"
|
||||
const val preferenceKtx = "1.1.1"
|
||||
const val okhttpVersion = "4.9.0"
|
||||
const val recyclerView = "1.1.0"
|
||||
const val retrofit = "2.9.0"
|
||||
const val room = "2.2.5"
|
||||
const val rxAndroid = "2.1.1"
|
||||
const val rxJava = "2.2.20"
|
||||
const val rxKotlin = "2.4.0"
|
||||
const val shareTarget = "1.0.0"
|
||||
const val simplestack = "2.6.2"
|
||||
const val simplestackExt = "2.2.2"
|
||||
const val sparkButton = "4.1.0"
|
||||
const val swipeRefreshLayout = "1.1.0"
|
||||
const val timber = "4.7.1"
|
||||
const val viewpager2 = "1.0.0"
|
||||
const val workRuntime = "2.4.0"
|
||||
}
|
||||
|
||||
object AndroidX {
|
||||
const val appCompat = "androidx.appcompat:appcompat:${Versions.appcompat}"
|
||||
const val browser = "androidx.browser:browser:1.3.0"
|
||||
const val cardView = "androidx.cardview:cardview:1.0.0"
|
||||
const val browser = "androidx.browser:browser:${Versions.browser}"
|
||||
const val cardView = "androidx.cardview:cardview:${Versions.cardView}"
|
||||
const val constraintLayout =
|
||||
"androidx.constraintlayout:constraintlayout:${Versions.constraintlayout}"
|
||||
const val coreKtx = "androidx.core:core-ktx:${Versions.coreKtx}"
|
||||
const val exifInterface = "androidx.exifinterface:exifinterface:1.3.2"
|
||||
const val emoji = "androidx.emoji:emoji:1.1.0"
|
||||
const val emojiAppCompat = "androidx.emoji:emoji-appcompat:1.1.0"
|
||||
const val emojiBundled = "androidx.emoji:emoji-bundled:1.1.0"
|
||||
const val fragmentKtx = "androidx.fragment:fragment-ktx:1.2.5"
|
||||
const val pagingRuntimeKtx = "androidx.paging:paging-runtime-ktx:2.1.2"
|
||||
const val preferenceKtx = "androidx.preference:preference-ktx:1.1.1"
|
||||
const val recyclerView = "androidx.recyclerview:recyclerview:1.1.0"
|
||||
const val emoji = "androidx.emoji:emoji:${Versions.emoji}"
|
||||
const val emojiAppCompat = "androidx.emoji:emoji-appcompat:${Versions.emoji}"
|
||||
const val emojiBundled = "androidx.emoji:emoji-bundled:${Versions.emoji}"
|
||||
const val exifInterface = "androidx.exifinterface:exifinterface:${Versions.exifInterface}"
|
||||
const val fragmentKtx = "androidx.fragment:fragment-ktx:${Versions.fragmentKtx}"
|
||||
const val pagingRuntimeKtx =
|
||||
"androidx.paging:paging-runtime-ktx:${Versions.pagingRuntimeKtx}"
|
||||
const val preferenceKtx = "androidx.preference:preference-ktx:${Versions.preferenceKtx}"
|
||||
const val recyclerView = "androidx.recyclerview:recyclerview:${Versions.recyclerView}"
|
||||
const val roomCompiler = "androidx.room:room-compiler:${Versions.room}"
|
||||
const val roomRuntime = "androidx.room:room-runtime:${Versions.room}"
|
||||
const val roomRxJava = "androidx.room:room-rxjava2:${Versions.room}"
|
||||
const val shareTarget = "androidx.sharetarget:sharetarget:1.0.0"
|
||||
const val swipeRefreshLayout = "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
|
||||
const val viewpager2 = "androidx.viewpager2:viewpager2:1.0.0"
|
||||
const val workRuntime = "androidx.work:work-runtime:2.4.0"
|
||||
const val shareTarget = "androidx.sharetarget:sharetarget:${Versions.shareTarget}"
|
||||
const val swipeRefreshLayout =
|
||||
"androidx.swiperefreshlayout:swiperefreshlayout:${Versions.swipeRefreshLayout}"
|
||||
const val viewpager2 = "androidx.viewpager2:viewpager2:${Versions.viewpager2}"
|
||||
const val workRuntime = "androidx.work:work-runtime:${Versions.workRuntime}"
|
||||
|
||||
object Lifecycle {
|
||||
const val liveDataKtx =
|
||||
|
@ -65,7 +93,8 @@ object ApplicationLibs {
|
|||
}
|
||||
|
||||
object Google {
|
||||
const val flexBox = "com.google.android:flexbox:2.0.1"
|
||||
const val flexbox = "com.google.android:flexbox:${Versions.flexbox}"
|
||||
const val exoplayer = "com.google.android.exoplayer:exoplayer:${Versions.exoplayer}"
|
||||
const val materialDesign = "com.google.android.material:material:${Versions.materialDesign}"
|
||||
}
|
||||
|
||||
|
@ -76,9 +105,9 @@ object ApplicationLibs {
|
|||
}
|
||||
|
||||
object RxJava {
|
||||
const val rxAndroid = "io.reactivex.rxjava2:rxandroid:2.1.1"
|
||||
const val rxJava = "io.reactivex.rxjava2:rxjava:2.2.20"
|
||||
const val rxKotlin = "io.reactivex.rxjava2:rxkotlin:2.4.0"
|
||||
const val rxAndroid = "io.reactivex.rxjava2:rxandroid:${Versions.rxAndroid}"
|
||||
const val rxJava = "io.reactivex.rxjava2:rxjava:${Versions.rxJava}"
|
||||
const val rxKotlin = "io.reactivex.rxjava2:rxkotlin:${Versions.rxKotlin}"
|
||||
}
|
||||
|
||||
object SimpleStack {
|
||||
|
@ -98,21 +127,23 @@ object ApplicationLibs {
|
|||
const val okhttpBrotli = "com.squareup.okhttp3:okhttp-brotli:${Versions.okhttpVersion}"
|
||||
}
|
||||
|
||||
const val androidImageCropper = "com.theartofdev.edmodo:android-image-cropper:2.8.0"
|
||||
const val autodispose = "com.uber.autodispose:autodispose:1.4.0"
|
||||
const val androidImageCropper =
|
||||
"com.theartofdev.edmodo:android-image-cropper:${Versions.androidImageCropper}"
|
||||
const val autodispose = "com.uber.autodispose:autodispose:${Versions.autodispose}"
|
||||
const val autodisposeAndroidArchComp =
|
||||
"com.uber.autodispose:autodispose-android-archcomponents:1.4.0"
|
||||
const val bigImageViewer = "com.github.piasy:BigImageViewer:1.7.0"
|
||||
const val conscryptAndroid = "org.conscrypt:conscrypt-android:2.5.1"
|
||||
const val filemojiCompat = "de.c1710:filemojicompat:1.0.17"
|
||||
const val glideImage = "com.github.piasy:GlideImageLoader:1.8.0"
|
||||
const val glideImageViewFactory = "com.github.piasy:GlideImageViewFactory:1.8.0"
|
||||
const val markdownEdit = "com.github.Tunous:MarkdownEdit:1.0.0"
|
||||
"com.uber.autodispose:autodispose-android-archcomponents:${Versions.autodispose}"
|
||||
const val bigImageViewer = "com.github.piasy:BigImageViewer:${Versions.bigImageViewer}"
|
||||
const val conscryptAndroid = "org.conscrypt:conscrypt-android:${Versions.conscryptAndroid}"
|
||||
const val filemojiCompat = "de.c1710:filemojicompat:${Versions.filemojiCompat}"
|
||||
const val glideImage = "com.github.piasy:GlideImageLoader:${Versions.glideImage}"
|
||||
const val glideImageViewFactory =
|
||||
"com.github.piasy:GlideImageViewFactory:${Versions.glideImage}"
|
||||
const val markdownEdit = "com.github.Tunous:MarkdownEdit:${Versions.markdownEdit}"
|
||||
const val materialDrawer = "com.mikepenz:materialdrawer:${Versions.materialDrawer}"
|
||||
const val materialDrawerIconics =
|
||||
"com.mikepenz:materialdrawer-iconics:${Versions.materialDrawer}"
|
||||
const val materialDrawerTypeface =
|
||||
"com.mikepenz:google-material-typeface:3.0.1.4.original-kotlin@aar"
|
||||
const val sparkButton = "com.github.connyduck:sparkbutton:4.1.0"
|
||||
"com.mikepenz:google-material-typeface:${Versions.materialDrawerTypeface}"
|
||||
const val sparkButton = "com.github.connyduck:sparkbutton:${Versions.sparkButton}"
|
||||
const val timber = "com.jakewharton.timber:timber:${Versions.timber}"
|
||||
}
|
||||
|
|
0
husky/gradlew
vendored
Executable file → Normal file
0
husky/gradlew
vendored
Executable file → Normal file
Loading…
Reference in a new issue