Improve login flow

This commit is contained in:
Phillip Thelen 2022-06-14 14:39:16 +02:00
parent 03c5115352
commit 600954fdc3
23 changed files with 189 additions and 29 deletions

View file

@ -6,3 +6,13 @@ fun Int.dpToPx(context: Context?): Int {
val displayMetrics = context?.resources?.displayMetrics
return ((this * (displayMetrics?.density ?: 1.0f)) + 0.5).toInt()
}
fun Float.dpToPx(context: Context?): Float {
val displayMetrics = context?.resources?.displayMetrics
return ((this * (displayMetrics?.density ?: 1.0f)) + 0.5).toFloat()
}
fun Double.dpToPx(context: Context?): Double {
val displayMetrics = context?.resources?.displayMetrics
return ((this * (displayMetrics?.density ?: 1.0f)) + 0.5)
}

View file

@ -55,7 +55,7 @@ dependencies {
}
implementation("com.squareup.retrofit2:converter-moshi:$retrofit_version")
implementation("com.squareup.moshi:moshi-kotlin:$moshi_version")
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.1.0'
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.2.0'
kapt("com.squareup.moshi:moshi-kotlin-codegen:$moshi_version")
//Analytics

View file

@ -2,18 +2,28 @@ package com.habitrpg.wearos.habitica.ui.activities
import android.content.Intent
import android.os.Bundle
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.activity.ComponentActivity
import androidx.core.view.children
import androidx.viewbinding.ViewBinding
import androidx.wear.activity.ConfirmationActivity
import com.habitrpg.android.habitica.databinding.ActivityWrapperBinding
import com.habitrpg.wearos.habitica.ui.viewmodels.BaseViewModel
import com.habitrpg.wearos.habitica.ui.views.IndeterminateProgressView
abstract class BaseActivity<B: ViewBinding, VM: BaseViewModel> : ComponentActivity() {
private lateinit var wrapperBinding: ActivityWrapperBinding
protected lateinit var binding: B
abstract val viewModel: VM
private var progressView: IndeterminateProgressView? = null
override fun onCreate(savedInstanceState: Bundle?) {
wrapperBinding = ActivityWrapperBinding.inflate(layoutInflater)
super.onCreate(savedInstanceState)
setContentView(binding.root)
wrapperBinding.root.addView(binding.root)
setContentView(wrapperBinding.root)
viewModel.errorValues.observe(this) {
val intent = Intent(this, ConfirmationActivity::class.java).apply {
@ -24,4 +34,25 @@ abstract class BaseActivity<B: ViewBinding, VM: BaseViewModel> : ComponentActivi
startActivity(intent)
}
}
fun startAnimatingProgress() {
if (progressView == null) {
progressView = IndeterminateProgressView(this)
wrapperBinding.root.addView(progressView)
progressView?.layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
progressView?.startAnimation()
}
}
fun stopAnimatingProgress() {
if (progressView != null) {
wrapperBinding.root.removeView(progressView)
} else {
wrapperBinding.root.children.forEach {
if (it is IndeterminateProgressView) {
wrapperBinding.root.removeView(it)
}
}
}
}
}

View file

@ -26,6 +26,7 @@ class SplashActivity: BaseActivity<ActivitySplashBinding, SplashViewModel>() {
super.onCreate(savedInstanceState)
if (viewModel.hasAuthentication) {
startMainActivity()
return
}
viewModel.onLoginCompleted = {
@ -36,9 +37,17 @@ class SplashActivity: BaseActivity<ActivitySplashBinding, SplashViewModel>() {
}
}
viewModel.showAccountLoader.observe(this) {
binding.progressBar.isVisible = it
binding.textView.isVisible = it
messageClient.addListener(viewModel)
lifecycleScope.launch(Dispatchers.IO) {
val info = Tasks.await(capabilityClient.getCapability("provide_auth", CapabilityClient.FILTER_REACHABLE))
val nodeID = info.nodes.firstOrNull { it.isNearby }
if (nodeID != null) {
showAccountLoader(true)
Tasks.await(messageClient.sendMessage(nodeID.id, "/request/auth", null))
} else {
showAccountLoader(false)
startLoginActivity()
}
}
}
@ -54,24 +63,9 @@ class SplashActivity: BaseActivity<ActivitySplashBinding, SplashViewModel>() {
finish()
}
private fun requestAuthenticationData(nodeID: String) {
Tasks.await(messageClient.sendMessage(nodeID, "/request/auth", null).apply {
addOnSuccessListener {
}
})
}
override fun onResume() {
super.onResume()
messageClient.addListener(viewModel)
lifecycleScope.launch(Dispatchers.IO) {
val info = Tasks.await(capabilityClient.getCapability("provide_auth", CapabilityClient.FILTER_REACHABLE))
val nodeID = info.nodes.firstOrNull { it.isNearby }
if (nodeID != null) {
requestAuthenticationData(nodeID.id)
}
}
private fun showAccountLoader(show: Boolean) {
binding.progressBar.isVisible = show
binding.textView.isVisible = show
}
override fun onPause() {

View file

@ -2,7 +2,6 @@ package com.habitrpg.wearos.habitica.ui.viewmodels
import android.content.SharedPreferences
import androidx.core.content.edit
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.google.android.gms.wearable.MessageClient
import com.google.android.gms.wearable.MessageEvent
@ -24,9 +23,6 @@ class SplashViewModel @Inject constructor(userRepository: UserRepository,
val keyHelper: KeyHelper?
) : BaseViewModel(userRepository, exceptionBuilder), MessageClient.OnMessageReceivedListener {
lateinit var onLoginCompleted: (Boolean) -> Unit
val showAccountLoader = MutableLiveData(false)
val hasAuthentication: Boolean
get() {
return hostConfig.hasAuthentication()
@ -44,6 +40,8 @@ class SplashViewModel @Inject constructor(userRepository: UserRepository,
try {
saveTokens(apiKey, userID)
} catch (e: Exception) {
onLoginCompleted(false)
return@launch
}
retrieveUser()
onLoginCompleted(true)

View file

@ -0,0 +1,86 @@
package com.habitrpg.wearos.habitica.ui.views
import android.animation.ValueAnimator
import android.content.Context
import android.graphics.Canvas
import android.graphics.Matrix
import android.graphics.Paint
import android.graphics.SweepGradient
import android.util.AttributeSet
import android.view.View
import android.view.animation.Animation
import android.view.animation.LinearInterpolator
import androidx.core.content.ContextCompat
import com.habitrpg.android.habitica.R
import com.habitrpg.common.habitica.extensions.dpToPx
class IndeterminateProgressView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
var progressBarWidth = 4f.dpToPx(context)
private val rainbow = listOf(
ContextCompat.getColor(context, R.color.red_100),
ContextCompat.getColor(context, R.color.orange_100),
ContextCompat.getColor(context, R.color.yellow_100),
ContextCompat.getColor(context, R.color.green_100),
ContextCompat.getColor(context, R.color.blue_100),
ContextCompat.getColor(context, R.color.brand_400),
ContextCompat.getColor(context, R.color.red_100),
).toIntArray()
val gradient = SweepGradient(225f, 225f, rainbow, null)
private val paint = Paint()
private val isCircular: Boolean
private val cornerRadius = 12f.dpToPx(context)
private var currentAngle = 0f
init {
paint.style = Paint.Style.STROKE
paint.shader = gradient
paint.strokeWidth = 0f
paint.isAntiAlias = true
setWillNotDraw(false)
isCircular = context.resources.configuration.isScreenRound
}
private var animator: ValueAnimator? = null
fun startAnimation() {
animator = ValueAnimator.ofFloat(0f, 359f).apply {
duration = 2000
repeatCount = Animation.INFINITE
repeatMode = ValueAnimator.RESTART
interpolator = LinearInterpolator()
addUpdateListener {
val matrix = Matrix()
matrix.postRotate(it.animatedValue as Float, 225f, 225f)
gradient.setLocalMatrix(matrix)
invalidate()
requestLayout()
}
start()
}
ValueAnimator.ofFloat(0f, progressBarWidth).apply {
duration = 200
addUpdateListener { paint.strokeWidth = it.animatedValue as Float }
start()
}
}
fun stopAnimation() {
animator?.end()
animator = null
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
if (canvas == null) return
val halfBar = paint.strokeWidth / 2f
if (isCircular) {
canvas.drawArc(halfBar, halfBar, width.toFloat() - halfBar, height.toFloat() - halfBar, currentAngle, 360f, false, paint)
} else {
canvas.drawRoundRect(halfBar, halfBar, width.toFloat() - halfBar, height.toFloat() - halfBar, cornerRadius, cornerRadius, paint)
}
}
}

View file

@ -0,0 +1,18 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="430"
android:viewportHeight="430">
<path
android:pathData="M290.6,290.5C287.6,286.6 284.4,291.1 276.8,290.3C271.8,289.8 271.8,287.6 271.8,257.7C271.8,257.7 271.3,255.9 274.2,254.2C277.1,252.6 285.8,238.1 280.4,225.8C275.1,213.4 278.9,214 281.2,215.3C283.4,216.5 284.5,214.9 283.2,208.2C280.4,194.2 276.1,189.4 266,183.1C259.4,179 261.8,171.5 271.2,172C275.9,172.3 275.9,172.3 276.4,168.7C277.4,163 275.5,153.2 268.5,151.9C263.6,151 257.5,156.2 250.3,152.6C243.1,148.9 231.2,158 226.3,160.1C221.5,162.2 217.4,162.2 211.9,162.1C206.3,162 214.8,171.3 223.3,173.4C230.7,175.2 227.8,176.5 227.4,182.5C226.9,189.2 228.8,192.1 225.7,192.8C220.9,193.8 215.6,181 207.4,175.6C188.7,163.3 177.7,169.2 145.1,133.4C139.5,127.2 141.8,146.4 145.2,156.6C154.5,184 172.5,187.6 181,188.4C188.1,189.1 192.9,187.5 192.9,190.2C192.9,192.2 184.5,193.1 181.3,193.1C178.2,193.1 174.7,192.8 170.7,192.1C164.2,190.9 174.1,207.3 180.5,212.5C191.2,221.3 203.4,224.6 213.3,225.6C216.1,225.9 220.2,225.9 220.2,228.1C220.2,230.2 218,230.7 215.5,230.7C201.7,230.8 193.8,241.9 191.5,254.6C190.1,262.7 191.2,271.9 191.3,278.7L191.9,283.6C193,292.1 162.1,295.5 158,281.6C153.6,266.5 177.9,258.6 178.4,244.2C178.7,235.1 170.4,231 170.4,231L170.4,227.3L170.4,217.7L160.8,217.7L160.8,208.2L151.3,208.2L151.3,198.6L141.7,198.6L141.7,208.2L141.7,217.7L141.7,227.3L151.3,227.3L151.3,236.8L160.8,236.8L167,236.8C170.6,236.8 173.4,239.2 173.1,244.3C172.3,256.8 145.5,264.6 152.9,284.6C158.2,299.1 181.1,299.3 204,299.3L204,299.3L246.9,299.3C248.2,299.3 250.1,298.7 247.3,291.7C245.1,286.2 237.5,290.1 231.1,290.1C224.2,290.1 224,284.3 228.4,278.2C231.1,274.5 234.5,271.7 240.3,269C249.2,264.9 256,270.2 259.4,274.8C264.2,281.4 265.7,289 261.5,290.2C256.3,291.6 254.8,291.8 254.6,296.7C254.5,300.2 256.1,299.2 264.2,299.2L290.9,299.2C295.1,299.2 294.8,296 290.6,290.5"
android:strokeColor="#00000000"
android:fillType="evenOdd"
android:fillColor="@color/white"
android:strokeWidth="1"/>
<path
android:pathData="M132.2,198.9l9.5,0l0,-9.5l-9.5,0z"
android:strokeColor="#00000000"
android:fillType="evenOdd"
android:fillColor="@color/white"
android:strokeWidth="1"/>
</vector>

View file

@ -18,13 +18,15 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textEmailAddress"
android:hint="@string/username"
android:autofillHints="username" />
<androidx.appcompat.widget.AppCompatEditText
<EditText
android:id="@+id/password_edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword"
android:hint="@string/password"
android:autofillHints="password"/>
<Button
android:id="@+id/login_button"

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
</FrameLayout>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 982 B

After

Width:  |  Height:  |  Size: 876 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">@color/brand_300</color>
</resources>

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="HabiticaAppTheme" parent="Theme.AppCompat.NoActionBar">
<style name="HabiticaAppTheme" parent="@android:style/Theme.DeviceDefault">
<item name="android:windowBackground">@color/black</item>
</style>
<style name="Chip">