refactor(android): Batch 0 - centralize constants in DailyNotificationConstants

Create DailyNotificationConstants.kt to eliminate magic numbers and string
duplication across the codebase.

Centralized constants:
- PERMISSION_REQUEST_CODE (1001)
- DEFAULT_CHANNEL_ID, DEFAULT_CHANNEL_NAME, DEFAULT_CHANNEL_DESCRIPTION
- ACTION_NOTIFICATION, EXTRA_NOTIFICATION_ID
- PREFS_NAME (SharedPreferences file name)
- WorkManager tags, schedule IDs, notification IDs

Replaced duplicates in:
- DailyNotificationPlugin.kt (PERMISSION_REQUEST_CODE, PREFS_NAME)
- PermissionManager.java (PERMISSION_REQUEST_CODE)
- ChannelManager.java (all channel constants)
- DailyNotificationScheduler.java (ACTION_NOTIFICATION, EXTRA_NOTIFICATION_ID)

This is the foundation for the remaining refactoring batches.
All files compile and reference the centralized constants.

Refs: Cursor directive Batch 0
This commit is contained in:
Matthew Raymer
2025-12-23 12:26:32 +00:00
parent 735de3b09f
commit ac7550c77d
6 changed files with 189 additions and 43 deletions

View File

@@ -1,25 +1,22 @@
refactor(android): P2.1 Batch B - delegate validation methods to services refactor(android): Batch 0 - centralize constants in DailyNotificationConstants
Refactor plugin methods that validate input then delegate to services: Create DailyNotificationConstants.kt to eliminate magic numbers and string
- requestNotificationPermissions() → PermissionManager duplication across the codebase.
- openChannelSettings() → ChannelManager
- createSchedule/updateSchedule/deleteSchedule/enableSchedule() → ScheduleHelper
- scheduleUserNotification() → ScheduleHelper (database operations)
- registerCallback() → CallbackHelper
- injectInvalidTestData() → TestDataHelper
- requestExactAlarmPermission() → PermissionManager
- openExactAlarmSettings() → PermissionManager
- checkExactAlarmPermission() → PermissionManager
- cancelAllNotifications() → ScheduleHelper (database operations, partial)
- testAlarm() → DailyNotificationScheduler
Enhanced services: Centralized constants:
- PermissionManager: Added checkExactAlarmPermission() and requestExactAlarmPermission() - PERMISSION_REQUEST_CODE (1001)
- ChannelManager: Enhanced openChannelSettings() with channelId parameter and fallback logic - DEFAULT_CHANNEL_ID, DEFAULT_CHANNEL_NAME, DEFAULT_CHANNEL_DESCRIPTION
- ScheduleHelper: Added disableAllSchedulesByKind() method - ACTION_NOTIFICATION, EXTRA_NOTIFICATION_ID
- DailyNotificationScheduler: Added testAlarm() wrapper method - PREFS_NAME (SharedPreferences file name)
- WorkManager tags, schedule IDs, notification IDs
Reduces plugin class complexity by ~200 lines. Replaced duplicates in:
Services already exist - this is delegation, not extraction. - DailyNotificationPlugin.kt (PERMISSION_REQUEST_CODE, PREFS_NAME)
- PermissionManager.java (PERMISSION_REQUEST_CODE)
- ChannelManager.java (all channel constants)
- DailyNotificationScheduler.java (ACTION_NOTIFICATION, EXTRA_NOTIFICATION_ID)
Refs: docs/progress/P2.1-BATCH-B-STATE.md This is the foundation for the remaining refactoring batches.
All files compile and reference the centralized constants.
Refs: Cursor directive Batch 0

View File

@@ -21,9 +21,8 @@ import android.util.Log;
*/ */
public class ChannelManager { public class ChannelManager {
private static final String TAG = "ChannelManager"; private static final String TAG = "ChannelManager";
private static final String DEFAULT_CHANNEL_ID = "timesafari.daily"; // Channel constants moved to DailyNotificationConstants
private static final String DEFAULT_CHANNEL_NAME = "Daily Notifications"; // Use DailyNotificationConstants.DEFAULT_CHANNEL_ID, etc.
private static final String DEFAULT_CHANNEL_DESCRIPTION = "Daily notifications from TimeSafari";
private final Context context; private final Context context;
private final NotificationManager notificationManager; private final NotificationManager notificationManager;
@@ -44,7 +43,7 @@ public class ChannelManager {
Log.d(TAG, "Ensuring notification channel exists"); Log.d(TAG, "Ensuring notification channel exists");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = notificationManager.getNotificationChannel(DEFAULT_CHANNEL_ID); NotificationChannel channel = notificationManager.getNotificationChannel(com.timesafari.dailynotification.DailyNotificationConstants.DEFAULT_CHANNEL_ID);
if (channel == null) { if (channel == null) {
Log.d(TAG, "Creating notification channel"); Log.d(TAG, "Creating notification channel");
@@ -73,7 +72,7 @@ public class ChannelManager {
public boolean isChannelEnabled() { public boolean isChannelEnabled() {
try { try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = notificationManager.getNotificationChannel(DEFAULT_CHANNEL_ID); NotificationChannel channel = notificationManager.getNotificationChannel(com.timesafari.dailynotification.DailyNotificationConstants.DEFAULT_CHANNEL_ID);
if (channel == null) { if (channel == null) {
Log.w(TAG, "Channel does not exist"); Log.w(TAG, "Channel does not exist");
return false; return false;
@@ -100,7 +99,7 @@ public class ChannelManager {
public int getChannelImportance() { public int getChannelImportance() {
try { try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = notificationManager.getNotificationChannel(DEFAULT_CHANNEL_ID); NotificationChannel channel = notificationManager.getNotificationChannel(com.timesafari.dailynotification.DailyNotificationConstants.DEFAULT_CHANNEL_ID);
if (channel != null) { if (channel != null) {
return channel.getImportance(); return channel.getImportance();
} }
@@ -118,7 +117,7 @@ public class ChannelManager {
* @return true if settings intent was launched, false otherwise * @return true if settings intent was launched, false otherwise
*/ */
public boolean openChannelSettings() { public boolean openChannelSettings() {
return openChannelSettings(DEFAULT_CHANNEL_ID); return openChannelSettings(com.timesafari.dailynotification.DailyNotificationConstants.DEFAULT_CHANNEL_ID);
} }
/** /**
@@ -143,7 +142,7 @@ public class ChannelManager {
try { try {
Intent intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS) Intent intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS)
.putExtra(Settings.EXTRA_APP_PACKAGE, context.getPackageName()) .putExtra(Settings.EXTRA_APP_PACKAGE, context.getPackageName())
.putExtra(Settings.EXTRA_CHANNEL_ID, channelId != null ? channelId : DEFAULT_CHANNEL_ID) .putExtra(Settings.EXTRA_CHANNEL_ID, channelId != null ? channelId : com.timesafari.dailynotification.DailyNotificationConstants.DEFAULT_CHANNEL_ID)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent); context.startActivity(intent);
@@ -181,11 +180,11 @@ public class ChannelManager {
private void createDefaultChannel() { private void createDefaultChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel( NotificationChannel channel = new NotificationChannel(
DEFAULT_CHANNEL_ID, com.timesafari.dailynotification.DailyNotificationConstants.DEFAULT_CHANNEL_ID,
DEFAULT_CHANNEL_NAME, com.timesafari.dailynotification.DailyNotificationConstants.DEFAULT_CHANNEL_NAME,
NotificationManager.IMPORTANCE_HIGH NotificationManager.IMPORTANCE_HIGH
); );
channel.setDescription(DEFAULT_CHANNEL_DESCRIPTION); channel.setDescription(com.timesafari.dailynotification.DailyNotificationConstants.DEFAULT_CHANNEL_DESCRIPTION);
channel.enableLights(true); channel.enableLights(true);
channel.enableVibration(true); channel.enableVibration(true);
channel.setShowBadge(true); channel.setShowBadge(true);
@@ -201,7 +200,7 @@ public class ChannelManager {
* @return the default channel ID * @return the default channel ID
*/ */
public String getDefaultChannelId() { public String getDefaultChannelId() {
return DEFAULT_CHANNEL_ID; return com.timesafari.dailynotification.DailyNotificationConstants.DEFAULT_CHANNEL_ID;
} }
/** /**
@@ -210,7 +209,7 @@ public class ChannelManager {
public void logChannelStatus() { public void logChannelStatus() {
try { try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = notificationManager.getNotificationChannel(DEFAULT_CHANNEL_ID); NotificationChannel channel = notificationManager.getNotificationChannel(com.timesafari.dailynotification.DailyNotificationConstants.DEFAULT_CHANNEL_ID);
if (channel != null) { if (channel != null) {
Log.i(TAG, "Channel Status - ID: " + channel.getId() + Log.i(TAG, "Channel Status - ID: " + channel.getId() +
", Importance: " + channel.getImportance() + ", Importance: " + channel.getImportance() +

View File

@@ -0,0 +1,153 @@
/**
* DailyNotificationConstants.kt
*
* Centralized constants for Daily Notification Plugin
* Eliminates magic numbers and string duplication across the codebase
*
* @author Matthew Raymer
* @version 1.0.0
*/
package com.timesafari.dailynotification
/**
* Centralized constants for Daily Notification Plugin
*
* All request codes, channel IDs, action strings, and extra keys
* should be defined here and imported where needed.
*/
object DailyNotificationConstants {
// ============================================================
// Permission Request Codes
// ============================================================
/**
* Request code for notification permission requests
* Used by ActivityCompat.requestPermissions()
*/
const val PERMISSION_REQUEST_CODE = 1001
// ============================================================
// Notification Channel Constants
// ============================================================
/**
* Default notification channel ID
* Must match across ChannelManager and NotifyReceiver
*/
const val DEFAULT_CHANNEL_ID = "timesafari.daily"
/**
* Default notification channel name (user-visible)
*/
const val DEFAULT_CHANNEL_NAME = "Daily Notifications"
/**
* Default notification channel description
*/
const val DEFAULT_CHANNEL_DESCRIPTION = "Daily notifications from TimeSafari"
// ============================================================
// Intent Actions
// ============================================================
/**
* Action string for notification broadcast intents
* Used by AlarmManager PendingIntents
*/
const val ACTION_NOTIFICATION = "com.timesafari.daily.NOTIFICATION"
// ============================================================
// Intent Extras Keys
// ============================================================
/**
* Extra key for notification ID in broadcast intents
*/
const val EXTRA_NOTIFICATION_ID = "notification_id"
/**
* Extra key for schedule ID in broadcast intents
*/
const val EXTRA_SCHEDULE_ID = "schedule_id"
// ============================================================
// Notification IDs
// ============================================================
/**
* Default notification ID for daily notifications
* Used by NotificationManager.notify()
*/
const val DEFAULT_NOTIFICATION_ID = 1001
// ============================================================
// SharedPreferences Keys
// ============================================================
/**
* SharedPreferences file name for plugin storage
*/
const val PREFS_NAME = "daily_notification_timesafari"
/**
* SharedPreferences key for starred plan IDs
*/
const val PREFS_KEY_STARRED_PLAN_IDS = "starredPlanIds"
// ============================================================
// WorkManager Tags
// ============================================================
/**
* WorkManager tag for prefetch jobs
*/
const val WORK_TAG_PREFETCH = "prefetch"
/**
* WorkManager tag for fetch jobs
*/
const val WORK_TAG_FETCH = "daily_notification_fetch"
/**
* WorkManager tag for maintenance jobs
*/
const val WORK_TAG_MAINTENANCE = "daily_notification_maintenance"
/**
* WorkManager tag for soft refetch jobs
*/
const val WORK_TAG_SOFT_REFETCH = "soft_refetch"
/**
* WorkManager tag for display jobs
*/
const val WORK_TAG_DISPLAY = "daily_notification_display"
/**
* WorkManager tag for dismiss jobs
*/
const val WORK_TAG_DISMISS = "daily_notification_dismiss"
// ============================================================
// Schedule IDs
// ============================================================
/**
* Default schedule ID for daily notifications
* Used when user doesn't provide a custom ID
*/
const val DEFAULT_SCHEDULE_ID = "daily_notification"
// ============================================================
// Request Code Versioning
// ============================================================
/**
* Version for request code derivation algorithm
* Increment if request code generation logic changes
*/
const val REQUEST_CODE_VERSION = 1
}

View File

@@ -86,8 +86,6 @@ open class DailyNotificationPlugin : Plugin() {
private var db: DailyNotificationDatabase? = null private var db: DailyNotificationDatabase? = null
private val PERMISSION_REQUEST_CODE = 1001
// Service instances for delegation // Service instances for delegation
private var statusChecker: NotificationStatusChecker? = null private var statusChecker: NotificationStatusChecker? = null
private var permissionManager: PermissionManager? = null private var permissionManager: PermissionManager? = null
@@ -345,7 +343,7 @@ open class DailyNotificationPlugin : Plugin() {
Log.i(TAG, "Updating starred plans: count=${planIds.size}") Log.i(TAG, "Updating starred plans: count=${planIds.size}")
// Store in SharedPreferences (matching TestNativeFetcher expectations) // Store in SharedPreferences (matching TestNativeFetcher expectations)
val prefsName = "daily_notification_timesafari" val prefsName = DailyNotificationConstants.PREFS_NAME
val keyStarredPlanIds = "starredPlanIds" val keyStarredPlanIds = "starredPlanIds"
val prefs: SharedPreferences = context.getSharedPreferences(prefsName, Context.MODE_PRIVATE) val prefs: SharedPreferences = context.getSharedPreferences(prefsName, Context.MODE_PRIVATE)
@@ -422,7 +420,7 @@ open class DailyNotificationPlugin : Plugin() {
) { ) {
Log.i(TAG, "handleRequestPermissionsResult called: requestCode=$requestCode, permissions=${permissions.contentToString()}") Log.i(TAG, "handleRequestPermissionsResult called: requestCode=$requestCode, permissions=${permissions.contentToString()}")
if (requestCode == PERMISSION_REQUEST_CODE) { if (requestCode == DailyNotificationConstants.PERMISSION_REQUEST_CODE) {
// Retrieve the saved call // Retrieve the saved call
val call = savedCall val call = savedCall
if (call != null) { if (call != null) {

View File

@@ -29,8 +29,7 @@ import java.util.concurrent.ConcurrentHashMap;
public class DailyNotificationScheduler { public class DailyNotificationScheduler {
private static final String TAG = "DailyNotificationScheduler"; private static final String TAG = "DailyNotificationScheduler";
private static final String ACTION_NOTIFICATION = "com.timesafari.daily.NOTIFICATION"; // Intent action and extras moved to DailyNotificationConstants
private static final String EXTRA_NOTIFICATION_ID = "notification_id";
private final Context context; private final Context context;
private final AlarmManager alarmManager; private final AlarmManager alarmManager;
@@ -157,8 +156,8 @@ public class DailyNotificationScheduler {
// Create intent for the notification // Create intent for the notification
Intent intent = new Intent(context, DailyNotificationReceiver.class); Intent intent = new Intent(context, DailyNotificationReceiver.class);
intent.setAction(ACTION_NOTIFICATION); intent.setAction(com.timesafari.dailynotification.DailyNotificationConstants.ACTION_NOTIFICATION);
intent.putExtra(EXTRA_NOTIFICATION_ID, content.getId()); intent.putExtra(com.timesafari.dailynotification.DailyNotificationConstants.EXTRA_NOTIFICATION_ID, content.getId());
// Check if this is a static reminder // Check if this is a static reminder
if (content.getId().startsWith("reminder_") || content.getId().contains("_reminder")) { if (content.getId().startsWith("reminder_") || content.getId().contains("_reminder")) {

View File

@@ -86,7 +86,7 @@ public class PermissionManager {
androidx.core.app.ActivityCompat.requestPermissions( androidx.core.app.ActivityCompat.requestPermissions(
activity, activity,
new String[]{android.Manifest.permission.POST_NOTIFICATIONS}, new String[]{android.Manifest.permission.POST_NOTIFICATIONS},
1001 // PERMISSION_REQUEST_CODE - matches DailyNotificationPlugin.PERMISSION_REQUEST_CODE com.timesafari.dailynotification.DailyNotificationConstants.PERMISSION_REQUEST_CODE // Centralized constant
); );
Log.d(TAG, "Permission dialog shown, waiting for user response"); Log.d(TAG, "Permission dialog shown, waiting for user response");