mirror of
https://github.com/sudoxnym/habitica-android.git
synced 2026-05-17 19:29:02 +00:00
Merge branch 'version/4.1' into Fiz/reminder-notification-permission
This commit is contained in:
commit
975df93006
106 changed files with 1268 additions and 554 deletions
BIN
Habitica/res/drawable-xhdpi/birthday_gems.png
Normal file
BIN
Habitica/res/drawable-xhdpi/birthday_gems.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.5 KiB |
BIN
Habitica/res/drawable-xxhdpi/birthday_gems.png
Normal file
BIN
Habitica/res/drawable-xxhdpi/birthday_gems.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8 KiB |
|
|
@ -2,5 +2,5 @@
|
|||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="oval">
|
||||
<size android:width="32dp" android:height="32dp" />
|
||||
<stroke android:width="2dp" android:color="?colorTintedBackgroundOffset" />
|
||||
<stroke android:width="2dp" android:color="?textColorTintedSecondary" />
|
||||
</shape>
|
||||
|
|
@ -2,5 +2,5 @@
|
|||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="oval">
|
||||
<size android:width="32dp" android:height="32dp" />
|
||||
<solid android:color="@color/white" />
|
||||
<solid android:color="?tintedUiMain" />
|
||||
</shape>
|
||||
|
|
@ -2,11 +2,17 @@
|
|||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="?colorPrimaryDistinct" />
|
||||
<solid android:color="?textColorTintedSecondary" />
|
||||
<corners android:radius="4dp" android:topLeftRadius="8dp" android:topRightRadius="8dp" />
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
<item android:bottom="2dp">
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="?colorTintedBackground" />
|
||||
<corners android:topLeftRadius="4dp" android:topRightRadius="4dp" android:bottomLeftRadius="2dp" android:bottomRightRadius="2dp" />
|
||||
</shape>
|
||||
</item>
|
||||
<item android:bottom="2dp">
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="?colorTintedBackgroundOffset" />
|
||||
|
|
|
|||
|
|
@ -95,7 +95,8 @@
|
|||
app:layout_anchorGravity="bottom"
|
||||
app:layout_collapseMode="pin"
|
||||
app:tabGravity="fill"
|
||||
app:tabIndicatorColor="?colorPrimary"
|
||||
app:tabIndicatorColor="?textColorPrimary"
|
||||
app:tabSelectedTextColor="?textColorPrimary"
|
||||
app:tabMode="fixed" />
|
||||
<FrameLayout
|
||||
android:id="@+id/connection_issue_view"
|
||||
|
|
|
|||
|
|
@ -177,13 +177,14 @@
|
|||
android:id="@+id/habit_adjust_positive_input_layout"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:boxBackgroundColor="?attr/colorTintedBackgroundOffset"
|
||||
android:background="@drawable/task_form_control_bg"
|
||||
android:hint="@string/positive_habit_form"
|
||||
android:layout_weight="1">
|
||||
<androidx.appcompat.widget.AppCompatEditText
|
||||
android:id="@+id/habit_adjust_positive_streak_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/transparent"
|
||||
android:inputType="number" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
<Space
|
||||
|
|
@ -194,12 +195,13 @@
|
|||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/negative_habit_form"
|
||||
app:boxBackgroundColor="?attr/colorTintedBackgroundOffset"
|
||||
android:background="@drawable/task_form_control_bg"
|
||||
android:layout_weight="1">
|
||||
<androidx.appcompat.widget.AppCompatEditText
|
||||
android:id="@+id/habit_adjust_negative_streak_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/transparent"
|
||||
android:inputType="number"/>
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
</LinearLayout>
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@
|
|||
android:text="@string/gem_purchase_subtitle"
|
||||
android:gravity="center"
|
||||
android:textStyle="normal|bold"
|
||||
android:textColor="?colorPrimary"
|
||||
android:textColor="?colorPrimaryText"
|
||||
android:textSize="16sp"
|
||||
android:lineSpacingExtra="4dp"
|
||||
android:layout_marginTop="23dp"
|
||||
|
|
@ -234,7 +234,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:text="@string/gift_gems"
|
||||
android:background="@color/transparent"
|
||||
android:textColor="?colorAccent"
|
||||
android:textColor="?colorPrimaryText"
|
||||
android:textAllCaps="false"/>
|
||||
<TextView android:id="@+id/supportTextView"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
|||
|
|
@ -75,5 +75,11 @@
|
|||
style="@style/HabiticaButton.Purple"
|
||||
android:layout_marginTop="@dimen/spacing_medium"
|
||||
android:text="@string/send_gift" />
|
||||
<ProgressBar
|
||||
android:id="@+id/progress_bar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:visibility="gone"/>
|
||||
</LinearLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
|
@ -17,6 +17,11 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
<androidx.compose.ui.platform.ComposeView
|
||||
android:id="@+id/promo_compose_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone" />
|
||||
<RelativeLayout
|
||||
android:id="@+id/promo_banner"
|
||||
android:layout_width="match_parent"
|
||||
|
|
|
|||
|
|
@ -9,12 +9,13 @@
|
|||
android:paddingEnd="20dp"
|
||||
android:paddingBottom="10dp">
|
||||
|
||||
<ImageView
|
||||
<com.habitrpg.common.habitica.views.PixelArtView
|
||||
android:id="@+id/notification_image"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginEnd="20dp"
|
||||
android:background="@color/transparent"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:visibility="gone" />
|
||||
|
||||
<TextView
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:text="@string/dismiss_tutorial"
|
||||
android:textColor="?textColorTintedSecondary"
|
||||
android:theme="@style/DialogButton"/>
|
||||
|
||||
<Button
|
||||
|
|
@ -57,6 +58,7 @@
|
|||
android:layout_gravity="center_vertical"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/complete_tutorial"
|
||||
android:textColor="?textColorTintedSecondary"
|
||||
android:theme="@style/DialogButton" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
|
|
|||
|
|
@ -259,6 +259,7 @@
|
|||
android:gravity="center"
|
||||
android:paddingStart="30dp"
|
||||
android:paddingEnd="30dp"
|
||||
android:visibility="gone"
|
||||
android:text="@string/subscribers_mythic_hourglasses" />
|
||||
|
||||
<ImageView
|
||||
|
|
|
|||
|
|
@ -12,17 +12,37 @@
|
|||
android:paddingBottom="@dimen/task_top_bottom_padding"
|
||||
android:layout_marginEnd="@dimen/task_text_padding"
|
||||
android:layout_marginStart="@dimen/task_text_padding">
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
<TextView
|
||||
android:id="@+id/assigned_textview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/Caption4"
|
||||
style="@style/Caption3"
|
||||
android:text="@string/pending_approval"
|
||||
android:textColor="@color/text_ternary"
|
||||
android:drawableStart="@drawable/assign"
|
||||
android:drawablePadding="@dimen/spacing_small"
|
||||
android:layout_marginBottom="2dp"
|
||||
/>
|
||||
<TextView
|
||||
android:id="@+id/completed_textview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/Caption4"
|
||||
android:text="@string/pending_approval"
|
||||
android:textColor="@color/text_ternary"
|
||||
android:drawablePadding="@dimen/spacing_small"
|
||||
android:drawableStart="@drawable/completed"
|
||||
android:drawableTint="@color/text_ternary"
|
||||
android:paddingVertical="1dp"
|
||||
android:paddingHorizontal="6dp"
|
||||
android:background="@drawable/pill_bg_gray"
|
||||
android:layout_marginStart="10dp"
|
||||
/>
|
||||
</LinearLayout>
|
||||
<com.habitrpg.android.habitica.ui.views.EllipsisTextView
|
||||
android:id="@+id/checkedTextView"
|
||||
style="@style/Subheader3"
|
||||
|
|
|
|||
|
|
@ -244,7 +244,7 @@
|
|||
android:id="@+id/birthdayActivity"
|
||||
android:name="com.habitrpg.android.habitica.ui.activities.BirthdayActivity"
|
||||
android:label="@string/gem_purchase_toolbartitle" >
|
||||
<deepLink app:uri="habitica.com/birthday" />
|
||||
<deepLink app:uri="habitica.com/promo/birthday" />
|
||||
</activity>
|
||||
<fragment
|
||||
android:id="@+id/newsFragment"
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@
|
|||
<color name="account_dialog_bars">@color/gray_1</color>
|
||||
<color name="habit_inactive_gray">@color/gray_10</color>
|
||||
<color name="equipment_overview_background">@color/gray_10</color>
|
||||
<color name="equipment_column_background">@color/gray_10</color>
|
||||
<color name="inverted_background">@color/gray_10</color>
|
||||
<color name="inverted_background_offset">@color/gray_50</color>
|
||||
<color name="background_red">@color/red_50</color>
|
||||
|
|
|
|||
|
|
@ -17,6 +17,9 @@
|
|||
<attr name="colorPrimaryText" format="color" />
|
||||
<attr name="colorBoxStroke" format="color" />
|
||||
<attr name="taskFormTint" format="color" />
|
||||
<attr name="tintedUiMain" format="color" />
|
||||
<attr name="tintedUiSub" format="color" />
|
||||
<attr name="tintedUiDetails" format="color" />
|
||||
<attr name="textColorSecondaryDark" format="color" />
|
||||
<attr name="statsColor" format="color" />
|
||||
<attr name="statsTitle" format="string" />
|
||||
|
|
|
|||
|
|
@ -86,6 +86,7 @@
|
|||
<color name="system_bars">@color/brand_200</color>
|
||||
<color name="account_dialog_bars">@color/white</color>
|
||||
<color name="equipment_overview_background">@color/gray_50</color>
|
||||
<color name="equipment_column_background">@color/gray_600</color>
|
||||
<color name="inverted_background">@color/gray_100</color>
|
||||
<color name="inverted_background_offset">@color/gray_200</color>
|
||||
<color name="background_red">@color/red_100</color>
|
||||
|
|
|
|||
|
|
@ -747,7 +747,7 @@
|
|||
<string name="gift_confirmation_title">Your gift was sent!</string>
|
||||
<string name="gift_confirmation_text_sub_g1g1">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!</string>
|
||||
<string name="gift_confirmation_text_sub">You sent @%1$s a %2$s-month Habitica subscription.</string>
|
||||
<string name="gift_confirmation_text_gems_new">You sent @%s %s gems.</string>
|
||||
<string name="gift_confirmation_text_gems_new">You sent @%1$s %2$s gems.</string>
|
||||
<string name="subscription_confirmation">You are now subscribed for 1 month</string>
|
||||
<string name="subscription_confirmation_multiple">You are now subscribed for %s months</string>
|
||||
<string name="gem_purchase_confirmation">You gained %s gems.</string>
|
||||
|
|
@ -1313,24 +1313,33 @@
|
|||
<string name="third">third</string>
|
||||
<string name="fourth">fourth</string>
|
||||
<string name="fifth">fifth</string>
|
||||
<string name="animated_gryphatrice_pet">Animated Gryphatrice Pet</string>
|
||||
<string name="animated_gryphatrice_pet">Jubilant Gryphatrice Pet</string>
|
||||
<string name="birthday_title_description">Celebrate Habitica’s 10th birthday with gifts and exclusive items below!</string>
|
||||
<string name="limited_edition">Limited Edition</string>
|
||||
<string name="gryphatrice_description">The rare, mystical Gryphatrice joins the birthday bash! Don’t miss your chance to own this exclusive animated Pet.</string>
|
||||
<string name="gryphatrice_description">The rare, Jubilant Gryphatrice joins the birthday bash! Don’t miss your chance to own this exclusive animated Pet.</string>
|
||||
<string name="thanks_for_support">Thanks for your support!</string>
|
||||
<string name="plenty_of_potions">Plenty of Potions</string>
|
||||
<string name="for_for_free">For for Free</string>
|
||||
<string name="for_for_free">Four for Free</string>
|
||||
<string name="buy_for_x">Buy for %s</string>
|
||||
<string name="buy_for">Buy for</string>
|
||||
<string name="plenty_of_potions_description">We’re bringing back 10 of the community’s favorite Magic Hatching Potions. Head over to the Market to fill out your collection!</string>
|
||||
<string name="for_for_free_description">To keep the party going, we’ll be giving away Party Robes, 20 Gems, and a limited edition Cape set and Background!</string>
|
||||
<string name="birthday_limitations">This is a limited time event that starts on January 23rd at 8:00 AM ET (13:00 UTC) and will end February 1st at 8:00 PM ET (01:00 UTC). The Limited Edition Gryphatrice and ten Magic Hatching Potions will be available to buy during this time. The other Gifts will be automatically delivered to all accounts active in the previous 30 days.</string>
|
||||
<string name="birthday_limitations">This is a limited time event that starts on %1$s and will end %2$s. The Limited Edition Jubilant Gryphatrice and ten Magic Hatching Potions will be available to buy during this time. The other Gifts listed in the Four for Free section will be automatically delivered to all accounts that were active in the 30 days prior to day the gift is sent. Accounts created after the gifts are sent will not be able to claim them.</string>
|
||||
<string name="visit_the_market">Visit the Market</string>
|
||||
<string name="exclusive_items_await">Exclusive items and gifts await</string>
|
||||
<string name="ends_in_x">Ends in %s</string>
|
||||
<string name="see_more">See More</string>
|
||||
<string name="jubilant_gryphatrice_confirmation">You purchased the Jubilant Gryphatrice!</string>
|
||||
<string name="jubilant_gryphatrice_confirmation_gift">You gifted the Jubilant Gryphatrice!</string>
|
||||
<string name="open_settings">Open Settings</string>
|
||||
<string name="undo">Undo</string>
|
||||
<string name="day_x">Day %d</string>
|
||||
<string name="a_party_robe">A Party Robe</string>
|
||||
<string name="twenty_gems">20 Gems</string>
|
||||
<string name="background">Background</string>
|
||||
<string name="birthday_set">Birthday Set</string>
|
||||
<string name="you_equipped_x">You equipped %s</string>
|
||||
<string name="purchase_gryphatrice_confirmation">Purchase the Jubilant Gryphatrice for %d Gems?</string>
|
||||
|
||||
<plurals name="you_x_others">
|
||||
<item quantity="zero">You</item>
|
||||
|
|
|
|||
|
|
@ -40,9 +40,12 @@
|
|||
<item name="colorContentBackgroundOffset">@color/content_background_offset</item>
|
||||
<item name="colorWindowBackground">@color/window_background</item>
|
||||
<item name="colorTintedBackground">@color/brand_800</item>
|
||||
<item name="colorTintedBackgroundOffset">@color/brand_700</item>
|
||||
<item name="colorTintedBackgroundOffset">@color/brand_50012</item>
|
||||
<item name="textColorTintedSecondary">@color/brand_sub_text</item>
|
||||
<item name="textColorTintedPrimary">@color/brand_100</item>
|
||||
<item name="tintedUiMain">@color/brand_500</item>
|
||||
<item name="tintedUiSub">@color/brand_400</item>
|
||||
<item name="tintedUiDetails">@color/brand_100</item>
|
||||
|
||||
<item name="popupMenuStyle">@style/PopupTheme</item>
|
||||
<item name="actionOverflowMenuStyle">@style/PopupTheme</item>
|
||||
|
|
@ -79,7 +82,7 @@
|
|||
<item name="textColorTintedSecondary">@color/brand_500</item>
|
||||
<item name="colorPrimaryText">@color/brand_600</item>
|
||||
<item name="colorPrimaryDark">@color/brand_400</item>
|
||||
<item name="textColorTintedPrimary">@color/brand_700</item>
|
||||
<item name="textColorTintedPrimary">@color/brand_800</item>
|
||||
</style>
|
||||
|
||||
|
||||
|
|
@ -102,9 +105,12 @@
|
|||
<item name="toolbarContentColor">@color/red_1</item>
|
||||
<item name="colorPrimaryText">@color/red_1</item>
|
||||
<item name="colorTintedBackground">@color/red_700</item>
|
||||
<item name="colorTintedBackgroundOffset">@color/red_600</item>
|
||||
<item name="colorTintedBackgroundOffset">@color/red_50012</item>
|
||||
<item name="textColorTintedSecondary">@color/red_sub_text</item>
|
||||
<item name="textColorTintedPrimary">@color/red_100</item>
|
||||
<item name="textColorTintedPrimary">@color/red_1</item>
|
||||
<item name="tintedUiMain">@color/red_400</item>
|
||||
<item name="tintedUiSub">@color/red_10</item>
|
||||
<item name="tintedUiDetails">@color/red_1</item>
|
||||
</style>
|
||||
|
||||
<style name="MainAppTheme.Red.Dark">
|
||||
|
|
@ -114,10 +120,9 @@
|
|||
<item name="textColorPrimaryDark">@color/gray_200</item>
|
||||
<item name="toolbarContentColor">@color/red_1</item>
|
||||
<item name="colorTintedBackground">@color/red_00</item>
|
||||
<item name="colorTintedBackgroundOffset">@color/red_0</item>
|
||||
<item name="colorPrimaryText">@color/red_600</item>
|
||||
<item name="textColorTintedSecondary">@color/red_500</item>
|
||||
<item name="textColorTintedPrimary">@color/red_600</item>
|
||||
<item name="textColorTintedPrimary">@color/red_700</item>
|
||||
</style>
|
||||
|
||||
<style name="MainAppTheme.Maroon">
|
||||
|
|
@ -138,9 +143,12 @@
|
|||
<item name="taskFormTint">@color/maroon_50</item>
|
||||
<item name="colorPrimaryText">@color/maroon_1</item>
|
||||
<item name="colorTintedBackground">@color/maroon_700</item>
|
||||
<item name="colorTintedBackgroundOffset">@color/maroon_600</item>
|
||||
<item name="colorTintedBackgroundOffset">@color/maroon_50012</item>
|
||||
<item name="textColorTintedSecondary">@color/maroon_sub_text</item>
|
||||
<item name="textColorTintedPrimary">@color/maroon_100</item>
|
||||
<item name="textColorTintedPrimary">@color/maroon_1</item>
|
||||
<item name="tintedUiMain">@color/maroon_400</item>
|
||||
<item name="tintedUiSub">@color/maroon_10</item>
|
||||
<item name="tintedUiDetails">@color/maroon_1</item>
|
||||
</style>
|
||||
|
||||
<style name="MainAppTheme.Maroon.Dark">
|
||||
|
|
@ -149,10 +157,9 @@
|
|||
<item name="textColorPrimaryDark">@color/gray_200</item>
|
||||
<item name="toolbarContentColor">@color/white</item>
|
||||
<item name="colorTintedBackground">@color/maroon_00</item>
|
||||
<item name="colorTintedBackgroundOffset">@color/maroon_0</item>
|
||||
<item name="colorPrimaryText">@color/maroon_600</item>
|
||||
<item name="textColorTintedSecondary">@color/maroon_500</item>
|
||||
<item name="textColorTintedPrimary">@color/maroon_600</item>
|
||||
<item name="textColorTintedPrimary">@color/maroon_700</item>
|
||||
</style>
|
||||
|
||||
<style name="MainAppTheme.Orange">
|
||||
|
|
@ -174,9 +181,12 @@
|
|||
<item name="toolbarContentColor">@color/orange_1</item>
|
||||
<item name="colorPrimaryText">@color/orange_1</item>
|
||||
<item name="colorTintedBackground">@color/orange_700</item>
|
||||
<item name="colorTintedBackgroundOffset">@color/orange_600</item>
|
||||
<item name="colorTintedBackgroundOffset">@color/orange_50012</item>
|
||||
<item name="textColorTintedSecondary">@color/orange_sub_text</item>
|
||||
<item name="textColorTintedPrimary">@color/orange_100</item>
|
||||
<item name="textColorTintedPrimary">@color/orange_1</item>
|
||||
<item name="tintedUiMain">@color/orange_400</item>
|
||||
<item name="tintedUiSub">@color/orange_10</item>
|
||||
<item name="tintedUiDetails">@color/orange_1</item>
|
||||
</style>
|
||||
|
||||
<style name="MainAppTheme.Orange.Dark">
|
||||
|
|
@ -186,10 +196,9 @@
|
|||
<item name="textColorPrimaryDark">@color/gray_200</item>
|
||||
<item name="toolbarContentColor">@color/orange_1</item>
|
||||
<item name="colorTintedBackground">@color/orange_00</item>
|
||||
<item name="colorTintedBackgroundOffset">@color/orange_0</item>
|
||||
<item name="colorPrimaryText">@color/orange_600</item>
|
||||
<item name="textColorTintedSecondary">@color/orange_500</item>
|
||||
<item name="textColorTintedPrimary">@color/orange_600</item>
|
||||
<item name="textColorTintedPrimary">@color/orange_700</item>
|
||||
</style>
|
||||
|
||||
<style name="MainAppTheme.Yellow">
|
||||
|
|
@ -211,9 +220,12 @@
|
|||
<item name="toolbarContentColor">@color/yellow_1</item>
|
||||
<item name="colorPrimaryText">@color/yellow_1</item>
|
||||
<item name="colorTintedBackground">@color/yellow_700</item>
|
||||
<item name="colorTintedBackgroundOffset">@color/yellow_600</item>
|
||||
<item name="colorTintedBackgroundOffset">@color/yellow_50012</item>
|
||||
<item name="textColorTintedSecondary">@color/yellow_sub_text</item>
|
||||
<item name="textColorTintedPrimary">@color/yellow_10</item>
|
||||
<item name="textColorTintedPrimary">@color/yellow_1</item>
|
||||
<item name="tintedUiMain">@color/yellow_400</item>
|
||||
<item name="tintedUiSub">@color/yellow_10</item>
|
||||
<item name="tintedUiDetails">@color/yellow_1</item>
|
||||
</style>
|
||||
|
||||
<style name="MainAppTheme.Yellow.Dark">
|
||||
|
|
@ -223,10 +235,9 @@
|
|||
<item name="textColorPrimaryDark">@color/gray_200</item>
|
||||
<item name="toolbarContentColor">@color/yellow_1</item>
|
||||
<item name="colorTintedBackground">@color/yellow_00</item>
|
||||
<item name="colorTintedBackgroundOffset">@color/yellow_0</item>
|
||||
<item name="colorPrimaryText">@color/yellow_600</item>
|
||||
<item name="textColorTintedSecondary">@color/yellow_500</item>
|
||||
<item name="textColorTintedPrimary">@color/yellow_600</item>
|
||||
<item name="textColorTintedPrimary">@color/yellow_700</item>
|
||||
</style>
|
||||
|
||||
<style name="MainAppTheme.Green">
|
||||
|
|
@ -248,9 +259,12 @@
|
|||
<item name="toolbarContentColor">@color/green_1</item>
|
||||
<item name="colorPrimaryText">@color/green_1</item>
|
||||
<item name="colorTintedBackground">@color/green_700</item>
|
||||
<item name="colorTintedBackgroundOffset">@color/green_600</item>
|
||||
<item name="colorTintedBackgroundOffset">@color/green_50012</item>
|
||||
<item name="textColorTintedSecondary">@color/green_sub_text</item>
|
||||
<item name="textColorTintedPrimary">@color/green_100</item>
|
||||
<item name="textColorTintedPrimary">@color/green_1</item>
|
||||
<item name="tintedUiMain">@color/green_400</item>
|
||||
<item name="tintedUiSub">@color/green_10</item>
|
||||
<item name="tintedUiDetails">@color/green_1</item>
|
||||
</style>
|
||||
|
||||
<style name="MainAppTheme.Green.Dark">
|
||||
|
|
@ -260,10 +274,9 @@
|
|||
<item name="textColorPrimaryDark">@color/gray_200</item>
|
||||
<item name="toolbarContentColor">@color/green_1</item>
|
||||
<item name="colorTintedBackground">@color/green_00</item>
|
||||
<item name="colorTintedBackgroundOffset">@color/green_0</item>
|
||||
<item name="colorPrimaryText">@color/green_600</item>
|
||||
<item name="textColorTintedSecondary">@color/green_500</item>
|
||||
<item name="textColorTintedPrimary">@color/green_600</item>
|
||||
<item name="textColorTintedPrimary">@color/green_700</item>
|
||||
</style>
|
||||
|
||||
<style name="MainAppTheme.Teal">
|
||||
|
|
@ -285,9 +298,12 @@
|
|||
<item name="toolbarContentColor">@color/teal_1</item>
|
||||
<item name="colorPrimaryText">@color/teal_1</item>
|
||||
<item name="colorTintedBackground">@color/teal_700</item>
|
||||
<item name="colorTintedBackgroundOffset">@color/teal_600</item>
|
||||
<item name="colorTintedBackgroundOffset">@color/teal_50012</item>
|
||||
<item name="textColorTintedSecondary">@color/teal_sub_text</item>
|
||||
<item name="textColorTintedPrimary">@color/teal_100</item>
|
||||
<item name="textColorTintedPrimary">@color/teal_1</item>
|
||||
<item name="tintedUiMain">@color/teal_400</item>
|
||||
<item name="tintedUiSub">@color/teal_10</item>
|
||||
<item name="tintedUiDetails">@color/teal_1</item>
|
||||
</style>
|
||||
|
||||
<style name="MainAppTheme.Teal.Dark">
|
||||
|
|
@ -297,10 +313,9 @@
|
|||
<item name="textColorPrimaryDark">@color/gray_200</item>
|
||||
<item name="toolbarContentColor">@color/teal_1</item>
|
||||
<item name="colorTintedBackground">@color/teal_00</item>
|
||||
<item name="colorTintedBackgroundOffset">@color/teal_0</item>
|
||||
<item name="colorPrimaryText">@color/teal_600</item>
|
||||
<item name="textColorTintedSecondary">@color/teal_500</item>
|
||||
<item name="textColorTintedPrimary">@color/teal_600</item>
|
||||
<item name="textColorTintedPrimary">@color/teal_700</item>
|
||||
</style>
|
||||
|
||||
<style name="MainAppTheme.Blue">
|
||||
|
|
@ -322,9 +337,12 @@
|
|||
<item name="toolbarContentColor">@color/blue_1</item>
|
||||
<item name="colorPrimaryText">@color/blue_1</item>
|
||||
<item name="colorTintedBackground">@color/blue_700</item>
|
||||
<item name="colorTintedBackgroundOffset">@color/blue_600</item>
|
||||
<item name="colorTintedBackgroundOffset">@color/blue_50012</item>
|
||||
<item name="textColorTintedSecondary">@color/blue_sub_text</item>
|
||||
<item name="textColorTintedPrimary">@color/blue_100</item>
|
||||
<item name="textColorTintedPrimary">@color/blue_1</item>
|
||||
<item name="tintedUiMain">@color/blue_400</item>
|
||||
<item name="tintedUiSub">@color/blue_10</item>
|
||||
<item name="tintedUiDetails">@color/blue_1</item>
|
||||
</style>
|
||||
|
||||
<style name="MainAppTheme.Blue.Dark">
|
||||
|
|
@ -334,10 +352,9 @@
|
|||
<item name="colorBoxStroke">@color/blue_10</item>
|
||||
<item name="toolbarContentColor">@color/blue_1</item>
|
||||
<item name="colorTintedBackground">@color/blue_00</item>
|
||||
<item name="colorTintedBackgroundOffset">@color/blue_0</item>
|
||||
<item name="colorPrimaryText">@color/blue_600</item>
|
||||
<item name="textColorTintedSecondary">@color/blue_500</item>
|
||||
<item name="textColorTintedPrimary">@color/blue_600</item>
|
||||
<item name="textColorTintedPrimary">@color/blue_700</item>
|
||||
</style>
|
||||
|
||||
<style name="MyWidgetTheme">
|
||||
|
|
@ -457,7 +474,7 @@
|
|||
|
||||
<style name="DialogButton" parent="android:Widget.Button">
|
||||
<item name="android:background">@android:color/transparent</item>
|
||||
<item name="android:textColor">@color/brand_100</item>
|
||||
<item name="android:textColor">?colorAccent</item>
|
||||
</style>
|
||||
|
||||
<style name="DialogTheme" parent="Theme.AppCompat.DayNight.Dialog">
|
||||
|
|
|
|||
|
|
@ -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<PartyDetailFragment, FragmentPa
|
|||
override fun makeFragment() {
|
||||
val group = Group()
|
||||
group.name = "Group Name"
|
||||
every { socialRepository.getGroup(any()) } returns Flowable.just(group)
|
||||
every { socialRepository.getGroup(any()) } returns flowOf(group)
|
||||
viewModel = PartyViewModel(false)
|
||||
viewModel.socialRepository = socialRepository
|
||||
viewModel.userRepository = userRepository
|
||||
|
|
|
|||
|
|
@ -10,10 +10,11 @@ import com.habitrpg.shared.habitica.models.tasks.Attribute
|
|||
import io.github.kakaocup.kakao.common.views.KView
|
||||
import io.github.kakaocup.kakao.screen.Screen
|
||||
import io.github.kakaocup.kakao.text.KButton
|
||||
import io.mockk.coVerify
|
||||
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.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
|
@ -63,9 +64,9 @@ class StatsFragmentTest : FragmentTestCase<StatsFragment, FragmentStatsBinding,
|
|||
fun setUpUser() {
|
||||
user.stats?.lvl = 20
|
||||
user.stats?.points = 30
|
||||
userState.onNext(user)
|
||||
userState.value = user
|
||||
|
||||
every { inventoryRepository.getEquipment(listOf()) } returns Flowable.just(listOf())
|
||||
every { inventoryRepository.getEquipment(listOf()) } returns flowOf(listOf())
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -96,13 +97,13 @@ class StatsFragmentTest : FragmentTestCase<StatsFragment, FragmentStatsBinding,
|
|||
fun allocatesOnClick() {
|
||||
screen {
|
||||
strengthAllocateButton.click()
|
||||
verify { userRepository.allocatePoint(Attribute.STRENGTH) }
|
||||
coVerify { userRepository.allocatePoint(Attribute.STRENGTH) }
|
||||
intelligenceAllocateButton.click()
|
||||
verify { userRepository.allocatePoint(Attribute.INTELLIGENCE) }
|
||||
coVerify { userRepository.allocatePoint(Attribute.INTELLIGENCE) }
|
||||
constitutionAllocateButton.click()
|
||||
verify { userRepository.allocatePoint(Attribute.CONSTITUTION) }
|
||||
coVerify { userRepository.allocatePoint(Attribute.CONSTITUTION) }
|
||||
perceptionAllocateButton.click()
|
||||
verify { userRepository.allocatePoint(Attribute.PERCEPTION) }
|
||||
coVerify { userRepository.allocatePoint(Attribute.PERCEPTION) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,12 +15,14 @@ import io.github.kakaocup.kakao.recycler.KRecyclerView
|
|||
import io.github.kakaocup.kakao.screen.Screen
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.mockk.CapturingSlot
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.mockkObject
|
||||
import io.mockk.spyk
|
||||
import io.mockk.verify
|
||||
import io.reactivex.rxjava3.core.Flowable
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import org.junit.Test
|
||||
|
||||
class PetDetailScreen : Screen<PetDetailScreen>() {
|
||||
|
|
@ -35,12 +37,12 @@ class PetDetailScreen : Screen<PetDetailScreen>() {
|
|||
internal class PetDetailRecyclerFragmentTest :
|
||||
FragmentTestCase<PetDetailRecyclerFragment, FragmentRecyclerviewBinding, PetDetailScreen>(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<FeedPetUseCase.RequestValues>()
|
||||
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<PetItem> { 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<FeedPetUseCase.RequestValues>()
|
||||
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<PetItem> { 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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<StableScreen>() {
|
|||
|
||||
internal class StableRecyclerFragmentTest : FragmentTestCase<StableRecyclerFragment, FragmentRecyclerviewBinding, StableScreen>(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"
|
||||
|
|
|
|||
|
|
@ -138,6 +138,8 @@ interface ApiService {
|
|||
|
||||
@POST("tasks/user")
|
||||
suspend fun createTask(@Body item: Task): HabitResponse<Task>
|
||||
@POST("tasks/group/{groupId}")
|
||||
suspend fun createGroupTask(@Path("groupId") groupId: String, @Body item: Task): HabitResponse<Task>
|
||||
|
||||
@POST("tasks/user")
|
||||
suspend fun createTasks(@Body tasks: List<Task>): HabitResponse<List<Task>>
|
||||
|
|
@ -198,7 +200,7 @@ interface ApiService {
|
|||
suspend fun disableClasses(): HabitResponse<User>
|
||||
|
||||
@POST("user/mark-pms-read")
|
||||
suspend fun markPrivateMessagesRead(): Void
|
||||
suspend fun markPrivateMessagesRead(): Void?
|
||||
|
||||
/* Group API */
|
||||
|
||||
|
|
@ -457,4 +459,7 @@ interface ApiService {
|
|||
|
||||
@GET("hall/heroes/{memberID}")
|
||||
suspend fun getHallMember(@Path("memberID") memberID: String): HabitResponse<Member>
|
||||
|
||||
@POST("tasks/{taskID}/needs-work/{userID}")
|
||||
suspend fun markTaskNeedsWork(@Path("taskID") taskID: String, @Path("userID") userID: String): HabitResponse<Task>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Task>): List<Task>?
|
||||
|
||||
|
|
@ -130,7 +131,7 @@ interface ApiClient {
|
|||
|
||||
suspend fun disableClasses(): User?
|
||||
|
||||
suspend fun markPrivateMessagesRead(): Void?
|
||||
suspend fun markPrivateMessagesRead()
|
||||
|
||||
/* Group API */
|
||||
|
||||
|
|
@ -274,4 +275,5 @@ interface ApiClient {
|
|||
suspend fun unassignFromTask(taskId: String, userID: String): Task?
|
||||
suspend fun updateMember(memberID: String, updateData: Map<String, Any?>): Member?
|
||||
suspend fun getHallMember(userId: String): Member?
|
||||
suspend fun markTaskNeedsWork(taskID: String, userID: String): Task?
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<WorldState>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ interface SocialRepository : BaseRepository {
|
|||
|
||||
fun getUserGroups(type: String?): Flow<List<Group>>
|
||||
suspend fun retrieveGroupChat(groupId: String): List<ChatMessage>?
|
||||
fun getGroupChat(groupId: String): Flow<out List<ChatMessage>>
|
||||
fun getGroupChat(groupId: String): Flow<List<ChatMessage>>
|
||||
|
||||
suspend fun markMessagesSeen(seenGroupId: String)
|
||||
|
||||
|
|
@ -92,7 +92,7 @@ interface SocialRepository : BaseRepository {
|
|||
id: String? = null
|
||||
): List<FindUsernameResult>?
|
||||
|
||||
suspend fun markPrivateMessagesRead(user: User?): Void?
|
||||
suspend fun markPrivateMessagesRead(user: User?)
|
||||
|
||||
fun markSomePrivateMessagesAsRead(user: User?, messages: List<ChatMessage>)
|
||||
|
||||
|
|
|
|||
|
|
@ -65,6 +65,8 @@ interface TaskRepository : BaseRepository {
|
|||
suspend fun retrieveCompletedTodos(userId: String? = null): TaskList?
|
||||
suspend fun syncErroredTasks(): List<Task>?
|
||||
suspend fun unlinkAllTasks(challengeID: String?, keepOption: String): Void?
|
||||
fun getTasksForChallenge(challengeID: String?): Flow<out List<Task>>
|
||||
fun getTasksForChallenge(challengeID: String?): Flow<List<Task>>
|
||||
suspend fun bulkScoreTasks(data: List<Map<String, String>>): BulkTaskScoringData?
|
||||
suspend fun markTaskNeedsWork(task: Task, userID: String)
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Task>): List<Task>? {
|
||||
return process { apiService.createTasks(tasks) }
|
||||
}
|
||||
|
|
@ -495,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<Group>? {
|
||||
|
|
@ -602,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<String, Any>): 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))
|
||||
|
||||
|
|
|
|||
|
|
@ -36,9 +36,9 @@ class ContentRepositoryImpl<T : ContentLocalRepository>(
|
|||
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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
@ -275,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
|
||||
|
|
|
|||
|
|
@ -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,19 @@ 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 }?.let {
|
||||
it.completed = false
|
||||
it.completedDate = null
|
||||
}
|
||||
localRepository.save(savedTask)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun taskChecked(
|
||||
user: User?,
|
||||
taskId: String,
|
||||
|
|
@ -223,7 +246,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
|
||||
|
|
@ -315,7 +342,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 ->
|
||||
|
|
@ -326,7 +353,7 @@ class TaskRepositoryImpl(
|
|||
if (savedTask != null) {
|
||||
savedTask.id = task.id
|
||||
savedTask.position = task.position
|
||||
localRepository.save(task)
|
||||
localRepository.save(savedTask)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -36,7 +38,11 @@ class UserRepositoryImpl(
|
|||
private val analyticsManager: AnalyticsManager
|
||||
) : BaseRepositoryImpl<UserLocalRepository>(localRepository, apiClient, userID), UserRepository {
|
||||
|
||||
private var lastSync: Date? = null
|
||||
companion object {
|
||||
private var lastReadNotification: String? = null
|
||||
private var lastSync: Date? = null
|
||||
}
|
||||
|
||||
|
||||
override fun getUser(): Flow<User?> = getUser(userID)
|
||||
override fun getUser(userID: String): Flow<User?> = localRepository.getUser(userID)
|
||||
|
|
@ -62,10 +68,12 @@ class UserRepositoryImpl(
|
|||
@Suppress("ReturnCount")
|
||||
override suspend fun retrieveUser(withTasks: Boolean, forced: Boolean, overrideExisting: Boolean): User? {
|
||||
// Only retrieve again after 3 minutes or it's forced.
|
||||
if (forced || this.lastSync == null || Date().time - (this.lastSync?.time ?: 0) > 180000) {
|
||||
if (forced || lastSync == null || Date().time - (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
|
||||
|
|
@ -104,7 +112,7 @@ class UserRepositoryImpl(
|
|||
override suspend fun sleep(user: User): User {
|
||||
val newValue = !(user.preferences?.sleep ?: false)
|
||||
localRepository.modify(user) { it.preferences?.sleep = newValue }
|
||||
if (apiClient.sleep() != true) {
|
||||
if (apiClient.sleep() == null) {
|
||||
localRepository.modify(user) { it.preferences?.sleep = !newValue }
|
||||
}
|
||||
return user
|
||||
|
|
@ -150,11 +158,12 @@ class UserRepositoryImpl(
|
|||
override suspend fun unlockPath(path: String, price: Int): UnlockResponse? {
|
||||
val unlockResponse = apiClient.unlockPath(path) ?: return null
|
||||
val user = localRepository.getUser(userID).firstOrNull() ?: return unlockResponse
|
||||
user.preferences = unlockResponse.preferences
|
||||
user.purchased = unlockResponse.purchased
|
||||
user.items = unlockResponse.items
|
||||
user.balance = user.balance - (price / 4.0)
|
||||
localRepository.saveUser(user, false)
|
||||
localRepository.modify(user) { liveUser ->
|
||||
liveUser.preferences = unlockResponse.preferences
|
||||
liveUser.purchased = unlockResponse.purchased
|
||||
liveUser.items = unlockResponse.items
|
||||
liveUser.balance = liveUser.balance - (price / 4.0)
|
||||
}
|
||||
return unlockResponse
|
||||
}
|
||||
|
||||
|
|
@ -162,7 +171,11 @@ class UserRepositoryImpl(
|
|||
runCron(ArrayList())
|
||||
}
|
||||
|
||||
override suspend fun readNotification(id: String) = apiClient.readNotification(id)
|
||||
override suspend fun readNotification(id: String): List<Any>? {
|
||||
if (lastReadNotification == id) return null
|
||||
lastReadNotification = id
|
||||
return apiClient.readNotification(id)
|
||||
}
|
||||
override fun getUserQuestStatus(): Flow<UserQuestStatus> {
|
||||
return localRepository.getUserQuestStatus(userID)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -216,7 +216,7 @@ class RealmSocialLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm)
|
|||
liveMessage?.likeCount = liveMessage?.likes?.size ?: 0
|
||||
}
|
||||
} else {
|
||||
liveMessage?.likes?.filter { userId == it.id }?.forEach { like ->
|
||||
liveMessage?.likes?.filter { userId == it.id && it.isManaged }?.forEach { like ->
|
||||
executeTransaction {
|
||||
like.deleteFromRealm()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import com.habitrpg.android.habitica.models.promotions.HabiticaWebPromotion
|
|||
import com.habitrpg.android.habitica.models.promotions.getHabiticaPromotionFromKey
|
||||
import com.habitrpg.common.habitica.helpers.AppTestingLevel
|
||||
import kotlinx.coroutines.MainScope
|
||||
import java.util.Date
|
||||
|
||||
class AppConfigManager(contentRepository: ContentRepository?): com.habitrpg.common.habitica.helpers.AppConfigManager() {
|
||||
|
||||
|
|
@ -169,6 +170,6 @@ class AppConfigManager(contentRepository: ContentRepository?): com.habitrpg.comm
|
|||
|
||||
fun getBirthdayEvent(): WorldStateEvent? {
|
||||
val events = ((worldState?.events as? List<WorldStateEvent>) ?: listOf(worldState?.currentEvent))
|
||||
return events.firstOrNull { it?.eventKey == "birthday10" }
|
||||
return events.firstOrNull { it?.eventKey == "birthday10" && it.end?.after(Date()) == true }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.habitrpg.android.habitica.helpers
|
|||
import android.util.Log
|
||||
import com.habitrpg.android.habitica.BuildConfig
|
||||
import com.habitrpg.android.habitica.proxy.AnalyticsManager
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -40,7 +41,8 @@ class ExceptionHandler {
|
|||
!HttpException::class.java.isAssignableFrom(throwable.javaClass) &&
|
||||
!retrofit2.HttpException::class.java.isAssignableFrom(throwable.javaClass) &&
|
||||
!EOFException::class.java.isAssignableFrom(throwable.javaClass) &&
|
||||
throwable !is ConnectionShutdownException
|
||||
throwable !is ConnectionShutdownException &&
|
||||
throwable !is CancellationException
|
||||
) {
|
||||
instance.analyticsManager?.logException(throwable)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,7 +54,14 @@ object MainNavigationController {
|
|||
|
||||
fun navigate(uriString: String) {
|
||||
val uri = Uri.parse(uriString)
|
||||
navigate(uri)
|
||||
var builder = uri.buildUpon()
|
||||
if (uri.scheme == null) {
|
||||
builder = builder.scheme("https")
|
||||
}
|
||||
if (uri.host == null) {
|
||||
builder = builder.authority("habitica.com")
|
||||
}
|
||||
navigate(builder.build())
|
||||
}
|
||||
|
||||
fun navigate(uri: Uri) {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import androidx.core.os.bundleOf
|
|||
import com.habitrpg.android.habitica.R
|
||||
import com.habitrpg.android.habitica.helpers.notifications.PushNotificationManager
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.MainScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class NotificationOpenHandler {
|
||||
|
|
@ -13,7 +13,7 @@ class NotificationOpenHandler {
|
|||
companion object {
|
||||
|
||||
fun handleOpenedByNotification(identifier: String, intent: Intent) {
|
||||
GlobalScope.launch(context = Dispatchers.Main) {
|
||||
MainScope().launch(context = Dispatchers.Main) {
|
||||
when (identifier) {
|
||||
PushNotificationManager.PARTY_INVITE_PUSH_NOTIFICATION_KEY -> 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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
@ -245,7 +247,7 @@ class PurchaseHandler(
|
|||
apiClient.validatePurchase(validationRequest)
|
||||
processedPurchase(purchase)
|
||||
val gift = removeGift(sku)
|
||||
CoroutineScope(Dispatchers.IO).launch(ExceptionHandler.coroutine()) {
|
||||
withContext(Dispatchers.IO) {
|
||||
consume(purchase)
|
||||
}
|
||||
displayGryphatriceConfirmationDialog(purchase, gift?.third)
|
||||
|
|
@ -261,7 +263,7 @@ class PurchaseHandler(
|
|||
apiClient.validatePurchase(validationRequest)
|
||||
processedPurchase(purchase)
|
||||
val gift = removeGift(sku)
|
||||
CoroutineScope(Dispatchers.IO).launch(ExceptionHandler.coroutine()) {
|
||||
withContext(Dispatchers.IO) {
|
||||
consume(purchase)
|
||||
}
|
||||
displayConfirmationDialog(purchase, gift?.third)
|
||||
|
|
@ -277,7 +279,7 @@ class PurchaseHandler(
|
|||
apiClient.validateNoRenewSubscription(validationRequest)
|
||||
processedPurchase(purchase)
|
||||
val gift = removeGift(sku)
|
||||
CoroutineScope(Dispatchers.IO).launch(ExceptionHandler.coroutine()) {
|
||||
withContext(Dispatchers.IO) {
|
||||
consume(purchase)
|
||||
}
|
||||
displayConfirmationDialog(purchase, gift?.third)
|
||||
|
|
@ -424,11 +426,17 @@ class PurchaseHandler(
|
|||
}
|
||||
}
|
||||
|
||||
private val displayedConfirmations = mutableListOf<String>()
|
||||
|
||||
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) -> {
|
||||
|
|
@ -478,7 +486,7 @@ class PurchaseHandler(
|
|||
}
|
||||
|
||||
private fun displayGryphatriceConfirmationDialog(purchase: Purchase, giftedTo: String? = null) {
|
||||
CoroutineScope(Dispatchers.Main).launch(ExceptionHandler.coroutine()) {
|
||||
MainScope().launch(ExceptionHandler.coroutine()) {
|
||||
val application = (context as? HabiticaBaseApplication)
|
||||
?: (context.applicationContext as? HabiticaBaseApplication) ?: return@launch
|
||||
val title = context.getString(R.string.successful_purchase_generic)
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -104,6 +104,7 @@ class ShowNotificationInteractor(
|
|||
}
|
||||
|
||||
lifecycleScope.launch(context = Dispatchers.Main) {
|
||||
if (activity.isFinishing) return@launch
|
||||
val alert = HabiticaAlertDialog(activity)
|
||||
alert.setAdditionalContentView(view)
|
||||
alert.setTitle(title)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ open class ShopItem : RealmObject(), BaseObject {
|
|||
|
||||
fun canAfford(user: User?, quantity: Int): Boolean = when (currency) {
|
||||
"gold" -> (value * quantity) <= (user?.stats?.gp ?: 0.0)
|
||||
"gems" -> (value * quantity) <= (user?.gemCount ?: 0)
|
||||
else -> true
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<Group>
|
||||
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<GroupCategory>? = null
|
||||
var purchased: SubscriptionPlan? = null
|
||||
|
||||
@Ignore
|
||||
var tasksOrder: TasksOrder? = null
|
||||
|
|
|
|||
|
|
@ -423,7 +423,7 @@ open class Task : RealmObject, BaseMainObject, Parcelable, BaseTask {
|
|||
else -> false
|
||||
}
|
||||
} else if (type == TaskType.TODO) {
|
||||
return dueDate != task.dueDate
|
||||
return (dueDate != task.dueDate && task.dueDate != null)
|
||||
} else if (type == TaskType.REWARD) {
|
||||
return value != task.value
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
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 android.text.format.DateFormat
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
|
|
@ -20,10 +20,17 @@ 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.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.livedata.observeAsState
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
|
|
@ -31,6 +38,7 @@ import androidx.compose.ui.draw.scale
|
|||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.res.painterResource
|
||||
|
|
@ -50,9 +58,11 @@ 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
|
||||
import com.habitrpg.android.habitica.extensions.addCloseButton
|
||||
import com.habitrpg.android.habitica.helpers.AppConfigManager
|
||||
import com.habitrpg.android.habitica.helpers.MainNavigationController
|
||||
import com.habitrpg.android.habitica.helpers.PurchaseHandler
|
||||
|
|
@ -60,23 +70,37 @@ 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.android.habitica.ui.views.dialogs.HabiticaAlertDialog
|
||||
import com.habitrpg.android.habitica.ui.views.insufficientCurrency.InsufficientGemsDialog
|
||||
import com.habitrpg.common.habitica.extensions.DataBindingUtils
|
||||
import kotlinx.coroutines.MainScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.map
|
||||
import java.text.DateFormat
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
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
|
||||
|
||||
val scaffoldState: ScaffoldState =
|
||||
ScaffoldState(DrawerState(DrawerValue.Closed), SnackbarHostState())
|
||||
private val isPurchasing = mutableStateOf(false)
|
||||
private val price = mutableStateOf("")
|
||||
private val hasGryphatrice = mutableStateOf(false)
|
||||
private val hasEquipped = mutableStateOf(false)
|
||||
private var gryphatriceProductDetails: ProductDetails? = null
|
||||
|
||||
override fun getLayoutResId(): Int? = null
|
||||
|
|
@ -86,20 +110,56 @@ 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)
|
||||
BirthdayActivityView(
|
||||
scaffoldState,
|
||||
isPurchasing.value,
|
||||
hasGryphatrice.value,
|
||||
hasEquipped.value,
|
||||
price.value,
|
||||
event?.start ?: Date(),
|
||||
event?.end ?: Date(),
|
||||
{
|
||||
gryphatriceProductDetails?.let {
|
||||
isPurchasing.value = true
|
||||
purchaseHandler.purchase(this, it)
|
||||
}
|
||||
},
|
||||
{
|
||||
lifecycleScope.launchCatching({
|
||||
isPurchasing.value = false
|
||||
}) {
|
||||
if ((userViewModel.user.value?.gemCount ?: 0) < 60) {
|
||||
val dialog = InsufficientGemsDialog(this@BirthdayActivity, 3)
|
||||
dialog.show()
|
||||
return@launchCatching
|
||||
}
|
||||
isPurchasing.value = true
|
||||
val dialog = HabiticaAlertDialog(this@BirthdayActivity)
|
||||
dialog.setTitle(
|
||||
getString(
|
||||
R.string.purchase_gryphatrice_confirmation,
|
||||
60
|
||||
)
|
||||
)
|
||||
dialog.addButton(
|
||||
getString(R.string.buy_for_x, "60 Gems"),
|
||||
true
|
||||
) { _, _ ->
|
||||
lifecycleScope.launchCatching {
|
||||
purchaseWithGems()
|
||||
}
|
||||
}
|
||||
dialog.addCloseButton { _, _ ->
|
||||
isPurchasing.value = false
|
||||
}
|
||||
dialog.show()
|
||||
}
|
||||
}
|
||||
}, {
|
||||
) {
|
||||
lifecycleScope.launchCatching {
|
||||
inventoryRepository.purchaseItem("", "Gryphatrice-Jubilant", 1)
|
||||
inventoryRepository.equip("pet", "Gryphatrice-Jubilant")
|
||||
}
|
||||
}, {
|
||||
lifecycleScope.launchCatching {
|
||||
inventoryRepository.equip("pets", "Gryphatrice-Jubilant")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -112,6 +172,22 @@ class BirthdayActivity : BaseActivity() {
|
|||
hasGryphatrice.value = (it?.trained ?: 0) >= 5
|
||||
}
|
||||
}
|
||||
|
||||
userViewModel.user.observe(this) {
|
||||
hasEquipped.value = it?.items?.currentPet == "Gryphatrice-Jubilant"
|
||||
}
|
||||
|
||||
lifecycleScope.launchCatching {
|
||||
gryphatriceProductDetails = purchaseHandler.getGryphatriceSKU()
|
||||
price.value =
|
||||
gryphatriceProductDetails?.oneTimePurchaseOfferDetails?.formattedPrice ?: ""
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun purchaseWithGems() {
|
||||
inventoryRepository.purchaseItem("pets", "Gryphatrice-Jubilant", 1)
|
||||
userRepository.retrieveUser(false, true)
|
||||
isPurchasing.value = false
|
||||
}
|
||||
|
||||
override fun injectActivity(component: UserComponent?) {
|
||||
|
|
@ -150,207 +226,303 @@ fun BirthdayTitle(text: String) {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
fun BirthdayActivityView(hasGryphatrice: Boolean, price: String, startDate: Date, endDate: Date, onPurchaseClick: () -> Unit, onGemPurchaseClick: () -> Unit, onEquipClick: () -> Unit) {
|
||||
fun BirthdayActivityView(
|
||||
scaffoldState: ScaffoldState,
|
||||
isPurchasing: Boolean,
|
||||
hasGryphatrice: Boolean,
|
||||
hasEquipped: 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 complexDateFormat = DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL)
|
||||
|
||||
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(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier = Modifier
|
||||
.padding(vertical = 20.dp)
|
||||
.size(161.dp, 129.dp)
|
||||
.background(colorResource(R.color.brand_50), RoundedCornerShape(8.dp))
|
||||
) {
|
||||
PixelArtView(
|
||||
imageName = "stable_Pet-Gryphatrice-Jubilant",
|
||||
Modifier.size(104.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(if (hasEquipped) R.string.unequip else R.string.equip),
|
||||
fontSize = 18.sp
|
||||
)
|
||||
}
|
||||
} 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), fontSize = 18.sp)
|
||||
}
|
||||
HabiticaButton(
|
||||
Color.White,
|
||||
colorResource(R.color.brand_200),
|
||||
{
|
||||
onGemPurchaseClick()
|
||||
},
|
||||
modifier = Modifier.padding(top = 20.dp)
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
Text(stringResource(R.string.buy_for), fontSize = 18.sp)
|
||||
CurrencyText(currency = "gems", value = 60, fontSize = 18.sp)
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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 {
|
||||
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), fontSize = 18.sp)
|
||||
}
|
||||
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 = "birthday10_robes",
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
FourFreeItem(
|
||||
day = 1,
|
||||
title = stringResource(R.string.twenty_gems),
|
||||
image = painterResource(R.drawable.birthday_gems),
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
}
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(14.dp),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
FourFreeItem(
|
||||
day = 5,
|
||||
title = stringResource(R.string.birthday_set),
|
||||
imageName = "birthday10_hero",
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
FourFreeItem(
|
||||
day = 10,
|
||||
title = stringResource(R.string.background),
|
||||
imageName = "birthday10_background",
|
||||
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),
|
||||
{
|
||||
onEquipClick()
|
||||
},
|
||||
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,
|
||||
complexDateFormat.format(startDate),
|
||||
complexDateFormat.format(endDate)
|
||||
),
|
||||
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(
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -369,15 +541,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 +566,48 @@ fun PotionGrid() {
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun FourFreeItem(
|
||||
day: Int,
|
||||
title: String,
|
||||
modifier: Modifier = Modifier,
|
||||
imageName: String? = null,
|
||||
image: Painter? = null
|
||||
) {
|
||||
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
|
||||
)
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier = Modifier
|
||||
.size(121.dp, 84.dp)
|
||||
.background(colorResource(R.color.brand_100), HabiticaTheme.shapes.medium)
|
||||
) {
|
||||
if (image != null) {
|
||||
Image(image, null)
|
||||
} else {
|
||||
PixelArtView(
|
||||
imageName,
|
||||
Modifier
|
||||
.size(84.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Text(title, color = Color.White, fontSize = 16.sp)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun HabiticaButton(
|
||||
background: Color,
|
||||
|
|
@ -411,9 +634,10 @@ fun HabiticaButton(
|
|||
}
|
||||
|
||||
@Preview(device = Devices.PIXEL_4)
|
||||
@Preview(device = Devices.PIXEL_4, uiMode = UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview() {
|
||||
BirthdayActivityView(false, "", Date(), Date(), {
|
||||
|
||||
}, {}, {})
|
||||
val scaffoldState = rememberScaffoldState()
|
||||
BirthdayActivityView(scaffoldState, true, false, false, "", Date(), Date(), {
|
||||
}, {}) {}
|
||||
}
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import android.content.res.Configuration
|
|||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.KeyEvent
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
|
|
@ -18,7 +17,6 @@ import androidx.activity.result.contract.ActivityResultContracts
|
|||
import androidx.activity.viewModels
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.appcompat.app.ActionBarDrawerToggle
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
|
|
@ -124,6 +122,21 @@ open class MainActivity : BaseActivity(), SnackbarActivity {
|
|||
private var sideAvatarView: AvatarView? = null
|
||||
private var drawerFragment: NavigationDrawerFragment? = null
|
||||
var drawerToggle: ActionBarDrawerToggle? = null
|
||||
var showBirthdayIcon = false
|
||||
var showBackButton: Boolean? = null
|
||||
set(value) {
|
||||
if (field == value) return
|
||||
if (value == true && showBirthdayIcon) {
|
||||
drawerToggle?.isDrawerIndicatorEnabled = false
|
||||
drawerToggle?.setHomeAsUpIndicator(R.drawable.arrow_back)
|
||||
} else if (value == false && showBirthdayIcon) {
|
||||
drawerToggle?.isDrawerIndicatorEnabled = false
|
||||
drawerToggle?.setHomeAsUpIndicator(R.drawable.icon_birthday)
|
||||
} else {
|
||||
drawerToggle?.isDrawerIndicatorEnabled = value != true
|
||||
}
|
||||
field = value
|
||||
}
|
||||
private var resumeFromActivity = false
|
||||
private var userQuestStatus = UserQuestStatus.NO_QUEST
|
||||
private var lastNotificationOpen: Long? = null
|
||||
|
|
@ -335,7 +348,7 @@ open class MainActivity : BaseActivity(), SnackbarActivity {
|
|||
return if (binding.root.parent is DrawerLayout && drawerToggle?.onOptionsItemSelected(item) == true) {
|
||||
true
|
||||
} else if (item.itemId == android.R.id.home) {
|
||||
if (drawerToggle?.isDrawerIndicatorEnabled == true) {
|
||||
if (showBackButton != true) {
|
||||
drawerFragment?.toggleDrawer()
|
||||
} else {
|
||||
MainNavigationController.navigateBack()
|
||||
|
|
|
|||
|
|
@ -24,16 +24,19 @@ import com.habitrpg.android.habitica.helpers.ExceptionHandler
|
|||
import com.habitrpg.android.habitica.helpers.launchCatching
|
||||
import com.habitrpg.android.habitica.models.inventory.QuestContent
|
||||
import com.habitrpg.android.habitica.ui.viewmodels.NotificationsViewModel
|
||||
import com.habitrpg.common.habitica.extensions.loadImage
|
||||
import com.habitrpg.common.habitica.models.Notification
|
||||
import com.habitrpg.common.habitica.models.notifications.GroupTaskApprovedData
|
||||
import com.habitrpg.common.habitica.models.notifications.GroupTaskNeedsWorkData
|
||||
import com.habitrpg.common.habitica.models.notifications.GroupTaskRequiresApprovalData
|
||||
import com.habitrpg.common.habitica.models.notifications.GuildInvitationData
|
||||
import com.habitrpg.common.habitica.models.notifications.ItemReceivedData
|
||||
import com.habitrpg.common.habitica.models.notifications.NewChatMessageData
|
||||
import com.habitrpg.common.habitica.models.notifications.NewStuffData
|
||||
import com.habitrpg.common.habitica.models.notifications.PartyInvitationData
|
||||
import com.habitrpg.common.habitica.models.notifications.QuestInvitationData
|
||||
import com.habitrpg.common.habitica.models.notifications.UnallocatedPointsData
|
||||
import com.habitrpg.common.habitica.views.PixelArtView
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
|
@ -134,6 +137,7 @@ class NotificationsActivity : BaseActivity(), androidx.swiperefreshlayout.widget
|
|||
Notification.Type.PARTY_INVITATION.type -> createPartyInvitationNotification(it)
|
||||
Notification.Type.GUILD_INVITATION.type -> createGuildInvitationNotification(it)
|
||||
Notification.Type.QUEST_INVITATION.type -> createQuestInvitationNotification(it)
|
||||
Notification.Type.ITEM_RECEIVED.type -> createItemReceivedNotification(it)
|
||||
else -> null
|
||||
}
|
||||
|
||||
|
|
@ -165,6 +169,15 @@ class NotificationsActivity : BaseActivity(), androidx.swiperefreshlayout.widget
|
|||
)
|
||||
}
|
||||
|
||||
private fun createItemReceivedNotification(notification: Notification): View? {
|
||||
val data = notification.data as? ItemReceivedData
|
||||
return createDismissableNotificationItem(
|
||||
notification,
|
||||
fromHtml("<b>" + data?.title + "</b><br>" + data?.text),
|
||||
imageName = data?.icon
|
||||
)
|
||||
}
|
||||
|
||||
private fun createNewStuffNotification(notification: Notification): View? {
|
||||
val data = notification.data as? NewStuffData
|
||||
val text = if (data?.title != null) {
|
||||
|
|
@ -206,7 +219,7 @@ class NotificationsActivity : BaseActivity(), androidx.swiperefreshlayout.widget
|
|||
notification,
|
||||
fromHtml(message),
|
||||
null,
|
||||
R.color.yellow_5
|
||||
textColor = R.color.yellow_5
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -218,7 +231,7 @@ class NotificationsActivity : BaseActivity(), androidx.swiperefreshlayout.widget
|
|||
notification,
|
||||
fromHtml(message),
|
||||
null,
|
||||
R.color.green_10
|
||||
textColor = R.color.green_10
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -252,6 +265,7 @@ class NotificationsActivity : BaseActivity(), androidx.swiperefreshlayout.widget
|
|||
notification: Notification,
|
||||
messageText: CharSequence,
|
||||
imageResourceId: Int? = null,
|
||||
imageName: String? = null,
|
||||
textColor: Int? = null
|
||||
): View? {
|
||||
val item = inflater?.inflate(R.layout.notification_item, binding.notificationItems, false)
|
||||
|
|
@ -276,6 +290,12 @@ class NotificationsActivity : BaseActivity(), androidx.swiperefreshlayout.widget
|
|||
notificationImage?.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
if (imageName != null) {
|
||||
val notificationImage = item?.findViewById(R.id.notification_image) as? PixelArtView
|
||||
notificationImage?.loadImage(imageName)
|
||||
notificationImage?.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
if (textColor != null) {
|
||||
messageTextView?.setTextColor(ContextCompat.getColor(this, textColor))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import android.app.Activity
|
|||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
|
|
@ -17,7 +16,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
|
||||
|
|
@ -256,6 +254,12 @@ class TaskFormActivity : BaseActivity() {
|
|||
{
|
||||
showAssignDialog()
|
||||
},
|
||||
{
|
||||
taskCompletedMap.remove(it)
|
||||
lifecycleScope.launchCatching {
|
||||
task?.let { it1 -> taskRepository.markTaskNeedsWork(it1, it) }
|
||||
}
|
||||
},
|
||||
showEditButton = true
|
||||
)
|
||||
}
|
||||
|
|
@ -517,7 +521,7 @@ class TaskFormActivity : BaseActivity() {
|
|||
val view = CheckBox(this)
|
||||
view.setPadding(padding, view.paddingTop, view.paddingRight, view.paddingBottom)
|
||||
view.text = tag.name
|
||||
view.setTextColor(getThemeColor(R.attr.colorPrimaryDark))
|
||||
view.setTextColor(getThemeColor(R.attr.textColorTintedPrimary))
|
||||
if (preselectedTags?.contains(tag.id) == true) {
|
||||
view.isChecked = true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import com.habitrpg.common.habitica.views.PixelArtView
|
|||
|
||||
class CustomizationEquipmentRecyclerViewAdapter : androidx.recyclerview.widget.RecyclerView.Adapter<androidx.recyclerview.widget.RecyclerView.ViewHolder>() {
|
||||
|
||||
var gemBalance: Int = 0
|
||||
var gemBalance: Int? = null
|
||||
var equipmentList: MutableList<Equipment> =
|
||||
ArrayList()
|
||||
set(value) {
|
||||
|
|
@ -111,7 +111,7 @@ class CustomizationEquipmentRecyclerViewAdapter : androidx.recyclerview.widget.R
|
|||
imageView.loadImage("shop_" + this.equipment?.key)
|
||||
|
||||
val priceLabel = dialogContent.findViewById<TextView>(R.id.priceLabel)
|
||||
priceLabel.text = if (equipment?.gearSet == "animal") {
|
||||
priceLabel?.text = if (equipment?.gearSet == "animal") {
|
||||
2.0
|
||||
} else {
|
||||
equipment?.value ?: 0
|
||||
|
|
@ -122,9 +122,14 @@ class CustomizationEquipmentRecyclerViewAdapter : androidx.recyclerview.widget.R
|
|||
|
||||
val dialog = HabiticaAlertDialog(itemView.context)
|
||||
dialog.addButton(R.string.purchase_button, true) { _, _ ->
|
||||
if ((equipment?.value ?: 0.0) > gemBalance) {
|
||||
MainNavigationController.navigate(R.id.gemPurchaseActivity, bundleOf(Pair("openSubscription", false)))
|
||||
return@addButton
|
||||
gemBalance?.let {
|
||||
if ((equipment?.value ?: 0.0) > it) {
|
||||
MainNavigationController.navigate(
|
||||
R.id.gemPurchaseActivity,
|
||||
bundleOf(Pair("openSubscription", false))
|
||||
)
|
||||
return@addButton
|
||||
}
|
||||
}
|
||||
|
||||
equipment?.let {
|
||||
|
|
|
|||
|
|
@ -110,18 +110,19 @@ class CustomizationRecyclerViewAdapter() : androidx.recyclerview.widget.Recycler
|
|||
var lastSet = CustomizationSet()
|
||||
val today = Date()
|
||||
for (customization in newCustomizationList) {
|
||||
val isOwned = ownedCustomizations.contains(customization.id)
|
||||
val isUsable = customization.isUsable(isOwned)
|
||||
if (customization.availableFrom != null || customization.availableUntil != null) {
|
||||
if (((customization.availableFrom?.compareTo(today)
|
||||
?: 0) > 0 || (customization.availableUntil?.compareTo(today)
|
||||
?: 0) < 0) && !customization.isUsable(
|
||||
ownedCustomizations.contains(
|
||||
customization.id
|
||||
)
|
||||
)
|
||||
?: 0) < 0) && !isUsable
|
||||
) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if (customization.identifier?.contains("birthday_bash") == true && !isOwned) {
|
||||
continue
|
||||
}
|
||||
if (customization.customizationSet != null && customization.customizationSet != lastSet.identifier) {
|
||||
if (lastSet.hasPurchasable && lastSet.price > 0) {
|
||||
customizationList.add(lastSet)
|
||||
|
|
@ -136,7 +137,7 @@ class CustomizationRecyclerViewAdapter() : androidx.recyclerview.widget.Recycler
|
|||
}
|
||||
customizationList.add(customization)
|
||||
lastSet.customizations.add(customization)
|
||||
if (customization.isUsable(ownedCustomizations.contains(customization.id)) && lastSet.hasPurchasable) {
|
||||
if (isUsable && lastSet.hasPurchasable) {
|
||||
lastSet.ownedCustomizations.add(customization)
|
||||
if (!lastSet.isSetDeal()) {
|
||||
lastSet.hasPurchasable = false
|
||||
|
|
|
|||
|
|
@ -189,7 +189,7 @@ class ItemRecyclerAdapter(val context: Context) : BaseRecyclerViewAdapter<OwnedI
|
|||
menu.setSelectionRunnable { index ->
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ abstract class BaseMainFragment<VB : ViewBinding> : BaseFragment<VB>() {
|
|||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
activity?.drawerToggle?.isDrawerIndicatorEnabled = !showsBackButton
|
||||
activity?.showBackButton = showsBackButton
|
||||
activity?.supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
@ -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,23 @@ class NavigationDrawerFragment : DialogFragment() {
|
|||
}) {
|
||||
updateSeasonalMenuEntries(gearEvent, pair.second)
|
||||
}
|
||||
|
||||
val event = configManager.getBirthdayEvent()
|
||||
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<HabiticaDrawerItem>()
|
||||
newItems.addAll(adapter.items)
|
||||
newItems.add(0, birthdayItem)
|
||||
adapter.updateItems(newItems)
|
||||
(activity as? MainActivity)?.showBirthdayIcon = true
|
||||
} else if (event == null && item != null) {
|
||||
item.isVisible = false
|
||||
adapter.updateItem(item)
|
||||
(activity as? MainActivity)?.showBirthdayIcon = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -551,12 +568,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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ class AvatarCustomizationFragment :
|
|||
internal var adapter: CustomizationRecyclerViewAdapter = CustomizationRecyclerViewAdapter()
|
||||
internal var layoutManager: FlexboxLayoutManager = FlexboxLayoutManager(activity, ROW)
|
||||
|
||||
private val currentFilter = MutableStateFlow(CustomizationFilter(false, type == "background"))
|
||||
private val currentFilter = MutableStateFlow(CustomizationFilter(false, true))
|
||||
private val ownedCustomizations = MutableStateFlow<List<OwnedCustomization>>(emptyList())
|
||||
|
||||
override fun onCreateView(
|
||||
|
|
@ -112,6 +112,7 @@ class AvatarCustomizationFragment :
|
|||
if (args.category.isNotEmpty()) {
|
||||
category = args.category
|
||||
}
|
||||
currentFilter.value.ascending = type != "background"
|
||||
}
|
||||
adapter.customizationType = type
|
||||
binding?.refreshLayout?.setOnRefreshListener(this)
|
||||
|
|
@ -334,6 +335,8 @@ class AvatarCustomizationFragment :
|
|||
button.text
|
||||
button.setOnCheckedChangeListener { _, isChecked ->
|
||||
val newFilter = filter.copy()
|
||||
newFilter.months = mutableListOf()
|
||||
newFilter.months.addAll(currentFilter.value.months)
|
||||
if (!isChecked && newFilter.months.contains(identifier)) {
|
||||
button.typeface = Typeface.create("sans-serif", Typeface.NORMAL)
|
||||
newFilter.months.remove(identifier)
|
||||
|
|
|
|||
|
|
@ -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 = ""
|
||||
|
|
|
|||
|
|
@ -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<FragmentGemPurchaseBinding>() {
|
|||
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")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<FragmentGiftGemBalanceBinding>() {
|
||||
|
||||
@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<FragmentGiftGemBalanceBinding>() {
|
|||
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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<FragmentSubscriptionBinding>() {
|
|||
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 {
|
||||
|
|
|
|||
|
|
@ -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<FragmentChatBinding>() {
|
||||
class ChatFragment : BaseFragment<FragmentChatBinding>() {
|
||||
|
||||
override var binding: FragmentChatBinding? = null
|
||||
|
||||
|
|
@ -40,9 +39,7 @@ class ChatFragment() : BaseFragment<FragmentChatBinding>() {
|
|||
return FragmentChatBinding.inflate(inflater, container, false)
|
||||
}
|
||||
|
||||
val viewModel: GroupViewModel by viewModels(
|
||||
ownerProducer = { requireParentFragment() }
|
||||
)
|
||||
lateinit var viewModel: GroupViewModel
|
||||
|
||||
@Inject
|
||||
lateinit var configManager: AppConfigManager
|
||||
|
|
|
|||
|
|
@ -91,7 +91,9 @@ class TavernFragment : BaseMainFragment<FragmentViewpagerBinding>() {
|
|||
TavernDetailFragment()
|
||||
}
|
||||
1 -> {
|
||||
ChatFragment()
|
||||
val fragment = ChatFragment()
|
||||
fragment.viewModel = viewModel
|
||||
fragment
|
||||
}
|
||||
else -> Fragment()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<FragmentGuildDetailBinding>() {
|
|||
binding?.guildBankText?.text = guild?.gemCount.toString()
|
||||
binding?.guildSummary?.setMarkdown(guild?.summary)
|
||||
binding?.guildDescription?.setMarkdown(guild?.description)
|
||||
|
||||
binding?.inviteButton?.isVisible = guild?.isGroupPlan != true
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
|||
|
|
@ -154,6 +154,7 @@ class GuildFragment : BaseMainFragment<FragmentViewpagerBinding>() {
|
|||
}
|
||||
1 -> {
|
||||
chatFragment = ChatFragment()
|
||||
chatFragment?.viewModel = viewModel
|
||||
fragment = chatFragment
|
||||
}
|
||||
else -> fragment = Fragment()
|
||||
|
|
|
|||
|
|
@ -98,8 +98,8 @@ class GuildOverviewFragment : BaseMainFragment<FragmentViewpagerBinding>(), Sear
|
|||
val uriUrl = "https://habitica.com/groups/myGuilds".toUri()
|
||||
val launchBrowser = Intent(Intent.ACTION_VIEW, uriUrl)
|
||||
val l = context.packageManager.queryIntentActivities(launchBrowser, PackageManager.MATCH_DEFAULT_ONLY)
|
||||
val notHabitica = l.first { !it.activityInfo.processName.contains("habitica") }
|
||||
launchBrowser.setPackage(notHabitica.activityInfo.processName)
|
||||
val notHabitica = l.firstOrNull() { !it.activityInfo.processName.contains("habitica") }
|
||||
launchBrowser.setPackage(notHabitica?.activityInfo?.processName)
|
||||
startActivity(launchBrowser)
|
||||
}
|
||||
dialog.addCloseButton()
|
||||
|
|
|
|||
|
|
@ -196,6 +196,7 @@ class PartyFragment : BaseMainFragment<FragmentViewpagerBinding>() {
|
|||
}
|
||||
1 -> {
|
||||
chatFragment = ChatFragment()
|
||||
chatFragment?.viewModel = viewModel
|
||||
chatFragment
|
||||
}
|
||||
else -> Fragment()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -141,6 +141,10 @@ object HabiticaTheme {
|
|||
textTertiary = Color(ContextCompat.getColor(context, R.color.text_ternary)),
|
||||
textQuad = Color(ContextCompat.getColor(context, R.color.text_quad)),
|
||||
textDimmed = Color(ContextCompat.getColor(context, R.color.text_dimmed)),
|
||||
tintedUiMain = Color(context.getThemeColor(R.attr.tintedUiMain)),
|
||||
tintedUiSub = Color(context.getThemeColor(R.attr.tintedUiSub)),
|
||||
tintedUiDetails = Color(context.getThemeColor(R.attr.tintedUiDetails)),
|
||||
pixelArtBackground = Color(context.getThemeColor(R.attr.colorContentBackground))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -153,7 +157,11 @@ class HabiticaColors(
|
|||
val textSecondary: Color,
|
||||
val textTertiary: Color,
|
||||
val textQuad: Color,
|
||||
val textDimmed: Color
|
||||
val textDimmed: Color,
|
||||
val tintedUiMain: Color,
|
||||
val tintedUiSub: Color,
|
||||
val tintedUiDetails: Color,
|
||||
val pixelArtBackground: Color
|
||||
) {
|
||||
@Composable
|
||||
fun textPrimaryFor(task: Task?): Color {
|
||||
|
|
@ -179,4 +187,13 @@ class HabiticaColors(
|
|||
fun contentBackgroundFor(task: Task?): Color {
|
||||
return (if (isSystemInDarkTheme()) task?.darkestTaskColor else task?.lightestTaskColor)?.let { colorResource(it) } ?: windowBackground
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun pixelArtBackground(hasIcon: Boolean): Color {
|
||||
return if (isSystemInDarkTheme()) {
|
||||
colorResource(if (hasIcon) R.color.gray_200 else R.color.gray_5)
|
||||
} else {
|
||||
colorResource(if (hasIcon) R.color.content_background else R.color.content_background_offset)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -44,6 +44,7 @@ abstract class BaseTaskViewHolder constructor(
|
|||
protected var context: Context
|
||||
private val mainTaskWrapper: ViewGroup = itemView.findViewById(R.id.main_task_wrapper)
|
||||
protected val assignedTextView: TextView = itemView.findViewById(R.id.assigned_textview)
|
||||
protected val completedCountTextView: TextView = itemView.findViewById(R.id.completed_textview)
|
||||
protected val titleTextView: EllipsisTextView = itemView.findViewById(R.id.checkedTextView)
|
||||
protected val notesTextView: EllipsisTextView? = itemView.findViewById(R.id.notesTextView)
|
||||
protected val calendarIconView: ImageView? = itemView.findViewById(R.id.iconViewCalendar)
|
||||
|
|
@ -249,6 +250,14 @@ abstract class BaseTaskViewHolder constructor(
|
|||
assignedTextView.visibility = View.GONE
|
||||
}
|
||||
|
||||
val completedCount = data.group?.assignedUsersDetail?.filter { it.completed }?.size ?: 0
|
||||
if (completedCount > 0) {
|
||||
completedCountTextView.text = "$completedCount/${data?.group?.assignedUsersDetail?.size}"
|
||||
completedCountTextView.visibility = View.VISIBLE
|
||||
} else {
|
||||
completedCountTextView.visibility = View.GONE
|
||||
}
|
||||
|
||||
syncingView?.visibility = if (task?.isSaving == true) View.VISIBLE else View.GONE
|
||||
errorIconView?.visibility = if (task?.hasErrored == true) View.VISIBLE else View.GONE
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import com.habitrpg.common.habitica.models.Notification
|
|||
import com.habitrpg.common.habitica.models.notifications.GroupTaskRequiresApprovalData
|
||||
import com.habitrpg.common.habitica.models.notifications.GuildInvitationData
|
||||
import com.habitrpg.common.habitica.models.notifications.GuildInvite
|
||||
import com.habitrpg.common.habitica.models.notifications.ItemReceivedData
|
||||
import com.habitrpg.common.habitica.models.notifications.NewChatMessageData
|
||||
import com.habitrpg.common.habitica.models.notifications.NewStuffData
|
||||
import com.habitrpg.common.habitica.models.notifications.PartyInvitationData
|
||||
|
|
@ -41,7 +42,8 @@ open class NotificationsViewModel : BaseViewModel() {
|
|||
Notification.Type.NEW_MYSTERY_ITEMS.type,
|
||||
Notification.Type.GROUP_TASK_NEEDS_WORK.type,
|
||||
Notification.Type.GROUP_TASK_APPROVED.type,
|
||||
Notification.Type.UNALLOCATED_STATS_POINTS.type
|
||||
Notification.Type.UNALLOCATED_STATS_POINTS.type,
|
||||
Notification.Type.ITEM_RECEIVED.type
|
||||
)
|
||||
|
||||
private val actionableNotificationTypes = listOf(
|
||||
|
|
@ -262,6 +264,19 @@ open class NotificationsViewModel : BaseViewModel() {
|
|||
// Group tasks should go to Group tasks view if that is added to this app at some point
|
||||
Notification.Type.GROUP_TASK_APPROVED.type -> navController.navigate(R.id.tasksFragment)
|
||||
Notification.Type.GROUP_TASK_NEEDS_WORK.type -> navController.navigate(R.id.tasksFragment)
|
||||
Notification.Type.ITEM_RECEIVED.type -> clickItemReceivedNotification(notification, navController)
|
||||
}
|
||||
}
|
||||
|
||||
private fun clickItemReceivedNotification(
|
||||
notification: Notification,
|
||||
navController: MainNavigationController
|
||||
) {
|
||||
val data = notification.data as? ItemReceivedData
|
||||
when (data?.destination) {
|
||||
"equipment" -> navController.navigate(R.id.equipmentOverviewFragment)
|
||||
"customization" -> navController.navigate(R.id.avatarCustomizationFragment)
|
||||
else -> navController.navigate(R.id.itemsFragment)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.graphics.asImageBitmap
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.TextUnit
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.habitrpg.android.habitica.R
|
||||
|
|
@ -22,17 +23,19 @@ fun CurrencyText(
|
|||
currency: String,
|
||||
value: Int,
|
||||
modifier: Modifier = Modifier,
|
||||
fontSize: TextUnit = 12.sp,
|
||||
decimals: Int = 0,
|
||||
minForAbbreviation: Int = 0,
|
||||
animated: Boolean = true
|
||||
) {
|
||||
CurrencyText(currency = currency, value = value.toDouble(), modifier, decimals, minForAbbreviation, animated)
|
||||
CurrencyText(currency = currency, value = value.toDouble(), modifier, fontSize, decimals, minForAbbreviation, animated)
|
||||
}
|
||||
@Composable
|
||||
fun CurrencyText(
|
||||
currency: String,
|
||||
value: Double,
|
||||
modifier: Modifier = Modifier,
|
||||
fontSize: TextUnit = 12.sp,
|
||||
decimals: Int = 0,
|
||||
minForAbbreviation: Int = 0,
|
||||
animated: Boolean = true
|
||||
|
|
@ -56,7 +59,7 @@ fun CurrencyText(
|
|||
"hourglasses" -> colorResource(R.color.text_brand)
|
||||
else -> colorResource(R.color.text_primary)
|
||||
},
|
||||
fontSize = 12.sp,
|
||||
fontSize = fontSize,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ fun OverviewItem(
|
|||
Modifier
|
||||
.size(70.dp)
|
||||
.clip(MaterialTheme.shapes.small)
|
||||
.background(colorResource(if (hasIcon) R.color.content_background else R.color.content_background_offset)),
|
||||
.background(HabiticaTheme.colors.pixelArtBackground(hasIcon)),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
if (isTwoHanded) {
|
||||
|
|
@ -55,6 +55,7 @@ fun OverviewItem(
|
|||
PixelArtView(
|
||||
imageName = iconName, modifier = Modifier
|
||||
.size(70.dp)
|
||||
|
||||
)
|
||||
} else {
|
||||
Image(painterResource(R.drawable.empty_slot), null)
|
||||
|
|
@ -82,7 +83,7 @@ fun EquipmentOverviewView(
|
|||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.clip(MaterialTheme.shapes.medium)
|
||||
.background(colorResource(R.color.offset_background))
|
||||
.background(colorResource(R.color.equipment_column_background))
|
||||
.padding(12.dp)
|
||||
) {
|
||||
Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) {
|
||||
|
|
@ -133,7 +134,7 @@ fun AvatarCustomizationOverviewView(
|
|||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.clip(MaterialTheme.shapes.medium)
|
||||
.background(colorResource(R.color.offset_background))
|
||||
.background(colorResource(R.color.equipment_column_background))
|
||||
.padding(12.dp)
|
||||
) {
|
||||
Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) {
|
||||
|
|
@ -228,7 +229,8 @@ fun AvatarCustomizationOverviewView(
|
|||
@Composable
|
||||
fun EquipmentOverviewItemPreview() {
|
||||
Column(Modifier.width(320.dp)) {
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Row(modifier = Modifier.background(colorResource(id = R.color.equipment_overview_background)),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
OverviewItem("Main-Hand", "shop_weapon_warrior_1")
|
||||
OverviewItem("Off-Hand", null, isTwoHanded = true)
|
||||
OverviewItem("Armor", null)
|
||||
|
|
|
|||
|
|
@ -1,78 +1,101 @@
|
|||
package com.habitrpg.android.habitica.ui.views.promo
|
||||
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.requiredSize
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.scale
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.TextUnit
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.habitrpg.android.habitica.R
|
||||
import com.habitrpg.android.habitica.extensions.getShortRemainingString
|
||||
import com.habitrpg.android.habitica.helpers.MainNavigationController
|
||||
import com.habitrpg.android.habitica.ui.views.PixelArtView
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.delay
|
||||
import java.util.Date
|
||||
import kotlin.time.Duration.Companion.hours
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
import kotlin.time.Duration.Companion.minutes
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
@Composable
|
||||
fun BirthdayBanner(endDate: Date) {
|
||||
var value by remember { mutableStateOf(0) }
|
||||
|
||||
DisposableEffect(Unit) {
|
||||
val handler = Handler(Looper.getMainLooper())
|
||||
|
||||
val runnable = {
|
||||
value += 1
|
||||
}
|
||||
|
||||
handler.postDelayed(runnable, 1000)
|
||||
|
||||
onDispose {
|
||||
handler.removeCallbacks(runnable)
|
||||
}
|
||||
fun BirthdayBanner(endDate: Date, modifier: Modifier = Modifier) {
|
||||
if (endDate.before(Date())) {
|
||||
return
|
||||
}
|
||||
Column(
|
||||
Modifier
|
||||
modifier
|
||||
.fillMaxWidth()
|
||||
.clickable {
|
||||
MainNavigationController.navigate(R.id.birthdayActivity)
|
||||
}
|
||||
) {
|
||||
}) {
|
||||
Column(Modifier.fillMaxWidth()) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(6.dp, Alignment.CenterVertically),
|
||||
Box(
|
||||
contentAlignment = Alignment.CenterStart,
|
||||
modifier = Modifier
|
||||
.height(67.dp)
|
||||
.fillMaxWidth()
|
||||
.background(colorResource(R.color.brand_100))
|
||||
.padding(start = 10.dp)) {
|
||||
Image(
|
||||
painterResource(R.drawable.birthday_menu_text), null
|
||||
)
|
||||
Text(
|
||||
stringResource(R.string.exclusive_items_await),
|
||||
color = colorResource(R.color.yellow_100),
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
) {
|
||||
Row(
|
||||
Modifier.align(Alignment.CenterEnd)
|
||||
) {
|
||||
Image(
|
||||
painterResource(R.drawable.birthday_menu_gems),
|
||||
null,
|
||||
modifier = Modifier
|
||||
.align(Alignment.Top)
|
||||
.offset((40).dp)
|
||||
)
|
||||
PixelArtView(
|
||||
imageName = "stable_Pet-Gryphatrice-Jubilant",
|
||||
Modifier
|
||||
.requiredSize(104.dp)
|
||||
.scale(-1f, 1f)
|
||||
.offset((-30).dp)
|
||||
)
|
||||
}
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(
|
||||
2.dp, Alignment.CenterVertically
|
||||
), modifier = Modifier.padding(start = 8.dp)
|
||||
) {
|
||||
Image(
|
||||
painterResource(R.drawable.birthday_menu_text), null
|
||||
)
|
||||
Text(
|
||||
stringResource(R.string.exclusive_items_await),
|
||||
color = colorResource(R.color.yellow_100),
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
modifier = Modifier.padding(start = 2.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
|
|
@ -82,8 +105,9 @@ fun BirthdayBanner(endDate: Date) {
|
|||
.background(colorResource(R.color.brand_300))
|
||||
.padding(horizontal = 10.dp)
|
||||
) {
|
||||
Text(
|
||||
stringResource(R.string.ends_in_x, endDate.getShortRemainingString()).uppercase(),
|
||||
TimeRemainingText(
|
||||
endDate,
|
||||
R.string.ends_in_x,
|
||||
color = colorResource(R.color.yellow_50),
|
||||
fontSize = 12.sp,
|
||||
fontWeight = FontWeight.Bold
|
||||
|
|
@ -98,4 +122,37 @@ fun BirthdayBanner(endDate: Date) {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun buildString(
|
||||
value: Int, endDate: Date, formatString: Int
|
||||
): String {
|
||||
return stringResource(
|
||||
formatString, endDate.getShortRemainingString()
|
||||
).uppercase()
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TimeRemainingText(
|
||||
endDate: Date, formatString: Int, color: Color, fontSize: TextUnit, fontWeight: FontWeight
|
||||
) {
|
||||
var value by remember { mutableStateOf(0) }
|
||||
LaunchedEffect(value) {
|
||||
val diff = endDate.time - Date().time
|
||||
if (diff.milliseconds > 1.hours) {
|
||||
delay(1.minutes)
|
||||
} else if (diff < 0) {
|
||||
this.cancel()
|
||||
} else {
|
||||
delay(1.seconds)
|
||||
}
|
||||
value += 1
|
||||
}
|
||||
Text(
|
||||
buildString(value = value, endDate = endDate, formatString = formatString),
|
||||
color = color,
|
||||
fontSize = fontSize,
|
||||
fontWeight = fontWeight
|
||||
)
|
||||
}
|
||||
|
|
@ -401,9 +401,13 @@ class PurchaseDialog(context: Context, component: UserComponent?, val item: Shop
|
|||
observable = { inventoryRepository.purchaseItem(shopItem.purchaseType, shopItem.key, quantity) }
|
||||
}
|
||||
lifecycleScope.launchCatching {
|
||||
val result = observable() ?: return@launchCatching
|
||||
val text = snackbarText[0].ifEmpty {
|
||||
context.getString(R.string.successful_purchase, shopItem.text)
|
||||
observable()
|
||||
val text = snackbarText[0].ifBlank {
|
||||
if (shopItem.text?.isNotBlank() == true) {
|
||||
context.getString(R.string.successful_purchase, shopItem.text)
|
||||
} else {
|
||||
context.getString(R.string.purchased)
|
||||
}
|
||||
}
|
||||
val rightTextColor = when (item.currency) {
|
||||
"gold" -> ContextCompat.getColor(context, R.color.text_yellow)
|
||||
|
|
@ -418,6 +422,7 @@ class PurchaseDialog(context: Context, component: UserComponent?, val item: Shop
|
|||
rightText = "-" + priceLabel.text
|
||||
)
|
||||
inventoryRepository.retrieveInAppRewards()
|
||||
userRepository.retrieveUser()
|
||||
if (item.isTypeGear || item.currency == "hourglasses") {
|
||||
onGearPurchased?.invoke(item)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,17 +50,30 @@ 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) }
|
||||
},
|
||||
endContent = {
|
||||
completedAt[assignable.id]?.let {
|
||||
if (showEditButton) {
|
||||
UndoTaskCompletion(Modifier.clickable {
|
||||
assignable.id?.let { it1 -> onUndoClick(it1) }
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
@ -71,7 +93,7 @@ fun AssignedView(
|
|||
Image(
|
||||
painterResource(R.drawable.edit),
|
||||
null,
|
||||
colorFilter = ColorFilter.tint(MaterialTheme.colors.primary)
|
||||
colorFilter = ColorFilter.tint(color)
|
||||
)
|
||||
Text(
|
||||
stringResource(R.string.edit_assignees), color = color,
|
||||
|
|
@ -80,4 +102,31 @@ fun AssignedView(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,6 @@
|
|||
package com.habitrpg.android.habitica.ui.views.tasks.form
|
||||
|
||||
import android.content.Context
|
||||
import android.text.Spannable
|
||||
import android.text.SpannableString
|
||||
import android.text.TextUtils
|
||||
import android.text.method.LinkMovementMethod
|
||||
import android.text.util.Linkify
|
||||
import android.util.AttributeSet
|
||||
import android.view.Gravity
|
||||
import android.view.View
|
||||
|
|
@ -38,7 +33,7 @@ class ChecklistItemFormView @JvmOverloads constructor(
|
|||
binding.editText.setText(item.text)
|
||||
}
|
||||
|
||||
var tintColor: Int = context.getThemeColor(R.attr.taskFormTint)
|
||||
var tintColor: Int = context.getThemeColor(R.attr.tintedUiSub)
|
||||
var textChangedListener: ((String) -> Unit)? = null
|
||||
var animDuration = 0L
|
||||
var isAddButton: Boolean = true
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@ import androidx.compose.ui.graphics.Color
|
|||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
|
|
@ -40,6 +39,7 @@ import androidx.compose.ui.tooling.preview.Preview
|
|||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.habitrpg.android.habitica.R
|
||||
import com.habitrpg.android.habitica.ui.theme.HabiticaTheme
|
||||
import com.habitrpg.common.habitica.extensions.getThemeColor
|
||||
|
||||
@Composable
|
||||
|
|
@ -80,14 +80,15 @@ private fun HabitScoringSelection(
|
|||
) {
|
||||
val selectedState = updateTransition(selected)
|
||||
val context = LocalContext.current
|
||||
|
||||
val borderColor = selectedState.animateColor {
|
||||
if (it) HabiticaTheme.colors.tintedUiMain else Color(context.getThemeColor(R.attr.textColorTintedSecondary))
|
||||
}
|
||||
val iconColor = selectedState.animateColor {
|
||||
if (it) Color(context.getThemeColor(R.attr.colorTintedBackground)) else colorResource(R.color.text_dimmed)
|
||||
if (it) HabiticaTheme.colors.tintedUiDetails else Color(context.getThemeColor(R.attr.textColorTintedSecondary))
|
||||
}
|
||||
val textColor = selectedState.animateColor {
|
||||
if (it) MaterialTheme.colors.primary else Color(context.getThemeColor(R.attr.textColorTintedSecondary))
|
||||
}
|
||||
val borderColor = selectedState.animateColor {
|
||||
if (it) MaterialTheme.colors.primary else Color(context.getThemeColor(R.attr.textColorTintedSecondary))
|
||||
if (it) Color(context.getThemeColor(R.attr.textColorTintedPrimary)) else Color(context.getThemeColor(R.attr.textColorTintedSecondary))
|
||||
}
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(12.dp), modifier = modifier) {
|
||||
Box(
|
||||
|
|
@ -107,7 +108,7 @@ private fun HabitScoringSelection(
|
|||
Box(
|
||||
Modifier
|
||||
.size(32.dp)
|
||||
.background(MaterialTheme.colors.primary, CircleShape)
|
||||
.background(HabiticaTheme.colors.tintedUiMain, CircleShape)
|
||||
)
|
||||
}
|
||||
Image(icon, null, colorFilter = ColorFilter.tint(iconColor.value))
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ class ReminderItemFormView @JvmOverloads constructor(
|
|||
|
||||
var firstDayOfWeek: Int? = null
|
||||
|
||||
var tintColor: Int = context.getThemeColor(R.attr.taskFormTint)
|
||||
var tintColor: Int = context.getThemeColor(R.attr.tintedUiSub)
|
||||
var valueChangedListener: ((Date) -> Unit)? = null
|
||||
var animDuration = 0L
|
||||
var isAddButton: Boolean = true
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
|||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.habitrpg.android.habitica.R
|
||||
import com.habitrpg.android.habitica.ui.theme.HabiticaTheme
|
||||
import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
|
||||
import com.habitrpg.common.habitica.extensions.getThemeColor
|
||||
import com.habitrpg.common.habitica.extensions.nameRes
|
||||
|
|
@ -79,10 +80,10 @@ private fun TaskDifficultySelection(
|
|||
val selectedState = updateTransition(selected)
|
||||
val context = LocalContext.current
|
||||
val iconColor = selectedState.animateColor {
|
||||
if (it) Color(context.getThemeColor(R.attr.colorTintedBackground)) else MaterialTheme.colors.primary
|
||||
if (it) HabiticaTheme.colors.tintedUiDetails else Color(context.getThemeColor(R.attr.textColorTintedSecondary))
|
||||
}
|
||||
val textColor = selectedState.animateColor {
|
||||
if (it) MaterialTheme.colors.primary else Color(context.getThemeColor(R.attr.textColorTintedSecondary))
|
||||
if (it) Color(context.getThemeColor(R.attr.textColorTintedPrimary)) else Color(context.getThemeColor(R.attr.textColorTintedSecondary))
|
||||
}
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(6.dp), modifier = modifier) {
|
||||
Box(
|
||||
|
|
@ -104,7 +105,7 @@ private fun TaskDifficultySelection(
|
|||
Box(
|
||||
Modifier
|
||||
.size(57.dp)
|
||||
.background(MaterialTheme.colors.primary, MaterialTheme.shapes.medium)
|
||||
.background(HabiticaTheme.colors.tintedUiMain, MaterialTheme.shapes.medium)
|
||||
)
|
||||
}
|
||||
Image(icon, null, colorFilter = ColorFilter.tint(iconColor.value))
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import androidx.compose.ui.unit.Dp
|
|||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.habitrpg.android.habitica.R
|
||||
import com.habitrpg.android.habitica.ui.theme.HabiticaTheme
|
||||
import com.habitrpg.common.habitica.extensions.getThemeColor
|
||||
|
||||
data class LabeledValue<V>(val label: String, val value: V)
|
||||
|
|
@ -76,8 +77,9 @@ private fun <V> TaskFormSelection(
|
|||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val selectedState = updateTransition(selected)
|
||||
val context = LocalContext.current
|
||||
val textColor = selectedState.animateColor {
|
||||
if (it) Color(LocalContext.current.getThemeColor(R.attr.colorTintedBackground)) else MaterialTheme.colors.primary
|
||||
if (it) HabiticaTheme.colors.tintedUiDetails else Color(context.getThemeColor(R.attr.textColorTintedSecondary))
|
||||
}
|
||||
Box(
|
||||
contentAlignment = Alignment.Center, modifier = modifier
|
||||
|
|
@ -97,7 +99,7 @@ private fun <V> TaskFormSelection(
|
|||
) {
|
||||
Box(
|
||||
Modifier
|
||||
.background(MaterialTheme.colors.primary, MaterialTheme.shapes.medium)
|
||||
.background(HabiticaTheme.colors.tintedUiMain, MaterialTheme.shapes.medium)
|
||||
.matchParentSize()
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -287,11 +287,10 @@ class TaskSchedulingControls @JvmOverloads constructor(
|
|||
button.tag = weekdayCode
|
||||
if (isActive) {
|
||||
button.background = ContextCompat.getDrawable(context, R.drawable.habit_scoring_circle_selected)
|
||||
button.background.mutate().setTint(tintColor)
|
||||
button.setTextColor(context.getThemeColor(R.attr.colorTintedBackground))
|
||||
button.setTextColor(context.getThemeColor(R.attr.tintedUiDetails))
|
||||
} else {
|
||||
button.background = ContextCompat.getDrawable(context, R.drawable.habit_scoring_circle)
|
||||
button.setTextColor(context.getThemeColor(R.attr.colorPrimaryDark))
|
||||
button.setTextColor(context.getThemeColor(R.attr.textColorTintedSecondary))
|
||||
}
|
||||
button.setOnClickListener {
|
||||
setWeekdayActive(weekdayCode, !isActive)
|
||||
|
|
|
|||
|
|
@ -47,7 +47,8 @@ abstract class TaskListFactory internal constructor(
|
|||
return
|
||||
}
|
||||
CoroutineScope(Dispatchers.Main + job).launch(ExceptionHandler.coroutine()) {
|
||||
val tasks = taskRepository.getTasks(taskType, null, emptyArray()).firstOrNull()?.filter { task ->
|
||||
val mirroredTasks = userRepository.getUser().firstOrNull()?.preferences?.tasks?.mirrorGroupTasks?.toTypedArray()
|
||||
val tasks = taskRepository.getTasks(taskType, null, mirroredTasks ?: emptyArray()).firstOrNull()?.filter { task ->
|
||||
task.type == TaskType.TODO && !task.completed || task.isDisplayedActive
|
||||
} ?: return@launch
|
||||
taskList = taskRepository.getTaskCopies(tasks)
|
||||
|
|
|
|||
|
|
@ -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<TaskList>()
|
||||
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<TaskScoringResult>()
|
||||
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<TaskScoringResult>()
|
||||
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<TaskScoringResult>()
|
||||
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<TaskScoringResult>()
|
||||
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<TaskScoringResult>()
|
||||
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<TaskScoringResult>()
|
||||
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<TaskScoringResult>()
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ buildscript {
|
|||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.3.1'
|
||||
classpath 'com.android.tools.build:gradle:7.4.0'
|
||||
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
|
||||
classpath 'com.google.gms:google-services:4.3.14'
|
||||
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.2'
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
@ -158,6 +150,8 @@ object DataBindingUtils {
|
|||
tempMap["quest_solarSystem"] = "gif"
|
||||
tempMap["quest_virtualpet"] = "gif"
|
||||
tempMap["Pet_HatchingPotion_VirtualPet"] = "gif"
|
||||
tempMap["Pet-Gryphatrice-Jubilant"] = "gif"
|
||||
tempMap["stable_Pet-Gryphatrice-Jubilant"] = "gif"
|
||||
FILEFORMAT_MAP = Collections.unmodifiableMap(tempMap)
|
||||
|
||||
val tempNameMap = HashMap<String, String>()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import com.habitrpg.common.habitica.models.notifications.GroupTaskApprovedData
|
|||
import com.habitrpg.common.habitica.models.notifications.GroupTaskNeedsWorkData
|
||||
import com.habitrpg.common.habitica.models.notifications.GroupTaskRequiresApprovalData
|
||||
import com.habitrpg.common.habitica.models.notifications.GuildInvitationData
|
||||
import com.habitrpg.common.habitica.models.notifications.ItemReceivedData
|
||||
import com.habitrpg.common.habitica.models.notifications.LoginIncentiveData
|
||||
import com.habitrpg.common.habitica.models.notifications.NewChatMessageData
|
||||
import com.habitrpg.common.habitica.models.notifications.NewStuffData
|
||||
|
|
@ -27,6 +28,7 @@ class Notification {
|
|||
GROUP_TASK_REQUIRES_APPROVAL("GROUP_TASK_REQUIRES_APPROVAL"),
|
||||
UNALLOCATED_STATS_POINTS("UNALLOCATED_STATS_POINTS"),
|
||||
WON_CHALLENGE("WON_CHALLENGE"),
|
||||
ITEM_RECEIVED("ITEM_RECEIVED"),
|
||||
|
||||
// Achievements
|
||||
ACHIEVEMENT_PARTY_UP("ACHIEVEMENT_PARTY_UP"),
|
||||
|
|
@ -93,6 +95,7 @@ class Notification {
|
|||
Type.FIRST_DROP.type -> FirstDropData::class.java
|
||||
Type.ACHIEVEMENT_GENERIC.type -> AchievementData::class.java
|
||||
Type.WON_CHALLENGE.type -> ChallengeWonData::class.java
|
||||
Type.ITEM_RECEIVED.type -> ItemReceivedData::class.java
|
||||
|
||||
Type.ACHIEVEMENT_ALL_YOUR_BASE.type -> AchievementData::class.java
|
||||
Type.ACHIEVEMENT_BACK_TO_BASICS.type -> AchievementData::class.java
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
package com.habitrpg.common.habitica.models.notifications
|
||||
|
||||
open class ItemReceivedData: NotificationData {
|
||||
var title: String? = null
|
||||
var text: String? = null
|
||||
var icon: String? = null
|
||||
var destination: String? = null
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue