123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292 |
- package fr.forum_thalie.tsumugi.ui.nowplaying
-
- import android.annotation.SuppressLint
- import android.content.ClipboardManager
- import android.content.Context
- import androidx.lifecycle.ViewModelProviders
- import android.os.Bundle
- import android.support.v4.media.session.PlaybackStateCompat
- import androidx.fragment.app.Fragment
- import android.view.LayoutInflater
- import android.view.View
- import android.view.ViewGroup
- import android.widget.*
- import androidx.constraintlayout.widget.ConstraintLayout
- import androidx.constraintlayout.widget.ConstraintSet
- import androidx.lifecycle.Observer
- import com.google.android.material.snackbar.BaseTransientBottomBar
- import com.google.android.material.snackbar.Snackbar
- import fr.forum_thalie.tsumugi.*
- import fr.forum_thalie.tsumugi.alarm.RadioSleeper
- import fr.forum_thalie.tsumugi.planning.Planning
- import fr.forum_thalie.tsumugi.playerstore.PlayerStore
- import fr.forum_thalie.tsumugi.playerstore.Song
-
-
- class NowPlayingFragment : Fragment() {
-
- private lateinit var root: View
- private lateinit var nowPlayingViewModel: NowPlayingViewModel
-
- @SuppressLint("SetTextI18n")
- override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View? {
-
- nowPlayingViewModel = ViewModelProviders.of(this).get(NowPlayingViewModel::class.java)
- root = inflater.inflate(R.layout.fragment_nowplaying, container, false)
-
- // View bindings to the ViewModel
- val songTitleText: TextView = root.findViewById(R.id.text_song_title)
- val songArtistText: TextView = root.findViewById(R.id.text_song_artist)
- val seekBarVolume: SeekBar = root.findViewById(R.id.seek_bar_volume)
- val volumeText: TextView = root.findViewById(R.id.volume_text)
- val progressBar: ProgressBar = root.findViewById(R.id.progressBar)
- val volumeIconImage : ImageView = root.findViewById(R.id.volume_icon)
- val currentProgrammeText: TextView = root.findViewById(R.id.current_programme)
- val streamerPictureImageView: ImageView = root.findViewById(R.id.streamerPicture)
-
- // Note: these values are not used in the generic app, but if you want to, you can use them.
- val songTitleNextText: TextView = root.findViewById(R.id.text_song_title_next)
- val songArtistNextText: TextView = root.findViewById(R.id.text_song_artist_next)
-
- /*
- val streamerNameText : TextView = root.findViewById(R.id.streamerName)
- val listenersText : TextView = root.findViewById(R.id.listenersCount)
- */
-
-
- /*
- TextViewCompat.setAutoSizeTextTypeUniformWithConfiguration(
- streamerNameText,8, 20, 2, TypedValue.COMPLEX_UNIT_SP)
- TextViewCompat.setAutoSizeTextTypeUniformWithConfiguration(
- listenersText,8, 16, 2, TypedValue.COMPLEX_UNIT_SP)
- */
-
- // trick : I can't observe the queue because it's an ArrayDeque that doesn't trigger any change...
- // so I observe a dedicated Mutable that gets set when the queue is updated.
- PlayerStore.instance.isQueueUpdated.observe(viewLifecycleOwner, Observer {
- val t = if (PlayerStore.instance.queue.size > 0) PlayerStore.instance.queue[0] else Song(noConnectionValue) // (it.peekFirst != null ? it.peekFirst : Song() )
- songTitleNextText.text = t.title.value
- songArtistNextText.text = t.artist.value
- })
- /*
- PlayerStore.instance.streamerName.observe(viewLifecycleOwner, Observer {
- streamerNameText.text = it
- })
-
- PlayerStore.instance.listenersCount.observe(viewLifecycleOwner, Observer {
- listenersText.text = "${getString(R.string.listeners)}: $it"
- })
- */
-
-
- PlayerStore.instance.currentSong.title.observe(viewLifecycleOwner, Observer {
- songTitleText.text = it
- })
-
-
- Planning.instance.currentProgramme.observe(viewLifecycleOwner, Observer {
- currentProgrammeText.text = "${context!!.getString(R.string.current_programme)} $it"
- })
-
-
- PlayerStore.instance.currentSong.artist.observe(viewLifecycleOwner, Observer {
- songArtistText.text = it
- })
-
- PlayerStore.instance.playbackState.observe(viewLifecycleOwner, Observer {
- syncPlayPauseButtonImage(root)
- })
-
-
- fun volumeIcon(it: Int)
- {
- volumeText.text = "$it%"
- when {
- it > 66 -> volumeIconImage.setImageResource(R.drawable.ic_volume_high)
- it in 33..66 -> volumeIconImage.setImageResource(R.drawable.ic_volume_medium)
- it in 0..33 -> volumeIconImage.setImageResource(R.drawable.ic_volume_low)
- else -> volumeIconImage.setImageResource(R.drawable.ic_volume_off)
- }
- }
-
- PlayerStore.instance.volume.observe(viewLifecycleOwner, Observer {
- volumeIcon(it)
- seekBarVolume.progress = it // this updates the seekbar if it's set by something else when going to sleep.
- })
-
- PlayerStore.instance.isMuted.observe(viewLifecycleOwner, Observer {
- if (it)
- volumeIconImage.setImageResource(R.drawable.ic_volume_off)
- else
- volumeIcon(PlayerStore.instance.volume.value!!)
- })
-
-
- PlayerStore.instance.streamerPicture.observe(viewLifecycleOwner, Observer { pic ->
- streamerPictureImageView.setImageBitmap(pic)
- })
-
- // fuck it, do it on main thread
- PlayerStore.instance.currentTime.observe(viewLifecycleOwner, Observer {
- val dd = (PlayerStore.instance.currentTime.value!! - PlayerStore.instance.currentSong.startTime.value!!).toInt()
- progressBar.progress = dd
- })
-
- PlayerStore.instance.currentSong.stopTime.observe(viewLifecycleOwner, Observer {
- val dd = (PlayerStore.instance.currentSong.stopTime.value!! - PlayerStore.instance.currentSong.startTime.value!!).toInt()
- progressBar.max = dd
- })
-
- PlayerStore.instance.currentSong.stopTime.observe(viewLifecycleOwner, Observer {
- val t : TextView= root.findViewById(R.id.endTime)
- val minutes: String = ((PlayerStore.instance.currentSong.stopTime.value!! - PlayerStore.instance.currentSong.startTime.value!!)/60/1000).toString()
- val seconds: String = ((PlayerStore.instance.currentSong.stopTime.value!! - PlayerStore.instance.currentSong.startTime.value!!)/1000%60).toString()
- t.text = "$minutes:${if (seconds.toInt() < 10) "0" else ""}$seconds"
- })
-
- PlayerStore.instance.currentTime.observe(viewLifecycleOwner, Observer {
- val t : TextView= root.findViewById(R.id.currentTime)
- val minutes: String = ((PlayerStore.instance.currentTime.value!! - PlayerStore.instance.currentSong.startTime.value!!)/60/1000).toString()
- val seconds: String = ((PlayerStore.instance.currentTime.value!! - PlayerStore.instance.currentSong.startTime.value!!)/1000%60).toString()
- t.text = "$minutes:${if (seconds.toInt() < 10) "0" else ""}$seconds"
-
- val sleepInfoText = root.findViewById<TextView>(R.id.sleepInfo)
- val sleepAtMillis = RadioSleeper.instance.sleepAtMillis.value
- if (sleepAtMillis != null)
- {
- val duration = ((sleepAtMillis - System.currentTimeMillis()).toFloat() / (60f * 1000f) + 1).toInt() // I put 1 + it because the division rounds to the lower integer. I'd like to display the round up, like it's usually done.
- sleepInfoText.text = String.format(getString(R.string.willCloseIn), duration) // "Will close in $duration minute${if (duration > 1) "s" else ""}"
- sleepInfoText.visibility = View.VISIBLE
- } else {
- sleepInfoText.visibility = View.GONE
- }
- })
-
-
- seekBarVolume.setOnSeekBarChangeListener(nowPlayingViewModel.seekBarChangeListener)
- seekBarVolume.progress = PlayerStore.instance.volume.value!!
- progressBar.max = 1000
- progressBar.progress = 0
-
- syncPlayPauseButtonImage(root)
-
- // initialize the value for isPlaying when displaying the fragment
- PlayerStore.instance.isPlaying.value = PlayerStore.instance.playbackState.value == PlaybackStateCompat.STATE_PLAYING
-
- val button: ImageButton = root.findViewById(R.id.play_pause)
- button.setOnClickListener{
- PlayerStore.instance.isPlaying.value = PlayerStore.instance.playbackState.value == PlaybackStateCompat.STATE_STOPPED
- }
-
- val setClipboardListener: View.OnLongClickListener = View.OnLongClickListener {
- val text = PlayerStore.instance.currentSong.artist.value + " - " + PlayerStore.instance.currentSong.title.value
- val clipboard = context!!.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
- val clip = android.content.ClipData.newPlainText("Copied Text", text)
- clipboard.setPrimaryClip(clip)
- val snackBarLength = if (preferenceStore.getBoolean("snackbarPersistent", false))
- Snackbar.LENGTH_INDEFINITE
- else Snackbar.LENGTH_LONG
- val snackBar = Snackbar.make(it, "", snackBarLength)
-
- if (snackBarLength == Snackbar.LENGTH_INDEFINITE)
- snackBar.setAction("OK") { snackBar.dismiss() }
-
- snackBar.behavior = BaseTransientBottomBar.Behavior().apply {
- setSwipeDirection(BaseTransientBottomBar.Behavior.SWIPE_DIRECTION_ANY)
- }
- snackBar.setText(getString(R.string.song_to_clipboard))
- snackBar.show()
- true
- }
-
- songTitleText.setOnLongClickListener(setClipboardListener)
- songArtistText.setOnLongClickListener(setClipboardListener)
-
- if (preferenceStore.getBoolean("splitLayout", true))
- root.addOnLayoutChangeListener(splitLayoutListener)
-
- return root
- }
-
- private val splitLayoutListener : View.OnLayoutChangeListener = View.OnLayoutChangeListener { _: View, _: Int, _: Int, _: Int, _: Int, _: Int, _: Int, _: Int, _: Int ->
-
- val isSplitLayout = preferenceStore.getBoolean("splitLayout", true)
-
- val viewHeight = (root.rootView?.height ?: 1)
- val viewWidth = (root.rootView?.width ?: 1)
-
- val newRatio = if (viewWidth > 0)
- (viewHeight*100)/viewWidth
- else
- 100
-
- if (isSplitLayout && nowPlayingViewModel.screenRatio != newRatio) {
- onOrientation()
- }
- }
-
- override fun onViewStateRestored(savedInstanceState: Bundle?) {
- onOrientation()
- super.onViewStateRestored(savedInstanceState)
- }
-
- private fun onOrientation() {
- val viewHeight = (root.rootView?.height ?: 1)
- val viewWidth = (root.rootView?.width ?: 1)
-
- val isSplitLayout = preferenceStore.getBoolean("splitLayout", true)
-
- // modify layout to adapt for portrait/landscape
- val isLandscape = viewHeight.toDouble()/viewWidth.toDouble() < 1
- val parentLayout = root.findViewById<ConstraintLayout>(R.id.parentNowPlaying)
- val constraintSet = ConstraintSet()
- constraintSet.clone(parentLayout)
-
- if (isLandscape && isSplitLayout)
- {
- constraintSet.connect(R.id.layoutBlock1, ConstraintSet.BOTTOM, R.id.parentNowPlaying, ConstraintSet.BOTTOM)
- constraintSet.connect(R.id.layoutBlock1, ConstraintSet.END, R.id.splitHorizontalLayout, ConstraintSet.END)
- constraintSet.connect(R.id.layoutBlock2, ConstraintSet.TOP, R.id.parentNowPlaying, ConstraintSet.TOP)
- constraintSet.connect(R.id.layoutBlock2, ConstraintSet.START, R.id.splitHorizontalLayout, ConstraintSet.END)
- constraintSet.setMargin(R.id.layoutBlock1, ConstraintSet.END, 16)
- constraintSet.setMargin(R.id.layoutBlock2, ConstraintSet.START, 16)
- } else {
- constraintSet.connect(R.id.layoutBlock1, ConstraintSet.BOTTOM, R.id.splitVerticalLayout, ConstraintSet.BOTTOM)
- constraintSet.connect(R.id.layoutBlock1, ConstraintSet.END, R.id.parentNowPlaying, ConstraintSet.END)
- constraintSet.connect(R.id.layoutBlock2, ConstraintSet.TOP, R.id.splitVerticalLayout, ConstraintSet.BOTTOM)
- constraintSet.connect(R.id.layoutBlock2, ConstraintSet.START, R.id.parentNowPlaying, ConstraintSet.START)
- constraintSet.setMargin(R.id.layoutBlock1, ConstraintSet.END, 0)
- constraintSet.setMargin(R.id.layoutBlock2, ConstraintSet.START, 0)
- }
- constraintSet.applyTo(parentLayout)
-
- // note : we have to COMPARE numbers that are FRACTIONS. And everyone knows that we should NEVER compare DOUBLES because of the imprecision at the end.
- // So instead, I multiply the result by 100 (to give 2 significant numbers), and do an INTEGER DIVISION. This is the right way to compare ratios.
- nowPlayingViewModel.screenRatio = if (viewWidth > 0)
- (viewHeight*100)/viewWidth
- else
- 100
- //[REMOVE LOG CALLS]Log.d(tag, "orientation set")
- }
-
- override fun onResume() {
- super.onResume()
- onOrientation()
- }
-
- private fun syncPlayPauseButtonImage(v: View)
- {
- val img = v.findViewById<ImageButton>(R.id.play_pause)
-
- if (PlayerStore.instance.playbackState.value == PlaybackStateCompat.STATE_STOPPED) {
- img.setImageResource(R.drawable.exo_controls_play)
- } else {
- img.setImageResource(R.drawable.exo_controls_pause)
- }
- }
- }
|