From 31794af1fdb50e8e09fc71b208afad35687d0daf Mon Sep 17 00:00:00 2001 From: Konrad Pozniak Date: Mon, 21 Sep 2020 16:38:22 +0200 Subject: [PATCH] improve login --- app/src/main/AndroidManifest.xml | 4 +- .../components/login/LoginActivity.kt | 71 ++++++++++++++---- .../pixelcat/components/login/LoginState.kt | 8 ++- .../components/login/LoginViewModel.kt | 72 ++++++++++--------- .../components/login/LoginWebViewActivity.kt | 22 ++++-- .../pixelcat/components/main/MainActivity.kt | 5 ++ .../notifications/NotificationsViewModel.kt | 1 - .../components/splash/SplashActivity.kt | 8 +-- .../at/connyduck/pixelcat/config/Config.kt | 2 +- .../pixelcat/dagger/ActivityModule.kt | 4 ++ .../db/entitity/TimelineAccountEntity.kt | 2 +- .../at/connyduck/pixelcat/model/Account.kt | 29 ++------ .../main/res/color/edit_text_color_white.xml | 5 ++ app/src/main/res/layout/activity_login.xml | 46 ++++++------ .../res/layout/activity_login_web_view.xml | 36 +++++++--- app/src/main/res/values-v27/styles.xml | 8 +++ app/src/main/res/values/strings.xml | 2 + app/src/main/res/values/styles.xml | 2 +- 18 files changed, 209 insertions(+), 118 deletions(-) create mode 100644 app/src/main/res/color/edit_text_color_white.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 6d9c63f..a1911b7 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -26,8 +26,8 @@ - - + { +@FlowPreview +@ExperimentalCoroutinesApi +class LoginActivity : BaseActivity() { @Inject lateinit var viewModelFactory: ViewModelFactory - private val loginViewModel: LoginViewModel by viewModels { viewModelFactory } + private val viewModel: LoginViewModel by viewModels { viewModelFactory } private val binding by viewBinding(ActivityLoginBinding::inflate) @@ -54,7 +62,7 @@ class LoginActivity : BaseActivity(), Observer { setContentView(binding.root) - ViewCompat.setOnApplyWindowInsetsListener(binding.loginContainer) { _, insets -> + ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _, insets -> val top = insets.getInsets(systemBars()).top val toolbarParams = binding.loginToolbar.layoutParams as ViewGroup.MarginLayoutParams toolbarParams.topMargin = top @@ -67,10 +75,14 @@ class LoginActivity : BaseActivity(), Observer { setDisplayShowTitleEnabled(false) } - loginViewModel.loginState.observe(this, this) + lifecycleScope.launch { + viewModel.observe().collect { loginModel -> + onChanged(loginModel) + } + } binding.loginButton.setOnClickListener { - loginViewModel.startLogin(binding.loginInput.text.toString()) + viewModel.startLogin(binding.loginInput.text.toString()) } } @@ -78,13 +90,21 @@ class LoginActivity : BaseActivity(), Observer { val authCode = data?.getStringExtra(LoginWebViewActivity.RESULT_AUTHORIZATION_CODE) if (requestCode == REQUEST_CODE && resultCode == Activity.RESULT_OK && !authCode.isNullOrEmpty()) { - loginViewModel.authCode(authCode) + viewModel.authCode(authCode) return } super.onActivityResult(requestCode, resultCode, data) } + override fun onStart() { + super.onStart() + + if (!intent.hasExtra(LoginWebViewActivity.RESULT_AUTHORIZATION_CODE)) { + viewModel.removeError() + } + } + override fun onCreateOptionsMenu(menu: Menu): Boolean { menuInflater.inflate(R.menu.login, menu) return super.onCreateOptionsMenu(menu) @@ -105,7 +125,8 @@ class LoginActivity : BaseActivity(), Observer { return super.onOptionsItemSelected(item) } - override fun onChanged(loginModel: LoginModel?) { + private fun onChanged(loginModel: LoginModel?) { + binding.loginInput.setText(loginModel?.input) if (loginModel == null) { @@ -113,23 +134,47 @@ class LoginActivity : BaseActivity(), Observer { } when (loginModel.state) { - LoginState.NO_ERROR -> binding.loginInputLayout.error = null - LoginState.AUTH_ERROR -> binding.loginInputLayout.error = "auth error" - LoginState.INVALID_DOMAIN -> binding.loginInputLayout.error = "invalid domain" - LoginState.NETWORK_ERROR -> binding.loginInputLayout.error = "network error" + LoginState.NO_ERROR -> { + binding.loginInputLayout.error = null + setLoading(false) + } + LoginState.AUTH_ERROR -> { + binding.loginInputLayout.error = "auth error" + setLoading(false) + } + LoginState.INVALID_DOMAIN -> { + binding.loginInputLayout.error = "invalid domain" + setLoading(false) + } + LoginState.NETWORK_ERROR -> { + binding.loginInputLayout.error = "network error" + setLoading(false) + } LoginState.LOADING -> { + setLoading(true) } LoginState.SUCCESS -> { + setLoading(true) startActivityForResult(LoginWebViewActivity.newIntent(loginModel.domain!!, loginModel.clientId!!, loginModel.clientSecret!!, this), REQUEST_CODE) } LoginState.SUCCESS_FINAL -> { - startActivity(Intent(this, MainActivity::class.java)) // TODO dont create intent here + setLoading(true) + startActivity(MainActivity.newIntent(this)) finish() } } } + private fun setLoading(loading: Boolean) { + binding.loginLoading.visible = loading + binding.loginImageView.visible = !loading + binding.loginInputLayout.visible = !loading + binding.loginButton.visible = !loading + } + companion object { private const val REQUEST_CODE = 14 + + fun newIntent(context: Context) = Intent(context, LoginActivity::class.java) } } diff --git a/app/src/main/kotlin/at/connyduck/pixelcat/components/login/LoginState.kt b/app/src/main/kotlin/at/connyduck/pixelcat/components/login/LoginState.kt index bc846d5..c77cad8 100644 --- a/app/src/main/kotlin/at/connyduck/pixelcat/components/login/LoginState.kt +++ b/app/src/main/kotlin/at/connyduck/pixelcat/components/login/LoginState.kt @@ -32,5 +32,11 @@ data class LoginModel( ) : Parcelable enum class LoginState { // TODO rename this stuff so it makes sense - LOADING, NO_ERROR, NETWORK_ERROR, INVALID_DOMAIN, AUTH_ERROR, SUCCESS, SUCCESS_FINAL + LOADING, + NO_ERROR, + NETWORK_ERROR, + INVALID_DOMAIN, + AUTH_ERROR, + SUCCESS, + SUCCESS_FINAL } diff --git a/app/src/main/kotlin/at/connyduck/pixelcat/components/login/LoginViewModel.kt b/app/src/main/kotlin/at/connyduck/pixelcat/components/login/LoginViewModel.kt index 07a2a12..0f23df5 100644 --- a/app/src/main/kotlin/at/connyduck/pixelcat/components/login/LoginViewModel.kt +++ b/app/src/main/kotlin/at/connyduck/pixelcat/components/login/LoginViewModel.kt @@ -19,52 +19,54 @@ package at.connyduck.pixelcat.components.login -import androidx.annotation.MainThread -import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import at.connyduck.pixelcat.config.Config import at.connyduck.pixelcat.db.AccountManager import at.connyduck.pixelcat.db.entitity.AccountAuthData import at.connyduck.pixelcat.network.FediverseApi +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.channels.ConflatedBroadcastChannel +import kotlinx.coroutines.flow.asFlow import kotlinx.coroutines.launch import okhttp3.HttpUrl import java.util.Locale import javax.inject.Inject +@FlowPreview +@ExperimentalCoroutinesApi class LoginViewModel @Inject constructor( private val fediverseApi: FediverseApi, private val accountManager: AccountManager ) : ViewModel() { - val loginState = MutableLiveData().apply { - value = LoginModel(state = LoginState.NO_ERROR) - } + private val loginState = ConflatedBroadcastChannel(LoginModel(state = LoginState.NO_ERROR)) + + fun observe() = loginState.asFlow() - @MainThread fun startLogin(input: String) { - - val domainInput = canonicalizeDomain(input) - - try { - HttpUrl.Builder().host(domainInput).scheme("https").build() - } catch (e: IllegalArgumentException) { - loginState.value = LoginModel(input, LoginState.INVALID_DOMAIN) - return - } - - val exceptionMatch = Config.domainExceptions.any { exception -> - domainInput.equals(exception, true) || domainInput.endsWith(".$exception", true) - } - - if (exceptionMatch) { - loginState.value = LoginModel(input, LoginState.AUTH_ERROR) - return - } - - loginState.value = LoginModel(input, LoginState.LOADING) - viewModelScope.launch { + val domainInput = canonicalizeDomain(input) + + try { + HttpUrl.Builder().host(domainInput).scheme("https").build() + } catch (e: IllegalArgumentException) { + loginState.send(LoginModel(input, LoginState.INVALID_DOMAIN)) + return@launch + } + + val exceptionMatch = Config.domainExceptions.any { exception -> + domainInput.equals(exception, true) || domainInput.endsWith(".$exception", true) + } + + if (exceptionMatch) { + loginState.send(LoginModel(input, LoginState.AUTH_ERROR)) + return@launch + } + + loginState.send(LoginModel(input, LoginState.LOADING)) + fediverseApi.authenticateAppAsync( domain = domainInput, clientName = "Pixelcat", @@ -73,19 +75,18 @@ class LoginViewModel @Inject constructor( scopes = Config.oAuthScopes ).fold( { appData -> - loginState.postValue(LoginModel(input, LoginState.SUCCESS, domainInput, appData.clientId, appData.clientSecret)) + loginState.send(LoginModel(input, LoginState.SUCCESS, domainInput, appData.clientId, appData.clientSecret)) }, { - loginState.postValue(LoginModel(input, LoginState.AUTH_ERROR)) + loginState.send(LoginModel(input, LoginState.AUTH_ERROR)) } ) } } - @MainThread fun authCode(authCode: String) { viewModelScope.launch { - val loginModel = loginState.value!! + val loginModel = loginState.value fediverseApi.fetchOAuthToken( domain = loginModel.domain!!, @@ -106,14 +107,21 @@ class LoginViewModel @Inject constructor( clientSecret = loginModel.clientSecret ) accountManager.addAccount(loginModel.domain, authData) - loginState.postValue(loginState.value?.copy(state = LoginState.SUCCESS_FINAL)) + loginState.send(loginState.value.copy(state = LoginState.SUCCESS_FINAL)) }, { + loginState.send(loginState.value.copy(state = LoginState.AUTH_ERROR)) } ) } } + fun removeError() { + viewModelScope.launch { + loginState.send(loginState.value.copy(state = LoginState.NO_ERROR)) + } + } + private fun canonicalizeDomain(domain: String): String { // Strip any schemes out. var s = domain.replaceFirst("http://", "") diff --git a/app/src/main/kotlin/at/connyduck/pixelcat/components/login/LoginWebViewActivity.kt b/app/src/main/kotlin/at/connyduck/pixelcat/components/login/LoginWebViewActivity.kt index b409340..c8e7968 100644 --- a/app/src/main/kotlin/at/connyduck/pixelcat/components/login/LoginWebViewActivity.kt +++ b/app/src/main/kotlin/at/connyduck/pixelcat/components/login/LoginWebViewActivity.kt @@ -23,23 +23,35 @@ import android.app.Activity import android.content.Context import android.content.Intent import android.net.Uri -import androidx.appcompat.app.AppCompatActivity import android.os.Bundle +import android.view.ViewGroup import android.webkit.WebResourceRequest import android.webkit.WebView import at.connyduck.pixelcat.config.Config import android.webkit.WebViewClient +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat +import at.connyduck.pixelcat.components.general.BaseActivity import at.connyduck.pixelcat.databinding.ActivityLoginWebViewBinding -class LoginWebViewActivity : AppCompatActivity() { - - private lateinit var binding: ActivityLoginWebViewBinding +class LoginWebViewActivity : BaseActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - binding = ActivityLoginWebViewBinding.inflate(layoutInflater) + val binding = ActivityLoginWebViewBinding.inflate(layoutInflater) setContentView(binding.root) + ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _, insets -> + val top = insets.getInsets(WindowInsetsCompat.Type.systemBars()).top + val toolbarParams = binding.loginToolbar.layoutParams as ViewGroup.MarginLayoutParams + toolbarParams.topMargin = top + WindowInsetsCompat.CONSUMED + } + + binding.loginToolbar.setNavigationOnClickListener { + onBackPressed() + } + val domain = intent.getStringExtra(EXTRA_DOMAIN)!! val clientId = intent.getStringExtra(EXTRA_CLIENT_ID)!! val clientSecret = intent.getStringExtra(EXTRA_CLIENT_SECRET)!! diff --git a/app/src/main/kotlin/at/connyduck/pixelcat/components/main/MainActivity.kt b/app/src/main/kotlin/at/connyduck/pixelcat/components/main/MainActivity.kt index 0aba3af..69c37bb 100644 --- a/app/src/main/kotlin/at/connyduck/pixelcat/components/main/MainActivity.kt +++ b/app/src/main/kotlin/at/connyduck/pixelcat/components/main/MainActivity.kt @@ -20,6 +20,7 @@ package at.connyduck.pixelcat.components.main import android.app.Activity +import android.content.Context import android.content.Intent import android.os.Bundle import android.view.ViewGroup @@ -115,4 +116,8 @@ class MainActivity : BaseActivity() { startActivity(ComposeActivity.newIntent(this, returnValue?.firstOrNull()!!)) } } + + companion object { + fun newIntent(context: Context) = Intent(context, MainActivity::class.java) + } } diff --git a/app/src/main/kotlin/at/connyduck/pixelcat/components/notifications/NotificationsViewModel.kt b/app/src/main/kotlin/at/connyduck/pixelcat/components/notifications/NotificationsViewModel.kt index b0dbbef..c2ff545 100644 --- a/app/src/main/kotlin/at/connyduck/pixelcat/components/notifications/NotificationsViewModel.kt +++ b/app/src/main/kotlin/at/connyduck/pixelcat/components/notifications/NotificationsViewModel.kt @@ -50,5 +50,4 @@ class NotificationsViewModel @Inject constructor( ).flow } .cachedIn(viewModelScope) - } diff --git a/app/src/main/kotlin/at/connyduck/pixelcat/components/splash/SplashActivity.kt b/app/src/main/kotlin/at/connyduck/pixelcat/components/splash/SplashActivity.kt index ccd5eab..6952bc4 100644 --- a/app/src/main/kotlin/at/connyduck/pixelcat/components/splash/SplashActivity.kt +++ b/app/src/main/kotlin/at/connyduck/pixelcat/components/splash/SplashActivity.kt @@ -19,7 +19,6 @@ package at.connyduck.pixelcat.components.splash -import android.content.Intent import android.os.Bundle import androidx.lifecycle.lifecycleScope import at.connyduck.pixelcat.components.login.LoginActivity @@ -40,12 +39,9 @@ class SplashActivity : DaggerAppCompatActivity() { lifecycleScope.launch { val intent = if (accountManager.activeAccount() != null) { - Intent( - this@SplashActivity, - MainActivity::class.java - ) // TODO don't create intents here + MainActivity.newIntent(this@SplashActivity) } else { - Intent(this@SplashActivity, LoginActivity::class.java) + LoginActivity.newIntent(this@SplashActivity) } startActivity(intent) finish() diff --git a/app/src/main/kotlin/at/connyduck/pixelcat/config/Config.kt b/app/src/main/kotlin/at/connyduck/pixelcat/config/Config.kt index 48b4384..6f5b96e 100644 --- a/app/src/main/kotlin/at/connyduck/pixelcat/config/Config.kt +++ b/app/src/main/kotlin/at/connyduck/pixelcat/config/Config.kt @@ -28,5 +28,5 @@ object Config { const val oAuthRedirect = "$oAuthScheme://$oAuthHost" const val oAuthScopes = "read write follow" - val domainExceptions = arrayOf("gab.com", "gab.ai", "gabfed.com") + val domainExceptions = arrayOf("gab.com", "gab.ai", "spinster.xyz") } diff --git a/app/src/main/kotlin/at/connyduck/pixelcat/dagger/ActivityModule.kt b/app/src/main/kotlin/at/connyduck/pixelcat/dagger/ActivityModule.kt index bdac11a..4b023bb 100644 --- a/app/src/main/kotlin/at/connyduck/pixelcat/dagger/ActivityModule.kt +++ b/app/src/main/kotlin/at/connyduck/pixelcat/dagger/ActivityModule.kt @@ -25,6 +25,7 @@ import at.connyduck.pixelcat.components.about.licenses.LicenseActivity import at.connyduck.pixelcat.components.compose.ComposeActivity import at.connyduck.pixelcat.components.timeline.detail.DetailActivity import at.connyduck.pixelcat.components.login.LoginActivity +import at.connyduck.pixelcat.components.login.LoginWebViewActivity import at.connyduck.pixelcat.components.profile.ProfileActivity import at.connyduck.pixelcat.components.settings.SettingsActivity import at.connyduck.pixelcat.components.splash.SplashActivity @@ -61,4 +62,7 @@ abstract class ActivityModule { @ContributesAndroidInjector abstract fun contributesDetailActivity(): DetailActivity + + @ContributesAndroidInjector + abstract fun contributesLoginWebViewActivity(): LoginWebViewActivity } diff --git a/app/src/main/kotlin/at/connyduck/pixelcat/db/entitity/TimelineAccountEntity.kt b/app/src/main/kotlin/at/connyduck/pixelcat/db/entitity/TimelineAccountEntity.kt index a4b410b..a089614 100644 --- a/app/src/main/kotlin/at/connyduck/pixelcat/db/entitity/TimelineAccountEntity.kt +++ b/app/src/main/kotlin/at/connyduck/pixelcat/db/entitity/TimelineAccountEntity.kt @@ -36,7 +36,7 @@ fun Account.toEntity(accountId: Long) = TimelineAccountEntity( id = id, localUsername = localUsername, username = username, - displayName = displayName, + displayName = name, url = url, avatar = avatar ) diff --git a/app/src/main/kotlin/at/connyduck/pixelcat/model/Account.kt b/app/src/main/kotlin/at/connyduck/pixelcat/model/Account.kt index 8a3584f..34687a6 100644 --- a/app/src/main/kotlin/at/connyduck/pixelcat/model/Account.kt +++ b/app/src/main/kotlin/at/connyduck/pixelcat/model/Account.kt @@ -19,14 +19,8 @@ package at.connyduck.pixelcat.model -import android.os.Parcel -import android.os.Parcelable -import android.text.Spanned -import androidx.core.text.HtmlCompat import com.squareup.moshi.Json import com.squareup.moshi.JsonClass -import kotlinx.android.parcel.Parceler -import kotlinx.android.parcel.Parcelize import java.util.Date @JsonClass(generateAdapter = true) @@ -55,38 +49,25 @@ data class Account( get() = if (displayName.isEmpty()) { localUsername } else displayName - - fun isRemote(): Boolean = this.username != this.localUsername } @JsonClass(generateAdapter = true) -@Parcelize data class AccountSource( - // val privacy: Status.Visibility, + val privacy: Status.Visibility, val sensitive: Boolean, val note: String, val fields: List? -) : Parcelable +) @JsonClass(generateAdapter = true) -@Parcelize data class Field( val name: String, - // val value: @WriteWith() Spanned, + val value: String, @Json(name = "verified_at") val verifiedAt: Date? -) : Parcelable +) @JsonClass(generateAdapter = true) -@Parcelize data class StringField( val name: String, val value: String -) : Parcelable - -object SpannedParceler : Parceler { - override fun create(parcel: Parcel): Spanned = HtmlCompat.fromHtml(parcel.readString() ?: "", HtmlCompat.FROM_HTML_SEPARATOR_LINE_BREAK_PARAGRAPH) - - override fun Spanned.write(parcel: Parcel, flags: Int) { - parcel.writeString(HtmlCompat.toHtml(this, HtmlCompat.TO_HTML_PARAGRAPH_LINES_INDIVIDUAL)) - } -} +) diff --git a/app/src/main/res/color/edit_text_color_white.xml b/app/src/main/res/color/edit_text_color_white.xml new file mode 100644 index 0000000..674f8e3 --- /dev/null +++ b/app/src/main/res/color/edit_text_color_white.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml index c9d335c..3990e13 100644 --- a/app/src/main/res/layout/activity_login.xml +++ b/app/src/main/res/layout/activity_login.xml @@ -1,7 +1,6 @@ @@ -18,6 +17,7 @@ android:id="@+id/loginToolbar" android:layout_width="match_parent" android:layout_height="wrap_content" /> + - + android:inputType="textUri" + android:textColor="@color/white" + android:textCursorDrawable="@null" /> +