diff --git a/app/src/main/java/com/starry/greenstash/reminder/ReminderManager.kt b/app/src/main/java/com/starry/greenstash/reminder/ReminderManager.kt index ab4414aa..0579b4e4 100644 --- a/app/src/main/java/com/starry/greenstash/reminder/ReminderManager.kt +++ b/app/src/main/java/com/starry/greenstash/reminder/ReminderManager.kt @@ -36,6 +36,14 @@ import java.util.Calendar import java.util.Locale +/** + * Manages the reminders for goals. + * This class is responsible for scheduling and stopping reminders for goals. + * It uses [AlarmManager] to schedule reminders. + * The reminder time is set to 9:30 AM or 09:30 Hrs. + * + * @param context The context of the application. + */ class ReminderManager(private val context: Context) { companion object { @@ -47,7 +55,10 @@ class ReminderManager(private val context: Context) { private val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager - /** Schedule a reminder for given goal id.*/ + /** + * Schedules reminder for the given goal id. + * @param goalId The id of the goal for which reminder is to be scheduled. + */ fun scheduleReminder(goalId: Long) { val (hours, min) = REMINDER_TIME.split(":").map { it.toInt() } val calendarNow = Calendar.getInstance(Locale.ENGLISH) @@ -75,7 +86,10 @@ class ReminderManager(private val context: Context) { } - /** Stops reminder for the given goal id */ + /** + * Stops the reminder for the given goal id. + * @param goalId The id of the goal for which reminder is to be stopped. + */ fun stopReminder(goalId: Long) { if (isReminderSet(goalId)) { val reminderIntent = createReminderIntent( @@ -90,7 +104,11 @@ class ReminderManager(private val context: Context) { } } - /** Check if reminder is et for the given goalId.*/ + /** + * Checks if reminder is set for the given goal id. + * @param goalId The id of the goal for which reminder is to be checked. + * @return true if reminder is set, false otherwise. + */ fun isReminderSet(goalId: Long): Boolean { val reminderIntent = createReminderIntent( goalId = goalId, flags = PendingIntent.FLAG_NO_CREATE or PendingIntent.FLAG_IMMUTABLE @@ -102,6 +120,7 @@ class ReminderManager(private val context: Context) { * Schedules reminder for goals which have reminder enabled * but reminder for them is not scheduled already, by calling * the [scheduleReminder] function internally. + * @param allGoals The list of goals with transactions. */ fun checkAndScheduleReminders(allGoals: List) { Log.d(TAG, "Scheduling reminders for goals with reminder.") @@ -114,6 +133,7 @@ class ReminderManager(private val context: Context) { Log.d(TAG, "Scheduled reminders for goals with reminder.") } + // Creates a pending intent for the reminder. private fun createReminderIntent(goalId: Long, flags: Int) = Intent(context.applicationContext, AlarmReceiver::class.java).apply { putExtra(INTENT_EXTRA_GOAL_ID, goalId) diff --git a/app/src/main/java/com/starry/greenstash/reminder/ReminderNotificationSender.kt b/app/src/main/java/com/starry/greenstash/reminder/ReminderNotificationSender.kt index d5e6d8c5..c8a6d77a 100644 --- a/app/src/main/java/com/starry/greenstash/reminder/ReminderNotificationSender.kt +++ b/app/src/main/java/com/starry/greenstash/reminder/ReminderNotificationSender.kt @@ -41,6 +41,11 @@ import com.starry.greenstash.utils.PreferenceUtil import com.starry.greenstash.utils.Utils +/** + * Handles the sending of notifications for goal reminders. + * @param context The context of the application. + * @param preferenceUtil The preference utility to access the user preferences. + */ class ReminderNotificationSender( private val context: Context, private val preferenceUtil: PreferenceUtil @@ -54,6 +59,13 @@ class ReminderNotificationSender( private val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + /** + * Sends a notification to the user for the goal reminder. + * The notification contains the title of the goal, a description, and two actions: + * 1. Deposit: To deposit the calculated amount for the goal. + * 2. Dismiss: To dismiss the notification. + * @param goalItem The goal with transactions for which the notification is to be sent. + */ fun sendNotification(goalItem: GoalWithTransactions) { val goal = goalItem.goal @@ -129,6 +141,11 @@ class ReminderNotificationSender( notificationManager.notify(goal.goalId.toInt(), notification.build()) } + /** + * Updates the notification with the deposited message. + * @param goalId The goal id for which the notification is to be updated. + * @param amount The amount deposited. + */ fun updateWithDepositNotification(goalId: Long, amount: Double) { val defCurrency = preferenceUtil.getString(PreferenceUtil.DEFAULT_CURRENCY_STR, "") val notification = NotificationCompat.Builder(context, REMINDER_CHANNEL_ID) @@ -148,8 +165,13 @@ class ReminderNotificationSender( notificationManager.notify(goalId.toInt(), notification.build()) } + /** + * Dismisses the notification for the goal. + * @param goalId The goal id for which the notification is to be dismissed. + */ fun dismissNotification(goalId: Long) = notificationManager.cancel(goalId.toInt()) + // Creates a pending intent for the deposit action. private fun createDepositIntent(goalId: Long, amount: Double) = Intent(context, ReminderDepositReceiver::class.java).apply { putExtra(ReminderDepositReceiver.REMINDER_GOAL_ID, goalId) @@ -161,6 +183,7 @@ class ReminderNotificationSender( ) } + // Creates a pending intent for the dismiss action. private fun createDismissIntent(goalId: Long) = Intent(context, ReminderDismissReceiver::class.java).apply { putExtra(ReminderDismissReceiver.REMINDER_GOAL_ID, goalId) @@ -171,6 +194,7 @@ class ReminderNotificationSender( ) } + // Creates a pending intent to open the main activity when the notification is clicked. private fun createActivityIntent() = Intent(context, MainActivity::class.java).let { intent -> PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE) } diff --git a/app/src/main/java/com/starry/greenstash/reminder/receivers/BootReceiver.kt b/app/src/main/java/com/starry/greenstash/reminder/receivers/BootReceiver.kt index e5a0e3e6..3464b22c 100644 --- a/app/src/main/java/com/starry/greenstash/reminder/receivers/BootReceiver.kt +++ b/app/src/main/java/com/starry/greenstash/reminder/receivers/BootReceiver.kt @@ -28,26 +28,15 @@ package com.starry.greenstash.reminder.receivers import android.content.BroadcastReceiver import android.content.Context import android.content.Intent -import androidx.compose.animation.ExperimentalAnimationApi -import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.ui.ExperimentalComposeUiApi import com.starry.greenstash.database.goal.GoalDao import com.starry.greenstash.reminder.ReminderManager import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.launch import javax.inject.Inject -@ExperimentalCoroutinesApi -@ExperimentalMaterial3Api -@ExperimentalAnimationApi -@ExperimentalComposeUiApi -@ExperimentalFoundationApi -@ExperimentalMaterialApi + @AndroidEntryPoint class BootReceiver : BroadcastReceiver() { diff --git a/app/src/main/java/com/starry/greenstash/reminder/receivers/DateTimeChangeReceiver.kt b/app/src/main/java/com/starry/greenstash/reminder/receivers/DateTimeChangeReceiver.kt index 6c988630..c72d70ac 100644 --- a/app/src/main/java/com/starry/greenstash/reminder/receivers/DateTimeChangeReceiver.kt +++ b/app/src/main/java/com/starry/greenstash/reminder/receivers/DateTimeChangeReceiver.kt @@ -28,26 +28,15 @@ package com.starry.greenstash.reminder.receivers import android.content.BroadcastReceiver import android.content.Context import android.content.Intent -import androidx.compose.animation.ExperimentalAnimationApi -import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.ui.ExperimentalComposeUiApi import com.starry.greenstash.database.goal.GoalDao import com.starry.greenstash.reminder.ReminderManager import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.launch import javax.inject.Inject -@ExperimentalCoroutinesApi -@ExperimentalMaterial3Api -@ExperimentalAnimationApi -@ExperimentalComposeUiApi -@ExperimentalFoundationApi -@ExperimentalMaterialApi + @AndroidEntryPoint class DateTimeChangeReceiver : BroadcastReceiver() { diff --git a/app/src/main/java/com/starry/greenstash/ui/screens/archive/ArchiveViewModel.kt b/app/src/main/java/com/starry/greenstash/ui/screens/archive/ArchiveViewModel.kt index 71785f1f..bff21647 100644 --- a/app/src/main/java/com/starry/greenstash/ui/screens/archive/ArchiveViewModel.kt +++ b/app/src/main/java/com/starry/greenstash/ui/screens/archive/ArchiveViewModel.kt @@ -60,6 +60,10 @@ class ArchiveViewModel @Inject constructor( val updatedGoal = goal.copy(archived = false) updatedGoal.goalId = goal.goalId goalDao.updateGoal(updatedGoal) + // Schedule the reminder if it was set for the goal + if (reminderManager.isReminderSet(goal.goalId)) { + reminderManager.scheduleReminder(goal.goalId) + } } } diff --git a/app/src/main/java/com/starry/greenstash/utils/GoalTextUtils.kt b/app/src/main/java/com/starry/greenstash/utils/GoalTextUtils.kt index 7ba84908..096fffc4 100644 --- a/app/src/main/java/com/starry/greenstash/utils/GoalTextUtils.kt +++ b/app/src/main/java/com/starry/greenstash/utils/GoalTextUtils.kt @@ -35,6 +35,9 @@ import java.time.LocalDateTime import java.time.format.DateTimeFormatter import java.time.temporal.ChronoUnit +/** + * Utility class to build text for goal items. + */ object GoalTextUtils { data class CalculatedDays( @@ -42,6 +45,15 @@ object GoalTextUtils { val parsedEndDate: String ) + /** + * Build primary text for the classic style goal item. + * + * @param context Context + * @param progressPercent Int + * @param goalItem GoalWithTransactions + * @param currencyCode String + * @return String + */ fun buildPrimaryText( context: Context, progressPercent: Int, @@ -81,6 +93,15 @@ object GoalTextUtils { return text } + /** + * Build secondary text for the classic style goal item. + * + * @param context Context + * @param goalItem GoalWithTransactions + * @param currencyCode String + * @param datePattern String + * @return String + */ fun buildSecondaryText( context: Context, goalItem: GoalWithTransactions, @@ -145,6 +166,14 @@ object GoalTextUtils { } + /** + * Get the remaining days text for the goal item. + * + * @param context Context + * @param goalItem GoalWithTransactions + * @param datePattern String + * @return String + */ fun getRemainingDaysText( context: Context, goalItem: GoalWithTransactions, @@ -164,6 +193,13 @@ object GoalTextUtils { } + /** + * Calculate the remaining days between today and the goal's deadline. + * + * @param goal Goal + * @param datePattern String + * @return CalculatedDays + */ fun calcRemainingDays(goal: Goal, datePattern: String): CalculatedDays { // calculate remaining days between today and endDate (deadline). val dateFormatter: DateTimeFormatter = diff --git a/app/src/main/java/com/starry/greenstash/utils/ImageUtils.kt b/app/src/main/java/com/starry/greenstash/utils/ImageUtils.kt index e9c5334d..bbc228e1 100644 --- a/app/src/main/java/com/starry/greenstash/utils/ImageUtils.kt +++ b/app/src/main/java/com/starry/greenstash/utils/ImageUtils.kt @@ -33,11 +33,17 @@ import android.util.Log import androidx.compose.material.icons.Icons import androidx.compose.ui.graphics.vector.ImageVector + +/** Utility class for image operations. */ object ImageUtils { private const val TAG = "ImageUtils" - /** Create image vector from icon name. */ + /** + * Create an image vector from the given name. + * @param name Name of the image vector. + * @return ImageVector object or null if not found. + */ fun createIconVector(name: String): ImageVector? { return try { val className = "androidx.compose.material.icons.filled.${name}Kt" @@ -50,13 +56,20 @@ object ImageUtils { } } - /** Get bitmap from image Uri. */ + /** + * Convert the given image URI to a bitmap. + * @param uri URI of the image. + * @param context Context of the application. + * @param maxSize Maximum size of the image. + * @return Bitmap object. + */ fun uriToBitmap(uri: Uri, context: Context, maxSize: Int): Bitmap { val stream = context.contentResolver.openInputStream(uri) val imageBm = BitmapFactory.decodeStream(stream) return compressBitmap(imageBm, maxSize) } + // Compress the bitmap to the given size. private fun compressBitmap(bitmap: Bitmap, maxSize: Int): Bitmap { var width = bitmap.width var height = bitmap.height diff --git a/app/src/main/java/com/starry/greenstash/utils/PreferenceUtil.kt b/app/src/main/java/com/starry/greenstash/utils/PreferenceUtil.kt index a027815d..854e6e1a 100644 --- a/app/src/main/java/com/starry/greenstash/utils/PreferenceUtil.kt +++ b/app/src/main/java/com/starry/greenstash/utils/PreferenceUtil.kt @@ -26,12 +26,16 @@ package com.starry.greenstash.utils import android.content.Context -import android.content.SharedPreferences import com.starry.greenstash.ui.screens.settings.DateStyle +/** + * Utility class to handle shared preferences for the app. + * @param context The context of the app. + */ class PreferenceUtil(context: Context) { companion object { + // Shared preferences file name private const val PREFS_NAME = "greenstash_settings" // Main preference keys @@ -54,10 +58,10 @@ class PreferenceUtil(context: Context) { const val INFO_TRANSACTION_SWIPE_TIP_BOOL = "info_transaction_swipe_tip" } - private var prefs: SharedPreferences + // Shared preferences instance + private var prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) init { - prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) // Pre-populate some preference data with default values if (!keyExists(DEFAULT_CURRENCY_STR)) { putString(DEFAULT_CURRENCY_STR, "USD") @@ -67,36 +71,74 @@ class PreferenceUtil(context: Context) { } } + /** + * Check if a key exists in the shared preferences. + * @param key The key to check. + * @return true if the key exists, false otherwise. + */ private fun keyExists(key: String): Boolean { return prefs.contains(key) } + /** + * Put a string value in the shared preferences. + * @param key The key to store the value under. + * @param value The value to store. + */ fun putString(key: String, value: String) { val prefsEditor = prefs.edit() prefsEditor.putString(key, value) prefsEditor.apply() } + /** + * Put an integer value in the shared preferences. + * @param key The key to store the value under. + * @param value The value to store. + */ fun putInt(key: String, value: Int) { val prefsEditor = prefs.edit() prefsEditor.putInt(key, value) prefsEditor.apply() } + /** + * Put a boolean value in the shared preferences. + * @param key The key to store the value under. + * @param value The value to store. + */ fun putBoolean(key: String, value: Boolean) { val prefsEditor = prefs.edit() prefsEditor.putBoolean(key, value) prefsEditor.apply() } + /** + * Get a string value from the shared preferences. + * @param key The key to get the value from. + * @param defValue The default value to return if the key does not exist. + * @return The value stored under the key, or the default value if the key does not exist. + */ fun getString(key: String, defValue: String): String? { return prefs.getString(key, defValue) } + /** + * Get an integer value from the shared preferences. + * @param key The key to get the value from. + * @param defValue The default value to return if the key does not exist. + * @return The value stored under the key, or the default value if the key does not exist. + */ fun getInt(key: String, defValue: Int): Int { return prefs.getInt(key, defValue) } + /** + * Get a boolean value from the shared preferences. + * @param key The key to get the value from. + * @param defValue The default value to return if the key does not exist. + * @return The value stored under the key, or the default value if the key does not exist. + */ fun getBoolean(key: String, defValue: Boolean): Boolean { return prefs.getBoolean(key, defValue) } diff --git a/app/src/main/res/font/opensans_variablefont_wdth_wght.ttf b/app/src/main/res/font/opensans_variablefont_wdth_wght.ttf deleted file mode 100644 index ac587b48..00000000 Binary files a/app/src/main/res/font/opensans_variablefont_wdth_wght.ttf and /dev/null differ