Newer
Older
music / app / src / main / java / com / lukas / music / ui / fragments / PlayFragment.kt
/*
 * Copyright (C) 2022 Lukas Eisenhauer
 *
 * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or(at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

package com.lukas.music.ui.fragments

import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.*
import androidx.cardview.widget.CardView
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import com.lukas.music.R
import com.lukas.music.databinding.FragmentPlayBinding
import com.lukas.music.instruments.Rhythm
import com.lukas.music.song.Song

class PlayFragment : Fragment() {
    private lateinit var binding: FragmentPlayBinding
    private val beatIndicators = mutableListOf<RadioButton>()
    private val chordDisplays = mutableListOf<CardView>()

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        binding = FragmentPlayBinding.inflate(inflater)
        binding.playButton.setOnClickListener {
            Rhythm.on = !Rhythm.on
            binding.playButton.setImageResource(
                if (Rhythm.on) android.R.drawable.ic_media_pause else android.R.drawable.ic_media_play
            )
        }
        binding.advancePhraseButton.setOnClickListener {
            Song.currentSong.chordProgression.bigStep(true)
        }
        binding.reversePhraseButton.setOnClickListener {
            Song.currentSong.chordProgression.bigReverse(true)
        }
        binding.advanceMeasureButton.setOnClickListener {
            Song.currentSong.chordProgression.step()
        }
        binding.reverseMeasureButton.setOnClickListener {
            Song.currentSong.chordProgression.currentItem?.let {
                chordDisplays[it.index].setCardBackgroundColor(
                    ContextCompat.getColor(binding.root.context, R.color.gray_0x40)
                )
            }
            Song.currentSong.chordProgression.reverse()
        }
        setupSlider(binding.masterVolumeSlider, 0, 100, 100) {
            setMasterVolume(it.toDouble() / 100.0)
            binding.masterVolumeText.text = "Master volume: $it%"
        }
        setupSlider(binding.tempoSlider, 50, 150, 90) {
            Rhythm.setTempo(it)
            binding.tempoText.text = "tempo: ${it}bpm"
        }
        setupBeatIndicator()
        Song.currentSong.stepCallback += {
            Handler(Looper.getMainLooper()).post {
                beatIndicators[Song.currentSong.indexBehind].isChecked = false
                beatIndicators[Song.currentSong.index].isChecked = true
            }
        }
        Song.currentSong.chordProgression.stepCallback += {
            Handler(Looper.getMainLooper()).post { updateChords() }
        }
        Song.currentSong.chordProgression.miniStepCallback += {
            Handler(Looper.getMainLooper()).post { updateChordView() }
        }
        return binding.root
    }

    private fun setupBeatIndicator() {
        val layout = RadioGroup.LayoutParams(
            RadioGroup.LayoutParams.WRAP_CONTENT,
            RadioGroup.LayoutParams.MATCH_PARENT
        )
        layout.weight = 1.0f
        val spacer = Space(binding.root.context)
        spacer.layoutParams = layout
        binding.beatIndicator.addView(spacer)
        for (i in 0 until Song.currentSong.beats) {
            val child = RadioButton(binding.root.context)
            child.layoutParams = layout
            child.isClickable = false
            if (i == 0) {
                child.isChecked = true
            }
            beatIndicators += child
            binding.beatIndicator.addView(child)
        }
    }

    private fun updateChordView() {
        if (chordDisplays.isEmpty()) {
            updateChords()
            return
        }
        chordDisplays[Song.currentSong.chordProgression.currentItem!!.index].setCardBackgroundColor(
            ContextCompat.getColor(binding.root.context, R.color.purple_700)
        )
        chordDisplays[Song.currentSong.chordProgression.currentItem!!.indexBehind].setCardBackgroundColor(
            ContextCompat.getColor(binding.root.context, R.color.gray_0x40)
        )
    }

    private fun setupSlider(
        slider: SeekBar,
        min: Int,
        max: Int,
        initialProgress: Int,
        callback: (Int) -> Unit
    ) {
        slider.min = min
        slider.max = max
        slider.setOnSeekBarChangeListener(object :
            SeekBar.OnSeekBarChangeListener {
            override fun onProgressChanged(
                seekBar: SeekBar,
                progress: Int, fromUser: Boolean
            ) {
                callback(progress)
            }

            override fun onStartTrackingTouch(seekBar: SeekBar) {
            }

            override fun onStopTrackingTouch(seekBar: SeekBar) {
            }
        })
        slider.progress = initialProgress
    }

    fun updateChords() {
        binding.phraseDisplay.removeAllViews()
        chordDisplays.clear()
        for (chord in Song.currentSong.chordProgression.currentItem ?: return) {
            val card = CardView(binding.root.context)
            card.layoutParams = SongFragment.tableRowLayout
            card.radius = 10f
            card.preventCornerOverlap = false
            val text = TextView(binding.root.context)
            text.text = chord.toString(true, Song.currentSong.root)
            text.layoutParams = SongFragment.tableRowLayout
            text.textSize = 20f
            text.textAlignment = TextView.TEXT_ALIGNMENT_CENTER
            card.addView(text)
            binding.phraseDisplay.addView(card)
            chordDisplays += card
        }
        binding.phraseTable.isStretchAllColumns = true
        binding.nextChordText.text =
            Song.currentSong.chordProgression.lookahead(1)[0].toString(true, Song.currentSong.root)
        updateChordView()
    }

    private external fun setMasterVolume(volume: Double)
}