Skip to content

Commit f6bf0cd

Browse files
committed
feat: add daily reminder notification
1 parent 383d363 commit f6bf0cd

7 files changed

Lines changed: 185 additions & 3 deletions

File tree

app/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ dependencies {
6767
implementation(libs.androidx.work.runtime.ktx)
6868
implementation(libs.androidx.preference.ktx)
6969
implementation(libs.androidx.hilt.common)
70+
implementation(libs.androidx.hilt.work)
7071
ksp(libs.hilt.android.compiler)
7172

7273
implementation(libs.retrofit2.retrofit)
Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,32 @@
11
package com.zekecode.akira_financialtracker
22

33
import android.app.Application
4+
import androidx.hilt.work.HiltWorkerFactory
5+
import androidx.work.Configuration
6+
import com.zekecode.akira_financialtracker.utils.NotificationScheduler
47
import dagger.hilt.android.HiltAndroidApp
8+
import javax.inject.Inject
59

610
@HiltAndroidApp
7-
class AkiraApplication : Application()
11+
class AkiraApplication : Application(), Configuration.Provider {
12+
13+
@Inject
14+
lateinit var workerFactory: HiltWorkerFactory
15+
16+
@Inject
17+
lateinit var notificationScheduler: NotificationScheduler
18+
19+
override fun onCreate() {
20+
super.onCreate()
21+
setupNotifications()
22+
}
23+
24+
private fun setupNotifications() {
25+
notificationScheduler.scheduleDailyReminder()
26+
}
27+
28+
override val workManagerConfiguration: Configuration
29+
get() = Configuration.Builder()
30+
.setWorkerFactory(workerFactory)
31+
.build()
32+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.zekecode.akira_financialtracker.di
2+
3+
import android.content.Context
4+
import com.zekecode.akira_financialtracker.utils.NotificationScheduler
5+
import dagger.Module
6+
import dagger.Provides
7+
import dagger.hilt.InstallIn
8+
import dagger.hilt.android.qualifiers.ApplicationContext
9+
import dagger.hilt.components.SingletonComponent
10+
import javax.inject.Singleton
11+
12+
@Module
13+
@InstallIn(SingletonComponent::class)
14+
object NotificationModule {
15+
16+
@Provides
17+
@Singleton
18+
fun provideNotificationScheduler(@ApplicationContext context: Context): NotificationScheduler {
19+
return NotificationScheduler(context)
20+
}
21+
}

app/src/main/java/com/zekecode/akira_financialtracker/ui/activities/MainActivity.kt

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
package com.zekecode.akira_financialtracker.ui.activities
22

33
import android.content.Intent
4+
import android.content.pm.PackageManager
5+
import android.os.Build
46
import android.os.Bundle
7+
import androidx.activity.result.contract.ActivityResultContracts
58
import androidx.appcompat.app.AlertDialog
69
import androidx.appcompat.app.AppCompatActivity
10+
import androidx.core.content.ContextCompat
711
import androidx.lifecycle.ViewModelProvider
812
import androidx.navigation.NavController
913
import androidx.navigation.fragment.NavHostFragment
@@ -21,6 +25,14 @@ class MainActivity : AppCompatActivity() {
2125
private lateinit var navController: NavController
2226
private lateinit var viewModel: MainViewModel
2327

28+
private val requestPermissionLauncher = registerForActivityResult(
29+
ActivityResultContracts.RequestPermission()
30+
) { isGranted: Boolean ->
31+
if (!isGranted) {
32+
// Handle permission denied
33+
}
34+
}
35+
2436
companion object {
2537
const val REQUEST_INSTALL_PERMISSION = 1001
2638
}
@@ -41,7 +53,7 @@ class MainActivity : AppCompatActivity() {
4153
binding = ActivityMainBinding.inflate(layoutInflater)
4254
setContentView(binding.root)
4355
setupNavigation()
44-
56+
askNotificationPermission()
4557
setUpObservers()
4658
}
4759

@@ -79,4 +91,16 @@ class MainActivity : AppCompatActivity() {
7991
.setNegativeButton("Later", null)
8092
.show()
8193
}
94+
95+
private fun askNotificationPermission() {
96+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
97+
if (ContextCompat.checkSelfPermission(
98+
this,
99+
android.Manifest.permission.POST_NOTIFICATIONS
100+
) != PackageManager.PERMISSION_GRANTED
101+
) {
102+
requestPermissionLauncher.launch(android.Manifest.permission.POST_NOTIFICATIONS)
103+
}
104+
}
105+
}
82106
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package com.zekecode.akira_financialtracker.utils
2+
3+
import android.content.Context
4+
import androidx.work.ExistingPeriodicWorkPolicy
5+
import androidx.work.PeriodicWorkRequestBuilder
6+
import androidx.work.WorkManager
7+
import com.zekecode.akira_financialtracker.workers.DailyReminderWorker
8+
import java.util.concurrent.TimeUnit
9+
import javax.inject.Inject
10+
11+
class NotificationScheduler @Inject constructor(private val context: Context) {
12+
13+
companion object {
14+
private const val DAILY_REMINDER_WORK = "daily_reminder_work"
15+
}
16+
17+
fun scheduleDailyReminder(hourOfDay: Int = 16) { // Default to 4 PM
18+
// Cancel any existing work
19+
WorkManager.getInstance(context).cancelUniqueWork(DAILY_REMINDER_WORK)
20+
21+
val dailyWorkRequest = PeriodicWorkRequestBuilder<DailyReminderWorker>(
22+
24, TimeUnit.HOURS
23+
).build()
24+
25+
WorkManager.getInstance(context)
26+
.enqueueUniquePeriodicWork(
27+
DAILY_REMINDER_WORK,
28+
ExistingPeriodicWorkPolicy.UPDATE,
29+
dailyWorkRequest
30+
)
31+
}
32+
33+
fun cancelDailyReminder() {
34+
WorkManager.getInstance(context).cancelUniqueWork(DAILY_REMINDER_WORK)
35+
}
36+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package com.zekecode.akira_financialtracker.workers
2+
3+
import android.app.NotificationChannel
4+
import android.app.NotificationManager
5+
import android.app.PendingIntent
6+
import android.content.Context
7+
import android.content.Intent
8+
import android.os.Build
9+
import androidx.core.app.NotificationCompat
10+
import androidx.hilt.work.HiltWorker
11+
import androidx.work.Worker
12+
import androidx.work.WorkerParameters
13+
import com.zekecode.akira_financialtracker.R
14+
import com.zekecode.akira_financialtracker.ui.activities.MainActivity
15+
import dagger.assisted.Assisted
16+
import dagger.assisted.AssistedInject
17+
18+
@HiltWorker
19+
class DailyReminderWorker @AssistedInject constructor(
20+
@Assisted private val context: Context,
21+
@Assisted workerParams: WorkerParameters
22+
) : Worker(context, workerParams) {
23+
24+
companion object {
25+
const val CHANNEL_ID = "transaction_reminder_channel"
26+
const val NOTIFICATION_ID = 1
27+
}
28+
29+
override fun doWork(): Result {
30+
createNotificationChannel()
31+
showNotification()
32+
return Result.success()
33+
}
34+
35+
private fun createNotificationChannel() {
36+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
37+
val name = "Daily Transaction Reminders"
38+
val descriptionText = "Reminds you to log your daily transactions"
39+
val importance = NotificationManager.IMPORTANCE_DEFAULT
40+
val channel = NotificationChannel(CHANNEL_ID, name, importance).apply {
41+
description = descriptionText
42+
}
43+
44+
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
45+
notificationManager.createNotificationChannel(channel)
46+
}
47+
}
48+
49+
private fun showNotification() {
50+
val intent = Intent(context, MainActivity::class.java).apply {
51+
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
52+
}
53+
54+
val pendingIntent = PendingIntent.getActivity(
55+
context,
56+
0,
57+
intent,
58+
PendingIntent.FLAG_IMMUTABLE
59+
)
60+
61+
val notification = NotificationCompat.Builder(context, CHANNEL_ID)
62+
.setSmallIcon(R.drawable.ic_notifications)
63+
.setContentTitle("Log Your Transactions")
64+
.setContentText("Don't forget to log today's transactions!")
65+
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
66+
.setAutoCancel(true)
67+
.setContentIntent(pendingIntent)
68+
.build()
69+
70+
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
71+
notificationManager.notify(NOTIFICATION_ID, notification)
72+
}
73+
}

gradle/libs.versions.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[versions]
22
agp = "8.5.2"
33
fragmentKtx = "1.8.5"
4-
hiltAndroidCompiler = "2.51.1"
4+
hiltAndroidCompiler = "2.55"
55
kotlin = "2.1.0"
66
coreKtx = "1.15.0"
77
junit = "4.13.2"
@@ -24,6 +24,7 @@ vico = "2.0.0-beta.5"
2424
preferenceKtx = "1.2.1"
2525
workRuntimeKtx = "2.10.0"
2626
hiltCommon = "1.2.0"
27+
hiltWork = "1.2.0"
2728

2829
[libraries]
2930
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
@@ -54,6 +55,7 @@ androidx-navigation-ui-ktx = { group = "androidx.navigation", name = "navigation
5455
vico-views = { group = "com.patrykandpatrick.vico", name = "views", version.ref = "vico" }
5556
androidx-preference-ktx = { group = "androidx.preference", name = "preference-ktx", version.ref = "preferenceKtx" }
5657
androidx-hilt-common = { group = "androidx.hilt", name = "hilt-common", version.ref = "hiltCommon" }
58+
androidx-hilt-work = { group = "androidx.hilt", name = "hilt-work", version.ref = "hiltWork" }
5759

5860
[plugins]
5961
android-application = { id = "com.android.application", version.ref = "agp" }

0 commit comments

Comments
 (0)