ref: continue without account to use local db instead of anon login

This commit is contained in:
tapframe 2026-04-04 15:11:02 +05:30
parent a826f58ded
commit a068b86403
7 changed files with 132 additions and 14 deletions

View file

@ -7,6 +7,7 @@ import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.activity.SystemBarStyle
import androidx.core.view.WindowCompat
import com.nuvio.app.core.auth.AuthStorage
import com.nuvio.app.core.deeplink.handleAppUrl
import com.nuvio.app.core.storage.PlatformLocalAccountDataCleaner
import com.nuvio.app.features.addons.AddonStorage
@ -43,6 +44,7 @@ class MainActivity : ComponentActivity() {
window.isNavigationBarContrastEnforced = false
WindowCompat.getInsetsController(window, window.decorView).isAppearanceLightStatusBars = false
AddonStorage.initialize(applicationContext)
AuthStorage.initialize(applicationContext)
LibraryStorage.initialize(applicationContext)
WatchedStorage.initialize(applicationContext)
MetaScreenSettingsStorage.initialize(applicationContext)

View file

@ -0,0 +1,26 @@
package com.nuvio.app.core.auth
import android.content.Context
import android.content.SharedPreferences
actual object AuthStorage {
private const val PREFS_NAME = "nuvio_auth"
private const val KEY_ANONYMOUS_USER_ID = "anonymous_user_id"
private var preferences: SharedPreferences? = null
fun initialize(context: Context) {
preferences = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
}
actual fun loadAnonymousUserId(): String? =
preferences?.getString(KEY_ANONYMOUS_USER_ID, null)
actual fun saveAnonymousUserId(userId: String) {
preferences?.edit()?.putString(KEY_ANONYMOUS_USER_ID, userId)?.apply()
}
actual fun clearAnonymousUserId() {
preferences?.edit()?.remove(KEY_ANONYMOUS_USER_ID)?.apply()
}
}

View file

@ -7,6 +7,8 @@ import io.github.jan.supabase.auth.auth
import io.github.jan.supabase.auth.providers.builtin.Email
import io.github.jan.supabase.auth.status.SessionStatus
import io.github.jan.supabase.functions.functions
import kotlin.uuid.ExperimentalUuidApi
import kotlin.uuid.Uuid
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
@ -30,22 +32,33 @@ object AuthRepository {
fun initialize() {
if (initialized) return
initialized = true
val savedAnonId = AuthStorage.loadAnonymousUserId()
if (savedAnonId != null) {
_state.value = AuthState.Authenticated(
userId = savedAnonId,
email = null,
isAnonymous = true,
)
}
scope.launch {
SupabaseProvider.client.auth.sessionStatus.collect { status ->
if (AuthStorage.loadAnonymousUserId() != null) return@collect
when (status) {
is SessionStatus.Authenticated -> {
val user = status.session.user
_state.value = AuthState.Authenticated(
userId = user?.id ?: "",
email = user?.email,
isAnonymous = user?.email.isNullOrBlank(),
isAnonymous = false,
)
}
is SessionStatus.NotAuthenticated -> {
_state.value = AuthState.Unauthenticated
}
is SessionStatus.Initializing -> {
_state.value = AuthState.Loading
if (savedAnonId == null) _state.value = AuthState.Loading
}
is SessionStatus.RefreshFailure -> {
_state.value = AuthState.Unauthenticated
@ -55,13 +68,16 @@ object AuthRepository {
}
}
suspend fun signInAnonymously(): Result<Unit> = runCatching {
@OptIn(ExperimentalUuidApi::class)
fun signInAnonymously() {
_error.value = null
SupabaseProvider.client.auth.signInAnonymously()
Unit
}.onFailure { e ->
log.e(e) { "Anonymous sign-in failed" }
_error.value = e.message ?: "Anonymous sign-in failed"
val userId = Uuid.random().toString()
AuthStorage.saveAnonymousUserId(userId)
_state.value = AuthState.Authenticated(
userId = userId,
email = null,
isAnonymous = true,
)
}
suspend fun signUpWithEmail(email: String, password: String): Result<Unit> = runCatching {
@ -89,7 +105,12 @@ object AuthRepository {
suspend fun signOut(): Result<Unit> = runCatching {
_error.value = null
SupabaseProvider.client.auth.signOut()
val wasAnonymous = AuthStorage.loadAnonymousUserId() != null
AuthStorage.clearAnonymousUserId()
if (!wasAnonymous) {
SupabaseProvider.client.auth.signOut()
}
_state.value = AuthState.Unauthenticated
LocalAccountDataCleaner.wipe()
}.onFailure { e ->
log.e(e) { "Sign-out failed" }

View file

@ -0,0 +1,7 @@
package com.nuvio.app.core.auth
internal expect object AuthStorage {
fun loadAnonymousUserId(): String?
fun saveAnonymousUserId(userId: String)
fun clearAnonymousUserId()
}

View file

@ -327,11 +327,7 @@ fun AuthScreen(
Button(
onClick = {
isLoading = true
scope.launch {
AuthRepository.signInAnonymously()
isLoading = false
}
AuthRepository.signInAnonymously()
},
modifier = Modifier
.fillMaxWidth()

View file

@ -3,6 +3,7 @@ package com.nuvio.app.features.profiles
import co.touchlab.kermit.Logger
import com.nuvio.app.core.auth.AuthRepository
import com.nuvio.app.core.auth.AuthState
import com.nuvio.app.core.auth.isAnonymous
import com.nuvio.app.core.network.SupabaseProvider
import com.nuvio.app.features.addons.AddonRepository
import com.nuvio.app.features.details.MetaScreenSettingsRepository
@ -94,6 +95,12 @@ object ProfileRepository {
}
suspend fun pullProfiles() {
if (AuthRepository.state.value.isAnonymous) {
if (!_state.value.isLoaded) {
_state.value = _state.value.copy(isLoaded = true)
}
return
}
runCatching {
val result = SupabaseProvider.client.postgrest.rpc("sync_pull_profiles")
val profiles = result.decodeList<NuvioProfile>()
@ -139,6 +146,10 @@ object ProfileRepository {
}
suspend fun pushProfiles(profiles: List<ProfilePushPayload>) {
if (AuthRepository.state.value.isAnonymous) {
applyPayloadsLocally(profiles)
return
}
runCatching {
val params = buildJsonObject {
put("p_profiles", json.encodeToJsonElement(profiles))
@ -211,6 +222,18 @@ object ProfileRepository {
}
suspend fun deleteProfile(profileIndex: Int) {
if (AuthRepository.state.value.isAnonymous) {
val remaining = _state.value.profiles.filter { it.profileIndex != profileIndex }
_state.value = _state.value.copy(
profiles = remaining,
activeProfile = if (_state.value.activeProfile?.profileIndex == profileIndex) remaining.firstOrNull() else _state.value.activeProfile,
)
if (_state.value.activeProfile != null) {
activeProfileIndex = _state.value.activeProfile!!.profileIndex
}
persist()
return
}
runCatching {
val params = buildJsonObject { put("p_profile_id", profileIndex) }
SupabaseProvider.client.postgrest.rpc("sync_delete_profile_data", params)
@ -284,6 +307,31 @@ object ProfileRepository {
}
}
private fun applyPayloadsLocally(payloads: List<ProfilePushPayload>) {
val authState = AuthRepository.state.value as? AuthState.Authenticated ?: return
val profiles = payloads.map { p ->
NuvioProfile(
id = "",
userId = authState.userId,
profileIndex = p.profileIndex,
name = p.name,
avatarColorHex = p.avatarColorHex,
avatarId = p.avatarId,
usesPrimaryAddons = p.usesPrimaryAddons,
usesPrimaryPlugins = p.usesPrimaryPlugins,
)
}.sortedBy { it.profileIndex }
_state.value = _state.value.copy(
profiles = profiles,
isLoaded = true,
activeProfile = profiles.find { it.profileIndex == activeProfileIndex } ?: profiles.firstOrNull(),
)
if (_state.value.activeProfile != null) {
activeProfileIndex = _state.value.activeProfile!!.profileIndex
}
persist()
}
private fun persist() {
val authState = AuthRepository.state.value as? AuthState.Authenticated ?: return
ProfileStorage.savePayload(

View file

@ -0,0 +1,18 @@
package com.nuvio.app.core.auth
import platform.Foundation.NSUserDefaults
actual object AuthStorage {
private const val KEY_ANONYMOUS_USER_ID = "anonymous_user_id"
actual fun loadAnonymousUserId(): String? =
NSUserDefaults.standardUserDefaults.stringForKey(KEY_ANONYMOUS_USER_ID)
actual fun saveAnonymousUserId(userId: String) {
NSUserDefaults.standardUserDefaults.setObject(userId, forKey = KEY_ANONYMOUS_USER_ID)
}
actual fun clearAnonymousUserId() {
NSUserDefaults.standardUserDefaults.removeObjectForKey(KEY_ANONYMOUS_USER_ID)
}
}