package com.vgmlr.wedge
import android.content.Context
import androidx.room.*
import androidx.datastore.preferences.core.*
import androidx.datastore.preferences.preferencesDataStore
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import androidx.compose.ui.graphics.Color
import androidx.core.graphics.toColorInt
@Entity(tableName = "notes")
data class NoteEntity(
@PrimaryKey val id: Int = 1,
val content: String = "",
val lastModified: Long = System.currentTimeMillis()
)
@Dao
interface NoteDao {
@Query("SELECT * FROM notes WHERE id = 1")
fun getNote(): Flow<NoteEntity?>
@Query("SELECT * FROM notes WHERE id = 1")
suspend fun getNoteSync(): NoteEntity?
@Query("UPDATE notes SET content = :content WHERE id = 1")
suspend fun updateContentPreservingTimestamp(content: String)
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun save(note: NoteEntity)
}
@Database(entities = [NoteEntity::class], version = 3, exportSchema = false)
abstract class AppDatabase : RoomDatabase() {
abstract fun noteDao(): NoteDao
companion object {
@Volatile private var INSTANCE: AppDatabase? = null
fun getInstance(context: Context): AppDatabase = INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(context.applicationContext, AppDatabase::class.java, "wedge_db")
.fallbackToDestructiveMigration()
.build()
INSTANCE = instance
instance
}
}
}
private val Context.dataStore by preferencesDataStore("settings")
data class SettingsState(
val textColor: String = WedgeConfig.TEXT_COLOR_DEFAULT,
val bgColor: String = WedgeConfig.BG_COLOR_DEFAULT,
val widgetTextColor: String = WedgeConfig.WIDGET_TEXT_COLOR_DEFAULT,
val widgetBgColor: String = WedgeConfig.WIDGET_BG_COLOR_DEFAULT,
val editorFontSize: Float = WedgeConfig.FONT_SIZE_DEFAULT,
val widgetFontSize: Float = WedgeConfig.WIDGET_FONT_SIZE_DEFAULT,
val lineHeight: Float = WedgeConfig.LINE_HEIGHT_DEFAULT,
val boldColor: String = WedgeConfig.BOLD_COLOR_DEFAULT,
val incognitoMode: Boolean = false,
val passKey: String = "wedge03569"
)
data class PhraseSpec(
val words: Boolean = WedgeConfig.PHRASE_WORDS_DEFAULT,
val wordCount: Int = WedgeConfig.PHRASE_WORD_COUNT_DEFAULT,
val letters: Int = WedgeConfig.PHRASE_LETTERS_DEFAULT,
val numbers: Boolean = WedgeConfig.PHRASE_NUMBERS_DEFAULT,
val numberCount: Int = WedgeConfig.PHRASE_NUMBER_COUNT_DEFAULT
)
class PreferenceManager(private val context: Context) {
private val keyTextCol = stringPreferencesKey("text_color")
private val keyBgCol = stringPreferencesKey("bg_color")
private val keyWTextCol = stringPreferencesKey("widget_text_color")
private val keyWBgCol = stringPreferencesKey("widget_bg_color")
private val keyBoldCol = stringPreferencesKey("bold_color")
private val keyLinkCol = stringPreferencesKey("link_color")
private val keyLinkEnabled = booleanPreferencesKey("link_enabled")
private val keyIncognito = booleanPreferencesKey("incognito_mode")
private val keyGuideEnabled = booleanPreferencesKey("guide_enabled")
private val keyGuideChars = intPreferencesKey("guide_chars")
private val keyGuideOpacity = intPreferencesKey("guide_opacity")
private val keyBtnPadRight = intPreferencesKey("btn_pad_right")
private val keyBtnPadBottom = intPreferencesKey("btn_pad_bottom")
private val keyEditorPadLeft = intPreferencesKey("editor_pad_left")
private val keyESize = floatPreferencesKey("editor_font_size_abs")
private val keyWSize = floatPreferencesKey("widget_font_size_abs")
private val keyWidgetBoldCol = stringPreferencesKey("widget_bold_color")
private val keyNextTimeBg = stringPreferencesKey("next_time_bg_color")
private val keyWidgetNextTimeBg = stringPreferencesKey("widget_next_time_bg_color")
private val keyLHeight = floatPreferencesKey("line_height_multiplier")
private val keyHeaderFormat = stringPreferencesKey("header_format")
private val keyPruneDays = booleanPreferencesKey("prune_days")
private val keyAlarmSoundUri = stringPreferencesKey("alarm_sound_uri")
private val keyAlarmDurationMinutes = intPreferencesKey("alarm_duration_minutes")
private val keyVibrationEnabled = booleanPreferencesKey("vibration_enabled")
private val keyPassKey = stringPreferencesKey("pass_key")
private val keyBackupInterval = intPreferencesKey("backup_interval_minutes")
private val keyBackupLocation = stringPreferencesKey("backup_location_uri")
private val keyBackupStamp = longPreferencesKey("backup_last_stamp")
private val keyPhraseWords = booleanPreferencesKey("phrase_words")
private val keyPhraseWordCount = intPreferencesKey("phrase_word_count")
private val keyPhraseLetters = intPreferencesKey("phrase_letters")
private val keyPhraseNumbers = booleanPreferencesKey("phrase_numbers")
private val keyPhraseNumberCount = intPreferencesKey("phrase_number_count")
// legacy keys
private val legacyBackupEncrypt = booleanPreferencesKey("backup_encrypt")
private val legacyBackupPhrase = stringPreferencesKey("backup_phrase")
private val legacyBtnRadius = intPreferencesKey("btn_radius")
private val legacyTopBarBtnLeft = booleanPreferencesKey("top_bar_btn_left")
val settings: Flow<SettingsState> = context.dataStore.data.map { prefs ->
SettingsState(
textColor = prefs[keyTextCol] ?: WedgeConfig.TEXT_COLOR_DEFAULT,
bgColor = prefs[keyBgCol] ?: WedgeConfig.BG_COLOR_DEFAULT,
widgetTextColor = prefs[keyWTextCol] ?: WedgeConfig.WIDGET_TEXT_COLOR_DEFAULT,
widgetBgColor = prefs[keyWBgCol] ?: WedgeConfig.WIDGET_BG_COLOR_DEFAULT,
editorFontSize = prefs[keyESize] ?: WedgeConfig.FONT_SIZE_DEFAULT,
widgetFontSize = prefs[keyWSize] ?: WedgeConfig.WIDGET_FONT_SIZE_DEFAULT,
lineHeight = prefs[keyLHeight] ?: WedgeConfig.LINE_HEIGHT_DEFAULT,
boldColor = prefs[keyBoldCol] ?: WedgeConfig.BOLD_COLOR_DEFAULT,
incognitoMode = prefs[keyIncognito] ?: false,
passKey = prefs[keyPassKey] ?: "wedge03569"
)
}
val textColor: Flow<String> =
context.dataStore.data.map { it[keyTextCol] ?: WedgeConfig.TEXT_COLOR_DEFAULT }
val bgColor: Flow<String> =
context.dataStore.data.map { it[keyBgCol] ?: WedgeConfig.BG_COLOR_DEFAULT }
val widgetTextColor: Flow<String> =
context.dataStore.data.map { it[keyWTextCol] ?: WedgeConfig.WIDGET_TEXT_COLOR_DEFAULT }
val widgetBgColor: Flow<String> =
context.dataStore.data.map { it[keyWBgCol] ?: WedgeConfig.WIDGET_BG_COLOR_DEFAULT }
val boldColor: Flow<String> =
context.dataStore.data.map { it[keyBoldCol] ?: WedgeConfig.BOLD_COLOR_DEFAULT }
val linkColor: Flow<String> =
context.dataStore.data.map { it[keyLinkCol] ?: WedgeConfig.LINK_COLOR_DEFAULT }
val linkEnabled: Flow<Boolean> = context.dataStore.data.map { it[keyLinkEnabled] ?: false }
val incognitoMode: Flow<Boolean> = context.dataStore.data.map { it[keyIncognito] ?: false }
val guideEnabled: Flow<Boolean> =
context.dataStore.data.map { it[keyGuideEnabled] ?: WedgeConfig.GUIDE_ENABLED_DEFAULT }
val guideChars: Flow<Int> =
context.dataStore.data.map { it[keyGuideChars] ?: WedgeConfig.GUIDE_CHARS_DEFAULT }
val guideOpacity: Flow<Int> =
context.dataStore.data.map { it[keyGuideOpacity] ?: WedgeConfig.GUIDE_OPACITY_DEFAULT }
val btnPadRight: Flow<Int> =
context.dataStore.data.map { it[keyBtnPadRight] ?: WedgeConfig.BTN_PAD_RIGHT_DEFAULT }
val btnPadBottom: Flow<Int> =
context.dataStore.data.map { it[keyBtnPadBottom] ?: WedgeConfig.BTN_PAD_BOTTOM_DEFAULT }
val editorPadLeft: Flow<Int> =
context.dataStore.data.map { it[keyEditorPadLeft] ?: WedgeConfig.EDITOR_PAD_LEFT_DEFAULT }
val widgetBoldColor: Flow<String> =
context.dataStore.data.map { it[keyWidgetBoldCol] ?: WedgeConfig.WIDGET_BOLD_COLOR_DEFAULT }
val nextTimeBg: Flow<String> =
context.dataStore.data.map { it[keyNextTimeBg] ?: WedgeConfig.CALENDAR_COLOR_DEFAULT }
val widgetNextTimeBg: Flow<String> = context.dataStore.data.map {
it[keyWidgetNextTimeBg] ?: WedgeConfig.WIDGET_CALENDAR_COLOR_DEFAULT
}
val editorFontSize: Flow<Float> =
context.dataStore.data.map { it[keyESize] ?: WedgeConfig.FONT_SIZE_DEFAULT }
val widgetFontSize: Flow<Float> =
context.dataStore.data.map { it[keyWSize] ?: WedgeConfig.FONT_SIZE_DEFAULT }
val lineHeight: Flow<Float> =
context.dataStore.data.map { it[keyLHeight] ?: WedgeConfig.LINE_HEIGHT_DEFAULT }
val headerFormat: Flow<String> =
context.dataStore.data.map { it[keyHeaderFormat] ?: WedgeConfig.HEADER_FORMAT_DEFAULT }
val autoPrune: Flow<Boolean> =
context.dataStore.data.map { it[keyPruneDays] ?: WedgeConfig.PRUNE_DAYS_DEFAULT }
val alarmSoundUri: Flow<String> = context.dataStore.data.map { it[keyAlarmSoundUri] ?: "" }
val alarmDurationMinutes: Flow<Int> =
context.dataStore.data.map { it[keyAlarmDurationMinutes] ?: 5 }
val vibrationEnabled: Flow<Boolean> =
context.dataStore.data.map { it[keyVibrationEnabled] ?: false }
val passKey: Flow<String> = context.dataStore.data.map { it[keyPassKey] ?: "wedge03569" }
val backupInterval: Flow<Int> =
context.dataStore.data.map { it[keyBackupInterval] ?: WedgeConfig.BACKUP_INTERVAL_DEFAULT }
val backupLocation: Flow<String> = context.dataStore.data.map { it[keyBackupLocation] ?: "" }
val backupStamp: Flow<Long> = context.dataStore.data.map { it[keyBackupStamp] ?: 0L }
val phraseSpec: Flow<PhraseSpec> = context.dataStore.data.map {
PhraseSpec(
words = it[keyPhraseWords] ?: WedgeConfig.PHRASE_WORDS_DEFAULT,
wordCount = it[keyPhraseWordCount] ?: WedgeConfig.PHRASE_WORD_COUNT_DEFAULT,
letters = it[keyPhraseLetters] ?: WedgeConfig.PHRASE_LETTERS_DEFAULT,
numbers = it[keyPhraseNumbers] ?: WedgeConfig.PHRASE_NUMBERS_DEFAULT,
numberCount = it[keyPhraseNumberCount] ?: WedgeConfig.PHRASE_NUMBER_COUNT_DEFAULT
)
}
suspend fun setColors(
txt: String,
bg: String,
wtxt: String,
wbg: String,
bcol: String,
wBcol: String,
nextBg: String,
wNextBg: String
) = context.dataStore.edit {
it[keyTextCol] = txt; it[keyBgCol] = bg; it[keyWTextCol] = wtxt; it[keyWBgCol] =
wbg; it[keyBoldCol] = bcol
it[keyWidgetBoldCol] = wBcol; it[keyNextTimeBg] = nextBg; it[keyWidgetNextTimeBg] = wNextBg
}
suspend fun setLinkColor(link: String) = context.dataStore.edit {
it[keyLinkCol] = link
}
suspend fun setLinkEnabled(enabled: Boolean) = context.dataStore.edit {
it[keyLinkEnabled] = enabled
}
suspend fun setFontSettings(eSize: Float, wSize: Float, lHeight: Float) =
context.dataStore.edit {
it[keyESize] = eSize; it[keyWSize] = wSize; it[keyLHeight] = lHeight
}
suspend fun setIncognito(incognito: Boolean) = context.dataStore.edit {
it[keyIncognito] = incognito
}
suspend fun setGuide(enabled: Boolean, chars: Int, opacity: Int) = context.dataStore.edit {
it[keyGuideEnabled] = enabled
it[keyGuideChars] = chars
it[keyGuideOpacity] = opacity
}
suspend fun setBtnPad(right: Int, bottom: Int) = context.dataStore.edit {
it[keyBtnPadRight] = right
it[keyBtnPadBottom] = bottom
}
suspend fun setEditorPadLeft(v: Int) = context.dataStore.edit {
it[keyEditorPadLeft] = v
}
suspend fun setHeaderFormat(fmt: String) = context.dataStore.edit {
it[keyHeaderFormat] = fmt
}
suspend fun setAutoPrune(on: Boolean) = context.dataStore.edit {
it[keyPruneDays] = on
}
suspend fun setAlarmSettings(uri: String, duration: Int, vibration: Boolean) = context.dataStore.edit {
it[keyAlarmSoundUri] = uri
it[keyAlarmDurationMinutes] = duration
it[keyVibrationEnabled] = vibration
}
suspend fun setPassKey(newPassKey: String) = context.dataStore.edit {
it[keyPassKey] = newPassKey
}
suspend fun setBackupSettings(interval: Int, location: String) = context.dataStore.edit {
it[keyBackupInterval] = interval
it[keyBackupLocation] = location
}
suspend fun setBackupStamp(stamp: Long) = context.dataStore.edit {
it[keyBackupStamp] = stamp
}
suspend fun setPhraseSpec(spec: PhraseSpec) = context.dataStore.edit {
it[keyPhraseWords] = spec.words
it[keyPhraseWordCount] = spec.wordCount
it[keyPhraseLetters] = spec.letters
it[keyPhraseNumbers] = spec.numbers
it[keyPhraseNumberCount] = spec.numberCount
}
// wipe legacy
suspend fun purgeLegacy() = context.dataStore.edit {
it.remove(legacyBackupEncrypt)
it.remove(legacyBackupPhrase)
it.remove(legacyBtnRadius)
it.remove(legacyTopBarBtnLeft)
}
}
fun parseColor(hex: String): Color {
return try { Color(hex.toColorInt()) } catch (_: Exception) { if (hex.length > 7) Color.White else Color.Black }
}