diff --git a/wearos/build.gradle b/wearos/build.gradle index 2e02a5ebe..0424d3541 100644 --- a/wearos/build.gradle +++ b/wearos/build.gradle @@ -56,6 +56,7 @@ dependencies { implementation("com.squareup.retrofit2:converter-moshi:$retrofit_version") implementation("com.squareup.moshi:moshi-kotlin:$moshi_version") implementation 'androidx.coordinatorlayout:coordinatorlayout:1.2.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.4' kapt("com.squareup.moshi:moshi-kotlin-codegen:$moshi_version") //Analytics diff --git a/wearos/src/main/java/com/habitrpg/wearos/habitica/extensions/ViewExt.kt b/wearos/src/main/java/com/habitrpg/wearos/habitica/extensions/ViewExt.kt new file mode 100644 index 000000000..124990d2e --- /dev/null +++ b/wearos/src/main/java/com/habitrpg/wearos/habitica/extensions/ViewExt.kt @@ -0,0 +1,15 @@ +package com.habitrpg.wearos.habitica.extensions + +import android.view.View +import android.view.ViewTreeObserver + +inline fun View.waitForLayout(crossinline f: View.() -> Unit) = with(viewTreeObserver) { + addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener { + override fun onGlobalLayout() { + viewTreeObserver.removeOnGlobalLayoutListener(this) + f() + } + }) +} + + diff --git a/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/activities/StatsActivity.kt b/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/activities/StatsActivity.kt index a6f15d7b0..4761da196 100644 --- a/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/activities/StatsActivity.kt +++ b/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/activities/StatsActivity.kt @@ -1,17 +1,70 @@ package com.habitrpg.wearos.habitica.ui.activities import android.os.Bundle +import android.view.View +import android.view.ViewTreeObserver import androidx.activity.viewModels +import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.databinding.ActivityStatsBinding +import com.habitrpg.android.habitica.extensions.observeOnce +import com.habitrpg.common.habitica.views.HabiticaIconsHelper +import com.habitrpg.wearos.habitica.extensions.waitForLayout +import com.habitrpg.wearos.habitica.models.Stats +import com.habitrpg.wearos.habitica.models.User import com.habitrpg.wearos.habitica.ui.viewmodels.StatsViewModel +import com.habitrpg.wearos.habitica.ui.views.StatValue import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint -class StatsActivity: BaseActivity() { +class StatsActivity : BaseActivity() { override val viewModel: StatsViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { binding = ActivityStatsBinding.inflate(layoutInflater) super.onCreate(savedInstanceState) + + setStatViews() + viewModel.user.observe(this) { + updateStats(it) + } } -} \ No newline at end of file + + private fun setStatViews() { + binding.hpStatValue.setStatValueResources(HabiticaIconsHelper.imageOfHeartLightBg(), R.color.hp_bar_color) + binding.expStatValue.setStatValueResources(HabiticaIconsHelper.imageOfExperience(), R.color.exp_bar_color) + binding.mpStatValue.setStatValueResources(HabiticaIconsHelper.imageOfMagic(), R.color.mpColor) + } + + private fun updateStats(user: User) { + val stats = user.stats + stats?.let { updateBarViews(it) } + stats?.let { updateStatViews(it) } + } + + private fun updateBarViews(stats: Stats) { + binding.hpBar.setPercentageValues(stats.hp?.toInt() ?: 0, stats.maxHealth ?: 0) + binding.hpBar.animateProgress() + + binding.expBar.setPercentageValues(stats.exp?.toInt() ?: 0, stats.toNextLevel ?: 0) + binding.expBar.animateProgress() + + if (stats.lvl ?: 0 < 10) { + binding.mpBar.visibility = View.GONE + } else { + binding.mpBar.setPercentageValues(stats.mp?.toInt() ?: 0, stats.maxMP ?: 0) + binding.mpBar.animateProgress() + } + } + + private fun updateStatViews(stats: Stats) { + binding.hpStatValue.setStatValue(stats.maxHealth ?: 0, stats.hp?.toInt() ?: 0) + binding.expStatValue.setStatValue(stats.toNextLevel ?: 0, stats.exp?.toInt() ?: 0) + if (stats.lvl ?: 0 < 10) { + binding.mpStatValue.visibility = View.GONE + } else { + binding.mpStatValue.setStatValue(stats.maxMP ?: 0, stats.mp?.toInt() ?: 0) + } + } +} + + diff --git a/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/viewmodels/StatsViewModel.kt b/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/viewmodels/StatsViewModel.kt index 270090c90..8623cfadb 100644 --- a/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/viewmodels/StatsViewModel.kt +++ b/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/viewmodels/StatsViewModel.kt @@ -1,6 +1,9 @@ package com.habitrpg.wearos.habitica.ui.viewmodels +import androidx.lifecycle.LiveData +import androidx.lifecycle.asLiveData import com.habitrpg.wearos.habitica.data.repositories.UserRepository +import com.habitrpg.wearos.habitica.models.User import com.habitrpg.wearos.habitica.util.ExceptionHandlerBuilder import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject @@ -10,4 +13,6 @@ class StatsViewModel @Inject constructor(userRepository: UserRepository, exceptionBuilder: ExceptionHandlerBuilder ) : BaseViewModel(userRepository, exceptionBuilder) { + var user: LiveData = userRepository.getUser().asLiveData() + } \ No newline at end of file diff --git a/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/views/CircularProgressView.kt b/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/views/CircularProgressView.kt new file mode 100644 index 000000000..f48981fce --- /dev/null +++ b/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/views/CircularProgressView.kt @@ -0,0 +1,127 @@ +package com.habitrpg.wearos.habitica.ui.views + +import android.animation.PropertyValuesHolder +import android.animation.ValueAnimator +import android.content.Context +import android.graphics.* +import android.util.AttributeSet +import android.view.View +import android.view.animation.AccelerateDecelerateInterpolator +import com.habitrpg.android.habitica.R + + +class CircularProgressView( + context: Context?, + attrs: AttributeSet? +) : View(context, attrs) { + private val ovalSpace = RectF() + private var ovalSize = (resources.displayMetrics.heightPixels / 2) + private var currentPercentage = 55 + private var PERCENTAGE_DIVIDER = 180 + private val ARC_FULL_ROTATION_DEGREE = 360 + val attributes = context?.theme?.obtainStyledAttributes( + attrs, + R.styleable.CircularProgressView, + 0, 0 + ) + private val offset = attributes?.getInt(R.styleable.CircularProgressView_offset, 0) + private val backgroundArcColor = attributes?.getColor(R.styleable.CircularProgressView_backgroundArcColor, 0) ?: Color.GRAY + private var fillArcColor = attributes?.getColor(R.styleable.CircularProgressView_arcFillColor, 0) ?: Color.GRAY + + + private val parentArcPaint = Paint().apply { + style = Paint.Style.STROKE + isAntiAlias = true + color = backgroundArcColor + strokeWidth = 10f + } + + private var fillArcPaint = Paint().apply { + style = Paint.Style.STROKE + isAntiAlias = true + color = fillArcColor + strokeWidth = 10f + strokeCap = Paint.Cap.ROUND + } + + override fun onDraw(canvas: Canvas?) { + setSpace() + canvas?.let { + drawBackgroundArc(it) + drawInnerArc(it) + } + } + + override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { + super.onLayout(changed, left, top, right, bottom) + offset?.let { ovalSize = (height / 2) - it } + invalidate() + } + + private fun setSpace() { + val horizontalCenter = (width.div(2)).toFloat() + val verticalCenter = (height.div(2)).toFloat() + val ovalSize = this.ovalSize + ovalSpace.set( + horizontalCenter - ovalSize, + verticalCenter - ovalSize, + horizontalCenter + ovalSize, + verticalCenter + ovalSize + ) + } + + + private fun drawBackgroundArc(it: Canvas) { + it.drawArc(ovalSpace, 0f, 360f, false, parentArcPaint) + } + + private fun drawInnerArc(canvas: Canvas) { + val percentageToFill = getCurrentAngleToFill() + canvas.drawArc(ovalSpace, 270f, percentageToFill, false, fillArcPaint) + } + + fun setBarColor(barColor: Int) { + fillArcColor = context?.resources?.getColor(barColor, null) ?: backgroundArcColor + fillArcPaint = Paint().apply { + style = Paint.Style.STROKE + isAntiAlias = true + color = fillArcColor + strokeWidth = 10f + strokeCap = Paint.Cap.ROUND + } + } + + fun setPercentageValues(currentValue: Int, maxValue: Int) { + currentPercentage = currentValue + PERCENTAGE_DIVIDER = maxValue + } + + fun animateProgress() { + val currentPercent: Int = currentPercentage + val valuesHolder = PropertyValuesHolder.ofFloat( + PERCENTAGE_VALUE_HOLDER, + 1f, + currentPercent.toFloat() + ) + + val animator = ValueAnimator().apply { + setValues(valuesHolder) + duration = 1000 + interpolator = AccelerateDecelerateInterpolator() + + addUpdateListener { + val percentage = it.getAnimatedValue(PERCENTAGE_VALUE_HOLDER) as Float + currentPercentage = percentage.toInt() + invalidate() + } + } + animator.start() + } + + companion object { + const val PERCENTAGE_VALUE_HOLDER = "percentage" + } + + private fun getCurrentAngleToFill() = if(currentPercentage > 0) {(ARC_FULL_ROTATION_DEGREE.toFloat() * (currentPercentage.toFloat() / PERCENTAGE_DIVIDER.toFloat()))} else {1f} +} + diff --git a/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/views/StatValue.kt b/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/views/StatValue.kt new file mode 100644 index 000000000..5a82b7ee2 --- /dev/null +++ b/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/views/StatValue.kt @@ -0,0 +1,34 @@ +package com.habitrpg.wearos.habitica.ui.views + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.Color +import android.util.AttributeSet +import androidx.constraintlayout.widget.ConstraintLayout +import com.habitrpg.android.habitica.databinding.StatValueLayoutBinding +import com.habitrpg.common.habitica.extensions.layoutInflater + +class StatValue @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) : + ConstraintLayout( + context, + attrs, + defStyle + ) { + + var binding = StatValueLayoutBinding.inflate(context.layoutInflater, this) + + fun setStatValue(maxValue: Int, currentValue: Int) { + binding.currentValue.text = currentValue.toString() + binding.maxValue.text = "/$maxValue" + invalidate() + } + + fun setStatValueResources(bitmap: Bitmap, bitmapColor: Int) { + binding.bitmap.setImageBitmap(bitmap) + binding.currentValue.setTextColor( + context?.resources?.getColor(bitmapColor, null) ?: Color.WHITE + ) + } + + +} \ No newline at end of file diff --git a/wearos/src/main/res/font/press_start_reg.ttf b/wearos/src/main/res/font/press_start_reg.ttf new file mode 100644 index 000000000..2442affbf Binary files /dev/null and b/wearos/src/main/res/font/press_start_reg.ttf differ diff --git a/wearos/src/main/res/layout/activity_stats.xml b/wearos/src/main/res/layout/activity_stats.xml index d829e291c..81d303763 100644 --- a/wearos/src/main/res/layout/activity_stats.xml +++ b/wearos/src/main/res/layout/activity_stats.xml @@ -1,7 +1,56 @@ - - \ No newline at end of file + + + + + + + + + + + + + + + + + + diff --git a/wearos/src/main/res/layout/stat_value_layout.xml b/wearos/src/main/res/layout/stat_value_layout.xml new file mode 100644 index 000000000..ba6196678 --- /dev/null +++ b/wearos/src/main/res/layout/stat_value_layout.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/wearos/src/main/res/values/attrs.xml b/wearos/src/main/res/values/attrs.xml index cad2a1e9c..f07dab31f 100644 --- a/wearos/src/main/res/values/attrs.xml +++ b/wearos/src/main/res/values/attrs.xml @@ -4,4 +4,9 @@ + + + + + \ No newline at end of file diff --git a/wearos/src/main/res/values/colors.xml b/wearos/src/main/res/values/colors.xml index 11acd5c24..394dc8f2d 100644 --- a/wearos/src/main/res/values/colors.xml +++ b/wearos/src/main/res/values/colors.xml @@ -1,4 +1,9 @@ @color/brand_300 + #34313a + @color/yellow_100 + #a9dcf6 + #f2666a + \ No newline at end of file