diff --git a/Habitica/res/drawable-mdpi/baseline_keyboard_arrow_right_black_18dp.png b/Habitica/res/drawable-mdpi/baseline_keyboard_arrow_right_black_18dp.png deleted file mode 100644 index 6ab2cf339..000000000 Binary files a/Habitica/res/drawable-mdpi/baseline_keyboard_arrow_right_black_18dp.png and /dev/null differ diff --git a/Habitica/res/drawable/icon_chat.xml b/Habitica/res/drawable/icon_chat.xml new file mode 100644 index 000000000..42714253f --- /dev/null +++ b/Habitica/res/drawable/icon_chat.xml @@ -0,0 +1,5 @@ + + + diff --git a/Habitica/res/layout/task_main_content.xml b/Habitica/res/layout/task_main_content.xml index bf2e2f2ba..ac11e210c 100644 --- a/Habitica/res/layout/task_main_content.xml +++ b/Habitica/res/layout/task_main_content.xml @@ -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"> + - Show assigned and open tasks on your personal task lists Copy shared tasks Group Plan Settings + + You + You, %d other + You, %d others + + + %d Person + %d People + diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/SocialRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/SocialRepository.kt index dfd31c660..dfb6713f7 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/SocialRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/SocialRepository.kt @@ -78,8 +78,9 @@ interface SocialRepository : BaseRepository { suspend fun postPrivateMessage(recipientId: String, message: String): List? + suspend fun getPartyMembers(id: String): Flow> suspend fun getGroupMembers(id: String): Flow> - suspend fun retrieveGroupMembers(id: String, includeAllPublicFields: Boolean): List? + suspend fun retrievePartyMembers(id: String, includeAllPublicFields: Boolean): List? fun inviteToGroup(id: String, inviteData: Map): Flowable> diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/SocialRepositoryImpl.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/SocialRepositoryImpl.kt index dd82f6db3..20de3196e 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/SocialRepositoryImpl.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/SocialRepositoryImpl.kt @@ -35,7 +35,7 @@ class SocialRepositoryImpl( override suspend fun removeMemberFromGroup(groupID: String, userID: String): List? { apiClient.removeMemberFromGroup(groupID, userID) - return retrieveGroupMembers(groupID, true) + return retrievePartyMembers(groupID, true) } override fun blockMember(userID: String): Flowable> { @@ -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? { + override suspend fun retrievePartyMembers(id: String, includeAllPublicFields: Boolean): List? { val members = apiClient.getGroupMembers(id, includeAllPublicFields) - members?.let { localRepository.saveGroupMembers(id, it) } + members?.let { localRepository.savePartyMembers(id, it) } return members } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/UserRepositoryImpl.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/UserRepositoryImpl.kt index c863f955c..15ff1a27b 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/UserRepositoryImpl.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/UserRepositoryImpl.kt @@ -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 } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/SocialLocalRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/SocialLocalRepository.kt index 66a106520..b1f8dbdd5 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/SocialLocalRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/SocialLocalRepository.kt @@ -23,13 +23,14 @@ interface SocialLocalRepository : BaseLocalRepository { fun deleteMessage(id: String) - fun getGroupMembers(partyId: String): Flow> + fun getPartyMembers(partyId: String): Flow> + fun getGroupMembers(groupID: String): Flow> fun updateRSVPNeeded(user: User?, newValue: Boolean) fun likeMessage(chatMessage: ChatMessage, userId: String, liked: Boolean) - fun saveGroupMembers(groupId: String?, members: List) + fun savePartyMembers(groupId: String?, members: List) fun removeQuest(partyId: String) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmSocialLocalRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmSocialLocalRepository.kt index 06fd7a6af..eb1f5dec9 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmSocialLocalRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmSocialLocalRepository.kt @@ -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) { + override fun savePartyMembers(groupId: String?, members: List) { saveSyncronous(members) if (groupId != null) { val existingMembers = realm.where(Member::class.java).equalTo("party.id", groupId).findAll() diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmTaskLocalRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmTaskLocalRepository.kt index 2f8d1e5bd..57bf84801 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmTaskLocalRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmTaskLocalRepository.kt @@ -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): Flow> { 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): Flowable> { 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 + ): RealmResults { + 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> { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/AssignedTextProvider.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/AssignedTextProvider.kt new file mode 100644 index 000000000..4d97ad970 --- /dev/null +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/AssignedTextProvider.kt @@ -0,0 +1,7 @@ +package com.habitrpg.android.habitica.helpers + +import android.content.res.Resources + +interface AssignedTextProvider { + fun textForTask(resources: Resources, assignedUsers: List): String +} diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/models/social/Group.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/models/social/Group.kt index 85c8e53fb..433cf3c7d 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/models/social/Group.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/models/social/Group.kt @@ -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? = 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) { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/SkillMemberActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/SkillMemberActivity.kt index 4e158006d..a041329ec 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/SkillMemberActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/SkillMemberActivity.kt @@ -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 } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/challenges/ChallengeTasksRecyclerViewAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/challenges/ChallengeTasksRecyclerViewAdapter.kt index 9e5a8dca9..b399fd74e 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/challenges/ChallengeTasksRecyclerViewAdapter.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/challenges/ChallengeTasksRecyclerViewAdapter.kt @@ -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 { val viewHolder: BindableViewHolder = 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 } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/DailiesRecyclerViewHolder.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/DailiesRecyclerViewHolder.kt index b645979aa..4dde8bfa9 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/DailiesRecyclerViewHolder.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/DailiesRecyclerViewHolder.kt @@ -15,11 +15,10 @@ class DailiesRecyclerViewHolder(layoutResource: Int, viewModel: TasksViewModel) { task -> taskOpenEventsSubject.onNext(task) - } - ) { + }, { task -> brokenTaskEventsSubject.onNext(task) - } + }, viewModel) } else { super.onCreateViewHolder(parent, viewType) } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/HabitsRecyclerViewAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/HabitsRecyclerViewAdapter.kt index f207c107e..f61beed69 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/HabitsRecyclerViewAdapter.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/HabitsRecyclerViewAdapter.kt @@ -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) } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/RealmBaseTasksRecyclerViewAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/RealmBaseTasksRecyclerViewAdapter.kt index 014c7ec48..e39b19af2 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/RealmBaseTasksRecyclerViewAdapter.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/RealmBaseTasksRecyclerViewAdapter.kt @@ -27,9 +27,8 @@ import io.realm.OrderedRealmCollection abstract class RealmBaseTasksRecyclerViewAdapter( private val layoutResource: Int, - private val viewModel: TasksViewModel + val viewModel: TasksViewModel ) : BaseRecyclerViewAdapter(), TaskRecyclerViewAdapter { - override var canScoreTasks = true private var unfilteredData: List? = 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("") } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/RewardsRecyclerViewAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/RewardsRecyclerViewAdapter.kt index 222d0a0a0..7983d1261 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/RewardsRecyclerViewAdapter.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/RewardsRecyclerViewAdapter.kt @@ -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?, - private val layoutResource: Int + private val layoutResource: Int, + val viewModel: TasksViewModel ) : BaseRecyclerViewAdapter(), 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? = null private val errorButtonEventsSubject: PublishSubject = 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) { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/TaskRecyclerViewAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/TaskRecyclerViewAdapter.kt index 2f38295b6..73e650fa3 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/TaskRecyclerViewAdapter.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/TaskRecyclerViewAdapter.kt @@ -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 val errorButtonEvents: Flowable diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/TodosRecyclerViewAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/TodosRecyclerViewAdapter.kt index dbebdd259..5be12d9b6 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/TodosRecyclerViewAdapter.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/TodosRecyclerViewAdapter.kt @@ -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) } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/items/ItemRecyclerFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/items/ItemRecyclerFragment.kt index 5eb2b0e9a..fca4bbc7d 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/items/ItemRecyclerFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/items/ItemRecyclerFragment.kt @@ -247,7 +247,7 @@ class ItemRecyclerFragment : BaseFragment(), 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)) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/challenges/ChallengeDetailFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/challenges/ChallengeDetailFragment.kt index 0e19c117c..ca8aeef27 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/challenges/ChallengeDetailFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/challenges/ChallengeDetailFragment.kt @@ -274,7 +274,7 @@ class ChallengeDetailFragment : BaseMainFragment 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 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 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 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) } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/NoPartyFragmentFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/NoPartyFragmentFragment.kt index f9ed1ea99..1843af8bd 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/NoPartyFragmentFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/NoPartyFragmentFragment.kt @@ -181,7 +181,7 @@ class NoPartyFragmentFragment : BaseMainFragment() { if (user?.hasParty == true) { lifecycleScope.launch(ExceptionHandler.coroutine()) { val group = socialRepository.retrieveGroup("party") - socialRepository.retrieveGroupMembers(group?.id ?: "", true) + socialRepository.retrievePartyMembers(group?.id ?: "", true) } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TaskRecyclerViewFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TaskRecyclerViewFragment.kt index 0e5a98fb3..f4552970b 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TaskRecyclerViewFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TaskRecyclerViewFragment.kt @@ -114,12 +114,11 @@ open class TaskRecyclerViewFragment : BaseFragment 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(), 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(), 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) } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/BindableViewHolder.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/BindableViewHolder.kt index 1d9936845..fa1f6469f 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/BindableViewHolder.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/BindableViewHolder.kt @@ -5,5 +5,9 @@ import androidx.recyclerview.widget.RecyclerView abstract class BindableViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { - abstract fun bind(data: T, position: Int, displayMode: String) + abstract fun bind( + data: T, + position: Int, + displayMode: String + ) } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/BaseTaskViewHolder.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/BaseTaskViewHolder.kt index eef5b0bcd..9b8e25deb 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/BaseTaskViewHolder.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/BaseTaskViewHolder.kt @@ -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) -> Unit), - var brokenTaskFunc: ((Task) -> Unit) + var brokenTaskFunc: ((Task) -> Unit), + var assignedTextProvider: AssignedTextProvider? ) : BindableViewHolder(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 diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/ChecklistedViewHolder.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/ChecklistedViewHolder.kt index 6d331221a..211c293df 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/ChecklistedViewHolder.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/ChecklistedViewHolder.kt @@ -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) -> 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 diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/DailyViewHolder.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/DailyViewHolder.kt index 9b1665f04..26ab150a7 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/DailyViewHolder.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/DailyViewHolder.kt @@ -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) -> 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 diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/HabitViewHolder.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/HabitViewHolder.kt index 2c8cf24ab..e31f0337d 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/HabitViewHolder.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/HabitViewHolder.kt @@ -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) -> 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)) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/RewardViewHolder.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/RewardViewHolder.kt index fb5b8b7d5..06f778ecb 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/RewardViewHolder.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/RewardViewHolder.kt @@ -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) -> 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) { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/TodoViewHolder.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/TodoViewHolder.kt index 6657681ba..b7bf10b2a 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/TodoViewHolder.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/TodoViewHolder.kt @@ -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) -> 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) { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/GroupViewModel.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/GroupViewModel.kt index e80a95bfd..0b07975b4 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/GroupViewModel.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/GroupViewModel.kt @@ -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() } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/MainUserViewModel.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/MainUserViewModel.kt index 810881d17..dcf766b42 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/MainUserViewModel.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/MainUserViewModel.kt @@ -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() diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/PartyViewModel.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/PartyViewModel.kt index b00f435aa..344def085 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/PartyViewModel.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/PartyViewModel.kt @@ -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 { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/TasksViewModel.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/TasksViewModel.kt index 0f3a0d098..566958c84 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/TasksViewModel.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/TasksViewModel.kt @@ -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 { + return if (assignedUsers.contains(userViewModel.userID)) { + resources.getQuantityString(R.plurals.you_x_others, assignedUsers.size - 1) + } else { + resources.getQuantityString(R.plurals.people, assignedUsers.size) + } + } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/AppHeaderView.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/AppHeaderView.kt index 43ac140c9..efcd3b6aa 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/AppHeaderView.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/AppHeaderView.kt @@ -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) + ) + } + } } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ComposableAvatarView.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ComposableAvatarView.kt index 8b2180169..5bee6721a 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ComposableAvatarView.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ComposableAvatarView.kt @@ -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?,