feat: persistently save user session token
this took some dependency injection work to do, but it means we can move on to the big stuff
This commit is contained in:
parent
8c90dbfd3e
commit
aca7817526
|
|
@ -4,6 +4,7 @@ plugins {
|
|||
id 'org.jetbrains.kotlin.plugin.serialization'
|
||||
id 'com.mikepenz.aboutlibraries.plugin'
|
||||
id 'com.google.dagger.hilt.android'
|
||||
|
||||
id 'kotlin-kapt'
|
||||
}
|
||||
|
||||
|
|
@ -89,6 +90,7 @@ dependencies {
|
|||
|
||||
// Hilt - Dependency Injection
|
||||
implementation "com.google.dagger:hilt-android:$hilt_version"
|
||||
implementation "androidx.hilt:hilt-navigation-compose:1.1.0-alpha01"
|
||||
kapt "com.google.dagger:hilt-compiler:$hilt_version"
|
||||
|
||||
// Coil - Image Loading
|
||||
|
|
|
|||
|
|
@ -20,7 +20,9 @@ import chat.revolt.ui.theme.RevoltTheme
|
|||
import com.google.accompanist.navigation.animation.AnimatedNavHost
|
||||
import com.google.accompanist.navigation.animation.rememberAnimatedNavController
|
||||
import com.google.accompanist.navigation.animation.composable
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
@AndroidEntryPoint
|
||||
class MainActivity : ComponentActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
|
|
|||
|
|
@ -1,9 +1,5 @@
|
|||
package chat.revolt.api
|
||||
|
||||
import android.content.Context
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.preferencesDataStore
|
||||
import chat.revolt.api.routes.user.fetchSelf
|
||||
import chat.revolt.api.schemas.CompleteUser
|
||||
import io.ktor.client.*
|
||||
|
|
@ -21,8 +17,6 @@ const val REVOLT_FILES = "https://autumn.revolt.chat"
|
|||
|
||||
private const val BACKEND_IS_STABLE = false
|
||||
|
||||
val Context.revoltKVStorage: DataStore<Preferences> by preferencesDataStore(name = "revolt_kv")
|
||||
|
||||
val RevoltJson = Json { ignoreUnknownKeys = true }
|
||||
|
||||
val RevoltHttp = HttpClient(OkHttp) {
|
||||
|
|
@ -51,6 +45,7 @@ val RevoltHttp = HttpClient(OkHttp) {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
object RevoltAPI {
|
||||
const val TOKEN_HEADER_NAME = "x-session-token"
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
package chat.revolt.persistence
|
||||
|
||||
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.firstOrNull
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
val Context.revoltKVStorage: DataStore<Preferences> by preferencesDataStore(name = "revolt_kv")
|
||||
|
||||
@Singleton
|
||||
class KVStorage @Inject constructor(
|
||||
@ApplicationContext private val mContext: Context
|
||||
) {
|
||||
private val dataStore = mContext.revoltKVStorage
|
||||
|
||||
suspend fun set(key: String, value: String) {
|
||||
dataStore.edit { preferences ->
|
||||
preferences[stringPreferencesKey(key)] = value
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun get(key: String): String? {
|
||||
return dataStore.data.firstOrNull()?.get(stringPreferencesKey(key))
|
||||
}
|
||||
|
||||
suspend fun remove(key: String) {
|
||||
dataStore.edit { preferences ->
|
||||
preferences.remove(stringPreferencesKey(key))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -13,13 +13,31 @@ import androidx.compose.ui.text.font.FontWeight
|
|||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.navigation.NavController
|
||||
import chat.revolt.api.REVOLT_FILES
|
||||
import chat.revolt.api.RevoltAPI
|
||||
import chat.revolt.components.generic.RemoteImage
|
||||
import chat.revolt.persistence.KVStorage
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class HomeScreenViewModel @Inject constructor(
|
||||
private val kvStorage: KVStorage
|
||||
) : ViewModel() {
|
||||
fun logout() {
|
||||
runBlocking {
|
||||
kvStorage.remove("sessionToken")
|
||||
RevoltAPI.logout()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun HomeScreen(navController: NavController) {
|
||||
fun HomeScreen(navController: NavController, viewModel: HomeScreenViewModel = hiltViewModel()) {
|
||||
val user = RevoltAPI.userCache[RevoltAPI.selfId]
|
||||
|
||||
Column() {
|
||||
|
|
@ -61,7 +79,7 @@ fun HomeScreen(navController: NavController) {
|
|||
}
|
||||
Button(
|
||||
onClick = {
|
||||
RevoltAPI.logout()
|
||||
viewModel.logout()
|
||||
navController.navigate("login/greeting") {
|
||||
popUpTo("chat/home") {
|
||||
inclusive = true
|
||||
|
|
|
|||
|
|
@ -19,16 +19,22 @@ import androidx.compose.ui.text.font.FontWeight
|
|||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavController
|
||||
import chat.revolt.api.RevoltAPI
|
||||
import chat.revolt.components.generic.RemoteImage
|
||||
import chat.revolt.components.generic.drawableResource
|
||||
import chat.revolt.persistence.KVStorage
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
class GreeterViewModel() : ViewModel() {
|
||||
@HiltViewModel
|
||||
class GreeterViewModel @Inject constructor(
|
||||
private val kvStorage: KVStorage
|
||||
) : ViewModel() {
|
||||
private var _skipLogin by mutableStateOf(false)
|
||||
val skipLogin: Boolean
|
||||
get() = _skipLogin
|
||||
|
|
@ -47,17 +53,24 @@ class GreeterViewModel() : ViewModel() {
|
|||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
val token = kvStorage.get("sessionToken")
|
||||
if (token != null) {
|
||||
RevoltAPI.setSessionHeader(token)
|
||||
}
|
||||
|
||||
RevoltAPI.initialize()
|
||||
|
||||
if (RevoltAPI.isLoggedIn()) {
|
||||
_skipLogin = true
|
||||
}
|
||||
|
||||
setFinishedLoading(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun GreeterScreen(navController: NavController, viewModel: GreeterViewModel = viewModel()) {
|
||||
fun GreeterScreen(navController: NavController, viewModel: GreeterViewModel = hiltViewModel()) {
|
||||
if (viewModel.skipLogin) {
|
||||
navController.navigate("chat/home") {
|
||||
popUpTo("login/greeting") {
|
||||
|
|
|
|||
|
|
@ -15,9 +15,9 @@ import androidx.compose.ui.text.input.KeyboardType
|
|||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavController
|
||||
import chat.revolt.R
|
||||
import chat.revolt.api.REVOLT_SUPPORT
|
||||
|
|
@ -27,9 +27,15 @@ import chat.revolt.api.routes.user.fetchSelfWithNewToken
|
|||
import chat.revolt.components.generic.AnyLink
|
||||
import chat.revolt.components.generic.FormTextField
|
||||
import chat.revolt.components.generic.Weblink
|
||||
import chat.revolt.persistence.KVStorage
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
class LoginViewModel() : ViewModel() {
|
||||
@HiltViewModel
|
||||
class LoginViewModel @Inject constructor(
|
||||
private val kvStorage: KVStorage,
|
||||
) : ViewModel() {
|
||||
private var _email by mutableStateOf("")
|
||||
val email: String
|
||||
get() = _email
|
||||
|
|
@ -68,7 +74,10 @@ class LoginViewModel() : ViewModel() {
|
|||
"Login",
|
||||
"No MFA required. Login is complete! We have a session token: ${response.firstUserHints!!.token}"
|
||||
)
|
||||
|
||||
fetchSelfWithNewToken(response.firstUserHints.token)
|
||||
kvStorage.set("sessionToken", response.firstUserHints.token)
|
||||
|
||||
_navigateTo = "home"
|
||||
}
|
||||
}
|
||||
|
|
@ -91,7 +100,7 @@ class LoginViewModel() : ViewModel() {
|
|||
@Composable
|
||||
fun LoginScreen(
|
||||
navController: NavController,
|
||||
viewModel: LoginViewModel = viewModel()
|
||||
viewModel: LoginViewModel = hiltViewModel()
|
||||
) {
|
||||
if (viewModel.navigateTo == "mfa") {
|
||||
navController.navigate(
|
||||
|
|
|
|||
|
|
@ -18,9 +18,9 @@ import androidx.compose.ui.text.input.KeyboardType
|
|||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavController
|
||||
import chat.revolt.R
|
||||
import chat.revolt.api.routes.account.MfaResponseRecoveryCode
|
||||
|
|
@ -30,9 +30,15 @@ import chat.revolt.api.routes.account.authenticateWithMfaTotpCode
|
|||
import chat.revolt.api.routes.user.fetchSelfWithNewToken
|
||||
import chat.revolt.components.generic.CollapsibleCard
|
||||
import chat.revolt.components.generic.FormTextField
|
||||
import chat.revolt.persistence.KVStorage
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
class MfaScreenViewModel : ViewModel() {
|
||||
@HiltViewModel
|
||||
class MfaScreenViewModel @Inject constructor(
|
||||
private val kvStorage: KVStorage,
|
||||
) : ViewModel() {
|
||||
private var _totpCode by mutableStateOf("")
|
||||
val totpCode: String
|
||||
get() = _totpCode
|
||||
|
|
@ -72,8 +78,10 @@ class MfaScreenViewModel : ViewModel() {
|
|||
"MFA",
|
||||
"Successfully authorized TOTP. Token: ${response.firstUserHints!!.token}"
|
||||
)
|
||||
val self = fetchSelfWithNewToken(response.firstUserHints.token)
|
||||
Log.d("MFA", "Self: ${self.username}")
|
||||
|
||||
fetchSelfWithNewToken(response.firstUserHints.token)
|
||||
kvStorage.set("sessionToken", response.firstUserHints.token)
|
||||
|
||||
_navigateToHome = true
|
||||
}
|
||||
}
|
||||
|
|
@ -91,8 +99,10 @@ class MfaScreenViewModel : ViewModel() {
|
|||
"MFA",
|
||||
"Successfully authorized recovery code. Token: ${response.firstUserHints!!.token}"
|
||||
)
|
||||
val self = fetchSelfWithNewToken(response.firstUserHints.token)
|
||||
Log.d("MFA", "Self: ${self.username}")
|
||||
|
||||
fetchSelfWithNewToken(response.firstUserHints.token)
|
||||
kvStorage.set("sessionToken", response.firstUserHints.token)
|
||||
|
||||
_navigateToHome = true
|
||||
}
|
||||
}
|
||||
|
|
@ -104,7 +114,7 @@ fun MfaScreen(
|
|||
navController: NavController,
|
||||
allowedAuthTypesCommaSep: String,
|
||||
mfaTicket: String,
|
||||
viewModel: MfaScreenViewModel = viewModel()
|
||||
viewModel: MfaScreenViewModel = hiltViewModel()
|
||||
) {
|
||||
val allowedAuthTypes = allowedAuthTypesCommaSep.split(",")
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue