diff --git a/.gitignore b/.gitignore index d7d6aa00..ec851b00 100644 --- a/.gitignore +++ b/.gitignore @@ -1,22 +1,10 @@ -start/.gradle/5.1.1/gc.properties -start/.gradle/5.1.1/executionHistory/executionHistory.bin -start/.gradle/5.1.1/executionHistory/executionHistory.lock -start/.gradle/5.1.1/fileChanges/last-build.bin -start/.gradle/5.1.1/fileHashes/fileHashes.bin -start/.gradle/5.1.1/fileHashes/fileHashes.lock -start/.gradle/5.4.1/gc.properties -start/.gradle/5.4.1/executionHistory/executionHistory.bin -start/.gradle/5.4.1/executionHistory/executionHistory.lock -start/.gradle/5.4.1/fileChanges/last-build.bin -start/.gradle/5.4.1/fileContent/fileContent.lock -start/.gradle/5.4.1/fileHashes/fileHashes.bin -start/.gradle/5.4.1/fileHashes/fileHashes.lock -start/.gradle/5.4.1/fileHashes/resourceHashesCache.bin -start/.gradle/5.4.1/javaCompile/classAnalysis.bin -start/.gradle/5.4.1/javaCompile/jarAnalysis.bin -start/.gradle/5.4.1/javaCompile/javaCompile.lock -start/.gradle/5.4.1/javaCompile/taskHistory.bin -start/.gradle/buildOutputCleanup/buildOutputCleanup.lock -start/.gradle/buildOutputCleanup/cache.properties -start/.gradle/buildOutputCleanup/outputFiles.bin -start/.gradle/vcs-1/gc.properties +*.iml +.gradle +/local.properties +/.idea +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties/ diff --git a/app/build.gradle b/app/build.gradle index 5ca4e427..508dfc59 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -17,11 +17,12 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-kapt' +apply plugin: 'com.google.gms.google-services' android { compileSdkVersion 30 defaultConfig { - applicationId "com.example.android.eggtimernotifications" + applicationId "dev.filipebezerra.android.eggtimernotifications" minSdkVersion 19 targetSdkVersion 30 versionCode 1 @@ -34,30 +35,46 @@ android { proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } - - // Enables data binding. buildFeatures { dataBinding true } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8 + } } dependencies { - implementation fileTree(dir: 'libs', include: ['*.jar']) + // Kotlin support implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - implementation 'androidx.appcompat:appcompat:1.2.0' - implementation 'androidx.core:core-ktx:1.3.1' - implementation 'androidx.constraintlayout:constraintlayout:1.1.3' - implementation 'androidx.legacy:legacy-support-v4:1.0.0' - implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' - implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.1.0-rc01' - testImplementation 'junit:junit:4.12' - androidTestImplementation 'androidx.test:runner:1.2.0' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' + // AndroidX + implementation "androidx.appcompat:appcompat:1.2.0" + implementation "androidx.core:core-ktx:1.3.2" + implementation "androidx.fragment:fragment-ktx:$fragment_version" + implementation "androidx.constraintlayout:constraintlayout:2.0.4" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" + implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version" + implementation "androidx.work:work-runtime-ktx:$work_version" + + // Firebase + implementation platform("com.google.firebase:firebase-bom:26.2.0") + implementation "com.google.firebase:firebase-messaging-ktx:21.0.1" + + // Testing + testImplementation "junit:junit:4.13.1" + androidTestImplementation "androidx.test:runner:1.3.0" + androidTestImplementation "androidx.test.espresso:espresso-core:3.3.0" + // optional - Test helpers for LiveData + testImplementation "androidx.arch.core:core-testing:$arch_version" + // Testing Fragments in Isolation + debugImplementation "androidx.fragment:fragment-testing:$fragment_version" + // Test helpers + androidTestImplementation "androidx.work:work-testing:$work_version" - //for fcm - implementation 'com.google.firebase:firebase-core:17.0.0' - implementation 'com.google.firebase:firebase-iid:19.0.1' - implementation 'com.google.firebase:firebase-messaging:19.0.1' - implementation 'android.arch.work:work-runtime:1.0.1' + // Third-party + implementation 'com.jakewharton.timber:timber:4.7.1' } diff --git a/app/google-services.json b/app/google-services.json new file mode 100644 index 00000000..039152cd --- /dev/null +++ b/app/google-services.json @@ -0,0 +1,47 @@ +{ + "project_info": { + "project_number": "662070897175", + "project_id": "egg-timer-59de8", + "storage_bucket": "egg-timer-59de8.appspot.com" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:662070897175:android:3becb7029d5f23f260ca52", + "android_client_info": { + "package_name": "dev.filipebezerra.android.eggtimernotifications" + } + }, + "oauth_client": [ + { + "client_id": "662070897175-vnfjeoi2oguro0dbvt48ktvbfru7kt34.apps.googleusercontent.com", + "client_type": 1, + "android_info": { + "package_name": "dev.filipebezerra.android.eggtimernotifications", + "certificate_hash": "8c2424ba513d384fd4ec9f1dfa6099b0b36e677d" + } + }, + { + "client_id": "662070897175-pd3g3r728ecgd20o2m3dil14br2k9kha.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyBB6hjA-ebIWIonwu0quEzYLaZG3v_4CM8" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "662070897175-pd3g3r728ecgd20o2m3dil14br2k9kha.apps.googleusercontent.com", + "client_type": 3 + } + ] + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 04993f0f..efa2bd40 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,50 +1,75 @@ - - + package="dev.filipebezerra.android.eggtimernotifications" + > + + - - + android:theme="@style/AppTheme" + > + - + - + + - + android:name="dev.filipebezerra.android.eggtimernotifications.receiver.AlarmReceiver" + android:enabled="true" + android:exported="false" + /> - + android:name="dev.filipebezerra.android.eggtimernotifications.receiver.SnoozeReceiver" + android:enabled="true" + android:exported="false" + /> + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/example/android/eggtimernotifications/ui/EggTimerFragment.kt b/app/src/main/java/com/example/android/eggtimernotifications/ui/EggTimerFragment.kt deleted file mode 100644 index 0802dc0f..00000000 --- a/app/src/main/java/com/example/android/eggtimernotifications/ui/EggTimerFragment.kt +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (C) 2019 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.android.eggtimernotifications.ui - -import android.app.NotificationChannel -import android.app.NotificationManager -import android.graphics.Color -import android.os.Build -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.Toast -import androidx.databinding.DataBindingUtil -import androidx.fragment.app.Fragment -import androidx.lifecycle.ViewModelProvider -import com.example.android.eggtimernotifications.R -import com.example.android.eggtimernotifications.databinding.FragmentEggTimerBinding -import com.google.firebase.messaging.FirebaseMessaging - -class EggTimerFragment : Fragment() { - - private val TOPIC = "breakfast" - - override fun onCreateView( - inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - - val binding: FragmentEggTimerBinding = DataBindingUtil.inflate( - inflater, R.layout.fragment_egg_timer, container, false - ) - - val viewModel = ViewModelProvider(this).get(EggTimerViewModel::class.java) - - binding.eggTimerViewModel = viewModel - binding.lifecycleOwner = this.viewLifecycleOwner - - // TODO: Step 1.7 call create channel - - return binding.root - } - - private fun createChannel(channelId: String, channelName: String) { - // TODO: Step 1.6 START create a channel - - // TODO: Step 1.6 END create a channel - - } - - companion object { - fun newInstance() = EggTimerFragment() - } -} - diff --git a/app/src/main/java/com/example/android/eggtimernotifications/util/NotificationUtils.kt b/app/src/main/java/com/example/android/eggtimernotifications/util/NotificationUtils.kt deleted file mode 100644 index 2f9e2a2e..00000000 --- a/app/src/main/java/com/example/android/eggtimernotifications/util/NotificationUtils.kt +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) 2019 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.android.eggtimernotifications.util - -import android.app.NotificationManager -import android.app.PendingIntent -import android.content.Context -import android.content.Intent -import android.graphics.BitmapFactory -import androidx.core.app.NotificationCompat -import com.example.android.eggtimernotifications.MainActivity -import com.example.android.eggtimernotifications.R -import com.example.android.eggtimernotifications.receiver.SnoozeReceiver - -// Notification ID. -private val NOTIFICATION_ID = 0 -private val REQUEST_CODE = 0 -private val FLAGS = 0 - -// TODO: Step 1.1 extension function to send messages (GIVEN) -/** - * Builds and delivers the notification. - * - * @param context, activity context. - */ -fun NotificationManager.sendNotification(messageBody: String, applicationContext: Context) { - // Create the content intent for the notification, which launches - // this activity - // TODO: Step 1.11 create intent - - // TODO: Step 1.12 create PendingIntent - - // TODO: Step 2.0 add style - - // TODO: Step 2.2 add snooze action - - // TODO: Step 1.2 get an instance of NotificationCompat.Builder - // Build the notification - - // TODO: Step 1.8 use the new 'breakfast' notification channel - - // TODO: Step 1.3 set title, text and icon to builder - - // TODO: Step 1.13 set content intent - - // TODO: Step 2.1 add style to builder - - // TODO: Step 2.3 add snooze action - - // TODO: Step 2.5 set priority - - // TODO: Step 1.4 call notify - -} - -// TODO: Step 1.14 Cancel all notifications diff --git a/app/src/main/java/dev/filipebezerra/android/eggtimernotifications/EggTimerApplication.kt b/app/src/main/java/dev/filipebezerra/android/eggtimernotifications/EggTimerApplication.kt new file mode 100644 index 00000000..e4a4ff2b --- /dev/null +++ b/app/src/main/java/dev/filipebezerra/android/eggtimernotifications/EggTimerApplication.kt @@ -0,0 +1,11 @@ +package dev.filipebezerra.android.eggtimernotifications + +import android.app.Application +import timber.log.Timber + +class EggTimerApplication : Application() { + override fun onCreate() { + super.onCreate() + BuildConfig.DEBUG.takeIf { it }?.run { Timber.plant(Timber.DebugTree()) } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/android/eggtimernotifications/MainActivity.kt b/app/src/main/java/dev/filipebezerra/android/eggtimernotifications/MainActivity.kt similarity index 84% rename from app/src/main/java/com/example/android/eggtimernotifications/MainActivity.kt rename to app/src/main/java/dev/filipebezerra/android/eggtimernotifications/MainActivity.kt index 10621e1d..5dbd07ad 100644 --- a/app/src/main/java/com/example/android/eggtimernotifications/MainActivity.kt +++ b/app/src/main/java/dev/filipebezerra/android/eggtimernotifications/MainActivity.kt @@ -14,11 +14,12 @@ * limitations under the License. */ -package com.example.android.eggtimernotifications +package dev.filipebezerra.android.eggtimernotifications import android.os.Bundle import androidx.appcompat.app.AppCompatActivity -import com.example.android.eggtimernotifications.ui.EggTimerFragment +import dev.filipebezerra.android.eggtimernotifications.R +import dev.filipebezerra.android.eggtimernotifications.timer.EggTimerFragment class MainActivity : AppCompatActivity() { diff --git a/app/src/main/java/dev/filipebezerra/android/eggtimernotifications/important/ImportantActivity.kt b/app/src/main/java/dev/filipebezerra/android/eggtimernotifications/important/ImportantActivity.kt new file mode 100644 index 00000000..226a884e --- /dev/null +++ b/app/src/main/java/dev/filipebezerra/android/eggtimernotifications/important/ImportantActivity.kt @@ -0,0 +1,15 @@ +package dev.filipebezerra.android.eggtimernotifications.important + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import androidx.databinding.DataBindingUtil +import androidx.databinding.DataBindingUtil.setContentView +import dev.filipebezerra.android.eggtimernotifications.R +import dev.filipebezerra.android.eggtimernotifications.databinding.ImportantActivityBinding + +class ImportantActivity : AppCompatActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(this, R.layout.important_activity) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/android/eggtimernotifications/receiver/AlarmReceiver.kt b/app/src/main/java/dev/filipebezerra/android/eggtimernotifications/receiver/AlarmReceiver.kt similarity index 62% rename from app/src/main/java/com/example/android/eggtimernotifications/receiver/AlarmReceiver.kt rename to app/src/main/java/dev/filipebezerra/android/eggtimernotifications/receiver/AlarmReceiver.kt index 7313457c..61caed86 100644 --- a/app/src/main/java/com/example/android/eggtimernotifications/receiver/AlarmReceiver.kt +++ b/app/src/main/java/dev/filipebezerra/android/eggtimernotifications/receiver/AlarmReceiver.kt @@ -14,25 +14,21 @@ * limitations under the License. */ -package com.example.android.eggtimernotifications.receiver +package dev.filipebezerra.android.eggtimernotifications.receiver -import android.app.NotificationManager import android.content.BroadcastReceiver import android.content.Context import android.content.Intent -import android.widget.Toast -import androidx.core.content.ContextCompat -import com.example.android.eggtimernotifications.R -import com.example.android.eggtimernotifications.util.sendNotification +import dev.filipebezerra.android.eggtimernotifications.R +import dev.filipebezerra.android.eggtimernotifications.util.getNotificationManager +import dev.filipebezerra.android.eggtimernotifications.util.sendNotification class AlarmReceiver: BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { - // TODO: Step 1.10 [Optional] remove toast - Toast.makeText(context, context.getText(R.string.eggs_ready), Toast.LENGTH_SHORT).show() - - // TODO: Step 1.9 add call to sendNotification - + context.getNotificationManager().sendNotification( + context.getString(R.string.eggs_ready), + context + ) } - } \ No newline at end of file diff --git a/app/src/main/java/com/example/android/eggtimernotifications/receiver/SnoozeReceiver.kt b/app/src/main/java/dev/filipebezerra/android/eggtimernotifications/receiver/SnoozeReceiver.kt similarity index 83% rename from app/src/main/java/com/example/android/eggtimernotifications/receiver/SnoozeReceiver.kt rename to app/src/main/java/dev/filipebezerra/android/eggtimernotifications/receiver/SnoozeReceiver.kt index ceea1381..d46883f4 100644 --- a/app/src/main/java/com/example/android/eggtimernotifications/receiver/SnoozeReceiver.kt +++ b/app/src/main/java/dev/filipebezerra/android/eggtimernotifications/receiver/SnoozeReceiver.kt @@ -14,10 +14,9 @@ * limitations under the License. */ -package com.example.android.eggtimernotifications.receiver +package dev.filipebezerra.android.eggtimernotifications.receiver import android.app.AlarmManager -import android.app.NotificationManager import android.app.PendingIntent import android.content.BroadcastReceiver import android.content.Context @@ -25,8 +24,10 @@ import android.content.Intent import android.os.SystemClock import android.text.format.DateUtils import androidx.core.app.AlarmManagerCompat -import androidx.core.content.ContextCompat +import dev.filipebezerra.android.eggtimernotifications.util.cancelNotifications +import dev.filipebezerra.android.eggtimernotifications.util.getNotificationManager +// TODO: Improve it - Remove this DRY code class SnoozeReceiver: BroadcastReceiver() { private val REQUEST_CODE = 0 @@ -47,6 +48,8 @@ class SnoozeReceiver: BroadcastReceiver() { triggerTime, notifyPendingIntent ) + + context.getNotificationManager().cancelNotifications() } } \ No newline at end of file diff --git a/app/src/main/java/dev/filipebezerra/android/eggtimernotifications/service/EggTimerFirebaseMessagingService.kt b/app/src/main/java/dev/filipebezerra/android/eggtimernotifications/service/EggTimerFirebaseMessagingService.kt new file mode 100644 index 00000000..a4892adc --- /dev/null +++ b/app/src/main/java/dev/filipebezerra/android/eggtimernotifications/service/EggTimerFirebaseMessagingService.kt @@ -0,0 +1,26 @@ +package dev.filipebezerra.android.eggtimernotifications.service + +import com.google.firebase.messaging.FirebaseMessagingService +import com.google.firebase.messaging.RemoteMessage +import dev.filipebezerra.android.eggtimernotifications.util.getNotificationManager +import dev.filipebezerra.android.eggtimernotifications.util.sendNotification +import timber.log.Timber + +class EggTimerFirebaseMessagingService : FirebaseMessagingService() { + override fun onNewToken(newToken: String) { + Timber.i("Refreshed Firebase Token: $newToken") + } + + override fun onMessageReceived(remoteMessage: RemoteMessage) { + with(remoteMessage) { + from?.run { Timber.d("Message received from $this") } + data.takeIf { it.isNotEmpty() }?.run { + Timber.d("Data received within the message $data") + } + notification?.body?.run { applicationContext.getNotificationManager().sendNotification( + this, + applicationContext + ) } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/dev/filipebezerra/android/eggtimernotifications/timer/EggTimerFragment.kt b/app/src/main/java/dev/filipebezerra/android/eggtimernotifications/timer/EggTimerFragment.kt new file mode 100644 index 00000000..46c18d1e --- /dev/null +++ b/app/src/main/java/dev/filipebezerra/android/eggtimernotifications/timer/EggTimerFragment.kt @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2019 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dev.filipebezerra.android.eggtimernotifications.timer + +import android.content.Context +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import com.google.firebase.messaging.FirebaseMessaging +import dev.filipebezerra.android.eggtimernotifications.R +import dev.filipebezerra.android.eggtimernotifications.databinding.FragmentEggTimerBinding +import dev.filipebezerra.android.eggtimernotifications.util.createChannel +import dev.filipebezerra.android.eggtimernotifications.util.getNotificationManager +import dev.filipebezerra.android.eggtimernotifications.util.subscribeToTopicBreakfast + +class EggTimerFragment : Fragment() { + + private val viewModel: EggTimerViewModel by viewModels() + + private val TOPIC = "breakfast" + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View = FragmentEggTimerBinding.inflate(inflater, container, false) + .apply { + eggTimerViewModel = viewModel + lifecycleOwner = viewLifecycleOwner + } + .root + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + context?.run { + createEggTimerNotificationChannel() + createBreakfastNotificationChannel() + subscribeToTopicBreakfast() + } + } + + private fun Context.createEggTimerNotificationChannel() = + getNotificationManager().createChannel( + getString(R.string.egg_notification_channel_id), + getString(R.string.egg_notification_channel_name), + getString(R.string.egg_notification_channel_description) + ) + + private fun Context.createBreakfastNotificationChannel() = + getNotificationManager().createChannel( + getString(R.string.breakfast_notification_channel_id), + getString(R.string.breakfast_notification_channel_name), + getString(R.string.breakfast_notification_channel_description) + ) + + private fun Context.subscribeToTopicBreakfast() = + FirebaseMessaging.getInstance().subscribeToTopicBreakfast(this) + + companion object { + fun newInstance() = EggTimerFragment() + } +} + diff --git a/app/src/main/java/com/example/android/eggtimernotifications/ui/EggTimerViewModel.kt b/app/src/main/java/dev/filipebezerra/android/eggtimernotifications/timer/EggTimerViewModel.kt similarity index 85% rename from app/src/main/java/com/example/android/eggtimernotifications/ui/EggTimerViewModel.kt rename to app/src/main/java/dev/filipebezerra/android/eggtimernotifications/timer/EggTimerViewModel.kt index 934306f1..0e0b0486 100644 --- a/app/src/main/java/com/example/android/eggtimernotifications/ui/EggTimerViewModel.kt +++ b/app/src/main/java/dev/filipebezerra/android/eggtimernotifications/timer/EggTimerViewModel.kt @@ -14,20 +14,27 @@ * limitations under the License. */ -package com.example.android.eggtimernotifications.ui +package dev.filipebezerra.android.eggtimernotifications.timer -import android.app.* +import android.app.AlarmManager +import android.app.Application +import android.app.PendingIntent import android.content.Context import android.content.Intent import android.os.CountDownTimer import android.os.SystemClock import androidx.core.app.AlarmManagerCompat -import androidx.core.content.ContextCompat -import androidx.lifecycle.* -import com.example.android.eggtimernotifications.receiver.AlarmReceiver -import com.example.android.eggtimernotifications.R -import com.example.android.eggtimernotifications.util.sendNotification -import kotlinx.coroutines.* +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.viewModelScope +import dev.filipebezerra.android.eggtimernotifications.R +import dev.filipebezerra.android.eggtimernotifications.receiver.AlarmReceiver +import dev.filipebezerra.android.eggtimernotifications.util.cancelNotifications +import dev.filipebezerra.android.eggtimernotifications.util.getNotificationManager +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext class EggTimerViewModel(private val app: Application) : AndroidViewModel(app) { @@ -57,7 +64,6 @@ class EggTimerViewModel(private val app: Application) : AndroidViewModel(app) { val isAlarmOn: LiveData get() = _alarmOn - private lateinit var timer: CountDownTimer init { @@ -68,6 +74,7 @@ class EggTimerViewModel(private val app: Application) : AndroidViewModel(app) { PendingIntent.FLAG_NO_CREATE ) != null + // TODO: Improve it - Remove this DRY code notifyPendingIntent = PendingIntent.getBroadcast( getApplication(), REQUEST_CODE, @@ -118,9 +125,8 @@ class EggTimerViewModel(private val app: Application) : AndroidViewModel(app) { } val triggerTime = SystemClock.elapsedRealtime() + selectedInterval - // TODO: Step 1.5 get an instance of NotificationManager and call sendNotification - - // TODO: Step 1.15 call cancel notification + // TODO: Improve it - Remove this DRY code + app.applicationContext.getNotificationManager().cancelNotifications() AlarmManagerCompat.setExactAndAllowWhileIdle( alarmManager, diff --git a/app/src/main/java/com/example/android/eggtimernotifications/util/BindingUtils.kt b/app/src/main/java/dev/filipebezerra/android/eggtimernotifications/util/BindingAdapters.kt similarity index 94% rename from app/src/main/java/com/example/android/eggtimernotifications/util/BindingUtils.kt rename to app/src/main/java/dev/filipebezerra/android/eggtimernotifications/util/BindingAdapters.kt index 28f2bafa..5ffca6d9 100644 --- a/app/src/main/java/com/example/android/eggtimernotifications/util/BindingUtils.kt +++ b/app/src/main/java/dev/filipebezerra/android/eggtimernotifications/util/BindingAdapters.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.example.android.eggtimernotifications.util +package dev.filipebezerra.android.eggtimernotifications.util import android.text.format.DateUtils import android.widget.TextView diff --git a/app/src/main/java/dev/filipebezerra/android/eggtimernotifications/util/ContextCompatExt.kt b/app/src/main/java/dev/filipebezerra/android/eggtimernotifications/util/ContextCompatExt.kt new file mode 100644 index 00000000..e2f90d6f --- /dev/null +++ b/app/src/main/java/dev/filipebezerra/android/eggtimernotifications/util/ContextCompatExt.kt @@ -0,0 +1,10 @@ +package dev.filipebezerra.android.eggtimernotifications.util + +import android.app.NotificationManager +import android.content.Context +import androidx.core.content.ContextCompat.getSystemService + +fun Context.getNotificationManager(): NotificationManager = getSystemService( + this, + NotificationManager::class.java +) as NotificationManager \ No newline at end of file diff --git a/app/src/main/java/dev/filipebezerra/android/eggtimernotifications/util/FirebaseMessagingExt.kt b/app/src/main/java/dev/filipebezerra/android/eggtimernotifications/util/FirebaseMessagingExt.kt new file mode 100644 index 00000000..a7472c2a --- /dev/null +++ b/app/src/main/java/dev/filipebezerra/android/eggtimernotifications/util/FirebaseMessagingExt.kt @@ -0,0 +1,19 @@ +package dev.filipebezerra.android.eggtimernotifications.util + +import android.content.Context +import android.widget.Toast +import com.google.firebase.messaging.FirebaseMessaging +import dev.filipebezerra.android.eggtimernotifications.R + +private const val BREAKFAST_TOPIC = "breakfast" + +fun FirebaseMessaging.subscribeToTopicBreakfast(context: Context) { + subscribeToTopic(BREAKFAST_TOPIC) + .addOnCompleteListener { + val userFeedback = if (it.isSuccessful) + R.string.message_subscribed + else + R.string.message_subscribe_failed + Toast.makeText(context.applicationContext, userFeedback, Toast.LENGTH_SHORT).show(); + } +} \ No newline at end of file diff --git a/app/src/main/java/dev/filipebezerra/android/eggtimernotifications/util/NotificationExt.kt b/app/src/main/java/dev/filipebezerra/android/eggtimernotifications/util/NotificationExt.kt new file mode 100644 index 00000000..90e1773c --- /dev/null +++ b/app/src/main/java/dev/filipebezerra/android/eggtimernotifications/util/NotificationExt.kt @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2019 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dev.filipebezerra.android.eggtimernotifications.util + +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import android.graphics.BitmapFactory +import android.graphics.Color +import android.os.Build +import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationManagerCompat +import androidx.core.content.ContextCompat +import dev.filipebezerra.android.eggtimernotifications.MainActivity +import dev.filipebezerra.android.eggtimernotifications.R +import dev.filipebezerra.android.eggtimernotifications.important.ImportantActivity +import dev.filipebezerra.android.eggtimernotifications.receiver.SnoozeReceiver + +private val NOTIFICATION_ID = 0 +private val REQUEST_CODE = 0 + +/** + * Builds and delivers the notification. + * + * @param context activity context. + */ +fun NotificationManager.sendNotification( + messageBody: String, + context: Context, +) { + val contentIntent = Intent(context, MainActivity::class.java) + .apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK } + val contentPendingIntent = PendingIntent.getActivity( + context, + REQUEST_CODE, + contentIntent, + PendingIntent.FLAG_UPDATE_CURRENT + ) + + val snoozeIntent = Intent(context, SnoozeReceiver::class.java) + val snoozePendingIntent = PendingIntent.getBroadcast( + context, + REQUEST_CODE, + snoozeIntent, + PendingIntent.FLAG_ONE_SHOT + ) + + val eggImage = BitmapFactory.decodeResource( + context.resources, + R.drawable.cooked_egg + ) + + val style = NotificationCompat.BigPictureStyle() + .bigPicture(eggImage) + .bigLargeIcon(null) + + val fullScreenIntent = Intent(context, ImportantActivity::class.java) + .apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK } + val fullScreenPedingIntent = PendingIntent.getActivity( + context, + REQUEST_CODE, + fullScreenIntent, + PendingIntent.FLAG_UPDATE_CURRENT + ) + + NotificationCompat.Builder( + context, + context.getString(R.string.egg_notification_channel_id) + ) + .setSmallIcon(R.drawable.egg_icon) + .setContentTitle(context.getString(R.string.notification_title)) + .setContentText(messageBody) + .setContentIntent(contentPendingIntent) + // auto dissmiss notification from status bar + .setAutoCancel(true) + // style notifications with Big Style containing a big image + .setStyle(style) + // use this to show a small icon when collapsing the notification + .setLargeIcon(eggImage) + // snooze action to reeschedule the notification + .addAction( + R.drawable.egg_icon, + context.getString(R.string.snooze), + snoozePendingIntent + ) + // priority defines the level of user interruption + // High priority makes a sound and appears as a heads up notification + // Default priority makes a sound + // Low priority makes no sound + // Min priority makes no sound and does not appear in the status bar + .setPriority(NotificationManagerCompat.IMPORTANCE_HIGH) + // This information about your notification category is used by the system to make decisions about displaying your notification when the device is in Do Not Disturb mode. + // https://developer.android.com/training/notify-user/build-notification#system-category + .setCategory(NotificationCompat.CATEGORY_REMINDER) + // https://developer.android.com/training/notify-user/build-notification#urgent-message + .setFullScreenIntent(fullScreenPedingIntent, true) + .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) + .setVibrate(longArrayOf(100, 200, 100, 200)) + .run { + notify(NOTIFICATION_ID, this.build()) + } +} + +fun NotificationManager.createChannel( + channelId: String, + channelName: String, + channelDescription: String, +) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + NotificationChannel( + channelId, + channelName, + NotificationManager.IMPORTANCE_HIGH + ).apply { + enableLights(true) + lightColor = Color.RED + enableVibration(true) + description = channelDescription + setShowBadge(false) + }.run { + createNotificationChannel(this) + } + } +} + +fun NotificationManager.cancelNotifications() = cancelAll() diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index a91ed6c4..88987da9 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -21,4 +21,4 @@ android:id="@+id/container" android:layout_width="match_parent" android:layout_height="match_parent" - tools:context=".MainActivity"/> + tools:context="dev.filipebezerra.android.eggtimernotifications.MainActivity"/> diff --git a/app/src/main/res/layout/fragment_egg_timer.xml b/app/src/main/res/layout/fragment_egg_timer.xml index b5eab981..c892f740 100644 --- a/app/src/main/res/layout/fragment_egg_timer.xml +++ b/app/src/main/res/layout/fragment_egg_timer.xml @@ -23,13 +23,13 @@ + type="dev.filipebezerra.android.eggtimernotifications.timer.EggTimerViewModel" /> + tools:context="dev.filipebezerra.android.eggtimernotifications.timer.EggTimerFragment"> + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml new file mode 100644 index 00000000..0aff7969 --- /dev/null +++ b/app/src/main/res/values/dimens.xml @@ -0,0 +1,4 @@ + + + 32dp + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 79164f9e..01d424b4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,5 +1,4 @@ - - egg_channel Egg + Time for breakfast + + fcm_default_channel Breakfast + It is time! Bon appetit! - Egg timer is running... + Egg timer is running… Breakfast reminder Snooze + Important!!! diff --git a/build.gradle b/build.gradle index edab77f3..64d9d446 100644 --- a/build.gradle +++ b/build.gradle @@ -16,17 +16,16 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = '1.3.72' + ext.kotlin_version = '1.4.21' repositories { google() jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:4.0.1' + classpath 'com.android.tools.build:gradle:4.1.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - // NOTE: Do not place your application dependencies here; they belong - // in the individual module build.gradle files + classpath 'com.google.gms:google-services:4.3.4' } } @@ -34,10 +33,16 @@ allprojects { repositories { google() jcenter() - } } task clean(type: Delete) { delete rootProject.buildDir } + +ext { + lifecycle_version = "2.2.0" + work_version = "2.4.0" + arch_version = "2.1.0" + fragment_version = "1.2.5" +} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 47a3857d..e346567c 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Wed Aug 28 16:34:52 PDT 2019 +#Sat Dec 26 13:10:10 BRT 2020 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip diff --git a/settings.gradle b/settings.gradle index e14c9a6d..c66fc0ad 100644 --- a/settings.gradle +++ b/settings.gradle @@ -16,3 +16,4 @@ */ include ':app' +rootProject.name = "Egg Timer" \ No newline at end of file