Auto commit Sat Mar 21 10:01:01 PM CDT 2026
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
package com.mindmachine.mvp.data
|
||||
|
||||
import android.content.Context
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||
import androidx.datastore.preferences.preferencesDataStore
|
||||
import com.mindmachine.mvp.session.TimelineCurve
|
||||
import com.mindmachine.mvp.session.TimelineEditorState
|
||||
import com.mindmachine.mvp.session.TimelinePoint
|
||||
import com.mindmachine.mvp.session.TimelineSelection
|
||||
import com.mindmachine.mvp.session.TimelineViewport
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
|
||||
private val Context.programDataStore by preferencesDataStore("mindmachine_programs")
|
||||
|
||||
class UserProgramRepository(private val context: Context) {
|
||||
private object Keys {
|
||||
val programsJson = stringPreferencesKey("user_programs_json")
|
||||
}
|
||||
|
||||
val userPrograms: Flow<List<UserProgramEntity>> = context.programDataStore.data.map { prefs ->
|
||||
parsePrograms(prefs[Keys.programsJson])
|
||||
}
|
||||
|
||||
suspend fun upsert(program: UserProgramEntity) {
|
||||
context.programDataStore.edit { prefs ->
|
||||
val current = parsePrograms(prefs[Keys.programsJson]).toMutableList()
|
||||
val idx = current.indexOfFirst { it.id == program.id }
|
||||
if (idx >= 0) {
|
||||
current[idx] = program
|
||||
} else {
|
||||
current += program
|
||||
}
|
||||
prefs[Keys.programsJson] = toJson(current)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun delete(id: String) {
|
||||
context.programDataStore.edit { prefs ->
|
||||
val current = parsePrograms(prefs[Keys.programsJson]).filterNot { it.id == id }
|
||||
prefs[Keys.programsJson] = toJson(current)
|
||||
}
|
||||
}
|
||||
|
||||
private fun parsePrograms(raw: String?): List<UserProgramEntity> {
|
||||
if (raw.isNullOrBlank()) return emptyList()
|
||||
return runCatching {
|
||||
val arr = JSONArray(raw)
|
||||
buildList {
|
||||
for (i in 0 until arr.length()) {
|
||||
val o = arr.getJSONObject(i)
|
||||
add(
|
||||
UserProgramEntity(
|
||||
id = o.getString("id"),
|
||||
name = o.getString("name"),
|
||||
description = o.optString("description", "Custom program"),
|
||||
timeline = parseTimeline(o.getJSONObject("timeline")),
|
||||
sortOrder = o.optInt("sortOrder", i + 1000),
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}.getOrDefault(emptyList())
|
||||
}
|
||||
|
||||
private fun parseTimeline(o: JSONObject): TimelineEditorState {
|
||||
fun parseCurve(name: String): TimelineCurve {
|
||||
val points = o.getJSONArray(name)
|
||||
return TimelineCurve(
|
||||
points = buildList {
|
||||
for (i in 0 until points.length()) {
|
||||
val p = points.getJSONObject(i)
|
||||
add(TimelinePoint(tSec = p.getInt("t"), value01 = p.getDouble("v").toFloat()))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
return TimelineEditorState(
|
||||
durationSec = o.getInt("durationSec"),
|
||||
selection = TimelineSelection(
|
||||
startSec = o.optInt("selectionStartSec", 0),
|
||||
endSec = o.optInt("selectionEndSec", o.getInt("durationSec")),
|
||||
),
|
||||
viewport = TimelineViewport(
|
||||
startSec = o.optDouble("viewportStartSec", 0.0).toFloat(),
|
||||
secondsPerScreen = o.optDouble("viewportSecondsPerScreen", 600.0).toFloat(),
|
||||
),
|
||||
activeCurve = TimelineEditorState.ActiveCurve.valueOf(o.optString("activeCurve", TimelineEditorState.ActiveCurve.VISUAL_LEFT.name)),
|
||||
visualLeft = parseCurve("visualLeft"),
|
||||
visualRight = parseCurve("visualRight"),
|
||||
audioLeft = parseCurve("audioLeft"),
|
||||
audioRight = parseCurve("audioRight"),
|
||||
isPlaying = false,
|
||||
playheadSec = 0f,
|
||||
).setDurationSec(o.getInt("durationSec"))
|
||||
}
|
||||
|
||||
private fun toJson(programs: List<UserProgramEntity>): String {
|
||||
val arr = JSONArray()
|
||||
programs.sortedBy { it.sortOrder }.forEach { program ->
|
||||
val o = JSONObject()
|
||||
o.put("id", program.id)
|
||||
o.put("name", program.name)
|
||||
o.put("description", program.description)
|
||||
o.put("sortOrder", program.sortOrder)
|
||||
|
||||
val timeline = JSONObject()
|
||||
timeline.put("durationSec", program.timeline.durationSec)
|
||||
timeline.put("selectionStartSec", program.timeline.selection.startSec)
|
||||
timeline.put("selectionEndSec", program.timeline.selection.endSec)
|
||||
timeline.put("viewportStartSec", program.timeline.viewport.startSec)
|
||||
timeline.put("viewportSecondsPerScreen", program.timeline.viewport.secondsPerScreen)
|
||||
timeline.put("activeCurve", program.timeline.activeCurve.name)
|
||||
timeline.put("visualLeft", pointsArray(program.timeline.visualLeft))
|
||||
timeline.put("visualRight", pointsArray(program.timeline.visualRight))
|
||||
timeline.put("audioLeft", pointsArray(program.timeline.audioLeft))
|
||||
timeline.put("audioRight", pointsArray(program.timeline.audioRight))
|
||||
o.put("timeline", timeline)
|
||||
|
||||
arr.put(o)
|
||||
}
|
||||
return arr.toString()
|
||||
}
|
||||
|
||||
private fun pointsArray(curve: TimelineCurve): JSONArray {
|
||||
val arr = JSONArray()
|
||||
curve.points.forEach { p ->
|
||||
arr.put(JSONObject().put("t", p.tSec).put("v", p.value01.toDouble()))
|
||||
}
|
||||
return arr
|
||||
}
|
||||
}
|
||||
|
||||
data class UserProgramEntity(
|
||||
val id: String,
|
||||
val name: String,
|
||||
val description: String,
|
||||
val timeline: TimelineEditorState,
|
||||
val sortOrder: Int,
|
||||
)
|
||||
Reference in New Issue
Block a user