improve login

This commit is contained in:
Konrad Pozniak 2020-09-21 16:38:22 +02:00
parent bc8ced0fd2
commit 31794af1fd
18 changed files with 209 additions and 118 deletions

View File

@ -26,8 +26,8 @@
</activity> </activity>
<activity android:name=".components.login.LoginWebViewActivity"
<activity android:name=".components.login.LoginWebViewActivity"> android:label="@string/title_login">
</activity> </activity>
<activity <activity
android:name=".components.settings.SettingsActivity" android:name=".components.settings.SettingsActivity"

View File

@ -20,6 +20,7 @@
package at.connyduck.pixelcat.components.login package at.connyduck.pixelcat.components.login
import android.app.Activity import android.app.Activity
import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.Menu import android.view.Menu
@ -29,23 +30,30 @@ import androidx.activity.viewModels
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsCompat.Type.systemBars import androidx.core.view.WindowInsetsCompat.Type.systemBars
import androidx.lifecycle.Observer import androidx.lifecycle.lifecycleScope
import at.connyduck.pixelcat.components.main.MainActivity import at.connyduck.pixelcat.components.main.MainActivity
import at.connyduck.pixelcat.R import at.connyduck.pixelcat.R
import at.connyduck.pixelcat.components.about.AboutActivity import at.connyduck.pixelcat.components.about.AboutActivity
import at.connyduck.pixelcat.components.general.BaseActivity import at.connyduck.pixelcat.components.general.BaseActivity
import at.connyduck.pixelcat.components.settings.SettingsActivity import at.connyduck.pixelcat.components.settings.SettingsActivity
import at.connyduck.pixelcat.components.util.extension.visible
import at.connyduck.pixelcat.dagger.ViewModelFactory import at.connyduck.pixelcat.dagger.ViewModelFactory
import at.connyduck.pixelcat.databinding.ActivityLoginBinding import at.connyduck.pixelcat.databinding.ActivityLoginBinding
import at.connyduck.pixelcat.util.viewBinding import at.connyduck.pixelcat.util.viewBinding
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import javax.inject.Inject import javax.inject.Inject
class LoginActivity : BaseActivity(), Observer<LoginModel> { @FlowPreview
@ExperimentalCoroutinesApi
class LoginActivity : BaseActivity() {
@Inject @Inject
lateinit var viewModelFactory: ViewModelFactory 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) private val binding by viewBinding(ActivityLoginBinding::inflate)
@ -54,7 +62,7 @@ class LoginActivity : BaseActivity(), Observer<LoginModel> {
setContentView(binding.root) setContentView(binding.root)
ViewCompat.setOnApplyWindowInsetsListener(binding.loginContainer) { _, insets -> ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _, insets ->
val top = insets.getInsets(systemBars()).top val top = insets.getInsets(systemBars()).top
val toolbarParams = binding.loginToolbar.layoutParams as ViewGroup.MarginLayoutParams val toolbarParams = binding.loginToolbar.layoutParams as ViewGroup.MarginLayoutParams
toolbarParams.topMargin = top toolbarParams.topMargin = top
@ -67,10 +75,14 @@ class LoginActivity : BaseActivity(), Observer<LoginModel> {
setDisplayShowTitleEnabled(false) setDisplayShowTitleEnabled(false)
} }
loginViewModel.loginState.observe(this, this) lifecycleScope.launch {
viewModel.observe().collect { loginModel ->
onChanged(loginModel)
}
}
binding.loginButton.setOnClickListener { binding.loginButton.setOnClickListener {
loginViewModel.startLogin(binding.loginInput.text.toString()) viewModel.startLogin(binding.loginInput.text.toString())
} }
} }
@ -78,13 +90,21 @@ class LoginActivity : BaseActivity(), Observer<LoginModel> {
val authCode = data?.getStringExtra(LoginWebViewActivity.RESULT_AUTHORIZATION_CODE) val authCode = data?.getStringExtra(LoginWebViewActivity.RESULT_AUTHORIZATION_CODE)
if (requestCode == REQUEST_CODE && resultCode == Activity.RESULT_OK && !authCode.isNullOrEmpty()) { if (requestCode == REQUEST_CODE && resultCode == Activity.RESULT_OK && !authCode.isNullOrEmpty()) {
loginViewModel.authCode(authCode) viewModel.authCode(authCode)
return return
} }
super.onActivityResult(requestCode, resultCode, data) 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 { override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.login, menu) menuInflater.inflate(R.menu.login, menu)
return super.onCreateOptionsMenu(menu) return super.onCreateOptionsMenu(menu)
@ -105,7 +125,8 @@ class LoginActivity : BaseActivity(), Observer<LoginModel> {
return super.onOptionsItemSelected(item) return super.onOptionsItemSelected(item)
} }
override fun onChanged(loginModel: LoginModel?) { private fun onChanged(loginModel: LoginModel?) {
binding.loginInput.setText(loginModel?.input) binding.loginInput.setText(loginModel?.input)
if (loginModel == null) { if (loginModel == null) {
@ -113,23 +134,47 @@ class LoginActivity : BaseActivity(), Observer<LoginModel> {
} }
when (loginModel.state) { when (loginModel.state) {
LoginState.NO_ERROR -> binding.loginInputLayout.error = null LoginState.NO_ERROR -> {
LoginState.AUTH_ERROR -> binding.loginInputLayout.error = "auth error" binding.loginInputLayout.error = null
LoginState.INVALID_DOMAIN -> binding.loginInputLayout.error = "invalid domain" setLoading(false)
LoginState.NETWORK_ERROR -> binding.loginInputLayout.error = "network error" }
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 -> { LoginState.LOADING -> {
setLoading(true)
} }
LoginState.SUCCESS -> { LoginState.SUCCESS -> {
setLoading(true)
startActivityForResult(LoginWebViewActivity.newIntent(loginModel.domain!!, loginModel.clientId!!, loginModel.clientSecret!!, this), REQUEST_CODE) startActivityForResult(LoginWebViewActivity.newIntent(loginModel.domain!!, loginModel.clientId!!, loginModel.clientSecret!!, this), REQUEST_CODE)
} }
LoginState.SUCCESS_FINAL -> { LoginState.SUCCESS_FINAL -> {
startActivity(Intent(this, MainActivity::class.java)) // TODO dont create intent here setLoading(true)
startActivity(MainActivity.newIntent(this))
finish() finish()
} }
} }
} }
private fun setLoading(loading: Boolean) {
binding.loginLoading.visible = loading
binding.loginImageView.visible = !loading
binding.loginInputLayout.visible = !loading
binding.loginButton.visible = !loading
}
companion object { companion object {
private const val REQUEST_CODE = 14 private const val REQUEST_CODE = 14
fun newIntent(context: Context) = Intent(context, LoginActivity::class.java)
} }
} }

View File

@ -32,5 +32,11 @@ data class LoginModel(
) : Parcelable ) : Parcelable
enum class LoginState { // TODO rename this stuff so it makes sense 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
} }

View File

@ -19,52 +19,54 @@
package at.connyduck.pixelcat.components.login package at.connyduck.pixelcat.components.login
import androidx.annotation.MainThread
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import at.connyduck.pixelcat.config.Config import at.connyduck.pixelcat.config.Config
import at.connyduck.pixelcat.db.AccountManager import at.connyduck.pixelcat.db.AccountManager
import at.connyduck.pixelcat.db.entitity.AccountAuthData import at.connyduck.pixelcat.db.entitity.AccountAuthData
import at.connyduck.pixelcat.network.FediverseApi 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 kotlinx.coroutines.launch
import okhttp3.HttpUrl import okhttp3.HttpUrl
import java.util.Locale import java.util.Locale
import javax.inject.Inject import javax.inject.Inject
@FlowPreview
@ExperimentalCoroutinesApi
class LoginViewModel @Inject constructor( class LoginViewModel @Inject constructor(
private val fediverseApi: FediverseApi, private val fediverseApi: FediverseApi,
private val accountManager: AccountManager private val accountManager: AccountManager
) : ViewModel() { ) : ViewModel() {
val loginState = MutableLiveData<LoginModel>().apply { private val loginState = ConflatedBroadcastChannel(LoginModel(state = LoginState.NO_ERROR))
value = LoginModel(state = LoginState.NO_ERROR)
} fun observe() = loginState.asFlow()
@MainThread
fun startLogin(input: String) { 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 { 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( fediverseApi.authenticateAppAsync(
domain = domainInput, domain = domainInput,
clientName = "Pixelcat", clientName = "Pixelcat",
@ -73,19 +75,18 @@ class LoginViewModel @Inject constructor(
scopes = Config.oAuthScopes scopes = Config.oAuthScopes
).fold( ).fold(
{ appData -> { 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) { fun authCode(authCode: String) {
viewModelScope.launch { viewModelScope.launch {
val loginModel = loginState.value!! val loginModel = loginState.value
fediverseApi.fetchOAuthToken( fediverseApi.fetchOAuthToken(
domain = loginModel.domain!!, domain = loginModel.domain!!,
@ -106,14 +107,21 @@ class LoginViewModel @Inject constructor(
clientSecret = loginModel.clientSecret clientSecret = loginModel.clientSecret
) )
accountManager.addAccount(loginModel.domain, authData) 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 { private fun canonicalizeDomain(domain: String): String {
// Strip any schemes out. // Strip any schemes out.
var s = domain.replaceFirst("http://", "") var s = domain.replaceFirst("http://", "")

View File

@ -23,23 +23,35 @@ import android.app.Activity
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle import android.os.Bundle
import android.view.ViewGroup
import android.webkit.WebResourceRequest import android.webkit.WebResourceRequest
import android.webkit.WebView import android.webkit.WebView
import at.connyduck.pixelcat.config.Config import at.connyduck.pixelcat.config.Config
import android.webkit.WebViewClient 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 import at.connyduck.pixelcat.databinding.ActivityLoginWebViewBinding
class LoginWebViewActivity : AppCompatActivity() { class LoginWebViewActivity : BaseActivity() {
private lateinit var binding: ActivityLoginWebViewBinding
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
binding = ActivityLoginWebViewBinding.inflate(layoutInflater) val binding = ActivityLoginWebViewBinding.inflate(layoutInflater)
setContentView(binding.root) 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 domain = intent.getStringExtra(EXTRA_DOMAIN)!!
val clientId = intent.getStringExtra(EXTRA_CLIENT_ID)!! val clientId = intent.getStringExtra(EXTRA_CLIENT_ID)!!
val clientSecret = intent.getStringExtra(EXTRA_CLIENT_SECRET)!! val clientSecret = intent.getStringExtra(EXTRA_CLIENT_SECRET)!!

View File

@ -20,6 +20,7 @@
package at.connyduck.pixelcat.components.main package at.connyduck.pixelcat.components.main
import android.app.Activity import android.app.Activity
import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.ViewGroup import android.view.ViewGroup
@ -115,4 +116,8 @@ class MainActivity : BaseActivity() {
startActivity(ComposeActivity.newIntent(this, returnValue?.firstOrNull()!!)) startActivity(ComposeActivity.newIntent(this, returnValue?.firstOrNull()!!))
} }
} }
companion object {
fun newIntent(context: Context) = Intent(context, MainActivity::class.java)
}
} }

View File

@ -50,5 +50,4 @@ class NotificationsViewModel @Inject constructor(
).flow ).flow
} }
.cachedIn(viewModelScope) .cachedIn(viewModelScope)
} }

View File

@ -19,7 +19,6 @@
package at.connyduck.pixelcat.components.splash package at.connyduck.pixelcat.components.splash
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import at.connyduck.pixelcat.components.login.LoginActivity import at.connyduck.pixelcat.components.login.LoginActivity
@ -40,12 +39,9 @@ class SplashActivity : DaggerAppCompatActivity() {
lifecycleScope.launch { lifecycleScope.launch {
val intent = if (accountManager.activeAccount() != null) { val intent = if (accountManager.activeAccount() != null) {
Intent( MainActivity.newIntent(this@SplashActivity)
this@SplashActivity,
MainActivity::class.java
) // TODO don't create intents here
} else { } else {
Intent(this@SplashActivity, LoginActivity::class.java) LoginActivity.newIntent(this@SplashActivity)
} }
startActivity(intent) startActivity(intent)
finish() finish()

View File

@ -28,5 +28,5 @@ object Config {
const val oAuthRedirect = "$oAuthScheme://$oAuthHost" const val oAuthRedirect = "$oAuthScheme://$oAuthHost"
const val oAuthScopes = "read write follow" const val oAuthScopes = "read write follow"
val domainExceptions = arrayOf("gab.com", "gab.ai", "gabfed.com") val domainExceptions = arrayOf("gab.com", "gab.ai", "spinster.xyz")
} }

View File

@ -25,6 +25,7 @@ import at.connyduck.pixelcat.components.about.licenses.LicenseActivity
import at.connyduck.pixelcat.components.compose.ComposeActivity import at.connyduck.pixelcat.components.compose.ComposeActivity
import at.connyduck.pixelcat.components.timeline.detail.DetailActivity import at.connyduck.pixelcat.components.timeline.detail.DetailActivity
import at.connyduck.pixelcat.components.login.LoginActivity 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.profile.ProfileActivity
import at.connyduck.pixelcat.components.settings.SettingsActivity import at.connyduck.pixelcat.components.settings.SettingsActivity
import at.connyduck.pixelcat.components.splash.SplashActivity import at.connyduck.pixelcat.components.splash.SplashActivity
@ -61,4 +62,7 @@ abstract class ActivityModule {
@ContributesAndroidInjector @ContributesAndroidInjector
abstract fun contributesDetailActivity(): DetailActivity abstract fun contributesDetailActivity(): DetailActivity
@ContributesAndroidInjector
abstract fun contributesLoginWebViewActivity(): LoginWebViewActivity
} }

View File

@ -36,7 +36,7 @@ fun Account.toEntity(accountId: Long) = TimelineAccountEntity(
id = id, id = id,
localUsername = localUsername, localUsername = localUsername,
username = username, username = username,
displayName = displayName, displayName = name,
url = url, url = url,
avatar = avatar avatar = avatar
) )

View File

@ -19,14 +19,8 @@
package at.connyduck.pixelcat.model 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.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
import kotlinx.android.parcel.Parceler
import kotlinx.android.parcel.Parcelize
import java.util.Date import java.util.Date
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
@ -55,38 +49,25 @@ data class Account(
get() = if (displayName.isEmpty()) { get() = if (displayName.isEmpty()) {
localUsername localUsername
} else displayName } else displayName
fun isRemote(): Boolean = this.username != this.localUsername
} }
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
@Parcelize
data class AccountSource( data class AccountSource(
// val privacy: Status.Visibility, val privacy: Status.Visibility,
val sensitive: Boolean, val sensitive: Boolean,
val note: String, val note: String,
val fields: List<StringField>? val fields: List<StringField>?
) : Parcelable )
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
@Parcelize
data class Field( data class Field(
val name: String, val name: String,
// val value: @WriteWith<SpannedParceler>() Spanned, val value: String,
@Json(name = "verified_at") val verifiedAt: Date? @Json(name = "verified_at") val verifiedAt: Date?
) : Parcelable )
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
@Parcelize
data class StringField( data class StringField(
val name: String, val name: String,
val value: String val value: String
) : Parcelable )
object SpannedParceler : Parceler<Spanned> {
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))
}
}

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_selected="false" android:color="#6BFFFFFF"/>
<item android:state_selected="true" android:color="#fff"/>
</selector>

View File

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/loginContainer"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@drawable/pixelcat_gradient"> android:background="@drawable/pixelcat_gradient">
@ -18,6 +17,7 @@
android:id="@+id/loginToolbar" android:id="@+id/loginToolbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" /> android:layout_height="wrap_content" />
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>
<ImageView <ImageView
@ -36,21 +36,27 @@
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense" style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
android:layout_width="250dp" android:layout_width="250dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:hint="@string/instance_input_hint"
app:boxStrokeColor="@color/edit_text_color_white"
app:boxStrokeErrorColor="@color/white"
app:errorIconTint="@color/white"
app:errorTextColor="@color/white"
app:hintTextColor="@color/white"
app:layout_constraintBottom_toTopOf="@id/loginButton" app:layout_constraintBottom_toTopOf="@id/loginButton"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
android:hint="@string/instance_input_hint"
app:layout_constraintTop_toBottomOf="@+id/loginImageView"> app:layout_constraintTop_toBottomOf="@+id/loginImageView">
<com.google.android.material.textfield.TextInputEditText <com.google.android.material.textfield.TextInputEditText
android:id="@+id/loginInput" android:id="@+id/loginInput"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textColor="#fff"
android:ems="10" android:ems="10"
android:inputType="textUri" /> android:inputType="textUri"
</com.google.android.material.textfield.TextInputLayout> android:textColor="@color/white"
android:textCursorDrawable="@null" />
</com.google.android.material.textfield.TextInputLayout>
<Button <Button
android:id="@+id/loginButton" android:id="@+id/loginButton"
@ -69,19 +75,19 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/loginInputLayout" /> app:layout_constraintTop_toBottomOf="@+id/loginInputLayout" />
<!--ProgressBar <ProgressBar
android:id="@+id/loading" android:id="@+id/loginLoading"
android:visibility="gone" android:layout_width="wrap_content"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:layout_height="wrap_content" android:layout_gravity="center"
android:layout_gravity="center" android:layout_marginStart="32dp"
android:layout_marginStart="32dp" android:layout_marginTop="64dp"
android:layout_marginTop="64dp" android:layout_marginEnd="32dp"
android:layout_marginEnd="32dp" android:layout_marginBottom="64dp"
android:layout_marginBottom="64dp" android:indeterminateTint="@color/white"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@+id/password" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/password" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent" />
app:layout_constraintVertical_bias="0.3"/-->
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,15 +1,29 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout <androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/pixelcat_gradient">
<androidx.appcompat.widget.Toolbar
android:id="@+id/loginToolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:navigationIcon="@drawable/ic_arrow_back"
app:title="@string/title_login"
app:titleTextAppearance="@style/TextAppearanceToolbar"
app:titleTextColor="#fff" />
</com.google.android.material.appbar.AppBarLayout>
<WebView
android:id="@+id/loginWebView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context=".components.login.LoginWebViewActivity"> app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" />
<WebView android:id="@+id/loginWebView" </androidx.coordinatorlayout.widget.CoordinatorLayout>
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -13,4 +13,12 @@
</style> </style>
<style name="AppTheme.Fullscreen">
<item name="android:windowTranslucentNavigation">true</item>
<item name="android:navigationBarColor">@color/transparent</item>
<item name="android:navigationBarDividerColor">@color/transparent</item>
<item name="android:windowLightNavigationBar">false</item>
</style>
</resources> </resources>

View File

@ -87,4 +87,6 @@
<string name="notification_favourited">%1$s liked your post</string> <string name="notification_favourited">%1$s liked your post</string>
<string name="notification_followed">%1$s followed you</string> <string name="notification_followed">%1$s followed you</string>
<string name="title_login">Login</string>
</resources> </resources>

View File

@ -27,7 +27,7 @@
</style> </style>
<style name="AppTheme.Fullscreen"> <style name="AppTheme.Fullscreen">
<item name="android:windowTranslucentNavigation">false</item> <item name="android:windowTranslucentNavigation">true</item>
</style> </style>
<style name="TextAppearanceToolbar" parent="@style/TextAppearance.MaterialComponents.Headline6"> <style name="TextAppearanceToolbar" parent="@style/TextAppearance.MaterialComponents.Headline6">