Various minor improvements
|
Before Width: | Height: | Size: 746 B |
|
Before Width: | Height: | Size: 499 B |
|
Before Width: | Height: | Size: 954 B |
|
Before Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 2.6 KiB |
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -270,7 +270,6 @@
|
|||
• Bosses won’t do damage for your missed Dailies\n
|
||||
• 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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
16
common/src/main/res/drawable-anydpi/failed_loading.xml
Normal 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>
|
||||
BIN
common/src/main/res/drawable-hdpi/failed_loading.png
Normal file
|
After Width: | Height: | Size: 577 B |
BIN
common/src/main/res/drawable-mdpi/failed_loading.png
Normal file
|
After Width: | Height: | Size: 424 B |
BIN
common/src/main/res/drawable-xhdpi/failed_loading.png
Normal file
|
After Width: | Height: | Size: 904 B |
BIN
common/src/main/res/drawable-xxhdpi/failed_loading.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
|
|
@ -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"/>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -43,4 +43,5 @@ class TaskRepository @Inject constructor(val apiClient: ApiClient, val localRepo
|
|||
}
|
||||
|
||||
fun getTaskCounts() = localRepository.getTaskCounts()
|
||||
fun getActiveTaskCounts() = localRepository.getActiveTaskCounts()
|
||||
}
|
||||
|
|
@ -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
|
||||
)
|
||||
|
|
@ -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 ->
|
||||
|
|
|
|||
|
|
@ -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() }
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -34,4 +34,9 @@ class TaskListViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
fun retrieveTasks() {
|
||||
viewModelScope.launch(exceptionBuilder.userFacing(this)) {
|
||||
taskRepository.retrieveTasks()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||