mirror of
https://github.com/avinal/nikki.git
synced 2026-07-03 21:40:09 +05:30
Add dependency wiring, token storage, and app entry points
- AppDependencies: manual DI container with lazy singletons - TokenStore: DataStore-backed persistence for server URL, access token, theme preference, and accent color selection - DataStoreFactory: multiplatform DataStore creation - LocalAppDependencies: CompositionLocal for DI access in composables - App.kt: root composable with theme + Coil ImageLoader (Ktor engine) - MainActivity: creates AppDependencies, provides via CompositionLocal - Coil configured with authenticated Ktor HttpClient for private images Co-Authored-By: Claude Opus 4.6 (1M context) Signed-off-by: Avinal Kumar <avinal.xlvii@gmail.com>
This commit is contained in:
@@ -0,0 +1,29 @@
|
|||||||
|
package com.avinal.memos
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.activity.compose.setContent
|
||||||
|
import androidx.activity.enableEdgeToEdge
|
||||||
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
|
import com.avinal.memos.util.LocalAppDependencies
|
||||||
|
|
||||||
|
class MainActivity : ComponentActivity() {
|
||||||
|
|
||||||
|
private val deps by lazy {
|
||||||
|
AppDependencies(
|
||||||
|
dataStorePath = filesDir.resolve("memos_prefs.preferences_pb").absolutePath,
|
||||||
|
platformContext = applicationContext,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
deps.initialize()
|
||||||
|
enableEdgeToEdge()
|
||||||
|
setContent {
|
||||||
|
CompositionLocalProvider(LocalAppDependencies provides deps) {
|
||||||
|
App()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package com.avinal.memos
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import coil3.ImageLoader
|
||||||
|
import coil3.compose.setSingletonImageLoaderFactory
|
||||||
|
import coil3.compose.LocalPlatformContext
|
||||||
|
import coil3.network.ktor3.KtorNetworkFetcherFactory
|
||||||
|
import com.avinal.memos.ui.navigation.AppNavHost
|
||||||
|
import com.avinal.memos.ui.theme.MemosAppTheme
|
||||||
|
import com.avinal.memos.util.LocalAppDependencies
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun App() {
|
||||||
|
val deps = LocalAppDependencies.current
|
||||||
|
|
||||||
|
setSingletonImageLoaderFactory { context ->
|
||||||
|
ImageLoader.Builder(context)
|
||||||
|
.components {
|
||||||
|
add(KtorNetworkFetcherFactory(httpClient = deps.httpClient))
|
||||||
|
}
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
MemosAppTheme {
|
||||||
|
AppNavHost(deps)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package com.avinal.memos
|
||||||
|
|
||||||
|
import com.avinal.memos.api.HttpClientFactory
|
||||||
|
import com.avinal.memos.api.MemosApiClient
|
||||||
|
import com.avinal.memos.db.MemosDatabase
|
||||||
|
import com.avinal.memos.db.createPlatformDatabase
|
||||||
|
import com.avinal.memos.domain.AuthRepository
|
||||||
|
import com.avinal.memos.domain.MemoRepository
|
||||||
|
import com.avinal.memos.util.TokenStore
|
||||||
|
import com.avinal.memos.util.createDataStore
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.IO
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
class AppDependencies(
|
||||||
|
dataStorePath: String,
|
||||||
|
platformContext: Any,
|
||||||
|
) {
|
||||||
|
@Volatile private var cachedToken: String? = null
|
||||||
|
@Volatile private var cachedServerUrl: String? = null
|
||||||
|
|
||||||
|
private val dataStore = createDataStore(dataStorePath)
|
||||||
|
val tokenStore = TokenStore(dataStore)
|
||||||
|
|
||||||
|
val database: MemosDatabase by lazy { createPlatformDatabase(platformContext) }
|
||||||
|
|
||||||
|
val httpClient by lazy {
|
||||||
|
HttpClientFactory.create { cachedToken }
|
||||||
|
}
|
||||||
|
|
||||||
|
val apiClient: MemosApiClient by lazy {
|
||||||
|
MemosApiClient(
|
||||||
|
httpClient = httpClient,
|
||||||
|
baseUrlProvider = { cachedServerUrl ?: "" },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val authRepository: AuthRepository by lazy { AuthRepository(apiClient, tokenStore) }
|
||||||
|
val memoRepository: MemoRepository by lazy { MemoRepository(apiClient, database.memoDao()) }
|
||||||
|
|
||||||
|
fun initialize() {
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
launch { tokenStore.accessToken.collect { cachedToken = it } }
|
||||||
|
launch { tokenStore.serverUrl.collect { cachedServerUrl = it } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.avinal.memos.util
|
||||||
|
|
||||||
|
import androidx.datastore.core.DataStore
|
||||||
|
import androidx.datastore.preferences.core.PreferenceDataStoreFactory
|
||||||
|
import androidx.datastore.preferences.core.Preferences
|
||||||
|
import okio.Path.Companion.toPath
|
||||||
|
|
||||||
|
fun createDataStore(path: String): DataStore<Preferences> =
|
||||||
|
PreferenceDataStoreFactory.createWithPath(produceFile = { path.toPath() })
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package com.avinal.memos.util
|
||||||
|
|
||||||
|
import androidx.compose.runtime.staticCompositionLocalOf
|
||||||
|
import com.avinal.memos.AppDependencies
|
||||||
|
|
||||||
|
val LocalAppDependencies = staticCompositionLocalOf<AppDependencies> {
|
||||||
|
error("AppDependencies not provided")
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package com.avinal.memos.util
|
||||||
|
|
||||||
|
import androidx.datastore.core.DataStore
|
||||||
|
import androidx.datastore.preferences.core.Preferences
|
||||||
|
import androidx.datastore.preferences.core.edit
|
||||||
|
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
|
||||||
|
class TokenStore(private val dataStore: DataStore<Preferences>) {
|
||||||
|
|
||||||
|
val serverUrl: Flow<String?> = dataStore.data.map { it[KEY_SERVER_URL] }
|
||||||
|
val accessToken: Flow<String?> = dataStore.data.map { it[KEY_ACCESS_TOKEN] }
|
||||||
|
val theme: Flow<String> = dataStore.data.map { it[KEY_THEME] ?: "DARK" }
|
||||||
|
val accentColor: Flow<String> = dataStore.data.map { it[KEY_ACCENT] ?: "Cobalt" }
|
||||||
|
|
||||||
|
suspend fun saveCredentials(serverUrl: String, token: String) {
|
||||||
|
dataStore.edit { prefs ->
|
||||||
|
prefs[KEY_SERVER_URL] = serverUrl
|
||||||
|
prefs[KEY_ACCESS_TOKEN] = token
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun saveTheme(theme: String) {
|
||||||
|
dataStore.edit { it[KEY_THEME] = theme }
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun saveAccentColor(name: String) {
|
||||||
|
dataStore.edit { it[KEY_ACCENT] = name }
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun clear() {
|
||||||
|
dataStore.edit {
|
||||||
|
val theme = it[KEY_THEME]
|
||||||
|
val accent = it[KEY_ACCENT]
|
||||||
|
it.clear()
|
||||||
|
if (theme != null) it[KEY_THEME] = theme
|
||||||
|
if (accent != null) it[KEY_ACCENT] = accent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val KEY_SERVER_URL = stringPreferencesKey("server_url")
|
||||||
|
private val KEY_ACCESS_TOKEN = stringPreferencesKey("access_token")
|
||||||
|
private val KEY_THEME = stringPreferencesKey("app_theme")
|
||||||
|
private val KEY_ACCENT = stringPreferencesKey("accent_color")
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user