diff --git a/Habitica/build.gradle b/Habitica/build.gradle index fc47a900d..af9c85fd1 100644 --- a/Habitica/build.gradle +++ b/Habitica/build.gradle @@ -150,7 +150,7 @@ android { buildConfigField "String", "TESTING_LEVEL", "\"production\"" multiDexEnabled true - versionCode 2140 + versionCode 2141 versionName "1.10" } diff --git a/Habitica/proguard-rules.pro b/Habitica/proguard-rules.pro index 1da078bc2..001931548 100644 --- a/Habitica/proguard-rules.pro +++ b/Habitica/proguard-rules.pro @@ -63,8 +63,8 @@ -keep class * implements com.google.gson.JsonSerializer -keep class * implements com.google.gson.JsonDeserializer -#keep models --keep class com.habitrpg.android.habitica.models.** { *; } +#keep Habitica code +-keep class com.habitrpg.android.habitica.** { *; } #realm -keep class io.realm.annotations.RealmModule diff --git a/Habitica/res/drawable/achievement_badge_bg.xml b/Habitica/res/drawable/achievement_badge_bg.xml new file mode 100644 index 000000000..47576caee --- /dev/null +++ b/Habitica/res/drawable/achievement_badge_bg.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/Habitica/res/drawable/achievement_section_badge_bg.xml b/Habitica/res/drawable/achievement_section_badge_bg.xml new file mode 100644 index 000000000..ae119c105 --- /dev/null +++ b/Habitica/res/drawable/achievement_section_badge_bg.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/Habitica/res/drawable/circle_gray600.xml b/Habitica/res/drawable/circle_gray600.xml new file mode 100644 index 000000000..e2ac51390 --- /dev/null +++ b/Habitica/res/drawable/circle_gray600.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Habitica/res/drawable/ic_round_view_list_24px.xml b/Habitica/res/drawable/ic_round_view_list_24px.xml new file mode 100644 index 000000000..01cb8ce9a --- /dev/null +++ b/Habitica/res/drawable/ic_round_view_list_24px.xml @@ -0,0 +1,5 @@ + + + diff --git a/Habitica/res/drawable/ic_round_view_module_24px.xml b/Habitica/res/drawable/ic_round_view_module_24px.xml new file mode 100644 index 000000000..d7388b410 --- /dev/null +++ b/Habitica/res/drawable/ic_round_view_module_24px.xml @@ -0,0 +1,5 @@ + + + diff --git a/Habitica/res/layout/achievement_grid_item.xml b/Habitica/res/layout/achievement_grid_item.xml new file mode 100644 index 000000000..6ed1113d8 --- /dev/null +++ b/Habitica/res/layout/achievement_grid_item.xml @@ -0,0 +1,61 @@ + + + + + + + + + + \ No newline at end of file diff --git a/Habitica/res/layout/achievement_list_item.xml b/Habitica/res/layout/achievement_list_item.xml new file mode 100644 index 000000000..4c085896d --- /dev/null +++ b/Habitica/res/layout/achievement_list_item.xml @@ -0,0 +1,53 @@ + + + + + + + + + + r + \ No newline at end of file diff --git a/Habitica/res/layout/achievement_quest_item.xml b/Habitica/res/layout/achievement_quest_item.xml new file mode 100644 index 000000000..e02d3234a --- /dev/null +++ b/Habitica/res/layout/achievement_quest_item.xml @@ -0,0 +1,28 @@ + + + + + \ No newline at end of file diff --git a/Habitica/res/layout/achievement_section_header.xml b/Habitica/res/layout/achievement_section_header.xml new file mode 100644 index 000000000..edb4961d7 --- /dev/null +++ b/Habitica/res/layout/achievement_section_header.xml @@ -0,0 +1,30 @@ + + + + + \ No newline at end of file diff --git a/Habitica/res/navigation/navigation.xml b/Habitica/res/navigation/navigation.xml index 9287e4c5f..b5278972c 100644 --- a/Habitica/res/navigation/navigation.xml +++ b/Habitica/res/navigation/navigation.xml @@ -289,4 +289,13 @@ + + + \ No newline at end of file diff --git a/Habitica/res/values/strings.xml b/Habitica/res/values/strings.xml index 179521a54..759d54728 100644 --- a/Habitica/res/values/strings.xml +++ b/Habitica/res/values/strings.xml @@ -919,4 +919,10 @@ Pin Unpin Take me Back + Achievements + Basic Achievements + Seasonal Achievements + Special Achievements + Switch to list view + Switch to grid view diff --git a/Habitica/res/values/styles.habitica.xml b/Habitica/res/values/styles.habitica.xml index eba9a313e..84196ec34 100644 --- a/Habitica/res/values/styles.habitica.xml +++ b/Habitica/res/values/styles.habitica.xml @@ -121,4 +121,9 @@ 12sp 0.035 + \ No newline at end of file diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/api/ApiService.java b/Habitica/src/main/java/com/habitrpg/android/habitica/api/ApiService.java index 0979c8a8f..76e26c9f3 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/api/ApiService.java +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/api/ApiService.java @@ -1,6 +1,6 @@ package com.habitrpg.android.habitica.api; -import com.habitrpg.android.habitica.models.AchievementResult; +import com.habitrpg.android.habitica.models.Achievement; import com.habitrpg.android.habitica.models.ContentResult; import com.habitrpg.android.habitica.models.LeaveChallengeBody; import com.habitrpg.android.habitica.models.PurchaseValidationRequest; @@ -284,7 +284,7 @@ public interface ApiService { Flowable> getMemberWithUsername(@Path("username") String username); @GET("members/{mid}/achievements") - Flowable> getMemberAchievements(@Path("mid") String memberId); + Flowable>> getMemberAchievements(@Path("mid") String memberId); @POST("members/send-private-message") Flowable> postPrivateMessage(@Body Map messageDetails); diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/api/GSonFactoryCreator.java b/Habitica/src/main/java/com/habitrpg/android/habitica/api/GSonFactoryCreator.java index db90b5088..ecd3f19c1 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/api/GSonFactoryCreator.java +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/api/GSonFactoryCreator.java @@ -3,6 +3,7 @@ package com.habitrpg.android.habitica.api; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.reflect.TypeToken; +import com.habitrpg.android.habitica.models.Achievement; import com.habitrpg.android.habitica.models.ContentResult; import com.habitrpg.android.habitica.models.FAQArticle; import com.habitrpg.android.habitica.models.Skill; @@ -11,8 +12,6 @@ import com.habitrpg.android.habitica.models.TutorialStep; import com.habitrpg.android.habitica.models.WorldState; import com.habitrpg.android.habitica.models.inventory.Customization; import com.habitrpg.android.habitica.models.inventory.Equipment; -import com.habitrpg.android.habitica.models.inventory.Mount; -import com.habitrpg.android.habitica.models.inventory.Pet; import com.habitrpg.android.habitica.models.inventory.Quest; import com.habitrpg.android.habitica.models.inventory.QuestCollect; import com.habitrpg.android.habitica.models.inventory.QuestDropItem; @@ -29,6 +28,7 @@ import com.habitrpg.android.habitica.models.user.OwnedMount; import com.habitrpg.android.habitica.models.user.OwnedPet; import com.habitrpg.android.habitica.models.user.Purchases; import com.habitrpg.android.habitica.models.user.User; +import com.habitrpg.android.habitica.utils.AchievementListDeserializer; import com.habitrpg.android.habitica.utils.BooleanAsIntAdapter; import com.habitrpg.android.habitica.utils.ChallengeDeserializer; import com.habitrpg.android.habitica.utils.ChallengeListDeserializer; @@ -53,15 +53,14 @@ import com.habitrpg.android.habitica.utils.QuestDropItemsListSerialization; import com.habitrpg.android.habitica.utils.SkillDeserializer; import com.habitrpg.android.habitica.utils.TaskListDeserializer; import com.habitrpg.android.habitica.utils.TaskSerializer; -import com.habitrpg.android.habitica.utils.TutorialStepListDeserializer; import com.habitrpg.android.habitica.utils.TaskTagDeserializer; +import com.habitrpg.android.habitica.utils.TutorialStepListDeserializer; import com.habitrpg.android.habitica.utils.UserDeserializer; import com.habitrpg.android.habitica.utils.WorldStateSerialization; import java.lang.reflect.Type; import java.util.Date; import java.util.List; -import java.util.Map; import io.realm.RealmList; import retrofit2.converter.gson.GsonConverterFactory; @@ -83,6 +82,7 @@ public class GSonFactoryCreator { Type ownedItemListType = new TypeToken>() {}.getType(); Type ownedPetListType = new TypeToken>() {}.getType(); Type ownedMountListType = new TypeToken>() {}.getType(); + Type achievementsListType = new TypeToken>() {}.getType(); //Exclusion strategy needed for DBFlow https://github.com/Raizlabs/DBFlow/issues/121 @@ -113,6 +113,7 @@ public class GSonFactoryCreator { .registerTypeAdapter(ownedItemListType, new OwnedItemListDeserializer()) .registerTypeAdapter(ownedPetListType, new OwnedPetListDeserializer()) .registerTypeAdapter(ownedMountListType, new OwnedMountListDeserializer()) + .registerTypeAdapter(achievementsListType, new AchievementListDeserializer()) .registerTypeAdapter(Quest.class, new QuestDeserializer()) .registerTypeAdapter(Member.class, new MemberSerialization()) .registerTypeAdapter(WorldState.class, new WorldStateSerialization()) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/components/AppComponent.java b/Habitica/src/main/java/com/habitrpg/android/habitica/components/AppComponent.java index 07de5c536..66e2d26e6 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/components/AppComponent.java +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/components/AppComponent.java @@ -41,6 +41,7 @@ import com.habitrpg.android.habitica.ui.adapter.tasks.HabitsRecyclerViewAdapter; import com.habitrpg.android.habitica.ui.adapter.tasks.RewardsRecyclerViewAdapter; import com.habitrpg.android.habitica.ui.adapter.tasks.TodosRecyclerViewAdapter; import com.habitrpg.android.habitica.ui.fragments.AboutFragment; +import com.habitrpg.android.habitica.ui.fragments.AchievementsFragment; import com.habitrpg.android.habitica.ui.fragments.GemsPurchaseFragment; import com.habitrpg.android.habitica.ui.fragments.NavigationDrawerFragment; import com.habitrpg.android.habitica.ui.fragments.NewsFragment; @@ -314,4 +315,6 @@ public interface AppComponent { void inject(@NotNull ReportMessageActivity reportMessageActivity); void inject(@NotNull GuildDetailFragment guildDetailFragment); + + void inject(@NotNull AchievementsFragment achievementsFragment); } 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 eb8b6e574..ecccbde42 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 @@ -179,7 +179,7 @@ interface ApiClient { fun getMember(memberId: String): Flowable fun getMemberWithUsername(username: String): Flowable - fun getMemberAchievements(memberId: String): Flowable + fun getMemberAchievements(memberId: String): Flowable> fun postPrivateMessage(messageDetails: Map): Flowable diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/InventoryRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/InventoryRepository.kt index 62f68a3ba..5d4a8371d 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/InventoryRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/InventoryRepository.kt @@ -1,20 +1,11 @@ package com.habitrpg.android.habitica.data -import com.habitrpg.android.habitica.models.inventory.Egg -import com.habitrpg.android.habitica.models.inventory.Equipment -import com.habitrpg.android.habitica.models.inventory.Food -import com.habitrpg.android.habitica.models.inventory.HatchingPotion -import com.habitrpg.android.habitica.models.inventory.Item -import com.habitrpg.android.habitica.models.inventory.Mount -import com.habitrpg.android.habitica.models.inventory.Pet -import com.habitrpg.android.habitica.models.inventory.Quest -import com.habitrpg.android.habitica.models.inventory.QuestContent +import com.habitrpg.android.habitica.models.inventory.* import com.habitrpg.android.habitica.models.responses.BuyResponse import com.habitrpg.android.habitica.models.responses.FeedResponse import com.habitrpg.android.habitica.models.shops.Shop import com.habitrpg.android.habitica.models.shops.ShopItem import com.habitrpg.android.habitica.models.user.* - import io.reactivex.Flowable import io.realm.RealmResults @@ -34,6 +25,7 @@ interface InventoryRepository : ContentRepository { fun getOwnedPets(): Flowable> fun getQuestContent(key: String): Flowable + fun getQuestContent(keys: List): Flowable> fun getEquipment(searchedKeys: List): Flowable> fun retrieveInAppRewards(): Flowable> diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/SocialRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/SocialRepository.kt index 899b6330c..9729b1f0e 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/SocialRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/SocialRepository.kt @@ -1,10 +1,13 @@ package com.habitrpg.android.habitica.data -import com.habitrpg.android.habitica.models.AchievementResult +import com.habitrpg.android.habitica.models.Achievement import com.habitrpg.android.habitica.models.inventory.Quest import com.habitrpg.android.habitica.models.members.Member import com.habitrpg.android.habitica.models.responses.PostChatMessageResult -import com.habitrpg.android.habitica.models.social.* +import com.habitrpg.android.habitica.models.social.ChatMessage +import com.habitrpg.android.habitica.models.social.FindUsernameResult +import com.habitrpg.android.habitica.models.social.Group +import com.habitrpg.android.habitica.models.social.GroupMembership import com.habitrpg.android.habitica.models.user.User import io.reactivex.Flowable import io.reactivex.Single @@ -75,7 +78,7 @@ interface SocialRepository : BaseRepository { fun forceStartQuest(party: Group): Flowable - fun getMemberAchievements(userId: String?): Flowable + fun getMemberAchievements(userId: String?): Flowable> fun getGroupMembership(id: String): Flowable fun getGroupMemberships(): 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 590912b04..b25ae6b31 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 @@ -1,5 +1,7 @@ package com.habitrpg.android.habitica.data +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.inventory.Customization import com.habitrpg.android.habitica.models.inventory.CustomizationSet @@ -69,4 +71,7 @@ interface UserRepository : BaseRepository { fun bulkAllocatePoints(user: User?, strength: Int, intelligence: Int, constitution: Int, perception: Int): Flowable fun useCustomization(user: User?, type: String, category: String?, identifier: String): Flowable + fun retrieveAchievements(): Flowable> + fun getAchievements(): Flowable> + fun getQuestAchievements(): 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 d1c920078..eae1bb3db 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 @@ -589,7 +589,7 @@ class ApiClientImpl//private OnHabitsAPIResult mResultListener; return apiService.getMemberWithUsername(username).compose(configureApiCallObserver()) } - override fun getMemberAchievements(memberId: String): Flowable { + override fun getMemberAchievements(memberId: String): Flowable> { return apiService.getMemberAchievements(memberId).compose(configureApiCallObserver()) } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/InventoryRepositoryImpl.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/InventoryRepositoryImpl.kt index 5564e2ded..df46a9f2f 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/InventoryRepositoryImpl.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/InventoryRepositoryImpl.kt @@ -11,9 +11,13 @@ import com.habitrpg.android.habitica.models.shops.Shop import com.habitrpg.android.habitica.models.shops.ShopItem import com.habitrpg.android.habitica.models.user.* import io.reactivex.Flowable +import io.realm.RealmList import io.realm.RealmResults class InventoryRepositoryImpl(localRepository: InventoryLocalRepository, apiClient: ApiClient, userID: String, var appConfigManager: AppConfigManager) : ContentRepositoryImpl(localRepository, apiClient, userID), InventoryRepository { + override fun getQuestContent(keys: List): Flowable> { + return localRepository.getQuestContent(keys) + } override fun getQuestContent(key: String): Flowable { return localRepository.getQuestContent(key) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/SocialRepositoryImpl.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/SocialRepositoryImpl.kt index 60ac890e0..a55dd821f 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/SocialRepositoryImpl.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/SocialRepositoryImpl.kt @@ -5,11 +5,14 @@ import com.habitrpg.android.habitica.data.SocialRepository import com.habitrpg.android.habitica.data.local.SocialLocalRepository import com.habitrpg.android.habitica.extensions.notNull import com.habitrpg.android.habitica.helpers.RxErrorHandler -import com.habitrpg.android.habitica.models.AchievementResult +import com.habitrpg.android.habitica.models.Achievement import com.habitrpg.android.habitica.models.inventory.Quest import com.habitrpg.android.habitica.models.members.Member import com.habitrpg.android.habitica.models.responses.PostChatMessageResult -import com.habitrpg.android.habitica.models.social.* +import com.habitrpg.android.habitica.models.social.ChatMessage +import com.habitrpg.android.habitica.models.social.FindUsernameResult +import com.habitrpg.android.habitica.models.social.Group +import com.habitrpg.android.habitica.models.social.GroupMembership import com.habitrpg.android.habitica.models.user.User import io.reactivex.Flowable import io.reactivex.Single @@ -284,7 +287,7 @@ class SocialRepositoryImpl(localRepository: SocialLocalRepository, apiClient: Ap .doOnNext { localRepository.setQuestActivity(party, true) } } - override fun getMemberAchievements(userId: String?): Flowable { + override fun getMemberAchievements(userId: String?): Flowable> { return if (userId == null) { Flowable.empty() } else apiClient.getMemberAchievements(userId) 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 5e7a39eb5..0da41bc73 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 @@ -6,6 +6,8 @@ import com.habitrpg.android.habitica.data.UserRepository import com.habitrpg.android.habitica.data.local.UserLocalRepository import com.habitrpg.android.habitica.helpers.AppConfigManager 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.inventory.Customization import com.habitrpg.android.habitica.models.inventory.CustomizationSet @@ -303,6 +305,20 @@ class UserRepositoryImpl(localRepository: UserLocalRepository, apiClient: ApiCli return updateUser(user, updatePath, identifier) } + override fun retrieveAchievements(): Flowable> { + return apiClient.getMemberAchievements(userID).doOnNext { + localRepository.save(it) + } + } + + override fun getAchievements(): Flowable> { + return localRepository.getAchievements() + } + + override fun getQuestAchievements(): Flowable> { + return localRepository.getQuestAchievements(userID) + } + 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/InventoryLocalRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/InventoryLocalRepository.kt index 743a457e9..a9fe60fb5 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/InventoryLocalRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/InventoryLocalRepository.kt @@ -24,6 +24,7 @@ interface InventoryLocalRepository : ContentLocalRepository { fun getInAppRewards(): Flowable> fun getQuestContent(key: String): Flowable + fun getQuestContent(keys: List): Flowable> fun getEquipment(searchedKeys: List): Flowable> 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 711ac1af4..0263abf08 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,5 +1,7 @@ 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.social.ChatMessage @@ -20,4 +22,6 @@ interface UserLocalRepository : BaseLocalRepository { fun getSkills(user: User): Flowable> fun getSpecialItems(user: User): Flowable> + fun getAchievements(): Flowable> + fun getQuestAchievements(userID: String): Flowable> } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmInventoryLocalRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmInventoryLocalRepository.kt index b1723d9c8..8b646df4a 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmInventoryLocalRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmInventoryLocalRepository.kt @@ -19,6 +19,13 @@ import io.realm.Sort class RealmInventoryLocalRepository(realm: Realm, private val context: Context) : RealmContentLocalRepository(realm), InventoryLocalRepository { + override fun getQuestContent(keys: List): Flowable> { + return realm.where(QuestContent::class.java) + .`in`("key", keys.toTypedArray()) + .findAll() + .asFlowable() + .filter { it.isLoaded } + } override fun getQuestContent(key: String): Flowable { return realm.where(QuestContent::class.java).equalTo("key", key) 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 71e57205d..fe583c5e3 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 @@ -1,9 +1,7 @@ package com.habitrpg.android.habitica.data.local.implementation import com.habitrpg.android.habitica.data.local.UserLocalRepository -import com.habitrpg.android.habitica.models.Skill -import com.habitrpg.android.habitica.models.Tag -import com.habitrpg.android.habitica.models.TutorialStep +import com.habitrpg.android.habitica.models.* import com.habitrpg.android.habitica.models.social.ChallengeMembership import com.habitrpg.android.habitica.models.social.ChatMessage import com.habitrpg.android.habitica.models.user.User @@ -12,6 +10,21 @@ import io.realm.Realm import io.realm.RealmResults class RealmUserLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm), UserLocalRepository { + override fun getAchievements(): Flowable> { + return realm.where(Achievement::class.java) + .sort("index") + .findAll() + .asFlowable() + .filter { it.isLoaded } + } + + override fun getQuestAchievements(userID: String): Flowable> { + return realm.where(QuestAchievement::class.java) + .equalTo("userID", userID) + .findAll() + .asFlowable() + .filter { it.isLoaded } + } override fun getTutorialSteps(): Flowable> = realm.where(TutorialStep::class.java).findAll().asFlowable() .filter { it.isLoaded } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/models/Achievement.java b/Habitica/src/main/java/com/habitrpg/android/habitica/models/Achievement.java deleted file mode 100644 index c0e957f31..000000000 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/models/Achievement.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.habitrpg.android.habitica.models; - -public class Achievement { - public String type; - public String title; - public String text; - public String icon; - public String category; - public String key; - public String value; - public boolean earned; - public int index; - public Integer optionalCount; -} diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/models/Achievement.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/models/Achievement.kt new file mode 100644 index 000000000..9580c1d67 --- /dev/null +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/models/Achievement.kt @@ -0,0 +1,17 @@ +package com.habitrpg.android.habitica.models + +import io.realm.RealmObject +import io.realm.annotations.PrimaryKey + +open class Achievement : RealmObject() { + @PrimaryKey + var key: String? = null + var type: String? = null + var title: String? = null + var text: String? = null + var icon: String? = null + var category: String? = null + var earned: Boolean = false + var index: Int = 0 + var optionalCount: Int? = null +} diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/models/QuestAchievement.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/models/QuestAchievement.kt new file mode 100644 index 000000000..e55835c59 --- /dev/null +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/models/QuestAchievement.kt @@ -0,0 +1,25 @@ +package com.habitrpg.android.habitica.models + +import io.realm.RealmObject +import io.realm.annotations.Ignore +import io.realm.annotations.PrimaryKey + +open class QuestAchievement: RealmObject() { + @PrimaryKey + var combinedKey: String? = null + + var questKey: String? = null + set(value) { + field = value + combinedKey = userID + questKey + } + var userID: String? = null + set(value) { + field = value + combinedKey = userID + questKey + } + var count: Int = 0 + + @Ignore + var title: String? = null +} diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/models/user/User.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/models/user/User.kt index bf9ab3912..7deec9cae 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/models/user/User.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/models/user/User.kt @@ -3,6 +3,7 @@ package com.habitrpg.android.habitica.models.user import com.google.gson.annotations.SerializedName import com.habitrpg.android.habitica.models.Avatar import com.habitrpg.android.habitica.models.PushDevice +import com.habitrpg.android.habitica.models.QuestAchievement import com.habitrpg.android.habitica.models.Tag import com.habitrpg.android.habitica.models.invitations.Invitations import com.habitrpg.android.habitica.models.social.ChallengeMembership @@ -118,6 +119,11 @@ open class User : RealmObject(), Avatar { } var tags = RealmList() + var questAchievements = RealmList() + set(value) { + field = value + field.forEach { it.userID = id } + } @Ignore var pushDevices: List? = null diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/FullProfileActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/FullProfileActivity.kt index f18d9fe85..ce2abff05 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/FullProfileActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/FullProfileActivity.kt @@ -20,15 +20,14 @@ import com.habitrpg.android.habitica.extensions.notNull import com.habitrpg.android.habitica.helpers.MainNavigationController import com.habitrpg.android.habitica.helpers.RxErrorHandler import com.habitrpg.android.habitica.helpers.UserStatComputer -import com.habitrpg.android.habitica.models.AchievementGroup -import com.habitrpg.android.habitica.models.AchievementResult +import com.habitrpg.android.habitica.models.Achievement import com.habitrpg.android.habitica.models.inventory.Equipment import com.habitrpg.android.habitica.models.members.Member import com.habitrpg.android.habitica.models.user.Outfit import com.habitrpg.android.habitica.models.user.Stats import com.habitrpg.android.habitica.ui.AvatarView import com.habitrpg.android.habitica.ui.AvatarWithBarsViewModel -import com.habitrpg.android.habitica.ui.adapter.social.AchievementAdapter +import com.habitrpg.android.habitica.ui.adapter.social.AchievementProfileAdapter import com.habitrpg.android.habitica.ui.helpers.DataBindingUtils import com.habitrpg.android.habitica.ui.helpers.MarkdownParser import com.habitrpg.android.habitica.ui.helpers.bindView @@ -208,7 +207,7 @@ class FullProfileActivity : BaseActivity() { // Load the members achievements now - compositeSubscription.add(socialRepository.getMemberAchievements(this.userID).subscribe(Consumer { this.fillAchievements(it) }, RxErrorHandler.handleEmptyError())) + compositeSubscription.add(socialRepository.getMemberAchievements(this.userID).subscribe(Consumer { this.fillAchievements(it) }, RxErrorHandler.handleEmptyError())) } private fun updatePetsMountsView(user: Member) { @@ -223,17 +222,17 @@ class FullProfileActivity : BaseActivity() { // region Attributes - private fun fillAchievements(achievements: AchievementResult?) { + private fun fillAchievements(achievements: List?) { if (achievements == null) { return } val items = ArrayList() - fillAchievements(achievements.basic, items) - fillAchievements(achievements.seasonal, items) - fillAchievements(achievements.special, items) + fillAchievements(R.string.basic_achievements, achievements.filter { it.category == "basic" }, items) + fillAchievements(R.string.seasonal_achievements, achievements.filter { it.category == "seasonal" }, items) + fillAchievements(R.string.special_achievements, achievements.filter { it.category == "special" }, items) - val adapter = AchievementAdapter() + val adapter = AchievementProfileAdapter() adapter.setItemList(items) val layoutManager = androidx.recyclerview.widget.GridLayoutManager(this, 3) @@ -252,12 +251,12 @@ class FullProfileActivity : BaseActivity() { stopAndHideProgress(achievementProgress) } - private fun fillAchievements(achievementGroup: AchievementGroup, targetList: MutableList) { + private fun fillAchievements(labelID: Int, achievements: List, targetList: MutableList) { // Order by ID first - val achievementList = ArrayList(achievementGroup.achievements.values) + val achievementList = ArrayList(achievements) achievementList.sortWith(Comparator { achievement, t1 -> java.lang.Double.compare(achievement.index.toDouble(), t1.index.toDouble()) }) - targetList.add(achievementGroup.label) + targetList.add(getString(labelID)) targetList.addAll(achievementList) } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/AchievementsAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/AchievementsAdapter.kt new file mode 100644 index 000000000..661d2a643 --- /dev/null +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/AchievementsAdapter.kt @@ -0,0 +1,115 @@ +package com.habitrpg.android.habitica.ui.adapter + +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import com.facebook.drawee.view.SimpleDraweeView +import com.habitrpg.android.habitica.R +import com.habitrpg.android.habitica.extensions.inflate +import com.habitrpg.android.habitica.models.Achievement +import com.habitrpg.android.habitica.models.QuestAchievement +import com.habitrpg.android.habitica.ui.helpers.DataBindingUtils +import com.habitrpg.android.habitica.ui.helpers.bindOptionalView +import com.habitrpg.android.habitica.ui.helpers.bindView + +class AchievementsAdapter: RecyclerView.Adapter() { + + var useGridLayout: Boolean = false + var entries = listOf() + var questAchievements = listOf() + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + return when (viewType) { + 0 -> SectionViewHolder(parent.inflate(R.layout.achievement_section_header)) + 3 -> QuestAchievementViewHolder(parent.inflate(R.layout.achievement_quest_item)) + else -> AchievementViewHolder(if (useGridLayout) { + parent.inflate(R.layout.achievement_grid_item) + } else { + parent.inflate(R.layout.achievement_list_item) + }) + } + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + when { + entries.size > position -> when (val entry = entries[position]) { + is Achievement -> (holder as? AchievementViewHolder)?.bind(entry) + is Pair<*, *> -> (holder as? SectionViewHolder)?.bind(entry) + } + entries.size == position -> (holder as? SectionViewHolder)?.bind(Pair("Quests completed", questAchievements.size)) + else -> (holder as? QuestAchievementViewHolder)?.bind(questAchievements[position - 1 - entries.size]) + } + } + + override fun getItemCount(): Int { + return entries.size + questAchievements.size + 1 + } + + override fun getItemViewType(position: Int): Int { + return when { + entries.size > position -> { + val entry = entries[position] + if (entry is Pair<*, *>) { + 0 + } else { + if (useGridLayout) 1 else 2 + } + } + entries.size == position -> 0 + else -> 3 + } + } + + class SectionViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) { + private val titleView: TextView by bindView(R.id.title) + private val countView: TextView by bindView(R.id.count_label) + + fun bind(category: Pair<*, *>) { + titleView.text = category.first as? String + countView.text = category.second.toString() + } + } + + class AchievementViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) { + private var achievement: Achievement? = null + + private val achievementContainer: ViewGroup? by bindOptionalView(R.id.achievement_container) + private val achievementIconView: SimpleDraweeView by bindView(R.id.achievement_icon) + private val achievementCountView: TextView by bindView(R.id.achievement_count_label) + private val achievementTitleView: TextView by bindView(R.id.achievement_title) + private val achievementDescriptionView: TextView? by bindOptionalView(R.id.achievement_description) + + fun bind(achievement: Achievement) { + this.achievement = achievement + val iconName = if (achievement.earned) { + achievement.icon + "2x" + } else { + "achievement-unearned2x" + } + DataBindingUtils.loadImage(achievementIconView, iconName) + achievementTitleView.text = achievement.title + achievementDescriptionView?.text = achievement.text + if (achievement.optionalCount ?: 0 > 0) { + achievementCountView.visibility = View.VISIBLE + achievementCountView.text = achievement.optionalCount.toString() + } else { + achievementCountView.visibility = View.GONE + } + achievementContainer?.clipToOutline = true + } + } + + class QuestAchievementViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) { + private var achievement: QuestAchievement? = null + + private val achievementCountView: TextView by bindView(R.id.achievement_count_label) + private val achievementTitleView: TextView by bindView(R.id.achievement_title) + + fun bind(achievement: QuestAchievement) { + this.achievement = achievement + achievementTitleView.text = achievement.title + achievementCountView.text = achievement.count.toString() + } + } +} diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/AchievementAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/AchievementProfileAdapter.kt similarity index 95% rename from Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/AchievementAdapter.kt rename to Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/AchievementProfileAdapter.kt index 0f4939a66..63ef0f33b 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/AchievementAdapter.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/AchievementProfileAdapter.kt @@ -20,7 +20,7 @@ import com.habitrpg.android.habitica.ui.helpers.bindView import com.habitrpg.android.habitica.ui.viewHolders.SectionViewHolder import com.habitrpg.android.habitica.ui.views.HabiticaAlertDialog -class AchievementAdapter : RecyclerView.Adapter() { +class AchievementProfileAdapter : RecyclerView.Adapter() { var itemType: String? = null var activity: MainActivity? = null diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/AchievementsFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/AchievementsFragment.kt new file mode 100644 index 000000000..671a2f723 --- /dev/null +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/AchievementsFragment.kt @@ -0,0 +1,136 @@ +package com.habitrpg.android.habitica.ui.fragments + +import android.graphics.drawable.ColorDrawable +import android.os.Bundle +import android.view.* +import androidx.core.content.ContextCompat +import androidx.recyclerview.widget.GridLayoutManager +import androidx.recyclerview.widget.RecyclerView +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout +import com.habitrpg.android.habitica.R +import com.habitrpg.android.habitica.components.AppComponent +import com.habitrpg.android.habitica.data.InventoryRepository +import com.habitrpg.android.habitica.extensions.subscribeWithErrorHandler +import com.habitrpg.android.habitica.helpers.RxErrorHandler +import com.habitrpg.android.habitica.models.Achievement +import com.habitrpg.android.habitica.ui.adapter.AchievementsAdapter +import com.habitrpg.android.habitica.ui.helpers.bindView +import com.habitrpg.android.habitica.ui.helpers.resetViews +import io.reactivex.functions.Action +import io.reactivex.functions.Consumer +import io.reactivex.rxkotlin.combineLatest +import io.realm.RealmResults +import javax.inject.Inject + +class AchievementsFragment: BaseMainFragment(), SwipeRefreshLayout.OnRefreshListener { + + @Inject + lateinit var inventoryRepository: InventoryRepository + + private var menuID: Int = 0 + private lateinit var adapter: AchievementsAdapter + private val layoutManager = GridLayoutManager(activity, 2) + private var useGridLayout = true + set(value) { + field = value + adapter.useGridLayout = value + adapter.notifyDataSetChanged() + } + + private val recyclerView: RecyclerView by bindView(R.id.recyclerView) + private val refreshLayout: SwipeRefreshLayout by bindView(R.id.refreshLayout) + + override fun injectFragment(component: AppComponent) { + component.inject(this) + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + hidesToolbar = true + super.onCreateView(inflater, container, savedInstanceState) + adapter = AchievementsAdapter() + return inflater.inflate(R.layout.fragment_refresh_recyclerview, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + resetViews() + + recyclerView.layoutManager = layoutManager + recyclerView.adapter = adapter + adapter.useGridLayout = useGridLayout + context?.let { recyclerView.background = ColorDrawable(ContextCompat.getColor(it, R.color.white)) } + + layoutManager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() { + override fun getSpanSize(position: Int): Int { + return if (adapter.getItemViewType(position) == 1) { + 1 + } else { + 2 + } + } + } + + refreshLayout.setOnRefreshListener(this) + + compositeSubscription.add(userRepository.getAchievements().subscribe(Consumer> { + val entries = mutableListOf() + var lastCategory = "" + it.forEach { achievement -> + val categoryIdentifier = achievement.category ?: "" + if (categoryIdentifier != lastCategory) { + val category = Pair(categoryIdentifier, it.count { check -> + check.category == categoryIdentifier && check.earned + }) + entries.add(category) + lastCategory = categoryIdentifier + } + entries.add(achievement) + } + adapter.entries = entries + adapter.notifyDataSetChanged() + }, RxErrorHandler.handleEmptyError())) + compositeSubscription.add(userRepository.getQuestAchievements() + .combineLatest(userRepository.getQuestAchievements() + .map { it.mapNotNull { achievement -> achievement.questKey } } + .flatMap { inventoryRepository.getQuestContent(it) }) + .subscribeWithErrorHandler(Consumer { result -> + val achievements = result.first.map {achievement -> + val questContent = result.second.firstOrNull { achievement.questKey == it.key } + achievement.title = questContent?.text + achievement + } + adapter.questAchievements = achievements + adapter.notifyDataSetChanged() + })) + } + + override fun onCreateOptionsMenu(menu: Menu?, inflater: MenuInflater?) { + if (useGridLayout) { + val menuItem = menu?.add(R.string.switch_to_list_view) + menuID = menuItem?.itemId ?: 0 + menuItem?.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS) + menuItem?.setIcon(R.drawable.ic_round_view_list_24px) + + } else { + val menuItem = menu?.add(R.string.switch_to_grid_view) + menuID = menuItem?.itemId ?: 0 + menuItem?.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS) + menuItem?.setIcon(R.drawable.ic_round_view_module_24px) + } + super.onCreateOptionsMenu(menu, inflater) + } + + override fun onOptionsItemSelected(item: MenuItem?): Boolean { + if (item?.itemId == menuID) { + useGridLayout = !useGridLayout + activity?.invalidateOptionsMenu() + } + return super.onOptionsItemSelected(item) + } + + override fun onRefresh() { + compositeSubscription.add(userRepository.retrieveAchievements().subscribe(Consumer { + }, RxErrorHandler.handleEmptyError(), Action { refreshLayout.isRefreshing = false })) + } +} \ No newline at end of file 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 5ca92abc9..e08319ad2 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 @@ -208,6 +208,7 @@ class NavigationDrawerFragment : DialogFragment() { items.add(HabiticaDrawerItem(R.id.tasksFragment, SIDEBAR_TASKS, context.getString(R.string.sidebar_tasks))) items.add(HabiticaDrawerItem(R.id.skillsFragment, SIDEBAR_SKILLS, context.getString(R.string.sidebar_skills))) 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_SOCIAL, context.getString(R.string.sidebar_section_social), true)) items.add(HabiticaDrawerItem(R.id.tavernFragment, SIDEBAR_TAVERN, context.getString(R.string.sidebar_tavern), false, false)) items.add(HabiticaDrawerItem(R.id.partyFragment, SIDEBAR_PARTY, context.getString(R.string.sidebar_party))) @@ -327,6 +328,7 @@ class NavigationDrawerFragment : DialogFragment() { const val SIDEBAR_TASKS = "tasks" const val SIDEBAR_SKILLS = "skills" const val SIDEBAR_STATS = "stats" + const val SIDEBAR_ACHIEVEMENTS = "achievements" const val SIDEBAR_SOCIAL = "social" const val SIDEBAR_INBOX = "inbox" const val SIDEBAR_TAVERN = "tavern" diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/QuestDetailFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/QuestDetailFragment.kt index 2d29ad1b1..e95fc3ebf 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/QuestDetailFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/QuestDetailFragment.kt @@ -232,15 +232,17 @@ class QuestDetailFragment : BaseMainFragment() { } private fun onQuestBegin() { - context?.let { - val alert = HabiticaAlertDialog(it) + val context = context + if (context != null) { + val alert = HabiticaAlertDialog(context) alert.setMessage(beginQuestMessage) alert.addButton(R.string.yes, true) { _, _ -> - party.notNull { party -> - socialRepository.forceStartQuest(party) - .subscribe(Consumer { }, RxErrorHandler.handleEmptyError()) - } - } + val party = party + if (party != null) { + socialRepository.forceStartQuest(party) + .subscribe(Consumer { }, RxErrorHandler.handleEmptyError()) + } + } alert.addButton(R.string.no, false) alert.show() } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartyDetailFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartyDetailFragment.kt index 275e96b81..9015e8c82 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartyDetailFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartyDetailFragment.kt @@ -183,8 +183,9 @@ class PartyDetailFragment : BaseFragment() { } private fun leaveParty() { - activity?.let { - val alert = HabiticaAlertDialog(it) + val context = context + if (context != null) { + val alert = HabiticaAlertDialog(context) alert.setMessage(R.string.leave_party_confirmation) alert.addButton(R.string.yes, true) { _, _ -> viewModel?.leaveGroup { } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/HabiticaAlertDialog.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/HabiticaAlertDialog.kt index 8b14769c2..e56e84559 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/HabiticaAlertDialog.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/HabiticaAlertDialog.kt @@ -166,7 +166,9 @@ open class HabiticaAlertDialog(context: Context) : AlertDialog(context, R.style. val buttonIndex = buttonsWrapper.childCount buttonView.setOnClickListener { weakThis.get()?.let { it1 -> - function?.invoke(it1, buttonIndex) + if (function != null) { + function(it1, buttonIndex) + } dismiss() } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/utils/AchievementListDeserializer.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/utils/AchievementListDeserializer.kt new file mode 100644 index 000000000..5a6f35e1a --- /dev/null +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/utils/AchievementListDeserializer.kt @@ -0,0 +1,33 @@ +package com.habitrpg.android.habitica.utils + +import com.google.gson.JsonDeserializationContext +import com.google.gson.JsonDeserializer +import com.google.gson.JsonElement +import com.habitrpg.android.habitica.extensions.getAsString +import com.habitrpg.android.habitica.models.Achievement +import java.lang.reflect.Type + +class AchievementListDeserializer: JsonDeserializer> { + + override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): List { + val achievements = mutableListOf() + for (categoryEntry in json?.asJsonObject?.entrySet() ?: emptySet()) { + val categoryIdentifier = categoryEntry.key + for (entry in categoryEntry.value.asJsonObject.getAsJsonObject("achievements").entrySet()) { + var obj = entry.value.asJsonObject + val achievement = Achievement() + achievement.key = entry.key + achievement.category = categoryIdentifier + achievement.earned = obj.get("earned").asBoolean + achievement.title = obj.getAsString("title") + achievement.text = obj.getAsString("text") + achievement.icon = obj.getAsString("icon") + achievement.index = if (obj.has("index")) obj["index"].asInt else 0 + achievement.optionalCount = if (obj.has("optionalCount")) obj["optionalCount"].asInt else 0 + achievements.add(achievement) + } + } + return achievements + } + +} \ No newline at end of file diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/utils/UserDeserializer.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/utils/UserDeserializer.kt index 689724bc6..2ab91ec6f 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/utils/UserDeserializer.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/utils/UserDeserializer.kt @@ -1,6 +1,5 @@ package com.habitrpg.android.habitica.utils -import android.os.Trace import com.google.firebase.perf.FirebasePerformance import com.google.gson.JsonDeserializationContext import com.google.gson.JsonDeserializer @@ -8,6 +7,7 @@ import com.google.gson.JsonElement import com.google.gson.JsonParseException import com.google.gson.reflect.TypeToken import com.habitrpg.android.habitica.models.PushDevice +import com.habitrpg.android.habitica.models.QuestAchievement import com.habitrpg.android.habitica.models.Tag import com.habitrpg.android.habitica.models.inventory.Quest import com.habitrpg.android.habitica.models.invitations.Invitations @@ -118,12 +118,22 @@ class UserDeserializer : JsonDeserializer { } if (obj.has("achievements")) { - if (obj.getAsJsonObject("achievements").has("streak")) { + val achievements = obj.getAsJsonObject("achievements") + if (achievements.has("streak")) { try { user.streakCount = obj.getAsJsonObject("achievements").get("streak").asInt } catch (ignored: UnsupportedOperationException) { } - + } + if (achievements.has("quests")) { + val questAchievements = RealmList() + for (entry in achievements.getAsJsonObject("quests").entrySet()) { + val questAchievement = QuestAchievement() + questAchievement.questKey = entry.key + questAchievement.count = entry.value.asInt + questAchievements.add(questAchievement) + } + user.questAchievements = questAchievements } }