@@ -2,6 +2,7 @@ package com.raulshma.lenscast.data
22
33import android.content.Context
44import android.util.Base64
5+ import android.util.Log
56import androidx.datastore.core.DataStore
67import androidx.datastore.preferences.core.Preferences
78import androidx.datastore.preferences.core.doublePreferencesKey
@@ -14,11 +15,18 @@ import androidx.datastore.preferences.preferencesDataStore
1415import com.raulshma.lenscast.camera.model.CameraSettings
1516import com.raulshma.lenscast.camera.model.FocusMode
1617import com.raulshma.lenscast.camera.model.HdrMode
18+ import com.raulshma.lenscast.camera.model.NightVisionMode
19+ import com.raulshma.lenscast.camera.model.MaskingType
20+ import com.raulshma.lenscast.camera.model.MaskingZone
21+ import com.raulshma.lenscast.camera.model.OverlayPosition
22+ import com.raulshma.lenscast.camera.model.OverlaySettings
1723import com.raulshma.lenscast.camera.model.Resolution
1824import com.raulshma.lenscast.camera.model.WhiteBalance
1925import com.raulshma.lenscast.streaming.rtsp.RtspInputFormat
2026import kotlinx.coroutines.flow.Flow
2127import kotlinx.coroutines.flow.map
28+ import org.json.JSONArray
29+ import org.json.JSONObject
2230import java.security.MessageDigest
2331import java.security.SecureRandom
2432import javax.crypto.SecretKeyFactory
@@ -139,6 +147,23 @@ class SettingsDataStore(private val context: Context) {
139147 val RTSP_INPUT_FORMAT = stringPreferencesKey(" rtsp_input_format" )
140148 val ADAPTIVE_BITRATE_ENABLED = stringPreferencesKey(" adaptive_bitrate_enabled" )
141149 val MDNS_ENABLED = stringPreferencesKey(" mdns_enabled" )
150+ val NIGHT_VISION_MODE = stringPreferencesKey(" night_vision_mode" )
151+ val OVERLAY_ENABLED = stringPreferencesKey(" overlay_enabled" )
152+ val OVERLAY_SHOW_TIMESTAMP = stringPreferencesKey(" overlay_show_timestamp" )
153+ val OVERLAY_TIMESTAMP_FORMAT = stringPreferencesKey(" overlay_timestamp_format" )
154+ val OVERLAY_SHOW_BRANDING = stringPreferencesKey(" overlay_show_branding" )
155+ val OVERLAY_BRANDING_TEXT = stringPreferencesKey(" overlay_branding_text" )
156+ val OVERLAY_SHOW_STATUS = stringPreferencesKey(" overlay_show_status" )
157+ val OVERLAY_SHOW_CUSTOM_TEXT = stringPreferencesKey(" overlay_show_custom_text" )
158+ val OVERLAY_CUSTOM_TEXT = stringPreferencesKey(" overlay_custom_text" )
159+ val OVERLAY_POSITION = stringPreferencesKey(" overlay_position" )
160+ val OVERLAY_FONT_SIZE = intPreferencesKey(" overlay_font_size" )
161+ val OVERLAY_TEXT_COLOR = stringPreferencesKey(" overlay_text_color" )
162+ val OVERLAY_BG_COLOR = stringPreferencesKey(" overlay_bg_color" )
163+ val OVERLAY_PADDING = intPreferencesKey(" overlay_padding" )
164+ val OVERLAY_LINE_HEIGHT = intPreferencesKey(" overlay_line_height" )
165+ val MASKING_ENABLED = stringPreferencesKey(" masking_enabled" )
166+ val MASKING_ZONES = stringPreferencesKey(" masking_zones" )
142167 }
143168
144169 val settings: Flow <CameraSettings > = context.dataStore.data.map { prefs ->
@@ -172,6 +197,11 @@ class SettingsDataStore(private val context: Context) {
172197 HdrMode .OFF
173198 },
174199 sceneMode = prefs[Keys .SCENE_MODE ],
200+ nightVisionMode = try {
201+ NightVisionMode .valueOf(prefs[Keys .NIGHT_VISION_MODE ] ? : NightVisionMode .OFF .name)
202+ } catch (_: Exception ) {
203+ NightVisionMode .OFF
204+ },
175205 )
176206 }
177207
@@ -276,6 +306,7 @@ class SettingsDataStore(private val context: Context) {
276306 } else {
277307 prefs.remove(Keys .SCENE_MODE )
278308 }
309+ prefs[Keys .NIGHT_VISION_MODE ] = settings.nightVisionMode.name
279310 }
280311 }
281312
@@ -385,4 +416,118 @@ class SettingsDataStore(private val context: Context) {
385416 prefs[Keys .MDNS_ENABLED ] = if (enabled) " true" else " false"
386417 }
387418 }
419+
420+ val nightVisionMode: Flow <NightVisionMode > = context.dataStore.data.map { prefs ->
421+ val raw = prefs[Keys .NIGHT_VISION_MODE ] ? : NightVisionMode .OFF .name
422+ try {
423+ NightVisionMode .valueOf(raw)
424+ } catch (_: Exception ) {
425+ NightVisionMode .OFF
426+ }
427+ }
428+
429+ suspend fun saveNightVisionMode (mode : NightVisionMode ) {
430+ context.dataStore.edit { prefs ->
431+ prefs[Keys .NIGHT_VISION_MODE ] = mode.name
432+ }
433+ }
434+
435+ val overlaySettings: Flow <OverlaySettings > = context.dataStore.data.map { prefs ->
436+ OverlaySettings (
437+ enabled = prefs[Keys .OVERLAY_ENABLED ] == " true" ,
438+ showTimestamp = prefs[Keys .OVERLAY_SHOW_TIMESTAMP ] != " false" ,
439+ timestampFormat = prefs[Keys .OVERLAY_TIMESTAMP_FORMAT ] ? : " yyyy-MM-dd HH:mm:ss" ,
440+ showBranding = prefs[Keys .OVERLAY_SHOW_BRANDING ] == " true" ,
441+ brandingText = prefs[Keys .OVERLAY_BRANDING_TEXT ] ? : " LensCast" ,
442+ showStatus = prefs[Keys .OVERLAY_SHOW_STATUS ] == " true" ,
443+ showCustomText = prefs[Keys .OVERLAY_SHOW_CUSTOM_TEXT ] == " true" ,
444+ customText = prefs[Keys .OVERLAY_CUSTOM_TEXT ] ? : " " ,
445+ position = try {
446+ OverlayPosition .valueOf(prefs[Keys .OVERLAY_POSITION ] ? : OverlayPosition .TOP_LEFT .name)
447+ } catch (_: Exception ) {
448+ OverlayPosition .TOP_LEFT
449+ },
450+ fontSize = prefs[Keys .OVERLAY_FONT_SIZE ] ? : 28 ,
451+ textColor = prefs[Keys .OVERLAY_TEXT_COLOR ] ? : " #FFFFFF" ,
452+ backgroundColor = prefs[Keys .OVERLAY_BG_COLOR ] ? : " #80000000" ,
453+ padding = prefs[Keys .OVERLAY_PADDING ] ? : 8 ,
454+ lineHeight = prefs[Keys .OVERLAY_LINE_HEIGHT ] ? : 4 ,
455+ maskingEnabled = prefs[Keys .MASKING_ENABLED ] == " true" ,
456+ maskingZones = parseMaskingZones(prefs[Keys .MASKING_ZONES ]),
457+ )
458+ }
459+
460+ suspend fun saveOverlaySettings (settings : OverlaySettings ) {
461+ context.dataStore.edit { prefs ->
462+ prefs[Keys .OVERLAY_ENABLED ] = if (settings.enabled) " true" else " false"
463+ prefs[Keys .OVERLAY_SHOW_TIMESTAMP ] = if (settings.showTimestamp) " true" else " false"
464+ prefs[Keys .OVERLAY_TIMESTAMP_FORMAT ] = settings.timestampFormat
465+ prefs[Keys .OVERLAY_SHOW_BRANDING ] = if (settings.showBranding) " true" else " false"
466+ prefs[Keys .OVERLAY_BRANDING_TEXT ] = settings.brandingText
467+ prefs[Keys .OVERLAY_SHOW_STATUS ] = if (settings.showStatus) " true" else " false"
468+ prefs[Keys .OVERLAY_SHOW_CUSTOM_TEXT ] = if (settings.showCustomText) " true" else " false"
469+ prefs[Keys .OVERLAY_CUSTOM_TEXT ] = settings.customText
470+ prefs[Keys .OVERLAY_POSITION ] = settings.position.name
471+ prefs[Keys .OVERLAY_FONT_SIZE ] = settings.fontSize
472+ prefs[Keys .OVERLAY_TEXT_COLOR ] = settings.textColor
473+ prefs[Keys .OVERLAY_BG_COLOR ] = settings.backgroundColor
474+ prefs[Keys .OVERLAY_PADDING ] = settings.padding
475+ prefs[Keys .OVERLAY_LINE_HEIGHT ] = settings.lineHeight
476+ prefs[Keys .MASKING_ENABLED ] = if (settings.maskingEnabled) " true" else " false"
477+ prefs[Keys .MASKING_ZONES ] = serializeMaskingZones(settings.maskingZones)
478+ }
479+ }
480+
481+ private fun parseMaskingZones (jsonString : String? ): List <MaskingZone > {
482+ if (jsonString.isNullOrEmpty()) return emptyList()
483+ return try {
484+ val array = JSONArray (jsonString)
485+ List (array.length()) { i ->
486+ val obj = array.getJSONObject(i)
487+ MaskingZone (
488+ id = obj.optString(" id" , java.util.UUID .randomUUID().toString()),
489+ label = obj.optString(" label" , " " ),
490+ enabled = obj.optBoolean(" enabled" , true ),
491+ type = try {
492+ MaskingType .valueOf(obj.optString(" type" , MaskingType .BLACKOUT .name))
493+ } catch (_: Exception ) {
494+ MaskingType .BLACKOUT
495+ },
496+ x = obj.optDouble(" x" , 0.0 ).toFloat(),
497+ y = obj.optDouble(" y" , 0.0 ).toFloat(),
498+ width = obj.optDouble(" width" , 0.2 ).toFloat(),
499+ height = obj.optDouble(" height" , 0.2 ).toFloat(),
500+ pixelateSize = obj.optInt(" pixelateSize" , 16 ),
501+ blurRadius = obj.optDouble(" blurRadius" , 10.0 ).toFloat(),
502+ )
503+ }
504+ } catch (e: Exception ) {
505+ Log .e(" SettingsDataStore" , " Failed to parse masking zones" , e)
506+ emptyList()
507+ }
508+ }
509+
510+ private fun serializeMaskingZones (zones : List <MaskingZone >): String {
511+ return try {
512+ val array = JSONArray ()
513+ for (zone in zones) {
514+ val obj = JSONObject ()
515+ obj.put(" id" , zone.id)
516+ obj.put(" label" , zone.label)
517+ obj.put(" enabled" , zone.enabled)
518+ obj.put(" type" , zone.type.name)
519+ obj.put(" x" , zone.x.toDouble())
520+ obj.put(" y" , zone.y.toDouble())
521+ obj.put(" width" , zone.width.toDouble())
522+ obj.put(" height" , zone.height.toDouble())
523+ obj.put(" pixelateSize" , zone.pixelateSize)
524+ obj.put(" blurRadius" , zone.blurRadius.toDouble())
525+ array.put(obj)
526+ }
527+ array.toString()
528+ } catch (e: Exception ) {
529+ Log .e(" SettingsDataStore" , " Failed to serialize masking zones" , e)
530+ " []"
531+ }
532+ }
388533}
0 commit comments