implausible/app/src/main/java/no/naiv/implausibly/data/AppPreferences.kt
Ole-Morten Duesund aa66172d58 feat: implement Phase 1 MVP of Implausibly
Working Android dashboard app for self-hosted Plausible Analytics CE.
Connects to Plausible API v2 (POST /api/v2/query), displays top stats,
visitor chart, top sources, and top pages with date range selection.

Architecture: Kotlin + Jetpack Compose + Material 3 + Hilt + Ktor +
SQLDelight + EncryptedSharedPreferences. Single :app module, four-layer
unidirectional data flow (UI → ViewModel → Repository → Data).

Includes: instance management, site list, caching with TTL per date
range, encrypted API key storage, custom Canvas visitor chart,
pull-to-refresh, and unit tests for API, cache, repository, and
domain model layers.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 16:46:08 +01:00

62 lines
2.1 KiB
Kotlin

// SPDX-License-Identifier: GPL-3.0-only
package no.naiv.implausibly.data
import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import javax.inject.Inject
import javax.inject.Singleton
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(
name = "implausibly_preferences"
)
/**
* Non-sensitive preferences stored via DataStore.
* Includes the user's selected instance, site, and date range.
*/
@Singleton
class AppPreferences @Inject constructor(
@ApplicationContext private val context: Context
) {
private object Keys {
val SELECTED_INSTANCE_ID = stringPreferencesKey("selected_instance_id")
val SELECTED_SITE_ID = stringPreferencesKey("selected_site_id")
val SELECTED_DATE_RANGE = stringPreferencesKey("selected_date_range")
}
val selectedInstanceId: Flow<String?> = context.dataStore.data
.map { it[Keys.SELECTED_INSTANCE_ID] }
val selectedSiteId: Flow<String?> = context.dataStore.data
.map { it[Keys.SELECTED_SITE_ID] }
val selectedDateRange: Flow<String?> = context.dataStore.data
.map { it[Keys.SELECTED_DATE_RANGE] }
suspend fun setSelectedInstanceId(id: String?) {
context.dataStore.edit { prefs ->
if (id != null) prefs[Keys.SELECTED_INSTANCE_ID] = id
else prefs.remove(Keys.SELECTED_INSTANCE_ID)
}
}
suspend fun setSelectedSiteId(id: String?) {
context.dataStore.edit { prefs ->
if (id != null) prefs[Keys.SELECTED_SITE_ID] = id
else prefs.remove(Keys.SELECTED_SITE_ID)
}
}
suspend fun setSelectedDateRange(range: String) {
context.dataStore.edit { prefs ->
prefs[Keys.SELECTED_DATE_RANGE] = range
}
}
}