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>
62 lines
2.1 KiB
Kotlin
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
|
|
}
|
|
}
|
|
}
|