Refine CookieSignInScreen
This commit is contained in:
1 parent
9159f4616f
commit
9284008afe
2 files changed
+215
-25
No files matched your search
@@ -9,10 +9,16 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Info
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
@@ -20,28 +26,169 @@ import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.dimensionResource
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.style.TextDecoration
|
||||
import androidx.compose.ui.text.withStyle
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.navigation.NavController
|
||||
import com.hippo.ehviewer.EhApplication
|
||||
import com.hippo.ehviewer.R
|
||||
import com.hippo.ehviewer.client.EhClient
|
||||
import com.hippo.ehviewer.client.EhClient.METHOD_GET_PROFILE
|
||||
import com.hippo.ehviewer.client.EhCookieStore
|
||||
import com.hippo.ehviewer.client.EhUrl
|
||||
import com.hippo.ehviewer.client.EhUtils
|
||||
import com.hippo.ehviewer.ui.LoginActivity.Companion.SELECT_SITE_ROUTE_NAME
|
||||
import com.hippo.ehviewer.ui.scene.getProfile
|
||||
import com.hippo.util.ExceptionUtils
|
||||
import com.hippo.util.getClipboardManager
|
||||
import com.hippo.util.getTextFromClipboard
|
||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||
import eu.kanade.tachiyomi.util.lang.withUIContext
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import okhttp3.Cookie
|
||||
import java.util.Locale
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun CookieSignInScene(navController: NavController) {
|
||||
val context = LocalContext.current
|
||||
val focusManager = LocalFocusManager.current
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
var isProgressIndicatorVisible by rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
var ipbMemberId by rememberSaveable { mutableStateOf("") }
|
||||
var ipbPassHash by rememberSaveable { mutableStateOf("") }
|
||||
var igneous by rememberSaveable { mutableStateOf("") }
|
||||
|
||||
var ipbMemberIdErrorState by rememberSaveable { mutableStateOf(false) }
|
||||
var ipbPassHashErrorState by rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
var signInJob by remember { mutableStateOf<Job?>(null) }
|
||||
|
||||
var showErrorDialog by rememberSaveable { mutableStateOf(false) }
|
||||
var loginErrorException by remember { mutableStateOf<Throwable?>(null) }
|
||||
|
||||
val noCookies = stringResource(R.string.from_clipboard_error)
|
||||
|
||||
fun newCookie(name: String, value: String, domain: String): Cookie {
|
||||
return Cookie.Builder().name(name).value(value).domain(domain).expiresAt(Long.MAX_VALUE)
|
||||
.build()
|
||||
}
|
||||
|
||||
fun storeCookie(id: String, hash: String, igneous: String) {
|
||||
EhUtils.signOut()
|
||||
val store = EhApplication.ehCookieStore
|
||||
store.addCookie(newCookie(EhCookieStore.KEY_IPD_MEMBER_ID, id, EhUrl.DOMAIN_E))
|
||||
store.addCookie(newCookie(EhCookieStore.KEY_IPD_MEMBER_ID, id, EhUrl.DOMAIN_EX))
|
||||
store.addCookie(newCookie(EhCookieStore.KEY_IPD_PASS_HASH, hash, EhUrl.DOMAIN_E))
|
||||
store.addCookie(newCookie(EhCookieStore.KEY_IPD_PASS_HASH, hash, EhUrl.DOMAIN_EX))
|
||||
if (igneous.isBlank()) return
|
||||
store.addCookie(newCookie(EhCookieStore.KEY_IGNEOUS, igneous, EhUrl.DOMAIN_E))
|
||||
store.addCookie(newCookie(EhCookieStore.KEY_IGNEOUS, igneous, EhUrl.DOMAIN_EX))
|
||||
}
|
||||
|
||||
fun login() {
|
||||
signInJob?.cancel()
|
||||
if (ipbMemberId.isBlank()) {
|
||||
ipbMemberIdErrorState = true
|
||||
return
|
||||
} else {
|
||||
ipbMemberIdErrorState = false
|
||||
}
|
||||
if (ipbPassHash.isBlank()) {
|
||||
ipbPassHashErrorState = true
|
||||
return
|
||||
} else {
|
||||
ipbPassHashErrorState = false
|
||||
}
|
||||
focusManager.clearFocus()
|
||||
storeCookie(ipbMemberId, ipbPassHash, igneous)
|
||||
isProgressIndicatorVisible = true
|
||||
signInJob = coroutineScope.launchIO {
|
||||
runCatching {
|
||||
EhClient.execute(METHOD_GET_PROFILE)
|
||||
}.onSuccess {
|
||||
launchIO {
|
||||
getProfile()
|
||||
}
|
||||
withUIContext {
|
||||
navController.navigate(SELECT_SITE_ROUTE_NAME)
|
||||
}
|
||||
}.onFailure {
|
||||
EhApplication.ehCookieStore.signOut()
|
||||
if (it !is CancellationException) {
|
||||
loginErrorException = it
|
||||
showErrorDialog = true
|
||||
isProgressIndicatorVisible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun fillCookiesFromClipboard() {
|
||||
focusManager.clearFocus()
|
||||
val text = context.getClipboardManager().getTextFromClipboard(context)
|
||||
if (text == null) {
|
||||
coroutineScope.launch { snackbarHostState.showSnackbar(noCookies) }
|
||||
return
|
||||
}
|
||||
runCatching {
|
||||
val kvs: Array<String> = if (text.contains(";")) {
|
||||
text.split(";".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||
} else if (text.contains("\n")) {
|
||||
text.split("\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||
} else {
|
||||
coroutineScope.launch { snackbarHostState.showSnackbar(noCookies) }
|
||||
return
|
||||
}
|
||||
if (kvs.size < 3) {
|
||||
coroutineScope.launch { snackbarHostState.showSnackbar(noCookies) }
|
||||
return
|
||||
}
|
||||
for (s in kvs) {
|
||||
val kv: Array<String> = if (s.contains("=")) {
|
||||
s.split("=".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||
} else if (s.contains(":")) {
|
||||
s.split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
if (kv.size != 2) {
|
||||
continue
|
||||
}
|
||||
when (kv[0].trim { it <= ' ' }.lowercase(Locale.getDefault())) {
|
||||
"ipb_member_id" -> ipbMemberId = kv[1].trim { it <= ' ' }
|
||||
"ipb_pass_hash" -> ipbPassHash = kv[1].trim { it <= ' ' }
|
||||
"igneous" -> igneous = kv[1].trim { it <= ' ' }
|
||||
}
|
||||
}
|
||||
login()
|
||||
}.onFailure {
|
||||
it.printStackTrace()
|
||||
coroutineScope.launch { snackbarHostState.showSnackbar(noCookies) }
|
||||
}
|
||||
}
|
||||
|
||||
Box(
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
@@ -49,11 +196,11 @@ fun CookieSignInScene(navController: NavController) {
|
||||
snackbarHost = {
|
||||
SnackbarHost(hostState = snackbarHostState)
|
||||
}
|
||||
) {
|
||||
) { padding ->
|
||||
Column(
|
||||
Modifier
|
||||
.padding(dimensionResource(id = R.dimen.keyline_margin))
|
||||
.padding(it)
|
||||
.padding(padding)
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState()),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
@@ -73,44 +220,59 @@ fun CookieSignInScene(navController: NavController) {
|
||||
fontSize = 16.sp
|
||||
)
|
||||
OutlinedTextField(
|
||||
value = "",
|
||||
onValueChange = { },
|
||||
value = ipbMemberId,
|
||||
onValueChange = { ipbMemberId = it.trim { char -> char <= ' ' } },
|
||||
Modifier.width(dimensionResource(id = R.dimen.single_max_width)),
|
||||
label = {
|
||||
Text(text = "")
|
||||
label = { Text(text = "ipb_member_id") },
|
||||
keyboardOptions = KeyboardOptions(
|
||||
imeAction = ImeAction.Next
|
||||
),
|
||||
supportingText = { if (ipbMemberIdErrorState) Text(stringResource(R.string.text_is_empty)) },
|
||||
trailingIcon = {
|
||||
if (ipbMemberIdErrorState) Icon(
|
||||
imageVector = Icons.Filled.Info,
|
||||
contentDescription = null
|
||||
)
|
||||
},
|
||||
isError = ipbMemberIdErrorState,
|
||||
singleLine = true
|
||||
)
|
||||
OutlinedTextField(
|
||||
value = "",
|
||||
onValueChange = { },
|
||||
value = ipbPassHash,
|
||||
onValueChange = { ipbPassHash = it.trim { char -> char <= ' ' } },
|
||||
Modifier.width(dimensionResource(id = R.dimen.single_max_width)),
|
||||
label = {
|
||||
Text(text = "")
|
||||
label = { Text(text = "ipb_pass_hash") },
|
||||
keyboardOptions = KeyboardOptions(
|
||||
imeAction = ImeAction.Next
|
||||
),
|
||||
supportingText = { if (ipbPassHashErrorState) Text(stringResource(R.string.text_is_empty)) },
|
||||
trailingIcon = {
|
||||
if (ipbPassHashErrorState) Icon(
|
||||
imageVector = Icons.Filled.Info,
|
||||
contentDescription = null
|
||||
)
|
||||
},
|
||||
isError = ipbPassHashErrorState,
|
||||
singleLine = true
|
||||
)
|
||||
OutlinedTextField(
|
||||
value = "",
|
||||
onValueChange = { },
|
||||
value = igneous,
|
||||
onValueChange = { igneous = it.trim { char -> char <= ' ' } },
|
||||
Modifier.width(dimensionResource(id = R.dimen.single_max_width)),
|
||||
label = {
|
||||
Text(text = "")
|
||||
},
|
||||
label = { Text(text = "igneous") },
|
||||
keyboardActions = KeyboardActions(
|
||||
onDone = { login() }
|
||||
),
|
||||
singleLine = true
|
||||
)
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
Button(
|
||||
onClick = {
|
||||
coroutineScope.launch {
|
||||
snackbarHostState.showSnackbar("1")
|
||||
}
|
||||
},
|
||||
onClick = { login() },
|
||||
Modifier.fillMaxWidth()
|
||||
) {
|
||||
Text(text = stringResource(id = android.R.string.ok))
|
||||
}
|
||||
TextButton(onClick = { /*TODO*/ }) {
|
||||
TextButton(onClick = { fillCookiesFromClipboard() }) {
|
||||
Text(
|
||||
text = buildAnnotatedString {
|
||||
withStyle(
|
||||
@@ -125,6 +287,33 @@ fun CookieSignInScene(navController: NavController) {
|
||||
}
|
||||
}
|
||||
}
|
||||
CircularProgressIndicator()
|
||||
if (isProgressIndicatorVisible)
|
||||
CircularProgressIndicator()
|
||||
if (showErrorDialog) {
|
||||
AlertDialog(
|
||||
onDismissRequest = {
|
||||
showErrorDialog = false
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
showErrorDialog = false
|
||||
}) {
|
||||
Text(text = stringResource(id = R.string.get_it))
|
||||
}
|
||||
},
|
||||
title = {
|
||||
Text(text = stringResource(id = R.string.sign_in_failed))
|
||||
},
|
||||
text = {
|
||||
Text(
|
||||
text =
|
||||
"""
|
||||
${ExceptionUtils.getReadableString(loginErrorException!!)}
|
||||
${stringResource(R.string.wrong_cookie_warning)}
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,7 @@ import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
@@ -77,12 +78,12 @@ fun SignInScreen(navController: NavController) {
|
||||
var showUsernameError by rememberSaveable { mutableStateOf(false) }
|
||||
var showPasswordError by rememberSaveable { mutableStateOf(false) }
|
||||
var showErrorDialog by rememberSaveable { mutableStateOf(false) }
|
||||
var loginErrorException by rememberSaveable { mutableStateOf<Throwable?>(null) }
|
||||
var loginErrorException by remember { mutableStateOf<Throwable?>(null) }
|
||||
var username by rememberSaveable { mutableStateOf("") }
|
||||
var password by rememberSaveable { mutableStateOf("") }
|
||||
var passwordHidden by rememberSaveable { mutableStateOf(true) }
|
||||
val context = LocalContext.current
|
||||
var signInJob by rememberSaveable { mutableStateOf<Job?>(null) }
|
||||
var signInJob by remember { mutableStateOf<Job?>(null) }
|
||||
|
||||
// Basic login request
|
||||
fun signIn() {
|
||||
@@ -113,8 +114,8 @@ fun SignInScreen(navController: NavController) {
|
||||
withUIContext {
|
||||
focusManager.clearFocus()
|
||||
isProgressIndicatorVisible = false
|
||||
showErrorDialog = true
|
||||
loginErrorException = it
|
||||
showErrorDialog = true
|
||||
}
|
||||
}.onSuccess {
|
||||
// This composable is to be finished
|
||||
|
||||
Reference in new issue
Block a user