Project/๊ทธ ์™ธ

๐Ÿ…PomodoroTimer : ํƒ€์ด๋จธ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜

DevGoni 2022. 5. 13. 17:53
728x90

๐Ÿ… PomodoroTimer 

º ํ”„๋กœ์ ํŠธ ์ธ์› : 1๋ช…

º ํ”„๋กœ์ ํŠธ ๊ธฐ๊ฐ„ : 2022์›” 3์›” 17์ผ / 1์ผ


 

๐Ÿ›  ๊ฐœ๋ฐœํ™˜๊ฒฝ ๋ฐ ์‚ฌ์šฉํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

Development Environment

Android Studio (version : Arctic Fox | 2020.3.1 Patch 4)

 

Device

Galaxy - S20 5G (OS : Android 10 / API Level : 29)

 

Using Library

  • CountDownTimer
  • SoundPool

 

๐Ÿ“œ Coding Convention

Coding Convention

Kotlin ๊ณต์‹ ์ฝ”๋”ฉ ์ปจ๋ฒค์…˜

 

ํด๋”๊ตฌ์กฐ

 

๋„ค์ด๋ฐ

  • Class                     : Pascal Case
  • Variable ๋ฐ Method  : Camel Case 
  • Resource File          : Lower Snake Case

 

โœ‰๏ธ Commit Messge Rules

- feat   : ๊ธฐ๋Šฅ (์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ)
- design : UI ๋””์ž์ธ

 

๐Ÿ† ๊ตฌํ˜„ ๊ธฐ๋Šฅ

1. 1 ~ 60๋ถ„ ๋ฒ”์œ„์˜ ํƒ€์ด๋จธ ์„ค์ •

    <!-- Gon : ํ•œ ์‹œ๊ฐ„์„ ์ตœ๋Œ€ ๊ฐ’์œผ๋กœ ํ•  ๊ฒƒ์ด๋ฏ€๋กœ SeekBar max๊ฐ’์„ 60์œผ๋กœ ์„ค์ • -->
    <SeekBar
        android:id="@+id/controlMinutesSeekBar"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="15dp"
        android:layout_marginEnd="15dp"
        android:max="60"
        android:progressTint="@color/gray"
        android:thumb="@drawable/ic_thumb"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/remainMinutesTextView" />
controlMinutesSeekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener{
            // Gon : ๋“œ๋ž˜๊ทธ ์ค‘ ํ˜ธ์ถœ
            override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
                // Gon : ํƒ€์ด๋จธ๋ฅผ 1์‹œ๊ฐ„ ๊ธฐ์ค€์œผ๋กœ ํ‘œํ˜„ํ•˜๊ธฐ ์œ„ํ•ด (progress * 1๋ถ„) ์ธ์ž๋กœ ๋„˜๊น€  
                if (fromUser)
                    updateRemainTime(progress * 60 * 1000L)
            }

            // Gon : ์ตœ์ดˆ์— ํƒญํ•˜์—ฌ ๋“œ๋ž˜๊ทธ ์‹œ์ž‘ ์‹œ ํ˜ธ์ถœ
            override fun onStartTrackingTouch(seekBar: SeekBar?) {
                stopCountDown()
            }

            // Gon : ๋“œ๋ž˜๊ทธ๋ฅผ ๋ฉˆ์ถ”๋Š” ๊ฒฝ์šฐ ํ˜ธ์ถœ
            override fun onStopTrackingTouch(seekBar: SeekBar?) {
                if (seekBar == null) return

                if (seekBar.progress == 0) stopCountDown()
                else startCountDown(seekBar)
            }
        })
private fun updateRemainTime(millis: Long) = with(binding) {
        val remainSeconds = millis / 1000

        // Gon : ๋‚จ์€ ์ „์ฒด ์‹œ๊ฐ„์„ ์ดˆ๋กœ ๋ฐ›๊ธฐ ๋•Œ๋ฌธ์— ๋ถ„,์ดˆ๋ฅผ ํ‘œํ˜„ํ•˜๊ธฐ ์œ„ํ•ด์„œ 60(1๋ถ„)์œผ๋กœ ๊ฐ’ ์ฒ˜๋ฆฌ
        remainMinutesTextView.text = resources.getString(R.string.minutesFormat, remainSeconds / 60)
        remainSecondsTextView.text = resources.getString(R.string.secondsFormat, remainSeconds % 60)
    }

 

 

2. 1์ดˆ ๋งˆ๋‹ค ํ™”๋ฉด ๊ฐฑ์‹ 

    // Gon : CountDown ์‹œ์ž‘
    private fun startCountDown(seekBar: SeekBar) {
        currentCountDownTimer = createCountDownTimer(seekBar.progress * 60 * 1000L).start()
        currentCountDownTimer?.start()

        //Gon : loop forever -> -1
        tickingSoundId?.let {
            soundPoll.play(it, 1F, 1F, 0, -1, 1F)
        }
    }
    
    
    // Gon : ์ข…๋ฃŒ๊นŒ์ง€ ๋‚จ์€ ๋ฐ€๋ฆฌ์ดˆ, onTick() ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ ๊ฐ„๊ฒฉ
    private fun createCountDownTimer(millis: Long): CountDownTimer =
        object: CountDownTimer(millis, 1000L) {

            override fun onTick(millisUntilFinished: Long) {
                updateRemainTime(millisUntilFinished)
                updateSeekBar(millisUntilFinished)
            }

            override fun onFinish() {
                completeCountDown()
            }
        }
        
        
    private fun updateRemainTime(millis: Long) = with(binding) {
        val remainSeconds = millis / 1000

        // Gon : ๋‚จ์€ ์ „์ฒด ์‹œ๊ฐ„์„ ์ดˆ๋กœ ๋ฐ›๊ธฐ ๋•Œ๋ฌธ์— ๋ถ„,์ดˆ๋ฅผ ํ‘œํ˜„ํ•˜๊ธฐ ์œ„ํ•ด์„œ 60(1๋ถ„)์œผ๋กœ ๊ฐ’ ์ฒ˜๋ฆฌ
        remainMinutesTextView.text = resources.getString(R.string.minutesFormat, remainSeconds / 60)
        remainSecondsTextView.text = resources.getString(R.string.secondsFormat, remainSeconds % 60)
    }


    private fun updateSeekBar(millis: Long) = with(binding) {
        // Gon : ๋‚จ์€ ์ „์ฒด ๋ฐ€๋ฆฌ์ดˆ๋กœ ๋ฐ›๊ธฐ ๋•Œ๋ฌธ์— ๋ถ„ ๋‹จ์œ„์˜ SeekBar๋ฅผ ํ‘œํ˜„ํ•˜๊ธฐ ์œ„ํ•ด์„œ 60000(1๋ถ„)์œผ๋กœ ๊ฐ’ ์ฒ˜๋ฆฌ
        controlMinutesSeekBar.progress = (millis / 60000).toInt()
    }

 

 

3. ํƒ€์ด๋จธ ํšจ๊ณผ์Œ ๋ฐ ์ข…๋ฃŒ ์•Œ๋ฆผ์Œ

    private fun init() = with(binding) {
    
        ... ์ƒ๋žต ...
    
        tickingSoundId = soundPoll.load(this@MainActivity, R.raw.timer_tick, 1)
        bellSoundId = soundPoll.load(this@MainActivity, R.raw.timer_bell, 1)
    }
    
    
    private fun startCountDown(seekBar: SeekBar) {
        currentCountDownTimer = createCountDownTimer(seekBar.progress * 60 * 1000L).start()
        currentCountDownTimer?.start()

        //Gon : loop forever -> -1
        tickingSoundId?.let {
            soundPoll.play(it, 1F, 1F, 0, -1, 1F)
        }
    }
    
    
    private fun completeCountDown() {
        updateRemainTime(0)
        updateSeekBar(0)

        bellSoundId?.let {
            soundPoll.play(it, 1F, 1F, 0, 0, 1F)
        }
    }