feat: add Room entities, DAOs, database, and DataStore preferences
Made-with: Cursor
This commit is contained in:
@@ -1,5 +1,17 @@
|
||||
package xyz.cottongin.radio247
|
||||
|
||||
import android.app.Application
|
||||
import androidx.room.Room
|
||||
import xyz.cottongin.radio247.data.db.RadioDatabase
|
||||
import xyz.cottongin.radio247.data.prefs.RadioPreferences
|
||||
|
||||
class RadioApplication : Application()
|
||||
class RadioApplication : Application() {
|
||||
val database: RadioDatabase by lazy {
|
||||
Room.databaseBuilder(this, RadioDatabase::class.java, "radio_database")
|
||||
.build()
|
||||
}
|
||||
|
||||
val preferences: RadioPreferences by lazy {
|
||||
RadioPreferences(this)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package xyz.cottongin.radio247.data.db
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.Query
|
||||
import xyz.cottongin.radio247.data.model.ConnectionSpan
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
interface ConnectionSpanDao {
|
||||
@Insert
|
||||
suspend fun insert(span: ConnectionSpan): Long
|
||||
|
||||
@Query("UPDATE connection_spans SET endedAt = :endedAt WHERE id = :id")
|
||||
suspend fun updateEndedAt(id: Long, endedAt: Long)
|
||||
|
||||
@Query("SELECT * FROM connection_spans WHERE sessionId = :sessionId ORDER BY startedAt DESC")
|
||||
fun getBySession(sessionId: Long): Flow<List<ConnectionSpan>>
|
||||
|
||||
@Query("SELECT * FROM connection_spans WHERE endedAt IS NULL LIMIT 1")
|
||||
suspend fun getActiveSpan(): ConnectionSpan?
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package xyz.cottongin.radio247.data.db
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.Query
|
||||
import xyz.cottongin.radio247.data.model.ListeningSession
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
interface ListeningSessionDao {
|
||||
@Insert
|
||||
suspend fun insert(session: ListeningSession): Long
|
||||
|
||||
@Query("UPDATE listening_sessions SET endedAt = :endedAt WHERE id = :id")
|
||||
suspend fun updateEndedAt(id: Long, endedAt: Long)
|
||||
|
||||
@Query("SELECT * FROM listening_sessions WHERE endedAt IS NULL LIMIT 1")
|
||||
suspend fun getActiveSession(): ListeningSession?
|
||||
|
||||
@Query("SELECT * FROM listening_sessions ORDER BY startedAt DESC LIMIT :limit")
|
||||
fun getRecentSessions(limit: Int = 50): Flow<List<ListeningSession>>
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package xyz.cottongin.radio247.data.db
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.Query
|
||||
import xyz.cottongin.radio247.data.model.MetadataSnapshot
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
interface MetadataSnapshotDao {
|
||||
@Insert
|
||||
suspend fun insert(snapshot: MetadataSnapshot): Long
|
||||
|
||||
@Query("SELECT * FROM metadata_snapshots WHERE stationId = :stationId ORDER BY timestamp DESC LIMIT :limit")
|
||||
fun getByStation(stationId: Long, limit: Int = 50): Flow<List<MetadataSnapshot>>
|
||||
|
||||
@Query("SELECT * FROM metadata_snapshots ORDER BY timestamp DESC LIMIT :limit")
|
||||
fun getRecent(limit: Int = 100): Flow<List<MetadataSnapshot>>
|
||||
|
||||
@Query("SELECT * FROM metadata_snapshots WHERE artist LIKE '%' || :query || '%' OR title LIKE '%' || :query || '%' ORDER BY timestamp DESC")
|
||||
fun search(query: String): Flow<List<MetadataSnapshot>>
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package xyz.cottongin.radio247.data.db
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Delete
|
||||
import androidx.room.Insert
|
||||
import androidx.room.Query
|
||||
import androidx.room.Update
|
||||
import xyz.cottongin.radio247.data.model.Playlist
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
interface PlaylistDao {
|
||||
@Query("SELECT * FROM playlists ORDER BY starred DESC, sortOrder ASC")
|
||||
fun getAllPlaylists(): Flow<List<Playlist>>
|
||||
|
||||
@Query("SELECT * FROM playlists WHERE id = :id")
|
||||
suspend fun getPlaylistById(id: Long): Playlist?
|
||||
|
||||
@Insert
|
||||
suspend fun insert(playlist: Playlist): Long
|
||||
|
||||
@Update
|
||||
suspend fun update(playlist: Playlist)
|
||||
|
||||
@Delete
|
||||
suspend fun delete(playlist: Playlist)
|
||||
|
||||
@Query("UPDATE playlists SET sortOrder = :sortOrder WHERE id = :id")
|
||||
suspend fun updateSortOrder(id: Long, sortOrder: Int)
|
||||
|
||||
@Query("UPDATE playlists SET starred = :starred WHERE id = :id")
|
||||
suspend fun toggleStarred(id: Long, starred: Boolean)
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package xyz.cottongin.radio247.data.db
|
||||
|
||||
import androidx.room.Database
|
||||
import androidx.room.RoomDatabase
|
||||
import xyz.cottongin.radio247.data.model.ConnectionSpan
|
||||
import xyz.cottongin.radio247.data.model.ListeningSession
|
||||
import xyz.cottongin.radio247.data.model.MetadataSnapshot
|
||||
import xyz.cottongin.radio247.data.model.Playlist
|
||||
import xyz.cottongin.radio247.data.model.Station
|
||||
|
||||
@Database(
|
||||
entities = [
|
||||
Station::class,
|
||||
Playlist::class,
|
||||
MetadataSnapshot::class,
|
||||
ListeningSession::class,
|
||||
ConnectionSpan::class
|
||||
],
|
||||
version = 1,
|
||||
exportSchema = true
|
||||
)
|
||||
abstract class RadioDatabase : RoomDatabase() {
|
||||
abstract fun stationDao(): StationDao
|
||||
abstract fun playlistDao(): PlaylistDao
|
||||
abstract fun metadataSnapshotDao(): MetadataSnapshotDao
|
||||
abstract fun listeningSessionDao(): ListeningSessionDao
|
||||
abstract fun connectionSpanDao(): ConnectionSpanDao
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package xyz.cottongin.radio247.data.db
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Delete
|
||||
import androidx.room.Insert
|
||||
import androidx.room.Query
|
||||
import androidx.room.Update
|
||||
import xyz.cottongin.radio247.data.model.Station
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
interface StationDao {
|
||||
@Query("SELECT * FROM stations ORDER BY starred DESC, sortOrder ASC")
|
||||
fun getAllStations(): Flow<List<Station>>
|
||||
|
||||
@Query("SELECT * FROM stations WHERE playlistId = :playlistId ORDER BY starred DESC, sortOrder ASC")
|
||||
fun getStationsByPlaylist(playlistId: Long): Flow<List<Station>>
|
||||
|
||||
@Query("SELECT * FROM stations WHERE playlistId IS NULL ORDER BY starred DESC, sortOrder ASC")
|
||||
fun getUnsortedStations(): Flow<List<Station>>
|
||||
|
||||
@Query("SELECT * FROM stations WHERE id = :id")
|
||||
suspend fun getStationById(id: Long): Station?
|
||||
|
||||
@Insert
|
||||
suspend fun insert(station: Station): Long
|
||||
|
||||
@Update
|
||||
suspend fun update(station: Station)
|
||||
|
||||
@Delete
|
||||
suspend fun delete(station: Station)
|
||||
|
||||
@Query("UPDATE stations SET sortOrder = :sortOrder WHERE id = :id")
|
||||
suspend fun updateSortOrder(id: Long, sortOrder: Int)
|
||||
|
||||
@Query("UPDATE stations SET starred = :starred WHERE id = :id")
|
||||
suspend fun toggleStarred(id: Long, starred: Boolean)
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package xyz.cottongin.radio247.data.model
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.ForeignKey
|
||||
import androidx.room.Index
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
@Entity(
|
||||
tableName = "connection_spans",
|
||||
foreignKeys = [ForeignKey(
|
||||
entity = ListeningSession::class,
|
||||
parentColumns = ["id"],
|
||||
childColumns = ["sessionId"],
|
||||
onDelete = ForeignKey.CASCADE
|
||||
)],
|
||||
indices = [Index("sessionId")]
|
||||
)
|
||||
data class ConnectionSpan(
|
||||
@PrimaryKey(autoGenerate = true) val id: Long = 0,
|
||||
val sessionId: Long,
|
||||
val startedAt: Long,
|
||||
val endedAt: Long? = null
|
||||
)
|
||||
@@ -0,0 +1,23 @@
|
||||
package xyz.cottongin.radio247.data.model
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.ForeignKey
|
||||
import androidx.room.Index
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
@Entity(
|
||||
tableName = "listening_sessions",
|
||||
foreignKeys = [ForeignKey(
|
||||
entity = Station::class,
|
||||
parentColumns = ["id"],
|
||||
childColumns = ["stationId"],
|
||||
onDelete = ForeignKey.CASCADE
|
||||
)],
|
||||
indices = [Index("stationId")]
|
||||
)
|
||||
data class ListeningSession(
|
||||
@PrimaryKey(autoGenerate = true) val id: Long = 0,
|
||||
val stationId: Long,
|
||||
val startedAt: Long,
|
||||
val endedAt: Long? = null
|
||||
)
|
||||
@@ -0,0 +1,25 @@
|
||||
package xyz.cottongin.radio247.data.model
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.ForeignKey
|
||||
import androidx.room.Index
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
@Entity(
|
||||
tableName = "metadata_snapshots",
|
||||
foreignKeys = [ForeignKey(
|
||||
entity = Station::class,
|
||||
parentColumns = ["id"],
|
||||
childColumns = ["stationId"],
|
||||
onDelete = ForeignKey.CASCADE
|
||||
)],
|
||||
indices = [Index("stationId"), Index("timestamp")]
|
||||
)
|
||||
data class MetadataSnapshot(
|
||||
@PrimaryKey(autoGenerate = true) val id: Long = 0,
|
||||
val stationId: Long,
|
||||
val title: String? = null,
|
||||
val artist: String? = null,
|
||||
val artworkUrl: String? = null,
|
||||
val timestamp: Long
|
||||
)
|
||||
@@ -0,0 +1,12 @@
|
||||
package xyz.cottongin.radio247.data.model
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
@Entity(tableName = "playlists")
|
||||
data class Playlist(
|
||||
@PrimaryKey(autoGenerate = true) val id: Long = 0,
|
||||
val name: String,
|
||||
val sortOrder: Int = 0,
|
||||
val starred: Boolean = false
|
||||
)
|
||||
@@ -0,0 +1,26 @@
|
||||
package xyz.cottongin.radio247.data.model
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.ForeignKey
|
||||
import androidx.room.Index
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
@Entity(
|
||||
tableName = "stations",
|
||||
foreignKeys = [ForeignKey(
|
||||
entity = Playlist::class,
|
||||
parentColumns = ["id"],
|
||||
childColumns = ["playlistId"],
|
||||
onDelete = ForeignKey.SET_NULL
|
||||
)],
|
||||
indices = [Index("playlistId")]
|
||||
)
|
||||
data class Station(
|
||||
@PrimaryKey(autoGenerate = true) val id: Long = 0,
|
||||
val name: String,
|
||||
val url: String,
|
||||
val playlistId: Long? = null,
|
||||
val sortOrder: Int = 0,
|
||||
val starred: Boolean = false,
|
||||
val defaultArtworkUrl: String? = null
|
||||
)
|
||||
@@ -0,0 +1,37 @@
|
||||
package xyz.cottongin.radio247.data.prefs
|
||||
|
||||
import android.content.Context
|
||||
import androidx.datastore.preferences.core.booleanPreferencesKey
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import androidx.datastore.preferences.core.intPreferencesKey
|
||||
import androidx.datastore.preferences.core.longPreferencesKey
|
||||
import androidx.datastore.preferences.preferencesDataStore
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
class RadioPreferences(private val context: Context) {
|
||||
private val dataStore = context.dataStore
|
||||
|
||||
val stayConnected: Flow<Boolean> = dataStore.data.map { it[STAY_CONNECTED] ?: false }
|
||||
val bufferMs: Flow<Int> = dataStore.data.map { it[BUFFER_MS] ?: 0 }
|
||||
val lastStationId: Flow<Long?> = dataStore.data.map { it[LAST_STATION_ID] }
|
||||
|
||||
suspend fun setStayConnected(value: Boolean) {
|
||||
dataStore.edit { it[STAY_CONNECTED] = value }
|
||||
}
|
||||
|
||||
suspend fun setBufferMs(value: Int) {
|
||||
dataStore.edit { it[BUFFER_MS] = value }
|
||||
}
|
||||
|
||||
suspend fun setLastStationId(value: Long) {
|
||||
dataStore.edit { it[LAST_STATION_ID] = value }
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val Context.dataStore by preferencesDataStore(name = "radio_prefs")
|
||||
private val STAY_CONNECTED = booleanPreferencesKey("stay_connected")
|
||||
private val BUFFER_MS = intPreferencesKey("buffer_ms")
|
||||
private val LAST_STATION_ID = longPreferencesKey("last_station_id")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user