add ability to view team task boards

This commit is contained in:
Phillip Thelen 2021-01-27 15:19:34 +01:00
parent 25f82c8d3c
commit f5e5a987d9
26 changed files with 717 additions and 109 deletions

View file

@ -21,6 +21,7 @@
android:layout_height="match_parent"
android:background="?barColor" />
<LinearLayout
android:id="@+id/cutout_wrapper"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
@ -68,6 +69,7 @@
android:paddingTop="@dimen/spacing_small"
android:paddingBottom="@dimen/spacing_small"/>
<androidx.legacy.widget.Space
android:id="@+id/cutout_space"
android:layout_width="50dp"
android:layout_height="wrap_content" />
<com.habitrpg.android.habitica.ui.views.navigation.BottomNavigationItem

View file

@ -430,4 +430,12 @@
android:id="@+id/promoInfoFragment"
android:name="com.habitrpg.android.habitica.ui.fragments.PromoInfoFragment"
android:label="" />
<fragment
android:id="@+id/teamBoardFragment"
android:name="com.habitrpg.android.habitica.ui.fragments.tasks.TeamBoardFragment"
android:label="TeamBoardFragment" >
<argument
android:name="teamID"
app:argType="string" />
</fragment>
</navigation>

View file

@ -1136,4 +1136,5 @@
<string name="three_months_one_time">3 month one-time subscription</string>
<string name="six_months_one_time">6 month one-time subscription</string>
<string name="twelve_months_one_time">12 month one-time subscription</string>
<string name="sidebar_teams">Teams</string>
</resources>

View file

@ -392,4 +392,12 @@ interface ApiService {
@POST("user/reroll")
fun reroll(): Flowable<HabitResponse<User>>
// Team Plans
@GET("group-plans")
fun getTeamPlans(): Flowable<HabitResponse<List<TeamPlan>>>
@GET("tasks/group/{groupID}")
fun getTeamPlanTasks(@Path("groupID") groupId: String): Flowable<HabitResponse<TaskList>>
}

View file

@ -97,6 +97,7 @@ import com.habitrpg.android.habitica.ui.fragments.support.FAQOverviewFragment;
import com.habitrpg.android.habitica.ui.fragments.support.SupportMainFragment;
import com.habitrpg.android.habitica.ui.fragments.tasks.TaskRecyclerViewFragment;
import com.habitrpg.android.habitica.ui.fragments.tasks.TasksFragment;
import com.habitrpg.android.habitica.ui.fragments.tasks.TeamBoardFragment;
import com.habitrpg.android.habitica.ui.viewmodels.GroupViewModel;
import com.habitrpg.android.habitica.ui.viewmodels.InboxViewModel;
import com.habitrpg.android.habitica.ui.viewmodels.NotificationsViewModel;
@ -337,4 +338,6 @@ public interface UserComponent {
void inject(AdventureGuideActivity adventureGuideFragment);
void inject(PromoInfoFragment promoInfoFragment);
void inject(@NotNull TeamBoardFragment teamBoardFragment);
}

View file

@ -262,4 +262,6 @@ interface ApiClient {
fun transferGems(giftedID: String, amount: Int): Flowable<Void>
fun unlinkAllTasks(challengeID: String?, keepOption: String): Flowable<Void>
fun blockMember(userID: String): Flowable<List<String>>
fun getTeamPlans(): Flowable<List<TeamPlan>>
fun getTeamPlanTasks(teamID: String): Flowable<TaskList>
}

View file

@ -4,11 +4,13 @@ import com.habitrpg.android.habitica.data.local.UserQuestStatus
import com.habitrpg.android.habitica.models.Achievement
import com.habitrpg.android.habitica.models.QuestAchievement
import com.habitrpg.android.habitica.models.Skill
import com.habitrpg.android.habitica.models.TeamPlan
import com.habitrpg.android.habitica.models.inventory.Customization
import com.habitrpg.android.habitica.models.inventory.CustomizationSet
import com.habitrpg.android.habitica.models.responses.SkillResponse
import com.habitrpg.android.habitica.models.responses.UnlockResponse
import com.habitrpg.android.habitica.models.responses.VerifyUsernameResponse
import com.habitrpg.android.habitica.models.social.Group
import com.habitrpg.android.habitica.models.tasks.Task
import com.habitrpg.android.habitica.models.user.Stats
import com.habitrpg.android.habitica.models.user.User
@ -81,4 +83,8 @@ interface UserRepository : BaseRepository {
fun getUserQuestStatus(): Flowable<UserQuestStatus>
fun reroll(): Flowable<User>
fun retrieveTeamPlans(): Flowable<List<TeamPlan>>
fun getTeamPlans(): Flowable<RealmResults<TeamPlan>>
fun retrieveTeamPlan(teamID: String): Flowable<Group>
fun getTeamPlan(teamID: String): Flowable<Group>
}

View file

@ -780,6 +780,14 @@ class ApiClientImpl//private OnHabitsAPIResult mResultListener;
return apiService.transferGems(mapOf(Pair("toUserId", giftedID), Pair("gemAmount", amount))).compose(configureApiCallObserver())
}
override fun getTeamPlans(): Flowable<List<TeamPlan>> {
return apiService.getTeamPlans().compose(configureApiCallObserver())
}
override fun getTeamPlanTasks(teamID: String): Flowable<TaskList> {
return apiService.getTeamPlanTasks(teamID).compose(configureApiCallObserver())
}
override fun bulkAllocatePoints(strength: Int, intelligence: Int, constitution: Int, perception: Int): Flowable<Stats> {
val body = HashMap<String, Map<String, Int>>()
val stats = HashMap<String, Int>()

View file

@ -35,8 +35,8 @@ class TaskRepositoryImpl(localRepository: TaskLocalRepository, apiClient: ApiCli
override fun getCurrentUserTasks(taskType: String): Flowable<RealmResults<Task>> =
this.localRepository.getTasks(taskType, userID)
override fun saveTasks(userId: String, order: TasksOrder, tasks: TaskList) {
localRepository.saveTasks(userId, order, tasks)
override fun saveTasks(ownerID: String, order: TasksOrder, tasks: TaskList) {
localRepository.saveTasks(ownerID, order, tasks)
}
override fun retrieveTasks(userId: String, tasksOrder: TasksOrder): Flowable<TaskList> {

View file

@ -10,11 +10,13 @@ import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.models.Achievement
import com.habitrpg.android.habitica.models.QuestAchievement
import com.habitrpg.android.habitica.models.Skill
import com.habitrpg.android.habitica.models.TeamPlan
import com.habitrpg.android.habitica.models.inventory.Customization
import com.habitrpg.android.habitica.models.inventory.CustomizationSet
import com.habitrpg.android.habitica.models.responses.SkillResponse
import com.habitrpg.android.habitica.models.responses.UnlockResponse
import com.habitrpg.android.habitica.models.responses.VerifyUsernameResponse
import com.habitrpg.android.habitica.models.social.Group
import com.habitrpg.android.habitica.models.tasks.Task
import com.habitrpg.android.habitica.models.user.Stats
import com.habitrpg.android.habitica.models.user.User
@ -340,6 +342,38 @@ class UserRepositoryImpl(localRepository: UserLocalRepository, apiClient: ApiCli
return localRepository.getQuestAchievements(userID)
}
override fun retrieveTeamPlans(): Flowable<List<TeamPlan>> {
return apiClient.getTeamPlans().doOnNext { teams ->
teams.forEach { it.userID = userID }
localRepository.save(teams)
}
}
override fun getTeamPlans(): Flowable<RealmResults<TeamPlan>> {
return localRepository.getTeamPlans(userID)
}
override fun retrieveTeamPlan(teamID: String): Flowable<Group> {
return Flowable.zip(apiClient.getGroup(teamID), apiClient.getTeamPlanTasks(teamID),
{ team, tasks ->
team.tasks = tasks
team
})
.doOnNext { localRepository.save(it) }
.doOnNext { 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)
}
}
}
override fun getTeamPlan(teamID: String): Flowable<Group> {
return localRepository.getTeamPlan(teamID)
}
private fun mergeUser(oldUser: User?, newUser: User): User {
if (oldUser == null || !oldUser.isValid) {
return oldUser ?: newUser

View file

@ -1,10 +1,8 @@
package com.habitrpg.android.habitica.data.local
import com.habitrpg.android.habitica.models.Achievement
import com.habitrpg.android.habitica.models.QuestAchievement
import com.habitrpg.android.habitica.models.Skill
import com.habitrpg.android.habitica.models.TutorialStep
import com.habitrpg.android.habitica.models.*
import com.habitrpg.android.habitica.models.social.ChatMessage
import com.habitrpg.android.habitica.models.social.Group
import com.habitrpg.android.habitica.models.user.User
import io.reactivex.rxjava3.core.Flowable
import io.realm.RealmResults
@ -32,4 +30,6 @@ interface UserLocalRepository : BaseLocalRepository {
fun getAchievements(): Flowable<RealmResults<Achievement>>
fun getQuestAchievements(userID: String): Flowable<RealmResults<QuestAchievement>>
fun getUserQuestStatus(userID: String): Flowable<UserQuestStatus>
fun getTeamPlans(userID: String): Flowable<RealmResults<TeamPlan>>
fun getTeamPlan(teamID: String): Flowable<Group>
}

View file

@ -37,21 +37,23 @@ class RealmTaskLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm),
.filter { it.isLoaded })
}
override fun saveTasks(userId: String, tasksOrder: TasksOrder, tasks: TaskList) {
override fun saveTasks(ownerID: String, tasksOrder: TasksOrder, tasks: TaskList) {
val sortedTasks = ArrayList<Task>()
sortedTasks.addAll(sortTasks(tasks.tasks, tasksOrder.habits))
sortedTasks.addAll(sortTasks(tasks.tasks, tasksOrder.dailys))
sortedTasks.addAll(sortTasks(tasks.tasks, tasksOrder.todos))
sortedTasks.addAll(sortTasks(tasks.tasks, tasksOrder.rewards))
removeOldTasks(userId, sortedTasks)
removeOldTasks(ownerID, sortedTasks)
val allChecklistItems = ArrayList<ChecklistItem>()
sortedTasks.forEach { it.checklist?.let { it1 -> allChecklistItems.addAll(it1) } }
removeOldChecklists(allChecklistItems)
val allReminders = ArrayList<RemindersItem>()
sortedTasks.forEach { it.reminders?.let { it1 -> allReminders.addAll(it1) } }
sortedTasks.forEach {
if (it.userId.isBlank()) it.userId = ownerID
it.checklist?.let { it1 -> allChecklistItems.addAll(it1) }
it.reminders?.let { it1 -> allReminders.addAll(it1) }
}
removeOldReminders(allReminders)
removeOldChecklists(allChecklistItems)
executeTransaction { realm1 -> realm1.insertOrUpdate(sortedTasks) }
}

View file

@ -53,7 +53,8 @@ class RealmUserLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm),
override fun getTutorialSteps(): Flowable<RealmResults<TutorialStep>> = RxJavaBridge.toV3Flowable(realm.where(TutorialStep::class.java).findAll().asFlowable()
.filter { it.isLoaded })
override fun getUser(userID: String): Flowable<User> {
override fun getUser(userID: String): Flowable<User> {
if (realm.isClosed) return Flowable.empty()
return RxJavaBridge.toV3Flowable(realm.where(User::class.java)
.equalTo("id", userID)
@ -134,6 +135,24 @@ override fun getUser(userID: String): Flowable<User> {
}
}
override fun getTeamPlans(userID: String): Flowable<RealmResults<TeamPlan>> {
return RxJavaBridge.toV3Flowable(realm.where(TeamPlan::class.java)
.equalTo("userID", userID)
.findAll()
.asFlowable()
.filter { it.isLoaded })
}
override fun getTeamPlan(teamID: String): Flowable<Group> {
if (realm.isClosed) return Flowable.empty()
return RxJavaBridge.toV3Flowable(realm.where(Group::class.java)
.equalTo("id", teamID)
.findAll()
.asFlowable()
.filter { realmObject -> realmObject.isLoaded && realmObject.isValid && !realmObject.isEmpty() }
.map { teams -> teams.first() })
}
override fun getSkills(user: User): Flowable<RealmResults<Skill>> {
val habitClass = if (user.preferences?.disableClasses == true) "none" else user.stats?.habitClass
return RxJavaBridge.toV3Flowable(realm.where(Skill::class.java)

View file

@ -21,12 +21,11 @@ open class Tag : RealmObject() {
}
override fun equals(o: Any?): Boolean {
if (Tag::class.java.isAssignableFrom(o!!.javaClass)) {
val otherTag = o as Tag?
return this.id == otherTag!!.id
override fun equals(other: Any?): Boolean {
if (other is Tag) {
return this.id == other.id
}
return super.equals(o)
return super.equals(other)
}
override fun hashCode(): Int {

View file

@ -0,0 +1,29 @@
package com.habitrpg.android.habitica.models
import com.google.gson.annotations.SerializedName
import io.realm.RealmList
import io.realm.RealmObject
import io.realm.annotations.PrimaryKey
open class TeamPlan : RealmObject() {
@PrimaryKey
var id: String = ""
var userID: String? = null
var summary: String = ""
@SerializedName("leader")
var leaderID: String? = null
//var managers: RealmList<String> = RealmList()
var isActive: Boolean = false
override fun equals(other: Any?): Boolean {
if (other is TeamPlan) {
return this.id == other.id
}
return super.equals(other)
}
override fun hashCode(): Int {
return id.hashCode()
}
}

View file

@ -3,10 +3,13 @@ package com.habitrpg.android.habitica.models.social
import com.google.gson.annotations.SerializedName
import com.habitrpg.android.habitica.models.BaseObject
import com.habitrpg.android.habitica.models.inventory.Quest
import com.habitrpg.android.habitica.models.tasks.TaskList
import com.habitrpg.android.habitica.models.tasks.TasksOrder
import com.habitrpg.android.habitica.models.user.User
import io.realm.RealmList
import io.realm.RealmObject
import io.realm.annotations.Ignore
import io.realm.annotations.PrimaryKey
open class Group : RealmObject(), BaseObject {
@ -38,6 +41,12 @@ open class Group : RealmObject(), BaseObject {
var leaderOnlyChallenges: Boolean = false
var leaderOnlyGetGems: Boolean = false
@Ignore
var tasksOrder: TasksOrder? = null
@Ignore
var tasks: TaskList? = null
override fun equals(other: Any?): Boolean {
if (this === other) {
return true

View file

@ -626,6 +626,7 @@ open class MainActivity : BaseActivity(), TutorialView.OnTutorialReaction {
pushNotificationManager.setUser(user1)
pushNotificationManager.addPushDeviceUsingStoredToken()
}
.flatMap { userRepository.retrieveTeamPlans() }
.flatMap { contentRepository.retrieveContent(this,false) }
.flatMap { contentRepository.retrieveWorldState(this) }
.subscribe({ }, RxErrorHandler.handleEmptyError()))

View file

@ -4,11 +4,14 @@ import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.core.os.bundleOf
import androidx.recyclerview.widget.RecyclerView
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.extensions.dpToPx
import com.habitrpg.android.habitica.extensions.inflate
import com.habitrpg.android.habitica.models.TeamPlan
import com.habitrpg.android.habitica.models.promotions.HabiticaPromotion
import com.habitrpg.android.habitica.ui.fragments.NavigationDrawerFragment
import com.habitrpg.android.habitica.ui.menu.HabiticaDrawerItem
import com.habitrpg.android.habitica.ui.views.adventureGuide.AdventureGuideMenuBanner
import com.habitrpg.android.habitica.ui.views.promo.PromoMenuView
@ -66,6 +69,30 @@ class NavigationDrawerAdapter(tintColor: Int, backgroundTintColor: Int): Recycle
notifyDataSetChanged()
}
fun setTeams(teams: List<TeamPlan>) {
var teamHeaderIndex = -1
var nextHeaderIndex = -1
for ((index, item) in items.withIndex()) {
if (teamHeaderIndex != -1 && item.isHeader) {
nextHeaderIndex = index
break
} else if (item.identifier == NavigationDrawerFragment.SIDEBAR_TEAMS) {
teamHeaderIndex = index
}
}
if (teamHeaderIndex != -1 && nextHeaderIndex != -1) {
for (x in nextHeaderIndex-1 downTo teamHeaderIndex+1) {
items.removeAt(x)
}
for ((index, team) in teams.withIndex()) {
val item = HabiticaDrawerItem(R.id.teamBoardFragment, team.id, team.summary)
item.bundle = bundleOf(Pair("teamID", team.id))
items.add(teamHeaderIndex + index + 1, item)
}
}
notifyDataSetChanged()
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val drawerItem = getItem(position)
when {

View file

@ -20,8 +20,8 @@ import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.subjects.PublishSubject
import io.realm.OrderedRealmCollection
class RewardsRecyclerViewAdapter(private var customRewards: OrderedRealmCollection<Task>?, private val layoutResource: Int, user: User?) : BaseRecyclerViewAdapter<Task, RecyclerView.ViewHolder>(), TaskRecyclerViewAdapter {
var user = user
class RewardsRecyclerViewAdapter(private var customRewards: OrderedRealmCollection<Task>?, private val layoutResource: Int) : BaseRecyclerViewAdapter<Task, RecyclerView.ViewHolder>(), TaskRecyclerViewAdapter {
var user: User? = null
set(value) {
field = value
notifyDataSetChanged()

View file

@ -185,6 +185,13 @@ class NavigationDrawerFragment : DialogFragment() {
questContent = it
}, RxErrorHandler.handleEmptyError()))
subscriptions?.add(userRepository.getTeamPlans()
.distinctUntilChanged { firstTeams, secondTeams -> firstTeams == secondTeams }
.subscribe( {
getItemWithIdentifier(SIDEBAR_TEAMS)?.isVisible = it.size != 0
adapter.setTeams(it)
}, RxErrorHandler.handleEmptyError()))
subscriptions?.add(userRepository.getUser().subscribe({
updateUser(it)
}, RxErrorHandler.handleEmptyError()))
@ -313,6 +320,8 @@ class NavigationDrawerFragment : DialogFragment() {
items.add(HabiticaDrawerItem(R.id.statsFragment, SIDEBAR_STATS, context.getString(R.string.sidebar_stats)))
items.add(HabiticaDrawerItem(R.id.achievementsFragment, SIDEBAR_ACHIEVEMENTS, context.getString(R.string.sidebar_achievements)))
items.add(HabiticaDrawerItem(0, SIDEBAR_TEAMS, context.getString(R.string.sidebar_teams), true))
items.add(HabiticaDrawerItem(0, SIDEBAR_INVENTORY, context.getString(R.string.sidebar_shops), true))
items.add(HabiticaDrawerItem(R.id.marketFragment, SIDEBAR_SHOPS_MARKET, context.getString(R.string.market)))
items.add(HabiticaDrawerItem(R.id.questShopFragment, SIDEBAR_SHOPS_QUEST, context.getString(R.string.questShop)))
@ -325,11 +334,11 @@ class NavigationDrawerFragment : DialogFragment() {
items.add(HabiticaDrawerItem(R.id.stableFragment, SIDEBAR_STABLE, context.getString(R.string.sidebar_stable)))
items.add(HabiticaDrawerItem(R.id.avatarOverviewFragment, SIDEBAR_AVATAR, context.getString(R.string.sidebar_avatar)))
items.add(HabiticaDrawerItem(R.id.gemPurchaseActivity, SIDEBAR_GEMS, context.getString(R.string.sidebar_gems)))
items.add(HabiticaDrawerItem(R.id.subscriptionPurchaseActivity, SIDEBAR_SUBSCRIPTION, context.getString(R.string.sidebar_subscription), isHeader = false))
items.add(HabiticaDrawerItem(R.id.subscriptionPurchaseActivity, SIDEBAR_SUBSCRIPTION, context.getString(R.string.sidebar_subscription)))
items.add(HabiticaDrawerItem(0, SIDEBAR_SOCIAL, context.getString(R.string.sidebar_section_social), true))
items.add(HabiticaDrawerItem(R.id.partyFragment, SIDEBAR_PARTY, context.getString(R.string.sidebar_party)))
items.add(HabiticaDrawerItem(R.id.tavernFragment, SIDEBAR_TAVERN, context.getString(R.string.sidebar_tavern), isHeader = false))
items.add(HabiticaDrawerItem(R.id.tavernFragment, SIDEBAR_TAVERN, context.getString(R.string.sidebar_tavern)))
items.add(HabiticaDrawerItem(R.id.guildsOverviewFragment, SIDEBAR_GUILDS, context.getString(R.string.sidebar_guilds)))
items.add(HabiticaDrawerItem(R.id.challengesOverviewFragment, SIDEBAR_CHALLENGES, context.getString(R.string.sidebar_challenges)))
@ -355,7 +364,7 @@ class NavigationDrawerFragment : DialogFragment() {
fun setSelection(transitionId: Int?, bundle: Bundle? = null, openSelection: Boolean = true, preventReselection: Boolean = true) {
closeDrawer()
if (adapter.selectedItem != null && adapter.selectedItem == transitionId && preventReselection) return
if (adapter.selectedItem != null && adapter.selectedItem == transitionId && bundle == null && preventReselection) return
adapter.selectedItem = transitionId
if (!openSelection) {
@ -517,6 +526,7 @@ class NavigationDrawerFragment : DialogFragment() {
const val SIDEBAR_SKILLS = "skills"
const val SIDEBAR_STATS = "stats"
const val SIDEBAR_ACHIEVEMENTS = "achievements"
const val SIDEBAR_TEAMS = "teams"
const val SIDEBAR_SOCIAL = "social"
const val SIDEBAR_TAVERN = "tavern"
const val SIDEBAR_PARTY = "party"

View file

@ -25,6 +25,7 @@ import java.util.*
class RewardsRecyclerviewFragment : TaskRecyclerViewFragment() {
private var showCustomRewards: Boolean = true
private var selectedCard: ShopItem? = null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
@ -51,9 +52,11 @@ class RewardsRecyclerviewFragment : TaskRecyclerViewFragment() {
}
binding?.recyclerView?.itemAnimator = SafeDefaultItemAnimator()
compositeSubscription.add(inventoryRepository.getInAppRewards().subscribe({
(recyclerAdapter as? RewardsRecyclerViewAdapter)?.updateItemRewards(it)
}, RxErrorHandler.handleEmptyError()))
if (showCustomRewards) {
compositeSubscription.add(inventoryRepository.getInAppRewards().subscribe({
(recyclerAdapter as? RewardsRecyclerViewAdapter)?.updateItemRewards(it)
}, RxErrorHandler.handleEmptyError()))
}
(recyclerAdapter as? RewardsRecyclerViewAdapter)?.purchaseCardEvents?.subscribe({
selectedCard = it
@ -100,7 +103,7 @@ class RewardsRecyclerviewFragment : TaskRecyclerViewFragment() {
when (requestCode) {
11 -> {
if (resultCode == Activity.RESULT_OK) {
userRepository.useSkill(user,
userRepository.useSkill(null,
selectedCard?.key ?: "",
"member",
data.getStringExtra("member_id") ?: "")
@ -118,11 +121,11 @@ class RewardsRecyclerviewFragment : TaskRecyclerViewFragment() {
companion object {
fun newInstance(context: Context?, user: User?, classType: String): RewardsRecyclerviewFragment {
fun newInstance(context: Context?, classType: String, showCustomRewards: Boolean): RewardsRecyclerviewFragment {
val fragment = RewardsRecyclerviewFragment()
fragment.retainInstance = true
fragment.user = user
fragment.classType = classType
fragment.showCustomRewards = showCustomRewards
if (context != null) {
fragment.tutorialStepIdentifier = "rewards"

View file

@ -1,7 +1,6 @@
package com.habitrpg.android.habitica.ui.fragments.tasks
import android.content.Context
import android.content.Intent
import android.graphics.drawable.BitmapDrawable
import android.os.Bundle
import android.util.TypedValue
@ -31,7 +30,6 @@ import com.habitrpg.android.habitica.models.tasks.Task
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.modules.AppModule
import com.habitrpg.android.habitica.ui.activities.MainActivity
import com.habitrpg.android.habitica.ui.activities.TaskFormActivity
import com.habitrpg.android.habitica.ui.adapter.tasks.*
import com.habitrpg.android.habitica.ui.fragments.BaseFragment
import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator
@ -54,8 +52,7 @@ open class TaskRecyclerViewFragment : BaseFragment<FragmentRefreshRecyclerviewBi
var recyclerAdapter: TaskRecyclerViewAdapter? = null
var itemAnimator = SafeDefaultItemAnimator()
@field:[Inject Named(AppModule.NAMED_USER_ID)]
lateinit var userID: String
var ownerID: String = ""
@Inject
lateinit var apiClient: ApiClient
@Inject
@ -74,9 +71,10 @@ open class TaskRecyclerViewFragment : BaseFragment<FragmentRefreshRecyclerviewBi
internal var layoutManager: RecyclerView.LayoutManager? = null
internal var classType: String? = null
internal var user: User? = null
private var itemTouchCallback: ItemTouchHelper.Callback? = null
var refreshAction: ((() -> Unit) -> Unit)? = null
internal val className: String
get() = this.classType ?: ""
@ -93,7 +91,7 @@ open class TaskRecyclerViewFragment : BaseFragment<FragmentRefreshRecyclerviewBi
TodosRecyclerViewAdapter(null, true, R.layout.todo_item_card, taskFilterHelper)
}
Task.TYPE_REWARD -> {
RewardsRecyclerViewAdapter(null, R.layout.reward_item_card, user)
RewardsRecyclerViewAdapter(null, R.layout.reward_item_card)
}
else -> null
}
@ -161,9 +159,9 @@ open class TaskRecyclerViewFragment : BaseFragment<FragmentRefreshRecyclerviewBi
when (classType) {
Task.TYPE_TODO -> taskFilterHelper.setActiveFilter(Task.TYPE_TODO, Task.FILTER_ACTIVE)
Task.TYPE_DAILY -> {
if (user?.isValid == true && user?.preferences?.dailyDueDefaultView == true) {
/*if (user?.isValid == true && user?.preferences?.dailyDueDefaultView == true) {
taskFilterHelper.setActiveFilter(Task.TYPE_DAILY, Task.FILTER_ACTIVE)
}
}*/
}
}
@ -256,7 +254,7 @@ open class TaskRecyclerViewFragment : BaseFragment<FragmentRefreshRecyclerviewBi
}?.subscribeWithErrorHandler {}?.let { compositeSubscription.add(it) }
recyclerAdapter?.brokenTaskEvents?.subscribeWithErrorHandler { showBrokenChallengeDialog(it) }?.let { compositeSubscription.add(it) }
compositeSubscription.add(taskRepository.getTasks(this.classType ?: "", userID).subscribe({
compositeSubscription.add(taskRepository.getTasks(this.classType ?: "", ownerID).subscribe({
this.recyclerAdapter?.updateUnfilteredData(it)
this.recyclerAdapter?.filter()
}, RxErrorHandler.handleEmptyError()))
@ -280,7 +278,7 @@ open class TaskRecyclerViewFragment : BaseFragment<FragmentRefreshRecyclerviewBi
setEmptyLabels()
if (Task.TYPE_REWARD == className) {
compositeSubscription.add(taskRepository.getTasks(this.className, userID)
compositeSubscription.add(taskRepository.getTasks(this.className, ownerID)
.subscribe({ recyclerAdapter?.data = it }, RxErrorHandler.handleEmptyError()))
}
}
@ -366,7 +364,7 @@ open class TaskRecyclerViewFragment : BaseFragment<FragmentRefreshRecyclerviewBi
}
private fun scoreTask(task: Task, direction: TaskDirection) {
compositeSubscription.add(taskRepository.taskChecked(user, task, direction == TaskDirection.UP, false) { result ->
compositeSubscription.add(taskRepository.taskChecked(null, task, direction == TaskDirection.UP, false) { result ->
handleTaskResult(result, task.value.toInt())
}.subscribeWithErrorHandler {})
}
@ -381,10 +379,9 @@ open class TaskRecyclerViewFragment : BaseFragment<FragmentRefreshRecyclerviewBi
override fun onRefresh() {
binding?.refreshLayout?.isRefreshing = true
compositeSubscription.add(userRepository.retrieveUser(true, true)
.doOnTerminate {
binding?.refreshLayout?.isRefreshing = false
}.subscribe({ }, RxErrorHandler.handleEmptyError()))
refreshAction?.invoke {
binding?.refreshLayout?.isRefreshing = false
}
}
override fun onResume() {
@ -399,35 +396,19 @@ open class TaskRecyclerViewFragment : BaseFragment<FragmentRefreshRecyclerviewBi
setEmptyLabels()
if (activeFilter == Task.FILTER_COMPLETED) {
compositeSubscription.add(taskRepository.retrieveCompletedTodos(userID).subscribe({}, RxErrorHandler.handleEmptyError()))
compositeSubscription.add(taskRepository.retrieveCompletedTodos(ownerID).subscribe({}, RxErrorHandler.handleEmptyError()))
}
}
private fun openTaskForm(task: Task) {
if (Date().time - (TasksFragment.lastTaskFormOpen?.time ?: 0) < 2000 || !task.isValid) {
return
}
val bundle = Bundle()
bundle.putString(TaskFormActivity.TASK_TYPE_KEY, task.type)
bundle.putString(TaskFormActivity.TASK_ID_KEY, task.id)
bundle.putDouble(TaskFormActivity.TASK_VALUE_KEY, task.value)
val intent = Intent(activity, TaskFormActivity::class.java)
intent.putExtras(bundle)
TasksFragment.lastTaskFormOpen = Date()
if (isAdded) {
startActivityForResult(intent, TasksFragment.TASK_UPDATED_RESULT)
}
}
companion object {
private const val CLASS_TYPE_KEY = "CLASS_TYPE_KEY"
fun newInstance(context: Context?, user: User?, classType: String): TaskRecyclerViewFragment {
fun newInstance(context: Context?, classType: String): TaskRecyclerViewFragment {
val fragment = TaskRecyclerViewFragment()
fragment.retainInstance = true
fragment.user = user
fragment.classType = classType
var tutorialTexts: List<String>? = null
if (context != null) {

View file

@ -23,6 +23,7 @@ import com.habitrpg.android.habitica.models.tasks.Task
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.ui.activities.TaskFormActivity
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
import com.habitrpg.android.habitica.ui.views.navigation.HabiticaBottomNavigationViewListener
import com.habitrpg.android.habitica.ui.views.tasks.TaskFilterDialog
import io.reactivex.rxjava3.disposables.Disposable
import java.util.*
@ -30,7 +31,7 @@ import javax.inject.Inject
import kotlin.collections.ArrayList
class TasksFragment : BaseMainFragment<FragmentViewpagerBinding>(), SearchView.OnQueryTextListener {
class TasksFragment : BaseMainFragment<FragmentViewpagerBinding>(), SearchView.OnQueryTextListener, HabiticaBottomNavigationViewListener {
override var binding: FragmentViewpagerBinding? = null
@ -50,13 +51,6 @@ class TasksFragment : BaseMainFragment<FragmentViewpagerBinding>(), SearchView.O
private var filterMenuItem: MenuItem? = null
override var user: User?
get() = super.user
set(value) {
super.user = value
viewFragmentsDictionary?.values?.forEach { it.user = value }
}
private val activeFragment: TaskRecyclerViewFragment?
get() {
var fragment = viewFragmentsDictionary?.get(binding?.viewPager?.currentItem)
@ -89,26 +83,14 @@ class TasksFragment : BaseMainFragment<FragmentViewpagerBinding>(), SearchView.O
3 -> Task.TYPE_REWARD
else -> Task.TYPE_HABIT
}
bottomNavigation?.onTabSelectedListener = {
val newItem = when (it) {
Task.TYPE_HABIT -> 0
Task.TYPE_DAILY -> 1
Task.TYPE_TODO -> 2
Task.TYPE_REWARD -> 3
else -> 0
}
binding?.viewPager?.currentItem = newItem
updateBottomBarBadges()
}
bottomNavigation?.onAddListener = { type ->
openNewTaskActivity(type)
}
bottomNavigation?.flipAddBehaviour = appConfigManager.flipAddTaskBehaviour()
bottomNavigation?.listener = this
bottomNavigation?.canAddTasks = true
}
override fun onPause() {
bottomNavigation?.onTabSelectedListener = null
bottomNavigation?.onAddListener = null
if (bottomNavigation?.listener == this) {
bottomNavigation?.listener = null
}
super.onPause()
}
@ -220,12 +202,18 @@ class TasksFragment : BaseMainFragment<FragmentViewpagerBinding>(), SearchView.O
override fun getItem(position: Int): androidx.fragment.app.Fragment {
val fragment: TaskRecyclerViewFragment = when (position) {
0 -> TaskRecyclerViewFragment.newInstance(context, user, Task.TYPE_HABIT)
1 -> TaskRecyclerViewFragment.newInstance(context, user, Task.TYPE_DAILY)
3 -> RewardsRecyclerviewFragment.newInstance(context, user, Task.TYPE_REWARD)
else -> TaskRecyclerViewFragment.newInstance(context, user, Task.TYPE_TODO)
0 -> TaskRecyclerViewFragment.newInstance(context, Task.TYPE_HABIT)
1 -> TaskRecyclerViewFragment.newInstance(context, Task.TYPE_DAILY)
3 -> RewardsRecyclerviewFragment.newInstance(context, Task.TYPE_REWARD)
else -> TaskRecyclerViewFragment.newInstance(context, Task.TYPE_TODO)
}
fragment.ownerID = user?.id ?: ""
fragment.refreshAction = {
compositeSubscription.add(userRepository.retrieveUser(true, true)
.doOnTerminate {
it()
}.subscribe({ }, RxErrorHandler.handleEmptyError()))
}
viewFragmentsDictionary?.put(position, fragment)
return fragment
@ -412,4 +400,21 @@ class TasksFragment : BaseMainFragment<FragmentViewpagerBinding>(), SearchView.O
internal const val TASK_CREATED_RESULT = 1
const val TASK_UPDATED_RESULT = 2
}
override fun onTabSelected(taskType: String) {
val newItem = when (taskType) {
Task.TYPE_HABIT -> 0
Task.TYPE_DAILY -> 1
Task.TYPE_TODO -> 2
Task.TYPE_REWARD -> 3
else -> 0
}
binding?.viewPager?.currentItem = newItem
updateBottomBarBadges()
}
override fun onAdd(taskType: String) {
openNewTaskActivity(taskType)
}
}

View file

@ -0,0 +1,435 @@
package com.habitrpg.android.habitica.ui.fragments.tasks
import android.app.Activity
import android.content.Intent
import android.graphics.PorterDuff
import android.os.Bundle
import android.view.*
import androidx.appcompat.widget.SearchView
import androidx.core.content.ContextCompat
import androidx.fragment.app.FragmentPagerAdapter
import com.habitrpg.android.habitica.HabiticaBaseApplication
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.databinding.FragmentViewpagerBinding
import com.habitrpg.android.habitica.extensions.getThemeColor
import com.habitrpg.android.habitica.extensions.setTintWith
import com.habitrpg.android.habitica.helpers.AmplitudeManager
import com.habitrpg.android.habitica.helpers.AppConfigManager
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.helpers.TaskFilterHelper
import com.habitrpg.android.habitica.models.tasks.Task
import com.habitrpg.android.habitica.ui.activities.TaskFormActivity
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
import com.habitrpg.android.habitica.ui.views.navigation.HabiticaBottomNavigationViewListener
import com.habitrpg.android.habitica.ui.views.tasks.TaskFilterDialog
import io.reactivex.rxjava3.disposables.Disposable
import java.util.*
import javax.inject.Inject
import kotlin.collections.ArrayList
class TeamBoardFragment : BaseMainFragment<FragmentViewpagerBinding>(), SearchView.OnQueryTextListener, HabiticaBottomNavigationViewListener {
override var binding: FragmentViewpagerBinding? = null
override fun createBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentViewpagerBinding {
return FragmentViewpagerBinding.inflate(inflater, container, false)
}
var teamID: String = ""
@Inject
lateinit var taskFilterHelper: TaskFilterHelper
@Inject
lateinit var tagRepository: TagRepository
@Inject
lateinit var appConfigManager: AppConfigManager
private var refreshItem: MenuItem? = null
internal var viewFragmentsDictionary: MutableMap<Int, TaskRecyclerViewFragment>? = WeakHashMap()
private var filterMenuItem: MenuItem? = null
private val activeFragment: TaskRecyclerViewFragment?
get() {
var fragment = viewFragmentsDictionary?.get(binding?.viewPager?.currentItem)
if (fragment == null) {
if (isAdded) {
fragment = (childFragmentManager.findFragmentByTag("android:switcher:" + R.id.viewPager + ":" + binding?.viewPager?.currentItem) as? TaskRecyclerViewFragment)
}
}
return fragment
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
this.usesTabLayout = false
this.hidesToolbar = true
this.usesBottomNavigation = true
return super.onCreateView(inflater, container, savedInstanceState)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
arguments?.let {
val args = TeamBoardFragmentArgs.fromBundle(it)
teamID = args.teamID
}
compositeSubscription.add(userRepository.getTeamPlan(teamID)
.subscribe( {
activity?.title = it.name
}, RxErrorHandler.handleEmptyError()))
compositeSubscription.add(userRepository.retrieveTeamPlan(teamID).subscribe({ }, RxErrorHandler.handleEmptyError()))
loadTaskLists()
}
override fun onResume() {
super.onResume()
bottomNavigation?.activeTaskType = when (binding?.viewPager?.currentItem) {
0 -> Task.TYPE_HABIT
1 -> Task.TYPE_DAILY
2 -> Task.TYPE_TODO
3 -> Task.TYPE_REWARD
else -> Task.TYPE_HABIT
}
bottomNavigation?.listener = this
bottomNavigation?.canAddTasks = false
}
override fun onPause() {
if (bottomNavigation?.listener == this) {
bottomNavigation?.listener = null
}
super.onPause()
}
override fun onDestroy() {
tagRepository.close()
super.onDestroy()
}
override fun injectFragment(component: UserComponent) {
component.inject(this)
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.menu_main_activity, menu)
filterMenuItem = menu.findItem(R.id.action_filter)
updateFilterIcon()
val item = menu.findItem(R.id.action_search)
tintMenuIcon(item)
val sv = item.actionView as? SearchView
sv?.setOnQueryTextListener(this)
sv?.setIconifiedByDefault(false)
item.setOnActionExpandListener(object : MenuItem.OnActionExpandListener {
override fun onMenuItemActionCollapse(item: MenuItem): Boolean {
filterMenuItem?.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS)
return true
}
override fun onMenuItemActionExpand(item: MenuItem): Boolean {
// Do something when expanded
filterMenuItem?.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER)
return true
}
})
}
override fun onQueryTextSubmit(query: String?): Boolean {
return true
}
override fun onQueryTextChange(newText: String?): Boolean {
taskFilterHelper.searchQuery = newText
viewFragmentsDictionary?.values?.forEach { values -> values.recyclerAdapter?.filter() }
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.action_filter -> {
showFilterDialog()
true
}
R.id.action_reload -> {
refreshItem = item
refresh()
true
}
else -> super.onOptionsItemSelected(item)
}
}
private fun showFilterDialog() {
context?.let {
var disposable: Disposable? = null
val dialog = TaskFilterDialog(it, HabiticaBaseApplication.userComponent)
if (user != null) {
dialog.setTags(user?.tags?.createSnapshot() ?: emptyList())
disposable = tagRepository.getTags(user?.id ?: "").subscribe({ tagsList -> dialog.setTags(tagsList)}, RxErrorHandler.handleEmptyError())
}
dialog.setActiveTags(taskFilterHelper.tags)
if (activeFragment != null) {
val taskType = activeFragment?.classType
if (taskType != null) {
dialog.setTaskType(taskType, taskFilterHelper.getActiveFilter(taskType))
}
}
dialog.setListener(object : TaskFilterDialog.OnFilterCompletedListener {
override fun onFilterCompleted(activeTaskFilter: String?, activeTags: MutableList<String>) {
if (viewFragmentsDictionary == null) {
return
}
taskFilterHelper.tags = activeTags
if (activeTaskFilter != null) {
activeFragment?.setActiveFilter(activeTaskFilter)
}
viewFragmentsDictionary?.values?.forEach { values -> values.recyclerAdapter?.filter() }
updateFilterIcon()
}
})
dialog.setOnDismissListener {
if (disposable?.isDisposed == false) {
disposable.dispose()
}
}
dialog.show()
}
}
private fun refresh() {
activeFragment?.onRefresh()
}
private fun loadTaskLists() {
val fragmentManager = childFragmentManager
binding?.viewPager?.adapter = object : FragmentPagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
override fun getItem(position: Int): androidx.fragment.app.Fragment {
val fragment: TaskRecyclerViewFragment = when (position) {
0 -> TaskRecyclerViewFragment.newInstance(context, Task.TYPE_HABIT)
1 -> TaskRecyclerViewFragment.newInstance(context, Task.TYPE_DAILY)
3 -> RewardsRecyclerviewFragment.newInstance(context, Task.TYPE_REWARD)
else -> TaskRecyclerViewFragment.newInstance(context, Task.TYPE_TODO)
}
fragment.ownerID = teamID
fragment.refreshAction = {
compositeSubscription.add(userRepository.retrieveTeamPlan(teamID)
.doOnTerminate {
it()
}.subscribe({ }, RxErrorHandler.handleEmptyError()))
}
viewFragmentsDictionary?.put(position, fragment)
return fragment
}
override fun getCount(): Int = 4
override fun getPageTitle(position: Int): CharSequence? = when (position) {
0 -> activity?.getString(R.string.habits)
1 -> activity?.getString(R.string.dailies)
2 -> activity?.getString(R.string.todos)
3 -> activity?.getString(R.string.rewards)
else -> ""
}
}
binding?.viewPager?.addOnPageChangeListener(object : androidx.viewpager.widget.ViewPager.OnPageChangeListener {
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) { /* no-op */ }
override fun onPageSelected(position: Int) {
bottomNavigation?.selectedPosition = position
updateFilterIcon()
}
override fun onPageScrollStateChanged(state: Int) { /* no-op */ }
})
}
private fun updateFilterIcon() {
if (filterMenuItem == null) {
return
}
var filterCount = 0
if (activeFragment != null) {
filterCount = taskFilterHelper.howMany(activeFragment?.classType)
}
if (filterCount == 0) {
filterMenuItem?.setIcon(R.drawable.ic_action_filter_list)
context?.let {
val filterIcon = ContextCompat.getDrawable(it, R.drawable.ic_action_filter_list)
filterIcon?.setTintWith(it.getThemeColor(R.attr.headerTextColor), PorterDuff.Mode.MULTIPLY)
filterMenuItem?.setIcon(filterIcon)
}
} else {
context?.let {
val filterIcon = ContextCompat.getDrawable(it, R.drawable.ic_filters_active)
filterIcon?.setTintWith(it.getThemeColor(R.attr.textColorPrimaryDark), PorterDuff.Mode.MULTIPLY)
filterMenuItem?.setIcon(filterIcon)
}
}
}
private fun updateBottomBarBadges() {
if (bottomNavigation == null) {
return
}
compositeSubscription.add(tutorialRepository.getTutorialSteps(listOf("habits", "dailies", "todos", "rewards")).subscribe({ tutorialSteps ->
val activeTutorialFragments = ArrayList<String>()
for (step in tutorialSteps) {
var id = -1
val taskType = when (step.identifier) {
"habits" -> {
id = R.id.habits_tab
Task.TYPE_HABIT
}
"dailies" -> {
id = R.id.dailies_tab
Task.TYPE_DAILY
}
"todos" -> {
id = R.id.todos_tab
Task.TYPE_TODO
}
"rewards" -> {
id = R.id.rewards_tab
Task.TYPE_REWARD
}
else -> ""
}
val tab = bottomNavigation?.tabWithId(id)
if (step.shouldDisplay()) {
tab?.badgeCount = 1
activeTutorialFragments.add(taskType)
} else {
tab?.badgeCount = 0
}
}
if (activeTutorialFragments.size == 1) {
val fragment = viewFragmentsDictionary?.get(indexForTaskType(activeTutorialFragments[0]))
if (fragment?.tutorialTexts != null && context != null) {
val finalText = context?.getString(R.string.tutorial_tasks_complete)
if (!fragment.tutorialTexts.contains(finalText) && finalText != null) {
fragment.tutorialTexts.add(finalText)
}
}
}
}, RxErrorHandler.handleEmptyError()))
}
// endregion
private fun openNewTaskActivity(type: String) {
if (Date().time - (lastTaskFormOpen?.time ?: 0) < 2000) {
return
}
val additionalData = HashMap<String, Any>()
additionalData["created task type"] = type
additionalData["viewed task type"] = when (binding?.viewPager?.currentItem) {
0 -> Task.TYPE_HABIT
1 -> Task.TYPE_DAILY
2 -> Task.TYPE_TODO
3 -> Task.TYPE_REWARD
else -> ""
}
AmplitudeManager.sendEvent("open create task form", AmplitudeManager.EVENT_CATEGORY_BEHAVIOUR, AmplitudeManager.EVENT_HITTYPE_EVENT, additionalData)
val bundle = Bundle()
bundle.putString(TaskFormActivity.TASK_TYPE_KEY, type)
bundle.putStringArrayList(TaskFormActivity.SELECTED_TAGS_KEY, ArrayList(taskFilterHelper.tags))
val intent = Intent(activity, TaskFormActivity::class.java)
intent.putExtras(bundle)
intent.flags = Intent.FLAG_ACTIVITY_REORDER_TO_FRONT
if (this.isAdded) {
lastTaskFormOpen = Date()
startActivityForResult(intent, TASK_CREATED_RESULT)
}
}
//endregion Events
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
TASK_CREATED_RESULT -> {
onTaskCreatedResult(resultCode, data)
}
}
}
private fun onTaskCreatedResult(resultCode: Int, data: Intent?) {
if (resultCode == Activity.RESULT_OK) {
val taskType = data?.getStringExtra(TaskFormActivity.TASK_TYPE_KEY)
if (taskType != null) {
switchToTaskTab(taskType)
val index = indexForTaskType(taskType)
if (index != -1) {
val fragment = viewFragmentsDictionary?.get(index)
fragment?.binding?.recyclerView?.scrollToPosition(0)
}
}
}
}
private fun switchToTaskTab(taskType: String) {
val index = indexForTaskType(taskType)
if (binding?.viewPager != null && index != -1) {
binding?.viewPager?.currentItem = index
updateBottomBarBadges()
}
}
private fun indexForTaskType(taskType: String?): Int {
if (taskType != null) {
for (index in 0 until (viewFragmentsDictionary?.size ?: 0)) {
val fragment = viewFragmentsDictionary?.get(index)
if (fragment != null && taskType == fragment.className) {
return index
}
}
}
return -1
}
override val displayedClassName: String?
get() = null
override fun addToBackStack(): Boolean = false
companion object {
var lastTaskFormOpen: Date? = null
internal const val TASK_CREATED_RESULT = 1
const val TASK_UPDATED_RESULT = 2
}
override fun onTabSelected(taskType: String) {
val newItem = when (taskType) {
Task.TYPE_HABIT -> 0
Task.TYPE_DAILY -> 1
Task.TYPE_TODO -> 2
Task.TYPE_REWARD -> 3
else -> 0
}
binding?.viewPager?.currentItem = newItem
updateBottomBarBadges()
}
override fun onAdd(taskType: String) {
openNewTaskActivity(taskType)
}
}

View file

@ -5,6 +5,7 @@ import android.content.Context
import android.graphics.PorterDuff
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import android.view.animation.Animation
import android.view.animation.BounceInterpolator
import android.view.animation.LinearInterpolator
@ -20,12 +21,16 @@ import com.habitrpg.android.habitica.extensions.layoutInflater
import com.habitrpg.android.habitica.extensions.setTintWith
import com.habitrpg.android.habitica.models.tasks.Task
interface HabiticaBottomNavigationViewListener {
fun onTabSelected(taskType: String)
fun onAdd(taskType: String)
}
class HabiticaBottomNavigationView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : RelativeLayout(context, attrs, defStyleAttr) {
private val binding = MainNavigationViewBinding.inflate(context.layoutInflater, this)
var flipAddBehaviour = true
private var isShowingSubmenu: Boolean = false
var selectedPosition: Int
get() {
@ -44,15 +49,28 @@ class HabiticaBottomNavigationView @JvmOverloads constructor(
else -> Task.TYPE_HABIT
}
}
var onTabSelectedListener: ((String) -> Unit)? = null
var onAddListener: ((String) -> Unit)? = null
var listener: HabiticaBottomNavigationViewListener? = null
var activeTaskType: String = Task.TYPE_HABIT
set(value) {
val wasChanged = field != value
field = value
if (wasChanged) {
updateItemSelection()
onTabSelectedListener?.invoke(value)
listener?.onTabSelected(value)
}
}
var canAddTasks = true
set(value) {
field = value
if (field) {
binding.cutoutWrapper.visibility = View.VISIBLE
binding.cutoutSpace.visibility = View.VISIBLE
binding.addButtonBackground.visibility = View.VISIBLE
} else {
binding.cutoutWrapper.visibility = View.GONE
binding.cutoutSpace.visibility = View.GONE
binding.addButtonBackground.visibility = View.GONE
}
}
@ -65,23 +83,15 @@ class HabiticaBottomNavigationView @JvmOverloads constructor(
binding.todosTab.setOnClickListener { activeTaskType = Task.TYPE_TODO }
binding.rewardsTab.setOnClickListener { activeTaskType = Task.TYPE_REWARD }
binding.addButton.setOnClickListener {
if (flipAddBehaviour) {
if (isShowingSubmenu) {
hideSubmenu()
} else {
onAddListener?.invoke(activeTaskType)
}
if (isShowingSubmenu) {
hideSubmenu()
} else {
showSubmenu()
listener?.onAdd(activeTaskType)
}
animateButtonTap()
}
binding.addButton.setOnLongClickListener {
if (flipAddBehaviour) {
showSubmenu()
} else {
onAddListener?.invoke(activeTaskType)
}
showSubmenu()
animateButtonTap()
true
}
@ -169,7 +179,7 @@ class HabiticaBottomNavigationView @JvmOverloads constructor(
}
}
view.onAddListener = {
onAddListener?.invoke(taskType)
listener?.onAdd(taskType)
hideSubmenu()
}
binding.submenuWrapper.addView(view)

View file

@ -6,6 +6,7 @@ import com.habitrpg.android.habitica.models.inventory.Quest
import com.habitrpg.android.habitica.models.inventory.QuestRageStrike
import com.habitrpg.android.habitica.models.members.Member
import com.habitrpg.android.habitica.models.social.Group
import com.habitrpg.android.habitica.models.tasks.TasksOrder
import io.realm.Realm
import io.realm.RealmList
import java.lang.reflect.Type
@ -107,6 +108,11 @@ class GroupSerialization : JsonDeserializer<Group>, JsonSerializer<Group> {
group.leaderOnlyGetGems = leaderOnly.get("getGems").asBoolean
}
}
if (obj.has("tasksOrder")) {
group.tasksOrder = context.deserialize(obj.get("tasksOrder"), TasksOrder::class.java)
}
return group
}