feat: будильник с радиостанцией + выбор битрейта по умолчанию

Будильник (Settings → Будильник): несколько будильников, время, станция, дни недели,
fade-in пробуждения. AlarmManager.setAlarmClock (вне doze) + фолбэк, BootReceiver
перепланирует после перезагрузки, AlarmReceiver→PlayerService (foreground) →
PlayerController.startAlarmPlayback (нарастание громкости). Room: AlarmEntity/Dao, БД v7.
Выбор битрейта по умолчанию в Settings (Авто/Эконом/Стандарт/Высокое) → preferredBitrate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
nk
2026-06-06 15:25:42 +03:00
parent 4411d53a6c
commit 861b0e2b8f
17 changed files with 1014 additions and 3 deletions

View File

@@ -4,10 +4,12 @@ import androidx.room.Database
import androidx.room.RoomDatabase
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import com.radiola.data.local.dao.AlarmDao
import com.radiola.data.local.dao.RecordingDao
import com.radiola.data.local.dao.StationDao
import com.radiola.data.local.dao.TagDao
import com.radiola.data.local.dao.TrackHistoryDao
import com.radiola.data.local.entity.AlarmEntity
import com.radiola.data.local.entity.RecordingEntity
import com.radiola.data.local.entity.StationEntity
import com.radiola.data.local.entity.TagEntity
@@ -56,13 +58,34 @@ val MIGRATION_5_6 = object : Migration(5, 6) {
}
}
// Добавляем таблицу будильников
val MIGRATION_6_7 = object : Migration(6, 7) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS alarms (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
hour INTEGER NOT NULL,
minute INTEGER NOT NULL,
daysMask INTEGER NOT NULL,
stationId INTEGER NOT NULL,
stationName TEXT NOT NULL,
enabled INTEGER NOT NULL,
fadeInSec INTEGER NOT NULL
)
""".trimIndent()
)
}
}
@Database(
entities = [StationEntity::class, TrackHistoryEntity::class, TagEntity::class, RecordingEntity::class],
version = 6
entities = [StationEntity::class, TrackHistoryEntity::class, TagEntity::class, RecordingEntity::class, AlarmEntity::class],
version = 7
)
abstract class AppDatabase : RoomDatabase() {
abstract fun stationDao(): StationDao
abstract fun trackHistoryDao(): TrackHistoryDao
abstract fun tagDao(): TagDao
abstract fun recordingDao(): RecordingDao
abstract fun alarmDao(): AlarmDao
}

View File

@@ -0,0 +1,30 @@
package com.radiola.data.local.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import com.radiola.data.local.entity.AlarmEntity
import kotlinx.coroutines.flow.Flow
@Dao
interface AlarmDao {
@Query("SELECT * FROM alarms ORDER BY hour ASC, minute ASC")
fun getAll(): Flow<List<AlarmEntity>>
@Query("SELECT * FROM alarms ORDER BY hour ASC, minute ASC")
suspend fun getAllOnce(): List<AlarmEntity>
@Query("SELECT * FROM alarms WHERE id = :id")
suspend fun getById(id: Int): AlarmEntity?
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun upsert(alarm: AlarmEntity): Long
@Query("DELETE FROM alarms WHERE id = :id")
suspend fun delete(id: Int)
@Query("UPDATE alarms SET enabled = :enabled WHERE id = :id")
suspend fun setEnabled(id: Int, enabled: Boolean)
}

View File

@@ -42,4 +42,8 @@ interface StationDao {
// не потерять отметки «избранное» (insertAll = REPLACE затирает строки).
@Query("SELECT id FROM stations WHERE isFavorite = 1")
suspend fun getFavoriteIdsOnce(): List<Int>
// Разовое чтение станции по id — используется в сервисе будильника.
@Query("SELECT * FROM stations WHERE id = :id")
suspend fun getByIdOnce(id: Int): StationEntity?
}

View File

@@ -0,0 +1,20 @@
package com.radiola.data.local.entity
import androidx.room.Entity
import androidx.room.PrimaryKey
/**
* Будильник: время, дни недели, станция, fade-in.
* daysMask — битовая маска Пн..Вс (биты 0..6); 0 = разовый (следующее совпадение).
*/
@Entity(tableName = "alarms")
data class AlarmEntity(
@PrimaryKey(autoGenerate = true) val id: Int = 0,
val hour: Int,
val minute: Int,
val daysMask: Int, // 0 = разовый; бит 0=Пн, ..., бит 6=Вс
val stationId: Int,
val stationName: String,
val enabled: Boolean = true,
val fadeInSec: Int = 60
)