Implement basic habit screen

This commit is contained in:
Phillip Thelen 2022-06-14 11:30:23 +02:00
parent 12b760131e
commit 03c5115352
45 changed files with 293 additions and 10 deletions

View file

@ -43,14 +43,14 @@ class CurrencyViews : LinearLayout {
get() = gemTextView.visibility
set(value) { gemTextView.visibility = value }
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {
val attributes = context?.theme?.obtainStyledAttributes(
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
val attributes = context.theme?.obtainStyledAttributes(
attrs,
R.styleable.CurrencyViews,
0, 0
)
setupViews()
val fallBackLight = context?.isUsingNightModeResources() != true
val fallBackLight = !context.isUsingNightModeResources()
lightBackground = attributes?.getBoolean(R.styleable.CurrencyViews_hasLightBackground, fallBackLight) ?: fallBackLight
}

View file

@ -37,6 +37,7 @@
<activity android:name="com.habitrpg.wearos.habitica.ui.activities.TaskListActivity" />
<activity android:name="com.habitrpg.wearos.habitica.ui.activities.TaskFormActivity" />
<activity android:name="com.habitrpg.wearos.habitica.ui.activities.HabitDirectionActivity" />
<activity android:name="com.habitrpg.wearos.habitica.ui.activities.AvatarActivity" />
<activity android:name="com.habitrpg.wearos.habitica.ui.activities.StatsActivity" />

View file

@ -7,6 +7,7 @@ import com.habitrpg.wearos.habitica.models.tasks.Task
import com.habitrpg.wearos.habitica.models.tasks.TaskList
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.flowOf
import javax.inject.Inject
import javax.inject.Singleton
@ -46,4 +47,14 @@ class TaskLocalRepository @Inject constructor() {
}
tasks[task.type]?.value = oldList
}
fun getTask(taskID: String): Flow<Task?> {
for (type in tasks.values) {
val task = type.value?.firstOrNull { it.id == taskID }
if (task != null) {
return flowOf(task)
}
}
return emptyFlow()
}
}

View file

@ -7,6 +7,7 @@ import com.habitrpg.wearos.habitica.data.ApiClient
import com.habitrpg.wearos.habitica.models.tasks.Task
import com.habitrpg.wearos.habitica.models.tasks.TaskList
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
import javax.inject.Inject
class TaskRepository @Inject constructor(val apiClient: ApiClient, val localRepository: TaskLocalRepository) {
@ -28,4 +29,9 @@ class TaskRepository @Inject constructor(val apiClient: ApiClient, val localRepo
}
return result?.let { TaskScoringResult(it, null) }
}
fun getTask(taskID: String?): Flow<Task?> {
if (taskID == null) return emptyFlow()
return localRepository.getTask(taskID)
}
}

View file

@ -0,0 +1,39 @@
package com.habitrpg.wearos.habitica.ui.activities
import android.os.Bundle
import androidx.activity.viewModels
import androidx.core.content.ContextCompat
import com.habitrpg.android.habitica.databinding.ActivityHabitDirectionBinding
import com.habitrpg.common.habitica.models.responses.TaskDirection
import com.habitrpg.wearos.habitica.ui.viewmodels.HabitDrectionViewModel
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class HabitDirectionActivity: BaseActivity<ActivityHabitDirectionBinding, HabitDrectionViewModel>() {
override val viewModel: HabitDrectionViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
binding = ActivityHabitDirectionBinding.inflate(layoutInflater)
super.onCreate(savedInstanceState)
viewModel.task.observe(this) {
if (it == null) return@observe
val lightTaskColor = ContextCompat.getColor(this, it.lightTaskColor)
val mediumTaskColor = ContextCompat.getColor(this, it.mediumTaskColor)
binding.plusButton.mainTaskColor = lightTaskColor
binding.plusButton.darkerTaskColor = mediumTaskColor
binding.minusButton.mainTaskColor = lightTaskColor
binding.minusButton.darkerTaskColor = mediumTaskColor
binding.textView.text = it.text
}
binding.plusButton.setOnClickListener {
viewModel.scoreTask(TaskDirection.UP)
finish()
}
binding.minusButton.setOnClickListener {
viewModel.scoreTask(TaskDirection.DOWN)
finish()
}
}
}

View file

@ -51,13 +51,16 @@ class TaskListActivity: BaseActivity<ActivityTasklistBinding, TaskListViewModel>
scoreTask(it)
}
binding.addTaskButton.setOnClickListener { }
binding.addTaskButton.setOnClickListener { openTaskFormActivity() }
}
private fun scoreTask(task: Task) {
var direction = TaskDirection.UP
if (task.type == TaskType.HABIT) {
if (task.up == true && task.down == true) {
startActivity(Intent(this, HabitDirectionActivity::class.java).apply {
putExtra("task_id", task.id)
})
return
} else {
direction = if (task.up == true) TaskDirection.UP else TaskDirection.DOWN

View file

@ -4,6 +4,7 @@ import android.content.res.ColorStateList
import android.view.View
import android.widget.TextView
import androidx.core.content.ContextCompat
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.databinding.RowHabitBinding
import com.habitrpg.wearos.habitica.models.tasks.Task
@ -20,6 +21,26 @@ class HabitViewHolder(itemView: View) : TaskViewHolder(itemView) {
override fun bind(data: Task) {
super.bind(data)
binding.habitButton.backgroundTintList = ColorStateList.valueOf(ContextCompat.getColor(itemView.context, data.mediumTaskColor))
if (data.up == true && data.down == true) {
binding.habitButtonIcon.setBackgroundResource(R.drawable.habit_diagonal)
binding.habitButtonIcon.setImageResource(R.drawable.watch_habit_posneg)
} else {
binding.habitButtonIcon.setBackgroundResource(R.drawable.habit_button_round)
if (data.up == true) {
binding.habitButtonIcon.setImageResource(R.drawable.watch_habit_positive)
} else {
binding.habitButtonIcon.setImageResource(R.drawable.watch_habit_negative)
}
}
if (data.up != true && data.down != true) {
binding.habitButton.backgroundTintList = ColorStateList.valueOf(ContextCompat.getColor(itemView.context, R.color.gray_300))
binding.habitButtonIcon.backgroundTintList = ColorStateList.valueOf(ContextCompat.getColor(itemView.context, R.color.gray_100))
binding.habitButtonIcon.imageTintList = ColorStateList.valueOf(ContextCompat.getColor(itemView.context, R.color.gray_300))
} else {
binding.habitButton.backgroundTintList = ColorStateList.valueOf(ContextCompat.getColor(itemView.context, data.lightTaskColor))
binding.habitButtonIcon.backgroundTintList = ColorStateList.valueOf(ContextCompat.getColor(itemView.context, data.mediumTaskColor))
binding.habitButtonIcon.imageTintList = ColorStateList.valueOf(ContextCompat.getColor(itemView.context, R.color.white))
}
}
}

View file

@ -0,0 +1,29 @@
package com.habitrpg.wearos.habitica.ui.viewmodels
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope
import com.habitrpg.common.habitica.models.responses.TaskDirection
import com.habitrpg.wearos.habitica.data.repositories.TaskRepository
import com.habitrpg.wearos.habitica.data.repositories.UserRepository
import com.habitrpg.wearos.habitica.util.ExceptionHandlerBuilder
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
class HabitDrectionViewModel @Inject constructor(
savedStateHandle: SavedStateHandle,
userRepository: UserRepository,
val taskRepository: TaskRepository,
exceptionBuilder: ExceptionHandlerBuilder
) : BaseViewModel(userRepository, exceptionBuilder) {
val taskID = savedStateHandle.get<String>("task_id")
val task = taskRepository.getTask(taskID).asLiveData()
fun scoreTask(direction: TaskDirection) {
viewModelScope.launch(exceptionBuilder.userFacing(this)) {
task.value?.let { taskRepository.scoreTask(it, direction) }
}
}
}

View file

@ -42,7 +42,7 @@ class AddTaskButton @JvmOverloads constructor(
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
rect.right = bottom.toFloat()
rect.right = right.toFloat()
rect.bottom = bottom.toFloat() / 2f
invalidate()
}

View file

@ -0,0 +1,91 @@
package com.habitrpg.wearos.habitica.ui.views
import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Path
import android.graphics.RectF
import android.graphics.drawable.BitmapDrawable
import android.util.AttributeSet
import android.widget.FrameLayout
import androidx.core.content.ContextCompat
import com.habitrpg.android.habitica.R
import com.habitrpg.common.habitica.extensions.dpToPx
class HabitDirectionPickerButton @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : FrameLayout(context, attrs) {
private var drawFromTop: Boolean
private var drawable: BitmapDrawable?
private val paint = Paint()
private val path = Path()
private val rect = RectF(0f, 0f, 0f, 0f)
private val bitmapRect = RectF(0f, 0f, 0f, 0f)
var mainTaskColor: Int = ContextCompat.getColor(context, R.color.gray_300)
var darkerTaskColor: Int = ContextCompat.getColor(context, R.color.gray_200)
var iconColor: Int = ContextCompat.getColor(context, R.color.white)
private val radius = 15.dpToPx(context)
init {
paint.style = Paint.Style.FILL
paint.isAntiAlias = true
setWillNotDraw(false)
val attributes = context.theme.obtainStyledAttributes(
attrs,
R.styleable.HabitDirectionPickerButton,
0, 0
)
drawable = attributes.getDrawable(R.styleable.HabitDirectionPickerButton_drawable) as? BitmapDrawable
drawFromTop = attributes.getBoolean(R.styleable.HabitDirectionPickerButton_drawFromTop, false)
}
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
rect.left = right.toFloat() * 0.125f
rect.right = right.toFloat() * 0.875f
if (drawFromTop) {
rect.top = -bottom.toFloat()
rect.bottom = bottom.toFloat()
} else {
rect.bottom = bottom.toFloat() / 2f
}
val middleX = width / 2f
val middleY = height / 2f
val bitmapWidthHalf = (drawable?.bitmap?.width?.toFloat() ?: 0f) / 2f
val bitmapHeightHalf = (drawable?.bitmap?.height?.toFloat() ?: 0f) / 2f
bitmapRect.left = middleX - bitmapWidthHalf
bitmapRect.top = middleY - bitmapHeightHalf
bitmapRect.right = middleX + bitmapWidthHalf
bitmapRect.bottom = middleY + bitmapHeightHalf
invalidate()
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
if (canvas == null) return
path.reset()
if (drawFromTop) {
path.addArc(rect, 0f, 180f)
} else {
path.addArc(rect, 180f, 360f)
}
paint.color = mainTaskColor
canvas.drawPath(path, paint)
val middleX = width / 2f
val middleY = height / 2f
paint.color = darkerTaskColor
canvas.drawArc(middleX - radius, middleY - radius, middleX + radius, middleY + radius, 0f, 360f, true, paint)
drawable?.let {
paint.color = iconColor
canvas.drawBitmap(it.bitmap, null, bitmapRect, paint)
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 636 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 232 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 636 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 219 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 279 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 243 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 292 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 B

View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:width="34dp" android:height="34dp">
<shape />
</item>
<item android:width="34dp" android:height="17dp" android:top="14dp">
<rotate android:fromDegrees="315">
<shape android:shape="rectangle">
<solid android:color="@color/white" />
<corners android:bottomLeftRadius="17dp" android:bottomRightRadius="17dp"/>
</shape>
</rotate>
</item>
</layer-list>

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:width="20dp" android:height="20dp" android:gravity="center">
<shape android:shape="oval">
<size android:width="20dp" android:height="20dp" />
<solid android:color="@color/white" />
</shape>
</item>
</layer-list>

View file

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.habitrpg.wearos.habitica.ui.views.HabitDirectionPickerButton
android:id="@+id/plus_button"
android:layout_width="match_parent"
android:layout_height="50dp"
app:drawable="@drawable/watch_task_add"
app:drawFromTop="true"
/>
<Space
android:layout_width="1dp"
android:layout_height="0dp"
android:layout_weight="1" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/how_did_you_do"
android:fontFamily="sans-serif-medium"
android:gravity="center"
android:textSize="14sp"
android:textColor="@color/gray_400"/>
<TextView
android:id="@+id/text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fontFamily="sans-serif-medium"
android:gravity="center"
android:textSize="16sp"
android:maxLines="2"
android:textColor="@color/white"/>
<Space
android:layout_width="1dp"
android:layout_height="0dp"
android:layout_weight="1" />
<com.habitrpg.wearos.habitica.ui.views.HabitDirectionPickerButton
android:id="@+id/minus_button"
android:layout_width="match_parent"
android:layout_height="50dp"
app:drawable="@drawable/watch_task_minus"
/>
</LinearLayout>

View file

@ -7,13 +7,19 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/Chip">
<ImageView
<FrameLayout
android:id="@+id/habit_button"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_width="34dp"
android:layout_height="34dp"
android:background="@drawable/circle"
android:layout_marginEnd="@dimen/spacing_medium"
/>
>
<ImageView
android:id="@+id/habit_button_icon"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="center"/>
</FrameLayout>
<TextView
android:id="@+id/title"
android:layout_width="match_parent"

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="HabitDirectionPickerButton">
<attr name="drawable" format="integer" />
<attr name="drawFromTop" format="boolean" />
</declare-styleable>
</resources>

View file

@ -1,4 +1,5 @@
<resources>
<string name="ok">OK</string>
<string name="syncing_account">Syncing Account from Phone</string>
<string name="how_did_you_do">How did you do?</string>
</resources>