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(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.progress = PlayerStore.instance.volume.value!! seekBarVolume.setOnSeekBarChangeListener(nowPlayingViewModel.seekBarChangeListener) progressBar.max = 100 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(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(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) } } }