various improvements

This commit is contained in:
Phillip Thelen 2022-06-24 15:15:15 +02:00
parent 6b2129c453
commit bd7caff14f
30 changed files with 239 additions and 44 deletions

View file

@ -815,7 +815,6 @@
<string name="guild_members">Guild Members</string>
<string name="guild_bank">Guild Bank</string>
<string name="invite_to_party">Invite to Party</string>
<string name="are_you_sure">Are you sure?</string>
<string name="delete_task">Delete Task</string>
<string name="delete_reminder">Delete Reminder</string>
<string name="not_now">Not right now</string>

View file

Before

Width:  |  Height:  |  Size: 518 B

After

Width:  |  Height:  |  Size: 518 B

View file

Before

Width:  |  Height:  |  Size: 366 B

After

Width:  |  Height:  |  Size: 366 B

View file

Before

Width:  |  Height:  |  Size: 674 B

After

Width:  |  Height:  |  Size: 674 B

View file

Before

Width:  |  Height:  |  Size: 970 B

After

Width:  |  Height:  |  Size: 970 B

View file

@ -54,4 +54,6 @@
<string name="create_task_title">Create a Task</string>
<string name="complete_task_title">Complete a Task</string>
<string name="create_task">Create %s</string>
<string name="are_you_sure">Are you sure?</string>
</resources>

View file

@ -62,4 +62,11 @@ class TaskLocalRepository @Inject constructor() {
}
return emptyFlow()
}
fun getTaskCounts() = flowOf(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),
))
}

View file

@ -18,8 +18,7 @@ class TaskRepository @Inject constructor(val apiClient: ApiClient, val localRepo
tasks?.let { localRepository.saveTasks(tasks) }
return tasks
}
fun getTasks(taskType: TaskType): Flow<List<Task>> = localRepository.getTasks(taskType)
fun getTasks(taskType: TaskType) = localRepository.getTasks(taskType)
suspend fun scoreTask(user: User?, task: Task, direction: TaskDirection): TaskScoringResult? {
val id = task.id ?: return null
@ -42,4 +41,6 @@ class TaskRepository @Inject constructor(val apiClient: ApiClient, val localRepo
localRepository.updateTask(newTask)
}
}
fun getTaskCounts() = localRepository.getTaskCounts()
}

View file

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

View file

@ -21,7 +21,7 @@ class LoginActivity: BaseActivity<ActivityLoginBinding, LoginViewModel>() {
INPUT
}
override val viewModel: LoginViewModel by viewModels()
var currentState: State = State.INITIAL
private var currentState: State = State.INITIAL
set(value) {
field = value
when(value) {
@ -59,6 +59,7 @@ class LoginActivity: BaseActivity<ActivityLoginBinding, LoginViewModel>() {
binding.loginButton.isVisible = true
}
}
binding.root.smoothScrollTo(0, 0)
}
override fun onCreate(savedInstanceState: Bundle?) {

View file

@ -44,7 +44,7 @@ class MainActivity : BaseActivity<ActivityMainBinding, MainViewModel>() {
openTaskFormActivity()
},
MenuItem(
"habits",
TaskType.HABIT.value,
getString(R.string.habits),
AppCompatResources.getDrawable(this, R.drawable.icon_habits),
ContextCompat.getColor(this, R.color.watch_purple_200),
@ -53,7 +53,7 @@ class MainActivity : BaseActivity<ActivityMainBinding, MainViewModel>() {
openTasklist(TaskType.HABIT)
},
MenuItem(
"dailies",
TaskType.DAILY.value,
getString(R.string.dailies),
AppCompatResources.getDrawable(this, R.drawable.icon_dailies),
ContextCompat.getColor(this, R.color.watch_purple_200),
@ -62,7 +62,7 @@ class MainActivity : BaseActivity<ActivityMainBinding, MainViewModel>() {
openTasklist(TaskType.DAILY)
},
MenuItem(
"todos",
TaskType.TODO.value,
getString(R.string.todos),
AppCompatResources.getDrawable(this, R.drawable.icon_todos),
ContextCompat.getColor(this, R.color.watch_purple_200),
@ -71,7 +71,7 @@ class MainActivity : BaseActivity<ActivityMainBinding, MainViewModel>() {
openTasklist(TaskType.TODO)
},
MenuItem(
"rewards",
TaskType.REWARD.value,
getString(R.string.rewards),
AppCompatResources.getDrawable(this, R.drawable.icon_rewards),
ContextCompat.getColor(this, R.color.watch_purple_200),
@ -111,6 +111,16 @@ class MainActivity : BaseActivity<ActivityMainBinding, MainViewModel>() {
adapter.title = it.profile?.name ?: ""
adapter.notifyItemChanged(0)
}
viewModel.taskCounts.observe(this) {
adapter.data.forEach { menuItem ->
if (it.containsKey(menuItem.identifier) && it[menuItem.identifier]!! > 0) {
menuItem.detailText = it[menuItem.identifier].toString()
} else {
menuItem.detailText = null
}
}
adapter.notifyDataSetChanged()
}
}
private fun openTaskFormActivity() {

View file

@ -7,7 +7,6 @@ import androidx.core.view.isVisible
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.databinding.ActivityRyaBinding
import com.habitrpg.android.habitica.databinding.RowDailyBinding
import com.habitrpg.common.habitica.extensions.dpToPx
import com.habitrpg.wearos.habitica.models.tasks.Task
import com.habitrpg.wearos.habitica.ui.viewHolders.tasks.DailyViewHolder
import com.habitrpg.wearos.habitica.ui.viewmodels.RYAViewModel
@ -60,7 +59,6 @@ class RYAActivity : BaseActivity<ActivityRyaBinding, RYAViewModel>() {
taskBinding.root.setOnClickListener {
viewModel.tappedTask(task)
}
val verticalPadding = 2.dpToPx(this)
val layoutParams = taskBinding.root.layoutParams as LinearLayout.LayoutParams
layoutParams.marginStart = 0
layoutParams.marginEnd = 0

View file

@ -1,5 +1,6 @@
package com.habitrpg.wearos.habitica.ui.activities
import android.app.AlertDialog
import android.content.Intent
import android.os.Bundle
import androidx.activity.viewModels
@ -39,14 +40,59 @@ class SettingsActivity: BaseActivity<ActivitySettingsBinding, SettingsViewModel>
private fun buildSettings(): List<SettingsItem> {
return listOf(
SettingsItem(
"header",
getString(R.string.settings),
SettingsItem.Types.HEADER,
null
) {
},
SettingsItem(
"sync",
getString(R.string.sync_data),
SettingsItem.Types.BUTTON,
null
) {
viewModel.resyncData()
},
SettingsItem(
"hide_results",
getString(R.string.hide_task_rewards),
SettingsItem.Types.TOGGLE,
viewModel.isTaskResultHidden()
) {
viewModel.setHideTaskResults(!viewModel.isTaskResultHidden())
val index = adapter.data.indexOfFirst { it.identifier == "hide_results" }
adapter.data[index].value = viewModel.isTaskResultHidden()
adapter.notifyItemChanged(index)
},
SettingsItem(
"spacer",
getString(R.string.settings),
SettingsItem.Types.SPACER,
null
) {
},
SettingsItem(
"logout",
getString(R.string.logout),
SettingsItem.Types.DESTRUCTIVE_BUTTON,
null
) {
logout()
showLogoutConfirmation()
}
)
}
private fun showLogoutConfirmation() {
AlertDialog.Builder(this)
.setTitle(R.string.are_you_sure)
.setPositiveButton(R.string.logout) { alert, _ ->
logout()
alert.dismiss()
}
.setPositiveButton(R.string.action_cancel) { alert, _ ->
alert.dismiss()
}.show()
}
}

View file

@ -8,6 +8,7 @@ import android.view.animation.AlphaAnimation
import android.widget.GridLayout
import androidx.activity.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceManager
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.databinding.ActivityTaskResultBinding
import com.habitrpg.android.habitica.databinding.TaskRewardDropBinding
@ -167,6 +168,8 @@ class TaskResultActivity : BaseActivity<ActivityTaskResultBinding, TaskResultVie
companion object {
fun show(context: Activity, result: TaskScoringResult) {
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
if (sharedPreferences.getBoolean("hide_task_results", false)) return
val intent = Intent(context, TaskResultActivity::class.java)
intent.putExtra("result", result)
context.startActivity(intent)

View file

@ -1,35 +1,69 @@
package com.habitrpg.wearos.habitica.ui.adapters
import android.content.res.ColorStateList
import android.view.View
import android.view.ViewGroup
import android.widget.RadioButton
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
import com.habitrpg.common.habitica.extensions.layoutInflater
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.databinding.RowHeaderBinding
import com.habitrpg.android.habitica.databinding.RowSettingsBinding
import com.habitrpg.android.habitica.databinding.RowSpacerBinding
import com.habitrpg.common.habitica.extensions.dpToPx
import com.habitrpg.common.habitica.extensions.layoutInflater
import com.habitrpg.wearos.habitica.ui.viewHolders.BindableViewHolder
import com.habitrpg.wearos.habitica.ui.viewHolders.HeaderViewHolder
import com.habitrpg.wearos.habitica.ui.viewHolders.SpacerViewHolder
class SettingsAdapter: RecyclerView.Adapter<SettingsViewHolder>() {
class SettingsAdapter: RecyclerView.Adapter<RecyclerView.ViewHolder>() {
var data = listOf<SettingsItem>()
set(value) {
field = value
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SettingsViewHolder {
return SettingsViewHolder(RowSettingsBinding.inflate(parent.context.layoutInflater, parent, false).root)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
if (viewType == 0) {
return HeaderViewHolder(RowHeaderBinding.inflate(parent.context.layoutInflater, parent, false).root)
} else if (viewType == 1) {
return SpacerViewHolder(RowSpacerBinding.inflate(parent.context.layoutInflater, parent, false).root)
} else {
return SettingsViewHolder(RowSettingsBinding.inflate(parent.context.layoutInflater, parent, false).root)
}
}
override fun onBindViewHolder(holder: SettingsViewHolder, position: Int) {
holder.bind(data[position])
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (holder is SettingsViewHolder) {
holder.bind(data[position])
} else if (holder is HeaderViewHolder) {
holder.bind(data[position].title)
} else if (holder is SpacerViewHolder) {
holder.bind(16.dpToPx(holder.itemView.context))
}
}
override fun getItemCount(): Int = data.size
override fun getItemViewType(position: Int): Int {
val item = data[position]
return when (item.type) {
SettingsItem.Types.HEADER -> 0
SettingsItem.Types.SPACER -> 1
else -> 2
}
}
}
class SettingsViewHolder(itemView: View) : BindableViewHolder<SettingsItem>(itemView) {
private val binding = RowSettingsBinding.bind(itemView)
private var widget: View? = null
override fun bind(data: SettingsItem) {
if (widget != null) {
(widget?.parent as? ViewGroup)?.removeView(widget)
widget = null
}
binding.titleView.text = data.title
if (data.type == SettingsItem.Types.DESTRUCTIVE_BUTTON) {
binding.titleView.setTextColor(ContextCompat.getColor(itemView.context, R.color.text_red))
@ -37,6 +71,22 @@ class SettingsViewHolder(itemView: View) : BindableViewHolder<SettingsItem>(item
binding.titleView.setTextColor(ContextCompat.getColor(itemView.context, R.color.white))
}
if (data.type == SettingsItem.Types.TOGGLE) {
val radio = RadioButton(itemView.context)
radio.isChecked = data.value as? Boolean == true
radio.isEnabled = false
widget = radio
binding.row.addView(radio)
if (data.value as? Boolean == true) {
binding.row.backgroundTintList = ColorStateList.valueOf(ContextCompat.getColor(itemView.context, R.color.watch_purple_100))
binding.row.background.alpha = 127
} else {
binding.row.backgroundTintList = ColorStateList.valueOf(ContextCompat.getColor(itemView.context, R.color.watch_purple_5))
binding.row.background.alpha = 255
}
}
binding.root.setOnClickListener {
data.onTap()
}
@ -47,12 +97,14 @@ data class SettingsItem(
val identifier: String,
val title: String,
val type: Types,
val value: Any?,
var value: Any?,
val onTap: () -> Unit
) {
enum class Types {
BUTTON,
DESTRUCTIVE_BUTTON,
TOGGLE
SPACER,
TOGGLE,
HEADER
}
}

View file

@ -12,7 +12,9 @@ class HubViewHolder(itemView: View): BindableViewHolder<MenuItem>(itemView) {
override fun bind(data: MenuItem) {
binding.title.text = data.title
binding.detailView.text = data.detailText
binding.title.setTextColor(data.textColor)
binding.detailView.setTextColor(data.textColor)
binding.iconView.setImageDrawable(data.icon)
if (data.isProminent) {
binding.iconView.setColorFilter(ContextCompat.getColor(itemView.context, R.color.black))

View file

@ -0,0 +1,12 @@
package com.habitrpg.wearos.habitica.ui.viewHolders
import android.view.View
import com.habitrpg.android.habitica.databinding.RowSpacerBinding
class SpacerViewHolder(itemView: View): BindableViewHolder<Int>(itemView) {
private val binding = RowSpacerBinding.bind(itemView)
override fun bind(data: Int) {
binding.root.layoutParams.height = data
}
}

View file

@ -27,10 +27,10 @@ class HabitViewHolder(itemView: View) : TaskViewHolder(itemView) {
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 {
if (data.down == true) {
binding.habitButtonIcon.setImageResource(R.drawable.watch_habit_negative)
} else {
binding.habitButtonIcon.setImageResource(R.drawable.watch_habit_positive)
}
}
if (data.up != true && data.down != true) {

View file

@ -26,6 +26,7 @@ import com.habitrpg.wearos.habitica.data.repositories.UserRepository
import com.habitrpg.wearos.habitica.managers.LoadingManager
import com.habitrpg.wearos.habitica.util.ExceptionHandlerBuilder
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.io.IOException
import javax.inject.Inject
@ -70,17 +71,19 @@ class LoginViewModel @Inject constructor(userRepository: UserRepository,
val scopesString = Scopes.PROFILE + " " + Scopes.EMAIL
val scopes = "oauth2:$scopesString"
viewModelScope.launch(exceptionBuilder.userFacing(this)) {
val token = try {
GoogleAuthUtil.getToken(activity, googleEmail ?: "", scopes)
} catch (e: IOException) {
return@launch
} catch (e: GoogleAuthException) {
if (recoverFromPlayServicesErrorResult != null) {
handleGoogleAuthException(e, activity, recoverFromPlayServicesErrorResult)
val token = launch(Dispatchers.IO) {
try {
GoogleAuthUtil.getToken(activity, googleEmail ?: "", scopes)
} catch (e: IOException) {
return@launch
} catch (e: GoogleAuthException) {
if (recoverFromPlayServicesErrorResult != null) {
handleGoogleAuthException(e, activity, recoverFromPlayServicesErrorResult)
}
return@launch
} catch (e: UserRecoverableException) {
return@launch
}
return@launch
} catch (e: UserRecoverableException) {
return@launch
}
val response = apiClient.loginSocial(UserAuthSocial())
handleAuthResponse(response)

View file

@ -13,10 +13,11 @@ import javax.inject.Inject
@HiltViewModel
class MainViewModel @Inject constructor(
userRepository: UserRepository,
val taskRepository: TaskRepository,
private val taskRepository: TaskRepository,
exceptionBuilder: ExceptionHandlerBuilder, loadingManager: LoadingManager
) : BaseViewModel(userRepository, exceptionBuilder, loadingManager) {
var user = userRepository.getUser().asLiveData()
val taskCounts = taskRepository.getTaskCounts().asLiveData()
val user = userRepository.getUser().asLiveData()
init {
viewModelScope.launch(exceptionBuilder.userFacing(this)) {

View file

@ -2,18 +2,23 @@ package com.habitrpg.wearos.habitica.ui.viewmodels
import android.content.SharedPreferences
import androidx.core.content.edit
import androidx.lifecycle.viewModelScope
import com.habitrpg.wearos.habitica.data.ApiClient
import com.habitrpg.wearos.habitica.data.repositories.TaskRepository
import com.habitrpg.wearos.habitica.data.repositories.UserRepository
import com.habitrpg.wearos.habitica.managers.LoadingManager
import com.habitrpg.wearos.habitica.util.ExceptionHandlerBuilder
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
class SettingsViewModel @Inject constructor(userRepository: UserRepository,
private val taskRepository: TaskRepository,
exceptionBuilder: ExceptionHandlerBuilder,
private val apiClient: ApiClient,
private val sharedPreferences: SharedPreferences, loadingManager: LoadingManager
private val sharedPreferences: SharedPreferences,
loadingManager: LoadingManager
) : BaseViewModel(userRepository, exceptionBuilder, loadingManager) {
fun logout() {
@ -22,4 +27,23 @@ class SettingsViewModel @Inject constructor(userRepository: UserRepository,
}
apiClient.updateAuthenticationCredentials(null, null)
}
fun resyncData() {
viewModelScope.launch(exceptionBuilder.userFacing(this)) {
loadingManager.startLoading()
userRepository.retrieveUser()
taskRepository.retrieveTasks()
loadingManager.endLoading()
}
}
fun setHideTaskResults(hide: Boolean) {
sharedPreferences.edit {
putBoolean("hide_task_results", hide)
}
}
fun isTaskResultHidden(): Boolean {
return sharedPreferences.getBoolean("hide_task_results", false)
}
}

View file

@ -11,6 +11,11 @@
android:orientation="vertical"
android:gravity="center"
android:animateLayoutChanges="true">
<ImageView
android:layout_width="16dp"
android:layout_height="16dp"
android:src="@drawable/ic_gryphon_white"
android:layout_marginBottom="@dimen/spacing_medium"/>
<TextView
android:id="@+id/title_view"
android:layout_width="wrap_content"

View file

@ -25,6 +25,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:lines="1"
tools:text="Task Title"/>
tools:text="Task Title"
style="@style/Text.Body2"/>
</LinearLayout>
</FrameLayout>

View file

@ -1,12 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/row_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
style="@style/Chip">
<ImageView
android:id="@+id/icon_view"
@ -18,10 +18,17 @@
android:layout_marginStart="0dp"/>
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:fontFamily="sans-serif-medium"
android:textColor="@color/white"
android:textSize="@dimen/row_text_size" />
<TextView
android:id="@+id/detail_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/Text.Body2"
android:textColor="@color/watch_purple_200" />
</LinearLayout>
</FrameLayout>

View file

@ -12,6 +12,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:lines="1"
tools:text="Task Title"/>
tools:text="Task Title"
style="@style/Text.Body2"/>
</LinearLayout>
</FrameLayout>

View file

@ -1,9 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:layout_height="wrap_content"
android:layout_gravity="center">
<LinearLayout
android:id="@+id/row"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/Chip">
@ -11,6 +15,12 @@
android:id="@+id/title_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/TextAppearance.AppCompat.Title" />
tools:text="Settings text"
android:textColor="@color/white"
style="@style/Text.Body1"/>
<Space
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
</LinearLayout>
</FrameLayout>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Space xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</Space>

View file

@ -27,6 +27,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:lines="1"
tools:text="Task Title"/>
tools:text="Task Title"
style="@style/Text.Body2"/>
</LinearLayout>
</FrameLayout>

View file

@ -27,4 +27,6 @@
<string name="task_type">Task Type</string>
<string name="task_title_hint">Task title...</string>
<string name="save">Save</string>
<string name="sync_data">Sync Data</string>
<string name="hide_task_rewards">Hide task rewards</string>
</resources>

View file

@ -9,7 +9,7 @@
<item name="android:paddingHorizontal">@dimen/row_padding_horizontal</item>
<item name="android:paddingVertical">@dimen/row_padding_vertical</item>
<item name="android:background">@drawable/row_background</item>
<item name="android:gravity">center</item>
<item name="android:gravity">start|center_vertical</item>
<item name="android:minHeight">52dp</item>
</style>
@ -23,7 +23,7 @@
<item name="android:background">@drawable/row_background</item>
<item name="android:textSize">16sp</item>
<item name="fontFamily">sans-serif-medium</item>
<item name="android:gravity">center</item>
<item name="android:gravity">start|center_vertical</item>
<item name="android:minHeight">52dp</item>
<item name="android:textAllCaps">false</item>
<item name="android:letterSpacing">0.01</item>