mirror of
https://github.com/LiveFastEatTrashRaccoon/RaccoonForLemmy.git
synced 2025-02-09 11:48:43 +01:00
feat: quick login implementation (#118)
* feat: quick login implementation * chore: update from PR feedback
This commit is contained in:
parent
5d032fd583
commit
e603644925
@ -62,8 +62,10 @@ internal class DefaultServiceProvider : ServiceProvider {
|
||||
}
|
||||
|
||||
override fun changeInstance(value: String) {
|
||||
currentInstance = value
|
||||
reinitialize()
|
||||
if (currentInstance != value) {
|
||||
currentInstance = value
|
||||
reinitialize()
|
||||
}
|
||||
}
|
||||
|
||||
private fun reinitialize() {
|
||||
|
@ -18,6 +18,7 @@ import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowDropDown
|
||||
import androidx.compose.material.icons.filled.Bookmarks
|
||||
import androidx.compose.material.icons.filled.Clear
|
||||
import androidx.compose.material.icons.filled.ManageAccounts
|
||||
import androidx.compose.material.pullrefresh.PullRefreshIndicator
|
||||
import androidx.compose.material.pullrefresh.pullRefresh
|
||||
@ -131,7 +132,13 @@ object ModalDrawerContent : Tab {
|
||||
user = uiState.user,
|
||||
instance = uiState.instance,
|
||||
autoLoadImages = uiState.autoLoadImages,
|
||||
onOpenChangeInstance = {
|
||||
onOpenChangeInstance = rememberCallback(model) {
|
||||
// suggests current instance
|
||||
model.reduce(
|
||||
ModalDrawerMviModel.Intent.ChangeInstanceName(
|
||||
uiState.instance.orEmpty()
|
||||
)
|
||||
)
|
||||
changeInstanceDialogOpen = true
|
||||
},
|
||||
)
|
||||
@ -432,6 +439,19 @@ private fun ChangeInstanceDialog(
|
||||
)
|
||||
}
|
||||
},
|
||||
trailingIcon = {
|
||||
if (instanceName.isNotEmpty()) {
|
||||
Icon(
|
||||
modifier = Modifier.onClick(
|
||||
rememberCallback {
|
||||
onChangeInstanceName?.invoke("")
|
||||
},
|
||||
),
|
||||
imageVector = Icons.Default.Clear,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
Button(
|
||||
|
@ -12,6 +12,7 @@ import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.SiteRepo
|
||||
import com.github.diegoberaldin.raccoonforlemmy.resources.MR
|
||||
import dev.icerock.moko.resources.desc.desc
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.IO
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.debounce
|
||||
@ -34,6 +35,7 @@ class ModalDrawerViewModel(
|
||||
) : ModalDrawerMviModel,
|
||||
MviModel<ModalDrawerMviModel.Intent, ModalDrawerMviModel.UiState, ModalDrawerMviModel.Effect> by mvi {
|
||||
|
||||
@OptIn(FlowPreview::class)
|
||||
override fun onStarted() {
|
||||
mvi.onStarted()
|
||||
mvi.scope?.launch(Dispatchers.Main) {
|
||||
|
@ -19,6 +19,7 @@ val coreIdentityModule = module {
|
||||
single<ApiConfigurationRepository> {
|
||||
DefaultApiConfigurationRepository(
|
||||
serviceProvider = get(named("default")),
|
||||
keyStore = get(),
|
||||
)
|
||||
}
|
||||
single<IdentityRepository> {
|
||||
|
@ -1,6 +1,7 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.domain.identity.repository
|
||||
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.api.provider.ServiceProvider
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.preferences.TemporaryKeyStore
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.delay
|
||||
@ -10,12 +11,21 @@ import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.isActive
|
||||
|
||||
private const val KEY_LAST_INSTANCE = "lastInstance"
|
||||
|
||||
internal class DefaultApiConfigurationRepository(
|
||||
private val serviceProvider: ServiceProvider,
|
||||
private val keyStore: TemporaryKeyStore,
|
||||
) : ApiConfigurationRepository {
|
||||
|
||||
private val scope = CoroutineScope(SupervisorJob())
|
||||
|
||||
init {
|
||||
val instance = keyStore[KEY_LAST_INSTANCE, ""]
|
||||
.takeIf { it.isNotEmpty() } ?: serviceProvider.currentInstance
|
||||
changeInstance(instance)
|
||||
}
|
||||
|
||||
override val instance = channelFlow {
|
||||
while (isActive) {
|
||||
val value = serviceProvider.currentInstance
|
||||
@ -30,5 +40,6 @@ internal class DefaultApiConfigurationRepository(
|
||||
|
||||
override fun changeInstance(value: String) {
|
||||
serviceProvider.changeInstance(value)
|
||||
keyStore.save(KEY_LAST_INSTANCE, value)
|
||||
}
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ val profileTabModule = module {
|
||||
identityRepository = get(),
|
||||
siteRepository = get(),
|
||||
communityRepository = get(),
|
||||
apiConfigurationRepository = get(),
|
||||
)
|
||||
}
|
||||
factory<ProfileLoggedMviModel> {
|
||||
|
@ -13,6 +13,7 @@ import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Clear
|
||||
import androidx.compose.material.icons.filled.HelpOutline
|
||||
import androidx.compose.material.icons.filled.Visibility
|
||||
import androidx.compose.material.icons.filled.VisibilityOff
|
||||
@ -53,6 +54,7 @@ import com.github.diegoberaldin.raccoonforlemmy.core.commonui.di.getNavigationCo
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.di.getSettingsRepository
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.utils.onClick
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.utils.rememberCallback
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.utils.rememberCallbackArgs
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.profile.di.getLoginBottomSheetViewModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.resources.MR
|
||||
import dev.icerock.moko.resources.compose.localized
|
||||
@ -145,6 +147,7 @@ class LoginBottomSheet : Screen {
|
||||
val passwordFocusRequester = remember { FocusRequester() }
|
||||
val tokenFocusRequester = remember { FocusRequester() }
|
||||
|
||||
// instance name
|
||||
TextField(
|
||||
modifier = Modifier.focusRequester(instanceFocusRequester),
|
||||
label = {
|
||||
@ -163,7 +166,7 @@ class LoginBottomSheet : Screen {
|
||||
autoCorrect = false,
|
||||
imeAction = ImeAction.Next,
|
||||
),
|
||||
onValueChange = { value ->
|
||||
onValueChange = rememberCallbackArgs(model) { value ->
|
||||
model.reduce(LoginBottomSheetMviModel.Intent.SetInstanceName(value))
|
||||
},
|
||||
supportingText = {
|
||||
@ -174,8 +177,24 @@ class LoginBottomSheet : Screen {
|
||||
)
|
||||
}
|
||||
},
|
||||
trailingIcon = {
|
||||
if (uiState.instanceName.isNotEmpty()) {
|
||||
Icon(
|
||||
modifier = Modifier.onClick(
|
||||
rememberCallback(model) {
|
||||
model.reduce(
|
||||
LoginBottomSheetMviModel.Intent.SetInstanceName("")
|
||||
)
|
||||
},
|
||||
),
|
||||
imageVector = Icons.Default.Clear,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
// user name
|
||||
TextField(
|
||||
modifier = Modifier.focusRequester(usernameFocusRequester),
|
||||
label = {
|
||||
@ -194,7 +213,7 @@ class LoginBottomSheet : Screen {
|
||||
autoCorrect = false,
|
||||
imeAction = ImeAction.Next,
|
||||
),
|
||||
onValueChange = { value ->
|
||||
onValueChange = rememberCallbackArgs(model) { value ->
|
||||
model.reduce(LoginBottomSheetMviModel.Intent.SetUsername(value))
|
||||
},
|
||||
supportingText = {
|
||||
@ -207,6 +226,7 @@ class LoginBottomSheet : Screen {
|
||||
},
|
||||
)
|
||||
|
||||
// password
|
||||
var transformation: VisualTransformation by remember {
|
||||
mutableStateOf(PasswordVisualTransformation())
|
||||
}
|
||||
@ -227,7 +247,7 @@ class LoginBottomSheet : Screen {
|
||||
keyboardType = KeyboardType.Password,
|
||||
imeAction = ImeAction.Next,
|
||||
),
|
||||
onValueChange = { value ->
|
||||
onValueChange = rememberCallbackArgs(model) { value ->
|
||||
model.reduce(LoginBottomSheetMviModel.Intent.SetPassword(value))
|
||||
},
|
||||
visualTransformation = transformation,
|
||||
@ -262,6 +282,7 @@ class LoginBottomSheet : Screen {
|
||||
},
|
||||
)
|
||||
|
||||
// TOTP 2FA token
|
||||
TextField(
|
||||
modifier = Modifier.focusRequester(tokenFocusRequester),
|
||||
label = {
|
||||
@ -282,7 +303,7 @@ class LoginBottomSheet : Screen {
|
||||
keyboardType = KeyboardType.Password,
|
||||
imeAction = ImeAction.Done,
|
||||
),
|
||||
onValueChange = { value ->
|
||||
onValueChange = rememberCallbackArgs(model) { value ->
|
||||
model.reduce(LoginBottomSheetMviModel.Intent.SetTotp2faToken(value))
|
||||
},
|
||||
visualTransformation = PasswordVisualTransformation(),
|
||||
@ -290,7 +311,7 @@ class LoginBottomSheet : Screen {
|
||||
Spacer(modifier = Modifier.height(Spacing.m))
|
||||
Button(
|
||||
modifier = Modifier.align(Alignment.CenterHorizontally),
|
||||
onClick = {
|
||||
onClick = rememberCallback(model) {
|
||||
model.reduce(LoginBottomSheetMviModel.Intent.Confirm)
|
||||
},
|
||||
) {
|
||||
|
@ -3,6 +3,7 @@ package com.github.diegoberaldin.raccoonforlemmy.feature.profile.login
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.DefaultMviModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.MviModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.AccountRepository
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain.identity.repository.ApiConfigurationRepository
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain.identity.repository.IdentityRepository
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain.identity.usecase.LoginUseCase
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.SearchResultType
|
||||
@ -17,6 +18,7 @@ import kotlinx.coroutines.launch
|
||||
class LoginBottomSheetViewModel(
|
||||
private val mvi: DefaultMviModel<LoginBottomSheetMviModel.Intent, LoginBottomSheetMviModel.UiState, LoginBottomSheetMviModel.Effect>,
|
||||
private val login: LoginUseCase,
|
||||
private val apiConfigurationRepository: ApiConfigurationRepository,
|
||||
private val identityRepository: IdentityRepository,
|
||||
private val accountRepository: AccountRepository,
|
||||
private val siteRepository: SiteRepository,
|
||||
@ -24,6 +26,14 @@ class LoginBottomSheetViewModel(
|
||||
) : LoginBottomSheetMviModel,
|
||||
MviModel<LoginBottomSheetMviModel.Intent, LoginBottomSheetMviModel.UiState, LoginBottomSheetMviModel.Effect> by mvi {
|
||||
|
||||
override fun onStarted() {
|
||||
mvi.onStarted()
|
||||
val instance = apiConfigurationRepository.instance.value
|
||||
mvi.updateState {
|
||||
it.copy(instanceName = instance)
|
||||
}
|
||||
}
|
||||
|
||||
override fun reduce(intent: LoginBottomSheetMviModel.Intent) {
|
||||
when (intent) {
|
||||
LoginBottomSheetMviModel.Intent.Confirm -> submit()
|
||||
|
@ -85,7 +85,7 @@ fun App() {
|
||||
val currentSettings = settingsRepository.getSettings(accountId)
|
||||
settingsRepository.changeCurrentSettings(currentSettings)
|
||||
val lastActiveAccount = accountRepository.getActive()
|
||||
val lastInstance = lastActiveAccount?.instance
|
||||
val lastInstance = lastActiveAccount?.instance?.takeIf { it.isNotEmpty() }
|
||||
if (lastInstance != null) {
|
||||
apiConfigurationRepository.changeInstance(lastInstance)
|
||||
}
|
||||
@ -134,41 +134,39 @@ fun App() {
|
||||
val uiFontScale by themeRepository.uiFontScale.collectAsState()
|
||||
val navigationCoordinator = remember { getNavigationCoordinator() }
|
||||
LaunchedEffect(navigationCoordinator) {
|
||||
navigationCoordinator.deepLinkUrl
|
||||
.debounce(750)
|
||||
.onEach { url ->
|
||||
val community = getCommunityFromUrl(url)
|
||||
val user = getUserFromUrl(url)
|
||||
val postAndInstance = getPostFromUrl(url)
|
||||
val newScreen = when {
|
||||
community != null -> {
|
||||
CommunityDetailScreen(
|
||||
community = community,
|
||||
otherInstance = community.host,
|
||||
)
|
||||
}
|
||||
|
||||
user != null -> {
|
||||
UserDetailScreen(
|
||||
user = user,
|
||||
otherInstance = user.host,
|
||||
)
|
||||
}
|
||||
|
||||
postAndInstance != null -> {
|
||||
val (post, otherInstance) = postAndInstance
|
||||
PostDetailScreen(
|
||||
post = post,
|
||||
otherInstance = otherInstance,
|
||||
)
|
||||
}
|
||||
|
||||
else -> null
|
||||
navigationCoordinator.deepLinkUrl.debounce(750).onEach { url ->
|
||||
val community = getCommunityFromUrl(url)
|
||||
val user = getUserFromUrl(url)
|
||||
val postAndInstance = getPostFromUrl(url)
|
||||
val newScreen = when {
|
||||
community != null -> {
|
||||
CommunityDetailScreen(
|
||||
community = community,
|
||||
otherInstance = community.host,
|
||||
)
|
||||
}
|
||||
if (newScreen != null) {
|
||||
navigationCoordinator.getRootNavigator()?.push(newScreen)
|
||||
|
||||
user != null -> {
|
||||
UserDetailScreen(
|
||||
user = user,
|
||||
otherInstance = user.host,
|
||||
)
|
||||
}
|
||||
}.launchIn(this)
|
||||
|
||||
postAndInstance != null -> {
|
||||
val (post, otherInstance) = postAndInstance
|
||||
PostDetailScreen(
|
||||
post = post,
|
||||
otherInstance = otherInstance,
|
||||
)
|
||||
}
|
||||
|
||||
else -> null
|
||||
}
|
||||
if (newScreen != null) {
|
||||
navigationCoordinator.getRootNavigator()?.push(newScreen)
|
||||
}
|
||||
}.launchIn(this)
|
||||
}
|
||||
|
||||
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
|
||||
@ -224,8 +222,7 @@ fun App() {
|
||||
) {
|
||||
BottomSheetNavigator(
|
||||
sheetShape = RoundedCornerShape(
|
||||
topStart = CornerSize.xl,
|
||||
topEnd = CornerSize.xl
|
||||
topStart = CornerSize.xl, topEnd = CornerSize.xl
|
||||
),
|
||||
sheetBackgroundColor = MaterialTheme.colorScheme.background,
|
||||
) { bottomNavigator ->
|
||||
@ -240,13 +237,10 @@ fun App() {
|
||||
}
|
||||
},
|
||||
) {
|
||||
Navigator(
|
||||
screen = MainScreen,
|
||||
onBackPressed = {
|
||||
val callback = navigationCoordinator.getCanGoBackCallback()
|
||||
callback?.let { it() } ?: true
|
||||
}
|
||||
) { navigator ->
|
||||
Navigator(screen = MainScreen, onBackPressed = {
|
||||
val callback = navigationCoordinator.getCanGoBackCallback()
|
||||
callback?.let { it() } ?: true
|
||||
}) { navigator ->
|
||||
LaunchedEffect(Unit) {
|
||||
navigationCoordinator.setRootNavigator(navigator)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user