diff --git a/Habitica/res/layout/main_navigation_view.xml b/Habitica/res/layout/main_navigation_view.xml index 1e01e4e0e..d6572f3e0 100644 --- a/Habitica/res/layout/main_navigation_view.xml +++ b/Habitica/res/layout/main_navigation_view.xml @@ -21,6 +21,7 @@ android:layout_height="match_parent" android:background="?barColor" /> @@ -68,6 +69,7 @@ android:paddingTop="@dimen/spacing_small" android:paddingBottom="@dimen/spacing_small"/> + + + \ No newline at end of file diff --git a/Habitica/res/values/strings.xml b/Habitica/res/values/strings.xml index 46d3f5176..41d36443a 100644 --- a/Habitica/res/values/strings.xml +++ b/Habitica/res/values/strings.xml @@ -1136,4 +1136,5 @@ 3 month one-time subscription 6 month one-time subscription 12 month one-time subscription + Teams diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/api/ApiService.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/api/ApiService.kt index 55131a5f2..d299f51b3 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/api/ApiService.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/api/ApiService.kt @@ -392,4 +392,12 @@ interface ApiService { @POST("user/reroll") fun reroll(): Flowable> + + // Team Plans + + @GET("group-plans") + fun getTeamPlans(): Flowable>> + + @GET("tasks/group/{groupID}") + fun getTeamPlanTasks(@Path("groupID") groupId: String): Flowable> } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/components/UserComponent.java b/Habitica/src/main/java/com/habitrpg/android/habitica/components/UserComponent.java index bb4f665cc..96ff76300 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/components/UserComponent.java +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/components/UserComponent.java @@ -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); } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/ApiClient.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/ApiClient.kt index e4e31eff4..b8de6d940 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/ApiClient.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/ApiClient.kt @@ -262,4 +262,6 @@ interface ApiClient { fun transferGems(giftedID: String, amount: Int): Flowable fun unlinkAllTasks(challengeID: String?, keepOption: String): Flowable fun blockMember(userID: String): Flowable> + fun getTeamPlans(): Flowable> + fun getTeamPlanTasks(teamID: String): Flowable } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/UserRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/UserRepository.kt index f920a9692..b837bea32 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/UserRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/UserRepository.kt @@ -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 fun reroll(): Flowable + fun retrieveTeamPlans(): Flowable> + fun getTeamPlans(): Flowable> + fun retrieveTeamPlan(teamID: String): Flowable + fun getTeamPlan(teamID: String): Flowable } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ApiClientImpl.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ApiClientImpl.kt index f79c174f4..a47d6c05e 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ApiClientImpl.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ApiClientImpl.kt @@ -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> { + return apiService.getTeamPlans().compose(configureApiCallObserver()) + } + + override fun getTeamPlanTasks(teamID: String): Flowable { + return apiService.getTeamPlanTasks(teamID).compose(configureApiCallObserver()) + } + override fun bulkAllocatePoints(strength: Int, intelligence: Int, constitution: Int, perception: Int): Flowable { val body = HashMap>() val stats = HashMap() diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/TaskRepositoryImpl.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/TaskRepositoryImpl.kt index c1cb335bf..b1939df49 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/TaskRepositoryImpl.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/TaskRepositoryImpl.kt @@ -35,8 +35,8 @@ class TaskRepositoryImpl(localRepository: TaskLocalRepository, apiClient: ApiCli override fun getCurrentUserTasks(taskType: String): Flowable> = 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 { 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 6734b555d..af89017cf 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 @@ -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> { + return apiClient.getTeamPlans().doOnNext { teams -> + teams.forEach { it.userID = userID } + localRepository.save(teams) + } + } + + override fun getTeamPlans(): Flowable> { + return localRepository.getTeamPlans(userID) + } + + override fun retrieveTeamPlan(teamID: String): Flowable { + 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 { + return localRepository.getTeamPlan(teamID) + } + private fun mergeUser(oldUser: User?, newUser: User): User { if (oldUser == null || !oldUser.isValid) { return oldUser ?: newUser diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/UserLocalRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/UserLocalRepository.kt index d42576902..508e2e775 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/UserLocalRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/UserLocalRepository.kt @@ -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> fun getQuestAchievements(userID: String): Flowable> fun getUserQuestStatus(userID: String): Flowable + fun getTeamPlans(userID: String): Flowable> + fun getTeamPlan(teamID: String): Flowable } 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 1f2fcda77..a6f92b7de 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 @@ -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() 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() - sortedTasks.forEach { it.checklist?.let { it1 -> allChecklistItems.addAll(it1) } } - removeOldChecklists(allChecklistItems) - val allReminders = ArrayList() - 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) } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmUserLocalRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmUserLocalRepository.kt index 5a2b168cf..aed8622af 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmUserLocalRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmUserLocalRepository.kt @@ -53,7 +53,8 @@ class RealmUserLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm), override fun getTutorialSteps(): Flowable> = RxJavaBridge.toV3Flowable(realm.where(TutorialStep::class.java).findAll().asFlowable() .filter { it.isLoaded }) -override fun getUser(userID: String): Flowable { + + override fun getUser(userID: String): Flowable { 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 { } } + override fun getTeamPlans(userID: String): Flowable> { + return RxJavaBridge.toV3Flowable(realm.where(TeamPlan::class.java) + .equalTo("userID", userID) + .findAll() + .asFlowable() + .filter { it.isLoaded }) + } + + override fun getTeamPlan(teamID: String): Flowable { + 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> { val habitClass = if (user.preferences?.disableClasses == true) "none" else user.stats?.habitClass return RxJavaBridge.toV3Flowable(realm.where(Skill::class.java) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/models/Tag.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/models/Tag.kt index 7601ea69a..845685c7d 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/models/Tag.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/models/Tag.kt @@ -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 { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/models/TeamPlan.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/models/TeamPlan.kt new file mode 100644 index 000000000..63158fc06 --- /dev/null +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/models/TeamPlan.kt @@ -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 = 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() + } +} 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 4d1ed7ab0..eb2785d9e 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 @@ -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 diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.kt index fac789fc8..7e4830b73 100755 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.kt @@ -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())) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/NavigationDrawerAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/NavigationDrawerAdapter.kt index 667035088..716d0099f 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/NavigationDrawerAdapter.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/NavigationDrawerAdapter.kt @@ -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) { + 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 { 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 b197f48ee..1a706f69c 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 @@ -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?, private val layoutResource: Int, user: User?) : BaseRecyclerViewAdapter(), TaskRecyclerViewAdapter { - var user = user +class RewardsRecyclerViewAdapter(private var customRewards: OrderedRealmCollection?, private val layoutResource: Int) : BaseRecyclerViewAdapter(), TaskRecyclerViewAdapter { + var user: User? = null set(value) { field = value notifyDataSetChanged() diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/NavigationDrawerFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/NavigationDrawerFragment.kt index 2b4d81cff..b7b2b768c 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/NavigationDrawerFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/NavigationDrawerFragment.kt @@ -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" diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/RewardsRecyclerviewFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/RewardsRecyclerviewFragment.kt index 9a112ded7..7adf2cc55 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/RewardsRecyclerviewFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/RewardsRecyclerviewFragment.kt @@ -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" 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 7c85e810b..4048e0a84 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 @@ -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 Unit) -> Unit)? = null + internal val className: String get() = this.classType ?: "" @@ -93,7 +91,7 @@ open class TaskRecyclerViewFragment : BaseFragment { - 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 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 + 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? = null if (context != null) { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TasksFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TasksFragment.kt index 76bc99460..33ddfab16 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TasksFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TasksFragment.kt @@ -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(), SearchView.OnQueryTextListener { +class TasksFragment : BaseMainFragment(), SearchView.OnQueryTextListener, HabiticaBottomNavigationViewListener { override var binding: FragmentViewpagerBinding? = null @@ -50,13 +51,6 @@ class TasksFragment : BaseMainFragment(), 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(), 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(), 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(), 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) + } } \ No newline at end of file diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TeamBoardFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TeamBoardFragment.kt new file mode 100644 index 000000000..a3d86c9fa --- /dev/null +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TeamBoardFragment.kt @@ -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(), 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? = 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) { + 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() + 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() + 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) + } +} \ No newline at end of file diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/navigation/HabiticaBottomNavigationView.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/navigation/HabiticaBottomNavigationView.kt index 2ec51fa70..b25498494 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/navigation/HabiticaBottomNavigationView.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/navigation/HabiticaBottomNavigationView.kt @@ -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) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/utils/GroupSerialization.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/utils/GroupSerialization.kt index b9e61c8cd..574283a73 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/utils/GroupSerialization.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/utils/GroupSerialization.kt @@ -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, JsonSerializer { group.leaderOnlyGetGems = leaderOnly.get("getGems").asBoolean } } + + if (obj.has("tasksOrder")) { + group.tasksOrder = context.deserialize(obj.get("tasksOrder"), TasksOrder::class.java) + } + return group }