ultrasonic-app-subsonic-and.../ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/PlayerFragment.kt

1214 lines
46 KiB
Kotlin
Raw Normal View History

/*
* PlayerFragment.kt
* Copyright (C) 2009-2021 Ultrasonic developers
*
* Distributed under terms of the GNU GPLv3 license.
*/
2021-06-19 20:05:38 +02:00
package org.moire.ultrasonic.fragment
2021-06-19 20:42:03 +02:00
import android.annotation.SuppressLint
2021-06-19 20:05:38 +02:00
import android.app.AlertDialog
import android.graphics.Point
import android.graphics.drawable.Drawable
import android.os.Bundle
import android.os.Handler
import android.view.ContextMenu
import android.view.ContextMenu.ContextMenuInfo
import android.view.GestureDetector
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.view.animation.AnimationUtils
import android.widget.AdapterView.AdapterContextMenuInfo
import android.widget.EditText
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.SeekBar
import android.widget.SeekBar.OnSeekBarChangeListener
import android.widget.TextView
import android.widget.ViewFlipper
import androidx.core.view.isVisible
2021-06-19 20:05:38 +02:00
import androidx.fragment.app.Fragment
import androidx.navigation.Navigation
2021-11-15 20:01:04 +01:00
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.ItemTouchHelper.ACTION_STATE_DRAG
2021-11-15 20:01:04 +01:00
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.LinearSmoothScroller
import androidx.recyclerview.widget.RecyclerView
2021-11-15 18:20:26 +01:00
import io.reactivex.rxjava3.disposables.Disposable
import java.text.DateFormat
import java.text.SimpleDateFormat
import java.util.ArrayList
import java.util.Date
import java.util.Locale
import java.util.concurrent.CancellationException
import java.util.concurrent.Executors
import java.util.concurrent.ScheduledExecutorService
import java.util.concurrent.TimeUnit
import kotlin.math.abs
import kotlin.math.max
2021-11-13 12:57:14 +01:00
import kotlinx.coroutines.CoroutineScope
2021-11-16 19:14:47 +01:00
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import org.koin.android.ext.android.inject
import org.koin.core.component.KoinComponent
import org.koin.core.component.get
2021-06-19 20:05:38 +02:00
import org.moire.ultrasonic.R
import org.moire.ultrasonic.adapters.BaseAdapter
2021-11-15 20:01:04 +01:00
import org.moire.ultrasonic.adapters.TrackViewBinder
2021-06-19 20:05:38 +02:00
import org.moire.ultrasonic.audiofx.EqualizerController
import org.moire.ultrasonic.audiofx.VisualizerController
import org.moire.ultrasonic.data.ActiveServerProvider.Companion.isOffline
2021-11-15 20:01:04 +01:00
import org.moire.ultrasonic.domain.Identifiable
2021-06-19 20:05:38 +02:00
import org.moire.ultrasonic.domain.MusicDirectory
import org.moire.ultrasonic.domain.PlayerState
import org.moire.ultrasonic.domain.RepeatMode
import org.moire.ultrasonic.fragment.FragmentTitle.Companion.setTitle
import org.moire.ultrasonic.service.DownloadFile
import org.moire.ultrasonic.service.LocalMediaPlayer
2021-06-19 20:05:38 +02:00
import org.moire.ultrasonic.service.MediaPlayerController
import org.moire.ultrasonic.service.MusicServiceFactory.getMusicService
import org.moire.ultrasonic.service.RxBus
2021-06-19 20:05:38 +02:00
import org.moire.ultrasonic.subsonic.ImageLoaderProvider
import org.moire.ultrasonic.subsonic.NetworkAndStorageChecker
import org.moire.ultrasonic.subsonic.ShareHandler
import org.moire.ultrasonic.util.CancellationToken
import org.moire.ultrasonic.util.CommunicationError
2021-06-19 20:05:38 +02:00
import org.moire.ultrasonic.util.Constants
import org.moire.ultrasonic.util.Settings
2021-06-19 20:05:38 +02:00
import org.moire.ultrasonic.util.Util
import org.moire.ultrasonic.view.AutoRepeatButton
import org.moire.ultrasonic.view.VisualizerView
import timber.log.Timber
2021-02-05 21:45:50 +01:00
/**
* Contains the Music Player screen of Ultrasonic with playback controls and the playlist
*/
2021-06-19 20:42:03 +02:00
@Suppress("LargeClass", "TooManyFunctions", "MagicNumber")
2021-11-16 19:14:47 +01:00
class PlayerFragment :
Fragment(),
GestureDetector.OnGestureListener,
KoinComponent,
CoroutineScope by CoroutineScope(Dispatchers.Main) {
2021-11-15 20:01:04 +01:00
// Settings
2021-06-19 20:05:38 +02:00
private var swipeDistance = 0
private var swipeVelocity = 0
private var jukeboxAvailable = false
private var useFiveStarRating = false
private var isEqualizerAvailable = false
private var isVisualizerAvailable = false
2021-06-19 20:42:03 +02:00
// Detectors & Callbacks
private lateinit var gestureScanner: GestureDetector
private lateinit var cancellationToken: CancellationToken
2021-11-15 20:01:04 +01:00
private lateinit var dragTouchHelper: ItemTouchHelper
2021-06-19 20:42:03 +02:00
// Data & Services
private val networkAndStorageChecker: NetworkAndStorageChecker by inject()
private val mediaPlayerController: MediaPlayerController by inject()
private val localMediaPlayer: LocalMediaPlayer by inject()
2021-06-19 20:42:03 +02:00
private val shareHandler: ShareHandler by inject()
private val imageLoaderProvider: ImageLoaderProvider by inject()
private lateinit var executorService: ScheduledExecutorService
private var currentPlaying: DownloadFile? = null
private var currentSong: MusicDirectory.Entry? = null
2021-11-15 20:01:04 +01:00
private lateinit var viewManager: LinearLayoutManager
2021-11-15 18:20:26 +01:00
private var rxBusSubscription: Disposable? = null
private var ioScope = CoroutineScope(Dispatchers.IO)
2021-06-19 20:42:03 +02:00
// Views and UI Elements
private lateinit var visualizerViewLayout: LinearLayout
private lateinit var visualizerView: VisualizerView
private lateinit var playlistNameView: EditText
private lateinit var starMenuItem: MenuItem
private lateinit var fiveStar1ImageView: ImageView
private lateinit var fiveStar2ImageView: ImageView
private lateinit var fiveStar3ImageView: ImageView
private lateinit var fiveStar4ImageView: ImageView
private lateinit var fiveStar5ImageView: ImageView
private lateinit var playlistFlipper: ViewFlipper
private lateinit var emptyTextView: TextView
private lateinit var songTitleTextView: TextView
private lateinit var albumTextView: TextView
private lateinit var artistTextView: TextView
private lateinit var albumArtImageView: ImageView
2021-11-15 20:01:04 +01:00
private lateinit var playlistView: RecyclerView
2021-06-19 20:42:03 +02:00
private lateinit var positionTextView: TextView
private lateinit var downloadTrackTextView: TextView
private lateinit var downloadTotalDurationTextView: TextView
private lateinit var durationTextView: TextView
private lateinit var pauseButton: View
private lateinit var stopButton: View
private lateinit var startButton: View
private lateinit var repeatButton: ImageView
private lateinit var hollowStar: Drawable
private lateinit var fullStar: Drawable
private lateinit var progressBar: SeekBar
2021-06-19 20:05:38 +02:00
internal val viewAdapter: BaseAdapter<Identifiable> by lazy {
BaseAdapter()
2021-11-15 20:01:04 +01:00
}
2021-06-19 20:05:38 +02:00
override fun onCreate(savedInstanceState: Bundle?) {
Util.applyTheme(this.context)
super.onCreate(savedInstanceState)
2021-02-05 21:45:50 +01:00
}
2021-06-19 20:05:38 +02:00
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
2021-06-19 20:05:38 +02:00
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.current_playing, container, false)
2021-02-05 21:45:50 +01:00
}
private fun findViews(view: View) {
2021-06-19 20:05:38 +02:00
playlistFlipper = view.findViewById(R.id.current_playing_playlist_flipper)
emptyTextView = view.findViewById(R.id.playlist_empty)
songTitleTextView = view.findViewById(R.id.current_playing_song)
albumTextView = view.findViewById(R.id.current_playing_album)
artistTextView = view.findViewById(R.id.current_playing_artist)
albumArtImageView = view.findViewById(R.id.current_playing_album_art_image)
positionTextView = view.findViewById(R.id.current_playing_position)
downloadTrackTextView = view.findViewById(R.id.current_playing_track)
downloadTotalDurationTextView = view.findViewById(R.id.current_total_duration)
durationTextView = view.findViewById(R.id.current_playing_duration)
progressBar = view.findViewById(R.id.current_playing_progress_bar)
playlistView = view.findViewById(R.id.playlist_view)
2021-06-19 20:42:03 +02:00
2021-06-19 20:05:38 +02:00
pauseButton = view.findViewById(R.id.button_pause)
stopButton = view.findViewById(R.id.button_stop)
startButton = view.findViewById(R.id.button_start)
repeatButton = view.findViewById(R.id.button_repeat)
visualizerViewLayout = view.findViewById(R.id.current_playing_visualizer_layout)
fiveStar1ImageView = view.findViewById(R.id.song_five_star_1)
fiveStar2ImageView = view.findViewById(R.id.song_five_star_2)
fiveStar3ImageView = view.findViewById(R.id.song_five_star_3)
fiveStar4ImageView = view.findViewById(R.id.song_five_star_4)
fiveStar5ImageView = view.findViewById(R.id.song_five_star_5)
2021-06-19 20:42:03 +02:00
}
@Suppress("LongMethod")
@SuppressLint("ClickableViewAccessibility")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
cancellationToken = CancellationToken()
setTitle(this, R.string.common_appname)
val windowManager = requireActivity().windowManager
val display = windowManager.defaultDisplay
val size = Point()
display.getSize(size)
val width = size.x
val height = size.y
setHasOptionsMenu(true)
2021-12-07 13:10:15 +01:00
useFiveStarRating = Settings.useFiveStarRating
2021-06-19 20:42:03 +02:00
swipeDistance = (width + height) * PERCENTAGE_OF_SCREEN_FOR_SWIPE / 100
swipeVelocity = swipeDistance
gestureScanner = GestureDetector(context, this)
// The secondary progress is an indicator of how far the song is cached.
localMediaPlayer.secondaryProgress.observe(
viewLifecycleOwner,
{
progressBar.secondaryProgress = it
}
)
2021-06-19 20:42:03 +02:00
findViews(view)
val previousButton: AutoRepeatButton = view.findViewById(R.id.button_previous)
val nextButton: AutoRepeatButton = view.findViewById(R.id.button_next)
val shuffleButton = view.findViewById<View>(R.id.button_shuffle)
val ratingLinearLayout = view.findViewById<LinearLayout>(R.id.song_rating)
if (!useFiveStarRating) ratingLinearLayout.isVisible = false
2021-06-19 20:05:38 +02:00
hollowStar = Util.getDrawableFromAttribute(view.context, R.attr.star_hollow)
fullStar = Util.getDrawableFromAttribute(view.context, R.attr.star_full)
2021-06-19 20:42:03 +02:00
fiveStar1ImageView.setOnClickListener { setSongRating(1) }
fiveStar2ImageView.setOnClickListener { setSongRating(2) }
fiveStar3ImageView.setOnClickListener { setSongRating(3) }
fiveStar4ImageView.setOnClickListener { setSongRating(4) }
fiveStar5ImageView.setOnClickListener { setSongRating(5) }
albumArtImageView.setOnTouchListener { _, me ->
gestureScanner.onTouchEvent(me)
}
albumArtImageView.setOnClickListener {
toggleFullScreenAlbumArt()
}
2021-06-19 20:05:38 +02:00
previousButton.setOnClickListener {
2021-06-19 20:42:03 +02:00
networkAndStorageChecker.warnIfNetworkOrStorageUnavailable()
2021-11-16 19:14:47 +01:00
launch(CommunicationError.getHandler(context)) {
2021-11-13 12:57:14 +01:00
mediaPlayerController.previous()
onCurrentChanged()
onSliderProgressChanged()
}
2021-06-19 20:05:38 +02:00
}
2021-06-19 20:42:03 +02:00
2021-06-19 20:05:38 +02:00
previousButton.setOnRepeatListener {
val incrementTime = Settings.incrementTime
2021-06-19 20:05:38 +02:00
changeProgress(-incrementTime)
}
2021-06-19 20:42:03 +02:00
2021-06-19 20:05:38 +02:00
nextButton.setOnClickListener {
2021-06-19 20:42:03 +02:00
networkAndStorageChecker.warnIfNetworkOrStorageUnavailable()
2021-11-16 19:14:47 +01:00
launch(CommunicationError.getHandler(context)) {
2021-11-13 12:57:14 +01:00
mediaPlayerController.next()
onCurrentChanged()
onSliderProgressChanged()
}
2021-06-19 20:05:38 +02:00
}
2021-06-19 20:42:03 +02:00
2021-06-19 20:05:38 +02:00
nextButton.setOnRepeatListener {
val incrementTime = Settings.incrementTime
2021-06-19 20:05:38 +02:00
changeProgress(incrementTime)
}
2021-06-19 20:42:03 +02:00
pauseButton.setOnClickListener {
2021-11-16 19:14:47 +01:00
launch(CommunicationError.getHandler(context)) {
2021-11-13 12:57:14 +01:00
mediaPlayerController.pause()
onCurrentChanged()
onSliderProgressChanged()
}
2021-06-19 20:42:03 +02:00
}
2021-06-19 20:42:03 +02:00
stopButton.setOnClickListener {
2021-11-16 19:14:47 +01:00
launch(CommunicationError.getHandler(context)) {
2021-11-13 12:57:14 +01:00
mediaPlayerController.reset()
onCurrentChanged()
onSliderProgressChanged()
}
2021-06-19 20:42:03 +02:00
}
2021-11-16 19:14:47 +01:00
2021-06-19 20:42:03 +02:00
startButton.setOnClickListener {
networkAndStorageChecker.warnIfNetworkOrStorageUnavailable()
2021-11-16 19:14:47 +01:00
launch(CommunicationError.getHandler(context)) {
2021-11-13 12:57:14 +01:00
start()
onCurrentChanged()
onSliderProgressChanged()
}
2021-06-19 20:42:03 +02:00
}
2021-11-16 19:14:47 +01:00
2021-06-19 20:05:38 +02:00
shuffleButton.setOnClickListener {
2021-06-19 20:42:03 +02:00
mediaPlayerController.shuffle()
2021-06-19 20:05:38 +02:00
Util.toast(activity, R.string.download_menu_shuffle_notification)
}
2021-06-19 20:42:03 +02:00
repeatButton.setOnClickListener {
val repeatMode = mediaPlayerController.repeatMode.next()
2021-06-19 20:42:03 +02:00
mediaPlayerController.repeatMode = repeatMode
2021-10-13 20:59:28 +02:00
onPlaylistChanged()
2021-06-19 20:05:38 +02:00
when (repeatMode) {
RepeatMode.OFF -> Util.toast(
context, R.string.download_repeat_off
)
RepeatMode.ALL -> Util.toast(
context, R.string.download_repeat_all
)
RepeatMode.SINGLE -> Util.toast(
context, R.string.download_repeat_single
)
else -> {
}
2021-02-05 21:45:50 +01:00
}
2021-06-19 20:42:03 +02:00
}
progressBar.setOnSeekBarChangeListener(object : OnSeekBarChangeListener {
2021-06-19 20:05:38 +02:00
override fun onStopTrackingTouch(seekBar: SeekBar) {
2021-11-16 19:14:47 +01:00
launch(CommunicationError.getHandler(context)) {
2021-11-13 12:57:14 +01:00
mediaPlayerController.seekTo(progressBar.progress)
onSliderProgressChanged()
}
2021-06-19 20:05:38 +02:00
}
override fun onStartTrackingTouch(seekBar: SeekBar) {}
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {}
})
2021-11-15 20:01:04 +01:00
initPlaylistDisplay()
2021-11-16 19:14:47 +01:00
2021-06-19 20:05:38 +02:00
registerForContextMenu(playlistView)
2021-06-19 20:42:03 +02:00
if (arguments != null && requireArguments().getBoolean(
2021-12-20 19:41:55 +01:00
Constants.INTENT_SHUFFLE,
false
)
2021-06-19 20:05:38 +02:00
) {
2021-06-19 20:42:03 +02:00
networkAndStorageChecker.warnIfNetworkOrStorageUnavailable()
2021-06-19 20:05:38 +02:00
mediaPlayerController.isShufflePlayEnabled = true
}
2021-06-19 20:42:03 +02:00
visualizerViewLayout.isVisible = false
VisualizerController.get().observe(
requireActivity(),
{ visualizerController ->
if (visualizerController != null) {
Timber.d("VisualizerController Observer.onChanged received controller")
visualizerView = VisualizerView(context)
visualizerViewLayout.addView(
visualizerView,
LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.MATCH_PARENT
)
2021-06-19 20:05:38 +02:00
)
visualizerViewLayout.isVisible = visualizerView.isActive
visualizerView.setOnTouchListener { _, _ ->
visualizerView.isActive = !visualizerView.isActive
mediaPlayerController.showVisualization = visualizerView.isActive
true
}
isVisualizerAvailable = true
2021-06-19 20:05:38 +02:00
} else {
Timber.d("VisualizerController Observer.onChanged has no controller")
visualizerViewLayout.isVisible = false
isVisualizerAvailable = false
2021-02-05 21:45:50 +01:00
}
2021-06-19 20:05:38 +02:00
}
)
2021-06-19 20:42:03 +02:00
EqualizerController.get().observe(
requireActivity(),
{ equalizerController ->
isEqualizerAvailable = if (equalizerController != null) {
Timber.d("EqualizerController Observer.onChanged received controller")
true
} else {
Timber.d("EqualizerController Observer.onChanged has no controller")
false
}
2021-06-19 20:05:38 +02:00
}
)
// Observe playlist changes and update the UI
2021-11-15 18:20:26 +01:00
rxBusSubscription = RxBus.playlistObservable.subscribe {
onPlaylistChanged()
}
// Query the Jukebox state in an IO Context
ioScope.launch(CommunicationError.getHandler(context)) {
2021-06-19 20:05:38 +02:00
try {
2021-06-19 20:42:03 +02:00
jukeboxAvailable = mediaPlayerController.isJukeboxAvailable
} catch (all: Exception) {
Timber.e(all)
2021-06-19 20:05:38 +02:00
}
}
2021-06-19 20:42:03 +02:00
view.setOnTouchListener { _, event -> gestureScanner.onTouchEvent(event) }
2021-02-05 21:45:50 +01:00
}
2021-06-19 20:05:38 +02:00
override fun onResume() {
super.onResume()
2021-06-19 20:42:03 +02:00
if (mediaPlayerController.currentPlaying == null) {
playlistFlipper.displayedChild = 1
2021-06-19 20:05:38 +02:00
} else {
2021-02-05 21:45:50 +01:00
// Download list and Album art must be updated when Resumed
2021-10-13 20:59:28 +02:00
onPlaylistChanged()
2021-06-19 20:05:38 +02:00
onCurrentChanged()
2021-02-05 21:45:50 +01:00
}
2021-06-19 20:05:38 +02:00
val handler = Handler()
// TODO Use Rx for Update instead of polling!
2021-06-19 20:05:38 +02:00
val runnable = Runnable { handler.post { update(cancellationToken) } }
executorService = Executors.newSingleThreadScheduledExecutor()
executorService.scheduleWithFixedDelay(runnable, 0L, 500L, TimeUnit.MILLISECONDS)
2021-06-19 20:42:03 +02:00
if (mediaPlayerController.keepScreenOn) {
requireActivity().window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
2021-06-19 20:05:38 +02:00
} else {
2021-06-19 20:42:03 +02:00
requireActivity().window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
2021-02-05 21:45:50 +01:00
}
2021-06-19 20:42:03 +02:00
if (::visualizerView.isInitialized) {
visualizerView.isActive = mediaPlayerController.showVisualization
}
2021-06-19 20:42:03 +02:00
requireActivity().invalidateOptionsMenu()
2021-02-05 21:45:50 +01:00
}
// Scroll to current playing.
2021-06-19 20:05:38 +02:00
private fun scrollToCurrent() {
2021-11-15 20:01:04 +01:00
val index = mediaPlayerController.playList.indexOf(currentPlaying)
if (index != -1) {
val smoothScroller = LinearSmoothScroller(context)
smoothScroller.targetPosition = index
viewManager.startSmoothScroll(smoothScroller)
2021-02-05 21:45:50 +01:00
}
}
2021-06-19 20:05:38 +02:00
override fun onPause() {
super.onPause()
2021-06-19 20:42:03 +02:00
executorService.shutdown()
if (::visualizerView.isInitialized) {
visualizerView.isActive = mediaPlayerController.showVisualization
}
2021-02-05 21:45:50 +01:00
}
2021-06-19 20:05:38 +02:00
override fun onDestroyView() {
2021-11-15 18:20:26 +01:00
rxBusSubscription?.dispose()
2021-11-16 19:14:47 +01:00
cancel("CoroutineScope cancelled because the view was destroyed")
2021-06-19 20:42:03 +02:00
cancellationToken.cancel()
2021-06-19 20:05:38 +02:00
super.onDestroyView()
2021-02-05 21:45:50 +01:00
}
2021-06-19 20:05:38 +02:00
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.nowplaying, menu)
super.onCreateOptionsMenu(menu, inflater)
2021-02-05 21:45:50 +01:00
}
2021-06-19 20:42:03 +02:00
@Suppress("ComplexMethod", "LongMethod", "NestedBlockDepth")
2021-06-19 20:05:38 +02:00
override fun onPrepareOptionsMenu(menu: Menu) {
super.onPrepareOptionsMenu(menu)
val screenOption = menu.findItem(R.id.menu_item_screen_on_off)
val jukeboxOption = menu.findItem(R.id.menu_item_jukebox)
val equalizerMenuItem = menu.findItem(R.id.menu_item_equalizer)
val visualizerMenuItem = menu.findItem(R.id.menu_item_visualizer)
val shareMenuItem = menu.findItem(R.id.menu_item_share)
val shareSongMenuItem = menu.findItem(R.id.menu_item_share_song)
2021-06-19 20:05:38 +02:00
starMenuItem = menu.findItem(R.id.menu_item_star)
val bookmarkMenuItem = menu.findItem(R.id.menu_item_bookmark_set)
val bookmarkRemoveMenuItem = menu.findItem(R.id.menu_item_bookmark_delete)
2021-06-19 20:42:03 +02:00
2021-06-19 20:05:38 +02:00
if (isOffline()) {
if (shareMenuItem != null) {
shareMenuItem.isVisible = false
}
2021-06-19 20:42:03 +02:00
starMenuItem.isVisible = false
2021-06-19 20:05:38 +02:00
if (bookmarkMenuItem != null) {
bookmarkMenuItem.isVisible = false
}
if (bookmarkRemoveMenuItem != null) {
bookmarkRemoveMenuItem.isVisible = false
2021-02-05 21:45:50 +01:00
}
}
2021-06-19 20:05:38 +02:00
if (equalizerMenuItem != null) {
equalizerMenuItem.isEnabled = isEqualizerAvailable
equalizerMenuItem.isVisible = isEqualizerAvailable
2021-02-05 21:45:50 +01:00
}
2021-06-19 20:05:38 +02:00
if (visualizerMenuItem != null) {
visualizerMenuItem.isEnabled = isVisualizerAvailable
visualizerMenuItem.isVisible = isVisualizerAvailable
2021-02-05 21:45:50 +01:00
}
2021-06-19 20:42:03 +02:00
val mediaPlayerController = mediaPlayerController
val downloadFile = mediaPlayerController.currentPlaying
2021-06-19 20:42:03 +02:00
if (downloadFile != null) {
currentSong = downloadFile.song
}
2021-06-19 20:42:03 +02:00
if (useFiveStarRating) starMenuItem.isVisible = false
2021-06-19 20:42:03 +02:00
if (currentSong != null) {
starMenuItem.icon = if (currentSong!!.starred) fullStar else hollowStar
shareSongMenuItem.isVisible = true
2021-06-19 20:42:03 +02:00
} else {
starMenuItem.icon = hollowStar
shareSongMenuItem.isVisible = false
2021-06-19 20:42:03 +02:00
}
2021-06-19 20:42:03 +02:00
if (mediaPlayerController.keepScreenOn) {
screenOption?.setTitle(R.string.download_menu_screen_off)
} else {
screenOption?.setTitle(R.string.download_menu_screen_on)
}
2021-06-19 20:42:03 +02:00
if (jukeboxOption != null) {
jukeboxOption.isEnabled = jukeboxAvailable
jukeboxOption.isVisible = jukeboxAvailable
if (mediaPlayerController.isJukeboxEnabled) {
jukeboxOption.setTitle(R.string.download_menu_jukebox_off)
2021-06-19 20:05:38 +02:00
} else {
2021-06-19 20:42:03 +02:00
jukeboxOption.setTitle(R.string.download_menu_jukebox_on)
2021-02-05 21:45:50 +01:00
}
}
}
2021-06-19 20:05:38 +02:00
override fun onCreateContextMenu(menu: ContextMenu, view: View, menuInfo: ContextMenuInfo?) {
super.onCreateContextMenu(menu, view, menuInfo)
if (view === playlistView) {
val info = menuInfo as AdapterContextMenuInfo?
2021-11-15 20:01:04 +01:00
val downloadFile = viewAdapter.getCurrentList()[info!!.position] as DownloadFile
2021-06-19 20:42:03 +02:00
val menuInflater = requireActivity().menuInflater
2021-06-19 20:05:38 +02:00
menuInflater.inflate(R.menu.nowplaying_context, menu)
2021-06-19 20:42:03 +02:00
val song: MusicDirectory.Entry?
song = downloadFile.song
if (song.parent == null) {
2021-06-19 20:05:38 +02:00
val menuItem = menu.findItem(R.id.menu_show_album)
if (menuItem != null) {
menuItem.isVisible = false
2021-02-05 21:45:50 +01:00
}
}
2021-06-19 20:42:03 +02:00
if (isOffline() || !Settings.shouldUseId3Tags) {
2021-06-19 20:42:03 +02:00
menu.findItem(R.id.menu_show_artist)?.isVisible = false
2021-02-05 21:45:50 +01:00
}
2021-06-19 20:42:03 +02:00
2021-06-19 20:05:38 +02:00
if (isOffline()) {
2021-06-19 20:42:03 +02:00
menu.findItem(R.id.menu_lyrics)?.isVisible = false
2021-02-05 21:45:50 +01:00
}
}
}
2021-06-19 20:05:38 +02:00
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return menuItemSelected(item.itemId, null) || super.onOptionsItemSelected(item)
2021-02-05 21:45:50 +01:00
}
2021-06-19 20:42:03 +02:00
@Suppress("ComplexMethod", "LongMethod", "ReturnCount")
2021-06-19 20:05:38 +02:00
private fun menuItemSelected(menuItemId: Int, song: DownloadFile?): Boolean {
var entry: MusicDirectory.Entry? = null
val bundle: Bundle
if (song != null) {
entry = song.song
2021-02-05 21:45:50 +01:00
}
2021-06-19 20:42:03 +02:00
when (menuItemId) {
R.id.menu_show_artist -> {
if (entry == null) return false
if (Settings.shouldUseId3Tags) {
2021-06-19 20:42:03 +02:00
bundle = Bundle()
2021-11-30 21:21:50 +01:00
bundle.putString(Constants.INTENT_ID, entry.artistId)
bundle.putString(Constants.INTENT_NAME, entry.artist)
bundle.putString(Constants.INTENT_PARENT_ID, entry.artistId)
bundle.putBoolean(Constants.INTENT_ARTIST, true)
Navigation.findNavController(requireView())
.navigate(R.id.playerToSelectAlbum, bundle)
2021-06-19 20:42:03 +02:00
}
return true
}
2021-06-19 20:42:03 +02:00
R.id.menu_show_album -> {
if (entry == null) return false
val albumId = if (Settings.shouldUseId3Tags) entry.albumId else entry.parent
2021-06-19 20:05:38 +02:00
bundle = Bundle()
2021-11-30 21:21:50 +01:00
bundle.putString(Constants.INTENT_ID, albumId)
bundle.putString(Constants.INTENT_NAME, entry.album)
bundle.putString(Constants.INTENT_PARENT_ID, entry.parent)
bundle.putBoolean(Constants.INTENT_IS_ALBUM, true)
Navigation.findNavController(requireView())
.navigate(R.id.playerToSelectAlbum, bundle)
2021-06-19 20:42:03 +02:00
return true
2021-06-19 20:05:38 +02:00
}
2021-06-19 20:42:03 +02:00
R.id.menu_lyrics -> {
if (entry == null) return false
2021-06-19 20:42:03 +02:00
bundle = Bundle()
2021-11-30 21:21:50 +01:00
bundle.putString(Constants.INTENT_ARTIST, entry.artist)
bundle.putString(Constants.INTENT_TITLE, entry.title)
Navigation.findNavController(requireView()).navigate(R.id.playerToLyrics, bundle)
2021-06-19 20:42:03 +02:00
return true
2021-06-19 20:05:38 +02:00
}
2021-06-19 20:42:03 +02:00
R.id.menu_remove -> {
mediaPlayerController.removeFromPlaylist(song!!)
2021-10-13 20:59:28 +02:00
onPlaylistChanged()
2021-06-19 20:42:03 +02:00
return true
}
2021-06-19 20:42:03 +02:00
R.id.menu_item_screen_on_off -> {
val window = requireActivity().window
2021-06-19 20:42:03 +02:00
if (mediaPlayerController.keepScreenOn) {
window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
2021-06-19 20:42:03 +02:00
mediaPlayerController.keepScreenOn = false
} else {
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
2021-06-19 20:42:03 +02:00
mediaPlayerController.keepScreenOn = true
}
return true
}
2021-06-19 20:42:03 +02:00
R.id.menu_shuffle -> {
mediaPlayerController.shuffle()
Util.toast(context, R.string.download_menu_shuffle_notification)
return true
2021-06-19 20:05:38 +02:00
}
2021-06-19 20:42:03 +02:00
R.id.menu_item_equalizer -> {
Navigation.findNavController(requireView()).navigate(R.id.playerToEqualizer)
2021-06-19 20:42:03 +02:00
return true
}
2021-06-19 20:42:03 +02:00
R.id.menu_item_visualizer -> {
val active = !visualizerView.isActive
visualizerView.isActive = active
visualizerViewLayout.isVisible = visualizerView.isActive
2021-06-19 20:42:03 +02:00
mediaPlayerController.showVisualization = visualizerView.isActive
Util.toast(
context,
if (active) R.string.download_visualizer_on
else R.string.download_visualizer_off
2021-06-19 20:42:03 +02:00
)
2021-06-19 20:05:38 +02:00
return true
}
2021-06-19 20:42:03 +02:00
R.id.menu_item_jukebox -> {
val jukeboxEnabled = !mediaPlayerController.isJukeboxEnabled
mediaPlayerController.isJukeboxEnabled = jukeboxEnabled
Util.toast(
context,
if (jukeboxEnabled) R.string.download_jukebox_on
else R.string.download_jukebox_off,
2021-06-19 20:42:03 +02:00
false
)
return true
2021-06-19 20:05:38 +02:00
}
2021-06-19 20:42:03 +02:00
R.id.menu_item_toggle_list -> {
toggleFullScreenAlbumArt()
2021-06-19 20:05:38 +02:00
return true
}
2021-06-19 20:42:03 +02:00
R.id.menu_item_clear_playlist -> {
mediaPlayerController.isShufflePlayEnabled = false
mediaPlayerController.clear()
2021-10-13 20:59:28 +02:00
onPlaylistChanged()
2021-06-19 20:42:03 +02:00
return true
}
R.id.menu_item_save_playlist -> {
if (mediaPlayerController.playlistSize > 0) {
showSavePlaylistDialog()
}
2021-06-19 20:05:38 +02:00
return true
}
2021-06-19 20:42:03 +02:00
R.id.menu_item_star -> {
if (currentSong == null) return true
2021-06-19 20:42:03 +02:00
val isStarred = currentSong!!.starred
val id = currentSong!!.id
if (isStarred) {
starMenuItem.icon = hollowStar
currentSong!!.starred = false
} else {
starMenuItem.icon = fullStar
currentSong!!.starred = true
}
Thread {
val musicService = getMusicService()
try {
if (isStarred) {
musicService.unstar(id, null, null)
} else {
musicService.star(id, null, null)
2021-02-05 21:45:50 +01:00
}
2021-06-19 20:42:03 +02:00
} catch (all: Exception) {
Timber.e(all)
}
}.start()
return true
}
R.id.menu_item_bookmark_set -> {
if (currentSong == null) return true
2021-06-19 20:42:03 +02:00
val songId = currentSong!!.id
val playerPosition = mediaPlayerController.playerPosition
currentSong!!.bookmarkPosition = playerPosition
val bookmarkTime = Util.formatTotalDuration(playerPosition.toLong(), true)
Thread {
val musicService = getMusicService()
try {
musicService.createBookmark(songId, playerPosition)
} catch (all: Exception) {
Timber.e(all)
2021-02-05 21:45:50 +01:00
}
2021-06-19 20:42:03 +02:00
}.start()
val msg = resources.getString(
R.string.download_bookmark_set_at_position,
bookmarkTime
)
2021-06-19 20:42:03 +02:00
Util.toast(context, msg)
return true
}
R.id.menu_item_bookmark_delete -> {
if (currentSong == null) return true
2021-06-19 20:42:03 +02:00
val bookmarkSongId = currentSong!!.id
currentSong!!.bookmarkPosition = 0
Thread {
val musicService = getMusicService()
try {
musicService.deleteBookmark(bookmarkSongId)
} catch (all: Exception) {
Timber.e(all)
}
}.start()
Util.toast(context, R.string.download_bookmark_removed)
return true
}
2021-06-19 20:42:03 +02:00
R.id.menu_item_share -> {
val mediaPlayerController = mediaPlayerController
val entries: MutableList<MusicDirectory.Entry?> = ArrayList()
val downloadServiceSongs = mediaPlayerController.playList
for (downloadFile in downloadServiceSongs) {
val playlistEntry = downloadFile.song
entries.add(playlistEntry)
}
shareHandler.createShare(this, entries, null, cancellationToken)
return true
}
R.id.menu_item_share_song -> {
if (currentSong == null) return true
val entries: MutableList<MusicDirectory.Entry?> = ArrayList()
entries.add(currentSong)
shareHandler.createShare(this, entries, null, cancellationToken)
return true
}
2021-06-19 20:42:03 +02:00
else -> return false
2021-02-05 21:45:50 +01:00
}
}
2021-06-19 20:05:38 +02:00
private fun update(cancel: CancellationToken?) {
if (cancel!!.isCancellationRequested) return
2021-06-19 20:42:03 +02:00
val mediaPlayerController = mediaPlayerController
2021-06-19 20:05:38 +02:00
if (currentPlaying != mediaPlayerController.currentPlaying) {
onCurrentChanged()
2021-02-05 21:45:50 +01:00
}
2021-06-19 20:05:38 +02:00
onSliderProgressChanged()
2021-06-19 20:42:03 +02:00
requireActivity().invalidateOptionsMenu()
2021-02-05 21:45:50 +01:00
}
2021-06-19 20:05:38 +02:00
private fun savePlaylistInBackground(playlistName: String) {
Util.toast(context, resources.getString(R.string.download_playlist_saving, playlistName))
2021-06-19 20:42:03 +02:00
mediaPlayerController.suggestedPlaylistName = playlistName
2021-02-05 21:45:50 +01:00
ioScope.launch {
val entries = mediaPlayerController.playList.map {
it.song
2021-02-05 21:45:50 +01:00
}
2021-11-13 12:57:14 +01:00
val musicService = getMusicService()
musicService.createPlaylist(null, playlistName, entries)
}.invokeOnCompletion {
if (it == null || it is CancellationException) {
Util.toast(context, R.string.download_playlist_done)
} else {
Timber.e(it, "Exception has occurred in savePlaylistInBackground")
val msg = String.format(
Locale.ROOT,
2021-06-19 20:05:38 +02:00
"%s %s",
resources.getString(R.string.download_playlist_error),
CommunicationError.getErrorMessage(it, context)
2021-06-19 20:05:38 +02:00
)
Util.toast(context, msg)
2021-02-05 21:45:50 +01:00
}
2021-11-13 12:57:14 +01:00
}
2021-02-05 21:45:50 +01:00
}
2021-06-19 20:05:38 +02:00
private fun toggleFullScreenAlbumArt() {
2021-06-19 20:42:03 +02:00
if (playlistFlipper.displayedChild == 1) {
playlistFlipper.inAnimation =
2021-06-19 20:05:38 +02:00
AnimationUtils.loadAnimation(context, R.anim.push_down_in)
2021-06-19 20:42:03 +02:00
playlistFlipper.outAnimation =
2021-06-19 20:05:38 +02:00
AnimationUtils.loadAnimation(context, R.anim.push_down_out)
2021-06-19 20:42:03 +02:00
playlistFlipper.displayedChild = 0
2021-06-19 20:05:38 +02:00
} else {
2021-06-19 20:42:03 +02:00
playlistFlipper.inAnimation =
2021-06-19 20:05:38 +02:00
AnimationUtils.loadAnimation(context, R.anim.push_up_in)
2021-06-19 20:42:03 +02:00
playlistFlipper.outAnimation =
2021-06-19 20:05:38 +02:00
AnimationUtils.loadAnimation(context, R.anim.push_up_out)
2021-06-19 20:42:03 +02:00
playlistFlipper.displayedChild = 1
2021-02-05 21:45:50 +01:00
}
2021-06-19 20:05:38 +02:00
scrollToCurrent()
2021-02-05 21:45:50 +01:00
}
2021-06-19 20:05:38 +02:00
private fun start() {
2021-06-19 20:42:03 +02:00
val service = mediaPlayerController
2021-06-19 20:05:38 +02:00
val state = service.playerState
if (state === PlayerState.PAUSED ||
state === PlayerState.COMPLETED || state === PlayerState.STOPPED
) {
2021-06-19 20:05:38 +02:00
service.start()
} else if (state === PlayerState.IDLE) {
2021-06-19 20:42:03 +02:00
networkAndStorageChecker.warnIfNetworkOrStorageUnavailable()
val current = mediaPlayerController.currentPlayingNumberOnPlaylist
2021-06-19 20:05:38 +02:00
if (current == -1) {
service.play(0)
} else {
service.play(current)
2021-02-05 21:45:50 +01:00
}
}
}
2021-11-15 20:01:04 +01:00
private fun initPlaylistDisplay() {
// Create a View Manager
viewManager = LinearLayoutManager(this.context)
// Hook up the view with the manager and the adapter
playlistView.apply {
setHasFixedSize(true)
layoutManager = viewManager
adapter = viewAdapter
}
// Create listener
val listener: ((DownloadFile) -> Unit) = { file ->
2021-11-15 20:01:04 +01:00
val list = mediaPlayerController.playList
val index = list.indexOf(file)
mediaPlayerController.play(index)
onCurrentChanged()
onSliderProgressChanged()
}
viewAdapter.register(
TrackViewBinder(
onItemClick = listener,
2021-11-15 20:01:04 +01:00
checkable = false,
draggable = true,
context = requireContext(),
lifecycleOwner = viewLifecycleOwner,
2021-12-07 00:06:41 +01:00
).apply {
this.startDrag = { holder ->
dragTouchHelper.startDrag(holder)
2021-02-05 21:45:50 +01:00
}
}
2021-11-15 20:01:04 +01:00
)
dragTouchHelper = ItemTouchHelper(object : ItemTouchHelper.SimpleCallback(
2021-11-30 21:50:53 +01:00
ItemTouchHelper.UP or ItemTouchHelper.DOWN,
ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
2021-11-15 20:01:04 +01:00
) {
2021-02-05 21:45:50 +01:00
2021-11-15 20:01:04 +01:00
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
2021-02-05 21:45:50 +01:00
2021-11-15 20:01:04 +01:00
val from = viewHolder.bindingAdapterPosition
val to = target.bindingAdapterPosition
2021-06-19 20:42:03 +02:00
// Move it in the data set
mediaPlayerController.moveItemInPlaylist(from, to)
viewAdapter.submitList(mediaPlayerController.playList)
2021-06-19 20:42:03 +02:00
2021-11-15 20:01:04 +01:00
return true
}
2021-11-30 21:50:53 +01:00
// Swipe to delete from playlist
2021-11-15 20:01:04 +01:00
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
2021-11-30 21:50:53 +01:00
val pos = viewHolder.bindingAdapterPosition
val file = mediaPlayerController.playList[pos]
mediaPlayerController.removeFromPlaylist(file)
val songRemoved = String.format(
resources.getString(R.string.download_song_removed),
file.song.title
)
Util.toast(context, songRemoved)
viewAdapter.submitList(mediaPlayerController.playList)
viewAdapter.notifyDataSetChanged()
2021-02-05 21:45:50 +01:00
}
override fun onSelectedChanged(
viewHolder: RecyclerView.ViewHolder?,
actionState: Int
) {
super.onSelectedChanged(viewHolder, actionState)
if (actionState == ACTION_STATE_DRAG) {
viewHolder?.itemView?.alpha = 0.6f
}
}
override fun clearView(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder
) {
super.clearView(recyclerView, viewHolder)
viewHolder.itemView.alpha = 1.0f
}
override fun isLongPressDragEnabled(): Boolean {
return false
2021-02-05 21:45:50 +01:00
}
2021-06-19 20:05:38 +02:00
}
2021-11-15 20:01:04 +01:00
)
dragTouchHelper.attachToRecyclerView(playlistView)
}
private fun onPlaylistChanged() {
val mediaPlayerController = mediaPlayerController
val list = mediaPlayerController.playList
emptyTextView.setText(R.string.playlist_empty)
viewAdapter.submitList(list)
emptyTextView.isVisible = list.isEmpty()
2021-06-19 20:05:38 +02:00
when (mediaPlayerController.repeatMode) {
2021-06-19 20:42:03 +02:00
RepeatMode.OFF -> repeatButton.setImageDrawable(
2021-06-19 20:05:38 +02:00
Util.getDrawableFromAttribute(
requireContext(), R.attr.media_repeat_off
2021-06-19 20:05:38 +02:00
)
)
2021-06-19 20:42:03 +02:00
RepeatMode.ALL -> repeatButton.setImageDrawable(
2021-06-19 20:05:38 +02:00
Util.getDrawableFromAttribute(
requireContext(), R.attr.media_repeat_all
2021-06-19 20:05:38 +02:00
)
)
2021-06-19 20:42:03 +02:00
RepeatMode.SINGLE -> repeatButton.setImageDrawable(
2021-06-19 20:05:38 +02:00
Util.getDrawableFromAttribute(
requireContext(), R.attr.media_repeat_single
2021-06-19 20:05:38 +02:00
)
)
else -> {
2021-02-05 21:45:50 +01:00
}
}
}
2021-06-19 20:05:38 +02:00
private fun onCurrentChanged() {
currentPlaying = mediaPlayerController.currentPlaying
scrollToCurrent()
val totalDuration = mediaPlayerController.playListDuration
val totalSongs = mediaPlayerController.playlistSize.toLong()
val currentSongIndex = mediaPlayerController.currentPlayingNumberOnPlaylist + 1
val duration = Util.formatTotalDuration(totalDuration)
val trackFormat =
String.format(Locale.getDefault(), "%d / %d", currentSongIndex, totalSongs)
if (currentPlaying != null) {
currentSong = currentPlaying!!.song
2021-06-19 20:42:03 +02:00
songTitleTextView.text = currentSong!!.title
albumTextView.text = currentSong!!.album
artistTextView.text = currentSong!!.artist
downloadTrackTextView.text = trackFormat
downloadTotalDurationTextView.text = duration
imageLoaderProvider.getImageLoader()
2021-06-19 20:05:38 +02:00
.loadImage(albumArtImageView, currentSong, true, 0)
displaySongRating()
} else {
currentSong = null
2021-06-19 20:42:03 +02:00
songTitleTextView.text = null
albumTextView.text = null
artistTextView.text = null
downloadTrackTextView.text = null
downloadTotalDurationTextView.text = null
imageLoaderProvider.getImageLoader()
.loadImage(albumArtImageView, null, true, 0)
2021-02-05 21:45:50 +01:00
}
}
2021-11-16 18:29:20 +01:00
@Suppress("LongMethod", "ComplexMethod")
2021-11-16 19:14:47 +01:00
@Synchronized
2021-06-19 20:05:38 +02:00
private fun onSliderProgressChanged() {
2021-11-13 12:57:14 +01:00
2021-11-16 19:14:47 +01:00
val isJukeboxEnabled: Boolean = mediaPlayerController.isJukeboxEnabled
val millisPlayed: Int = max(0, mediaPlayerController.playerPosition)
val duration: Int = mediaPlayerController.playerDuration
val playerState: PlayerState = mediaPlayerController.playerState
2021-11-13 12:57:14 +01:00
2021-11-16 19:14:47 +01:00
if (cancellationToken.isCancellationRequested) return
if (currentPlaying != null) {
positionTextView.text = Util.formatTotalDuration(millisPlayed.toLong(), true)
durationTextView.text = Util.formatTotalDuration(duration.toLong(), true)
progressBar.max =
if (duration == 0) 100 else duration // Work-around for apparent bug.
progressBar.progress = millisPlayed
progressBar.isEnabled = currentPlaying!!.isWorkDone || isJukeboxEnabled
} else {
positionTextView.setText(R.string.util_zero_time)
durationTextView.setText(R.string.util_no_time)
progressBar.progress = 0
progressBar.max = 0
progressBar.isEnabled = false
}
2021-06-19 20:05:38 +02:00
2021-11-16 19:14:47 +01:00
when (playerState) {
PlayerState.DOWNLOADING -> {
val progress =
if (currentPlaying != null) currentPlaying!!.progress.value!! else 0
val downloadStatus = resources.getString(
R.string.download_playerstate_downloading,
Util.formatPercentage(progress)
2021-11-13 12:57:14 +01:00
)
2021-11-16 19:14:47 +01:00
setTitle(this@PlayerFragment, downloadStatus)
2021-11-13 12:57:14 +01:00
}
2021-11-16 19:14:47 +01:00
PlayerState.PREPARING -> setTitle(
this@PlayerFragment,
R.string.download_playerstate_buffering
)
PlayerState.STARTED -> {
if (mediaPlayerController.isShufflePlayEnabled) {
setTitle(
this@PlayerFragment,
R.string.download_playerstate_playing_shuffle
)
} else {
setTitle(this@PlayerFragment, R.string.common_appname)
2021-11-13 12:57:14 +01:00
}
2021-02-05 21:45:50 +01:00
}
2021-11-16 19:14:47 +01:00
PlayerState.IDLE,
PlayerState.PREPARED,
PlayerState.STOPPED,
PlayerState.PAUSED,
PlayerState.COMPLETED -> {
}
else -> setTitle(this@PlayerFragment, R.string.common_appname)
}
2021-11-13 12:57:14 +01:00
2021-11-16 19:14:47 +01:00
when (playerState) {
PlayerState.STARTED -> {
pauseButton.isVisible = true
stopButton.isVisible = false
startButton.isVisible = false
}
PlayerState.DOWNLOADING, PlayerState.PREPARING -> {
pauseButton.isVisible = false
stopButton.isVisible = true
startButton.isVisible = false
}
else -> {
pauseButton.isVisible = false
stopButton.isVisible = false
startButton.isVisible = true
}
2021-02-05 21:45:50 +01:00
}
2021-11-16 19:14:47 +01:00
// TODO: It would be a lot nicer if MediaPlayerController would send an event
// when this is necessary instead of updating every time
displaySongRating()
2021-02-05 21:45:50 +01:00
}
2021-06-19 20:05:38 +02:00
private fun changeProgress(ms: Int) {
2021-11-16 19:14:47 +01:00
launch(CommunicationError.getHandler(context)) {
2021-11-13 12:57:14 +01:00
val msPlayed: Int = max(0, mediaPlayerController.playerPosition)
val duration = mediaPlayerController.playerDuration
val seekTo = (msPlayed + ms).coerceAtMost(duration)
mediaPlayerController.seekTo(seekTo)
progressBar.progress = seekTo
}
2021-02-05 21:45:50 +01:00
}
2021-06-19 20:05:38 +02:00
override fun onDown(me: MotionEvent): Boolean {
return false
}
2021-02-05 21:45:50 +01:00
2021-06-19 20:42:03 +02:00
@Suppress("ReturnCount")
2021-06-19 20:05:38 +02:00
override fun onFling(
e1: MotionEvent,
e2: MotionEvent,
velocityX: Float,
velocityY: Float
): Boolean {
val e1X = e1.x
val e2X = e2.x
val e1Y = e1.y
val e2Y = e2.y
2021-06-19 20:42:03 +02:00
val absX = abs(velocityX)
val absY = abs(velocityY)
2021-02-05 21:45:50 +01:00
// Right to Left swipe
2021-06-19 20:05:38 +02:00
if (e1X - e2X > swipeDistance && absX > swipeVelocity) {
2021-06-19 20:42:03 +02:00
networkAndStorageChecker.warnIfNetworkOrStorageUnavailable()
2021-06-19 20:05:38 +02:00
mediaPlayerController.next()
onCurrentChanged()
onSliderProgressChanged()
return true
2021-02-05 21:45:50 +01:00
}
// Left to Right swipe
2021-06-19 20:05:38 +02:00
if (e2X - e1X > swipeDistance && absX > swipeVelocity) {
2021-06-19 20:42:03 +02:00
networkAndStorageChecker.warnIfNetworkOrStorageUnavailable()
2021-06-19 20:05:38 +02:00
mediaPlayerController.previous()
onCurrentChanged()
onSliderProgressChanged()
return true
2021-02-05 21:45:50 +01:00
}
// Top to Bottom swipe
2021-06-19 20:05:38 +02:00
if (e2Y - e1Y > swipeDistance && absY > swipeVelocity) {
2021-06-19 20:42:03 +02:00
networkAndStorageChecker.warnIfNetworkOrStorageUnavailable()
2021-06-19 20:05:38 +02:00
mediaPlayerController.seekTo(mediaPlayerController.playerPosition + 30000)
onSliderProgressChanged()
return true
2021-02-05 21:45:50 +01:00
}
// Bottom to Top swipe
2021-06-19 20:05:38 +02:00
if (e1Y - e2Y > swipeDistance && absY > swipeVelocity) {
2021-06-19 20:42:03 +02:00
networkAndStorageChecker.warnIfNetworkOrStorageUnavailable()
2021-06-19 20:05:38 +02:00
mediaPlayerController.seekTo(mediaPlayerController.playerPosition - 8000)
onSliderProgressChanged()
return true
2021-02-05 21:45:50 +01:00
}
2021-06-19 20:05:38 +02:00
return false
2021-02-05 21:45:50 +01:00
}
2021-06-19 20:05:38 +02:00
override fun onLongPress(e: MotionEvent) {}
override fun onScroll(
e1: MotionEvent,
e2: MotionEvent,
distanceX: Float,
distanceY: Float
): Boolean {
return false
2021-02-05 21:45:50 +01:00
}
2021-06-19 20:05:38 +02:00
override fun onShowPress(e: MotionEvent) {}
override fun onSingleTapUp(e: MotionEvent): Boolean {
return false
2021-02-05 21:45:50 +01:00
}
2021-06-19 20:05:38 +02:00
private fun displaySongRating() {
var rating = 0
if (currentSong?.userRating != null) {
rating = currentSong!!.userRating!!
}
2021-06-19 20:42:03 +02:00
fiveStar1ImageView.setImageDrawable(if (rating > 0) fullStar else hollowStar)
fiveStar2ImageView.setImageDrawable(if (rating > 1) fullStar else hollowStar)
fiveStar3ImageView.setImageDrawable(if (rating > 2) fullStar else hollowStar)
fiveStar4ImageView.setImageDrawable(if (rating > 3) fullStar else hollowStar)
fiveStar5ImageView.setImageDrawable(if (rating > 4) fullStar else hollowStar)
2021-02-05 21:45:50 +01:00
}
2021-06-19 20:05:38 +02:00
private fun setSongRating(rating: Int) {
if (currentSong == null) return
displaySongRating()
2021-06-19 20:42:03 +02:00
mediaPlayerController.setSongRating(rating)
2021-02-05 21:45:50 +01:00
}
2021-06-19 20:05:38 +02:00
private fun showSavePlaylistDialog() {
val layout = LayoutInflater.from(this.context).inflate(R.layout.save_playlist, null)
playlistNameView = layout.findViewById(R.id.save_playlist_name)
2021-06-19 20:42:03 +02:00
val builder: AlertDialog.Builder = AlertDialog.Builder(context)
2021-06-19 20:05:38 +02:00
builder.setTitle(R.string.download_playlist_title)
builder.setMessage(R.string.download_playlist_name)
2021-06-19 20:42:03 +02:00
builder.setPositiveButton(R.string.common_save) { _, _ ->
2021-06-19 20:05:38 +02:00
savePlaylistInBackground(
2021-06-19 20:42:03 +02:00
playlistNameView.text.toString()
2021-06-19 20:05:38 +02:00
)
2021-02-05 21:45:50 +01:00
}
2021-06-19 20:42:03 +02:00
builder.setNegativeButton(R.string.common_cancel) { dialog, _ -> dialog.cancel() }
2021-06-19 20:05:38 +02:00
builder.setView(layout)
builder.setCancelable(true)
val dialog = builder.create()
2021-06-19 20:42:03 +02:00
val playlistName = mediaPlayerController.suggestedPlaylistName
2021-06-19 20:05:38 +02:00
if (playlistName != null) {
2021-06-19 20:42:03 +02:00
playlistNameView.setText(playlistName)
2021-06-19 20:05:38 +02:00
} else {
val dateFormat: DateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
2021-06-19 20:42:03 +02:00
playlistNameView.setText(dateFormat.format(Date()))
2021-02-05 21:45:50 +01:00
}
2021-06-19 20:05:38 +02:00
dialog.show()
}
2021-02-05 21:45:50 +01:00
2021-06-19 20:05:38 +02:00
companion object {
private const val PERCENTAGE_OF_SCREEN_FOR_SWIPE = 5
2021-02-05 21:45:50 +01:00
}
2021-06-19 20:42:03 +02:00
}