From bc8d9544736a650ec27e5c487cfc39858ab6d6b6 Mon Sep 17 00:00:00 2001 From: Hafiz Date: Thu, 12 Jan 2023 13:51:01 -0500 Subject: [PATCH 01/22] set push notifications setting unchecked if system setting disabled --- .../ui/fragments/preferences/PreferencesFragment.kt | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/PreferencesFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/PreferencesFragment.kt index e6dec2937..8b745dbcc 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/PreferencesFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/PreferencesFragment.kt @@ -191,6 +191,8 @@ class PreferencesFragment : BasePreferencesFragment(), SharedPreferences.OnShare ActivityResultContracts.RequestPermission() ) { granted -> if (granted) { + val usePushPreference = findPreference("usePushNotifications") as? CheckBoxPreference + usePushPreference?.isChecked = true pushNotificationManager.addPushDeviceUsingStoredToken() } else { //If user denies notification settings originally - they must manually enable it through notification settings. @@ -226,13 +228,14 @@ class PreferencesFragment : BasePreferencesFragment(), SharedPreferences.OnShare TaskAlarmManager.scheduleDailyReminder(context) } "usePushNotifications" -> { + val notifPermissionEnabled: Boolean = pushNotificationManager.notificationPermissionEnabled() val usePushNotifications = sharedPreferences.getBoolean(key, true) pushNotificationsPreference?.isEnabled = usePushNotifications lifecycleScope.launchCatching { userRepository.updateUser("preferences.pushNotifications.unsubscribeFromAll", !usePushNotifications) } if (usePushNotifications) { - if (!pushNotificationManager.notificationPermissionEnabled() && Build.VERSION.SDK_INT >= 33) { + if (!notifPermissionEnabled && Build.VERSION.SDK_INT >= 33) { notificationPermissionLauncher.launch(android.Manifest.permission.POST_NOTIFICATIONS) } else { pushNotificationManager.addPushDeviceUsingStoredToken() @@ -389,12 +392,13 @@ class PreferencesFragment : BasePreferencesFragment(), SharedPreferences.OnShare val inbox = user?.inbox disablePMsPreference?.isChecked = inbox?.optOut ?: true + val notifPermissionEnabled: Boolean = pushNotificationManager.notificationPermissionEnabled() val usePushPreference = findPreference("usePushNotifications") as? CheckBoxPreference pushNotificationsPreference = findPreference("pushNotifications") as? PreferenceScreen val usePushNotifications = !(user?.preferences?.pushNotifications?.unsubscribeFromAll ?: false) pushNotificationsPreference?.isEnabled = usePushNotifications - usePushPreference?.isChecked = usePushNotifications - if (!pushNotificationManager.notificationPermissionEnabled() && Build.VERSION.SDK_INT >= 33 && !usePushNotifications) { + usePushPreference?.isChecked = (usePushNotifications && notifPermissionEnabled) + if (!notifPermissionEnabled) { usePushPreference?.summary = getString(R.string.push_notification_system_settings_description) } else { usePushPreference?.summary = "" From 6f5aa2d6f749956ae4655d1de47f5a9b465ba6b4 Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Tue, 10 Jan 2023 15:01:18 +0100 Subject: [PATCH 02/22] Fix displaying banner --- Habitica/res/layout/subscription_details.xml | 1 + Habitica/res/values/strings.xml | 1 + .../habitica/data/ContentRepository.kt | 2 +- .../implementation/ContentRepositoryImpl.kt | 4 +- .../ui/activities/BirthdayActivity.kt | 4 +- .../ui/activities/TaskFormActivity.kt | 2 + .../adapter/inventory/ItemRecyclerAdapter.kt | 2 +- .../ui/fragments/NavigationDrawerFragment.kt | 37 ++++++++++++------- .../views/shops/PurchaseDialogQuestContent.kt | 2 - .../subscriptions/SubscriptionDetailsView.kt | 21 +++++++---- version.properties | 2 +- 11 files changed, 47 insertions(+), 31 deletions(-) diff --git a/Habitica/res/layout/subscription_details.xml b/Habitica/res/layout/subscription_details.xml index b3f3dc081..4e6dfa7ad 100644 --- a/Habitica/res/layout/subscription_details.xml +++ b/Habitica/res/layout/subscription_details.xml @@ -259,6 +259,7 @@ android:gravity="center" android:paddingStart="30dp" android:paddingEnd="30dp" + android:visibility="gone" android:text="@string/subscribers_mythic_hourglasses" /> See More You purchased the Jubilant Gryphatrice! You gifted the Jubilant Gryphatrice! + Open Settings You diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/ContentRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/ContentRepository.kt index 141c5cacc..fc6899a84 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/ContentRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/ContentRepository.kt @@ -7,6 +7,6 @@ import kotlinx.coroutines.flow.Flow interface ContentRepository: BaseRepository { suspend fun retrieveContent(forced: Boolean = false): ContentResult? - suspend fun retrieveWorldState(): WorldState? + suspend fun retrieveWorldState(forced: Boolean = false): WorldState? fun getWorldState(): Flow } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ContentRepositoryImpl.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ContentRepositoryImpl.kt index ba6ed961b..0800867f1 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ContentRepositoryImpl.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ContentRepositoryImpl.kt @@ -36,9 +36,9 @@ class ContentRepositoryImpl( return null } - override suspend fun retrieveWorldState(): WorldState? { + override suspend fun retrieveWorldState(forced: Boolean): WorldState? { val now = Date().time - if (now - this.lastWorldStateSync > 3600000) { + if (forced || now - this.lastWorldStateSync > 3600000) { val state = apiClient.getWorldState() ?: return null lastWorldStateSync = now localRepository.save(state) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/BirthdayActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/BirthdayActivity.kt index b6a6b6b9f..d6c028796 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/BirthdayActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/BirthdayActivity.kt @@ -2,7 +2,6 @@ package com.habitrpg.android.habitica.ui.activities import android.app.Activity import android.os.Bundle -import android.text.format.DateFormat import androidx.activity.compose.setContent import androidx.compose.foundation.Image import androidx.compose.foundation.background @@ -62,6 +61,7 @@ import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel import com.habitrpg.android.habitica.ui.views.CurrencyText import com.habitrpg.common.habitica.extensions.DataBindingUtils import kotlinx.coroutines.flow.map +import java.text.SimpleDateFormat import java.util.Date import javax.inject.Inject @@ -154,7 +154,7 @@ fun BirthdayTitle(text: String) { @Composable fun BirthdayActivityView(hasGryphatrice: Boolean, price: String, startDate: Date, endDate: Date, onPurchaseClick: () -> Unit, onGemPurchaseClick: () -> Unit, onEquipClick: () -> Unit) { val activity = LocalContext.current as? Activity - val dateFormat = DateFormat.getDateFormat(activity) + val dateFormat = SimpleDateFormat("MMM dd", java.util.Locale.getDefault()) val textColor = Color.White val specialTextColor = colorResource(R.color.yellow_50) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/TaskFormActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/TaskFormActivity.kt index 0946bf7fe..4b6ccca79 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/TaskFormActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/TaskFormActivity.kt @@ -9,7 +9,9 @@ import android.graphics.drawable.ColorDrawable import android.os.Build import android.os.Bundle import android.os.Handler +import android.text.SpannableString import android.text.method.LinkMovementMethod +import android.text.style.ForegroundColorSpan import android.view.Menu import android.view.MenuItem import android.view.MotionEvent diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/inventory/ItemRecyclerAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/inventory/ItemRecyclerAdapter.kt index 98191e6f9..b08bc874b 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/inventory/ItemRecyclerAdapter.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/inventory/ItemRecyclerAdapter.kt @@ -189,7 +189,7 @@ class ItemRecyclerAdapter(val context: Context) : BaseRecyclerViewAdapter item?.let { selectedItem -> if (!(selectedItem is QuestContent || selectedItem is SpecialItem || ownedItem?.itemType == "special") && index == 0) { - ownedItem?.let { selectedOwnedItem -> sellItemEvents.onNext(selectedOwnedItem) } + ownedItem?.let { selectedOwnedItem -> onSellItem?.invoke(selectedOwnedItem) } return@let } when (selectedItem) { 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 23eeb4856..77211baf3 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 @@ -140,15 +140,15 @@ class NavigationDrawerFragment : DialogFragment() { false initializeMenuItems() - adapter.itemSelectedEvents = { - setSelection(it.transitionId, it.bundle, true) - } - adapter.promoClosedSubject = { - sharedPreferences.edit { - putBoolean("hide$it", true) - } - updatePromo() + adapter.itemSelectedEvents = { + setSelection(it.transitionId, it.bundle, true) + } + adapter.promoClosedSubject = { + sharedPreferences.edit { + putBoolean("hide$it", true) } + updatePromo() + } lifecycleScope.launchCatching { contentRepository.getWorldState() @@ -167,6 +167,21 @@ class NavigationDrawerFragment : DialogFragment() { }) { updateSeasonalMenuEntries(gearEvent, pair.second) } + + val event = pair.first.events.firstOrNull { it.eventKey == "birthday10" } + val item = getItemWithIdentifier(SIDEBAR_BIRTHDAY) + if (event != null && item == null) { + adapter.currentEvent = event + val birthdayItem = HabiticaDrawerItem(R.id.birthdayActivity, SIDEBAR_BIRTHDAY) + birthdayItem.itemViewType = 6 + val newItems = mutableListOf() + newItems.addAll(adapter.items) + newItems.add(0, birthdayItem) + adapter.updateItems(newItems) + } else if (event == null && item != null) { + item.isVisible = false + adapter.updateItem(item) + } } } @@ -551,12 +566,6 @@ class NavigationDrawerFragment : DialogFragment() { item.itemViewType = 2 items.add(item) } - - configManager.getBirthdayEvent()?.let { - val birthdayItem = HabiticaDrawerItem(R.id.birthdayActivity, SIDEBAR_BIRTHDAY) - birthdayItem.itemViewType = 6 - items.add(0, birthdayItem) - } adapter.updateItems(items) } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/shops/PurchaseDialogQuestContent.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/shops/PurchaseDialogQuestContent.kt index a9b5fd55d..d1a6c9a8c 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/shops/PurchaseDialogQuestContent.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/shops/PurchaseDialogQuestContent.kt @@ -9,7 +9,6 @@ import android.widget.ImageView import android.widget.TextView import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.databinding.DialogPurchaseContentQuestBinding -import com.habitrpg.android.habitica.extensions.fromHtml import com.habitrpg.android.habitica.models.inventory.QuestContent import com.habitrpg.android.habitica.models.inventory.QuestDropItem import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper @@ -26,7 +25,6 @@ class PurchaseDialogQuestContent(context: Context) : PurchaseDialogContent(conte override fun setQuestContentItem(questContent: QuestContent) { super.setQuestContentItem(questContent) - binding.notesTextView.setText(questContent.notes.fromHtml(), TextView.BufferType.SPANNABLE) binding.rageMeterView.visibility = View.GONE if (questContent.isBossQuest) { binding.questTypeTextView.setText(R.string.boss_quest) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/subscriptions/SubscriptionDetailsView.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/subscriptions/SubscriptionDetailsView.kt index daf2fb7fd..62e5b89ec 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/subscriptions/SubscriptionDetailsView.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/subscriptions/SubscriptionDetailsView.kt @@ -50,14 +50,19 @@ class SubscriptionDetailsView : LinearLayout { var duration: String? = null if (plan.planId != null && plan.dateTerminated == null) { - if (plan.planId == SubscriptionPlan.PLANID_BASIC || plan.planId == SubscriptionPlan.PLANID_BASICEARNED) { - duration = resources.getString(R.string.month) - } else if (plan.planId == SubscriptionPlan.PLANID_BASIC3MONTH) { - duration = resources.getString(R.string.three_months) - } else if (plan.planId == SubscriptionPlan.PLANID_BASIC6MONTH || plan.planId == SubscriptionPlan.PLANID_GOOGLE6MONTH) { - duration = resources.getString(R.string.six_months) - } else if (plan.planId == SubscriptionPlan.PLANID_BASIC12MONTH) { - duration = resources.getString(R.string.twelve_months) + when (plan.planId) { + SubscriptionPlan.PLANID_BASIC, SubscriptionPlan.PLANID_BASICEARNED -> { + duration = resources.getString(R.string.month) + } + SubscriptionPlan.PLANID_BASIC3MONTH -> { + duration = resources.getString(R.string.three_months) + } + SubscriptionPlan.PLANID_BASIC6MONTH, SubscriptionPlan.PLANID_GOOGLE6MONTH -> { + duration = resources.getString(R.string.six_months) + } + SubscriptionPlan.PLANID_BASIC12MONTH -> { + duration = resources.getString(R.string.twelve_months) + } } } diff --git a/version.properties b/version.properties index e00baeddc..19dae3716 100644 --- a/version.properties +++ b/version.properties @@ -1,2 +1,2 @@ NAME=4.1 -CODE=4971 \ No newline at end of file +CODE=4981 \ No newline at end of file From 17164d3feffa7038c512c16810bff85d2b0a402f Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Tue, 10 Jan 2023 17:49:26 +0100 Subject: [PATCH 03/22] fix handling urls --- Habitica/res/navigation/navigation.xml | 2 +- .../android/habitica/helpers/MainNavigationController.kt | 9 ++++++++- .../android/habitica/helpers/NotificationOpenHandler.kt | 5 +++-- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/Habitica/res/navigation/navigation.xml b/Habitica/res/navigation/navigation.xml index 3d030339b..a7fe59f7c 100644 --- a/Habitica/res/navigation/navigation.xml +++ b/Habitica/res/navigation/navigation.xml @@ -244,7 +244,7 @@ android:id="@+id/birthdayActivity" android:name="com.habitrpg.android.habitica.ui.activities.BirthdayActivity" android:label="@string/gem_purchase_toolbartitle" > - + openPartyScreen() PushNotificationManager.QUEST_BEGUN_PUSH_NOTIFICATION_KEY -> openPartyScreen() @@ -27,6 +27,7 @@ class NotificationOpenHandler { PushNotificationManager.G1G1_PROMO_KEY -> openGiftOneGetOneInfoScreen() else -> { intent.getStringExtra("openURL")?.let { + MainNavigationController.navigate(it) } } From 50619b73125f3496af0fe4a1a03c998e3b49e81a Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Tue, 10 Jan 2023 17:49:37 +0100 Subject: [PATCH 04/22] improve background filtering --- .../android/habitica/models/CustomizationFilter.kt | 14 ++++++++++++++ .../customization/AvatarCustomizationFragment.kt | 2 ++ 2 files changed, 16 insertions(+) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/models/CustomizationFilter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/models/CustomizationFilter.kt index c2a055f03..3c5a5ae69 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/models/CustomizationFilter.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/models/CustomizationFilter.kt @@ -9,4 +9,18 @@ data class CustomizationFilter( get() { return onlyPurchased || months.isNotEmpty() } + + override fun equals(other: Any?): Boolean { + if (other is CustomizationFilter) { + return onlyPurchased == other.onlyPurchased && ascending == other.ascending && months.size == other.months.size && months.containsAll(other.months) + } + return super.equals(other) + } + + override fun hashCode(): Int { + var result = onlyPurchased.hashCode() + result = 31 * result + ascending.hashCode() + result = 31 * result + months.hashCode() + return result + } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/customization/AvatarCustomizationFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/customization/AvatarCustomizationFragment.kt index 75c727b64..924ca94f3 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/customization/AvatarCustomizationFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/customization/AvatarCustomizationFragment.kt @@ -334,6 +334,8 @@ class AvatarCustomizationFragment : button.text button.setOnCheckedChangeListener { _, isChecked -> val newFilter = filter.copy() + newFilter.months = mutableListOf() + newFilter.months.addAll(filter.months) if (!isChecked && newFilter.months.contains(identifier)) { button.typeface = Typeface.create("sans-serif", Typeface.NORMAL) newFilter.months.remove(identifier) From c06a2e2d07aaa9da01f6468527d379402757fa72 Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Tue, 10 Jan 2023 18:01:30 +0100 Subject: [PATCH 05/22] fix creating group tasks --- .../java/com/habitrpg/android/habitica/api/ApiService.kt | 2 ++ .../java/com/habitrpg/android/habitica/data/ApiClient.kt | 1 + .../android/habitica/data/implementation/ApiClientImpl.kt | 4 ++++ .../habitica/data/implementation/TaskRepositoryImpl.kt | 6 +++++- 4 files changed, 12 insertions(+), 1 deletion(-) 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 a2fb08c5b..774a0344b 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 @@ -138,6 +138,8 @@ interface ApiService { @POST("tasks/user") suspend fun createTask(@Body item: Task): HabitResponse + @POST("tasks/group/{groupId}") + suspend fun createGroupTask(@Path("groupId") groupId: String, @Body item: Task): HabitResponse @POST("tasks/user") suspend fun createTasks(@Body tasks: List): HabitResponse> 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 0d6e62742..3b1c7e948 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 @@ -97,6 +97,7 @@ interface ApiClient { suspend fun scoreChecklistItem(taskId: String, itemId: String): Task? suspend fun createTask(item: Task): Task? + suspend fun createGroupTask(groupId: String, item: Task): Task? suspend fun createTasks(tasks: List): List? 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 8f317f21c..3bce8ab70 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 @@ -447,6 +447,10 @@ class ApiClientImpl( return process { apiService.createTask(item) } } + override suspend fun createGroupTask(groupId: String, item: Task): Task? { + return process { apiService.createGroupTask(groupId, item) } + } + override suspend fun createTasks(tasks: List): List? { return process { apiService.createTasks(tasks) } } 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 697d232f1..a1f5985c3 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 @@ -223,7 +223,11 @@ class TaskRepositoryImpl( } localRepository.save(task) - val savedTask = apiClient.createTask(task) + val savedTask = if (task.isGroupTask) { + apiClient.createGroupTask(task.group?.groupID ?: "", task) + } else { + apiClient.createTask(task) + } savedTask?.dateCreated = Date() if (savedTask != null) { savedTask.tags = task.tags From 1b81f0ce2f22a8fbe4549d37c421cb7480690595 Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Wed, 11 Jan 2023 10:43:03 +0100 Subject: [PATCH 06/22] fix bugs and crashes --- .../res/layout/fragment_gift_gem_balance.xml | 5 +++ Habitica/res/values/strings.xml | 2 +- .../implementation/SocialRepositoryImpl.kt | 5 ++- .../habitica/helpers/PurchaseHandler.kt | 4 ++- .../notifications/PushNotificationManager.kt | 9 +++-- .../habitica/ui/activities/LoginActivity.kt | 2 +- ...stomizationEquipmentRecyclerViewAdapter.kt | 2 +- .../AvatarCustomizationFragment.kt | 2 +- .../purchases/GiftBalanceGemsFragment.kt | 35 ++++++++++++++++--- .../common/habitica/helpers/MarkdownParser.kt | 8 +++-- fastlane/changelog.txt | 11 +++--- version.properties | 2 +- 12 files changed, 66 insertions(+), 21 deletions(-) diff --git a/Habitica/res/layout/fragment_gift_gem_balance.xml b/Habitica/res/layout/fragment_gift_gem_balance.xml index 614f6c32b..a1f4f3532 100644 --- a/Habitica/res/layout/fragment_gift_gem_balance.xml +++ b/Habitica/res/layout/fragment_gift_gem_balance.xml @@ -75,5 +75,10 @@ style="@style/HabiticaButton.Purple" android:layout_marginTop="@dimen/spacing_medium" android:text="@string/send_gift" /> + \ No newline at end of file diff --git a/Habitica/res/values/strings.xml b/Habitica/res/values/strings.xml index c4ac3fda7..5d3ffe647 100644 --- a/Habitica/res/values/strings.xml +++ b/Habitica/res/values/strings.xml @@ -747,7 +747,7 @@ Your gift was sent! You sent %1$s a %2$s-month Habitica subscription and the same subscription was applied to your account for our Gift One Get One promotion! You sent @%1$s a %2$s-month Habitica subscription. - You sent @%s %s gems. + You sent @%1$s %2$s gems. You are now subscribed for 1 month You are now subscribed for %s months You gained %s gems. 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 0e507d1fc..75933a88e 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 @@ -89,11 +89,10 @@ class SocialRepositoryImpl( return null } val liked = chatMessage.userLikesMessage(userID) - if (chatMessage.isManaged) { - localRepository.likeMessage(chatMessage, userID, !liked) - } + localRepository.likeMessage(chatMessage, userID, !liked) val message = apiClient.likeMessage(chatMessage.groupId ?: "", chatMessage.id) message?.groupId = chatMessage.groupId + message?.let { localRepository.save(it) } return null } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/PurchaseHandler.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/PurchaseHandler.kt index 1222416d5..7c8b3b0e3 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/PurchaseHandler.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/PurchaseHandler.kt @@ -211,7 +211,9 @@ class PurchaseHandler( } val flowParams = BillingFlowParams.newBuilder() .setProductDetailsParamsList(listOf(skuDetails).map { - BillingFlowParams.ProductDetailsParams.newBuilder().setProductDetails(skuDetails) + BillingFlowParams.ProductDetailsParams.newBuilder() + .setProductDetails(skuDetails) + .setOfferToken(skuDetails.subscriptionOfferDetails?.first()?.offerToken ?: "") .build() }) .build() diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/PushNotificationManager.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/PushNotificationManager.kt index fb14775c2..79adfd5c4 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/PushNotificationManager.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/PushNotificationManager.kt @@ -11,6 +11,7 @@ import com.habitrpg.android.habitica.helpers.AmplitudeManager import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.user.User import kotlinx.coroutines.MainScope +import java.io.IOException class PushNotificationManager( var apiClient: ApiClient, @@ -52,8 +53,12 @@ class PushNotificationManager( addRefreshToken() } else { FirebaseMessaging.getInstance().token.addOnCompleteListener { - refreshedToken = it.result - addRefreshToken() + try { + refreshedToken = it.result + addRefreshToken() + } catch (_: IOException) { + // This can happen during google test runs + } } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/LoginActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/LoginActivity.kt index acc6756d5..d67256641 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/LoginActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/LoginActivity.kt @@ -126,7 +126,6 @@ class LoginActivity : BaseActivity() { } override fun getLayoutResId(): Int { - window.requestFeature(Window.FEATURE_ACTION_BAR) return R.layout.activity_login } @@ -136,6 +135,7 @@ class LoginActivity : BaseActivity() { } override fun onCreate(savedInstanceState: Bundle?) { + window.requestFeature(Window.FEATURE_ACTION_BAR) super.onCreate(savedInstanceState) viewModel = AuthenticationViewModel() supportActionBar?.hide() diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/CustomizationEquipmentRecyclerViewAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/CustomizationEquipmentRecyclerViewAdapter.kt index 39bdd2d16..c111d3dfd 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/CustomizationEquipmentRecyclerViewAdapter.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/CustomizationEquipmentRecyclerViewAdapter.kt @@ -111,7 +111,7 @@ class CustomizationEquipmentRecyclerViewAdapter : androidx.recyclerview.widget.R imageView.loadImage("shop_" + this.equipment?.key) val priceLabel = dialogContent.findViewById(R.id.priceLabel) - priceLabel.text = if (equipment?.gearSet == "animal") { + priceLabel?.text = if (equipment?.gearSet == "animal") { 2.0 } else { equipment?.value ?: 0 diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/customization/AvatarCustomizationFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/customization/AvatarCustomizationFragment.kt index 924ca94f3..4215b112d 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/customization/AvatarCustomizationFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/customization/AvatarCustomizationFragment.kt @@ -335,7 +335,7 @@ class AvatarCustomizationFragment : button.setOnCheckedChangeListener { _, isChecked -> val newFilter = filter.copy() newFilter.months = mutableListOf() - newFilter.months.addAll(filter.months) + newFilter.months.addAll(currentFilter.value.months) if (!isChecked && newFilter.months.contains(identifier)) { button.typeface = Typeface.create("sans-serif", Typeface.NORMAL) newFilter.months.remove(identifier) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/GiftBalanceGemsFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/GiftBalanceGemsFragment.kt index 4dde1b73f..ac78413e9 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/GiftBalanceGemsFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/GiftBalanceGemsFragment.kt @@ -5,28 +5,41 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.core.view.isVisible import androidx.lifecycle.lifecycleScope +import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.components.UserComponent import com.habitrpg.android.habitica.data.SocialRepository import com.habitrpg.android.habitica.data.UserRepository import com.habitrpg.android.habitica.databinding.FragmentGiftGemBalanceBinding +import com.habitrpg.android.habitica.extensions.addCloseButton import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.models.members.Member import com.habitrpg.android.habitica.ui.fragments.BaseFragment +import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog import javax.inject.Inject class GiftBalanceGemsFragment : BaseFragment() { @Inject lateinit var socialRepository: SocialRepository + @Inject lateinit var userRepository: UserRepository override var binding: FragmentGiftGemBalanceBinding? = null private var isGifting = false + set(value) { + field = value + binding?.giftButton?.isVisible = !isGifting + binding?.progressBar?.isVisible = isGifting + } - override fun createBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentGiftGemBalanceBinding { + override fun createBinding( + inflater: LayoutInflater, + container: ViewGroup? + ): FragmentGiftGemBalanceBinding { return FragmentGiftGemBalanceBinding.inflate(inflater, container, false) } @@ -61,15 +74,29 @@ class GiftBalanceGemsFragment : BaseFragment() { if (isGifting) return isGifting = true try { - val amount = binding?.giftEditText?.text.toString().toInt() + val amount = binding?.giftEditText?.text.toString().strip().toInt() giftedMember?.id?.let { - lifecycleScope.launchCatching({ + activity?.lifecycleScope?.launchCatching({ isGifting = false }) { socialRepository.transferGems(it, amount) userRepository.retrieveUser(false) + val dialog = context?.let { it1 -> HabiticaAlertDialog(it1) } + dialog?.setTitle(R.string.gift_confirmation_title) + dialog?.setMessage( + getString( + R.string.gift_confirmation_text_gems_new, + giftedMember?.username, + amount.toString() + ) + ) + dialog?.addCloseButton { _, _ -> + activity?.finish() + } + dialog?.show() } } - } catch (ignored: NumberFormatException) {} + } catch (ignored: NumberFormatException) { + } } } diff --git a/common/src/main/java/com/habitrpg/common/habitica/helpers/MarkdownParser.kt b/common/src/main/java/com/habitrpg/common/habitica/helpers/MarkdownParser.kt index 1b28948cb..adeb49fc2 100644 --- a/common/src/main/java/com/habitrpg/common/habitica/helpers/MarkdownParser.kt +++ b/common/src/main/java/com/habitrpg/common/habitica/helpers/MarkdownParser.kt @@ -91,8 +91,12 @@ object MarkdownParser { return SpannableString("") } val hashCode = input.hashCode() - if (cache.containsKey(hashCode)) { - return cache[hashCode] ?: SpannableString(input) + try { + if (cache.containsKey(hashCode)) { + return cache[hashCode] ?: SpannableString(input) + } + } catch (_: NullPointerException) { + // Sometimes happens } val text = EmojiParser.parseEmojis(input) ?: input // Adding this space here bc for some reason some markdown is not rendered correctly when the whole string is supposed to be formatted diff --git a/fastlane/changelog.txt b/fastlane/changelog.txt index 13189c062..fd05a0cc1 100644 --- a/fastlane/changelog.txt +++ b/fastlane/changelog.txt @@ -1,9 +1,12 @@ New in 4.1: --You can view, complete, assign, and add tasks to your Group Plan’s shared task board! +-You can view, complete, assign, and add tasks to your Group Plan's shared task board! -Tap your name on a task screen to switch to different task boards --Subscription details will show extra months and when you’ll get your next Hourglass --More intuitive system notification settings --Audio will be controlled by media volume now -Task details are now tinted based on task health +-New Avatar Customization interface +-Audio will be controlled by media volume now -Fixed a bug that caused tasks to shuffle around -Monthly Dailies should recur more consistently now +-More intuitive system notification settings +-Improved link support +-More special event support + diff --git a/version.properties b/version.properties index 19dae3716..8c685bc20 100644 --- a/version.properties +++ b/version.properties @@ -1,2 +1,2 @@ NAME=4.1 -CODE=4981 \ No newline at end of file +CODE=4991 \ No newline at end of file From b199c2ea73f4c6eb5d8b9554e04758a17debaf9c Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Wed, 11 Jan 2023 15:44:48 +0100 Subject: [PATCH 07/22] Improve event display --- Habitica/res/layout/fragment_subscription.xml | 5 ++++ .../android/habitica/api/ApiService.kt | 2 +- .../android/habitica/data/ApiClient.kt | 2 +- .../android/habitica/data/SocialRepository.kt | 4 +-- .../data/implementation/ApiClientImpl.kt | 4 +-- .../implementation/SocialRepositoryImpl.kt | 2 +- .../data/implementation/UserRepositoryImpl.kt | 6 +++- .../habitica/helpers/PurchaseHandler.kt | 12 ++++++-- .../purchases/GemsPurchaseFragment.kt | 18 ++++++++++++ .../purchases/SubscriptionFragment.kt | 18 ++++++++++++ .../ui/views/promo/BirthdayMenuView.kt | 4 +-- .../ui/views/shops/PurchaseDialogContent.kt | 3 +- .../habitica/ui/views/tasks/AssignedView.kt | 13 ++++++++- .../extensions/Application-Extensions.kt | 1 + .../habitica/extensions/DataBindingUtils.kt | 28 +++++++------------ .../common/habitica/views/AvatarView.kt | 6 +++- 16 files changed, 93 insertions(+), 35 deletions(-) diff --git a/Habitica/res/layout/fragment_subscription.xml b/Habitica/res/layout/fragment_subscription.xml index 951b73876..d2d47c673 100644 --- a/Habitica/res/layout/fragment_subscription.xml +++ b/Habitica/res/layout/fragment_subscription.xml @@ -17,6 +17,11 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> + @POST("user/mark-pms-read") - suspend fun markPrivateMessagesRead(): Void + suspend fun markPrivateMessagesRead(): Void? /* Group API */ 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 3b1c7e948..2f6060b29 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 @@ -131,7 +131,7 @@ interface ApiClient { suspend fun disableClasses(): User? - suspend fun markPrivateMessagesRead(): Void? + suspend fun markPrivateMessagesRead() /* Group API */ 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 bf5e37ecf..af0b2674f 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 @@ -18,7 +18,7 @@ interface SocialRepository : BaseRepository { fun getUserGroups(type: String?): Flow> suspend fun retrieveGroupChat(groupId: String): List? - fun getGroupChat(groupId: String): Flow> + fun getGroupChat(groupId: String): Flow> suspend fun markMessagesSeen(seenGroupId: String) @@ -92,7 +92,7 @@ interface SocialRepository : BaseRepository { id: String? = null ): List? - suspend fun markPrivateMessagesRead(user: User?): Void? + suspend fun markPrivateMessagesRead(user: User?) fun markSomePrivateMessagesAsRead(user: User?, messages: List) 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 3bce8ab70..446bf4c41 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 @@ -499,8 +499,8 @@ class ApiClientImpl( override suspend fun disableClasses(): User? = process { apiService.disableClasses() } - override suspend fun markPrivateMessagesRead(): Void { - return apiService.markPrivateMessagesRead() + override suspend fun markPrivateMessagesRead() { + apiService.markPrivateMessagesRead() } override suspend fun listGroups(type: String): List? { 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 75933a88e..df5c4589b 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 @@ -274,7 +274,7 @@ class SocialRepositoryImpl( return apiClient.findUsernames(username, context, id) } - override suspend fun markPrivateMessagesRead(user: User?): Void? { + override suspend fun markPrivateMessagesRead(user: User?) { if (user?.isManaged == true) { localRepository.modify(user) { it.inbox?.hasUserSeenInbox = true 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 6aeae16c6..cfbad3f3d 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 @@ -20,9 +20,11 @@ import com.habitrpg.android.habitica.models.user.UserQuestStatus import com.habitrpg.android.habitica.proxy.AnalyticsManager import com.habitrpg.shared.habitica.models.responses.TaskDirection import com.habitrpg.shared.habitica.models.tasks.Attribute +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.map +import kotlinx.coroutines.withContext import java.util.Date import java.util.GregorianCalendar import java.util.concurrent.TimeUnit @@ -65,7 +67,9 @@ class UserRepositoryImpl( if (forced || this.lastSync == null || Date().time - (this.lastSync?.time ?: 0) > 180000) { val user = apiClient.retrieveUser(withTasks) ?: return null lastSync = Date() - localRepository.saveUser(user) + withContext(Dispatchers.Main) { + localRepository.saveUser(user) + } if (withTasks) { val id = user.id val tasksOrder = user.tasksOrder diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/PurchaseHandler.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/PurchaseHandler.kt index 7c8b3b0e3..76d056f4d 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/PurchaseHandler.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/PurchaseHandler.kt @@ -426,11 +426,17 @@ class PurchaseHandler( } } + private val displayedConfirmations = mutableListOf() + private fun displayConfirmationDialog(purchase: Purchase, giftedTo: String? = null) { - CoroutineScope(Dispatchers.Main).launch(ExceptionHandler.coroutine()) { + if (displayedConfirmations.contains(purchase.orderId)) { + return + } + displayedConfirmations.add(purchase.orderId) + CoroutineScope(Dispatchers.Main).launchCatching { val application = (context as? HabiticaBaseApplication) - ?: (context.applicationContext as? HabiticaBaseApplication) ?: return@launch - val sku = purchase.products.firstOrNull() ?: return@launch + ?: (context.applicationContext as? HabiticaBaseApplication) ?: return@launchCatching + val sku = purchase.products.firstOrNull() ?: return@launchCatching var title = context.getString(R.string.successful_purchase_generic) val message = when { PurchaseTypes.allSubscriptionNoRenewTypes.contains(sku) -> { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/GemsPurchaseFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/GemsPurchaseFragment.kt index 118eb8552..9cb17712b 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/GemsPurchaseFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/GemsPurchaseFragment.kt @@ -6,6 +6,11 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.EditText +import androidx.compose.foundation.layout.padding +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.unit.dp +import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope import com.android.billingclient.api.ProductDetails @@ -26,7 +31,9 @@ import com.habitrpg.android.habitica.ui.activities.GiftGemsActivity import com.habitrpg.android.habitica.ui.fragments.BaseFragment import com.habitrpg.android.habitica.ui.fragments.PromoInfoFragment import com.habitrpg.android.habitica.ui.helpers.dismissKeyboard +import com.habitrpg.android.habitica.ui.theme.HabiticaTheme import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog +import com.habitrpg.android.habitica.ui.views.promo.BirthdayBanner import com.habitrpg.common.habitica.extensions.isUsingNightModeResources import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -92,6 +99,17 @@ class GemsPurchaseFragment : BaseFragment() { binding?.promoBanner?.visibility = View.GONE } + val birthdayEventEnd = appConfigManager.getBirthdayEvent()?.end + if (birthdayEventEnd != null) { + binding?.promoComposeView?.setContent { + HabiticaTheme { + BirthdayBanner(endDate = birthdayEventEnd, Modifier.padding(horizontal = 20.dp).clip(HabiticaTheme.shapes.medium) + .padding(bottom = 20.dp)) + } + } + binding?.promoComposeView?.isVisible = true + } + AmplitudeManager.sendNavigationEvent("gem screen") } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/SubscriptionFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/SubscriptionFragment.kt index d9f6b9820..1665a76c0 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/SubscriptionFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/SubscriptionFragment.kt @@ -7,6 +7,11 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.EditText +import androidx.compose.foundation.layout.padding +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.unit.dp +import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope import com.android.billingclient.api.ProductDetails @@ -26,7 +31,9 @@ import com.habitrpg.android.habitica.models.user.User import com.habitrpg.android.habitica.ui.activities.GiftSubscriptionActivity import com.habitrpg.android.habitica.ui.fragments.BaseFragment import com.habitrpg.android.habitica.ui.fragments.PromoInfoFragment +import com.habitrpg.android.habitica.ui.theme.HabiticaTheme import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog +import com.habitrpg.android.habitica.ui.views.promo.BirthdayBanner import com.habitrpg.android.habitica.ui.views.subscriptions.SubscriptionOptionView import com.habitrpg.common.habitica.extensions.isUsingNightModeResources import com.habitrpg.common.habitica.extensions.layoutInflater @@ -87,6 +94,17 @@ class SubscriptionFragment : BaseFragment() { binding?.promoBanner?.visibility = View.GONE } + val birthdayEventEnd = appConfigManager.getBirthdayEvent()?.end + if (birthdayEventEnd != null) { + binding?.promoComposeView?.setContent { + HabiticaTheme { + BirthdayBanner(endDate = birthdayEventEnd, Modifier.padding(horizontal = 20.dp).clip(HabiticaTheme.shapes.medium) + .padding(bottom = 10.dp)) + } + } + binding?.promoComposeView?.isVisible = true + } + binding?.refreshLayout?.setOnRefreshListener { refresh() } lifecycleScope.launchCatching { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/promo/BirthdayMenuView.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/promo/BirthdayMenuView.kt index d4ba11603..a7da9773f 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/promo/BirthdayMenuView.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/promo/BirthdayMenuView.kt @@ -33,7 +33,7 @@ import com.habitrpg.android.habitica.helpers.MainNavigationController import java.util.Date @Composable -fun BirthdayBanner(endDate: Date) { +fun BirthdayBanner(endDate: Date, modifier: Modifier = Modifier) { var value by remember { mutableStateOf(0) } DisposableEffect(Unit) { @@ -50,7 +50,7 @@ fun BirthdayBanner(endDate: Date) { } } Column( - Modifier + modifier .fillMaxWidth() .clickable { MainNavigationController.navigate(R.id.birthdayActivity) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/shops/PurchaseDialogContent.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/shops/PurchaseDialogContent.kt index c49a79b95..cedab5f2b 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/shops/PurchaseDialogContent.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/shops/PurchaseDialogContent.kt @@ -9,7 +9,6 @@ import com.habitrpg.android.habitica.extensions.fromHtml import com.habitrpg.android.habitica.models.inventory.QuestContent import com.habitrpg.android.habitica.models.shops.ShopItem import com.habitrpg.common.habitica.extensions.dpToPx -import com.habitrpg.common.habitica.extensions.loadGif import com.habitrpg.common.habitica.extensions.loadImage import com.habitrpg.common.habitica.views.PixelArtView @@ -29,7 +28,7 @@ abstract class PurchaseDialogContent @JvmOverloads constructor( open fun setItem(item: ShopItem) { if (item.path?.contains("timeTravelBackgrounds") == true) { - imageView.loadGif(item.imageName?.replace("icon_", "")) + imageView.loadImage(item.imageName?.replace("icon_", "")) val params = imageView.layoutParams params.height = 147.dpToPx(context) params.width = 140.dpToPx(context) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/tasks/AssignedView.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/tasks/AssignedView.kt index 18ca09270..839c3cc4f 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/tasks/AssignedView.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/tasks/AssignedView.kt @@ -52,6 +52,13 @@ fun AssignedView( color = color, extraContent = { completedAt[assignable.id]?.let { CompletedAt(completedAt = it) } + }, + endContent = { + completedAt[assignable.id]?.let { + if (showEditButton) { + UndoTaskCompletion() + } + } } ) } @@ -80,4 +87,8 @@ fun AssignedView( } } } -} \ No newline at end of file +} + +@Composable +fun UndoTaskCompletion() { +} diff --git a/common/src/main/java/com/habitrpg/common/habitica/extensions/Application-Extensions.kt b/common/src/main/java/com/habitrpg/common/habitica/extensions/Application-Extensions.kt index 363d913e9..e3101625c 100644 --- a/common/src/main/java/com/habitrpg/common/habitica/extensions/Application-Extensions.kt +++ b/common/src/main/java/com/habitrpg/common/habitica/extensions/Application-Extensions.kt @@ -12,6 +12,7 @@ import com.habitrpg.common.habitica.BuildConfig fun Application.setupCoil() { var builder = ImageLoader.Builder(this) .allowHardware(false) + .crossfade(false) .components { if (Build.VERSION.SDK_INT >= 28) { add(ImageDecoderDecoder.Factory()) diff --git a/common/src/main/java/com/habitrpg/common/habitica/extensions/DataBindingUtils.kt b/common/src/main/java/com/habitrpg/common/habitica/extensions/DataBindingUtils.kt index 1a78961a1..a75c17c87 100644 --- a/common/src/main/java/com/habitrpg/common/habitica/extensions/DataBindingUtils.kt +++ b/common/src/main/java/com/habitrpg/common/habitica/extensions/DataBindingUtils.kt @@ -2,6 +2,7 @@ package com.habitrpg.common.habitica.extensions import android.content.Context import android.graphics.PorterDuff +import android.graphics.drawable.Animatable import android.graphics.drawable.Drawable import android.view.View import android.view.animation.Animation @@ -13,7 +14,6 @@ import coil.imageLoader import coil.request.ImageRequest import com.habitrpg.android.habitica.extensions.setTintWith import com.habitrpg.common.habitica.R -import com.habitrpg.common.habitica.extensions.DataBindingUtils.BASE_IMAGE_URL import com.habitrpg.common.habitica.helpers.AppConfigManager import com.habitrpg.common.habitica.views.PixelArtView import java.util.Collections @@ -26,30 +26,22 @@ fun PixelArtView.loadImage(imageName: String?, imageFormat: String? = null) { return } tag = fullname - bitmap = null + setImageDrawable(null) DataBindingUtils.loadImage(context, imageName, imageFormat) { if (tag == fullname) { - bitmap = it.toBitmap() + if (fullname.endsWith("gif")) { + setImageDrawable(it) + if (it is Animatable) { + it.start() + } + } else { + bitmap = it.toBitmap() + } } } } } -fun PixelArtView.loadGif( - imageName: String?, - builder: ImageRequest.Builder.() -> Unit = {} -) { - if (imageName != null) { - val fullname = BASE_IMAGE_URL + DataBindingUtils.getFullFilename(imageName) - val request = ImageRequest.Builder(context) - .data(fullname) - .target(this) - .apply(builder) - .build() - context.imageLoader.enqueue(request) - } -} - object DataBindingUtils { fun loadImage(context: Context, imageName: String, imageResult: (Drawable) -> Unit) { diff --git a/common/src/main/java/com/habitrpg/common/habitica/views/AvatarView.kt b/common/src/main/java/com/habitrpg/common/habitica/views/AvatarView.kt index 4657863e8..69df63be5 100644 --- a/common/src/main/java/com/habitrpg/common/habitica/views/AvatarView.kt +++ b/common/src/main/java/com/habitrpg/common/habitica/views/AvatarView.kt @@ -7,6 +7,7 @@ import android.graphics.Matrix import android.graphics.PointF import android.graphics.Rect import android.graphics.RectF +import android.graphics.drawable.Animatable import android.graphics.drawable.Drawable import android.text.TextUtils import android.util.AttributeSet @@ -170,6 +171,9 @@ class AvatarView : FrameLayout { result.isFilterBitmap = false super.onSuccess(result) imageView.setImageDrawable(result) + if (result is Animatable) { + result.start() + } val bounds = getLayerBounds(layerKey, layerName, result) imageView.imageMatrix = avatarMatrix val layoutParams = imageView.layoutParams as? LayoutParams @@ -288,7 +292,7 @@ class AvatarView : FrameLayout { val hair = prefs.hair if (!hasVisualBuffs) { - if (!TextUtils.isEmpty(prefs.chair)) { + if (prefs.chair?.isNotBlank() == true && prefs.chair != "chair_none") { layerMap[LayerType.CHAIR] = prefs.chair } From 553cd19176cea3110877ac56faa5e022895ffc55 Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Fri, 13 Jan 2023 10:24:05 +0100 Subject: [PATCH 08/22] work in feedback --- Habitica/res/layout/activity_main_content.xml | 2 +- Habitica/res/layout/fragment_gem_purchase.xml | 4 +- .../res/layout/fragment_gift_gem_balance.xml | 3 +- Habitica/res/values/strings.xml | 8 +- .../android/habitica/api/ApiService.kt | 3 + .../android/habitica/data/ApiClient.kt | 1 + .../android/habitica/data/TaskRepository.kt | 3 +- .../data/implementation/ApiClientImpl.kt | 10 +- .../data/implementation/TaskRepositoryImpl.kt | 22 ++- .../android/habitica/helpers/PurchaseTypes.kt | 2 +- .../android/habitica/models/social/Group.kt | 6 + .../habitica/models/user/SubscriptionPlan.kt | 3 +- .../ui/activities/BirthdayActivity.kt | 175 +++++++++++++++--- .../ui/activities/TaskFormActivity.kt | 7 +- .../tasks/RewardsRecyclerViewAdapter.kt | 4 +- .../ui/fragments/NavigationDrawerFragment.kt | 2 +- .../ui/fragments/social/ChatFragment.kt | 7 +- .../ui/fragments/social/TavernFragment.kt | 4 +- .../social/guilds/GuildDetailFragment.kt | 3 + .../fragments/social/guilds/GuildFragment.kt | 1 + .../fragments/social/party/PartyFragment.kt | 1 + .../tasks/RewardsRecyclerviewFragment.kt | 2 +- .../tasks/ChecklistedViewHolder.kt | 2 +- .../ui/viewHolders/tasks/HabitViewHolder.kt | 8 +- .../habitica/ui/viewmodels/GroupViewModel.kt | 12 +- .../android/habitica/ui/views/UserRow.kt | 3 +- .../habitica/ui/views/tasks/AssignedView.kt | 46 ++++- version.properties | 2 +- 28 files changed, 285 insertions(+), 61 deletions(-) diff --git a/Habitica/res/layout/activity_main_content.xml b/Habitica/res/layout/activity_main_content.xml index 803ea4575..08a09a097 100644 --- a/Habitica/res/layout/activity_main_content.xml +++ b/Habitica/res/layout/activity_main_content.xml @@ -95,7 +95,7 @@ app:layout_anchorGravity="bottom" app:layout_collapseMode="pin" app:tabGravity="fill" - app:tabIndicatorColor="?colorPrimary" + app:tabIndicatorColor="?textColorPrimary" app:tabMode="fixed" /> + android:layout_gravity="center" + android:visibility="gone"/> \ No newline at end of file diff --git a/Habitica/res/values/strings.xml b/Habitica/res/values/strings.xml index 5d3ffe647..bd43e139e 100644 --- a/Habitica/res/values/strings.xml +++ b/Habitica/res/values/strings.xml @@ -1319,7 +1319,7 @@ The rare, mystical Gryphatrice joins the birthday bash! Don’t miss your chance to own this exclusive animated Pet. Thanks for your support! Plenty of Potions - For for Free + Four for Free Buy for %s Buy for We’re bringing back 10 of the community’s favorite Magic Hatching Potions. Head over to the Market to fill out your collection! @@ -1332,6 +1332,12 @@ You purchased the Jubilant Gryphatrice! You gifted the Jubilant Gryphatrice! Open Settings + Undo + Day %d + A Party Robe + 20 Gems + Background + Birthday Set You 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 561386e74..a09102e2a 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 @@ -459,4 +459,7 @@ interface ApiService { @GET("hall/heroes/{memberID}") suspend fun getHallMember(@Path("memberID") memberID: String): HabitResponse + + @POST("tasks/{taskID}/needs-work/{userID}") + suspend fun markTaskNeedsWork(@Path("taskID") taskID: String, @Path("userID") userID: String): HabitResponse } 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 2f6060b29..d6de7a6a0 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 @@ -275,4 +275,5 @@ interface ApiClient { suspend fun unassignFromTask(taskId: String, userID: String): Task? suspend fun updateMember(memberID: String, updateData: Map): Member? suspend fun getHallMember(userId: String): Member? + suspend fun markTaskNeedsWork(taskID: String, userID: String): Task? } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/TaskRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/TaskRepository.kt index 9ae35d9d0..b768f2623 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/TaskRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/TaskRepository.kt @@ -65,6 +65,7 @@ interface TaskRepository : BaseRepository { suspend fun retrieveCompletedTodos(userId: String? = null): TaskList? suspend fun syncErroredTasks(): List? suspend fun unlinkAllTasks(challengeID: String?, keepOption: String): Void? - fun getTasksForChallenge(challengeID: String?): Flow> + fun getTasksForChallenge(challengeID: String?): Flow> suspend fun bulkScoreTasks(data: List>): BulkTaskScoringData? + suspend fun markTaskNeedsWork(task: Task, userID: String) } 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 446bf4c41..86f5ced97 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 @@ -606,14 +606,22 @@ class ApiClientImpl( return process { apiService.leaveQuest(groupId) } } + private val lastPurchaseValidation: Date? = null override suspend fun validatePurchase(request: PurchaseValidationRequest): PurchaseValidationResult? { - return process { apiService.validatePurchase(request) } + // make sure a purchase attempt doesn't happen + return if (lastPurchaseValidation == null || Date().time - lastPurchaseValidation.time > 5000) { + return process { apiService.validatePurchase(request) } + } else null } override suspend fun changeCustomDayStart(updateObject: Map): User? { return process { apiService.changeCustomDayStart(updateObject) } } + override suspend fun markTaskNeedsWork(taskID: String, userID: String): Task? { + return process { apiService.markTaskNeedsWork(taskID, userID) } + } + override suspend fun getMember(memberId: String) = processResponse(apiService.getMember(memberId)) override suspend fun getMemberWithUsername(username: String) = processResponse(apiService.getMemberWithUsername(username)) 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 a1f5985c3..c08f94933 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 @@ -147,6 +147,16 @@ class TaskRepositoryImpl( bgTask.counterDown = (bgTask.counterDown ?: 0) + 1 } } + + if (bgTask.isGroupTask) { + val entry = bgTask.group?.assignedUsersDetail?.firstOrNull { it.assignedUserID == user.id } + entry?.completed = up + if (up) { + entry?.completedDate = Date() + } else { + entry?.completedDate = null + } + } } res._tmp?.drop?.key?.let { key -> val type = when (res._tmp?.drop?.type?.lowercase(Locale.US)) { @@ -183,6 +193,14 @@ class TaskRepositoryImpl( } } + override suspend fun markTaskNeedsWork(task: Task, userID: String) { + val savedTask = apiClient.markTaskNeedsWork(task.id ?: "", userID) + if (savedTask != null) { + savedTask.position = task.position + localRepository.save(savedTask) + } + } + override suspend fun taskChecked( user: User?, taskId: String, @@ -319,7 +337,7 @@ class TaskRepositoryImpl( val savedTask = apiClient.assignToTask(taskID, assignments) ?: return@let savedTask.id = task.id savedTask.position = task.position - localRepository.save(task) + localRepository.save(savedTask) } assignChanges["unassign"]?.let { unassignments -> @@ -330,7 +348,7 @@ class TaskRepositoryImpl( if (savedTask != null) { savedTask.id = task.id savedTask.position = task.position - localRepository.save(task) + localRepository.save(savedTask) } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/PurchaseTypes.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/PurchaseTypes.kt index 0752272be..1977fc29f 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/PurchaseTypes.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/PurchaseTypes.kt @@ -1,7 +1,7 @@ package com.habitrpg.android.habitica.helpers object PurchaseTypes { - const val JubilantGrphatrice = "com.habitrpg.android.habitica.iap.gryphatrice" + const val JubilantGrphatrice = "com.habitrpg.android.habitica.iap.pets.gryphatrice_jubilant" const val Purchase4Gems = "com.habitrpg.android.habitica.iap.4gems" const val Purchase21Gems = "com.habitrpg.android.habitica.iap.21gems" const val Purchase42Gems = "com.habitrpg.android.habitica.iap.42gems" 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 eb5dcceca..594e454f1 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,6 +3,7 @@ package com.habitrpg.android.habitica.models.social import com.google.gson.annotations.SerializedName import com.habitrpg.android.habitica.models.BaseMainObject import com.habitrpg.android.habitica.models.inventory.Quest +import com.habitrpg.android.habitica.models.user.SubscriptionPlan import com.habitrpg.shared.habitica.models.tasks.TasksOrder import io.realm.RealmList import io.realm.RealmObject @@ -11,6 +12,10 @@ import io.realm.annotations.PrimaryKey open class Group : RealmObject(), BaseMainObject { + val isGroupPlan: Boolean + get() { + return purchased?.isActive == true + } override val realmClass: Class get() = Group::class.java override val primaryIdentifier: String? @@ -38,6 +43,7 @@ open class Group : RealmObject(), BaseMainObject { var leaderOnlyChallenges: Boolean = false var leaderOnlyGetGems: Boolean = false var categories: RealmList? = null + var purchased: SubscriptionPlan? = null @Ignore var tasksOrder: TasksOrder? = null diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/models/user/SubscriptionPlan.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/models/user/SubscriptionPlan.kt index f47e7a6f3..366f59dfc 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/models/user/SubscriptionPlan.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/models/user/SubscriptionPlan.kt @@ -18,6 +18,7 @@ open class SubscriptionPlan : RealmObject(), BaseObject { @JvmField var planId: String? = null + var active: Boolean? = null var gemsBought: Int? = null var extraMonths: Int? = null var quantity: Int? = null @@ -34,7 +35,7 @@ open class SubscriptionPlan : RealmObject(), BaseObject { val isActive: Boolean get() { val today = Date() - return customerId != null && (dateTerminated == null || dateTerminated!!.after(today)) + return customerId != null && (dateTerminated == null || dateTerminated!!.after(today) || active == true) } val totalNumberOfGems: Int diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/BirthdayActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/BirthdayActivity.kt index d6c028796..e3805a069 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/BirthdayActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/BirthdayActivity.kt @@ -19,10 +19,10 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.Button import androidx.compose.material.ButtonDefaults +import androidx.compose.material.CircularProgressIndicator import androidx.compose.material.ProvideTextStyle import androidx.compose.material.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -59,7 +59,10 @@ import com.habitrpg.android.habitica.helpers.launchCatching import com.habitrpg.android.habitica.ui.theme.HabiticaTheme import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel import com.habitrpg.android.habitica.ui.views.CurrencyText +import com.habitrpg.android.habitica.ui.views.PixelArtView import com.habitrpg.common.habitica.extensions.DataBindingUtils +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.map import java.text.SimpleDateFormat import java.util.Date @@ -68,13 +71,17 @@ import javax.inject.Inject class BirthdayActivity : BaseActivity() { @Inject lateinit var userViewModel: MainUserViewModel + @Inject lateinit var purchaseHandler: PurchaseHandler + @Inject lateinit var inventoryRepository: InventoryRepository + @Inject lateinit var configManager: AppConfigManager + private val isPurchasing = mutableStateOf(false) private val price = mutableStateOf("") private val hasGryphatrice = mutableStateOf(false) private var gryphatriceProductDetails: ProductDetails? = null @@ -86,20 +93,33 @@ class BirthdayActivity : BaseActivity() { val event = configManager.getBirthdayEvent() setContent { HabiticaTheme { - val user = userViewModel.user.observeAsState() - BirthdayActivityView(hasGryphatrice.value, price.value, event?.start ?: Date(), event?.end ?: Date(), { - gryphatriceProductDetails?.let { - purchaseHandler.purchase(this, it) - } - }, { - lifecycleScope.launchCatching { - inventoryRepository.purchaseItem("", "Gryphatrice-Jubilant", 1) - } - }, { - lifecycleScope.launchCatching { - inventoryRepository.equip("pets", "Gryphatrice-Jubilant") - } - }) + BirthdayActivityView( + isPurchasing.value, + hasGryphatrice.value, + price.value, + event?.start ?: Date(), + event?.end ?: Date(), + { + gryphatriceProductDetails?.let { + isPurchasing.value = true + purchaseHandler.purchase(this, it) + } + }, + { + lifecycleScope.launchCatching({ + isPurchasing.value = false + }) { + isPurchasing.value = true + inventoryRepository.purchaseItem("pets", "Gryphatrice-Jubilant", 1) + userRepository.retrieveUser(false, true) + isPurchasing.value = false + } + }, + { + lifecycleScope.launchCatching { + inventoryRepository.equip("pet", "Gryphatrice-Jubilant") + } + }) } } @@ -112,6 +132,11 @@ class BirthdayActivity : BaseActivity() { hasGryphatrice.value = (it?.trained ?: 0) >= 5 } } + + lifecycleScope.launchCatching { + gryphatriceProductDetails = purchaseHandler.getGryphatriceSKU() + price.value = gryphatriceProductDetails?.oneTimePurchaseOfferDetails?.formattedPrice ?: "" + } } override fun injectActivity(component: UserComponent?) { @@ -150,9 +175,17 @@ fun BirthdayTitle(text: String) { } } - @Composable -fun BirthdayActivityView(hasGryphatrice: Boolean, price: String, startDate: Date, endDate: Date, onPurchaseClick: () -> Unit, onGemPurchaseClick: () -> Unit, onEquipClick: () -> Unit) { +fun BirthdayActivityView( + isPurchasing: Boolean, + hasGryphatrice: Boolean, + price: String, + startDate: Date, + endDate: Date, + onPurchaseClick: () -> Unit, + onGemPurchaseClick: () -> Unit, + onEquipClick: () -> Unit +) { val activity = LocalContext.current as? Activity val dateFormat = SimpleDateFormat("MMM dd", java.util.Locale.getDefault()) val textColor = Color.White @@ -192,8 +225,13 @@ fun BirthdayActivityView(hasGryphatrice: Boolean, price: String, startDate: Date Column( horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier .padding(horizontal = 20.dp) - .fillMaxWidth()) { - Image(painterResource(R.drawable.birthday_header), null, Modifier.padding(bottom = 8.dp)) + .fillMaxWidth() + ) { + Image( + painterResource(R.drawable.birthday_header), + null, + Modifier.padding(bottom = 8.dp) + ) Row(verticalAlignment = Alignment.CenterVertically) { Image(painterResource(R.drawable.birthday_gifts), null) Column( @@ -207,7 +245,11 @@ fun BirthdayActivityView(hasGryphatrice: Boolean, price: String, startDate: Date fontWeight = FontWeight.Bold ) Text( - stringResource(R.string.x_to_y, dateFormat.format(startDate), dateFormat.format(endDate)), + stringResource( + R.string.x_to_y, + dateFormat.format(startDate), + dateFormat.format(endDate) + ), fontSize = 12.sp, color = textColor, fontWeight = FontWeight.Bold @@ -266,6 +308,8 @@ fun BirthdayActivityView(hasGryphatrice: Boolean, price: String, startDate: Date ) { Text(stringResource(R.string.equip)) } + } else if (isPurchasing) { + CircularProgressIndicator() } else { Text(buildAnnotatedString { append("Buy for ") @@ -281,7 +325,7 @@ fun BirthdayActivityView(hasGryphatrice: Boolean, price: String, startDate: Date Color.White, colorResource(R.color.brand_200), { - onPurchaseClick() + onPurchaseClick() }, modifier = Modifier.padding(top = 20.dp) ) { @@ -291,7 +335,7 @@ fun BirthdayActivityView(hasGryphatrice: Boolean, price: String, startDate: Date Color.White, colorResource(R.color.brand_200), { - onGemPurchaseClick() + onGemPurchaseClick() }, modifier = Modifier.padding(top = 20.dp) ) { @@ -314,7 +358,11 @@ fun BirthdayActivityView(hasGryphatrice: Boolean, price: String, startDate: Date Color.White, colorResource(R.color.brand_200), { - onEquipClick() + MainScope().launchCatching { + activity?.finish() + delay(500) + MainNavigationController.navigate(R.id.marketFragment) + } }, modifier = Modifier.padding(top = 20.dp) ) { @@ -328,6 +376,42 @@ fun BirthdayActivityView(hasGryphatrice: Boolean, price: String, startDate: Date textAlign = TextAlign.Center, lineHeight = 20.sp ) + Column(verticalArrangement = Arrangement.spacedBy(14.dp), modifier = Modifier.padding(vertical = 20.dp)) { + Row( + horizontalArrangement = Arrangement.spacedBy(14.dp), + modifier = Modifier.fillMaxWidth() + ) { + FourFreeItem( + day = 1, + title = stringResource(R.string.a_party_robe), + imageName = "", + modifier = Modifier.weight(1f) + ) + FourFreeItem( + day = 1, + title = stringResource(R.string.twenty_gems), + imageName = "", + modifier = Modifier.weight(1f) + ) + } + Row( + horizontalArrangement = Arrangement.spacedBy(14.dp), + modifier = Modifier.fillMaxWidth() + ) { + FourFreeItem( + day = 5, + title = stringResource(R.string.birthday_set), + imageName = "", + modifier = Modifier.weight(1f) + ) + FourFreeItem( + day = 10, + title = stringResource(R.string.background), + imageName = "", + modifier = Modifier.weight(1f) + ) + } + } } Column( horizontalAlignment = Alignment.CenterHorizontally, @@ -369,15 +453,24 @@ fun PotionGrid() { "Peppermint", "Shimmer" ).windowed(4, 4, true) - Column(verticalArrangement = Arrangement.spacedBy(8.dp), horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.padding(top = 20.dp)) { + Column( + verticalArrangement = Arrangement.spacedBy(8.dp), + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.padding(top = 20.dp) + ) { for (potionGroup in potions) { Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { for (potion in potionGroup) { Box( Modifier .size(68.dp) - .background(colorResource(R.color.brand_50), RoundedCornerShape(8.dp))) { - AsyncImage(model = DataBindingUtils.BASE_IMAGE_URL + DataBindingUtils.getFullFilename("Pet_HatchingPotion_$potion"), null, Modifier.size(68.dp)) + .background(colorResource(R.color.brand_50), RoundedCornerShape(8.dp)) + ) { + AsyncImage( + model = DataBindingUtils.BASE_IMAGE_URL + DataBindingUtils.getFullFilename( + "Pet_HatchingPotion_$potion" + ), null, Modifier.size(68.dp) + ) } } } @@ -385,6 +478,34 @@ fun PotionGrid() { } } +@Composable +fun FourFreeItem( + day: Int, + title: String, + imageName: String, + modifier: Modifier = Modifier +) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(18.dp), + modifier = modifier + .background(colorResource(R.color.brand_50), HabiticaTheme.shapes.medium) + .padding(16.dp) + ) { + Text( + stringResource(R.string.day_x, day).uppercase(), + color = colorResource(R.color.yellow_50), + fontSize = 12.sp, + fontWeight = FontWeight.Bold + ) + PixelArtView(imageName, + Modifier + .size(121.dp, 84.dp) + .background(colorResource(R.color.brand_100), HabiticaTheme.shapes.medium)) + Text(title, color = Color.White, fontSize = 16.sp) + } +} + @Composable fun HabiticaButton( background: Color, @@ -413,7 +534,7 @@ fun HabiticaButton( @Preview(device = Devices.PIXEL_4) @Composable private fun Preview() { - BirthdayActivityView(false, "", Date(), Date(), { + BirthdayActivityView(true, false, "", Date(), Date(), { }, {}, {}) } \ No newline at end of file diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/TaskFormActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/TaskFormActivity.kt index 4b6ccca79..78cbee974 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/TaskFormActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/TaskFormActivity.kt @@ -19,7 +19,6 @@ import android.view.View import android.view.ViewGroup import android.view.WindowManager import android.widget.CheckBox -import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.viewModels import androidx.appcompat.widget.AppCompatCheckBox import androidx.compose.runtime.mutableStateListOf @@ -236,6 +235,12 @@ class TaskFormActivity : BaseActivity() { { showAssignDialog() }, + { + taskCompletedMap.remove(it) + lifecycleScope.launchCatching { + task?.let { it1 -> taskRepository.markTaskNeedsWork(it1, it) } + } + }, showEditButton = true ) } 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 3eae1407f..4c36548cc 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 @@ -100,7 +100,7 @@ class RewardsRecyclerViewAdapter( } override fun getItemViewType(position: Int): Int { - return if (customRewards != null && position < customRewardCount) { + return if ((customRewards != null && position < customRewardCount) || (customRewardCount == 0 && inAppRewardCount == 0)) { VIEWTYPE_CUSTOM_REWARD } else { VIEWTYPE_IN_APP_REWARD @@ -139,6 +139,6 @@ class RewardsRecyclerViewAdapter( companion object { private const val VIEWTYPE_CUSTOM_REWARD = 0 - private const val VIEWTYPE_IN_APP_REWARD = 2 + private const val VIEWTYPE_IN_APP_REWARD = 3 } } 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 77211baf3..a0ecd55cf 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 @@ -104,7 +104,7 @@ class NavigationDrawerFragment : DialogFragment() { val context = context adapter = if (context != null) { NavigationDrawerAdapter( - context.getThemeColor(R.attr.colorPrimary), + context.getThemeColor(R.attr.colorPrimaryText), context.getThemeColor(R.attr.colorPrimaryOffset) ) } else { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/ChatFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/ChatFragment.kt index 353e8eb5a..14541b6fe 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/ChatFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/ChatFragment.kt @@ -7,7 +7,6 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.fragment.app.viewModels import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager import com.habitrpg.android.habitica.MainNavDirections @@ -32,7 +31,7 @@ import javax.inject.Inject import kotlin.time.DurationUnit import kotlin.time.toDuration -class ChatFragment() : BaseFragment() { +class ChatFragment : BaseFragment() { override var binding: FragmentChatBinding? = null @@ -40,9 +39,7 @@ class ChatFragment() : BaseFragment() { return FragmentChatBinding.inflate(inflater, container, false) } - val viewModel: GroupViewModel by viewModels( - ownerProducer = { requireParentFragment() } - ) + lateinit var viewModel: GroupViewModel @Inject lateinit var configManager: AppConfigManager diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/TavernFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/TavernFragment.kt index 7ae349903..6d91ad36a 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/TavernFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/TavernFragment.kt @@ -91,7 +91,9 @@ class TavernFragment : BaseMainFragment() { TavernDetailFragment() } 1 -> { - ChatFragment() + val fragment = ChatFragment() + fragment.viewModel = viewModel + fragment } else -> Fragment() } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/guilds/GuildDetailFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/guilds/GuildDetailFragment.kt index 3645a9ec5..c06830de5 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/guilds/GuildDetailFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/guilds/GuildDetailFragment.kt @@ -8,6 +8,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.activity.result.contract.ActivityResultContracts +import androidx.core.view.isVisible import androidx.lifecycle.lifecycleScope import com.habitrpg.android.habitica.MainNavDirections import com.habitrpg.android.habitica.R @@ -197,6 +198,8 @@ class GuildDetailFragment : BaseFragment() { binding?.guildBankText?.text = guild?.gemCount.toString() binding?.guildSummary?.setMarkdown(guild?.summary) binding?.guildDescription?.setMarkdown(guild?.description) + + binding?.inviteButton?.isVisible = guild?.isGroupPlan == true } companion object { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/guilds/GuildFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/guilds/GuildFragment.kt index d2ed0f720..1725970f3 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/guilds/GuildFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/guilds/GuildFragment.kt @@ -154,6 +154,7 @@ class GuildFragment : BaseMainFragment() { } 1 -> { chatFragment = ChatFragment() + chatFragment?.viewModel = viewModel fragment = chatFragment } else -> fragment = Fragment() diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartyFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartyFragment.kt index d416fd700..0f5d48b8e 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartyFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartyFragment.kt @@ -196,6 +196,7 @@ class PartyFragment : BaseMainFragment() { } 1 -> { chatFragment = ChatFragment() + chatFragment?.viewModel = viewModel chatFragment } else -> Fragment() 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 cd86dd2e4..870429bb7 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 @@ -45,7 +45,7 @@ class RewardsRecyclerviewFragment : TaskRecyclerViewFragment() { (layoutManager as? GridLayoutManager)?.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() { override fun getSpanSize(position: Int): Int { - return if ((recyclerAdapter?.getItemViewType(position) ?: 0) < 2) { + return if ((recyclerAdapter?.getItemViewType(position) ?: 0) < 3) { (layoutManager as? GridLayoutManager)?.spanCount ?: 1 } else { 1 diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/ChecklistedViewHolder.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/ChecklistedViewHolder.kt index 0941f0e78..704c29203 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/ChecklistedViewHolder.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/ChecklistedViewHolder.kt @@ -207,7 +207,7 @@ abstract class ChecklistedViewHolder( override fun onLeftActionTouched() { super.onLeftActionTouched() - if (task?.isValid == true) { + if (task?.isValid == true && !isLocked) { onCheckedChanged(!(task?.completed(userID) ?: false)) } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/HabitViewHolder.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/HabitViewHolder.kt index 10662021b..cca29990d 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/HabitViewHolder.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/HabitViewHolder.kt @@ -137,12 +137,16 @@ class HabitViewHolder( override fun onLeftActionTouched() { super.onLeftActionTouched() - onPlusButtonClicked() + if (!isLocked) { + onPlusButtonClicked() + } } override fun onRightActionTouched() { super.onRightActionTouched() - onMinusButtonClicked() + if (!isLocked) { + onMinusButtonClicked() + } } private fun onPlusButtonClicked() { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/GroupViewModel.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/GroupViewModel.kt index 06f351c1b..8e9d34100 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/GroupViewModel.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/GroupViewModel.kt @@ -202,10 +202,13 @@ open class GroupViewModel(initializeComponent: Boolean) : BaseViewModel(initiali } fun likeMessage(message: ChatMessage) { - val index = _chatMessages.value?.indexOf(message) - if (index == null || index < 0) return viewModelScope.launchCatching { val message = socialRepository.likeMessage(message) + val index = _chatMessages.value?.indexOfFirst { it.id == message?.id } + if (index == null || index < 0) { + retrieveGroupChat { } + return@launchCatching + } val list = _chatMessages.value?.toMutableList() if (message != null) { list?.set(index, message) @@ -246,7 +249,10 @@ open class GroupViewModel(initializeComponent: Boolean) : BaseViewModel(initiali } fun retrieveGroupChat(onComplete: () -> Unit) { - val groupID = groupID + var groupID = groupID + if (groupViewType == GroupViewType.PARTY) { + groupID = "party" + } if (groupID.isNullOrEmpty()) { onComplete() return diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/UserRow.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/UserRow.kt index 164798779..76633d458 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/UserRow.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/UserRow.kt @@ -26,6 +26,7 @@ fun UserRow( username: String, avatar: Avatar?, modifier: Modifier = Modifier, + mainContentModifier: Modifier = Modifier, extraContent: @Composable (() -> Unit)? = null, endContent: @Composable (() -> Unit)? = null, color: Color? = null @@ -46,7 +47,7 @@ fun UserRow( } } - Column { + Column(mainContentModifier) { Text( "@$username", fontSize = 16.sp, diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/tasks/AssignedView.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/tasks/AssignedView.kt index 839c3cc4f..6c76f2203 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/tasks/AssignedView.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/tasks/AssignedView.kt @@ -3,23 +3,31 @@ package com.habitrpg.android.habitica.ui.views.tasks import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.models.Assignable +import com.habitrpg.android.habitica.ui.theme.HabiticaTheme import com.habitrpg.android.habitica.ui.views.CompletedAt import com.habitrpg.android.habitica.ui.views.UserRow import java.util.Date @@ -31,6 +39,7 @@ fun AssignedView( backgroundColor: Color, color: Color, onEditClick: () -> Unit, + onUndoClick: (String) -> Unit, modifier: Modifier = Modifier, showEditButton: Boolean = false ) { @@ -41,14 +50,18 @@ fun AssignedView( backgroundColor, MaterialTheme.shapes.medium ) - .padding(15.dp, 12.dp) - .heightIn(min = 24.dp) + .heightIn(min = 66.dp) + .padding(start = 16.dp) .fillMaxWidth() + .clip(MaterialTheme.shapes.medium) for (assignable in assigned) { UserRow( username = assignable.identifiableName, avatar = assignable.avatar, modifier = rowModifier, + mainContentModifier = Modifier + .padding(vertical = 12.dp) + .heightIn(min = 24.dp), color = color, extraContent = { completedAt[assignable.id]?.let { CompletedAt(completedAt = it) } @@ -56,7 +69,9 @@ fun AssignedView( endContent = { completedAt[assignable.id]?.let { if (showEditButton) { - UndoTaskCompletion() + UndoTaskCompletion(Modifier.clickable { + assignable.id?.let { it1 -> onUndoClick(it1) } + }) } } } @@ -90,5 +105,28 @@ fun AssignedView( } @Composable -fun UndoTaskCompletion() { +fun UndoTaskCompletion(modifier: Modifier = Modifier) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + modifier = modifier + .width(51.dp) + .heightIn(min = 66.dp) + .fillMaxHeight() + .background(HabiticaTheme.colors.contentBackgroundOffset) + ) { + Image( + painterResource(R.drawable.checkmark), + null, + contentScale = ContentScale.None, + modifier = Modifier + .size(24.dp) + .background(HabiticaTheme.colors.windowBackground, HabiticaTheme.shapes.small) + ) + Text( + stringResource(R.string.undo), + fontSize = 12.sp, + color = HabiticaTheme.colors.textSecondary + ) + } } diff --git a/version.properties b/version.properties index 8c685bc20..91ea3ae7f 100644 --- a/version.properties +++ b/version.properties @@ -1,2 +1,2 @@ NAME=4.1 -CODE=4991 \ No newline at end of file +CODE=5031 \ No newline at end of file From 6222b24df63ecbca181c475a94671bdffedc82ee Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Fri, 13 Jan 2023 12:05:29 +0100 Subject: [PATCH 09/22] Fix some tests --- Habitica/res/layout/activity_main_content.xml | 1 + Habitica/res/values/strings.xml | 1 + .../ui/fragments/PartyDetailFragmentTest.kt | 4 +- .../ui/fragments/StatsFragmentTest.kt | 15 +- .../inventory/stable/PetDetailFragmentTest.kt | 28 +- .../stable/StableRecyclerFragmentTest.kt | 6 +- .../android/habitica/data/TaskRepository.kt | 1 + .../data/implementation/TaskRepositoryImpl.kt | 2 + .../ui/activities/BirthdayActivity.kt | 489 ++++++++++-------- .../social/guilds/GuildDetailFragment.kt | 2 +- .../implementation/TaskRepositoryImplTest.kt | 93 ++-- 11 files changed, 330 insertions(+), 312 deletions(-) diff --git a/Habitica/res/layout/activity_main_content.xml b/Habitica/res/layout/activity_main_content.xml index 08a09a097..3203655ea 100644 --- a/Habitica/res/layout/activity_main_content.xml +++ b/Habitica/res/layout/activity_main_content.xml @@ -96,6 +96,7 @@ app:layout_collapseMode="pin" app:tabGravity="fill" app:tabIndicatorColor="?textColorPrimary" + app:tabSelectedTextColor="?textColorPrimary" app:tabMode="fixed" /> 20 Gems Background Birthday Set + You equipped %s You diff --git a/Habitica/src/androidTest/java/com/habitrpg/android/habitica/ui/fragments/PartyDetailFragmentTest.kt b/Habitica/src/androidTest/java/com/habitrpg/android/habitica/ui/fragments/PartyDetailFragmentTest.kt index e0933e63e..82687b6de 100644 --- a/Habitica/src/androidTest/java/com/habitrpg/android/habitica/ui/fragments/PartyDetailFragmentTest.kt +++ b/Habitica/src/androidTest/java/com/habitrpg/android/habitica/ui/fragments/PartyDetailFragmentTest.kt @@ -17,7 +17,7 @@ import io.github.kakaocup.kakao.text.KTextView import io.mockk.every import io.mockk.mockk import io.mockk.spyk -import io.reactivex.rxjava3.core.Flowable +import kotlinx.coroutines.flow.flowOf import org.junit.Test import org.junit.runner.RunWith @@ -38,7 +38,7 @@ class PartyDetailFragmentTest : FragmentTestCase() { @@ -35,12 +37,12 @@ class PetDetailScreen : Screen() { internal class PetDetailRecyclerFragmentTest : FragmentTestCase(false) { override fun makeFragment() { - every { inventoryRepository.getOwnedPets() } returns Flowable.just(user.items?.pets!!) - every { inventoryRepository.getOwnedMounts() } returns Flowable.just(user.items?.mounts!!) - every { inventoryRepository.getOwnedItems("food") } returns Flowable.just(user.items?.food!!.filter { it.numberOwned > 0 }) + every { inventoryRepository.getOwnedPets() } returns flowOf(user.items?.pets!!) + every { inventoryRepository.getOwnedMounts() } returns flowOf(user.items?.mounts!!) + every { inventoryRepository.getOwnedItems("food") } returns flowOf(user.items?.food!!.filter { it.numberOwned > 0 }) val saddle = OwnedItem() saddle.numberOwned = 1 - every { inventoryRepository.getOwnedItems(true) } returns Flowable.just( + every { inventoryRepository.getOwnedItems(true) } returns flowOf( mapOf( Pair( "Saddle-food", @@ -69,21 +71,21 @@ internal class PetDetailRecyclerFragmentTest : @Test fun canFeedPet() { val slot = CapturingSlot() - every { feedPetUseCase.callInteractor(capture(slot)) } returns mockk(relaxed = true) + coEvery { feedPetUseCase.callInteractor(capture(slot)) } returns mockk(relaxed = true) every { inventoryRepository.getPets( any(), any(), any() ) - } returns Flowable.just(content.pets.filter { it.animal == "Cactus" }) + } returns flowOf(content.pets.filter { it.animal == "Cactus" }) every { inventoryRepository.getMounts( any(), any(), any() ) - } returns Flowable.just(content.mounts.filter { it.animal == "Cactus" }) + } returns flowOf(content.mounts.filter { it.animal == "Cactus" }) launchFragment( PetDetailRecyclerFragmentArgs.Builder("cactus", "drop", "").build().toBundle() ) @@ -92,7 +94,7 @@ internal class PetDetailRecyclerFragmentTest : childWith { withContentDescription("Skeleton Cactus") }.click() KView { withText(R.string.feed) }.click() KView { withText("Meat") }.click() - verify { feedPetUseCase.callInteractor(any()) } + coVerify { feedPetUseCase.callInteractor(any()) } slot.captured.pet.key shouldBe "Cactus-Skeleton" slot.captured.food.key shouldBe "Meat" } @@ -102,27 +104,27 @@ internal class PetDetailRecyclerFragmentTest : @Test fun canUseSaddle() { val slot = CapturingSlot() - every { feedPetUseCase.callInteractor(capture(slot)) } returns mockk(relaxed = true) + coEvery { feedPetUseCase.callInteractor(capture(slot)) } returns mockk(relaxed = true) every { inventoryRepository.getPets( any(), any(), any() ) - } returns Flowable.just(content.pets.filter { it.animal == "Fox" }) + } returns flowOf(content.pets.filter { it.animal == "Fox" }) every { inventoryRepository.getMounts( any(), any(), any() ) - } returns Flowable.just(content.mounts.filter { it.animal == "Fox" }) + } returns flowOf(content.mounts.filter { it.animal == "Fox" }) launchFragment(PetDetailRecyclerFragmentArgs.Builder("fox", "drop", "").build().toBundle()) screen { recycler { childWith { withContentDescription("Shade Fox") }.click() KView { withText(R.string.use_saddle) }.click() - verify { feedPetUseCase.callInteractor(any()) } + coVerify { feedPetUseCase.callInteractor(any()) } slot.captured.pet.key shouldBe "Fox-Shade" slot.captured.food.key shouldBe "Saddle" } diff --git a/Habitica/src/androidTest/java/com/habitrpg/android/habitica/ui/fragments/inventory/stable/StableRecyclerFragmentTest.kt b/Habitica/src/androidTest/java/com/habitrpg/android/habitica/ui/fragments/inventory/stable/StableRecyclerFragmentTest.kt index 11e44c7d8..fcfbee05a 100644 --- a/Habitica/src/androidTest/java/com/habitrpg/android/habitica/ui/fragments/inventory/stable/StableRecyclerFragmentTest.kt +++ b/Habitica/src/androidTest/java/com/habitrpg/android/habitica/ui/fragments/inventory/stable/StableRecyclerFragmentTest.kt @@ -15,7 +15,7 @@ import io.github.kakaocup.kakao.text.KTextView import io.mockk.every import io.mockk.spyk import io.mockk.verify -import io.reactivex.rxjava3.core.Flowable +import kotlinx.coroutines.flow.flowOf import org.hamcrest.Matcher import org.junit.Test @@ -40,8 +40,8 @@ class StableScreen : Screen() { internal class StableRecyclerFragmentTest : FragmentTestCase(false) { override fun makeFragment() { - every { inventoryRepository.getOwnedPets() } returns Flowable.just(user.items?.pets!!) - every { inventoryRepository.getOwnedMounts() } returns Flowable.just(user.items?.mounts!!) + every { inventoryRepository.getOwnedPets() } returns flowOf(user.items?.pets!!) + every { inventoryRepository.getOwnedMounts() } returns flowOf(user.items?.mounts!!) fragment = spyk() fragment.shouldInitializeComponent = false fragment.itemType = "pets" diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/TaskRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/TaskRepository.kt index b768f2623..b0d8a7676 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/TaskRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/TaskRepository.kt @@ -68,4 +68,5 @@ interface TaskRepository : BaseRepository { fun getTasksForChallenge(challengeID: String?): Flow> suspend fun bulkScoreTasks(data: List>): BulkTaskScoringData? suspend fun markTaskNeedsWork(task: Task, userID: String) + } 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 c08f94933..bb752f7a7 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 @@ -196,7 +196,9 @@ class TaskRepositoryImpl( override suspend fun markTaskNeedsWork(task: Task, userID: String) { val savedTask = apiClient.markTaskNeedsWork(task.id ?: "", userID) if (savedTask != null) { + savedTask.id = task.id savedTask.position = task.position + savedTask.group?.assignedUsersDetail?.firstOrNull { it.assignedUserID == userID }?.completed = false localRepository.save(savedTask) } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/BirthdayActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/BirthdayActivity.kt index e3805a069..26fe4e7d4 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/BirthdayActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/BirthdayActivity.kt @@ -1,6 +1,7 @@ package com.habitrpg.android.habitica.ui.activities import android.app.Activity +import android.content.res.Configuration.UI_MODE_NIGHT_YES import android.os.Bundle import androidx.activity.compose.setContent import androidx.compose.foundation.Image @@ -20,9 +21,16 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material.Button import androidx.compose.material.ButtonDefaults import androidx.compose.material.CircularProgressIndicator +import androidx.compose.material.DrawerState +import androidx.compose.material.DrawerValue import androidx.compose.material.ProvideTextStyle +import androidx.compose.material.Scaffold +import androidx.compose.material.ScaffoldState +import androidx.compose.material.SnackbarHostState import androidx.compose.material.Text +import androidx.compose.material.rememberScaffoldState import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -49,6 +57,7 @@ import androidx.compose.ui.unit.sp import androidx.lifecycle.lifecycleScope import coil.compose.AsyncImage import com.android.billingclient.api.ProductDetails +import com.google.accompanist.systemuicontroller.rememberSystemUiController import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.components.UserComponent import com.habitrpg.android.habitica.data.InventoryRepository @@ -81,6 +90,7 @@ class BirthdayActivity : BaseActivity() { @Inject lateinit var configManager: AppConfigManager + val scaffoldState: ScaffoldState = ScaffoldState(DrawerState(DrawerValue.Closed), SnackbarHostState()) private val isPurchasing = mutableStateOf(false) private val price = mutableStateOf("") private val hasGryphatrice = mutableStateOf(false) @@ -94,6 +104,7 @@ class BirthdayActivity : BaseActivity() { setContent { HabiticaTheme { BirthdayActivityView( + scaffoldState, isPurchasing.value, hasGryphatrice.value, price.value, @@ -114,12 +125,13 @@ class BirthdayActivity : BaseActivity() { userRepository.retrieveUser(false, true) isPurchasing.value = false } - }, - { - lifecycleScope.launchCatching { - inventoryRepository.equip("pet", "Gryphatrice-Jubilant") - } - }) + } + ) { + lifecycleScope.launchCatching { + inventoryRepository.equip("pet", "Gryphatrice-Jubilant") + scaffoldState.snackbarHostState.showSnackbar(getString(R.string.you_equipped_x, getString(R.string.animated_gryphatrice_pet))) + } + } } } @@ -177,6 +189,7 @@ fun BirthdayTitle(text: String) { @Composable fun BirthdayActivityView( + scaffoldState: ScaffoldState, isPurchasing: Boolean, hasGryphatrice: Boolean, price: String, @@ -191,250 +204,269 @@ fun BirthdayActivityView( val textColor = Color.White val specialTextColor = colorResource(R.color.yellow_50) - Column( - horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier - .background( - Brush.verticalGradient( - Pair(0.0f, colorResource(id = R.color.brand_300)), - Pair(1.0f, colorResource(id = R.color.brand_200)) - ) - ) - .fillMaxWidth() - .verticalScroll(rememberScrollState()) - ) { - Button( - onClick = { - if (activity != null) { - activity.finish() - return@Button - } - MainNavigationController.navigateBack() - }, - colors = ButtonDefaults.textButtonColors(contentColor = textColor), - elevation = ButtonDefaults.elevation(0.dp, 0.dp), - modifier = Modifier.align(Alignment.Start) - ) { - Image( - painterResource(R.drawable.arrow_back), - stringResource(R.string.action_back), - colorFilter = ColorFilter.tint( - textColor - ) - ) - } + val systemUiController = rememberSystemUiController() + val statusbarColor = colorResource(R.color.brand_300) + val navigationbarColor = colorResource(R.color.brand_50) + DisposableEffect(systemUiController) { + systemUiController.setStatusBarColor(statusbarColor, darkIcons = false) + systemUiController.setNavigationBarColor(navigationbarColor) + onDispose {} + } + + Scaffold( + scaffoldState = scaffoldState + ) { padding -> Column( horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier - .padding(horizontal = 20.dp) - .fillMaxWidth() - ) { - Image( - painterResource(R.drawable.birthday_header), - null, - Modifier.padding(bottom = 8.dp) - ) - Row(verticalAlignment = Alignment.CenterVertically) { - Image(painterResource(R.drawable.birthday_gifts), null) - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier.padding(horizontal = 22.dp) - ) { - Text( - stringResource(id = R.string.limited_event).toUpperCase(Locale.current), - fontSize = 12.sp, - color = specialTextColor, - fontWeight = FontWeight.Bold + .background( + Brush.verticalGradient( + Pair(0.0f, colorResource(id = R.color.brand_300)), + Pair(1.0f, colorResource(id = R.color.brand_200)) ) + ) + .fillMaxWidth() + .verticalScroll(rememberScrollState()) + .padding(padding) + ) { + Button( + onClick = { + if (activity != null) { + activity.finish() + return@Button + } + MainNavigationController.navigateBack() + }, + colors = ButtonDefaults.textButtonColors(contentColor = textColor), + elevation = ButtonDefaults.elevation(0.dp, 0.dp), + modifier = Modifier.align(Alignment.Start) + ) { + Image( + painterResource(R.drawable.arrow_back), + stringResource(R.string.action_back), + colorFilter = ColorFilter.tint( + textColor + ) + ) + } + Column( + horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier + .padding(horizontal = 20.dp) + .fillMaxWidth() + ) { + Image( + painterResource(R.drawable.birthday_header), + null, + Modifier.padding(bottom = 8.dp) + ) + Row(verticalAlignment = Alignment.CenterVertically) { + Image(painterResource(R.drawable.birthday_gifts), null) + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.padding(horizontal = 22.dp) + ) { + Text( + stringResource(id = R.string.limited_event).toUpperCase(Locale.current), + fontSize = 12.sp, + color = specialTextColor, + fontWeight = FontWeight.Bold + ) + Text( + stringResource( + R.string.x_to_y, + dateFormat.format(startDate), + dateFormat.format(endDate) + ), + fontSize = 12.sp, + color = textColor, + fontWeight = FontWeight.Bold + ) + } + // right image should be flipped + Image( + painterResource(id = R.drawable.birthday_gifts), + null, + modifier = Modifier.scale(-1f, 1f) + ) + } + Text( + stringResource(R.string.birthday_title_description), + fontSize = 16.sp, + color = specialTextColor, + fontWeight = FontWeight.SemiBold, + textAlign = TextAlign.Center, + modifier = Modifier.padding(top = 22.dp) + ) + BirthdayTitle(stringResource(id = R.string.animated_gryphatrice_pet)) + Box( + Modifier + .size(161.dp, 129.dp) + .padding(vertical = 20.dp) + .background(colorResource(R.color.brand_50), RoundedCornerShape(8.dp)) + ) { + + } + Text( + stringResource(R.string.limited_edition).toUpperCase(Locale.current), + fontSize = 12.sp, + color = specialTextColor, + fontWeight = FontWeight.Bold + ) + Text( + stringResource(R.string.gryphatrice_description), + fontSize = 16.sp, + color = textColor, + textAlign = TextAlign.Center, + lineHeight = 20.sp, + modifier = Modifier.padding(bottom = 16.dp) + ) + if (hasGryphatrice) { Text( - stringResource( - R.string.x_to_y, - dateFormat.format(startDate), - dateFormat.format(endDate) - ), + stringResource(R.string.thanks_for_support), fontSize = 12.sp, color = textColor, - fontWeight = FontWeight.Bold + fontWeight = FontWeight.SemiBold ) + HabiticaButton( + Color.White, + colorResource(R.color.brand_200), + { + onEquipClick() + }, + modifier = Modifier.padding(top = 20.dp) + ) { + Text(stringResource(R.string.equip)) + } + } else if (isPurchasing) { + CircularProgressIndicator() + } else { + Text(buildAnnotatedString { + append("Buy for ") + withStyle(SpanStyle(fontWeight = FontWeight.Bold)) { + append(price) + } + append(" or ") + withStyle(SpanStyle(fontWeight = FontWeight.Bold)) { + append("60 Gems") + } + }, color = Color.White) + HabiticaButton( + Color.White, + colorResource(R.color.brand_200), + { + onPurchaseClick() + }, + modifier = Modifier.padding(top = 20.dp) + ) { + Text(stringResource(R.string.buy_for_x, price)) + } + HabiticaButton( + Color.White, + colorResource(R.color.brand_200), + { + onGemPurchaseClick() + }, + modifier = Modifier.padding(top = 20.dp) + ) { + Row(verticalAlignment = Alignment.CenterVertically) { + Text(stringResource(R.string.buy_for)) + CurrencyText(currency = "gems", value = 60) + } + } } - // right image should be flipped - Image( - painterResource(id = R.drawable.birthday_gifts), - null, - modifier = Modifier.scale(-1f, 1f) - ) - } - Text( - stringResource(R.string.birthday_title_description), - fontSize = 16.sp, - color = specialTextColor, - fontWeight = FontWeight.SemiBold, - textAlign = TextAlign.Center, - modifier = Modifier.padding(top = 22.dp) - ) - BirthdayTitle(stringResource(id = R.string.animated_gryphatrice_pet)) - Box( - Modifier - .size(161.dp, 129.dp) - .padding(vertical = 20.dp) - .background(colorResource(R.color.brand_50), RoundedCornerShape(8.dp)) - ) { - - } - Text( - stringResource(R.string.limited_edition).toUpperCase(Locale.current), - fontSize = 12.sp, - color = specialTextColor, - fontWeight = FontWeight.Bold - ) - Text( - stringResource(R.string.gryphatrice_description), - fontSize = 16.sp, - color = textColor, - textAlign = TextAlign.Center, - lineHeight = 20.sp, - modifier = Modifier.padding(bottom = 16.dp) - ) - if (hasGryphatrice) { + BirthdayTitle(stringResource(id = R.string.plenty_of_potions)) Text( - stringResource(R.string.thanks_for_support), - fontSize = 12.sp, + stringResource(R.string.plenty_of_potions_description), + fontSize = 16.sp, color = textColor, - fontWeight = FontWeight.SemiBold + textAlign = TextAlign.Center, + lineHeight = 20.sp ) - HabiticaButton( - Color.White, - colorResource(R.color.brand_200), - {}, - modifier = Modifier.padding(top = 20.dp) - ) { - Text(stringResource(R.string.equip)) - } - } else if (isPurchasing) { - CircularProgressIndicator() - } else { - Text(buildAnnotatedString { - append("Buy for ") - withStyle(SpanStyle(fontWeight = FontWeight.Bold)) { - append(price) - } - append(" or ") - withStyle(SpanStyle(fontWeight = FontWeight.Bold)) { - append("60 Gems") - } - }, color = Color.White) + PotionGrid() HabiticaButton( Color.White, colorResource(R.color.brand_200), { - onPurchaseClick() + MainScope().launchCatching { + activity?.finish() + delay(500) + MainNavigationController.navigate(R.id.marketFragment) + } }, modifier = Modifier.padding(top = 20.dp) ) { - Text(stringResource(R.string.buy_for_x, "")) + Text(stringResource(R.string.visit_the_market)) } - HabiticaButton( - Color.White, - colorResource(R.color.brand_200), - { - onGemPurchaseClick() - }, - modifier = Modifier.padding(top = 20.dp) + BirthdayTitle(stringResource(id = R.string.for_for_free)) + Text( + stringResource(R.string.for_for_free_description), + fontSize = 16.sp, + color = textColor, + textAlign = TextAlign.Center, + lineHeight = 20.sp + ) + Column( + verticalArrangement = Arrangement.spacedBy(14.dp), + modifier = Modifier.padding(vertical = 20.dp) ) { - Row(verticalAlignment = Alignment.CenterVertically) { - Text(stringResource(R.string.buy_for)) - CurrencyText(currency = "gems", value = 60) + Row( + horizontalArrangement = Arrangement.spacedBy(14.dp), + modifier = Modifier.fillMaxWidth() + ) { + FourFreeItem( + day = 1, + title = stringResource(R.string.a_party_robe), + imageName = "", + modifier = Modifier.weight(1f) + ) + FourFreeItem( + day = 1, + title = stringResource(R.string.twenty_gems), + imageName = "", + modifier = Modifier.weight(1f) + ) + } + Row( + horizontalArrangement = Arrangement.spacedBy(14.dp), + modifier = Modifier.fillMaxWidth() + ) { + FourFreeItem( + day = 5, + title = stringResource(R.string.birthday_set), + imageName = "", + modifier = Modifier.weight(1f) + ) + FourFreeItem( + day = 10, + title = stringResource(R.string.background), + imageName = "", + modifier = Modifier.weight(1f) + ) } } } - BirthdayTitle(stringResource(id = R.string.plenty_of_potions)) - Text( - stringResource(R.string.plenty_of_potions_description), - fontSize = 16.sp, - color = textColor, - textAlign = TextAlign.Center, - lineHeight = 20.sp - ) - PotionGrid() - HabiticaButton( - Color.White, - colorResource(R.color.brand_200), - { - MainScope().launchCatching { - activity?.finish() - delay(500) - MainNavigationController.navigate(R.id.marketFragment) - } - }, - modifier = Modifier.padding(top = 20.dp) + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier + .padding(top = 20.dp) + .background(colorResource(R.color.brand_50)) + .padding(horizontal = 20.dp) + .padding(top = 20.dp, bottom = 60.dp) ) { - Text(stringResource(R.string.visit_the_market)) + Text( + stringResource(R.string.limitations), + fontSize = 16.sp, + color = colorResource(R.color.brand_600), + fontWeight = FontWeight.Bold, + textAlign = TextAlign.Center + ) + Text( + stringResource(R.string.birthday_limitations), + fontSize = 14.sp, + color = colorResource(R.color.brand_600), + lineHeight = 20.sp, + textAlign = TextAlign.Center + ) } - BirthdayTitle(stringResource(id = R.string.for_for_free)) - Text( - stringResource(R.string.for_for_free_description), - fontSize = 16.sp, - color = textColor, - textAlign = TextAlign.Center, - lineHeight = 20.sp - ) - Column(verticalArrangement = Arrangement.spacedBy(14.dp), modifier = Modifier.padding(vertical = 20.dp)) { - Row( - horizontalArrangement = Arrangement.spacedBy(14.dp), - modifier = Modifier.fillMaxWidth() - ) { - FourFreeItem( - day = 1, - title = stringResource(R.string.a_party_robe), - imageName = "", - modifier = Modifier.weight(1f) - ) - FourFreeItem( - day = 1, - title = stringResource(R.string.twenty_gems), - imageName = "", - modifier = Modifier.weight(1f) - ) - } - Row( - horizontalArrangement = Arrangement.spacedBy(14.dp), - modifier = Modifier.fillMaxWidth() - ) { - FourFreeItem( - day = 5, - title = stringResource(R.string.birthday_set), - imageName = "", - modifier = Modifier.weight(1f) - ) - FourFreeItem( - day = 10, - title = stringResource(R.string.background), - imageName = "", - modifier = Modifier.weight(1f) - ) - } - } - } - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier - .padding(top = 20.dp) - .background(colorResource(R.color.brand_50)) - .padding(horizontal = 20.dp) - .padding(top = 20.dp, bottom = 60.dp) - ) { - Text( - stringResource(R.string.limitations), - fontSize = 16.sp, - color = colorResource(R.color.brand_600), - fontWeight = FontWeight.Bold, - textAlign = TextAlign.Center - ) - Text( - stringResource(R.string.birthday_limitations), - fontSize = 14.sp, - color = colorResource(R.color.brand_600), - lineHeight = 20.sp, - textAlign = TextAlign.Center - ) } } } @@ -532,9 +564,10 @@ fun HabiticaButton( } @Preview(device = Devices.PIXEL_4) +@Preview(device = Devices.PIXEL_4, uiMode = UI_MODE_NIGHT_YES) @Composable private fun Preview() { - BirthdayActivityView(true, false, "", Date(), Date(), { - - }, {}, {}) + val scaffoldState = rememberScaffoldState() + BirthdayActivityView(scaffoldState, true, false, "", Date(), Date(), { + }, {}) {} } \ No newline at end of file diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/guilds/GuildDetailFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/guilds/GuildDetailFragment.kt index c06830de5..4559e8305 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/guilds/GuildDetailFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/guilds/GuildDetailFragment.kt @@ -199,7 +199,7 @@ class GuildDetailFragment : BaseFragment() { binding?.guildSummary?.setMarkdown(guild?.summary) binding?.guildDescription?.setMarkdown(guild?.description) - binding?.inviteButton?.isVisible = guild?.isGroupPlan == true + binding?.inviteButton?.isVisible = guild?.isGroupPlan != true } companion object { diff --git a/Habitica/src/test/java/com/habitrpg/android/habitica/data/implementation/TaskRepositoryImplTest.kt b/Habitica/src/test/java/com/habitrpg/android/habitica/data/implementation/TaskRepositoryImplTest.kt index bfa0ada14..8c03c44d4 100644 --- a/Habitica/src/test/java/com/habitrpg/android/habitica/data/implementation/TaskRepositoryImplTest.kt +++ b/Habitica/src/test/java/com/habitrpg/android/habitica/data/implementation/TaskRepositoryImplTest.kt @@ -4,27 +4,27 @@ import com.habitrpg.android.habitica.data.ApiClient import com.habitrpg.android.habitica.data.TaskRepository import com.habitrpg.android.habitica.data.local.TaskLocalRepository import com.habitrpg.android.habitica.models.BaseObject -import com.habitrpg.shared.habitica.models.responses.TaskDirectionData -import com.habitrpg.shared.habitica.models.responses.TaskScoringResult import com.habitrpg.android.habitica.models.tasks.Task import com.habitrpg.android.habitica.models.tasks.TaskList -import com.habitrpg.shared.habitica.models.tasks.TaskType -import com.habitrpg.shared.habitica.models.tasks.TasksOrder import com.habitrpg.android.habitica.models.user.Stats import com.habitrpg.android.habitica.models.user.User +import com.habitrpg.shared.habitica.models.responses.TaskDirectionData +import com.habitrpg.shared.habitica.models.tasks.TaskType +import com.habitrpg.shared.habitica.models.tasks.TasksOrder import io.kotest.common.ExperimentalKotest import io.kotest.core.spec.style.WordSpec import io.kotest.framework.concurrency.eventually import io.kotest.matchers.shouldBe import io.mockk.clearAllMocks +import io.mockk.coEvery +import io.mockk.coVerify import io.mockk.every import io.mockk.mockk import io.mockk.slot import io.mockk.spyk import io.mockk.verify -import io.reactivex.rxjava3.core.Flowable -import io.reactivex.subscribers.TestSubscriber import io.realm.Realm +import kotlinx.coroutines.flow.flowOf import java.util.UUID @OptIn(ExperimentalKotest::class) @@ -52,12 +52,10 @@ class TaskRepositoryImplTest : WordSpec({ "retrieveTasks" should { "save tasks locally" { val list = TaskList() - every { apiClient.tasks } returns Flowable.just(list) + coEvery { apiClient.getTasks() } returns list every { localRepository.saveTasks("", any(), any()) } returns Unit val order = TasksOrder() - val subscriber = TestSubscriber() - repository.retrieveTasks("", order).subscribe(subscriber) - subscriber.assertComplete() + repository.retrieveTasks("", order) verify { localRepository.saveTasks("", order, list) } } } @@ -70,32 +68,25 @@ class TaskRepositoryImplTest : WordSpec({ user.stats = Stats() } "debounce" { - every { apiClient.postTaskDirection(any(), "up") } returns Flowable.just( - TaskDirectionData() - ) - repository.taskChecked(user, task, true, false, null).subscribe() - repository.taskChecked(user, task, true, false, null).subscribe() - verify(exactly = 1) { apiClient.postTaskDirection(any(), any()) } + coEvery { apiClient.postTaskDirection(any(), "up") } returns TaskDirectionData() + repository.taskChecked(user, task, true, false, null) + repository.taskChecked(user, task, true, false, null) + coVerify(exactly = 1) { apiClient.postTaskDirection(any(), any()) } } "get user if not passed" { - every { apiClient.postTaskDirection(any(), "up") } returns Flowable.just( - TaskDirectionData() - ) - every { localRepository.getUserFlowable("") } returns Flowable.just(user) + coEvery { apiClient.postTaskDirection(any(), "up") } returns TaskDirectionData() + coEvery { localRepository.getUser("") } returns flowOf(user) repository.taskChecked(null, task, true, false, null) eventually(5000) { - localRepository.getUserFlowable("") + localRepository.getUser("") } } "does not update user for team tasks" { val data = TaskDirectionData() data.lvl = 0 - every { apiClient.postTaskDirection(any(), "up") } returns Flowable.just(data) - val subscriber = TestSubscriber() - repository.taskChecked(user, task, true, false, null).subscribe(subscriber) - subscriber.assertComplete() + coEvery { apiClient.postTaskDirection(any(), "up") } returns data + repository.taskChecked(user, task, true, false, null) verify(exactly = 0) { user.stats } - subscriber.values().first().level shouldBe null } "builds task result correctly" { val data = TaskDirectionData() @@ -106,67 +97,53 @@ class TaskRepositoryImplTest : WordSpec({ user.stats?.lvl = 10 user.stats?.hp = 8.0 user.stats?.mp = 4.0 - every { apiClient.postTaskDirection(any(), "up") } returns Flowable.just(data) - val subscriber = TestSubscriber() - repository.taskChecked(user, task, true, false, null).subscribe(subscriber) - subscriber.assertComplete() - subscriber.values().first().level shouldBe 10 - subscriber.values().first().healthDelta shouldBe 12.0 - subscriber.values().first().manaDelta shouldBe 26.0 - subscriber.values().first().hasLeveledUp shouldBe false + coEvery { apiClient.postTaskDirection(any(), "up") } returns data + val result = repository.taskChecked(user, task, true, false, null) + result?.level shouldBe 10 + result?.healthDelta shouldBe 12.0 + result?.manaDelta shouldBe 26.0 + result?.hasLeveledUp shouldBe false } "set hasLeveledUp correctly" { - val subscriber = TestSubscriber() val data = TaskDirectionData() data.lvl = 11 user.stats?.lvl = 10 - every { apiClient.postTaskDirection(any(), "up") } returns Flowable.just(data) - repository.taskChecked(user, task, true, false, null).subscribe(subscriber) - - subscriber.assertComplete() - subscriber.values().first().level shouldBe 11 - subscriber.values().first().hasLeveledUp shouldBe true + coEvery { apiClient.postTaskDirection(any(), "up") } returns data + val result = repository.taskChecked(user, task, true, false, null) + result?.level shouldBe 11 + result?.hasLeveledUp shouldBe true } "handle stats not being there" { - val subscriber = TestSubscriber() val data = TaskDirectionData() data.lvl = 1 user.stats = null - every { apiClient.postTaskDirection(any(), "up") } returns Flowable.just(data) - repository.taskChecked(user, task, true, false, null).subscribe(subscriber) - subscriber.assertComplete() + coEvery { apiClient.postTaskDirection(any(), "up") } returns data + repository.taskChecked(user, task, true, false, null) } "update daily streak" { - val subscriber = TestSubscriber() val data = TaskDirectionData() data.delta = 1.0f data.lvl = 1 task.type = TaskType.DAILY task.value = 0.0 - every { apiClient.postTaskDirection(any(), "up") } returns Flowable.just(data) - repository.taskChecked(user, task, true, false, null).subscribe(subscriber) - - subscriber.assertComplete() + coEvery { apiClient.postTaskDirection(any(), "up") } returns data + repository.taskChecked(user, task, true, false, null) task.streak shouldBe 1 task.completed shouldBe true } "update habit counter" { - val subscriber = TestSubscriber() val data = TaskDirectionData() data.delta = 1.0f data.lvl = 1 task.type = TaskType.HABIT task.value = 0.0 - every { apiClient.postTaskDirection(any(), "up") } returns Flowable.just(data) - repository.taskChecked(user, task, true, false, null).subscribe(subscriber) - subscriber.assertComplete() + coEvery { apiClient.postTaskDirection(any(), "up") } returns data + repository.taskChecked(user, task, true, false, null) task.counterUp shouldBe 1 data.delta = -10.0f - every { apiClient.postTaskDirection(any(), "down") } returns Flowable.just(data) - val downSubscriber = TestSubscriber() - repository.taskChecked(user, task, false, true, null).subscribe(downSubscriber) - downSubscriber.assertComplete() + coEvery { apiClient.postTaskDirection(any(), "down") } returns data + repository.taskChecked(user, task, false, true, null) task.counterUp shouldBe 1 task.counterDown shouldBe 1 } From 52b411a836976a52ce6c238877e3fc32f61424fe Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Fri, 13 Jan 2023 17:23:54 +0100 Subject: [PATCH 10/22] Improve tint colors --- Habitica/res/values/attrs.xml | 3 + Habitica/res/values/styles.xml | 77 +++++++++++-------- .../data/implementation/UserRepositoryImpl.kt | 6 +- .../RealmSocialLocalRepository.kt | 2 +- .../habitica/helpers/ExceptionHandler.kt | 4 +- .../habitica/helpers/PurchaseHandler.kt | 8 +- .../interactors/ShowNotificationInteractor.kt | 1 + .../ui/activities/BirthdayActivity.kt | 42 ++++++---- .../ui/activities/TaskFormActivity.kt | 6 +- .../social/guilds/GuildOverviewFragment.kt | 4 +- .../habitica/ui/theme/HabiticaTheme.kt | 8 +- .../android/habitica/ui/views/CurrencyText.kt | 7 +- .../ui/views/promo/BirthdayMenuView.kt | 61 +++++++++++---- .../views/tasks/form/ChecklistItemFormView.kt | 7 +- .../tasks/form/HabitScoringButtonsView.kt | 19 ++--- .../views/tasks/form/ReminderItemFormView.kt | 2 +- .../views/tasks/form/TaskDifficultyButtons.kt | 10 +-- .../ui/views/tasks/form/TaskFormSelector.kt | 9 +-- .../tasks/form/TaskSchedulingControls.kt | 2 +- build.gradle | 2 +- .../habitica/extensions/DataBindingUtils.kt | 2 + common/src/main/res/values/colors.xml | 29 +++++-- gradle/wrapper/gradle-wrapper.properties | 2 +- 23 files changed, 194 insertions(+), 119 deletions(-) diff --git a/Habitica/res/values/attrs.xml b/Habitica/res/values/attrs.xml index 9bba9c726..93f47d87d 100644 --- a/Habitica/res/values/attrs.xml +++ b/Habitica/res/values/attrs.xml @@ -17,6 +17,9 @@ + + + diff --git a/Habitica/res/values/styles.xml b/Habitica/res/values/styles.xml index e49d36bd7..dac71f53a 100644 --- a/Habitica/res/values/styles.xml +++ b/Habitica/res/values/styles.xml @@ -40,9 +40,12 @@ @color/content_background_offset @color/window_background @color/brand_800 - @color/brand_700 + @color/brand_50012 @color/brand_sub_text @color/brand_100 + @color/brand_500 + @color/brand_400 + @color/brand_100 @style/PopupTheme @style/PopupTheme @@ -79,7 +82,7 @@ @color/brand_500 @color/brand_600 @color/brand_400 - @color/brand_700 + @color/brand_800 @@ -102,9 +105,12 @@ @color/red_1 @color/red_1 @color/red_700 - @color/red_600 + @color/red_50012 @color/red_sub_text - @color/red_100 + @color/red_1 + @color/red_400 + @color/red_10 + @color/red_1