Skip to content

Commit f7ebacf

Browse files
authored
Merge pull request #151 from FossifyOrg/v1.2.0
Release 1.2.0
2 parents f563c37 + 2787af7 commit f7ebacf

File tree

9 files changed

+226
-137
lines changed

9 files changed

+226
-137
lines changed

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,19 @@
11
Changelog
22
==========
33

4+
Version 1.2.0 *(2025-04-15)*
5+
----------------------------
6+
7+
* Added notifications for missed or replaced alarms
8+
* Added placeholders for empty alarms and timers (#124)
9+
* Fixed issue where alarms could be silenced unintentionally (#77, #93)
10+
* Fixed issue where alarms didn't go off in some cases (#89)
11+
* Fixed broken behavior when re-enabling one-time alarms (#110)
12+
* Replaced checkboxes with switches (https://github.com/orgs/FossifyOrg/discussions/78)
13+
* Removed support for Android 7 and older versions (https://github.com/orgs/FossifyOrg/discussions/241)
14+
* Other minor bug fixes and improvements
15+
* Added more translations
16+
417
Version 1.1.0 *(2025-03-24)*
518
----------------------------
619

app/src/main/kotlin/org/fossify/clock/App.kt

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package org.fossify.clock
22

3-
import android.app.Application
43
import android.os.CountDownTimer
54
import android.os.Handler
65
import android.os.Looper
@@ -22,14 +21,14 @@ import org.fossify.clock.services.StopwatchStopService
2221
import org.fossify.clock.services.TimerStopService
2322
import org.fossify.clock.services.startStopwatchService
2423
import org.fossify.clock.services.startTimerService
25-
import org.fossify.commons.extensions.checkUseEnglish
24+
import org.fossify.commons.FossifyApp
2625
import org.fossify.commons.extensions.notificationManager
2726
import org.fossify.commons.extensions.showErrorToast
2827
import org.greenrobot.eventbus.EventBus
2928
import org.greenrobot.eventbus.Subscribe
3029
import org.greenrobot.eventbus.ThreadMode
3130

32-
class App : Application(), LifecycleObserver {
31+
class App : FossifyApp(), LifecycleObserver {
3332

3433
private var countDownTimers = mutableMapOf<Int, CountDownTimer>()
3534

@@ -41,8 +40,6 @@ class App : Application(), LifecycleObserver {
4140
if (BuildConfig.DEBUG) {
4241
Stetho.initializeWithDefaults(this)
4342
}
44-
45-
checkUseEnglish()
4643
}
4744

4845
override fun onTerminate() {

app/src/main/kotlin/org/fossify/clock/helpers/AlarmController.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,12 @@ class AlarmController(
215215
this.action = action
216216
putExtra(ALARM_ID, alarmId)
217217
}
218-
context.startForegroundService(serviceIntent)
218+
219+
when (action) {
220+
AlarmService.ACTION_START_ALARM -> context.startForegroundService(serviceIntent)
221+
AlarmService.ACTION_STOP_ALARM -> context.startService(serviceIntent)
222+
else -> throw IllegalArgumentException("Unknown action: $action")
223+
}
219224
} catch (e: Exception) {
220225
context.showErrorToast(e)
221226
}
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
package org.fossify.clock.helpers
2+
3+
import android.app.Notification
4+
import android.app.NotificationChannel
5+
import android.app.NotificationManager
6+
import android.app.PendingIntent
7+
import android.content.Context
8+
import android.content.Intent
9+
import androidx.core.app.NotificationCompat
10+
import org.fossify.clock.R
11+
import org.fossify.clock.activities.AlarmActivity
12+
import org.fossify.clock.extensions.getFormattedTime
13+
import org.fossify.clock.extensions.getOpenAlarmTabIntent
14+
import org.fossify.clock.extensions.getSnoozePendingIntent
15+
import org.fossify.clock.extensions.getStopAlarmPendingIntent
16+
import org.fossify.clock.models.Alarm
17+
import org.fossify.commons.extensions.notificationManager
18+
19+
/**
20+
* Helper class to handle alarm notifications in the app.
21+
* This includes creating notification channels, building notifications for active alarms,
22+
* and posting notifications for missed or replaced alarms.
23+
*/
24+
class AlarmNotificationHelper(private val context: Context) {
25+
26+
/**
27+
* Builds and returns the active alarm notification to be shown in the foreground service.
28+
*/
29+
fun buildActiveAlarmNotification(alarm: Alarm): Notification {
30+
val channelId = ALARM_NOTIFICATION_CHANNEL_ID
31+
val channel = NotificationChannel(
32+
channelId,
33+
context.getString(org.fossify.commons.R.string.alarm),
34+
NotificationManager.IMPORTANCE_HIGH
35+
).apply {
36+
setBypassDnd(true)
37+
setSound(null, null)
38+
}
39+
40+
context.notificationManager.createNotificationChannel(channel)
41+
42+
val contentTitle = alarm.label.ifEmpty {
43+
context.getString(org.fossify.commons.R.string.alarm)
44+
}
45+
46+
val contentText = context.getFormattedTime(
47+
passedSeconds = alarm.timeInMinutes * 60,
48+
showSeconds = false,
49+
makeAmPmSmaller = false
50+
)
51+
52+
val reminderIntent = Intent(context, AlarmActivity::class.java).apply {
53+
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
54+
putExtra(ALARM_ID, alarm.id)
55+
}
56+
57+
val pendingIntent = PendingIntent.getActivity(
58+
context, alarm.id, reminderIntent,
59+
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
60+
)
61+
62+
val dismissIntent = context.getStopAlarmPendingIntent(alarm)
63+
val snoozeIntent = context.getSnoozePendingIntent(alarm)
64+
65+
return NotificationCompat.Builder(context, channelId)
66+
.setContentTitle(contentTitle)
67+
.setContentText(contentText)
68+
.setSmallIcon(R.drawable.ic_alarm_vector)
69+
.setContentIntent(pendingIntent)
70+
.setPriority(NotificationCompat.PRIORITY_HIGH)
71+
.setCategory(NotificationCompat.CATEGORY_ALARM)
72+
.setDefaults(NotificationCompat.DEFAULT_LIGHTS)
73+
.addAction(
74+
org.fossify.commons.R.drawable.ic_snooze_vector,
75+
context.getString(org.fossify.commons.R.string.snooze),
76+
snoozeIntent
77+
)
78+
.addAction(
79+
org.fossify.commons.R.drawable.ic_cross_vector,
80+
context.getString(org.fossify.commons.R.string.dismiss),
81+
dismissIntent
82+
)
83+
.setDeleteIntent(dismissIntent)
84+
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
85+
.setFullScreenIntent(pendingIntent, true)
86+
.build()
87+
}
88+
89+
/**
90+
* Creates the missed alarm notification channel.
91+
*/
92+
private fun createMissedAlarmNotificationChannel() {
93+
val channel = NotificationChannel(
94+
MISSED_ALARM_NOTIFICATION_CHANNEL_ID,
95+
context.getString(R.string.missed_alarm),
96+
NotificationManager.IMPORTANCE_DEFAULT
97+
).apply {
98+
setSound(null, null)
99+
}
100+
101+
context.notificationManager.createNotificationChannel(channel)
102+
}
103+
104+
/**
105+
* Posts a notification for a missed alarm (auto-dismissed).
106+
*/
107+
fun postMissedAlarmNotification(missedAlarm: Alarm) {
108+
createMissedAlarmNotificationChannel()
109+
val replacedTime = context.getFormattedTime(
110+
passedSeconds = missedAlarm.timeInMinutes * 60,
111+
showSeconds = false,
112+
makeAmPmSmaller = false
113+
)
114+
val contentIntent = context.getOpenAlarmTabIntent()
115+
val notification = NotificationCompat.Builder(context, MISSED_ALARM_NOTIFICATION_CHANNEL_ID)
116+
.setContentTitle(context.getString(R.string.missed_alarm))
117+
.setContentText(context.getString(R.string.alarm_timed_out))
118+
.setContentIntent(contentIntent)
119+
.setSubText(replacedTime)
120+
.setSmallIcon(R.drawable.ic_alarm_off_vector)
121+
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
122+
.setShowWhen(false)
123+
.setAutoCancel(true)
124+
.build()
125+
126+
context.notificationManager.notify(
127+
MISSED_ALARM_NOTIFICATION_TAG,
128+
missedAlarm.id,
129+
notification
130+
)
131+
}
132+
133+
/**
134+
* Posts a notification for a replaced alarm (when a new alarm starts while another is active).
135+
*/
136+
fun postReplacedAlarmNotification(replacedAlarm: Alarm) {
137+
createMissedAlarmNotificationChannel()
138+
139+
val replacedTime = context.getFormattedTime(
140+
passedSeconds = replacedAlarm.timeInMinutes * 60,
141+
showSeconds = false,
142+
makeAmPmSmaller = false
143+
)
144+
val contentIntent = context.getOpenAlarmTabIntent()
145+
val notification = NotificationCompat.Builder(context, MISSED_ALARM_NOTIFICATION_CHANNEL_ID)
146+
.setContentTitle(context.getString(R.string.missed_alarm))
147+
.setContentText(context.getString(R.string.replaced_by_another_alarm))
148+
.setContentIntent(contentIntent)
149+
.setSubText(replacedTime)
150+
.setSmallIcon(R.drawable.ic_alarm_off_vector)
151+
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
152+
.setShowWhen(false)
153+
.setAutoCancel(true)
154+
.build()
155+
156+
context.notificationManager.notify(
157+
MISSED_ALARM_NOTIFICATION_TAG,
158+
replacedAlarm.id,
159+
notification
160+
)
161+
}
162+
}

0 commit comments

Comments
 (0)