Authentication Dialog Enhancement (#633)

* Authentication Dialog Enhancement

* Add "Remember" checkbox

* Add button for password visibility
This commit is contained in:
Neonicus 2020-07-07 21:50:37 +02:00 committed by GitHub
parent f419fca874
commit 50d80413ce
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 218 additions and 25 deletions

View file

@ -114,6 +114,10 @@ dependencies {
implementation(Config.Dependency.AndroidX.preference)
implementation(Config.Dependency.Google.material)
implementation(Config.Dependency.AndroidX.roomRuntime)
implementation(Config.Dependency.AndroidX.roomKtx)
kapt(Config.Dependency.AndroidX.roomCompiler)
implementation(Config.Dependency.Misc.threeTenAbp) {
exclude(group = "org.threeten")
}

View file

@ -0,0 +1,9 @@
package io.homeassistant.companion.android.database
import androidx.room.Database
import androidx.room.RoomDatabase
@Database(entities = [AuthenticationList::class], version = 1)
abstract class AppDataBase : RoomDatabase() {
abstract fun authenticationDatabaseDao(): AuthenticationDataBaseDao
}

View file

@ -0,0 +1,22 @@
package io.homeassistant.companion.android.database
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Update
@Dao
interface AuthenticationDataBaseDao {
@Insert
fun insert(authentication: AuthenticationList)
@Update
fun update(authentication: AuthenticationList)
@Query("SELECT * from Authentication_List WHERE Host = :key")
fun get(key: String): AuthenticationList?
@Query("DELETE FROM Authentication_List")
fun clear()
}

View file

@ -0,0 +1,17 @@
package io.homeassistant.companion.android.database
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "Authentication_List")
data class AuthenticationList(
@PrimaryKey
var host: String,
@ColumnInfo(name = "Username")
val username: String,
@ColumnInfo(name = "Password")
var password: String
)

View file

@ -1,7 +1,6 @@
package io.homeassistant.companion.android.webview
import android.net.http.SslError
import android.webkit.HttpAuthHandler
interface WebView {
@ -14,6 +13,4 @@ interface WebView {
fun openOnBoarding()
fun showError(isAuthenticationError: Boolean = false, error: SslError? = null, description: String? = null)
fun authenticationDialog(handler: HttpAuthHandler)
}

View file

@ -13,6 +13,8 @@ import android.net.http.SslError
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.text.method.HideReturnsTransformationMethod
import android.text.method.PasswordTransformationMethod
import android.util.Log
import android.util.Rational
import android.view.MenuInflater
@ -28,11 +30,14 @@ import android.webkit.WebResourceRequest
import android.webkit.WebResourceResponse
import android.webkit.WebView
import android.webkit.WebViewClient
import android.widget.CheckBox
import android.widget.EditText
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.room.Room
import com.lokalise.sdk.LokaliseContextWrapper
import com.lokalise.sdk.menu_inflater.LokaliseMenuInflater
import eightbitlab.com.blurview.RenderScriptBlur
@ -43,6 +48,8 @@ import io.homeassistant.companion.android.R
import io.homeassistant.companion.android.authenticator.Authenticator
import io.homeassistant.companion.android.background.LocationBroadcastReceiver
import io.homeassistant.companion.android.common.dagger.GraphComponentAccessor
import io.homeassistant.companion.android.database.AppDataBase
import io.homeassistant.companion.android.database.AuthenticationList
import io.homeassistant.companion.android.onboarding.OnboardingActivity
import io.homeassistant.companion.android.settings.SettingsActivity
import io.homeassistant.companion.android.util.PermissionManager
@ -80,6 +87,8 @@ class WebViewActivity : AppCompatActivity(), io.homeassistant.companion.android.
private var alertDialog: AlertDialog? = null
private var isVideoFullScreen = false
private var videoHeight = 0
private var firstAuthTime: Long = 0
private var resourceURL: String = ""
private var unlocked = false
@SuppressLint("SetJavaScriptEnabled")
@ -127,7 +136,7 @@ class WebViewActivity : AppCompatActivity(), io.homeassistant.companion.android.
) {
Log.e(TAG, "onReceivedHttpError: errorCode: $errorCode url:$failingUrl")
if (failingUrl == loadedUrl) {
showError(description = description)
showError()
}
}
@ -148,7 +157,10 @@ class WebViewActivity : AppCompatActivity(), io.homeassistant.companion.android.
host: String,
realm: String
) {
authenticationDialog(handler)
var authError = false
if (System.currentTimeMillis() <= (firstAuthTime + 500))
authError = true
authenticationDialog(handler, host, realm, authError)
}
override fun onReceivedSslError(
@ -157,7 +169,14 @@ class WebViewActivity : AppCompatActivity(), io.homeassistant.companion.android.
error: SslError?
) {
Log.e(TAG, "onReceivedHttpError: $error")
showError(error = error)
showError()
}
override fun onLoadResource(
view: WebView?,
url: String?
) {
resourceURL = url!!
}
override fun shouldOverrideUrlLoading(
@ -517,22 +536,77 @@ class WebViewActivity : AppCompatActivity(), io.homeassistant.companion.android.
}
@SuppressLint("InflateParams")
override fun authenticationDialog(handler: HttpAuthHandler) {
fun authenticationDialog(handler: HttpAuthHandler, host: String, realm: String, authError: Boolean) {
val db = Room.databaseBuilder(
applicationContext,
AppDataBase::class.java, "HomeAssistantDB")
.allowMainThreadQueries()
.build()
val authenticationDao = db.authenticationDatabaseDao()
val httpAuth = authenticationDao.get((resourceURL + realm))
val inflater = layoutInflater
val dialogLayout = inflater.inflate(R.layout.dialog_authentication, null)
val username = dialogLayout.findViewById<EditText>(R.id.username)
val password = dialogLayout.findViewById<EditText>(R.id.password)
val remember = dialogLayout.findViewById<CheckBox>(R.id.checkBox)
val viewPassword = dialogLayout.findViewById<ImageView>(R.id.viewPassword)
var autoAuth = false
AlertDialog.Builder(this)
.setTitle(R.string.auth_request)
.setView(dialogLayout)
.setPositiveButton(android.R.string.ok) { _, _ ->
handler.proceed(username.text.toString(), password.text.toString())
viewPassword.setOnClickListener() {
if (password.transformationMethod == PasswordTransformationMethod.getInstance()) {
password.transformationMethod = HideReturnsTransformationMethod.getInstance()
viewPassword.setImageResource(R.drawable.ic_visibility_off)
password.setSelection(password.text.length)
} else {
password.transformationMethod = PasswordTransformationMethod.getInstance()
viewPassword.setImageResource(R.drawable.ic_visibility)
password.setSelection(password.text.length)
}
.setNeutralButton(android.R.string.cancel) { _, _ ->
Toast.makeText(applicationContext, R.string.auth_cancel, Toast.LENGTH_SHORT).show()
}
if (!httpAuth?.host.isNullOrBlank()) {
if (!authError) {
handler.proceed(httpAuth?.username, httpAuth?.password)
autoAuth = true
firstAuthTime = System.currentTimeMillis()
}
.show()
}
var message = host + " " + getString(R.string.required_fields)
if (resourceURL.subSequence(0, 5).toString() == "http:")
message = "http://" + message + " " + getString(R.string.not_private)
else
message = "https://" + message
if (!autoAuth || authError) {
AlertDialog.Builder(this, R.style.Authentication_Dialog)
.setTitle(R.string.auth_request)
.setMessage(message)
.setView(dialogLayout)
.setPositiveButton(android.R.string.ok) { _, _ ->
if (username.text.toString() != "" && password.text.toString() != "") {
if (remember.isChecked) {
if (authError)
authenticationDao.update(AuthenticationList((resourceURL + realm), username.text.toString(), password.text.toString()))
else
authenticationDao.insert(AuthenticationList((resourceURL + realm), username.text.toString(), password.text.toString()))
db.close()
}
handler.proceed(username.text.toString(), password.text.toString())
} else AlertDialog.Builder(this)
.setTitle(R.string.auth_cancel)
.setMessage(R.string.auth_error_message)
.setPositiveButton(android.R.string.ok) { _, _ ->
authenticationDialog(handler, host, realm, authError)
}
.show()
}
.setNeutralButton(android.R.string.cancel) { _, _ ->
Toast.makeText(applicationContext, R.string.auth_cancel, Toast.LENGTH_SHORT).show()
}
.show()
}
}
override fun onRequestPermissionsResult(

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@color/colorAccent"
android:pathData="M12,4.5C7,4.5 2.73,7.61 1,12c1.73,4.39 6,7.5 11,7.5s9.27,-3.11 11,-7.5c-1.73,-4.39 -6,-7.5 -11,-7.5zM12,17c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5zM12,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3 3,-1.34 3,-3 -1.34,-3 -3,-3z"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@color/colorAccent"
android:pathData="M12,7c2.76,0 5,2.24 5,5 0,0.65 -0.13,1.26 -0.36,1.83l2.92,2.92c1.51,-1.26 2.7,-2.89 3.43,-4.75 -1.73,-4.39 -6,-7.5 -11,-7.5 -1.4,0 -2.74,0.25 -3.98,0.7l2.16,2.16C10.74,7.13 11.35,7 12,7zM2,4.27l2.28,2.28 0.46,0.46C3.08,8.3 1.78,10.02 1,12c1.73,4.39 6,7.5 11,7.5 1.55,0 3.03,-0.3 4.38,-0.84l0.42,0.42L19.73,22 21,20.73 3.27,3 2,4.27zM7.53,9.8l1.55,1.55c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.66 1.34,3 3,3 0.22,0 0.44,-0.03 0.65,-0.08l1.55,1.55c-0.67,0.33 -1.41,0.53 -2.2,0.53 -2.76,0 -5,-2.24 -5,-5 0,-0.79 0.2,-1.53 0.53,-2.2zM11.84,9.02l3.15,3.15 0.02,-0.16c0,-1.66 -1.34,-3 -3,-3l-0.17,0.01z"/>
</vector>

View file

@ -1,20 +1,57 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingLeft="20dp"
android:paddingRight="20dp">
<EditText
android:id="@+id/username"
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/username" />
app:boxBackgroundColor="@android:color/transparent">
<EditText
android:id="@+id/password"
<EditText
android:id="@+id/username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/username"
android:singleLine="true" />
</com.google.android.material.textfield.TextInputLayout>
<TableRow
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/password"
android:inputType="textWebPassword" />
android:orientation="horizontal">
<com.google.android.material.textfield.TextInputLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
app:boxBackgroundColor="@android:color/transparent">
<EditText
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/password"
android:inputType="textPassword"
android:singleLine="true" />
</com.google.android.material.textfield.TextInputLayout>
<ImageView
android:id="@+id/viewPassword"
android:layout_width="wrap_content"
android:layout_height="match_parent"
app:srcCompat="@drawable/ic_visibility" />
</TableRow>
<CheckBox
android:id="@+id/checkBox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Remember" />
</LinearLayout>

View file

@ -7,7 +7,9 @@
<string name="application_version">Application Version</string>
<string name="attempting_registration">Registering application…</string>
<string name="auth_cancel">Authentication Cancelled</string>
<string name="auth_request">Authentication Requested:</string>
<string name="auth_error">Authentication Error</string>
<string name="auth_error_message">The \'Username\' and \'Password\' fields must be completed</string>
<string name="auth_request">Authentication Requested</string>
<string name="biometric_message">Unlock using your biometric or screenlock credential</string>
<string name="biometric_set_title">Confirm to continue</string>
<string name="biometric_title">Home Assistant need to be unlock</string>
@ -60,6 +62,7 @@ Home Assistant instance</string>
<string name="manage_ssids_input_exists">This SSID already exists.</string>
<string name="manual_setup">enter address manually</string>
<string name="map">Map</string>
<string name="not_private">Your connection to this site is not private.</string>
<string name="other_settings">Other Settings</string>
<string name="password">Password</string>
<string name="permission_explanation">In order to use location tracking features or different connection urls based on WiFi SSID we need access to your location. If you want consistent background updates you will also need to allow background processing</string>
@ -76,6 +79,7 @@ Home Assistant instance</string>
<string name="rate_limit_notification.body">You have now sent more than %s notifications today. You will not receive new notifications until midnight UTC.</string>
<string name="rate_limit_notification.title">Notifications Rate Limited</string>
<string name="refresh">Refresh</string>
<string name="required_fields">requires a username and password.</string>
<string name="retry">Retry</string>
<string name="save">Save</string>
<string name="scan_again">scan again</string>

View file

@ -38,4 +38,9 @@
<style name="Theme.HomeAssistant.Dialog.Alert" parent="Theme.MaterialComponents.Light.Dialog.Alert" />
<style name="Authentication_Dialog" parent="Theme.MaterialComponents.Light.Dialog">
<item name="windowMinWidthMajor">300dp</item>
<item name="windowMinWidthMinor">0dp</item>
</style>
</resources>

View file

@ -43,6 +43,7 @@ object Config {
}
object AndroidX {
const val appcompat = "androidx.appcompat:appcompat:1.1.0"
const val lifecycle = "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0"
const val recyclerview = "androidx.recyclerview:recyclerview:1.1.0"
@ -51,6 +52,11 @@ object Config {
const val workManager = "androidx.work:work-runtime-ktx:2.3.4"
const val biometric = "androidx.biometric:biometric:1.0.1"
private const val roomVersion = "2.2.5"
const val roomRuntime = "androidx.room:room-runtime:${roomVersion}"
const val roomKtx = "androidx.room:room-ktx:${roomVersion}"
const val roomCompiler = "androidx.room:room-compiler:${roomVersion}"
}
object Play {