package com.dmc.callcrm.data import android.content.Context import com.google.gson.Gson import com.google.gson.reflect.TypeToken /** Tiny persistent store for the device's registration + sync state. */ class Prefs(ctx: Context) { private val sp = ctx.getSharedPreferences("dmc_call_crm", Context.MODE_PRIVATE) private val gson = Gson() var apiToken: String? get() = sp.getString("api_token", null) set(v) = sp.edit().putString("api_token", v).apply() var employeeId: Long get() = sp.getLong("employee_id", 0L) set(v) = sp.edit().putLong("employee_id", v).apply() var name: String? get() = sp.getString("name", null) set(v) = sp.edit().putString("name", v).apply() var mobile: String? get() = sp.getString("mobile", null) set(v) = sp.edit().putString("mobile", v).apply() var simSlot: Int get() = sp.getInt("sim_slot", -1) set(v) = sp.edit().putInt("sim_slot", v).apply() /** Highest CallLog.DATE we've already uploaded (epoch millis). */ var lastCallDate: Long get() = sp.getLong("last_call_date", 0L) set(v) = sp.edit().putLong("last_call_date", v).apply() /** File.lastModified() of the newest recording we've already processed. */ var lastRecordingScan: Long get() = sp.getLong("last_rec_scan", 0L) set(v) = sp.edit().putLong("last_rec_scan", v).apply() /** Paths already uploaded, to avoid re-sending. */ var uploadedRecordings: MutableSet get() = HashSet(sp.getStringSet("uploaded_recs", emptySet()) ?: emptySet()) set(v) = sp.edit().putStringSet("uploaded_recs", v).apply() fun markUploaded(path: String) { val s = uploadedRecordings s.add(path) // keep the set from growing unbounded val trimmed = if (s.size > 2000) s.toList().takeLast(1500).toMutableSet() else s uploadedRecordings = trimmed } val isRegistered: Boolean get() = !apiToken.isNullOrEmpty() fun bearer(): String = "Bearer ${apiToken.orEmpty()}" // ---- Call notes (local cache, mirrored to the server via call_note.php) ---- /** Key identifying one call the same way the server does: number + date(ms) + duration(s). */ fun noteKey(number: String, dateMs: Long, durationSec: Int): String = "$number|$dateMs|$durationSec" private var notesCacheRaw: MutableMap get() { val raw = sp.getString("notes_cache", null) ?: return LinkedHashMap() return try { val type = object : TypeToken>() {}.type gson.fromJson>(raw, type) ?: LinkedHashMap() } catch (_: Exception) { LinkedHashMap() } } set(v) = sp.edit().putString("notes_cache", gson.toJson(v)).apply() fun noteFor(key: String): String? = notesCacheRaw[key] fun saveNoteLocally(key: String, note: String) { val m = notesCacheRaw if (note.isBlank()) m.remove(key) else m[key] = note // keep the cache bounded notesCacheRaw = if (m.size > 3000) m.entries.toList().takeLast(2000).associate { it.key to it.value }.toMutableMap() else m } // ---- Excluded numbers (cached from excluded_numbers.php for the Analytics filter) ---- var excludedNumberDigits: Set get() = sp.getStringSet("excluded_digits", emptySet()) ?: emptySet() set(v) = sp.edit().putStringSet("excluded_digits", v).apply() var excludedLastFetch: Long get() = sp.getLong("excluded_last_fetch", 0L) set(v) = sp.edit().putLong("excluded_last_fetch", v).apply() /** Analytics "Ex. No." filter: whether to leave excluded numbers out of the totals. */ var analyticsExcludeExcluded: Boolean get() = sp.getBoolean("analytics_exclude_excluded", false) set(v) = sp.edit().putBoolean("analytics_exclude_excluded", v).apply() /** Analytics type filter: call-type constants (CallLog.Calls.TYPE) left OUT of the totals. */ var analyticsDeselectedTypes: Set get() = sp.getStringSet("analytics_deselected_types", emptySet()) ?: emptySet() set(v) = sp.edit().putStringSet("analytics_deselected_types", v).apply() }