diff --git a/.github/workflows/build-apk.yml b/.github/workflows/build-apk.yml deleted file mode 100644 index 1bfc0fb..0000000 --- a/.github/workflows/build-apk.yml +++ /dev/null @@ -1,38 +0,0 @@ -name: Build Release APK - -on: - workflow_dispatch: - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Set up JDK 17 - uses: actions/setup-java@v4 - with: - java-version: '17' - distribution: 'temurin' - - - name: Setup Android SDK - uses: android-actions/setup-android@v3 - - - name: Decode keystore - run: | - echo "${{ secrets.RELEASE_KEYSTORE_B64 }}" | base64 -d > $RUNNER_TEMP/helios-release.jks - - - name: Build release APK - env: - RELEASE_KEYSTORE_PATH: ${{ runner.temp }}/helios-release.jks - RELEASE_KEYSTORE_PASS: ${{ secrets.RELEASE_KEYSTORE_PASS }} - RELEASE_KEY_ALIAS: helios - RELEASE_KEY_PASS: ${{ secrets.RELEASE_KEYSTORE_PASS }} - run: ./gradlew assembleRelease - - - name: Upload APK - uses: actions/upload-artifact@v4 - with: - name: helios-alarm-clock-debug - path: app/build/outputs/apk/release/*.apk - retention-days: 7 diff --git a/.github/workflows/generate-keystore.yml b/.github/workflows/generate-keystore.yml deleted file mode 100644 index 7c71ef3..0000000 --- a/.github/workflows/generate-keystore.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: Generate Keystore (run once) - -on: - workflow_dispatch: - -jobs: - generate: - runs-on: ubuntu-latest - steps: - - name: Set up JDK 17 - uses: actions/setup-java@v4 - with: - java-version: '17' - distribution: 'temurin' - - - name: Generate keystore - run: | - keytool -genkeypair \ - -keystore helios-release.jks \ - -alias helios \ - -keyalg RSA \ - -keysize 2048 \ - -validity 10000 \ - -storepass helios123 \ - -keypass helios123 \ - -dname "CN=Helios, OU=Helios, O=Helios, L=Berlin, ST=Berlin, C=DE" - echo "KEYSTORE_B64=$(base64 -w 0 helios-release.jks)" >> $GITHUB_OUTPUT - id: keygen - - - name: Upload keystore - uses: actions/upload-artifact@v4 - with: - name: helios-release-keystore - path: helios-release.jks - retention-days: 1 diff --git a/README.md b/README.md index e701c3a..7bb2fcb 100644 --- a/README.md +++ b/README.md @@ -31,14 +31,6 @@ Response: `201 Created` `hour` must be 0-23, `minute` must be 0-59. `label` is optional (defaults to empty). -To set an alarm for a specific future date, add an optional `date` field in `YYYY-MM-DD` format. Without `date`, the alarm fires at the next occurrence of the given time (today or tomorrow). - -```bash -curl -X POST http://PHONE_IP:8080/set \ - -H "Content-Type: application/json" \ - -d '{"hour": 8, "minute": 0, "label": "Vacation start", "date": "2026-10-08"}' -``` - ### Remove an alarm ```bash diff --git a/app/build.gradle.kts b/app/build.gradle.kts index a56f93f..47e155b 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -26,12 +26,10 @@ android { signingConfigs { create("release") { - val keystorePath = System.getenv("RELEASE_KEYSTORE_PATH") - storeFile = if (keystorePath != null) file(keystorePath) - else file(System.getProperty("user.home") + "/.android/debug.keystore") - storePassword = System.getenv("RELEASE_KEYSTORE_PASS") ?: "android" - keyAlias = System.getenv("RELEASE_KEY_ALIAS") ?: "androiddebugkey" - keyPassword = System.getenv("RELEASE_KEY_PASS") ?: "android" + storeFile = file(System.getProperty("user.home") + "/.android/debug.keystore") + storePassword = "android" + keyAlias = "androiddebugkey" + keyPassword = "android" } } buildTypes { diff --git a/app/src/main/java/com/example/helios_alarm_clock/data/AlarmDatabase.kt b/app/src/main/java/com/example/helios_alarm_clock/data/AlarmDatabase.kt index 2ff82ae..85f911c 100644 --- a/app/src/main/java/com/example/helios_alarm_clock/data/AlarmDatabase.kt +++ b/app/src/main/java/com/example/helios_alarm_clock/data/AlarmDatabase.kt @@ -2,45 +2,12 @@ package com.example.helios_alarm_clock.data import androidx.room.Database import androidx.room.RoomDatabase -import androidx.room.migration.Migration -import androidx.sqlite.db.SupportSQLiteDatabase -@Database(entities = [AlarmEntity::class], version = 3, exportSchema = true) +@Database(entities = [AlarmEntity::class], version = 1, exportSchema = true) abstract class AlarmDatabase : RoomDatabase() { abstract fun alarmDao(): AlarmDao companion object { const val NAME = "helios_alarms.db" - - // Fresh install path: add date column with correct default - val MIGRATION_1_2 = object : Migration(1, 2) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL("ALTER TABLE alarms ADD COLUMN date TEXT DEFAULT ''") - } - } - - // Fix path: first install had DEFAULT NULL instead of DEFAULT '' - // SQLite can't alter column defaults, so recreate the table - val MIGRATION_2_3 = object : Migration(2, 3) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL(""" - CREATE TABLE alarms_new ( - id TEXT NOT NULL PRIMARY KEY, - hour INTEGER NOT NULL, - minute INTEGER NOT NULL, - label TEXT NOT NULL, - triggerTimeMillis INTEGER NOT NULL, - date TEXT DEFAULT '' - ) - """.trimIndent()) - db.execSQL(""" - INSERT INTO alarms_new (id, hour, minute, label, triggerTimeMillis, date) - SELECT id, hour, minute, label, triggerTimeMillis, COALESCE(date, '') - FROM alarms - """.trimIndent()) - db.execSQL("DROP TABLE alarms") - db.execSQL("ALTER TABLE alarms_new RENAME TO alarms") - } - } } } diff --git a/app/src/main/java/com/example/helios_alarm_clock/data/AlarmEntity.kt b/app/src/main/java/com/example/helios_alarm_clock/data/AlarmEntity.kt index b663c2b..7adf2eb 100644 --- a/app/src/main/java/com/example/helios_alarm_clock/data/AlarmEntity.kt +++ b/app/src/main/java/com/example/helios_alarm_clock/data/AlarmEntity.kt @@ -1,6 +1,5 @@ package com.example.helios_alarm_clock.data -import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey import kotlinx.serialization.Serializable @@ -13,7 +12,5 @@ data class AlarmEntity( val hour: Int, val minute: Int, val label: String, - val triggerTimeMillis: Long, - @ColumnInfo(defaultValue = "") - val date: String? = null + val triggerTimeMillis: Long ) diff --git a/app/src/main/java/com/example/helios_alarm_clock/di/AppModule.kt b/app/src/main/java/com/example/helios_alarm_clock/di/AppModule.kt index 14e33e2..a21afb6 100644 --- a/app/src/main/java/com/example/helios_alarm_clock/di/AppModule.kt +++ b/app/src/main/java/com/example/helios_alarm_clock/di/AppModule.kt @@ -22,9 +22,7 @@ object AppModule { context, AlarmDatabase::class.java, AlarmDatabase.NAME - ) - .addMigrations(AlarmDatabase.MIGRATION_1_2, AlarmDatabase.MIGRATION_2_3) - .build() + ).build() @Provides @Singleton diff --git a/app/src/main/java/com/example/helios_alarm_clock/service/AlarmRingService.kt b/app/src/main/java/com/example/helios_alarm_clock/service/AlarmRingService.kt index f56cb60..8c4bce4 100644 --- a/app/src/main/java/com/example/helios_alarm_clock/service/AlarmRingService.kt +++ b/app/src/main/java/com/example/helios_alarm_clock/service/AlarmRingService.kt @@ -22,7 +22,6 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancel import kotlinx.coroutines.launch -import java.util.Calendar import javax.inject.Inject @AndroidEntryPoint @@ -56,17 +55,13 @@ class AlarmRingService : Service() { startSound() - // Save as last alarm and delete from the database + // Delete the fired alarm from the database if (alarmId.isNotEmpty()) { scope.launch { try { - val alarm = alarmDao.getById(alarmId) - if (alarm != null) { - saveLastAlarm(alarm.hour, alarm.minute, alarm.label) - } alarmDao.deleteById(alarmId) } catch (e: Exception) { - Log.e(TAG, "Failed to process alarm $alarmId", e) + Log.e(TAG, "Failed to delete alarm $alarmId", e) } } } @@ -147,17 +142,6 @@ class AlarmRingService : Service() { .build() } - private fun saveLastAlarm(hour: Int, minute: Int, label: String) { - val prefs = getSharedPreferences("last_alarm", Context.MODE_PRIVATE) - val now = Calendar.getInstance() - prefs.edit() - .putInt("hour", hour) - .putInt("minute", minute) - .putString("label", label) - .putLong("firedAt", now.timeInMillis) - .apply() - } - companion object { private const val TAG = "AlarmRingService" const val NOTIFICATION_ID = 2 diff --git a/app/src/main/java/com/example/helios_alarm_clock/service/KtorService.kt b/app/src/main/java/com/example/helios_alarm_clock/service/KtorService.kt index 28c830d..5cebd75 100644 --- a/app/src/main/java/com/example/helios_alarm_clock/service/KtorService.kt +++ b/app/src/main/java/com/example/helios_alarm_clock/service/KtorService.kt @@ -36,9 +36,6 @@ import kotlinx.coroutines.cancel import kotlinx.coroutines.launch import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json -import java.time.LocalDate -import java.time.format.DateTimeFormatter -import java.time.format.DateTimeParseException import java.util.Calendar import java.util.UUID import javax.inject.Inject @@ -129,40 +126,13 @@ class KtorService : Service() { } val id = UUID.randomUUID().toString() - // Parse date outside apply block so return@post works at the correct scope - val parsedDate: LocalDate? = if (req.date != null) { - try { - LocalDate.parse(req.date, DateTimeFormatter.ISO_LOCAL_DATE) - } catch (e: DateTimeParseException) { - call.respond( - HttpStatusCode.BadRequest, - ErrorResponse("date must be in YYYY-MM-DD format (e.g. 2026-10-08)") - ) - return@post - } - } else null - val now = Calendar.getInstance() val trigger = Calendar.getInstance().apply { set(Calendar.HOUR_OF_DAY, req.hour) set(Calendar.MINUTE, req.minute) set(Calendar.SECOND, 0) set(Calendar.MILLISECOND, 0) - if (parsedDate != null) { - set(Calendar.YEAR, parsedDate.year) - set(Calendar.MONTH, parsedDate.monthValue - 1) - set(Calendar.DAY_OF_MONTH, parsedDate.dayOfMonth) - } else { - if (before(now)) add(Calendar.DAY_OF_YEAR, 1) - } - } - - if (parsedDate != null && trigger.before(now)) { - call.respond( - HttpStatusCode.BadRequest, - ErrorResponse("Alarm time is in the past") - ) - return@post + if (before(now)) add(Calendar.DAY_OF_YEAR, 1) } val entity = AlarmEntity( @@ -170,8 +140,7 @@ class KtorService : Service() { hour = req.hour, minute = req.minute, label = req.label, - triggerTimeMillis = trigger.timeInMillis, - date = req.date + triggerTimeMillis = trigger.timeInMillis ) alarmDao.insert(entity) @@ -212,29 +181,6 @@ class KtorService : Service() { val alarms = alarmDao.getAll() call.respond(alarms) } - - get("/last-alarm") { - val prefs = this@KtorService.getSharedPreferences( - "last_alarm", - Context.MODE_PRIVATE - ) - val firedAt = prefs.getLong("firedAt", -1) - if (firedAt == -1L) { - call.respond( - HttpStatusCode.NotFound, - ErrorResponse("No alarm has fired yet") - ) - } else { - call.respond( - LastAlarmResponse( - hour = prefs.getInt("hour", 0), - minute = prefs.getInt("minute", 0), - label = prefs.getString("label", "") ?: "", - firedAt = firedAt - ) - ) - } - } } }.also { it.start(wait = false) } } @@ -272,7 +218,7 @@ class KtorService : Service() { } @Serializable -data class SetAlarmRequest(val hour: Int, val minute: Int, val label: String = "", val date: String? = null) +data class SetAlarmRequest(val hour: Int, val minute: Int, val label: String = "") @Serializable data class RemoveAlarmRequest(val id: String) @@ -285,6 +231,3 @@ data class StatusResponse(val status: String) @Serializable data class ErrorResponse(val error: String) - -@Serializable -data class LastAlarmResponse(val hour: Int, val minute: Int, val label: String, val firedAt: Long) diff --git a/app/src/main/java/com/example/helios_alarm_clock/ui/MainActivity.kt b/app/src/main/java/com/example/helios_alarm_clock/ui/MainActivity.kt index b2d2104..8abef33 100644 --- a/app/src/main/java/com/example/helios_alarm_clock/ui/MainActivity.kt +++ b/app/src/main/java/com/example/helios_alarm_clock/ui/MainActivity.kt @@ -55,10 +55,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties import androidx.core.content.ContextCompat -import java.time.LocalDate -import java.time.format.DateTimeFormatter import java.util.Calendar -import java.util.Locale import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.example.helios_alarm_clock.data.AlarmEntity @@ -265,17 +262,6 @@ fun AlarmCard( fontWeight = FontWeight.Light, color = MaterialTheme.colorScheme.onSurface ) - if (!alarm.date.isNullOrBlank()) { - val formattedDate = runCatching { - val parsed = LocalDate.parse(alarm.date, DateTimeFormatter.ISO_LOCAL_DATE) - parsed.format(DateTimeFormatter.ofPattern("EEE, dd.MM.yyyy", Locale.GERMAN)) - }.getOrElse { alarm.date } - Text( - text = formattedDate, - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.primary - ) - } if (alarm.label.isNotBlank()) { Text( text = alarm.label, diff --git a/app/src/main/java/com/example/helios_alarm_clock/ui/MainViewModel.kt b/app/src/main/java/com/example/helios_alarm_clock/ui/MainViewModel.kt index 09aab8f..cc870cc 100644 --- a/app/src/main/java/com/example/helios_alarm_clock/ui/MainViewModel.kt +++ b/app/src/main/java/com/example/helios_alarm_clock/ui/MainViewModel.kt @@ -30,7 +30,7 @@ class MainViewModel @Inject constructor( val port: Int = KtorService.PORT - fun createAlarm(hour: Int, minute: Int, label: String, date: String? = null) { + fun createAlarm(hour: Int, minute: Int, label: String) { viewModelScope.launch { val now = Calendar.getInstance() val trigger = Calendar.getInstance().apply { @@ -38,22 +38,14 @@ class MainViewModel @Inject constructor( set(Calendar.MINUTE, minute) set(Calendar.SECOND, 0) set(Calendar.MILLISECOND, 0) - if (date != null) { - val parsedDate = java.time.LocalDate.parse(date) - set(Calendar.YEAR, parsedDate.year) - set(Calendar.MONTH, parsedDate.monthValue - 1) - set(Calendar.DAY_OF_MONTH, parsedDate.dayOfMonth) - } else { - if (before(now)) add(Calendar.DAY_OF_YEAR, 1) - } + if (before(now)) add(Calendar.DAY_OF_YEAR, 1) } val entity = AlarmEntity( id = UUID.randomUUID().toString(), hour = hour, minute = minute, label = label, - triggerTimeMillis = trigger.timeInMillis, - date = date + triggerTimeMillis = trigger.timeInMillis ) alarmDao.insert(entity) alarmScheduler.schedule(entity) diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp index a646846..8c12e6d 100644 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher.webp and b/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp index 68bf370..782c9a4 100644 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp and b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp index a646846..8c12e6d 100644 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp index e0b2e91..023a85f 100644 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher.webp and b/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp index b0a0e0f..ebe4711 100644 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp and b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp index e0b2e91..023a85f 100644 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp index 6b223f3..a151099 100644 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp and b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp index 631ec87..28831bb 100644 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp and b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp index 6b223f3..a151099 100644 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp index a45856e..82c4f75 100644 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp index 939adf5..fd1f2e1 100644 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp index a45856e..82c4f75 100644 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp index 46e9b81..2fca466 100644 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp index 5268dd6..cc0cc3f 100644 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp index 46e9b81..2fca466 100644 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/gradlew b/gradlew old mode 100755 new mode 100644 diff --git a/helios-alarm-app-icon.png b/helios-alarm-app-icon.png index 2047087..2582ae9 100644 Binary files a/helios-alarm-app-icon.png and b/helios-alarm-app-icon.png differ