implement new header and group task display

This commit is contained in:
Phillip Thelen 2022-09-26 18:52:24 +02:00
parent 03d5648003
commit fc96c6146e
39 changed files with 293 additions and 137 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 B

View file

@ -0,0 +1,5 @@
<vector android:autoMirrored="true" android:height="24dp"
android:tint="#FFFFFF" android:viewportHeight="24"
android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M20,2L4,2c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM6,9h12v2L6,11L6,9zM14,14L6,14v-2h8v2zM18,8L6,8L6,6h12v2z"/>
</vector>

View file

@ -12,6 +12,14 @@
android:paddingBottom="@dimen/task_top_bottom_padding"
android:layout_marginEnd="@dimen/task_text_padding"
android:layout_marginStart="@dimen/task_text_padding">
<TextView
android:id="@+id/assigned_textview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/Caption4"
android:text="@string/pending_approval"
android:textColor="@color/text_ternary"
/>
<com.habitrpg.android.habitica.ui.views.EllipsisTextView
android:id="@+id/checkedTextView"
style="@style/Subheader3"

View file

@ -2,10 +2,6 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context="com.habitrpg.android.habitica.TaskActivity">
<item android:id="@+id/action_team_info"
android:title="@string/team_information"
android:icon="@drawable/team_info_icon"
app:showAsAction="collapseActionView|always" />
<item android:id="@+id/action_search"
android:title="@string/search"
android:icon="@drawable/ic_search"

View file

@ -1255,4 +1255,13 @@
<string name="copy_tasks_description">Show assigned and open tasks on your personal task lists</string>
<string name="copy_shared_tasks">Copy shared tasks</string>
<string name="group_plan_settings">Group Plan Settings</string>
<plurals name="you_x_others">
<item quantity="zero">You</item>
<item quantity="one">You, %d other</item>
<item quantity="other">You, %d others</item>
</plurals>
<plurals name="people">
<item quantity="one">%d Person</item>
<item quantity="other">%d People</item>
</plurals>
</resources>

View file

@ -78,8 +78,9 @@ interface SocialRepository : BaseRepository {
suspend fun postPrivateMessage(recipientId: String, message: String): List<ChatMessage>?
suspend fun getPartyMembers(id: String): Flow<List<Member>>
suspend fun getGroupMembers(id: String): Flow<List<Member>>
suspend fun retrieveGroupMembers(id: String, includeAllPublicFields: Boolean): List<Member>?
suspend fun retrievePartyMembers(id: String, includeAllPublicFields: Boolean): List<Member>?
fun inviteToGroup(id: String, inviteData: Map<String, Any>): Flowable<List<Void>>

View file

@ -35,7 +35,7 @@ class SocialRepositoryImpl(
override suspend fun removeMemberFromGroup(groupID: String, userID: String): List<Member>? {
apiClient.removeMemberFromGroup(groupID, userID)
return retrieveGroupMembers(groupID, true)
return retrievePartyMembers(groupID, true)
}
override fun blockMember(userID: String): Flowable<List<String>> {
@ -237,11 +237,12 @@ class SocialRepositoryImpl(
return postPrivateMessage(recipientId, messageObject)
}
override suspend fun getPartyMembers(id: String) = localRepository.getPartyMembers(id)
override suspend fun getGroupMembers(id: String) = localRepository.getGroupMembers(id)
override suspend fun retrieveGroupMembers(id: String, includeAllPublicFields: Boolean): List<Member>? {
override suspend fun retrievePartyMembers(id: String, includeAllPublicFields: Boolean): List<Member>? {
val members = apiClient.getGroupMembers(id, includeAllPublicFields)
members?.let { localRepository.saveGroupMembers(id, it) }
members?.let { localRepository.savePartyMembers(id, it) }
return members
}

View file

@ -4,7 +4,6 @@ import com.habitrpg.android.habitica.data.ApiClient
import com.habitrpg.android.habitica.data.TaskRepository
import com.habitrpg.android.habitica.data.UserRepository
import com.habitrpg.android.habitica.data.local.UserLocalRepository
import com.habitrpg.android.habitica.models.user.UserQuestStatus
import com.habitrpg.android.habitica.extensions.filterMapEmpty
import com.habitrpg.android.habitica.helpers.AppConfigManager
import com.habitrpg.android.habitica.helpers.ExceptionHandler
@ -16,9 +15,11 @@ import com.habitrpg.android.habitica.models.inventory.Customization
import com.habitrpg.android.habitica.models.responses.SkillResponse
import com.habitrpg.android.habitica.models.responses.UnlockResponse
import com.habitrpg.android.habitica.models.social.Group
import com.habitrpg.android.habitica.models.social.GroupMembership
import com.habitrpg.android.habitica.models.tasks.Task
import com.habitrpg.android.habitica.models.user.Stats
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.models.user.UserQuestStatus
import com.habitrpg.android.habitica.proxy.AnalyticsManager
import com.habitrpg.common.habitica.extensions.Optional
import com.habitrpg.shared.habitica.models.responses.TaskDirection
@ -382,14 +383,16 @@ class UserRepositoryImpl(
override suspend fun retrieveTeamPlan(teamID: String): Group? {
val team = apiClient.getGroup(teamID) ?: return null
team.tasks = apiClient.getTeamPlanTasks(teamID)
val tasks = apiClient.getTeamPlanTasks(teamID)
localRepository.save(team)
val id = team.id
val tasksOrder = team.tasksOrder
val tasks = team.tasks
if (id.isNotBlank() && tasksOrder != null && tasks != null) {
taskRepository.saveTasks(id, tasksOrder, tasks)
}
val members = apiClient.getGroupMembers(teamID, true) ?: return team
localRepository.save(members.map { it.id?.let { member -> GroupMembership(member, id) } }.filterNotNull())
members.let { localRepository.save(members) }
return team
}

View file

@ -23,13 +23,14 @@ interface SocialLocalRepository : BaseLocalRepository {
fun deleteMessage(id: String)
fun getGroupMembers(partyId: String): Flow<List<Member>>
fun getPartyMembers(partyId: String): Flow<List<Member>>
fun getGroupMembers(groupID: String): Flow<List<Member>>
fun updateRSVPNeeded(user: User?, newValue: Boolean)
fun likeMessage(chatMessage: ChatMessage, userId: String, liked: Boolean)
fun saveGroupMembers(groupId: String?, members: List<Member>)
fun savePartyMembers(groupId: String?, members: List<Member>)
fun removeQuest(partyId: String)

View file

@ -190,11 +190,18 @@ class RealmSocialLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm)
executeTransaction { chatMessage?.deleteFromRealm() }
}
override fun getGroupMembers(partyId: String) = realm.where(Member::class.java)
override fun getPartyMembers(partyId: String) = realm.where(Member::class.java)
.equalTo("party.id", partyId)
.findAll()
.toFlow()
override fun getGroupMembers(groupID: String) = realm.where(GroupMembership::class.java)
.equalTo("groupID", groupID)
.findAll()
.toFlow()
.map { memberships -> memberships.map { it.userID }.toTypedArray() }
.flatMapLatest { realm.where(Member::class.java).`in`("id", it).findAll().toFlow() }
override fun updateRSVPNeeded(user: User?, newValue: Boolean) {
executeTransaction { user?.party?.quest?.RSVPNeeded = newValue }
}
@ -221,7 +228,7 @@ class RealmSocialLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm)
}
}
override fun saveGroupMembers(groupId: String?, members: List<Member>) {
override fun savePartyMembers(groupId: String?, members: List<Member>) {
saveSyncronous(members)
if (groupId != null) {
val existingMembers = realm.where(Member::class.java).equalTo("party.id", groupId).findAll()

View file

@ -12,6 +12,7 @@ import hu.akarnokd.rxjava3.bridge.RxJavaBridge
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.core.Maybe
import io.realm.Realm
import io.realm.RealmResults
import io.realm.Sort
import io.realm.kotlin.toFlow
import kotlinx.coroutines.flow.Flow
@ -22,32 +23,34 @@ class RealmTaskLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm),
override fun getTasks(taskType: TaskType, userID: String, includedGroupIDs: Array<String>): Flow<List<Task>> {
if (realm.isClosed) return emptyFlow()
return realm.where(Task::class.java)
.equalTo("typeValue", taskType.value)
.beginGroup()
.equalTo("userId", userID)
.or()
.`in`("group.groupID", includedGroupIDs)
.endGroup()
.sort("position", Sort.ASCENDING, "dateCreated", Sort.DESCENDING)
.findAll()
return findTasks(taskType, userID, includedGroupIDs)
.toFlow()
.filter { it.isLoaded }
}
override fun getTasksFlowable(taskType: TaskType, userID: String, includedGroupIDs: Array<String>): Flowable<out List<Task>> {
if (realm.isClosed) return Flowable.empty()
return RxJavaBridge.toV3Flowable(realm.where(Task::class.java)
return RxJavaBridge.toV3Flowable(findTasks(taskType, userID, includedGroupIDs)
.asFlowable()
.filter { it.isLoaded })
}
private fun findTasks(
taskType: TaskType,
ownerID: String,
includedGroupIDs: Array<String>
): RealmResults<Task> {
return realm.where(Task::class.java)
.equalTo("typeValue", taskType.value)
.beginGroup()
.equalTo("userId", userID)
.equalTo("userId", ownerID)
.or()
.`in`("group.groupID", includedGroupIDs)
.or()
.equalTo("group.groupID", ownerID)
.endGroup()
.sort("position", Sort.ASCENDING, "dateCreated", Sort.DESCENDING)
.findAll()
.asFlowable()
.filter { it.isLoaded })
}
override fun getTasks(userId: String): Flow<List<Task>> {

View file

@ -0,0 +1,7 @@
package com.habitrpg.android.habitica.helpers
import android.content.res.Resources
interface AssignedTextProvider {
fun textForTask(resources: Resources, assignedUsers: List<String>): String
}

View file

@ -4,7 +4,6 @@ import com.google.gson.annotations.SerializedName
import com.habitrpg.android.habitica.models.BaseMainObject
import com.habitrpg.android.habitica.models.inventory.Quest
import com.habitrpg.android.habitica.models.tasks.TaskList
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.shared.habitica.models.tasks.TasksOrder
import io.realm.RealmList
import io.realm.RealmObject
@ -34,7 +33,6 @@ open class Group : RealmObject(), BaseMainObject {
var logo: String? = null
var quest: Quest? = null
var privacy: String? = null
var members: RealmList<User>? = null
var challengeCount: Int = 0
var leaderMessage: String? = null
var leaderOnlyChallenges: Boolean = false
@ -43,8 +41,6 @@ open class Group : RealmObject(), BaseMainObject {
@Ignore
var tasksOrder: TasksOrder? = null
@Ignore
var tasks: TaskList? = null
override fun equals(other: Any?): Boolean {
if (this === other) {

View file

@ -14,7 +14,6 @@ import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.ui.adapter.social.PartyMemberRecyclerViewAdapter
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flatMapConcat
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
@ -66,7 +65,7 @@ class SkillMemberActivity : BaseActivity() {
userRepository.getUser()
.map { it?.party?.id }
.filterNotNull()
.flatMapLatest { socialRepository.getGroupMembers(it) }
.flatMapLatest { socialRepository.getPartyMembers(it) }
.collect { viewAdapter?.data = it }
}
}

View file

@ -8,7 +8,6 @@ import android.widget.TextView
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.models.tasks.Task
import com.habitrpg.shared.habitica.models.tasks.TaskType
import com.habitrpg.android.habitica.ui.adapter.tasks.BaseTasksRecyclerViewAdapter
import com.habitrpg.android.habitica.ui.viewHolders.BindableViewHolder
import com.habitrpg.android.habitica.ui.viewHolders.tasks.BaseTaskViewHolder
@ -17,6 +16,7 @@ import com.habitrpg.android.habitica.ui.viewHolders.tasks.HabitViewHolder
import com.habitrpg.android.habitica.ui.viewHolders.tasks.RewardViewHolder
import com.habitrpg.android.habitica.ui.viewHolders.tasks.TodoViewHolder
import com.habitrpg.android.habitica.ui.viewmodels.TasksViewModel
import com.habitrpg.shared.habitica.models.tasks.TaskType
import io.reactivex.rxjava3.core.BackpressureStrategy
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.subjects.PublishSubject
@ -69,18 +69,18 @@ class ChallengeTasksRecyclerViewAdapter(
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindableViewHolder<Task> {
val viewHolder: BindableViewHolder<Task> = when (viewType) {
TYPE_HABIT -> HabitViewHolder(getContentView(parent, R.layout.habit_item_card), { _, _ -> }, { }) { task ->
TYPE_HABIT -> HabitViewHolder(getContentView(parent, R.layout.habit_item_card), { _, _ -> }, { }, { task ->
taskOpenEventsSubject.onNext(task)
}
TYPE_DAILY -> DailyViewHolder(getContentView(parent, R.layout.daily_item_card), { _, _ -> }, { _, _ -> }, { }) { task ->
}, null)
TYPE_DAILY -> DailyViewHolder(getContentView(parent, R.layout.daily_item_card), { _, _ -> }, { _, _ -> }, { }, { task ->
taskOpenEventsSubject.onNext(task)
}
TYPE_TODO -> TodoViewHolder(getContentView(parent, R.layout.todo_item_card), { _, _ -> }, { _, _ -> }, { }) { task ->
}, null)
TYPE_TODO -> TodoViewHolder(getContentView(parent, R.layout.todo_item_card), { _, _ -> }, { _, _ -> }, { }, { task ->
taskOpenEventsSubject.onNext(task)
}
TYPE_REWARD -> RewardViewHolder(getContentView(parent, R.layout.reward_item_card), { _, _ -> }, { }) { task ->
}, null)
TYPE_REWARD -> RewardViewHolder(getContentView(parent, R.layout.reward_item_card), { _, _ -> }, { }, { task ->
taskOpenEventsSubject.onNext(task)
}
}, null)
TYPE_ADD_ITEM -> AddItemViewHolder(getContentView(parent, R.layout.challenge_add_task_item), addItemSubject)
else -> DividerViewHolder(getContentView(parent, R.layout.challenge_task_divider))
}
@ -124,7 +124,11 @@ class ChallengeTasksRecyclerViewAdapter(
addBtn.setOnClickListener { newTask?.let { callback.onNext(it) } }
}
override fun bind(data: Task, position: Int, displayMode: String) {
override fun bind(
data: Task,
position: Int,
displayMode: String
) {
this.newTask = data
addBtn.text = data.text
}
@ -134,7 +138,11 @@ class ChallengeTasksRecyclerViewAdapter(
private val dividerName: TextView = itemView.findViewById(R.id.divider_name)
override fun bind(data: Task, position: Int, displayMode: String) {
override fun bind(
data: Task,
position: Int,
displayMode: String
) {
dividerName.text = data.text
}
}

View file

@ -15,11 +15,10 @@ class DailiesRecyclerViewHolder(layoutResource: Int, viewModel: TasksViewModel)
{
task ->
taskOpenEventsSubject.onNext(task)
}
) {
}, {
task ->
brokenTaskEventsSubject.onNext(task)
}
}, viewModel)
} else {
super.onCreateViewHolder(parent, viewType)
}

View file

@ -14,11 +14,10 @@ class HabitsRecyclerViewAdapter(layoutResource: Int, viewModel: TasksViewModel)
{
task ->
taskOpenEventsSubject.onNext(task)
}
) {
task ->
brokenTaskEventsSubject.onNext(task)
}
}, {
task ->
brokenTaskEventsSubject.onNext(task)
}, viewModel)
} else {
super.onCreateViewHolder(parent, viewType)
}

View file

@ -27,9 +27,8 @@ import io.realm.OrderedRealmCollection
abstract class RealmBaseTasksRecyclerViewAdapter(
private val layoutResource: Int,
private val viewModel: TasksViewModel
val viewModel: TasksViewModel
) : BaseRecyclerViewAdapter<Task, RecyclerView.ViewHolder>(), TaskRecyclerViewAdapter {
override var canScoreTasks = true
private var unfilteredData: List<Task>? = null
override var showAdventureGuide = false
set(value) {
@ -80,8 +79,8 @@ abstract class RealmBaseTasksRecyclerViewAdapter(
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val item = getItem(position)
if (item != null && holder is BaseTaskViewHolder) {
holder.isLocked = !canScoreTasks
holder.bind(item, position, taskDisplayMode)
holder.isLocked = !viewModel.canScoreTask(item)
holder.bind(item, position, taskDisplayMode, viewModel.ownerID.value)
holder.errorButtonClicked = Action {
errorButtonEventsSubject.onNext("")
}

View file

@ -5,7 +5,6 @@ import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.habitrpg.android.habitica.R
import com.habitrpg.shared.habitica.models.responses.TaskDirection
import com.habitrpg.android.habitica.models.shops.ShopItem
import com.habitrpg.android.habitica.models.tasks.ChecklistItem
import com.habitrpg.android.habitica.models.tasks.Task
@ -13,13 +12,16 @@ import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.ui.adapter.BaseRecyclerViewAdapter
import com.habitrpg.android.habitica.ui.viewHolders.ShopItemViewHolder
import com.habitrpg.android.habitica.ui.viewHolders.tasks.RewardViewHolder
import com.habitrpg.android.habitica.ui.viewmodels.TasksViewModel
import com.habitrpg.shared.habitica.models.responses.TaskDirection
import io.reactivex.rxjava3.core.BackpressureStrategy
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.subjects.PublishSubject
class RewardsRecyclerViewAdapter(
private var customRewards: List<Task>?,
private val layoutResource: Int
private val layoutResource: Int,
val viewModel: TasksViewModel
) : BaseRecyclerViewAdapter<Task, RecyclerView.ViewHolder>(), TaskRecyclerViewAdapter {
override var user: User? = null
set(value) {
@ -30,7 +32,6 @@ class RewardsRecyclerViewAdapter(
notifyDataSetChanged()
}
override var showAdventureGuide: Boolean = false
override var canScoreTasks = true
private var inAppRewards: List<ShopItem>? = null
private val errorButtonEventsSubject: PublishSubject<String> = PublishSubject.create()
@ -80,8 +81,10 @@ class RewardsRecyclerViewAdapter(
taskScoreEventsSubject.onNext(Pair(task, direction))
}
},
{ task -> taskOpenEventsSubject.onNext(task) }
) { task -> brokenTaskEventsSubject.onNext(task) }
{ task -> taskOpenEventsSubject.onNext(task) }, {
task ->
brokenTaskEventsSubject.onNext(task)
}, viewModel)
} else {
val viewHolder = ShopItemViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.row_shopitem, parent, false))
viewHolder.purchaseCardAction = { purchaseCardSubject.onNext(it) }
@ -93,8 +96,8 @@ class RewardsRecyclerViewAdapter(
if (customRewards != null && position < customRewardCount) {
val reward = customRewards?.get(position) ?: return
val gold = user?.stats?.gp ?: 0.0
(holder as? RewardViewHolder)?.isLocked = !canScoreTasks
(holder as? RewardViewHolder)?.bind(reward, position, reward.value <= gold, taskDisplayMode)
(holder as? RewardViewHolder)?.isLocked = false
(holder as? RewardViewHolder)?.bind(reward, position, reward.value <= gold, taskDisplayMode, viewModel.ownerID.value)
} else if (inAppRewards != null) {
val item = inAppRewards?.get(position - customRewardCount) ?: return
if (holder is ShopItemViewHolder) {

View file

@ -1,16 +1,15 @@
package com.habitrpg.android.habitica.ui.adapter.tasks
import android.view.View
import com.habitrpg.shared.habitica.models.responses.TaskDirection
import com.habitrpg.android.habitica.models.tasks.ChecklistItem
import com.habitrpg.android.habitica.models.tasks.Task
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.shared.habitica.models.responses.TaskDirection
import io.reactivex.rxjava3.core.Flowable
interface TaskRecyclerViewAdapter {
var user: User?
var showAdventureGuide: Boolean
var canScoreTasks: Boolean
var data: List<Task>
val errorButtonEvents: Flowable<String>

View file

@ -15,11 +15,10 @@ class TodosRecyclerViewAdapter(layoutResource: Int, viewModel: TasksViewModel) :
{
task ->
taskOpenEventsSubject.onNext(task)
}
) {
task ->
brokenTaskEventsSubject.onNext(task)
}
}, {
task ->
brokenTaskEventsSubject.onNext(task)
}, viewModel)
} else {
super.onCreateViewHolder(parent, viewType)
}

View file

@ -247,7 +247,7 @@ class ItemRecyclerFragment : BaseFragment<FragmentItemsBinding>(), SwipeRefreshL
val user = userRepository.retrieveUser(false, true)
if (user?.hasParty == true) {
val party = socialRepository.retrieveGroup("party")
socialRepository.retrieveGroupMembers(party?.id ?: "", true)
socialRepository.retrievePartyMembers(party?.id ?: "", true)
MainNavigationController.navigate(
R.id.partyFragment,
bundleOf(Pair("partyID", user.party?.id))

View file

@ -274,7 +274,7 @@ class ChallengeDetailFragment : BaseMainFragment<FragmentChallengeDetailBinding>
for (i in 0 until habits.size) {
val task = habits[i]
val entry = groupBinding.tasksLayout.inflate(R.layout.habit_item_card)
val viewHolder = HabitViewHolder(entry, { _, _ -> }, {}, {})
val viewHolder = HabitViewHolder(entry, { _, _ -> }, {}, {}, null)
viewHolder.isLocked = true
viewHolder.bind(task, i, "normal")
groupBinding.tasksLayout.addView(entry)
@ -289,7 +289,7 @@ class ChallengeDetailFragment : BaseMainFragment<FragmentChallengeDetailBinding>
for (i in 0 until dailies.size) {
val task = dailies[i]
val entry = groupBinding.tasksLayout.inflate(R.layout.daily_item_card)
val viewHolder = DailyViewHolder(entry, { _, _ -> }, { _, _ -> }, {}, {})
val viewHolder = DailyViewHolder(entry, { _, _ -> }, { _, _ -> }, {}, {}, null)
viewHolder.isLocked = true
viewHolder.bind(task, i, "normal")
groupBinding.tasksLayout.addView(entry)
@ -304,7 +304,7 @@ class ChallengeDetailFragment : BaseMainFragment<FragmentChallengeDetailBinding>
for (i in 0 until todos.size) {
val task = todos[i]
val entry = groupBinding.tasksLayout.inflate(R.layout.todo_item_card)
val viewHolder = TodoViewHolder(entry, { _, _ -> }, { _, _ -> }, {}, {})
val viewHolder = TodoViewHolder(entry, { _, _ -> }, { _, _ -> }, {}, {}, null)
viewHolder.isLocked = true
viewHolder.bind(task, i, "normal")
groupBinding.tasksLayout.addView(entry)
@ -319,9 +319,9 @@ class ChallengeDetailFragment : BaseMainFragment<FragmentChallengeDetailBinding>
for (i in 0 until rewards.size) {
val task = rewards[i]
val entry = groupBinding.tasksLayout.inflate(R.layout.reward_item_card)
val viewHolder = RewardViewHolder(entry, { _, _ -> }, {}, {})
val viewHolder = RewardViewHolder(entry, { _, _ -> }, {}, {}, null)
viewHolder.isLocked = true
viewHolder.bind(task, i, true, "normal")
viewHolder.bind(task, i, true, "normal", null)
groupBinding.tasksLayout.addView(entry)
}
}

View file

@ -181,7 +181,7 @@ class NoPartyFragmentFragment : BaseMainFragment<FragmentNoPartyBinding>() {
if (user?.hasParty == true) {
lifecycleScope.launch(ExceptionHandler.coroutine()) {
val group = socialRepository.retrieveGroup("party")
socialRepository.retrieveGroupMembers(group?.id ?: "", true)
socialRepository.retrievePartyMembers(group?.id ?: "", true)
}
}
}

View file

@ -114,12 +114,11 @@ open class TaskRecyclerViewFragment : BaseFragment<FragmentRefreshRecyclerviewBi
TaskType.HABIT -> HabitsRecyclerViewAdapter(R.layout.habit_item_card, viewModel)
TaskType.DAILY -> DailiesRecyclerViewHolder(R.layout.daily_item_card, viewModel)
TaskType.TODO -> TodosRecyclerViewAdapter(R.layout.todo_item_card, viewModel)
TaskType.REWARD -> RewardsRecyclerViewAdapter(null, R.layout.reward_item_card)
TaskType.REWARD -> RewardsRecyclerViewAdapter(null, R.layout.reward_item_card, viewModel)
else -> null
}
recyclerAdapter = adapter as? TaskRecyclerViewAdapter
recyclerAdapter?.canScoreTasks = canScoreTaks
binding?.recyclerView?.adapter = adapter
viewModel.getFilterSet(taskType)?.observe(viewLifecycleOwner) {
@ -152,7 +151,6 @@ open class TaskRecyclerViewFragment : BaseFragment<FragmentRefreshRecyclerviewBi
viewModel.ownerID.observe(viewLifecycleOwner) {
canEditTasks = viewModel.isPersonalBoard
canScoreTaks = viewModel.isPersonalBoard
recyclerAdapter?.canScoreTasks = canScoreTaks
updateTaskSubscription(it)
}
lifecycleScope.launch {
@ -346,7 +344,7 @@ open class TaskRecyclerViewFragment : BaseFragment<FragmentRefreshRecyclerviewBi
if (taskFlowJob?.isActive == true) {
taskFlowJob?.cancel()
}
val additionalGroupIDs = viewModel.userViewModel.mirrorGroupTasks.toTypedArray()
val additionalGroupIDs = if (ownerID == viewModel.userViewModel.userID) viewModel.userViewModel.mirrorGroupTasks.toTypedArray() else emptyArray()
taskFlowJob = lifecycleScope.launch(ExceptionHandler.coroutine()) {
taskRepository.getTasks(taskType, ownerID, additionalGroupIDs).collect {
recyclerAdapter?.updateUnfilteredData(it)

View file

@ -15,7 +15,6 @@ import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.widget.SearchView
import androidx.core.content.ContextCompat
import androidx.core.content.edit
import androidx.core.os.bundleOf
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.viewpager2.adapter.FragmentStateAdapter
@ -27,7 +26,6 @@ import com.habitrpg.android.habitica.databinding.FragmentViewpagerBinding
import com.habitrpg.android.habitica.extensions.setTintWith
import com.habitrpg.android.habitica.helpers.AmplitudeManager
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.ui.activities.TaskFormActivity
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
import com.habitrpg.android.habitica.ui.viewmodels.TasksViewModel
@ -81,7 +79,7 @@ class TasksFragment : BaseMainFragment<FragmentViewpagerBinding>(), SearchView.O
if (args.ownerID?.isNotBlank() == true) {
viewModel.canSwitchOwners.value = false
viewModel.ownerID.value = args.ownerID ?: viewModel.userViewModel.userID
} else {
} else if (viewModel.ownerID.value?.isNotBlank() != true) {
viewModel.ownerID.value = viewModel.userViewModel.userID
}
if (taskTypeValue?.isNotBlank() == true) {
@ -185,10 +183,6 @@ class TasksFragment : BaseMainFragment<FragmentViewpagerBinding>(), SearchView.O
viewModel.refreshData { }
true
}
R.id.action_team_info -> {
MainNavigationController.navigate(R.id.guildFragment, bundleOf(Pair("groupID", viewModel.ownerID)))
true
}
else -> super.onOptionsItemSelected(item)
}
}

View file

@ -5,5 +5,9 @@ import androidx.recyclerview.widget.RecyclerView
abstract class BindableViewHolder<T>(itemView: View) : RecyclerView.ViewHolder(itemView) {
abstract fun bind(data: T, position: Int, displayMode: String)
abstract fun bind(
data: T,
position: Int,
displayMode: String
)
}

View file

@ -12,7 +12,9 @@ import android.widget.LinearLayout
import android.widget.ProgressBar
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.lifecycle.MutableLiveData
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.helpers.AssignedTextProvider
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.models.tasks.Task
import com.habitrpg.android.habitica.ui.viewHolders.BindableViewHolder
@ -34,7 +36,8 @@ abstract class BaseTaskViewHolder constructor(
itemView: View,
var scoreTaskFunc: ((Task, TaskDirection) -> Unit),
var openTaskFunc: ((Pair<Task, View>) -> Unit),
var brokenTaskFunc: ((Task) -> Unit)
var brokenTaskFunc: ((Task) -> Unit),
var assignedTextProvider: AssignedTextProvider?
) : BindableViewHolder<Task>(itemView), View.OnTouchListener {
private val scope = MainScope()
@ -44,6 +47,7 @@ abstract class BaseTaskViewHolder constructor(
var isLocked = false
protected var context: Context
private val mainTaskWrapper: ViewGroup = itemView.findViewById(R.id.main_task_wrapper)
protected val assignedTextView: EllipsisTextView = itemView.findViewById(R.id.assigned_textview)
protected val titleTextView: EllipsisTextView = itemView.findViewById(R.id.checkedTextView)
protected val notesTextView: EllipsisTextView? = itemView.findViewById(R.id.notesTextView)
protected val calendarIconView: ImageView? = itemView.findViewById(R.id.iconViewCalendar)
@ -52,7 +56,7 @@ abstract class BaseTaskViewHolder constructor(
private val iconViewChallenge: ImageView? = itemView.findViewById(R.id.iconviewChallenge)
private val iconViewReminder: ImageView? = itemView.findViewById(R.id.iconviewReminder)
private val taskIconWrapper: LinearLayout? = itemView.findViewById(R.id.taskIconWrapper)
private val approvalRequiredTextView: TextView? = itemView.findViewById(R.id.approvalRequiredTextField)
private val approvalRequiredTextView: TextView = itemView.findViewById(R.id.approvalRequiredTextField)
private val expandNotesButton: Button? = itemView.findViewById(R.id.expand_notes_button)
private val syncingView: ProgressBar? = itemView.findViewById(R.id.syncing_view)
private val errorIconView: ImageButton? = itemView.findViewById(R.id.error_icon)
@ -133,6 +137,15 @@ abstract class BaseTaskViewHolder constructor(
}
override fun bind(data: Task, position: Int, displayMode: String) {
bind(data, position, displayMode, null)
}
open fun bind(
data: Task,
position: Int,
displayMode: String,
ownerID: String?
) {
notesExpanded = false
task = data
itemView.setBackgroundColor(context.getThemeColor(R.attr.colorContentBackground))
@ -227,7 +240,7 @@ abstract class BaseTaskViewHolder constructor(
}
configureSpecialTaskTextView(data)
iconViewTeam?.visibility = if (data.isGroupTask) View.VISIBLE else View.GONE
iconViewTeam?.visibility = if (data.isGroupTask && ownerID != data.group?.groupID) View.VISIBLE else View.GONE
taskIconWrapper?.visibility = if (taskIconWrapperIsVisible) View.VISIBLE else View.GONE
} else {
@ -236,9 +249,16 @@ abstract class BaseTaskViewHolder constructor(
}
if (data.isPendingApproval) {
approvalRequiredTextView?.visibility = View.VISIBLE
approvalRequiredTextView.visibility = View.VISIBLE
} else {
approvalRequiredTextView?.visibility = View.GONE
approvalRequiredTextView.visibility = View.GONE
}
if (data.group?.assignedUsers?.isEmpty() != false) {
assignedTextView.text = assignedTextProvider?.textForTask(context.resources, data.group?.assignedUsers ?: emptyList())
assignedTextView.visibility = View.VISIBLE
} else {
assignedTextView.visibility = View.GONE
}
syncingView?.visibility = if (task?.isSaving == true) View.VISIBLE else View.GONE

View file

@ -12,6 +12,7 @@ import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.helpers.AssignedTextProvider
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.models.tasks.ChecklistItem
import com.habitrpg.android.habitica.models.tasks.Task
@ -30,8 +31,9 @@ abstract class ChecklistedViewHolder(
scoreTaskFunc: ((Task, TaskDirection) -> Unit),
var scoreChecklistItemFunc: ((Task, ChecklistItem) -> Unit),
openTaskFunc: ((Pair<Task, View>) -> Unit),
brokenTaskFunc: ((Task) -> Unit)
) : BaseTaskViewHolder(itemView, scoreTaskFunc, openTaskFunc, brokenTaskFunc) {
brokenTaskFunc: ((Task) -> Unit),
assignedTextProvider: AssignedTextProvider?
) : BaseTaskViewHolder(itemView, scoreTaskFunc, openTaskFunc, brokenTaskFunc, assignedTextProvider) {
private val checkboxHolder: ViewGroup = itemView.findViewById(R.id.checkBoxHolder)
private val checkmarkView: ImageView = itemView.findViewById(R.id.checkmark)
@ -48,7 +50,12 @@ abstract class ChecklistedViewHolder(
checklistIndicatorWrapper.setOnClickListener { onChecklistIndicatorClicked() }
}
override fun bind(data: Task, position: Int, displayMode: String) {
override fun bind(
data: Task,
position: Int,
displayMode: String,
ownerID: String?
) {
var completed = data.completed
if (data.isPendingApproval) {
completed = false
@ -69,7 +76,7 @@ abstract class ChecklistedViewHolder(
this.updateChecklistDisplay()
this.checklistIndicatorWrapper.visibility = if (data.checklist?.size == 0) View.GONE else View.VISIBLE
super.bind(data, position, displayMode)
super.bind(data, position, displayMode, ownerID)
val regularBoxBackground = if (task?.type == TaskType.DAILY) R.drawable.daily_unchecked else R.drawable.todo_unchecked
val completedBoxBackground = if (task?.type == TaskType.DAILY) R.drawable.daily_checked else R.drawable.todo_checked
val inactiveBoxBackground = R.drawable.daily_inactive

View file

@ -1,9 +1,10 @@
package com.habitrpg.android.habitica.ui.viewHolders.tasks
import android.view.View
import com.habitrpg.shared.habitica.models.responses.TaskDirection
import com.habitrpg.android.habitica.helpers.AssignedTextProvider
import com.habitrpg.android.habitica.models.tasks.ChecklistItem
import com.habitrpg.android.habitica.models.tasks.Task
import com.habitrpg.shared.habitica.models.responses.TaskDirection
import java.text.DateFormat
import java.util.Calendar
import java.util.Date
@ -13,8 +14,9 @@ class DailyViewHolder(
scoreTaskFunc: ((Task, TaskDirection) -> Unit),
scoreChecklistItemFunc: ((Task, ChecklistItem) -> Unit),
openTaskFunc: ((Pair<Task, View>) -> Unit),
brokenTaskFunc: ((Task) -> Unit)
) : ChecklistedViewHolder(itemView, scoreTaskFunc, scoreChecklistItemFunc, openTaskFunc, brokenTaskFunc) {
brokenTaskFunc: ((Task) -> Unit),
assignedTextProvider: AssignedTextProvider?
) : ChecklistedViewHolder(itemView, scoreTaskFunc, scoreChecklistItemFunc, openTaskFunc, brokenTaskFunc, assignedTextProvider) {
override val taskIconWrapperIsVisible: Boolean
get() {
@ -25,7 +27,12 @@ class DailyViewHolder(
return isVisible
}
override fun bind(data: Task, position: Int, displayMode: String) {
override fun bind(
data: Task,
position: Int,
displayMode: String,
ownerID: String?
) {
this.task = data
setChecklistIndicatorBackgroundActive(data.isChecklistDisplayActive)
@ -59,7 +66,7 @@ class DailyViewHolder(
reminderTextView.text = reminderString
}
super.bind(data, position, displayMode)
super.bind(data, position, displayMode, ownerID)
}
override fun shouldDisplayAsActive(newTask: Task?): Boolean {
@ -68,7 +75,7 @@ class DailyViewHolder(
override fun configureSpecialTaskTextView(task: Task) {
super.configureSpecialTaskTextView(task)
if (task.streak ?: 0 > 0) {
if ((task.streak ?: 0) > 0) {
this.streakTextView.text = task.streak.toString()
this.streakTextView.visibility = View.VISIBLE
this.streakIconView.visibility = View.VISIBLE

View file

@ -6,7 +6,9 @@ import android.widget.Button
import android.widget.FrameLayout
import android.widget.ImageView
import androidx.core.content.ContextCompat
import androidx.lifecycle.MutableLiveData
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.helpers.AssignedTextProvider
import com.habitrpg.android.habitica.models.tasks.Task
import com.habitrpg.shared.habitica.models.responses.TaskDirection
@ -14,8 +16,9 @@ class HabitViewHolder(
itemView: View,
scoreTaskFunc: ((Task, TaskDirection) -> Unit),
openTaskFunc: ((Pair<Task, View>) -> Unit),
brokenTaskFunc: ((Task) -> Unit)
) : BaseTaskViewHolder(itemView, scoreTaskFunc, openTaskFunc, brokenTaskFunc) {
brokenTaskFunc: ((Task) -> Unit),
assignedTextProvider: AssignedTextProvider?
) : BaseTaskViewHolder(itemView, scoreTaskFunc, openTaskFunc, brokenTaskFunc, assignedTextProvider) {
private val btnPlusWrapper: FrameLayout = itemView.findViewById(R.id.btnPlusWrapper)
private val btnPlusIconView: ImageView = itemView.findViewById(R.id.btnPlusIconView)
@ -33,7 +36,12 @@ class HabitViewHolder(
btnMinus.isClickable = true
}
override fun bind(data: Task, position: Int, displayMode: String) {
override fun bind(
data: Task,
position: Int,
displayMode: String,
ownerID: String?
) {
this.task = data
if (data.up == true) {
val plusIcon = if (isLocked) {
@ -121,7 +129,7 @@ class HabitViewHolder(
}
reminderTextView.visibility = View.GONE
calendarIconView?.visibility = View.GONE
super.bind(data, position, displayMode)
super.bind(data, position, displayMode, ownerID)
if (data.up == false && data.down == false) {
titleTextView.setTextColor(ContextCompat.getColor(context, R.color.text_quad))
notesTextView?.setTextColor(ContextCompat.getColor(context, R.color.text_quad))

View file

@ -6,18 +6,20 @@ import androidx.core.content.ContextCompat
import androidx.core.graphics.ColorUtils
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.databinding.RewardItemCardBinding
import com.habitrpg.common.habitica.helpers.NumberAbbreviator
import com.habitrpg.shared.habitica.models.responses.TaskDirection
import com.habitrpg.android.habitica.helpers.AssignedTextProvider
import com.habitrpg.android.habitica.models.tasks.Task
import com.habitrpg.android.habitica.ui.ItemDetailDialog
import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
import com.habitrpg.common.habitica.helpers.NumberAbbreviator
import com.habitrpg.shared.habitica.models.responses.TaskDirection
class RewardViewHolder(
itemView: View,
scoreTaskFunc: ((Task, TaskDirection) -> Unit),
openTaskFunc: ((Pair<Task, View>) -> Unit),
brokenTaskFunc: ((Task) -> Unit)
) : BaseTaskViewHolder(itemView, scoreTaskFunc, openTaskFunc, brokenTaskFunc) {
brokenTaskFunc: ((Task) -> Unit),
assignedTextProvider: AssignedTextProvider?
) : BaseTaskViewHolder(itemView, scoreTaskFunc, openTaskFunc, brokenTaskFunc, assignedTextProvider) {
private val binding = RewardItemCardBinding.bind(itemView)
private val isItem: Boolean
@ -61,10 +63,10 @@ class RewardViewHolder(
binding.buyButton.isEnabled = !taskActionsDisabled
}
fun bind(reward: Task, position: Int, canBuy: Boolean, displayMode: String) {
fun bind(reward: Task, position: Int, canBuy: Boolean, displayMode: String, ownerID: String?) {
this.task = reward
streakTextView.visibility = View.GONE
super.bind(reward, position, displayMode)
super.bind(reward, position, displayMode, ownerID)
binding.priceLabel.text = NumberAbbreviator.abbreviate(itemView.context, this.task?.value ?: 0.0)
if (isLocked) {

View file

@ -1,9 +1,10 @@
package com.habitrpg.android.habitica.ui.viewHolders.tasks
import android.view.View
import com.habitrpg.shared.habitica.models.responses.TaskDirection
import com.habitrpg.android.habitica.helpers.AssignedTextProvider
import com.habitrpg.android.habitica.models.tasks.ChecklistItem
import com.habitrpg.android.habitica.models.tasks.Task
import com.habitrpg.shared.habitica.models.responses.TaskDirection
import java.text.DateFormat
class TodoViewHolder(
@ -11,17 +12,23 @@ class TodoViewHolder(
scoreTaskFunc: ((Task, TaskDirection) -> Unit),
scoreChecklistItemFunc: ((Task, ChecklistItem) -> Unit),
openTaskFunc: ((Pair<Task, View>) -> Unit),
brokenTaskFunc: ((Task) -> Unit)
) : ChecklistedViewHolder(itemView, scoreTaskFunc, scoreChecklistItemFunc, openTaskFunc, brokenTaskFunc) {
brokenTaskFunc: ((Task) -> Unit),
assignedTextProvider: AssignedTextProvider?
) : ChecklistedViewHolder(itemView, scoreTaskFunc, scoreChecklistItemFunc, openTaskFunc, brokenTaskFunc, assignedTextProvider) {
private val dateFormatter: DateFormat = android.text.format.DateFormat.getDateFormat(context)
override fun bind(data: Task, position: Int, displayMode: String) {
override fun bind(
data: Task,
position: Int,
displayMode: String,
ownerID: String?
) {
this.task = data
setChecklistIndicatorBackgroundActive(data.isChecklistDisplayActive)
reminderTextView.visibility = View.GONE
this.streakTextView.visibility = View.GONE
super.bind(data, position, displayMode)
super.bind(data, position, displayMode, ownerID)
}
override fun configureSpecialTaskTextView(task: Task) {

View file

@ -20,7 +20,6 @@ import io.realm.kotlin.toFlow
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flatMapConcat
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
@ -129,7 +128,7 @@ open class GroupViewModel(initializeComponent: Boolean) : BaseViewModel(initiali
}) {
val group = socialRepository.retrieveGroup(groupID ?: "")
if (groupViewType == GroupViewType.PARTY) {
socialRepository.retrieveGroupMembers(group?.id ?: "", true)
socialRepository.retrievePartyMembers(group?.id ?: "", true)
}
function?.invoke()
}

View file

@ -43,6 +43,10 @@ class MainUserViewModel(private val providedUserID: String, val userRepository:
.filterNotNull()
.distinctUntilChanged { old, new -> old.id == new.id }
.flatMapLatest { socialRepository.getGroup(it.id) }
var currentTeamPlanMembers = currentTeamPlan
.filterNotNull()
.distinctUntilChanged { old, new -> old.id == new.id }
.flatMapLatest { socialRepository.getGroupMembers(it.id) }
fun onCleared() {
userRepository.close()

View file

@ -24,7 +24,7 @@ class PartyViewModel(initializeComponent: Boolean) : GroupViewModel(initializeCo
private val membersFlow = groupIDFlow
.filterNotNull()
.flatMapLatest { socialRepository.getGroupMembers(it) }
.flatMapLatest { socialRepository.getPartyMembers(it) }
private val members = membersFlow.asLiveData()
init {

View file

@ -1,15 +1,18 @@
package com.habitrpg.android.habitica.ui.viewmodels
import android.content.SharedPreferences
import android.content.res.Resources
import android.text.format.DateUtils
import androidx.core.content.edit
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.TagRepository
import com.habitrpg.android.habitica.data.TaskRepository
import com.habitrpg.android.habitica.helpers.AmplitudeManager
import com.habitrpg.android.habitica.helpers.AppConfigManager
import com.habitrpg.android.habitica.helpers.AssignedTextProvider
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.models.TeamPlan
import com.habitrpg.android.habitica.models.tasks.Task
@ -25,7 +28,7 @@ import kotlinx.coroutines.launch
import java.util.Date
import javax.inject.Inject
class TasksViewModel : BaseViewModel() {
class TasksViewModel : BaseViewModel(), AssignedTextProvider {
private var compositeSubscription: CompositeDisposable = CompositeDisposable()
override fun inject(component: UserComponent) {
@ -301,4 +304,19 @@ class TasksViewModel : BaseViewModel() {
}
return query
}
fun canScoreTask(item: Task): Boolean {
if (!item.isGroupTask) {
return true
}
return item.isAssignedToUser(userViewModel.userID) || item.group?.assignedUsers?.isEmpty() != false
}
override fun textForTask(resources: Resources, assignedUsers: List<String>): String {
return if (assignedUsers.contains(userViewModel.userID)) {
resources.getQuantityString(R.plurals.you_x_others, assignedUsers.size - 1)
} else {
resources.getQuantityString(R.plurals.people, assignedUsers.size)
}
}
}

View file

@ -11,13 +11,16 @@ import androidx.compose.animation.slideOutHorizontally
import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredSize
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
@ -33,15 +36,19 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.os.bundleOf
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import com.habitrpg.common.habitica.helpers.NumberAbbreviator
@ -104,7 +111,8 @@ fun AppHeaderView(
viewModel: MainUserViewModel,
) {
val user by viewModel.user.observeAsState(null)
val displayedTeamPlan by viewModel.currentTeamPlan.collectAsState()
val teamPlan by viewModel.currentTeamPlan.collectAsState(null)
val teamPlanMembers by viewModel.currentTeamPlanMembers.collectAsState(null)
Column {
Row {
ComposableAvatarView(
@ -114,7 +122,7 @@ fun AppHeaderView(
.padding(end = 16.dp)
)
Column(modifier = Modifier.height(100.dp)) {
Row(modifier = Modifier.weight(1f)) {
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.weight(1f)) {
Column(modifier = Modifier.weight(1f)) {
LabeledBar(
icon = HabiticaIconsHelper.imageOfHeartLightBg(),
@ -122,7 +130,7 @@ fun AppHeaderView(
color = colorResource(R.color.hpColor),
value = user?.stats?.hp ?: 0.0,
maxValue = user?.stats?.maxHealth?.toDouble() ?: 0.0,
displayCompact = displayedTeamPlan != null,
displayCompact = teamPlan != null,
modifier = Modifier.weight(1f)
)
LabeledBar(
@ -131,7 +139,7 @@ fun AppHeaderView(
color = colorResource(R.color.xpColor),
value = user?.stats?.exp ?: 0.0,
maxValue = user?.stats?.toNextLevel?.toDouble() ?: 0.0,
displayCompact = displayedTeamPlan != null,
displayCompact = teamPlan != null,
modifier = Modifier.weight(1f)
)
if (user?.hasClass == true) {
@ -141,50 +149,73 @@ fun AppHeaderView(
color = colorResource(R.color.mpColor),
value = user?.stats?.mp ?: 0.0,
maxValue = user?.stats?.maxMP?.toDouble() ?: 0.0,
displayCompact = displayedTeamPlan != null,
displayCompact = teamPlan != null,
modifier = Modifier.weight(1f)
)
}
}
val animWidth = with(LocalDensity.current) { 48.dp.roundToPx() }
AnimatedVisibility(
visible = displayedTeamPlan != null,
visible = teamPlan != null,
enter = slideInHorizontally { animWidth } + fadeIn(),
exit = slideOutHorizontally { animWidth } + fadeOut()) {
Row(
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.height(72.dp)
.width(48.dp)
.padding(start = 12.dp)
.width(72.dp)
.height(48.dp)
.clip(RoundedCornerShape(8.dp))
.background(
colorResource(R.color.window_background)
)
.clickable {
MainNavigationController.navigate(
R.id.guildFragment,
bundleOf("groupID" to teamPlan?.id)
)
}
) {
Text("M")
Image(painterResource(R.drawable.icon_chat), null, colorFilter = ColorFilter.tint(
colorResource(R.color.text_ternary)))
}
}
}
val animHeight = with(LocalDensity.current) { 40.dp.roundToPx() }
AnimatedVisibility(
visible = displayedTeamPlan != null,
visible = teamPlan != null,
enter = slideInVertically { animHeight } + fadeIn(),
exit = slideOutVertically { animHeight } + fadeOut()) {
Row(
horizontalArrangement = Arrangement.Center,
horizontalArrangement = Arrangement.spacedBy(14.dp, Alignment.CenterHorizontally),
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.height(40.dp)
.padding(top = 12.dp)
.height(40.dp)
.width(72.dp)
.clip(RoundedCornerShape(8.dp))
.background(
colorResource(R.color.window_background)
)
.clickable {
MainNavigationController.navigate(
R.id.guildFragment,
bundleOf("groupID" to teamPlan?.id)
)
}
) {
Text("A")
for (member in teamPlanMembers?.filter { it.id != user?.id }?.take(6) ?: emptyList()) {
Box(modifier = Modifier.clip(CircleShape).size(26.dp).padding(end = 6.dp, top = 4.dp)) {
ComposableAvatarView(
avatar = member,
Modifier
.size(64.dp)
.requiredSize(64.dp)
)
}
}
}
}
}

View file

@ -1,11 +1,26 @@
package com.habitrpg.android.habitica.ui.views
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Outline
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.viewinterop.AndroidView
import com.habitrpg.common.habitica.views.AvatarView
import com.habitrpg.shared.habitica.models.Avatar
object AvatarCircleShape : Shape {
override fun createOutline(
size: Size,
layoutDirection: LayoutDirection,
density: Density
): Outline =
CircleShape.createOutline(size, 20f, 20f, 20f, 20f, layoutDirection)
}
@Composable
fun ComposableAvatarView(
avatar: Avatar?,