├── .gitignore ├── .idea ├── gradle.xml ├── misc.xml ├── modules.xml └── runConfigurations.xml ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── resocoder │ │ └── timertutorial │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── resocoder │ │ │ └── timertutorial │ │ │ ├── AppConstants.kt │ │ │ ├── SettingsActivity.kt │ │ │ ├── SettingsActivityFragment.kt │ │ │ ├── TimerActivity.kt │ │ │ ├── TimerExpiredReceiver.kt │ │ │ ├── TimerNotificationActionReceiver.kt │ │ │ └── util │ │ │ ├── NotificationUtil.kt │ │ │ └── PrefUtil.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── ic_launcher_background.xml │ │ ├── ic_pause.xml │ │ ├── ic_play_arrow.xml │ │ ├── ic_settings.xml │ │ ├── ic_stop.xml │ │ └── ic_timer.xml │ │ ├── layout │ │ ├── activity_settings.xml │ │ ├── activity_timer.xml │ │ └── content_timer.xml │ │ ├── menu │ │ └── menu_timer.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ │ └── xml │ │ └── preferences.xml │ └── test │ └── java │ └── com │ └── resocoder │ └── timertutorial │ └── ExampleUnitTest.kt ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 26 | 27 | 28 | 29 | 30 | 31 | 33 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | apply plugin: 'kotlin-android' 4 | 5 | apply plugin: 'kotlin-android-extensions' 6 | 7 | android { 8 | compileSdkVersion 26 9 | defaultConfig { 10 | applicationId "com.resocoder.timertutorial" 11 | minSdkVersion 19 12 | targetSdkVersion 26 13 | versionCode 1 14 | versionName "1.0" 15 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 16 | vectorDrawables.useSupportLibrary = true 17 | } 18 | buildTypes { 19 | release { 20 | minifyEnabled false 21 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 22 | } 23 | } 24 | } 25 | 26 | dependencies { 27 | implementation fileTree(dir: 'libs', include: ['*.jar']) 28 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version" 29 | implementation 'com.android.support:appcompat-v7:26.1.0' 30 | implementation 'com.android.support.constraint:constraint-layout:1.0.2' 31 | implementation 'com.android.support:design:26.1.0' 32 | implementation 'com.android.support:support-v4:26.1.0' 33 | testImplementation 'junit:junit:4.12' 34 | androidTestImplementation 'com.android.support.test:runner:1.0.1' 35 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' 36 | compile 'me.zhanghai.android.materialprogressbar:library:1.4.2' 37 | implementation 'com.android.support:preference-v7:26.1.0' 38 | compile 'com.pavelsikun:material-seekbar-preference:2.3.0' 39 | } 40 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/resocoder/timertutorial/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.resocoder.timertutorial 2 | 3 | import android.support.test.InstrumentationRegistry 4 | import android.support.test.runner.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getTargetContext() 22 | assertEquals("com.resocoder.timertutorial", appContext.packageName) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 27 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 43 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /app/src/main/java/com/resocoder/timertutorial/AppConstants.kt: -------------------------------------------------------------------------------- 1 | package com.resocoder.timertutorial 2 | 3 | 4 | class AppConstants { 5 | companion object { 6 | const val ACTION_STOP = "stop" 7 | const val ACTION_PAUSE = "pause" 8 | const val ACTION_RESUME = "resume" 9 | const val ACTION_START = "start" 10 | } 11 | } -------------------------------------------------------------------------------- /app/src/main/java/com/resocoder/timertutorial/SettingsActivity.kt: -------------------------------------------------------------------------------- 1 | package com.resocoder.timertutorial 2 | 3 | import android.support.v7.app.AppCompatActivity 4 | import android.os.Bundle 5 | import kotlinx.android.synthetic.main.activity_timer.* 6 | 7 | class SettingsActivity : AppCompatActivity() { 8 | 9 | override fun onCreate(savedInstanceState: Bundle?) { 10 | super.onCreate(savedInstanceState) 11 | setContentView(R.layout.activity_settings) 12 | setSupportActionBar(toolbar) 13 | supportActionBar?.setDisplayHomeAsUpEnabled(true) 14 | supportActionBar?.title = "Settings" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/com/resocoder/timertutorial/SettingsActivityFragment.kt: -------------------------------------------------------------------------------- 1 | package com.resocoder.timertutorial 2 | 3 | import android.os.Bundle 4 | import android.support.v7.preference.PreferenceFragmentCompat 5 | 6 | 7 | class SettingsActivityFragment : PreferenceFragmentCompat() { 8 | override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { 9 | addPreferencesFromResource(R.xml.preferences) 10 | } 11 | } -------------------------------------------------------------------------------- /app/src/main/java/com/resocoder/timertutorial/TimerActivity.kt: -------------------------------------------------------------------------------- 1 | package com.resocoder.timertutorial 2 | 3 | import android.app.AlarmManager 4 | import android.app.PendingIntent 5 | import android.content.Context 6 | import android.content.Intent 7 | import android.os.Bundle 8 | import android.os.CountDownTimer 9 | import android.support.v7.app.AppCompatActivity 10 | import android.view.Menu 11 | import android.view.MenuItem 12 | import com.resocoder.timertutorial.util.NotificationUtil 13 | import com.resocoder.timertutorial.util.PrefUtil 14 | import kotlinx.android.synthetic.main.activity_timer.* 15 | import kotlinx.android.synthetic.main.content_timer.* 16 | import java.util.* 17 | 18 | class TimerActivity : AppCompatActivity() { 19 | 20 | companion object { 21 | fun setAlarm(context: Context, nowSeconds: Long, secondsRemaining: Long): Long{ 22 | val wakeUpTime = (nowSeconds + secondsRemaining) * 1000 23 | val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager 24 | val intent = Intent(context, TimerExpiredReceiver::class.java) 25 | val pendingIntent = PendingIntent.getBroadcast(context, 0, intent, 0) 26 | alarmManager.setExact(AlarmManager.RTC_WAKEUP, wakeUpTime, pendingIntent) 27 | PrefUtil.setAlarmSetTime(nowSeconds, context) 28 | return wakeUpTime 29 | } 30 | 31 | fun removeAlarm(context: Context){ 32 | val intent = Intent(context, TimerExpiredReceiver::class.java) 33 | val pendingIntent = PendingIntent.getBroadcast(context, 0, intent, 0) 34 | val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager 35 | alarmManager.cancel(pendingIntent) 36 | PrefUtil.setAlarmSetTime(0, context) 37 | } 38 | 39 | val nowSeconds: Long 40 | get() = Calendar.getInstance().timeInMillis / 1000 41 | } 42 | 43 | enum class TimerState{ 44 | Stopped, Paused, Running 45 | } 46 | 47 | private lateinit var timer: CountDownTimer 48 | private var timerLengthSeconds: Long = 0 49 | private var timerState = TimerState.Stopped 50 | 51 | private var secondsRemaining: Long = 0 52 | 53 | override fun onCreate(savedInstanceState: Bundle?) { 54 | super.onCreate(savedInstanceState) 55 | setContentView(R.layout.activity_timer) 56 | setSupportActionBar(toolbar) 57 | supportActionBar?.setIcon(R.drawable.ic_timer) 58 | supportActionBar?.title = " Timer" 59 | 60 | fab_start.setOnClickListener{v -> 61 | startTimer() 62 | timerState = TimerState.Running 63 | updateButtons() 64 | } 65 | 66 | fab_pause.setOnClickListener { v -> 67 | timer.cancel() 68 | timerState = TimerState.Paused 69 | updateButtons() 70 | } 71 | 72 | fab_stop.setOnClickListener { v -> 73 | timer.cancel() 74 | onTimerFinished() 75 | } 76 | } 77 | 78 | override fun onResume() { 79 | super.onResume() 80 | 81 | initTimer() 82 | 83 | removeAlarm(this) 84 | NotificationUtil.hideTimerNotification(this) 85 | } 86 | 87 | override fun onPause() { 88 | super.onPause() 89 | 90 | if (timerState == TimerState.Running){ 91 | timer.cancel() 92 | val wakeUpTime = setAlarm(this, nowSeconds, secondsRemaining) 93 | NotificationUtil.showTimerRunning(this, wakeUpTime) 94 | } 95 | else if (timerState == TimerState.Paused){ 96 | NotificationUtil.showTimerPaused(this) 97 | } 98 | 99 | PrefUtil.setPreviousTimerLengthSeconds(timerLengthSeconds, this) 100 | PrefUtil.setSecondsRemaining(secondsRemaining, this) 101 | PrefUtil.setTimerState(timerState, this) 102 | } 103 | 104 | private fun initTimer(){ 105 | timerState = PrefUtil.getTimerState(this) 106 | 107 | //we don't want to change the length of the timer which is already running 108 | //if the length was changed in settings while it was backgrounded 109 | if (timerState == TimerState.Stopped) 110 | setNewTimerLength() 111 | else 112 | setPreviousTimerLength() 113 | 114 | secondsRemaining = if (timerState == TimerState.Running || timerState == TimerState.Paused) 115 | PrefUtil.getSecondsRemaining(this) 116 | else 117 | timerLengthSeconds 118 | 119 | val alarmSetTime = PrefUtil.getAlarmSetTime(this) 120 | if (alarmSetTime > 0) 121 | secondsRemaining -= nowSeconds - alarmSetTime 122 | 123 | if (secondsRemaining <= 0) 124 | onTimerFinished() 125 | else if (timerState == TimerState.Running) 126 | startTimer() 127 | 128 | updateButtons() 129 | updateCountdownUI() 130 | } 131 | 132 | private fun onTimerFinished(){ 133 | timerState = TimerState.Stopped 134 | 135 | //set the length of the timer to be the one set in SettingsActivity 136 | //if the length was changed when the timer was running 137 | setNewTimerLength() 138 | 139 | progress_countdown.progress = 0 140 | 141 | PrefUtil.setSecondsRemaining(timerLengthSeconds, this) 142 | secondsRemaining = timerLengthSeconds 143 | 144 | updateButtons() 145 | updateCountdownUI() 146 | } 147 | 148 | private fun startTimer(){ 149 | timerState = TimerState.Running 150 | 151 | timer = object : CountDownTimer(secondsRemaining * 1000, 1000) { 152 | override fun onFinish() = onTimerFinished() 153 | 154 | override fun onTick(millisUntilFinished: Long) { 155 | secondsRemaining = millisUntilFinished / 1000 156 | updateCountdownUI() 157 | } 158 | }.start() 159 | } 160 | 161 | private fun setNewTimerLength(){ 162 | val lengthInMinutes = PrefUtil.getTimerLength(this) 163 | timerLengthSeconds = (lengthInMinutes * 60L) 164 | progress_countdown.max = timerLengthSeconds.toInt() 165 | } 166 | 167 | private fun setPreviousTimerLength(){ 168 | timerLengthSeconds = PrefUtil.getPreviousTimerLengthSeconds(this) 169 | progress_countdown.max = timerLengthSeconds.toInt() 170 | } 171 | 172 | private fun updateCountdownUI(){ 173 | val minutesUntilFinished = secondsRemaining / 60 174 | val secondsInMinuteUntilFinished = secondsRemaining - minutesUntilFinished * 60 175 | val secondsStr = secondsInMinuteUntilFinished.toString() 176 | textView_countdown.text = "$minutesUntilFinished:${if (secondsStr.length == 2) secondsStr else "0" + secondsStr}" 177 | progress_countdown.progress = (timerLengthSeconds - secondsRemaining).toInt() 178 | } 179 | 180 | private fun updateButtons(){ 181 | when (timerState) { 182 | TimerState.Running ->{ 183 | fab_start.isEnabled = false 184 | fab_pause.isEnabled = true 185 | fab_stop.isEnabled = true 186 | } 187 | TimerState.Stopped -> { 188 | fab_start.isEnabled = true 189 | fab_pause.isEnabled = false 190 | fab_stop.isEnabled = false 191 | } 192 | TimerState.Paused -> { 193 | fab_start.isEnabled = true 194 | fab_pause.isEnabled = false 195 | fab_stop.isEnabled = true 196 | } 197 | } 198 | } 199 | 200 | 201 | override fun onCreateOptionsMenu(menu: Menu): Boolean { 202 | // Inflate the menu; this adds items to the action bar if it is present. 203 | menuInflater.inflate(R.menu.menu_timer, menu) 204 | return true 205 | } 206 | 207 | override fun onOptionsItemSelected(item: MenuItem): Boolean { 208 | // Handle action bar item clicks here. The action bar will 209 | // automatically handle clicks on the Home/Up button, so long 210 | // as you specify a parent activity in AndroidManifest.xml. 211 | return when (item.itemId) { 212 | R.id.action_settings -> { 213 | val intent = Intent(this, SettingsActivity::class.java) 214 | startActivity(intent) 215 | true 216 | } 217 | else -> super.onOptionsItemSelected(item) 218 | } 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /app/src/main/java/com/resocoder/timertutorial/TimerExpiredReceiver.kt: -------------------------------------------------------------------------------- 1 | package com.resocoder.timertutorial 2 | 3 | import android.content.BroadcastReceiver 4 | import android.content.Context 5 | import android.content.Intent 6 | import com.resocoder.timertutorial.util.NotificationUtil 7 | import com.resocoder.timertutorial.util.PrefUtil 8 | 9 | class TimerExpiredReceiver : BroadcastReceiver() { 10 | 11 | override fun onReceive(context: Context, intent: Intent) { 12 | NotificationUtil.showTimerExpired(context) 13 | 14 | PrefUtil.setTimerState(TimerActivity.TimerState.Stopped, context) 15 | PrefUtil.setAlarmSetTime(0, context) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/resocoder/timertutorial/TimerNotificationActionReceiver.kt: -------------------------------------------------------------------------------- 1 | package com.resocoder.timertutorial 2 | 3 | import android.content.BroadcastReceiver 4 | import android.content.Context 5 | import android.content.Intent 6 | import com.resocoder.timertutorial.util.NotificationUtil 7 | import com.resocoder.timertutorial.util.PrefUtil 8 | 9 | class TimerNotificationActionReceiver : BroadcastReceiver() { 10 | 11 | override fun onReceive(context: Context, intent: Intent) { 12 | when (intent.action){ 13 | AppConstants.ACTION_STOP -> { 14 | TimerActivity.removeAlarm(context) 15 | PrefUtil.setTimerState(TimerActivity.TimerState.Stopped, context) 16 | NotificationUtil.hideTimerNotification(context) 17 | } 18 | AppConstants.ACTION_PAUSE -> { 19 | var secondsRemaining = PrefUtil.getSecondsRemaining(context) 20 | val alarmSetTime = PrefUtil.getAlarmSetTime(context) 21 | val nowSeconds = TimerActivity.nowSeconds 22 | 23 | secondsRemaining -= nowSeconds - alarmSetTime 24 | PrefUtil.setSecondsRemaining(secondsRemaining, context) 25 | 26 | TimerActivity.removeAlarm(context) 27 | PrefUtil.setTimerState(TimerActivity.TimerState.Paused, context) 28 | NotificationUtil.showTimerPaused(context) 29 | } 30 | AppConstants.ACTION_RESUME -> { 31 | val secondsRemaining = PrefUtil.getSecondsRemaining(context) 32 | val wakeUpTime = TimerActivity.setAlarm(context, TimerActivity.nowSeconds, secondsRemaining) 33 | PrefUtil.setTimerState(TimerActivity.TimerState.Running, context) 34 | NotificationUtil.showTimerRunning(context, wakeUpTime) 35 | } 36 | AppConstants.ACTION_START -> { 37 | val minutesRemaining = PrefUtil.getTimerLength(context) 38 | val secondsRemaining = minutesRemaining * 60L 39 | val wakeUpTime = TimerActivity.setAlarm(context, TimerActivity.nowSeconds, secondsRemaining) 40 | PrefUtil.setTimerState(TimerActivity.TimerState.Running, context) 41 | PrefUtil.setSecondsRemaining(secondsRemaining, context) 42 | NotificationUtil.showTimerRunning(context, wakeUpTime) 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/java/com/resocoder/timertutorial/util/NotificationUtil.kt: -------------------------------------------------------------------------------- 1 | package com.resocoder.timertutorial.util 2 | 3 | import android.annotation.TargetApi 4 | import android.app.NotificationChannel 5 | import android.app.NotificationManager 6 | import android.app.PendingIntent 7 | import android.app.TaskStackBuilder 8 | import android.content.Context 9 | import android.content.Intent 10 | import android.graphics.Color 11 | import android.media.RingtoneManager 12 | import android.net.Uri 13 | import android.os.Build 14 | import android.support.v4.app.NotificationCompat 15 | import com.resocoder.timertutorial.AppConstants 16 | import com.resocoder.timertutorial.R 17 | import com.resocoder.timertutorial.TimerActivity 18 | import com.resocoder.timertutorial.TimerNotificationActionReceiver 19 | import java.text.SimpleDateFormat 20 | import java.util.* 21 | 22 | 23 | class NotificationUtil { 24 | companion object { 25 | private const val CHANNEL_ID_TIMER = "menu_timer" 26 | private const val CHANNEL_NAME_TIMER = "Timer App Timer" 27 | private const val TIMER_ID = 0 28 | 29 | fun showTimerExpired(context: Context){ 30 | val startIntent = Intent(context, TimerNotificationActionReceiver::class.java) 31 | startIntent.action = AppConstants.ACTION_START 32 | val startPendingIntent = PendingIntent.getBroadcast(context, 33 | 0, startIntent, PendingIntent.FLAG_UPDATE_CURRENT) 34 | 35 | val nBuilder = getBasicNotificationBuilder(context, CHANNEL_ID_TIMER, true) 36 | nBuilder.setContentTitle("Timer Expired!") 37 | .setContentText("Start again?") 38 | .setContentIntent(getPendingIntentWithStack(context, TimerActivity::class.java)) 39 | .addAction(R.drawable.ic_play_arrow, "Start", startPendingIntent) 40 | 41 | val nManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager 42 | nManager.createNotificationChannel(CHANNEL_ID_TIMER, CHANNEL_NAME_TIMER, true) 43 | 44 | nManager.notify(TIMER_ID, nBuilder.build()) 45 | } 46 | 47 | fun showTimerRunning(context: Context, wakeUpTime: Long){ 48 | val stopIntent = Intent(context, TimerNotificationActionReceiver::class.java) 49 | stopIntent.action = AppConstants.ACTION_STOP 50 | val stopPendingIntent = PendingIntent.getBroadcast(context, 51 | 0, stopIntent, PendingIntent.FLAG_UPDATE_CURRENT) 52 | 53 | val pauseIntent = Intent(context, TimerNotificationActionReceiver::class.java) 54 | pauseIntent.action = AppConstants.ACTION_PAUSE 55 | val pausePendingIntent = PendingIntent.getBroadcast(context, 56 | 0, pauseIntent, PendingIntent.FLAG_UPDATE_CURRENT) 57 | 58 | val df = SimpleDateFormat.getTimeInstance(SimpleDateFormat.SHORT) 59 | 60 | val nBuilder = getBasicNotificationBuilder(context, CHANNEL_ID_TIMER, true) 61 | nBuilder.setContentTitle("Timer is Running.") 62 | .setContentText("End: ${df.format(Date(wakeUpTime))}") 63 | .setContentIntent(getPendingIntentWithStack(context, TimerActivity::class.java)) 64 | .setOngoing(true) 65 | .addAction(R.drawable.ic_stop, "Stop", stopPendingIntent) 66 | .addAction(R.drawable.ic_pause, "Pause", pausePendingIntent) 67 | 68 | val nManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager 69 | nManager.createNotificationChannel(CHANNEL_ID_TIMER, CHANNEL_NAME_TIMER, true) 70 | 71 | nManager.notify(TIMER_ID, nBuilder.build()) 72 | } 73 | 74 | fun showTimerPaused(context: Context){ 75 | val resumeIntent = Intent(context, TimerNotificationActionReceiver::class.java) 76 | resumeIntent.action = AppConstants.ACTION_RESUME 77 | val resumePendingIntent = PendingIntent.getBroadcast(context, 78 | 0, resumeIntent, PendingIntent.FLAG_UPDATE_CURRENT) 79 | 80 | val nBuilder = getBasicNotificationBuilder(context, CHANNEL_ID_TIMER, true) 81 | nBuilder.setContentTitle("Timer is paused.") 82 | .setContentText("Resume?") 83 | .setContentIntent(getPendingIntentWithStack(context, TimerActivity::class.java)) 84 | .setOngoing(true) 85 | .addAction(R.drawable.ic_play_arrow, "Resume", resumePendingIntent) 86 | 87 | val nManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager 88 | nManager.createNotificationChannel(CHANNEL_ID_TIMER, CHANNEL_NAME_TIMER, true) 89 | 90 | nManager.notify(TIMER_ID, nBuilder.build()) 91 | } 92 | 93 | fun hideTimerNotification(context: Context){ 94 | val nManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager 95 | nManager.cancel(TIMER_ID) 96 | } 97 | 98 | private fun getBasicNotificationBuilder(context: Context, channelId: String, playSound: Boolean) 99 | : NotificationCompat.Builder{ 100 | val notificationSound: Uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION) 101 | val nBuilder = NotificationCompat.Builder(context, channelId) 102 | .setSmallIcon(R.drawable.ic_timer) 103 | .setAutoCancel(true) 104 | .setDefaults(0) 105 | if (playSound) nBuilder.setSound(notificationSound) 106 | return nBuilder 107 | } 108 | 109 | private fun getPendingIntentWithStack(context: Context, javaClass: Class): PendingIntent{ 110 | val resultIntent = Intent(context, javaClass) 111 | resultIntent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP 112 | 113 | val stackBuilder = TaskStackBuilder.create(context) 114 | stackBuilder.addParentStack(javaClass) 115 | stackBuilder.addNextIntent(resultIntent) 116 | 117 | return stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT) 118 | } 119 | 120 | @TargetApi(26) 121 | private fun NotificationManager.createNotificationChannel(channelID: String, 122 | channelName: String, 123 | playSound: Boolean){ 124 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){ 125 | val channelImportance = if (playSound) NotificationManager.IMPORTANCE_DEFAULT 126 | else NotificationManager.IMPORTANCE_LOW 127 | val nChannel = NotificationChannel(channelID, channelName, channelImportance) 128 | nChannel.enableLights(true) 129 | nChannel.lightColor = Color.BLUE 130 | this.createNotificationChannel(nChannel) 131 | } 132 | } 133 | } 134 | } -------------------------------------------------------------------------------- /app/src/main/java/com/resocoder/timertutorial/util/PrefUtil.kt: -------------------------------------------------------------------------------- 1 | package com.resocoder.timertutorial.util 2 | 3 | import android.content.Context 4 | import android.preference.PreferenceManager 5 | import com.resocoder.timertutorial.TimerActivity 6 | 7 | 8 | class PrefUtil { 9 | companion object { 10 | 11 | private const val TIMER_LENGTH_ID = "com.resocoder.timer.timer_length" 12 | fun getTimerLength(context: Context): Int{ 13 | val preferences = PreferenceManager.getDefaultSharedPreferences(context) 14 | return preferences.getInt(TIMER_LENGTH_ID, 10) 15 | } 16 | 17 | private const val PREVIOUS_TIMER_LENGTH_SECONDS_ID = "com.resocoder.timer.previous_timer_length_seconds" 18 | 19 | fun getPreviousTimerLengthSeconds(context: Context): Long{ 20 | val preferences = PreferenceManager.getDefaultSharedPreferences(context) 21 | return preferences.getLong(PREVIOUS_TIMER_LENGTH_SECONDS_ID, 0) 22 | } 23 | 24 | fun setPreviousTimerLengthSeconds(seconds: Long, context: Context){ 25 | val editor = PreferenceManager.getDefaultSharedPreferences(context).edit() 26 | editor.putLong(PREVIOUS_TIMER_LENGTH_SECONDS_ID, seconds) 27 | editor.apply() 28 | } 29 | 30 | 31 | private const val TIMER_STATE_ID = "com.resocoder.timer.timer_state" 32 | 33 | fun getTimerState(context: Context): TimerActivity.TimerState{ 34 | val preferences = PreferenceManager.getDefaultSharedPreferences(context) 35 | val ordinal = preferences.getInt(TIMER_STATE_ID, 0) 36 | return TimerActivity.TimerState.values()[ordinal] 37 | } 38 | 39 | fun setTimerState(state: TimerActivity.TimerState, context: Context){ 40 | val editor = PreferenceManager.getDefaultSharedPreferences(context).edit() 41 | val ordinal = state.ordinal 42 | editor.putInt(TIMER_STATE_ID, ordinal) 43 | editor.apply() 44 | } 45 | 46 | 47 | private const val SECONDS_REMAINING_ID = "com.resocoder.timer.seconds_remaining" 48 | 49 | fun getSecondsRemaining(context: Context): Long{ 50 | val preferences = PreferenceManager.getDefaultSharedPreferences(context) 51 | return preferences.getLong(SECONDS_REMAINING_ID, 0) 52 | } 53 | 54 | fun setSecondsRemaining(seconds: Long, context: Context){ 55 | val editor = PreferenceManager.getDefaultSharedPreferences(context).edit() 56 | editor.putLong(SECONDS_REMAINING_ID, seconds) 57 | editor.apply() 58 | } 59 | 60 | 61 | private const val ALARM_SET_TIME_ID = "com.resocoder.timer.backgrounded_time" 62 | 63 | fun getAlarmSetTime(context: Context): Long{ 64 | val preferences = PreferenceManager.getDefaultSharedPreferences(context) 65 | return preferences.getLong(ALARM_SET_TIME_ID, 0) 66 | } 67 | 68 | fun setAlarmSetTime(time: Long, context: Context){ 69 | val editor = PreferenceManager.getDefaultSharedPreferences(context).edit() 70 | editor.putLong(ALARM_SET_TIME_ID, time) 71 | editor.apply() 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_pause.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_play_arrow.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_settings.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_stop.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_timer.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | 14 | 20 | 21 | 22 | 23 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_timer.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | 14 | 20 | 21 | 22 | 23 | 24 | 25 | 32 | 33 | 40 | 41 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /app/src/main/res/layout/content_timer.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 22 | 23 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_timer.xml: -------------------------------------------------------------------------------- 1 | 5 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ResoCoder/TimerAppAndroidTutorial/9846b940fe8da3c910942903a7b5310e457aa42e/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ResoCoder/TimerAppAndroidTutorial/9846b940fe8da3c910942903a7b5310e457aa42e/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ResoCoder/TimerAppAndroidTutorial/9846b940fe8da3c910942903a7b5310e457aa42e/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ResoCoder/TimerAppAndroidTutorial/9846b940fe8da3c910942903a7b5310e457aa42e/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ResoCoder/TimerAppAndroidTutorial/9846b940fe8da3c910942903a7b5310e457aa42e/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ResoCoder/TimerAppAndroidTutorial/9846b940fe8da3c910942903a7b5310e457aa42e/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ResoCoder/TimerAppAndroidTutorial/9846b940fe8da3c910942903a7b5310e457aa42e/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ResoCoder/TimerAppAndroidTutorial/9846b940fe8da3c910942903a7b5310e457aa42e/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ResoCoder/TimerAppAndroidTutorial/9846b940fe8da3c910942903a7b5310e457aa42e/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ResoCoder/TimerAppAndroidTutorial/9846b940fe8da3c910942903a7b5310e457aa42e/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 16dp 3 | 64dp 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | TimerTutorial 3 | Settings 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | 16 | 17 |