@@ -17,9 +17,12 @@ import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridItemSpan
1717import androidx.compose.foundation.text.input.TextFieldLineLimits
1818import androidx.compose.foundation.text.input.rememberTextFieldState
1919import androidx.compose.material3.Button
20- import androidx.compose.material3.CircularProgressIndicator
20+ import androidx.compose.material3.ButtonGroup
21+ import androidx.compose.material3.CircularWavyProgressIndicator
2122import androidx.compose.material3.ExperimentalMaterial3Api
23+ import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
2224import androidx.compose.material3.IconButton
25+ import androidx.compose.material3.LinearWavyProgressIndicator
2326import androidx.compose.material3.MaterialTheme
2427import androidx.compose.material3.OutlinedSecureTextField
2528import androidx.compose.material3.OutlinedTextField
@@ -29,7 +32,9 @@ import androidx.compose.runtime.Composable
2932import androidx.compose.runtime.LaunchedEffect
3033import androidx.compose.runtime.derivedStateOf
3134import androidx.compose.runtime.getValue
35+ import androidx.compose.runtime.mutableStateOf
3236import androidx.compose.runtime.remember
37+ import androidx.compose.runtime.setValue
3338import androidx.compose.runtime.snapshotFlow
3439import androidx.compose.ui.Alignment
3540import androidx.compose.ui.Modifier
@@ -73,7 +78,7 @@ import kotlinx.coroutines.FlowPreview
7378import kotlinx.coroutines.flow.distinctUntilChanged
7479import moe.tlaster.precompose.molecule.producePresenter
7580
76- @OptIn(ExperimentalMaterial3Api ::class )
81+ @OptIn(ExperimentalMaterial3Api ::class , ExperimentalMaterial3ExpressiveApi :: class )
7782@Composable
7883internal fun ServiceSelectScreen (
7984 onXQT : () -> Unit ,
@@ -178,7 +183,7 @@ internal fun ServiceSelectScreen(
178183 contentDescription = null ,
179184 )
180185 }.onLoading {
181- CircularProgressIndicator (
186+ CircularWavyProgressIndicator (
182187 modifier = Modifier .size(24 .dp),
183188 )
184189 }
@@ -194,52 +199,117 @@ internal fun ServiceSelectScreen(
194199 when (state.detectedPlatformType.takeSuccess()) {
195200 null -> Unit
196201 PlatformType .Bluesky -> {
202+ val oauthString = stringResource(id = R .string.bluesky_login_oauth_button)
203+ val passwordString =
204+ stringResource(id = R .string.bluesky_login_use_password_button)
205+ ButtonGroup (
206+ overflowIndicator = {},
207+ ) {
208+ toggleableItem(
209+ ! state.blueskyInputState.usePasswordLogin,
210+ onCheckedChange = {
211+ state.blueskyLoginState.clear()
212+ state.blueskyOauthLoginState.clear()
213+ state.blueskyInputState.setUsePasswordLogin(! it)
214+ },
215+ label = oauthString,
216+ )
217+ toggleableItem(
218+ state.blueskyInputState.usePasswordLogin,
219+ onCheckedChange = {
220+ state.blueskyLoginState.clear()
221+ state.blueskyOauthLoginState.clear()
222+ state.blueskyInputState.setUsePasswordLogin(it)
223+ },
224+ label = passwordString,
225+ )
226+ }
197227 OutlinedTextField (
198228 state = state.blueskyInputState.username,
199229 label = {
200230 Text (text = stringResource(id = R .string.bluesky_login_username_hint))
201231 },
202- enabled = ! state.blueskyLoginState.loading,
232+ enabled =
233+ ! state.blueskyOauthLoginState.loading &&
234+ ! state.blueskyLoginState.loading,
203235 modifier =
204236 Modifier
205237 .width(300 .dp),
206238 lineLimits = TextFieldLineLimits .SingleLine ,
207239 )
208- OutlinedSecureTextField (
209- state = state.blueskyInputState.password,
210- label = {
211- Text (text = stringResource(id = R .string.bluesky_login_password_hint))
212- },
213- enabled = ! state.blueskyLoginState.loading,
214- modifier =
215- Modifier
216- .width(300 .dp),
217- // lineLimits = TextFieldLineLimits.SingleLine,
218- onKeyboardAction = {
219- state.blueskyLoginState.login(
220- " https://${state.instanceInputState.text} " ,
221- state.blueskyInputState.username.text
222- .toString(),
223- state.blueskyInputState.password.text
224- .toString(),
225- )
226- },
227- )
240+ AnimatedVisibility (state.blueskyInputState.usePasswordLogin) {
241+ OutlinedSecureTextField (
242+ state = state.blueskyInputState.password,
243+ label = {
244+ Text (text = stringResource(id = R .string.bluesky_login_password_hint))
245+ },
246+ enabled = ! state.blueskyLoginState.loading,
247+ modifier =
248+ Modifier
249+ .width(300 .dp),
250+ )
251+ }
252+ AnimatedVisibility (state.blueskyLoginState.require2FA && state.blueskyInputState.usePasswordLogin) {
253+ OutlinedTextField (
254+ state = state.blueskyInputState.authFactorToken,
255+ label = {
256+ Text (text = stringResource(id = R .string.bluesky_login_auth_factor_token_hint))
257+ },
258+ enabled = ! state.blueskyLoginState.loading,
259+ modifier =
260+ Modifier
261+ .width(300 .dp),
262+ lineLimits = TextFieldLineLimits .SingleLine ,
263+ )
264+ }
265+ if (! state.blueskyInputState.usePasswordLogin) {
266+ OnNewIntent {
267+ state.blueskyOauthLoginState.resume(it.dataString.orEmpty())
268+ }
269+ }
270+
228271 Button (
229272 onClick = {
230- state.blueskyLoginState.login(
231- " https://${state.instanceInputState.text} " ,
232- state.blueskyInputState.username.text
233- .toString(),
234- state.blueskyInputState.password.text
235- .toString(),
236- )
273+ if (state.blueskyInputState.usePasswordLogin) {
274+ state.blueskyLoginState.login(
275+ baseUrl = state.instanceInputState.text.toString(),
276+ username =
277+ state.blueskyInputState.username.text
278+ .toString(),
279+ password =
280+ state.blueskyInputState.password.text
281+ .toString(),
282+ authFactorToken =
283+ state.blueskyInputState.authFactorToken.text
284+ .toString(),
285+ )
286+ } else {
287+ state.blueskyOauthLoginState.login(
288+ userName =
289+ state.blueskyInputState.username.text
290+ .toString(),
291+ launchUrl = uriHandler::openUri,
292+ )
293+ }
237294 },
238295 modifier = Modifier .width(300 .dp),
239- enabled = state.blueskyInputState.canLogin && ! state.blueskyLoginState.loading,
296+ enabled =
297+ state.blueskyInputState.canLogin &&
298+ (
299+ ! state.blueskyOauthLoginState.loading &&
300+ ! state.blueskyLoginState.loading
301+ ),
240302 ) {
241303 Text (text = stringResource(id = R .string.login_button))
242304 }
305+ if (state.blueskyOauthLoginState.error != null ) {
306+ Text (
307+ text = state.blueskyOauthLoginState.error.toString(),
308+ )
309+ }
310+ if (state.blueskyOauthLoginState.loading) {
311+ LinearWavyProgressIndicator ()
312+ }
243313 }
244314
245315 PlatformType .Misskey -> {
@@ -251,7 +321,7 @@ internal fun ServiceSelectScreen(
251321 Text (
252322 text = stringResource(id = R .string.mastodon_login_verify_message),
253323 )
254- CircularProgressIndicator ()
324+ LinearWavyProgressIndicator ()
255325 }?.onError {
256326 Text (text = it.message ? : " Unknown error" )
257327 } ? : run {
@@ -284,7 +354,7 @@ internal fun ServiceSelectScreen(
284354 Text (
285355 text = stringResource(id = R .string.mastodon_login_verify_message),
286356 )
287- CircularProgressIndicator ()
357+ LinearWavyProgressIndicator ()
288358 }?.onError {
289359 Text (text = it.message ? : " Unknown error" )
290360 } ? : run {
@@ -479,10 +549,10 @@ private fun serviceSelectPresenter(onBack: (() -> Unit)?) =
479549 state.setFilter(it.toString())
480550 }
481551 }
482- val blueskyLoginState = blueskyLoginPresenter ()
552+ val blueskyInputState = blueskyInputPresenter ()
483553 object : ServiceSelectState by state {
484554 val instanceInputState = instanceInputState
485- val blueskyInputState = blueskyLoginState
555+ val blueskyInputState = blueskyInputState
486556
487557 fun selectInstance (instance : UiInstance ) {
488558 instanceInputState.edit {
@@ -501,18 +571,31 @@ private fun serviceSelectPresenter(onBack: (() -> Unit)?) =
501571 }
502572
503573@Composable
504- private fun blueskyLoginPresenter () =
574+ private fun blueskyInputPresenter () =
505575 run {
576+ var usePasswordLogin by remember { mutableStateOf(false ) }
506577 val username = rememberTextFieldState()
507578 val password = rememberTextFieldState()
579+ val authFactorToken = rememberTextFieldState()
508580 val canLogin by remember(username, password) {
509581 derivedStateOf {
510- username.text.isNotEmpty() && password.text.isNotEmpty()
582+ username.text.isNotEmpty() &&
583+ if (usePasswordLogin) {
584+ password.text.isNotEmpty()
585+ } else {
586+ true
587+ }
511588 }
512589 }
513590 object {
514591 val username = username
515592 val password = password
593+ val authFactorToken = authFactorToken
594+ val usePasswordLogin = usePasswordLogin
516595 val canLogin = canLogin
596+
597+ fun setUsePasswordLogin (value : Boolean ) {
598+ usePasswordLogin = value
599+ }
517600 }
518601 }
0 commit comments