feat: add Room entities, DAOs, database, and DataStore preferences
Made-with: Cursor
This commit is contained in:
@@ -5,6 +5,10 @@ plugins {
|
|||||||
alias(libs.plugins.ksp)
|
alias(libs.plugins.ksp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ksp {
|
||||||
|
arg("room.schemaLocation", "$projectDir/schemas")
|
||||||
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "xyz.cottongin.radio247"
|
namespace = "xyz.cottongin.radio247"
|
||||||
compileSdk = 35
|
compileSdk = 35
|
||||||
|
|||||||
321
app/schemas/xyz.cottongin.radio247.data.db.RadioDatabase/1.json
Normal file
321
app/schemas/xyz.cottongin.radio247.data.db.RadioDatabase/1.json
Normal file
@@ -0,0 +1,321 @@
|
|||||||
|
{
|
||||||
|
"formatVersion": 1,
|
||||||
|
"database": {
|
||||||
|
"version": 1,
|
||||||
|
"identityHash": "2959b46abce28a2c49ca387873ac9c0d",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"tableName": "stations",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `playlistId` INTEGER, `sortOrder` INTEGER NOT NULL, `starred` INTEGER NOT NULL, `defaultArtworkUrl` TEXT, FOREIGN KEY(`playlistId`) REFERENCES `playlists`(`id`) ON UPDATE NO ACTION ON DELETE SET NULL )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "name",
|
||||||
|
"columnName": "name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "url",
|
||||||
|
"columnName": "url",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "playlistId",
|
||||||
|
"columnName": "playlistId",
|
||||||
|
"affinity": "INTEGER"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "sortOrder",
|
||||||
|
"columnName": "sortOrder",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "starred",
|
||||||
|
"columnName": "starred",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "defaultArtworkUrl",
|
||||||
|
"columnName": "defaultArtworkUrl",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_stations_playlistId",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"playlistId"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_stations_playlistId` ON `${TABLE_NAME}` (`playlistId`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "playlists",
|
||||||
|
"onDelete": "SET NULL",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"playlistId"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "playlists",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `sortOrder` INTEGER NOT NULL, `starred` INTEGER NOT NULL)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "name",
|
||||||
|
"columnName": "name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "sortOrder",
|
||||||
|
"columnName": "sortOrder",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "starred",
|
||||||
|
"columnName": "starred",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "metadata_snapshots",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `stationId` INTEGER NOT NULL, `title` TEXT, `artist` TEXT, `artworkUrl` TEXT, `timestamp` INTEGER NOT NULL, FOREIGN KEY(`stationId`) REFERENCES `stations`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "stationId",
|
||||||
|
"columnName": "stationId",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "title",
|
||||||
|
"columnName": "title",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "artist",
|
||||||
|
"columnName": "artist",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "artworkUrl",
|
||||||
|
"columnName": "artworkUrl",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "timestamp",
|
||||||
|
"columnName": "timestamp",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_metadata_snapshots_stationId",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"stationId"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_metadata_snapshots_stationId` ON `${TABLE_NAME}` (`stationId`)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "index_metadata_snapshots_timestamp",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"timestamp"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_metadata_snapshots_timestamp` ON `${TABLE_NAME}` (`timestamp`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "stations",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"stationId"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "listening_sessions",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `stationId` INTEGER NOT NULL, `startedAt` INTEGER NOT NULL, `endedAt` INTEGER, FOREIGN KEY(`stationId`) REFERENCES `stations`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "stationId",
|
||||||
|
"columnName": "stationId",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "startedAt",
|
||||||
|
"columnName": "startedAt",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "endedAt",
|
||||||
|
"columnName": "endedAt",
|
||||||
|
"affinity": "INTEGER"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_listening_sessions_stationId",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"stationId"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_listening_sessions_stationId` ON `${TABLE_NAME}` (`stationId`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "stations",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"stationId"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "connection_spans",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `sessionId` INTEGER NOT NULL, `startedAt` INTEGER NOT NULL, `endedAt` INTEGER, FOREIGN KEY(`sessionId`) REFERENCES `listening_sessions`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "sessionId",
|
||||||
|
"columnName": "sessionId",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "startedAt",
|
||||||
|
"columnName": "startedAt",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "endedAt",
|
||||||
|
"columnName": "endedAt",
|
||||||
|
"affinity": "INTEGER"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_connection_spans_sessionId",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"sessionId"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_connection_spans_sessionId` ON `${TABLE_NAME}` (`sessionId`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "listening_sessions",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"sessionId"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"setupQueries": [
|
||||||
|
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||||
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '2959b46abce28a2c49ca387873ac9c0d')"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,17 @@
|
|||||||
package xyz.cottongin.radio247
|
package xyz.cottongin.radio247
|
||||||
|
|
||||||
import android.app.Application
|
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