Improve login flow
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
18
wearos/src/main/res/drawable/ic_launcher_foreground.xml
Normal 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>
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
7
wearos/src/main/res/layout/activity_wrapper.xml
Normal 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>
|
||||
5
wearos/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
Normal 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>
|
||||
|
|
@ -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>
|
||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
BIN
wearos/src/main/res/mipmap-hdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 982 B After Width: | Height: | Size: 876 B |
BIN
wearos/src/main/res/mipmap-mdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
BIN
wearos/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
BIN
wearos/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.6 KiB |
BIN
wearos/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 8.9 KiB |
4
wearos/src/main/res/values/colors.xml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">@color/brand_300</color>
|
||||
</resources>
|
||||
|
|
@ -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">
|
||||
|
|
|
|||