mirror of
https://github.com/iSoron/uhabits.git
synced 2025-12-14 21:18:51 -06:00
Implement notifications for both habit groups and sub habits
This commit is contained in:
@@ -181,6 +181,16 @@ class ListHabitsActivity : AppCompatActivity(), Preferences.Listener {
|
|||||||
component.listHabitsBehavior.onEdit(habit, Timestamp(timestamp))
|
component.listHabitsBehavior.onEdit(habit, Timestamp(timestamp))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
intent.getLongExtra("CLEAR_NOTIFICATION_HABIT_ID", -1).takeIf { it != -1L }?.let { id ->
|
||||||
|
val dismissHabit = appComponent.habitList.getById(id) ?: appComponent.habitGroupList.getHabitByID(id)
|
||||||
|
if (dismissHabit != null) {
|
||||||
|
appComponent.reminderController.onDismiss(dismissHabit)
|
||||||
|
} else {
|
||||||
|
val dismissHabitGroup = appComponent.habitGroupList.getById(id)!!
|
||||||
|
appComponent.reminderController.onDismiss(dismissHabitGroup)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
intent = null
|
intent = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -63,9 +63,10 @@ class HabitsModule(dbFile: File) {
|
|||||||
sys: IntentScheduler,
|
sys: IntentScheduler,
|
||||||
commandRunner: CommandRunner,
|
commandRunner: CommandRunner,
|
||||||
habitList: HabitList,
|
habitList: HabitList,
|
||||||
|
habitGroupList: HabitGroupList,
|
||||||
widgetPreferences: WidgetPreferences
|
widgetPreferences: WidgetPreferences
|
||||||
): ReminderScheduler {
|
): ReminderScheduler {
|
||||||
return ReminderScheduler(commandRunner, habitList, sys, widgetPreferences)
|
return ReminderScheduler(commandRunner, habitList, habitGroupList, sys, widgetPreferences)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import android.os.Build
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import org.isoron.uhabits.core.AppScope
|
import org.isoron.uhabits.core.AppScope
|
||||||
import org.isoron.uhabits.core.models.Habit
|
import org.isoron.uhabits.core.models.Habit
|
||||||
|
import org.isoron.uhabits.core.models.HabitGroup
|
||||||
import org.isoron.uhabits.core.reminders.ReminderScheduler.SchedulerResult
|
import org.isoron.uhabits.core.reminders.ReminderScheduler.SchedulerResult
|
||||||
import org.isoron.uhabits.core.reminders.ReminderScheduler.SystemScheduler
|
import org.isoron.uhabits.core.reminders.ReminderScheduler.SystemScheduler
|
||||||
import org.isoron.uhabits.core.utils.DateFormats
|
import org.isoron.uhabits.core.utils.DateFormats
|
||||||
@@ -75,6 +76,16 @@ class IntentScheduler
|
|||||||
return schedule(reminderTime, intent, RTC_WAKEUP)
|
return schedule(reminderTime, intent, RTC_WAKEUP)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun scheduleShowReminder(
|
||||||
|
reminderTime: Long,
|
||||||
|
habitGroup: HabitGroup,
|
||||||
|
timestamp: Long
|
||||||
|
): SchedulerResult {
|
||||||
|
val intent = pendingIntents.showReminder(habitGroup, reminderTime, timestamp)
|
||||||
|
logReminderScheduled(habitGroup, reminderTime)
|
||||||
|
return schedule(reminderTime, intent, RTC_WAKEUP)
|
||||||
|
}
|
||||||
|
|
||||||
override fun scheduleWidgetUpdate(updateTime: Long): SchedulerResult {
|
override fun scheduleWidgetUpdate(updateTime: Long): SchedulerResult {
|
||||||
val intent = pendingIntents.updateWidgets()
|
val intent = pendingIntents.updateWidgets()
|
||||||
return schedule(updateTime, intent, RTC)
|
return schedule(updateTime, intent, RTC)
|
||||||
@@ -94,4 +105,15 @@ class IntentScheduler
|
|||||||
String.format("Setting alarm (%s): %s", time, name)
|
String.format("Setting alarm (%s): %s", time, name)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun logReminderScheduled(habitGroup: HabitGroup, reminderTime: Long) {
|
||||||
|
val min = min(5, habitGroup.name.length)
|
||||||
|
val name = habitGroup.name.substring(0, min)
|
||||||
|
val df = DateFormats.getBackupDateFormat()
|
||||||
|
val time = df.format(Date(reminderTime))
|
||||||
|
Log.i(
|
||||||
|
"ReminderHelper",
|
||||||
|
String.format("Setting alarm (%s): %s", time, name)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import org.isoron.uhabits.activities.habits.list.ListHabitsActivity
|
|||||||
import org.isoron.uhabits.activities.habits.show.ShowHabitActivity
|
import org.isoron.uhabits.activities.habits.show.ShowHabitActivity
|
||||||
import org.isoron.uhabits.core.AppScope
|
import org.isoron.uhabits.core.AppScope
|
||||||
import org.isoron.uhabits.core.models.Habit
|
import org.isoron.uhabits.core.models.Habit
|
||||||
|
import org.isoron.uhabits.core.models.HabitGroup
|
||||||
import org.isoron.uhabits.core.models.Timestamp
|
import org.isoron.uhabits.core.models.Timestamp
|
||||||
import org.isoron.uhabits.inject.AppContext
|
import org.isoron.uhabits.inject.AppContext
|
||||||
import org.isoron.uhabits.receivers.ReminderReceiver
|
import org.isoron.uhabits.receivers.ReminderReceiver
|
||||||
@@ -69,6 +70,17 @@ class PendingIntentFactory
|
|||||||
FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT
|
FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fun dismissNotification(habitGroup: HabitGroup): PendingIntent =
|
||||||
|
getBroadcast(
|
||||||
|
context,
|
||||||
|
0,
|
||||||
|
Intent(context, ReminderReceiver::class.java).apply {
|
||||||
|
action = WidgetReceiver.ACTION_DISMISS_REMINDER
|
||||||
|
data = Uri.parse(habitGroup.uriString)
|
||||||
|
},
|
||||||
|
FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT
|
||||||
|
)
|
||||||
|
|
||||||
fun removeRepetition(habit: Habit, timestamp: Timestamp?): PendingIntent =
|
fun removeRepetition(habit: Habit, timestamp: Timestamp?): PendingIntent =
|
||||||
getBroadcast(
|
getBroadcast(
|
||||||
context,
|
context,
|
||||||
@@ -92,6 +104,17 @@ class PendingIntentFactory
|
|||||||
)
|
)
|
||||||
.getPendingIntent(0, FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT)!!
|
.getPendingIntent(0, FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT)!!
|
||||||
|
|
||||||
|
fun showHabitGroup(habitGroup: HabitGroup): PendingIntent =
|
||||||
|
androidx.core.app.TaskStackBuilder
|
||||||
|
.create(context)
|
||||||
|
.addNextIntentWithParentStack(
|
||||||
|
intentFactory.startShowHabitGroupActivity(
|
||||||
|
context,
|
||||||
|
habitGroup
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.getPendingIntent(0, FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT)!!
|
||||||
|
|
||||||
fun showHabitTemplate(): PendingIntent {
|
fun showHabitTemplate(): PendingIntent {
|
||||||
return getActivity(
|
return getActivity(
|
||||||
context,
|
context,
|
||||||
@@ -123,6 +146,23 @@ class PendingIntentFactory
|
|||||||
FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT
|
FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fun showReminder(
|
||||||
|
habitGroup: HabitGroup,
|
||||||
|
reminderTime: Long?,
|
||||||
|
timestamp: Long
|
||||||
|
): PendingIntent =
|
||||||
|
getBroadcast(
|
||||||
|
context,
|
||||||
|
(habitGroup.id!! % Integer.MAX_VALUE).toInt() + 1,
|
||||||
|
Intent(context, ReminderReceiver::class.java).apply {
|
||||||
|
action = ReminderReceiver.ACTION_SHOW_REMINDER
|
||||||
|
data = Uri.parse(habitGroup.uriString)
|
||||||
|
putExtra("timestamp", timestamp)
|
||||||
|
putExtra("reminderTime", reminderTime)
|
||||||
|
},
|
||||||
|
FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT
|
||||||
|
)
|
||||||
|
|
||||||
fun snoozeNotification(habit: Habit): PendingIntent =
|
fun snoozeNotification(habit: Habit): PendingIntent =
|
||||||
getBroadcast(
|
getBroadcast(
|
||||||
context,
|
context,
|
||||||
@@ -134,6 +174,17 @@ class PendingIntentFactory
|
|||||||
FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT
|
FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fun snoozeNotification(habitGroup: HabitGroup): PendingIntent =
|
||||||
|
getBroadcast(
|
||||||
|
context,
|
||||||
|
0,
|
||||||
|
Intent(context, ReminderReceiver::class.java).apply {
|
||||||
|
data = Uri.parse(habitGroup.uriString)
|
||||||
|
action = ReminderReceiver.ACTION_SNOOZE_REMINDER
|
||||||
|
},
|
||||||
|
FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT
|
||||||
|
)
|
||||||
|
|
||||||
fun toggleCheckmark(habit: Habit, timestamp: Long?): PendingIntent =
|
fun toggleCheckmark(habit: Habit, timestamp: Long?): PendingIntent =
|
||||||
getBroadcast(
|
getBroadcast(
|
||||||
context,
|
context,
|
||||||
@@ -185,6 +236,26 @@ class PendingIntentFactory
|
|||||||
putExtra("timestamp", timestamp.unixTime)
|
putExtra("timestamp", timestamp.unixTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun showHabitList(): PendingIntent {
|
||||||
|
return getActivity(
|
||||||
|
context,
|
||||||
|
1,
|
||||||
|
Intent(context, ListHabitsActivity::class.java),
|
||||||
|
getIntentTemplateFlags()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun showHabitListWithNotificationClear(id: Long): PendingIntent {
|
||||||
|
return getActivity(
|
||||||
|
context,
|
||||||
|
0,
|
||||||
|
Intent(context, ListHabitsActivity::class.java).apply {
|
||||||
|
putExtra("CLEAR_NOTIFICATION_HABIT_ID", id)
|
||||||
|
},
|
||||||
|
getIntentTemplateFlags()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private fun getIntentTemplateFlags(): Int {
|
private fun getIntentTemplateFlags(): Int {
|
||||||
var flags = 0
|
var flags = 0
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import androidx.core.app.NotificationManagerCompat
|
|||||||
import org.isoron.uhabits.R
|
import org.isoron.uhabits.R
|
||||||
import org.isoron.uhabits.core.AppScope
|
import org.isoron.uhabits.core.AppScope
|
||||||
import org.isoron.uhabits.core.models.Habit
|
import org.isoron.uhabits.core.models.Habit
|
||||||
|
import org.isoron.uhabits.core.models.HabitGroup
|
||||||
import org.isoron.uhabits.core.models.Timestamp
|
import org.isoron.uhabits.core.models.Timestamp
|
||||||
import org.isoron.uhabits.core.preferences.Preferences
|
import org.isoron.uhabits.core.preferences.Preferences
|
||||||
import org.isoron.uhabits.core.ui.NotificationTray
|
import org.isoron.uhabits.core.ui.NotificationTray
|
||||||
@@ -90,6 +91,34 @@ class AndroidNotificationTray
|
|||||||
active.add(notificationId)
|
active.add(notificationId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun showNotification(
|
||||||
|
habitGroup: HabitGroup,
|
||||||
|
notificationId: Int,
|
||||||
|
timestamp: Timestamp,
|
||||||
|
reminderTime: Long
|
||||||
|
) {
|
||||||
|
val notificationManager = NotificationManagerCompat.from(context)
|
||||||
|
val notification = buildNotification(habitGroup, reminderTime, timestamp)
|
||||||
|
createAndroidNotificationChannel(context)
|
||||||
|
try {
|
||||||
|
notificationManager.notify(notificationId, notification)
|
||||||
|
} catch (e: RuntimeException) {
|
||||||
|
// Some Xiaomi phones produce a RuntimeException if custom notification sounds are used.
|
||||||
|
Log.i(
|
||||||
|
"AndroidNotificationTray",
|
||||||
|
"Failed to show notification. Retrying without sound."
|
||||||
|
)
|
||||||
|
val n = buildNotification(
|
||||||
|
habitGroup,
|
||||||
|
reminderTime,
|
||||||
|
timestamp,
|
||||||
|
disableSound = true
|
||||||
|
)
|
||||||
|
notificationManager.notify(notificationId, n)
|
||||||
|
}
|
||||||
|
active.add(notificationId)
|
||||||
|
}
|
||||||
|
|
||||||
fun buildNotification(
|
fun buildNotification(
|
||||||
habit: Habit,
|
habit: Habit,
|
||||||
reminderTime: Long,
|
reminderTime: Long,
|
||||||
@@ -163,6 +192,58 @@ class AndroidNotificationTray
|
|||||||
return builder.build()
|
return builder.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun buildNotification(
|
||||||
|
habitGroup: HabitGroup,
|
||||||
|
reminderTime: Long,
|
||||||
|
timestamp: Timestamp,
|
||||||
|
disableSound: Boolean = false
|
||||||
|
): Notification {
|
||||||
|
val enterAction = Action(
|
||||||
|
R.drawable.ic_action_check,
|
||||||
|
context.getString(R.string.enter),
|
||||||
|
pendingIntents.showHabitListWithNotificationClear(habitGroup.id!!)
|
||||||
|
)
|
||||||
|
|
||||||
|
val wearableBg = decodeResource(context.resources, R.drawable.stripe)
|
||||||
|
|
||||||
|
// Even though the set of actions is the same on the phone and
|
||||||
|
// on the watch, Pebble requires us to add them to the
|
||||||
|
// WearableExtender.
|
||||||
|
val wearableExtender = WearableExtender().setBackground(wearableBg)
|
||||||
|
|
||||||
|
val defaultText = context.getString(R.string.default_reminder_question)
|
||||||
|
val builder = Builder(context, REMINDERS_CHANNEL_ID)
|
||||||
|
.setSmallIcon(R.drawable.ic_notification)
|
||||||
|
.setContentTitle(habitGroup.name)
|
||||||
|
.setContentText(if (habitGroup.question.isBlank()) defaultText else habitGroup.question)
|
||||||
|
.setContentIntent(pendingIntents.showHabitGroup(habitGroup))
|
||||||
|
.setDeleteIntent(pendingIntents.dismissNotification(habitGroup))
|
||||||
|
.setSound(null)
|
||||||
|
.setWhen(reminderTime)
|
||||||
|
.setShowWhen(true)
|
||||||
|
.setOngoing(preferences.shouldMakeNotificationsSticky())
|
||||||
|
|
||||||
|
wearableExtender.addAction(enterAction)
|
||||||
|
builder.addAction(enterAction)
|
||||||
|
|
||||||
|
if (!disableSound) {
|
||||||
|
builder.setSound(ringtoneManager.getURI())
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SDK_INT < Build.VERSION_CODES.S) {
|
||||||
|
val snoozeAction = Action(
|
||||||
|
R.drawable.ic_action_snooze,
|
||||||
|
context.getString(R.string.snooze),
|
||||||
|
pendingIntents.snoozeNotification(habitGroup)
|
||||||
|
)
|
||||||
|
wearableExtender.addAction(snoozeAction)
|
||||||
|
builder.addAction(snoozeAction)
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.extend(wearableExtender)
|
||||||
|
return builder.build()
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val REMINDERS_CHANNEL_ID = "REMINDERS"
|
private const val REMINDERS_CHANNEL_ID = "REMINDERS"
|
||||||
fun createAndroidNotificationChannel(context: Context) {
|
fun createAndroidNotificationChannel(context: Context) {
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import org.isoron.uhabits.HabitsApplication
|
|||||||
import org.isoron.uhabits.R
|
import org.isoron.uhabits.R
|
||||||
import org.isoron.uhabits.activities.AndroidThemeSwitcher
|
import org.isoron.uhabits.activities.AndroidThemeSwitcher
|
||||||
import org.isoron.uhabits.core.models.Habit
|
import org.isoron.uhabits.core.models.Habit
|
||||||
|
import org.isoron.uhabits.core.models.HabitGroup
|
||||||
import org.isoron.uhabits.core.ui.views.DarkTheme
|
import org.isoron.uhabits.core.ui.views.DarkTheme
|
||||||
import org.isoron.uhabits.core.ui.views.LightTheme
|
import org.isoron.uhabits.core.ui.views.LightTheme
|
||||||
import org.isoron.uhabits.receivers.ReminderController
|
import org.isoron.uhabits.receivers.ReminderController
|
||||||
@@ -41,6 +42,7 @@ import java.util.Calendar
|
|||||||
|
|
||||||
class SnoozeDelayPickerActivity : FragmentActivity(), OnItemClickListener {
|
class SnoozeDelayPickerActivity : FragmentActivity(), OnItemClickListener {
|
||||||
private var habit: Habit? = null
|
private var habit: Habit? = null
|
||||||
|
private var habitGroup: HabitGroup? = null
|
||||||
private var reminderController: ReminderController? = null
|
private var reminderController: ReminderController? = null
|
||||||
private var dialog: AlertDialog? = null
|
private var dialog: AlertDialog? = null
|
||||||
private var androidColor: Int = 0
|
private var androidColor: Int = 0
|
||||||
@@ -58,10 +60,13 @@ class SnoozeDelayPickerActivity : FragmentActivity(), OnItemClickListener {
|
|||||||
if (data == null) {
|
if (data == null) {
|
||||||
finish()
|
finish()
|
||||||
} else {
|
} else {
|
||||||
habit = appComponent.habitList.getById(ContentUris.parseId(data))
|
val id = ContentUris.parseId(data)
|
||||||
|
habit = appComponent.habitList.getById(id) ?: appComponent.habitGroupList.getHabitByID(id)
|
||||||
|
habitGroup = appComponent.habitGroupList.getById(id)
|
||||||
}
|
}
|
||||||
if (habit == null) finish()
|
if (habit == null && habitGroup == null) finish()
|
||||||
androidColor = themeSwitcher.currentTheme.color(habit!!.color).toInt()
|
val color = habit?.color ?: habitGroup!!.color
|
||||||
|
androidColor = themeSwitcher.currentTheme.color(color).toInt()
|
||||||
reminderController = appComponent.reminderController
|
reminderController = appComponent.reminderController
|
||||||
dialog = AlertDialog.Builder(this)
|
dialog = AlertDialog.Builder(this)
|
||||||
.setTitle(R.string.select_snooze_delay)
|
.setTitle(R.string.select_snooze_delay)
|
||||||
@@ -87,7 +92,11 @@ class SnoozeDelayPickerActivity : FragmentActivity(), OnItemClickListener {
|
|||||||
val calendar = Calendar.getInstance()
|
val calendar = Calendar.getInstance()
|
||||||
val dialog = TimePickerDialog.newInstance(
|
val dialog = TimePickerDialog.newInstance(
|
||||||
{ view: RadialPickerLayout?, hour: Int, minute: Int ->
|
{ view: RadialPickerLayout?, hour: Int, minute: Int ->
|
||||||
reminderController!!.onSnoozeTimePicked(habit, hour, minute)
|
if (habit != null) {
|
||||||
|
reminderController!!.onSnoozeTimePicked(habit, hour, minute)
|
||||||
|
} else {
|
||||||
|
reminderController!!.onSnoozeTimePicked(habitGroup, hour, minute)
|
||||||
|
}
|
||||||
finish()
|
finish()
|
||||||
},
|
},
|
||||||
calendar[Calendar.HOUR_OF_DAY],
|
calendar[Calendar.HOUR_OF_DAY],
|
||||||
@@ -101,7 +110,11 @@ class SnoozeDelayPickerActivity : FragmentActivity(), OnItemClickListener {
|
|||||||
override fun onItemClick(parent: AdapterView<*>?, view: View, position: Int, id: Long) {
|
override fun onItemClick(parent: AdapterView<*>?, view: View, position: Int, id: Long) {
|
||||||
val snoozeValues = resources.getIntArray(R.array.snooze_picker_values)
|
val snoozeValues = resources.getIntArray(R.array.snooze_picker_values)
|
||||||
if (snoozeValues[position] >= 0) {
|
if (snoozeValues[position] >= 0) {
|
||||||
reminderController!!.onSnoozeDelayPicked(habit!!, snoozeValues[position])
|
if (habit != null) {
|
||||||
|
reminderController!!.onSnoozeDelayPicked(habit!!, snoozeValues[position])
|
||||||
|
} else {
|
||||||
|
reminderController!!.onSnoozeDelayPicked(habitGroup!!, snoozeValues[position])
|
||||||
|
}
|
||||||
finish()
|
finish()
|
||||||
} else {
|
} else {
|
||||||
showTimePicker()
|
showTimePicker()
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import android.content.Intent
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import org.isoron.uhabits.core.AppScope
|
import org.isoron.uhabits.core.AppScope
|
||||||
import org.isoron.uhabits.core.models.Habit
|
import org.isoron.uhabits.core.models.Habit
|
||||||
|
import org.isoron.uhabits.core.models.HabitGroup
|
||||||
import org.isoron.uhabits.core.models.Timestamp
|
import org.isoron.uhabits.core.models.Timestamp
|
||||||
import org.isoron.uhabits.core.preferences.Preferences
|
import org.isoron.uhabits.core.preferences.Preferences
|
||||||
import org.isoron.uhabits.core.reminders.ReminderScheduler
|
import org.isoron.uhabits.core.reminders.ReminderScheduler
|
||||||
@@ -50,21 +51,45 @@ class ReminderController @Inject constructor(
|
|||||||
reminderScheduler.scheduleAll()
|
reminderScheduler.scheduleAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onShowReminder(
|
||||||
|
habitGroup: HabitGroup,
|
||||||
|
timestamp: Timestamp,
|
||||||
|
reminderTime: Long
|
||||||
|
) {
|
||||||
|
notificationTray.show(habitGroup, timestamp, reminderTime)
|
||||||
|
reminderScheduler.scheduleAll()
|
||||||
|
}
|
||||||
|
|
||||||
fun onSnoozePressed(habit: Habit, context: Context) {
|
fun onSnoozePressed(habit: Habit, context: Context) {
|
||||||
showSnoozeDelayPicker(habit, context)
|
showSnoozeDelayPicker(habit, context)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onSnoozePressed(habitGroup: HabitGroup, context: Context) {
|
||||||
|
showSnoozeDelayPicker(habitGroup, context)
|
||||||
|
}
|
||||||
|
|
||||||
fun onSnoozeDelayPicked(habit: Habit, delayInMinutes: Int) {
|
fun onSnoozeDelayPicked(habit: Habit, delayInMinutes: Int) {
|
||||||
reminderScheduler.snoozeReminder(habit, delayInMinutes.toLong())
|
reminderScheduler.snoozeReminder(habit, delayInMinutes.toLong())
|
||||||
notificationTray.cancel(habit)
|
notificationTray.cancel(habit)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onSnoozeDelayPicked(habitGroup: HabitGroup, delayInMinutes: Int) {
|
||||||
|
reminderScheduler.snoozeReminder(habitGroup, delayInMinutes.toLong())
|
||||||
|
notificationTray.cancel(habitGroup)
|
||||||
|
}
|
||||||
|
|
||||||
fun onSnoozeTimePicked(habit: Habit?, hour: Int, minute: Int) {
|
fun onSnoozeTimePicked(habit: Habit?, hour: Int, minute: Int) {
|
||||||
val time: Long = getUpcomingTimeInMillis(hour, minute)
|
val time: Long = getUpcomingTimeInMillis(hour, minute)
|
||||||
reminderScheduler.scheduleAtTime(habit!!, time)
|
reminderScheduler.scheduleAtTime(habit!!, time)
|
||||||
notificationTray.cancel(habit)
|
notificationTray.cancel(habit)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onSnoozeTimePicked(habitGroup: HabitGroup?, hour: Int, minute: Int) {
|
||||||
|
val time: Long = getUpcomingTimeInMillis(hour, minute)
|
||||||
|
reminderScheduler.scheduleAtTime(habitGroup!!, time)
|
||||||
|
notificationTray.cancel(habitGroup)
|
||||||
|
}
|
||||||
|
|
||||||
fun onDismiss(habit: Habit) {
|
fun onDismiss(habit: Habit) {
|
||||||
if (preferences.shouldMakeNotificationsSticky()) {
|
if (preferences.shouldMakeNotificationsSticky()) {
|
||||||
// This is a workaround to keep sticky notifications non-dismissible in Android 14+.
|
// This is a workaround to keep sticky notifications non-dismissible in Android 14+.
|
||||||
@@ -75,6 +100,10 @@ class ReminderController @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onDismiss(habitGroup: HabitGroup) {
|
||||||
|
notificationTray.cancel(habitGroup)
|
||||||
|
}
|
||||||
|
|
||||||
private fun showSnoozeDelayPicker(habit: Habit, context: Context) {
|
private fun showSnoozeDelayPicker(habit: Habit, context: Context) {
|
||||||
context.sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS))
|
context.sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS))
|
||||||
val intent = Intent(context, SnoozeDelayPickerActivity::class.java)
|
val intent = Intent(context, SnoozeDelayPickerActivity::class.java)
|
||||||
@@ -82,4 +111,12 @@ class ReminderController @Inject constructor(
|
|||||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
context.startActivity(intent)
|
context.startActivity(intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun showSnoozeDelayPicker(habitGroup: HabitGroup, context: Context) {
|
||||||
|
context.sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS))
|
||||||
|
val intent = Intent(context, SnoozeDelayPickerActivity::class.java)
|
||||||
|
intent.data = Uri.parse(habitGroup.uriString)
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
context.startActivity(intent)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import android.os.Build.VERSION.SDK_INT
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import org.isoron.uhabits.HabitsApplication
|
import org.isoron.uhabits.HabitsApplication
|
||||||
import org.isoron.uhabits.core.models.Habit
|
import org.isoron.uhabits.core.models.Habit
|
||||||
|
import org.isoron.uhabits.core.models.HabitGroup
|
||||||
import org.isoron.uhabits.core.models.Timestamp
|
import org.isoron.uhabits.core.models.Timestamp
|
||||||
import org.isoron.uhabits.core.utils.DateUtils.Companion.getStartOfTodayWithOffset
|
import org.isoron.uhabits.core.utils.DateUtils.Companion.getStartOfTodayWithOffset
|
||||||
|
|
||||||
@@ -44,52 +45,81 @@ class ReminderReceiver : BroadcastReceiver() {
|
|||||||
val app = context.applicationContext as HabitsApplication
|
val app = context.applicationContext as HabitsApplication
|
||||||
val appComponent = app.component
|
val appComponent = app.component
|
||||||
val habits = appComponent.habitList
|
val habits = appComponent.habitList
|
||||||
|
val habitGroups = appComponent.habitGroupList
|
||||||
val reminderController = appComponent.reminderController
|
val reminderController = appComponent.reminderController
|
||||||
Log.i(TAG, String.format("Received intent: %s", intent.toString()))
|
Log.i(TAG, String.format("Received intent: %s", intent.toString()))
|
||||||
var habit: Habit? = null
|
var habit: Habit? = null
|
||||||
|
var habitGroup: HabitGroup? = null
|
||||||
|
var id: Long? = null
|
||||||
|
var type: String? = null
|
||||||
val today: Long = getStartOfTodayWithOffset()
|
val today: Long = getStartOfTodayWithOffset()
|
||||||
val data = intent.data
|
val data = intent.data
|
||||||
if (data != null) habit = habits.getById(ContentUris.parseId(data))
|
if (data != null) {
|
||||||
|
type = data.pathSegments[0]
|
||||||
|
id = ContentUris.parseId(data)
|
||||||
|
when (type) {
|
||||||
|
"habit" -> habit = habits.getById(id) ?: habitGroups.getHabitByID(id)
|
||||||
|
"habitgroup" -> habitGroup = habitGroups.getById(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
val timestamp = intent.getLongExtra("timestamp", today)
|
val timestamp = intent.getLongExtra("timestamp", today)
|
||||||
val reminderTime = intent.getLongExtra("reminderTime", today)
|
val reminderTime = intent.getLongExtra("reminderTime", today)
|
||||||
try {
|
try {
|
||||||
when (intent.action) {
|
when (intent.action) {
|
||||||
ACTION_SHOW_REMINDER -> {
|
ACTION_SHOW_REMINDER -> {
|
||||||
if (habit == null) return
|
if (id == null) return
|
||||||
Log.d(
|
Log.d(
|
||||||
"ReminderReceiver",
|
"ReminderReceiver",
|
||||||
String.format(
|
String.format(
|
||||||
"onShowReminder habit=%d timestamp=%d reminderTime=%d",
|
"onShowReminder %s=%d timestamp=%d reminderTime=%d",
|
||||||
habit.id,
|
type,
|
||||||
|
id,
|
||||||
timestamp,
|
timestamp,
|
||||||
reminderTime
|
reminderTime
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
reminderController.onShowReminder(
|
if (habit != null) {
|
||||||
habit,
|
reminderController.onShowReminder(
|
||||||
Timestamp(timestamp),
|
habit,
|
||||||
reminderTime
|
Timestamp(timestamp),
|
||||||
)
|
reminderTime
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
reminderController.onShowReminder(
|
||||||
|
habitGroup!!,
|
||||||
|
Timestamp(timestamp),
|
||||||
|
reminderTime
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ACTION_DISMISS_REMINDER -> {
|
ACTION_DISMISS_REMINDER -> {
|
||||||
if (habit == null) return
|
if (id == null) return
|
||||||
Log.d("ReminderReceiver", String.format("onDismiss habit=%d", habit.id))
|
Log.d("ReminderReceiver", String.format("onDismiss %s=%d", type, id))
|
||||||
reminderController.onDismiss(habit)
|
if (habit != null) {
|
||||||
|
reminderController.onDismiss(habit)
|
||||||
|
} else {
|
||||||
|
reminderController.onDismiss(habitGroup!!)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ACTION_SNOOZE_REMINDER -> {
|
ACTION_SNOOZE_REMINDER -> {
|
||||||
if (habit == null) return
|
if (id == null) return
|
||||||
if (SDK_INT < Build.VERSION_CODES.S) {
|
if (SDK_INT < Build.VERSION_CODES.S) {
|
||||||
Log.d(
|
Log.d(
|
||||||
"ReminderReceiver",
|
"ReminderReceiver",
|
||||||
String.format("onSnoozePressed habit=%d", habit.id)
|
String.format("onSnoozePressed %s=%d", type, id)
|
||||||
)
|
)
|
||||||
reminderController.onSnoozePressed(habit, context)
|
if (habit != null) {
|
||||||
|
reminderController.onSnoozePressed(habit, context)
|
||||||
|
} else {
|
||||||
|
reminderController.onSnoozePressed(habitGroup!!, context)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Log.w(
|
Log.w(
|
||||||
"ReminderReceiver",
|
"ReminderReceiver",
|
||||||
String.format(
|
String.format(
|
||||||
"onSnoozePressed habit=%d, should be deactivated in recent versions.",
|
"onSnoozePressed %s=%d, should be deactivated in recent versions.",
|
||||||
habit.id
|
type,
|
||||||
|
id
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ data class HabitGroup(
|
|||||||
var observable = ModelObservable()
|
var observable = ModelObservable()
|
||||||
|
|
||||||
val uriString: String
|
val uriString: String
|
||||||
get() = "content://org.isoron.uhabits/habit/$id"
|
get() = "content://org.isoron.uhabits/habitgroup/$id"
|
||||||
|
|
||||||
fun hasReminder(): Boolean = reminder != null
|
fun hasReminder(): Boolean = reminder != null
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ import org.isoron.uhabits.core.commands.Command
|
|||||||
import org.isoron.uhabits.core.commands.CommandRunner
|
import org.isoron.uhabits.core.commands.CommandRunner
|
||||||
import org.isoron.uhabits.core.commands.CreateRepetitionCommand
|
import org.isoron.uhabits.core.commands.CreateRepetitionCommand
|
||||||
import org.isoron.uhabits.core.models.Habit
|
import org.isoron.uhabits.core.models.Habit
|
||||||
|
import org.isoron.uhabits.core.models.HabitGroup
|
||||||
|
import org.isoron.uhabits.core.models.HabitGroupList
|
||||||
import org.isoron.uhabits.core.models.HabitList
|
import org.isoron.uhabits.core.models.HabitList
|
||||||
import org.isoron.uhabits.core.models.HabitMatcher
|
import org.isoron.uhabits.core.models.HabitMatcher
|
||||||
import org.isoron.uhabits.core.preferences.WidgetPreferences
|
import org.isoron.uhabits.core.preferences.WidgetPreferences
|
||||||
@@ -39,6 +41,7 @@ import javax.inject.Inject
|
|||||||
class ReminderScheduler @Inject constructor(
|
class ReminderScheduler @Inject constructor(
|
||||||
private val commandRunner: CommandRunner,
|
private val commandRunner: CommandRunner,
|
||||||
private val habitList: HabitList,
|
private val habitList: HabitList,
|
||||||
|
private val habitGroupList: HabitGroupList,
|
||||||
private val sys: SystemScheduler,
|
private val sys: SystemScheduler,
|
||||||
private val widgetPreferences: WidgetPreferences
|
private val widgetPreferences: WidgetPreferences
|
||||||
) : CommandRunner.Listener {
|
) : CommandRunner.Listener {
|
||||||
@@ -83,6 +86,40 @@ class ReminderScheduler @Inject constructor(
|
|||||||
scheduleAtTime(habit, reminderTime)
|
scheduleAtTime(habit, reminderTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
fun schedule(habitGroup: HabitGroup) {
|
||||||
|
if (habitGroup.id == null) {
|
||||||
|
sys.log("ReminderScheduler", "Habit group has null id. Returning.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!habitGroup.hasReminder()) {
|
||||||
|
sys.log("ReminderScheduler", "habit group=" + habitGroup.id + " has no reminder. Skipping.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var reminderTime = Objects.requireNonNull(habitGroup.reminder)!!.timeInMillis
|
||||||
|
val snoozeReminderTime = widgetPreferences.getSnoozeTime(habitGroup.id!!)
|
||||||
|
if (snoozeReminderTime != 0L) {
|
||||||
|
val now = applyTimezone(getLocalTime())
|
||||||
|
sys.log(
|
||||||
|
"ReminderScheduler",
|
||||||
|
String.format(
|
||||||
|
Locale.US,
|
||||||
|
"Habit group %d has been snoozed until %d",
|
||||||
|
habitGroup.id,
|
||||||
|
snoozeReminderTime
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if (snoozeReminderTime > now) {
|
||||||
|
sys.log("ReminderScheduler", "Snooze time is in the future. Accepting.")
|
||||||
|
reminderTime = snoozeReminderTime
|
||||||
|
} else {
|
||||||
|
sys.log("ReminderScheduler", "Snooze time is in the past. Discarding.")
|
||||||
|
widgetPreferences.removeSnoozeTime(habitGroup.id!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
scheduleAtTime(habitGroup, reminderTime)
|
||||||
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun scheduleAtTime(habit: Habit, reminderTime: Long) {
|
fun scheduleAtTime(habit: Habit, reminderTime: Long) {
|
||||||
sys.log("ReminderScheduler", "Scheduling alarm for habit=" + habit.id)
|
sys.log("ReminderScheduler", "Scheduling alarm for habit=" + habit.id)
|
||||||
@@ -108,16 +145,48 @@ class ReminderScheduler @Inject constructor(
|
|||||||
sys.scheduleShowReminder(reminderTime, habit, timestamp)
|
sys.scheduleShowReminder(reminderTime, habit, timestamp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
fun scheduleAtTime(habitGroup: HabitGroup, reminderTime: Long) {
|
||||||
|
sys.log("ReminderScheduler", "Scheduling alarm for habit group=" + habitGroup.id)
|
||||||
|
if (!habitGroup.hasReminder()) {
|
||||||
|
sys.log("ReminderScheduler", "habit group=" + habitGroup.id + " has no reminder. Skipping.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (habitGroup.isArchived) {
|
||||||
|
sys.log("ReminderScheduler", "habit group=" + habitGroup.id + " is archived. Skipping.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val timestamp = getStartOfDayWithOffset(removeTimezone(reminderTime))
|
||||||
|
sys.log(
|
||||||
|
"ReminderScheduler",
|
||||||
|
String.format(
|
||||||
|
Locale.US,
|
||||||
|
"reminderTime=%d removeTimezone=%d timestamp=%d",
|
||||||
|
reminderTime,
|
||||||
|
removeTimezone(reminderTime),
|
||||||
|
timestamp
|
||||||
|
)
|
||||||
|
)
|
||||||
|
sys.scheduleShowReminder(reminderTime, habitGroup, timestamp)
|
||||||
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun scheduleAll() {
|
fun scheduleAll() {
|
||||||
sys.log("ReminderScheduler", "Scheduling all alarms")
|
sys.log("ReminderScheduler", "Scheduling all alarms")
|
||||||
val reminderHabits = habitList.getFiltered(HabitMatcher.WITH_ALARM)
|
val reminderHabits = habitList.getFiltered(HabitMatcher.WITH_ALARM)
|
||||||
|
val reminderSubHabits = habitGroupList.map { it.habitList.getFiltered(HabitMatcher.WITH_ALARM) }.flatten()
|
||||||
|
val reminderHabitGroups = habitGroupList.getFiltered(HabitMatcher.WITH_ALARM)
|
||||||
for (habit in reminderHabits) schedule(habit)
|
for (habit in reminderHabits) schedule(habit)
|
||||||
|
for (habit in reminderSubHabits) schedule(habit)
|
||||||
|
for (hgr in reminderHabitGroups) schedule(hgr)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun hasHabitsWithReminders(): Boolean {
|
fun hasHabitsWithReminders(): Boolean {
|
||||||
return !habitList.getFiltered(HabitMatcher.WITH_ALARM).isEmpty
|
if (!habitList.getFiltered(HabitMatcher.WITH_ALARM).isEmpty) return true
|
||||||
|
if (habitGroupList.map { it.habitList.getFiltered(HabitMatcher.WITH_ALARM) }.flatten().isNotEmpty()) return true
|
||||||
|
if (!habitGroupList.getFiltered(HabitMatcher.WITH_ALARM).isEmpty) return true
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
@@ -138,6 +207,14 @@ class ReminderScheduler @Inject constructor(
|
|||||||
schedule(habit)
|
schedule(habit)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
fun snoozeReminder(habitGroup: HabitGroup, minutes: Long) {
|
||||||
|
val now = applyTimezone(getLocalTime())
|
||||||
|
val snoozedUntil = now + minutes * 60 * 1000
|
||||||
|
widgetPreferences.setSnoozeTime(habitGroup.id!!, snoozedUntil)
|
||||||
|
schedule(habitGroup)
|
||||||
|
}
|
||||||
|
|
||||||
interface SystemScheduler {
|
interface SystemScheduler {
|
||||||
fun scheduleShowReminder(
|
fun scheduleShowReminder(
|
||||||
reminderTime: Long,
|
reminderTime: Long,
|
||||||
@@ -145,6 +222,12 @@ class ReminderScheduler @Inject constructor(
|
|||||||
timestamp: Long
|
timestamp: Long
|
||||||
): SchedulerResult
|
): SchedulerResult
|
||||||
|
|
||||||
|
fun scheduleShowReminder(
|
||||||
|
reminderTime: Long,
|
||||||
|
habitGroup: HabitGroup,
|
||||||
|
timestamp: Long
|
||||||
|
): SchedulerResult
|
||||||
|
|
||||||
fun scheduleWidgetUpdate(updateTime: Long): SchedulerResult?
|
fun scheduleWidgetUpdate(updateTime: Long): SchedulerResult?
|
||||||
fun log(componentName: String, msg: String)
|
fun log(componentName: String, msg: String)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,13 +22,14 @@ import org.isoron.uhabits.core.AppScope
|
|||||||
import org.isoron.uhabits.core.commands.Command
|
import org.isoron.uhabits.core.commands.Command
|
||||||
import org.isoron.uhabits.core.commands.CommandRunner
|
import org.isoron.uhabits.core.commands.CommandRunner
|
||||||
import org.isoron.uhabits.core.commands.CreateRepetitionCommand
|
import org.isoron.uhabits.core.commands.CreateRepetitionCommand
|
||||||
|
import org.isoron.uhabits.core.commands.DeleteHabitGroupsCommand
|
||||||
import org.isoron.uhabits.core.commands.DeleteHabitsCommand
|
import org.isoron.uhabits.core.commands.DeleteHabitsCommand
|
||||||
import org.isoron.uhabits.core.models.Habit
|
import org.isoron.uhabits.core.models.Habit
|
||||||
|
import org.isoron.uhabits.core.models.HabitGroup
|
||||||
import org.isoron.uhabits.core.models.Timestamp
|
import org.isoron.uhabits.core.models.Timestamp
|
||||||
import org.isoron.uhabits.core.preferences.Preferences
|
import org.isoron.uhabits.core.preferences.Preferences
|
||||||
import org.isoron.uhabits.core.tasks.Task
|
import org.isoron.uhabits.core.tasks.Task
|
||||||
import org.isoron.uhabits.core.tasks.TaskRunner
|
import org.isoron.uhabits.core.tasks.TaskRunner
|
||||||
import java.util.HashMap
|
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import java.util.Objects
|
import java.util.Objects
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@@ -40,11 +41,18 @@ class NotificationTray @Inject constructor(
|
|||||||
private val preferences: Preferences,
|
private val preferences: Preferences,
|
||||||
private val systemTray: SystemTray
|
private val systemTray: SystemTray
|
||||||
) : CommandRunner.Listener, Preferences.Listener {
|
) : CommandRunner.Listener, Preferences.Listener {
|
||||||
private val active: HashMap<Habit, NotificationData> = HashMap()
|
private val activeHabits: HashMap<Habit, NotificationData> = HashMap()
|
||||||
|
private val activeHabitGroups: HashMap<HabitGroup, NotificationData> = HashMap()
|
||||||
fun cancel(habit: Habit) {
|
fun cancel(habit: Habit) {
|
||||||
val notificationId = getNotificationId(habit)
|
val notificationId = getNotificationId(habit)
|
||||||
systemTray.removeNotification(notificationId)
|
systemTray.removeNotification(notificationId)
|
||||||
active.remove(habit)
|
activeHabits.remove(habit)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cancel(habitGroup: HabitGroup) {
|
||||||
|
val notificationId = getNotificationId(habitGroup)
|
||||||
|
systemTray.removeNotification(notificationId)
|
||||||
|
activeHabitGroups.remove(habitGroup)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCommandFinished(command: Command) {
|
override fun onCommandFinished(command: Command) {
|
||||||
@@ -56,6 +64,13 @@ class NotificationTray @Inject constructor(
|
|||||||
val (_, deleted) = command
|
val (_, deleted) = command
|
||||||
for (habit in deleted) cancel(habit)
|
for (habit in deleted) cancel(habit)
|
||||||
}
|
}
|
||||||
|
if (command is DeleteHabitGroupsCommand) {
|
||||||
|
val (_, deletedGroups) = command
|
||||||
|
for (hgr in deletedGroups) {
|
||||||
|
for (h in hgr.habitList) cancel(h)
|
||||||
|
cancel(hgr)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onNotificationsChanged() {
|
override fun onNotificationsChanged() {
|
||||||
@@ -64,10 +79,16 @@ class NotificationTray @Inject constructor(
|
|||||||
|
|
||||||
fun show(habit: Habit, timestamp: Timestamp, reminderTime: Long) {
|
fun show(habit: Habit, timestamp: Timestamp, reminderTime: Long) {
|
||||||
val data = NotificationData(timestamp, reminderTime)
|
val data = NotificationData(timestamp, reminderTime)
|
||||||
active[habit] = data
|
activeHabits[habit] = data
|
||||||
taskRunner.execute(ShowNotificationTask(habit, data))
|
taskRunner.execute(ShowNotificationTask(habit, data))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun show(habitGroup: HabitGroup, timestamp: Timestamp, reminderTime: Long) {
|
||||||
|
val data = NotificationData(timestamp, reminderTime)
|
||||||
|
activeHabitGroups[habitGroup] = data
|
||||||
|
taskRunner.execute(ShowNotificationTask(habitGroup, data))
|
||||||
|
}
|
||||||
|
|
||||||
fun startListening() {
|
fun startListening() {
|
||||||
commandRunner.addListener(this)
|
commandRunner.addListener(this)
|
||||||
preferences.addListener(this)
|
preferences.addListener(this)
|
||||||
@@ -83,14 +104,22 @@ class NotificationTray @Inject constructor(
|
|||||||
return (id % Int.MAX_VALUE).toInt()
|
return (id % Int.MAX_VALUE).toInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getNotificationId(habitGroup: HabitGroup): Int {
|
||||||
|
val id = habitGroup.id ?: return 0
|
||||||
|
return (id % Int.MAX_VALUE).toInt()
|
||||||
|
}
|
||||||
|
|
||||||
private fun reshowAll() {
|
private fun reshowAll() {
|
||||||
for ((habit, data) in active.entries) {
|
for ((habit, data) in activeHabits.entries) {
|
||||||
taskRunner.execute(ShowNotificationTask(habit, data))
|
taskRunner.execute(ShowNotificationTask(habit, data))
|
||||||
}
|
}
|
||||||
|
for ((habitGroup, data) in activeHabitGroups.entries) {
|
||||||
|
taskRunner.execute(ShowNotificationTask(habitGroup, data))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun reshow(habit: Habit) {
|
fun reshow(habit: Habit) {
|
||||||
active[habit]?.let {
|
activeHabits[habit]?.let {
|
||||||
taskRunner.execute(ShowNotificationTask(habit, it))
|
taskRunner.execute(ShowNotificationTask(habit, it))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -104,48 +133,79 @@ class NotificationTray @Inject constructor(
|
|||||||
reminderTime: Long
|
reminderTime: Long
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fun showNotification(
|
||||||
|
habitGroup: HabitGroup,
|
||||||
|
notificationId: Int,
|
||||||
|
timestamp: Timestamp,
|
||||||
|
reminderTime: Long
|
||||||
|
)
|
||||||
|
|
||||||
fun log(msg: String)
|
fun log(msg: String)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class NotificationData(val timestamp: Timestamp, val reminderTime: Long)
|
internal class NotificationData(val timestamp: Timestamp, val reminderTime: Long)
|
||||||
private inner class ShowNotificationTask(private val habit: Habit, data: NotificationData) :
|
private inner class ShowNotificationTask private constructor(
|
||||||
Task {
|
private val habit: Habit? = null,
|
||||||
|
private val habitGroup: HabitGroup? = null,
|
||||||
|
data: NotificationData
|
||||||
|
) : Task {
|
||||||
|
// Secondary constructor for Habit
|
||||||
|
constructor(habit: Habit, data: NotificationData) : this(habit, null, data)
|
||||||
|
|
||||||
|
// Secondary constructor for HabitGroup
|
||||||
|
constructor(habitGroup: HabitGroup, data: NotificationData) : this(null, habitGroup, data)
|
||||||
|
|
||||||
var isCompleted = false
|
var isCompleted = false
|
||||||
private val timestamp: Timestamp = data.timestamp
|
private val timestamp: Timestamp = data.timestamp
|
||||||
private val reminderTime: Long = data.reminderTime
|
private val reminderTime: Long = data.reminderTime
|
||||||
|
|
||||||
|
private val type = if (habit != null) "habit" else "habitgroup"
|
||||||
|
private val id = habit?.id ?: habitGroup?.id
|
||||||
|
private val hasReminder = habit?.hasReminder() ?: habitGroup!!.hasReminder()
|
||||||
|
private val isArchived = habit?.isArchived ?: habitGroup!!.isArchived
|
||||||
|
|
||||||
override fun doInBackground() {
|
override fun doInBackground() {
|
||||||
isCompleted = habit.isCompletedToday()
|
isCompleted = habit?.isCompletedToday() ?: habitGroup!!.isCompletedToday()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPostExecute() {
|
override fun onPostExecute() {
|
||||||
systemTray.log("Showing notification for habit=" + habit.id)
|
systemTray.log(
|
||||||
|
String.format(
|
||||||
|
Locale.US,
|
||||||
|
"Showing notification for %s=%d",
|
||||||
|
type,
|
||||||
|
id
|
||||||
|
)
|
||||||
|
)
|
||||||
if (isCompleted) {
|
if (isCompleted) {
|
||||||
systemTray.log(
|
systemTray.log(
|
||||||
String.format(
|
String.format(
|
||||||
Locale.US,
|
Locale.US,
|
||||||
"Habit %d already checked. Skipping.",
|
"%s %d already checked. Skipping.",
|
||||||
habit.id
|
type,
|
||||||
|
id
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (!habit.hasReminder()) {
|
if (!hasReminder) {
|
||||||
systemTray.log(
|
systemTray.log(
|
||||||
String.format(
|
String.format(
|
||||||
Locale.US,
|
Locale.US,
|
||||||
"Habit %d does not have a reminder. Skipping.",
|
"%s %d does not have a reminder. Skipping.",
|
||||||
habit.id
|
type,
|
||||||
|
id
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (habit.isArchived) {
|
if (isArchived) {
|
||||||
systemTray.log(
|
systemTray.log(
|
||||||
String.format(
|
String.format(
|
||||||
Locale.US,
|
Locale.US,
|
||||||
"Habit %d is archived. Skipping.",
|
"%s %d is archived. Skipping.",
|
||||||
habit.id
|
type,
|
||||||
|
id
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
@@ -154,23 +214,33 @@ class NotificationTray @Inject constructor(
|
|||||||
systemTray.log(
|
systemTray.log(
|
||||||
String.format(
|
String.format(
|
||||||
Locale.US,
|
Locale.US,
|
||||||
"Habit %d not supposed to run today. Skipping.",
|
"%s %d not supposed to run today. Skipping.",
|
||||||
habit.id
|
type,
|
||||||
|
id
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
systemTray.showNotification(
|
if (habit != null) {
|
||||||
habit,
|
systemTray.showNotification(
|
||||||
getNotificationId(habit),
|
habit,
|
||||||
timestamp,
|
getNotificationId(habit),
|
||||||
reminderTime
|
timestamp,
|
||||||
)
|
reminderTime
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
systemTray.showNotification(
|
||||||
|
habitGroup!!,
|
||||||
|
getNotificationId(habitGroup),
|
||||||
|
timestamp,
|
||||||
|
reminderTime
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun shouldShowReminderToday(): Boolean {
|
private fun shouldShowReminderToday(): Boolean {
|
||||||
if (!habit.hasReminder()) return false
|
if (!hasReminder) return false
|
||||||
val reminder = habit.reminder
|
val reminder = habit?.reminder ?: habitGroup!!.reminder
|
||||||
val reminderDays = Objects.requireNonNull(reminder)!!.days.toArray()
|
val reminderDays = Objects.requireNonNull(reminder)!!.days.toArray()
|
||||||
val weekday = timestamp.weekday
|
val weekday = timestamp.weekday
|
||||||
return reminderDays[weekday]
|
return reminderDays[weekday]
|
||||||
|
|||||||
Reference in New Issue
Block a user