1
0
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:
2026-05-19 17:08:37 +05:30
parent e842355a6b
commit 5759ab3738
6 changed files with 169 additions and 0 deletions
@@ -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")
}
}