Various minor improvements

This commit is contained in:
Phillip Thelen 2022-06-27 15:23:00 +02:00
parent b71e0c4842
commit 0d0c4e4038
43 changed files with 340 additions and 165 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 746 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 499 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 954 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

View file

@ -4,8 +4,7 @@
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">8dp</dimen>
<dimen name="card_medium_text">18.0sp</dimen>
<dimen name="card_small_text">14.0sp</dimen>
<dimen name="card_padding">12dp</dimen>
<dimen name="card_padding_compact">6dp</dimen>
<dimen name="section_leftright_padding">6dp</dimen>

View file

@ -270,7 +270,6 @@
&#8226; Bosses wont do damage for your missed Dailies\n
&#8226; Your boss damage or collection quest items will stay pending until check-out</string>
<string name="empty_items">You don\'t have any %s</string>
<string name="user_level">Lvl %d</string>
<string name="user_level_with_class">Lvl %1$d %2$s</string>
<string name="user_level_with_class_unabbreviated">Level %1$d %2$s</string>
<string name="warrior">Warrior</string>
@ -1138,9 +1137,7 @@
<string name="locked_equipment_shop_dialog">You must purchase the previous items in this sequence to unlock</string>
<string name="caused_damage">You caused damage to the boss</string>
<string name="avatar_style">Style</string>
<string name="failed">Failed to load</string>
<string name="daily_item_checkbox">Daily Checkbox</string>
<string name="retry">Retry</string>
<string name="empty_title">Nothing here yet</string>
<string name="empty_equipment_description">You don\'t have any equipment of this type yet. You can purchase equipment from the market using the gold you earned.</string>
<string name="todo_item_checkbox">Todo Checkbox</string>

View file

@ -31,7 +31,7 @@ import com.habitrpg.android.habitica.models.user.Outfit
import com.habitrpg.android.habitica.models.user.Stats
import com.habitrpg.android.habitica.ui.AvatarWithBarsViewModel
import com.habitrpg.android.habitica.ui.adapter.social.AchievementProfileAdapter
import com.habitrpg.android.habitica.ui.helpers.RecyclerViewState
import com.habitrpg.common.habitica.helpers.RecyclerViewState
import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar
import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar.SnackbarDisplayType
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog

View file

@ -15,7 +15,7 @@ import com.habitrpg.android.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.ui.adapter.inventory.EquipmentRecyclerViewAdapter
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
import com.habitrpg.android.habitica.ui.helpers.EmptyItem
import com.habitrpg.common.habitica.helpers.EmptyItem
import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator
import javax.inject.Inject

View file

@ -31,7 +31,7 @@ import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.ui.activities.MainActivity
import com.habitrpg.android.habitica.ui.adapter.inventory.ItemRecyclerAdapter
import com.habitrpg.android.habitica.ui.fragments.BaseDialogFragment
import com.habitrpg.android.habitica.ui.helpers.EmptyItem
import com.habitrpg.common.habitica.helpers.EmptyItem
import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator
import com.habitrpg.common.habitica.extensions.loadImage
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel

View file

@ -37,7 +37,7 @@ import com.habitrpg.android.habitica.ui.activities.MainActivity
import com.habitrpg.android.habitica.ui.activities.SkillMemberActivity
import com.habitrpg.android.habitica.ui.adapter.inventory.ItemRecyclerAdapter
import com.habitrpg.android.habitica.ui.fragments.BaseFragment
import com.habitrpg.android.habitica.ui.helpers.EmptyItem
import com.habitrpg.common.habitica.helpers.EmptyItem
import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator
import com.habitrpg.common.habitica.extensions.loadImage
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel

View file

@ -21,7 +21,7 @@ import com.habitrpg.android.habitica.models.social.Group
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.ui.adapter.inventory.ShopRecyclerAdapter
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
import com.habitrpg.android.habitica.ui.helpers.RecyclerViewState
import com.habitrpg.common.habitica.helpers.RecyclerViewState
import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import com.habitrpg.android.habitica.ui.views.CurrencyViews

View file

@ -19,7 +19,7 @@ import com.habitrpg.android.habitica.models.inventory.Egg
import com.habitrpg.android.habitica.models.inventory.HatchingPotion
import com.habitrpg.android.habitica.ui.adapter.inventory.StableRecyclerAdapter
import com.habitrpg.android.habitica.ui.fragments.BaseFragment
import com.habitrpg.android.habitica.ui.helpers.EmptyItem
import com.habitrpg.common.habitica.helpers.EmptyItem
import com.habitrpg.android.habitica.ui.helpers.MarginDecoration
import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel

View file

@ -18,7 +18,7 @@ import com.habitrpg.android.habitica.models.social.Group
import com.habitrpg.android.habitica.modules.AppModule
import com.habitrpg.android.habitica.ui.adapter.social.ChallengesListViewAdapter
import com.habitrpg.android.habitica.ui.fragments.BaseFragment
import com.habitrpg.android.habitica.ui.helpers.EmptyItem
import com.habitrpg.common.habitica.helpers.EmptyItem
import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.kotlin.Flowables

View file

@ -13,7 +13,7 @@ import com.habitrpg.android.habitica.databinding.FragmentRefreshRecyclerviewBind
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.ui.adapter.social.GuildListAdapter
import com.habitrpg.android.habitica.ui.fragments.BaseFragment
import com.habitrpg.android.habitica.ui.helpers.EmptyItem
import com.habitrpg.common.habitica.helpers.EmptyItem
import com.habitrpg.android.habitica.ui.helpers.KeyboardUtil
import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator
import javax.inject.Inject

View file

@ -38,7 +38,7 @@ import com.habitrpg.android.habitica.ui.adapter.tasks.RewardsRecyclerViewAdapter
import com.habitrpg.android.habitica.ui.adapter.tasks.TaskRecyclerViewAdapter
import com.habitrpg.android.habitica.ui.adapter.tasks.TodosRecyclerViewAdapter
import com.habitrpg.android.habitica.ui.fragments.BaseFragment
import com.habitrpg.android.habitica.ui.helpers.EmptyItem
import com.habitrpg.common.habitica.helpers.EmptyItem
import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator
import com.habitrpg.android.habitica.ui.viewHolders.tasks.BaseTaskViewHolder
import com.habitrpg.android.habitica.ui.viewmodels.TasksViewModel

View file

@ -2,34 +2,18 @@ package com.habitrpg.android.habitica.ui.helpers
import android.content.Context
import android.util.AttributeSet
import android.view.View
import android.view.ViewGroup
import android.view.animation.AlphaAnimation
import android.widget.ProgressBar
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.databinding.EmptyItemBinding
import com.habitrpg.android.habitica.databinding.FailedItemBinding
import com.habitrpg.android.habitica.extensions.inflate
import com.habitrpg.common.habitica.helpers.EmptyItem
import com.habitrpg.common.habitica.helpers.RecyclerViewState
import com.habitrpg.common.habitica.helpers.RecyclerViewStateAdapter
data class EmptyItem(
var title: String,
var text: String? = null,
var iconResource: Int? = null,
var buttonLabel: String? = null,
var onButtonTap: (() -> Unit)? = null
)
class RecyclerViewEmptySupport @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : RecyclerView(context, attrs) {
var onRefresh: (() -> Unit)?
get() = emptyAdapter.onRefresh
set(value) { emptyAdapter.onRefresh = value }
enum class RecyclerViewState {
LOADING,
EMPTY,
DISPLAYING_DATA,
FAILED
}
class RecyclerViewEmptySupport : RecyclerView {
var onRefresh: (() -> Unit)? = null
var state: RecyclerViewState = RecyclerViewState.LOADING
set(value) {
field = value
@ -37,7 +21,7 @@ class RecyclerViewEmptySupport : RecyclerView {
RecyclerViewState.DISPLAYING_DATA -> updateAdapter(actualAdapter)
else -> {
updateAdapter(emptyAdapter)
emptyAdapter.notifyDataSetChanged()
emptyAdapter.state = value
}
}
}
@ -48,53 +32,16 @@ class RecyclerViewEmptySupport : RecyclerView {
}
}
var emptyItem: EmptyItem? = null
var emptyItem: EmptyItem?
get() = emptyAdapter.emptyItem
set(value) {
field = value
emptyAdapter.notifyDataSetChanged()
emptyAdapter.emptyItem = value
}
private var actualAdapter: Adapter<*>? = null
private val emptyAdapter: Adapter<ViewHolder> = object : Adapter<ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return when (viewType) {
0 -> {
val view = parent.inflate(R.layout.loading_item)
val animation1 = AlphaAnimation(0.0f, 1.0f)
animation1.duration = 300
animation1.startOffset = 500
animation1.fillAfter = true
view.findViewById<ProgressBar>(R.id.loading_indicator).startAnimation(animation1)
object : ViewHolder(view) {}
}
1 -> FailedViewHolder(parent.inflate(R.layout.failed_item))
else -> EmptyViewHolder(parent.inflate(R.layout.empty_item))
}
}
private val emptyAdapter = RecyclerViewStateAdapter()
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
if (holder is EmptyViewHolder) {
holder.bind(emptyItem)
} else if (holder is FailedViewHolder) {
holder.bind(onRefresh)
}
(holder as? EmptyViewHolder)?.bind(emptyItem)
}
override fun getItemCount(): Int {
return 1
}
override fun getItemViewType(position: Int): Int {
return when (state) {
RecyclerViewState.LOADING -> 0
RecyclerViewState.FAILED -> 1
else -> 2
}
}
}
private val observer = object : RecyclerView.AdapterDataObserver() {
private val observer = object : AdapterDataObserver() {
override fun onChanged() {
updateState()
}
@ -108,22 +55,16 @@ class RecyclerViewEmptySupport : RecyclerView {
}
}
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle)
internal fun updateState(isInitial: Boolean = false) {
if (actualAdapter != null && !isInitial) {
state = if (actualAdapter != null && !isInitial) {
val emptyViewVisible = actualAdapter?.itemCount == 0
if (emptyViewVisible) {
state = RecyclerViewState.EMPTY
RecyclerViewState.EMPTY
} else {
state = RecyclerViewState.DISPLAYING_DATA
RecyclerViewState.DISPLAYING_DATA
}
} else {
state = RecyclerViewState.LOADING
RecyclerViewState.LOADING
}
}
@ -135,38 +76,4 @@ class RecyclerViewEmptySupport : RecyclerView {
actualAdapter = adapter
updateState(true)
}
}
class FailedViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val binding = FailedItemBinding.bind(itemView)
fun bind(onRefresh: (() -> Unit)?) {
if (onRefresh != null) {
binding.refreshButton.visibility = View.VISIBLE
binding.refreshButton.setOnClickListener { onRefresh() }
} else {
binding.refreshButton.visibility = View.GONE
}
}
}
class EmptyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val binding = EmptyItemBinding.bind(itemView)
fun bind(emptyItem: EmptyItem?) {
binding.emptyIconView.setColorFilter(ContextCompat.getColor(itemView.context, R.color.text_dimmed), android.graphics.PorterDuff.Mode.MULTIPLY)
emptyItem?.iconResource?.let { binding.emptyIconView.setImageResource(it) }
binding.emptyViewTitle.text = emptyItem?.title
binding.emptyViewDescription.text = emptyItem?.text
val buttonLabel = emptyItem?.buttonLabel
if (buttonLabel != null) {
binding.button.visibility = View.VISIBLE
binding.button.text = buttonLabel
binding.button.setOnClickListener { emptyItem.onButtonTap?.invoke() }
} else {
binding.button.visibility = View.GONE
}
}
}
}

View file

@ -0,0 +1,118 @@
package com.habitrpg.common.habitica.helpers
import android.view.View
import android.view.ViewGroup
import android.view.animation.AlphaAnimation
import android.widget.ProgressBar
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
import com.habitrpg.android.habitica.extensions.inflate
import com.habitrpg.common.habitica.R
import com.habitrpg.common.habitica.databinding.EmptyItemBinding
import com.habitrpg.common.habitica.databinding.FailedItemBinding
data class EmptyItem(
var title: String,
var text: String? = null,
var iconResource: Int? = null,
var buttonLabel: String? = null,
var onButtonTap: (() -> Unit)? = null
)
enum class RecyclerViewState {
LOADING,
EMPTY,
DISPLAYING_DATA,
FAILED
}
class FailedViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val binding = FailedItemBinding.bind(itemView)
fun bind(onRefresh: (() -> Unit)?) {
if (onRefresh != null) {
binding.refreshButton.visibility = View.VISIBLE
binding.refreshButton.setOnClickListener { onRefresh() }
} else {
binding.refreshButton.visibility = View.GONE
}
}
}
class EmptyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val binding = EmptyItemBinding.bind(itemView)
fun bind(emptyItem: EmptyItem?) {
binding.emptyIconView.setColorFilter(
ContextCompat.getColor(
itemView.context,
R.color.text_dimmed
), android.graphics.PorterDuff.Mode.MULTIPLY)
emptyItem?.iconResource?.let { binding.emptyIconView.setImageResource(it) }
binding.emptyViewTitle.text = emptyItem?.title
binding.emptyViewDescription.text = emptyItem?.text
val buttonLabel = emptyItem?.buttonLabel
if (buttonLabel != null) {
binding.button.visibility = View.VISIBLE
binding.button.text = buttonLabel
binding.button.setOnClickListener { emptyItem.onButtonTap?.invoke() }
} else {
binding.button.visibility = View.GONE
}
}
}
class RecyclerViewStateAdapter(val showLoadingAsEmpty: Boolean = false) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
var onRefresh: (() -> Unit)? = null
var emptyItem: EmptyItem? = null
set(value) {
field = value
notifyItemChanged(0)
}
var state: RecyclerViewState = RecyclerViewState.LOADING
set(value) {
field = value
notifyItemChanged(0)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
0 -> {
val view = parent.inflate(R.layout.loading_item)
val animation1 = AlphaAnimation(0.0f, 1.0f)
animation1.duration = 300
animation1.startOffset = 500
animation1.fillAfter = true
view.findViewById<ProgressBar>(R.id.loading_indicator).startAnimation(animation1)
object : RecyclerView.ViewHolder(view) {}
}
1 -> FailedViewHolder(parent.inflate(R.layout.failed_item))
else -> EmptyViewHolder(parent.inflate(R.layout.empty_item))
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (holder is EmptyViewHolder) {
holder.bind(emptyItem)
} else if (holder is FailedViewHolder) {
holder.bind(onRefresh)
}
(holder as? EmptyViewHolder)?.bind(emptyItem)
}
override fun getItemCount(): Int {
return 1
}
override fun getItemViewType(position: Int): Int {
return when {
state == RecyclerViewState.LOADING && !showLoadingAsEmpty -> 0
state == RecyclerViewState.FAILED -> 1
else -> 2
}
}
}

View file

@ -0,0 +1,16 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#FFFFFF"
android:alpha="0.8">
<group android:scaleX="1.2"
android:scaleY="1.2"
android:translateX="-2.4"
android:translateY="-2.4">
<path
android:fillColor="@android:color/white"
android:pathData="M11,15h2v2h-2zM11,7h2v6h-2zM11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z"/>
</group>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 577 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 424 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 904 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -26,10 +26,10 @@
<TextView
android:id="@+id/emptyViewDescription"
style="@style/Body2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:textSize="14sp"
android:textColor="@color/text_ternary"
tools:text="No Items" />
@ -37,7 +37,6 @@
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/HabiticaButton.Primary"
android:visibility="gone"
tools:visibility="visible"
android:layout_marginTop="@dimen/spacing_large"/>

View file

@ -2,24 +2,23 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:gravity="center"
android:orientation="vertical">
<androidx.constraintlayout.utils.widget.ImageFilterView
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/baseline_error_outline_black_36dp"
android:tint="@color/text_quad"/>
android:src="@drawable/failed_loading"
app:tint="@color/text_quad"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/failed"
style="@style/Headline"
android:textColor="@color/text_primary"/>
<Button
android:id="@+id/refresh_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/HabiticaButton.Primary"
android:text="@string/retry"
android:layout_marginTop="@dimen/spacing_large" />
</LinearLayout>

View file

@ -12,4 +12,7 @@
<dimen name="spacing_xlarge">32dp</dimen>
<dimen name="icon_size">18dp</dimen>
<dimen name="card_medium_text">18.0sp</dimen>
<dimen name="card_small_text">14.0sp</dimen>
</resources>

View file

@ -27,6 +27,7 @@
<string name="settings">Settings</string>
<string name="new_task">New Task</string>
<string name="avatar">Avatar</string>
<string name="user_level">Lvl. %d</string>
<string name="action_edit">Edit</string>
<string name="action_cancel">Cancel</string>
@ -56,4 +57,8 @@
<string name="create_task">Create %s</string>
<string name="are_you_sure">Are you sure?</string>
<string name="task">Task</string>
<string name="failed">Failed to load</string>
<string name="retry">Retry</string>
</resources>

View file

@ -8,6 +8,8 @@ import com.habitrpg.wearos.habitica.models.tasks.TaskList
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import java.util.Date
import javax.inject.Inject
import javax.inject.Singleton
@ -19,6 +21,9 @@ class TaskLocalRepository @Inject constructor() {
TaskType.TODO to MutableLiveData<List<Task>>(),
TaskType.REWARD to MutableLiveData<List<Task>>()
)
private val taskCountHelperValue = MutableLiveData<Long>()
fun getTasks(type: TaskType): Flow<List<Task>> {
return tasks[type]?.asFlow() ?: emptyFlow()
}
@ -38,6 +43,7 @@ class TaskLocalRepository @Inject constructor() {
for (type in taskMap) {
this.tasks[type.key]?.value = type.value
}
taskCountHelperValue.value = Date().time
}
fun updateTask(task: Task) {
@ -51,6 +57,7 @@ class TaskLocalRepository @Inject constructor() {
oldList?.let {
tasks[task.type]?.value = it
}
taskCountHelperValue.value = Date().time
}
fun getTask(taskID: String): Flow<Task?> {
@ -63,10 +70,23 @@ class TaskLocalRepository @Inject constructor() {
return emptyFlow()
}
fun getTaskCounts() = flowOf(mapOf(
fun getTaskCounts() = taskCountHelperValue.asFlow().map {
mapOf(
TaskType.HABIT.value to (tasks[TaskType.HABIT]?.value?.size ?: 0),
TaskType.DAILY.value to (tasks[TaskType.DAILY]?.value?.size ?: 0),
TaskType.TODO.value to (tasks[TaskType.TODO]?.value?.size ?: 0),
TaskType.REWARD.value to (tasks[TaskType.REWARD]?.value?.size ?: 0),
))
)
}
fun getActiveTaskCounts() = taskCountHelperValue.asFlow().map {
mapOf(
TaskType.HABIT.value to (tasks[TaskType.HABIT]?.value?.size ?: 0),
TaskType.DAILY.value to (tasks[TaskType.DAILY]?.value?.filter { it.isDue == true && !it.completed }?.size
?: 0),
TaskType.TODO.value to (tasks[TaskType.TODO]?.value?.filter { !it.completed }?.size
?: 0),
TaskType.REWARD.value to (tasks[TaskType.REWARD]?.value?.size ?: 0),
)
}
}

View file

@ -43,4 +43,5 @@ class TaskRepository @Inject constructor(val apiClient: ApiClient, val localRepo
}
fun getTaskCounts() = localRepository.getTaskCounts()
fun getActiveTaskCounts() = localRepository.getActiveTaskCounts()
}

View file

@ -9,6 +9,7 @@ data class MenuItem(
val color: Int,
val textColor: Int,
val isProminent: Boolean = false,
val isHidden: Boolean = false,
var detailText: String? = null,
val onClick: () -> Unit
)

View file

@ -43,15 +43,6 @@ class MainActivity : BaseActivity<ActivityMainBinding, MainViewModel>() {
) {
openTaskFormActivity()
},
MenuItem(
TaskType.HABIT.value,
getString(R.string.habits),
AppCompatResources.getDrawable(this, R.drawable.icon_habits),
ContextCompat.getColor(this, R.color.watch_purple_200),
ContextCompat.getColor(this, R.color.watch_purple_700)
) {
openTasklist(TaskType.HABIT)
},
MenuItem(
TaskType.DAILY.value,
getString(R.string.dailies),
@ -70,17 +61,27 @@ class MainActivity : BaseActivity<ActivityMainBinding, MainViewModel>() {
) {
openTasklist(TaskType.TODO)
},
MenuItem(
TaskType.HABIT.value,
getString(R.string.habits),
AppCompatResources.getDrawable(this, R.drawable.icon_habits),
ContextCompat.getColor(this, R.color.watch_purple_200),
ContextCompat.getColor(this, R.color.watch_purple_700)
) {
openTasklist(TaskType.HABIT)
},
MenuItem(
TaskType.REWARD.value,
getString(R.string.rewards),
AppCompatResources.getDrawable(this, R.drawable.icon_rewards),
ContextCompat.getColor(this, R.color.watch_purple_200),
ContextCompat.getColor(this, R.color.watch_purple_700)
ContextCompat.getColor(this, R.color.watch_purple_700),
isHidden = true
) {
openTasklist(TaskType.REWARD)
},
MenuItem(
"Stats",
"stats",
getString(R.string.stats),
AppCompatResources.getDrawable(this, R.drawable.icon_stats),
ContextCompat.getColor(this, R.color.watch_purple_200),
@ -110,6 +111,9 @@ class MainActivity : BaseActivity<ActivityMainBinding, MainViewModel>() {
viewModel.user.observe(this) {
adapter.title = it.profile?.name ?: ""
adapter.notifyItemChanged(0)
val index = adapter.data.indexOfFirst { it.identifier == "stats" }
adapter.data[index].detailText = getString(R.string.user_level, it.stats?.lvl ?: 0)
adapter.notifyItemChanged(index)
}
viewModel.taskCounts.observe(this) {
adapter.data.forEach { menuItem ->

View file

@ -6,6 +6,7 @@ import androidx.activity.viewModels
import androidx.wear.widget.WearableLinearLayoutManager
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.databinding.ActivityTasklistBinding
import com.habitrpg.common.habitica.helpers.EmptyItem
import com.habitrpg.common.habitica.models.responses.TaskDirection
import com.habitrpg.common.habitica.models.responses.TaskScoringResult
import com.habitrpg.common.habitica.models.tasks.TaskType
@ -32,6 +33,18 @@ class TaskListActivity: BaseActivity<ActivityTasklistBinding, TaskListViewModel>
layoutManager =
WearableLinearLayoutManager(this@TaskListActivity, HabiticaScrollingLayoutCallback())
adapter = this@TaskListActivity.adapter
emptyItem = EmptyItem(
getString(R.string.no_tasks, getString(when(viewModel.taskType) {
TaskType.HABIT -> R.string.habit
TaskType.DAILY -> R.string.daily
TaskType.TODO -> R.string.todo
TaskType.REWARD -> R.string.reward
else -> R.string.task
}))
)
onRefresh = {
viewModel.retrieveTasks()
}
}
viewModel.tasks.observe(this) {
@ -46,10 +59,6 @@ class TaskListActivity: BaseActivity<ActivityTasklistBinding, TaskListViewModel>
openTaskDetailActivity(it)
}
viewModel.user.observe(this) {
}
binding.addTaskButton.setOnClickListener { openTaskFormActivity() }
}

View file

@ -19,9 +19,11 @@ class HubViewHolder(itemView: View): BindableViewHolder<MenuItem>(itemView) {
if (data.isProminent) {
binding.iconView.setColorFilter(ContextCompat.getColor(itemView.context, R.color.black))
binding.rowContainer.backgroundTintList = ColorStateList.valueOf(data.color)
binding.detailView.setTextColor(data.textColor)
} else {
binding.iconView.setColorFilter(data.color)
binding.rowContainer.backgroundTintList = ContextCompat.getColorStateList(itemView.context, R.color.surface)
binding.detailView.setTextColor(ContextCompat.getColor(itemView.context, R.color.watch_purple_200))
}
binding.root.setOnClickListener {
data.onClick()

View file

@ -16,7 +16,7 @@ class MainViewModel @Inject constructor(
private val taskRepository: TaskRepository,
exceptionBuilder: ExceptionHandlerBuilder, loadingManager: LoadingManager
) : BaseViewModel(userRepository, exceptionBuilder, loadingManager) {
val taskCounts = taskRepository.getTaskCounts().asLiveData()
val taskCounts = taskRepository.getActiveTaskCounts().asLiveData()
val user = userRepository.getUser().asLiveData()
init {

View file

@ -34,4 +34,9 @@ class TaskListViewModel @Inject constructor(
}
}
fun retrieveTasks() {
viewModelScope.launch(exceptionBuilder.userFacing(this)) {
taskRepository.retrieveTasks()
}
}
}

View file

@ -20,8 +20,7 @@ class AddTaskButton @JvmOverloads constructor(
) : FrameLayout(context, attrs) {
val binding = ButtonAddTaskBinding.inflate(context.layoutInflater, this)
private val paint = Paint()
private val gradient = LinearGradient(
private var gradient = LinearGradient(
0f,
0f,
0f,
@ -30,20 +29,34 @@ class AddTaskButton @JvmOverloads constructor(
ContextCompat.getColor(context, R.color.watch_blue_100),
Shader.TileMode.CLAMP
)
private val fillPaint = Paint().apply {
style = Paint.Style.FILL_AND_STROKE
shader = gradient
isAntiAlias = true
}
private val strokePaint = Paint().apply {
style = Paint.Style.STROKE
color = ContextCompat.getColor(context, R.color.watch_purple_200)
strokeWidth = 3f.dpToPx(context)
isAntiAlias = true
}
private val path = Path()
private val rect = RectF(0f, 0f, 0f, 0f)
private val rect = RectF(0f, 1.5f.dpToPx(context), 0f, 0f)
init {
paint.style = Paint.Style.FILL_AND_STROKE
paint.shader = gradient
paint.isAntiAlias = true
setWillNotDraw(false)
clipToOutline = false
clipChildren = false
}
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
rect.right = right.toFloat()
rect.bottom = bottom.toFloat() / 2f
val totalWidth = right - left
val width = totalWidth / 1.2375f
rect.left = (totalWidth - width)/2
rect.right = rect.left + width
rect.bottom = width / 1.8f
invalidate()
}
@ -53,6 +66,7 @@ class AddTaskButton @JvmOverloads constructor(
if (canvas == null) return
path.reset()
path.addArc(rect, 180f, 360f)
canvas.drawPath(path, paint)
canvas.drawPath(path, fillPaint)
canvas.drawPath(path, strokePaint)
}
}

View file

@ -3,6 +3,9 @@ package com.habitrpg.wearos.habitica.ui.views
import android.content.Context
import android.util.AttributeSet
import androidx.wear.widget.WearableRecyclerView
import com.habitrpg.common.habitica.helpers.EmptyItem
import com.habitrpg.common.habitica.helpers.RecyclerViewState
import com.habitrpg.common.habitica.helpers.RecyclerViewStateAdapter
class HabiticaRecyclerView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
@ -10,8 +13,75 @@ class HabiticaRecyclerView @JvmOverloads constructor(
override fun onAttachedToWindow() {
super.onAttachedToWindow()
post {
setPaddingRelative(0, (height * 0.06).toInt(), 0, (height * 0.25).toInt())
setPaddingRelative(0, (height * 0.1).toInt(), 0, (height * 0.25).toInt())
scrollToPosition(0)
}
}
var onRefresh: (() -> Unit)?
get() = emptyAdapter.onRefresh
set(value) { emptyAdapter.onRefresh = value }
var state: RecyclerViewState = RecyclerViewState.LOADING
set(value) {
field = value
when (field) {
RecyclerViewState.DISPLAYING_DATA -> updateAdapter(actualAdapter)
else -> {
updateAdapter(emptyAdapter)
emptyAdapter.state = value
}
}
}
private fun updateAdapter(newAdapter: Adapter<*>?) {
if (adapter != newAdapter) {
super.setAdapter(newAdapter)
}
}
var emptyItem: EmptyItem?
get() = emptyAdapter.emptyItem
set(value) {
emptyAdapter.emptyItem = value
}
private var actualAdapter: Adapter<*>? = null
private val emptyAdapter = RecyclerViewStateAdapter(true)
private val observer = object : AdapterDataObserver() {
override fun onChanged() {
updateState()
}
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
updateState()
}
override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {
updateState()
}
}
internal fun updateState(isInitial: Boolean = false) {
state = if (actualAdapter != null && !isInitial) {
val emptyViewVisible = actualAdapter?.itemCount == 0
if (emptyViewVisible) {
RecyclerViewState.EMPTY
} else {
RecyclerViewState.DISPLAYING_DATA
}
} else {
RecyclerViewState.LOADING
}
}
override fun setAdapter(adapter: Adapter<*>?) {
val oldAdapter = actualAdapter
oldAdapter?.unregisterAdapterDataObserver(observer)
super.setAdapter(adapter)
adapter?.registerAdapterDataObserver(observer)
actualAdapter = adapter
updateState(true)
}
}

View file

@ -16,6 +16,7 @@
android:textColor="@color/watch_purple_200"
android:text="@string/day_start"
android:fontFamily="sans-serif-medium"
android:gravity="center"
android:layout_marginBottom="10dp"/>
<TextView
android:id="@+id/description_view"
@ -32,6 +33,7 @@
android:layout_height="wrap_content"
android:text="@string/review_dailies"
style="@style/ChipButton.Purple"
android:gravity="center"
android:layout_marginBottom="@dimen/spacing_small"/>
<Button
android:id="@+id/phone_button"
@ -39,7 +41,8 @@
android:layout_height="wrap_content"
android:text="@string/check_on_phone"
android:drawableStart="@drawable/handoff"
style="@style/ChipButton"/>
style="@style/ChipButton"
android:gravity="center"/>
<LinearLayout
android:id="@+id/task_view"
@ -54,6 +57,7 @@
style="@style/ChipButton.Purple"
android:text="@string/start_new_day"
android:layout_marginTop="2dp"
android:gravity="center"
android:visibility="gone"/>
<TextView
android:layout_width="match_parent"

View file

@ -12,7 +12,7 @@
<com.habitrpg.wearos.habitica.ui.views.AddTaskButton
android:id="@+id/add_task_button"
android:layout_width="match_parent"
android:layout_height="38dp"
android:layout_height="44dp"
android:layout_gravity="center|bottom"
app:layout_behavior="com.habitrpg.wearos.habitica.util.ScrollAwayBehavior"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -29,6 +29,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/Text.Body2"
android:textColor="@color/watch_purple_200" />
android:textColor="@color/watch_purple_200"
android:layout_marginEnd="@dimen/spacing_medium"/>
</LinearLayout>
</FrameLayout>

View file

@ -29,4 +29,5 @@
<string name="save">Save</string>
<string name="sync_data">Sync Data</string>
<string name="hide_task_rewards">Hide task rewards</string>
<string name="no_tasks">Create a %s to add to this list</string>
</resources>