Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
## [1.6.0] - 2026-01-06
### Changed
- Added specific date alarm functionality ([#42])


## [1.5.0] - 2025-12-16
### Changed
Expand Down Expand Up @@ -93,6 +97,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Initial release.

[#11]: https://github.com/FossifyOrg/Clock/issues/11
[#42]: https://github.com/FossifyOrg/Clock/issues/42
[#107]: https://github.com/FossifyOrg/Clock/issues/107
[#144]: https://github.com/FossifyOrg/Clock/issues/144
[#158]: https://github.com/FossifyOrg/Clock/issues/158
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -209,8 +209,7 @@ class AlarmsAdapter(

return when {
!isEnabled -> resources.getString(R.string.not_scheduled)
alarm.isToday() -> resources.getString(org.fossify.commons.R.string.today)
else -> resources.getString(org.fossify.commons.R.string.tomorrow)
else -> alarm.getDateLabel(activity)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.fossify.clock.dialogs

import android.app.DatePickerDialog
import android.app.TimePickerDialog
import android.graphics.drawable.Drawable
import android.media.AudioManager
Expand Down Expand Up @@ -27,6 +28,7 @@ import org.fossify.commons.dialogs.SelectAlarmSoundDialog
import org.fossify.commons.extensions.addBit
import org.fossify.commons.extensions.applyColorFilter
import org.fossify.commons.extensions.beVisibleIf
import org.fossify.commons.extensions.beGone
import org.fossify.commons.extensions.getAlertDialogBuilder
import org.fossify.commons.extensions.getDefaultAlarmSound
import org.fossify.commons.extensions.getProperBackgroundColor
Expand All @@ -38,6 +40,7 @@ import org.fossify.commons.extensions.setupDialogStuff
import org.fossify.commons.extensions.toast
import org.fossify.commons.extensions.value
import org.fossify.commons.models.AlarmSound
import java.util.Calendar

class EditAlarmDialog(
val activity: SimpleActivity,
Expand All @@ -51,6 +54,7 @@ class EditAlarmDialog(
init {
restoreLastAlarm()
updateAlarmTime()
updateDateSelectorUI()

binding.apply {
editAlarmTime.setOnClickListener {
Expand Down Expand Up @@ -119,11 +123,21 @@ class EditAlarmDialog(

editAlarmLabelImage.applyColorFilter(textColor)
editAlarm.setText(alarm.label)
// Date selector setup
editAlarmCalendarIcon.applyColorFilter(textColor)
editAlarmDateClear.applyColorFilter(textColor)
editAlarmDateSelector.setOnClickListener {
showDatePicker()
}
editAlarmDateClear.setOnClickListener {
clearSpecificDate()
}

val dayLetters =
activity.resources.getStringArray(org.fossify.commons.R.array.week_day_letters)
.toList() as ArrayList<String>
val dayIndexes = activity.rotateWeekdays(arrayListOf(0, 1, 2, 3, 4, 5, 6))


dayIndexes.forEach {
val bitmask = 1 shl it
Expand Down Expand Up @@ -154,6 +168,7 @@ class EditAlarmDialog(

editAlarmDaysHolder.addView(day)
}
updateWeekdaysVisibility()
}

activity.getAlertDialogBuilder()
Expand Down Expand Up @@ -239,14 +254,16 @@ class EditAlarmDialog(
}

private fun checkDaylessAlarm() {
if (!alarm.isRecurring()) {
if (!alarm.isRecurring() && !alarm.hasSpecificDate()) {
val textId = if (alarm.timeInMinutes > getCurrentDayMinutes()) {
org.fossify.commons.R.string.today
} else {
org.fossify.commons.R.string.tomorrow
}

binding.editAlarmDaylessLabel.text = "(${activity.getString(textId)})"
} else if (alarm.hasSpecificDate()) {
binding.editAlarmDaylessLabel.text = "(${alarm.getDateLabel(activity)})"
}
binding.editAlarmDaylessLabel.beVisibleIf(!alarm.isRecurring())
}
Expand All @@ -268,4 +285,60 @@ class EditAlarmDialog(
alarm.soundUri = alarmSound.uri
binding.editAlarmSound.text = alarmSound.title
}

private fun showDatePicker() {
val calendar = Calendar.getInstance()
alarm.specificDate?.let {
calendar.timeInMillis = it
}

DatePickerDialog(
activity,
{ _, year, month, dayOfMonth ->
val selectedDate = Calendar.getInstance().apply {
set(Calendar.YEAR, year)
set(Calendar.MONTH, month)
set(Calendar.DAY_OF_MONTH, dayOfMonth)
set(Calendar.HOUR_OF_DAY, 0)
set(Calendar.MINUTE, 0)
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
}
alarm.specificDate = selectedDate.timeInMillis
alarm.days = 0 // Clear recurring days when setting specific date
updateDateSelectorUI()
updateWeekdaysVisibility()
checkDaylessAlarm()
},
calendar.get(Calendar.YEAR),
calendar.get(Calendar.MONTH),
calendar.get(Calendar.DAY_OF_MONTH)
).apply {
datePicker.minDate = System.currentTimeMillis() - 1000 // Allow today
show()
}
}

private fun clearSpecificDate() {
alarm.specificDate = null
updateDateSelectorUI()
updateWeekdaysVisibility()
checkDaylessAlarm()
}

private fun updateDateSelectorUI() {
binding.apply {
if (alarm.hasSpecificDate()) {
editAlarmDateLabel.text = alarm.getDateLabel(activity)
editAlarmDateClear.beVisibleIf(true)
} else {
editAlarmDateLabel.text = activity.getString(R.string.select_specific_date)
editAlarmDateClear.beGone()
}
}
}

private fun updateWeekdaysVisibility() {
binding.editAlarmDaysHolder.beVisibleIf(!alarm.hasSpecificDate())
}
}
11 changes: 10 additions & 1 deletion app/src/main/kotlin/org/fossify/clock/helpers/Constants.kt
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,15 @@ fun getAllTimeZones() = arrayListOf(
)

fun getTimeOfNextAlarm(alarm: Alarm): Calendar? {
if (alarm.specificDate != null) {
return Calendar.getInstance().apply {
timeInMillis = alarm.specificDate!!
set(Calendar.HOUR_OF_DAY, alarm.timeInMinutes / 60)
set(Calendar.MINUTE, alarm.timeInMinutes % 60)
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
}
}
return getTimeOfNextAlarm(alarm.timeInMinutes, alarm.days)
}

Expand Down Expand Up @@ -304,7 +313,7 @@ fun getTimeOfNextAlarm(alarmTimeInMinutes: Int, days: Int): Calendar? {
}

fun updateNonRecurringAlarmDay(alarm: Alarm) {
if (alarm.isRecurring()) return
if (alarm.isRecurring() || alarm.hasSpecificDate()) return
alarm.days = if (alarm.timeInMinutes > getCurrentDayMinutes()) {
TODAY_BIT
} else {
Expand Down
30 changes: 24 additions & 6 deletions app/src/main/kotlin/org/fossify/clock/helpers/DBHelper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import org.fossify.commons.helpers.THURSDAY_BIT
import org.fossify.commons.helpers.TUESDAY_BIT
import org.fossify.commons.helpers.WEDNESDAY_BIT

@Suppress("VariableNaming")
class DBHelper private constructor(
val context: Context,
) : SQLiteOpenHelper(context, DB_NAME, null, DB_VERSION) {
Expand All @@ -34,11 +35,14 @@ class DBHelper private constructor(
private val COL_SOUND_URI = "sound_uri"
private val COL_LABEL = "label"
private val COL_ONE_SHOT = "one_shot"
private val COL_SPECIFIC_DATE = "specific_date"

private val mDb = writableDatabase

companion object {
private const val DB_VERSION = 2
private const val DB_VERSION = 3
private const val DB_VERSION_1 = 1
private const val DB_VERSION_3 = 3
const val DB_NAME = "alarms.db"

@SuppressLint("StaticFieldLeak")
Expand All @@ -55,16 +59,21 @@ class DBHelper private constructor(

override fun onCreate(db: SQLiteDatabase) {
db.execSQL(
"CREATE TABLE IF NOT EXISTS $ALARMS_TABLE_NAME ($COL_ID INTEGER PRIMARY KEY AUTOINCREMENT, $COL_TIME_IN_MINUTES INTEGER, $COL_DAYS INTEGER, " +
"$COL_IS_ENABLED INTEGER, $COL_VIBRATE INTEGER, $COL_SOUND_TITLE TEXT, $COL_SOUND_URI TEXT, $COL_LABEL TEXT, $COL_ONE_SHOT INTEGER)"
"CREATE TABLE IF NOT EXISTS $ALARMS_TABLE_NAME ($COL_ID INTEGER PRIMARY KEY AUTOINCREMENT, " +
"$COL_TIME_IN_MINUTES INTEGER, $COL_DAYS INTEGER, $COL_IS_ENABLED INTEGER, " +
"$COL_VIBRATE INTEGER, $COL_SOUND_TITLE TEXT, $COL_SOUND_URI TEXT, $COL_LABEL TEXT, " +
"$COL_ONE_SHOT INTEGER, $COL_SPECIFIC_DATE INTEGER)"
)
insertInitialAlarms(db)
}

override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
if (oldVersion == 1 && newVersion > oldVersion) {
if (oldVersion == DB_VERSION_1 && newVersion > oldVersion) {
db.execSQL("ALTER TABLE $ALARMS_TABLE_NAME ADD COLUMN $COL_ONE_SHOT INTEGER NOT NULL DEFAULT 0")
}
if (oldVersion < DB_VERSION_3 && newVersion >= DB_VERSION_3) {
db.execSQL("ALTER TABLE $ALARMS_TABLE_NAME ADD COLUMN $COL_SPECIFIC_DATE INTEGER")
}
}

private fun insertInitialAlarms(db: SQLiteDatabase) {
Expand Down Expand Up @@ -121,6 +130,7 @@ class DBHelper private constructor(
put(COL_SOUND_URI, alarm.soundUri)
put(COL_LABEL, alarm.label)
put(COL_ONE_SHOT, alarm.oneShot)
put(COL_SPECIFIC_DATE, alarm.specificDate)
}
}

Expand All @@ -137,7 +147,8 @@ class DBHelper private constructor(
COL_SOUND_TITLE,
COL_SOUND_URI,
COL_LABEL,
COL_ONE_SHOT
COL_ONE_SHOT,
COL_SPECIFIC_DATE
)
var cursor: Cursor? = null
try {
Expand All @@ -154,6 +165,12 @@ class DBHelper private constructor(
val soundUri = cursor.getStringValue(COL_SOUND_URI)
val label = cursor.getStringValue(COL_LABEL)
val oneShot = cursor.getIntValue(COL_ONE_SHOT) == 1
val specificDateIndex = cursor.getColumnIndex(COL_SPECIFIC_DATE)
val specificDate = if (specificDateIndex != -1 && !cursor.isNull(specificDateIndex)) {
cursor.getLong(specificDateIndex)
} else {
null
}

val alarm = Alarm(
id = id,
Expand All @@ -164,7 +181,8 @@ class DBHelper private constructor(
soundTitle = soundTitle,
soundUri = soundUri,
label = label,
oneShot = oneShot
oneShot = oneShot,
specificDate = specificDate
)
alarms.add(alarm)
} catch (e: Exception) {
Expand Down
Loading
Loading