diff --git a/Habitica/AndroidManifest.xml b/Habitica/AndroidManifest.xml
index d453c944a..3cce445e3 100644
--- a/Habitica/AndroidManifest.xml
+++ b/Habitica/AndroidManifest.xml
@@ -30,6 +30,9 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Habitica/res/drawable-hdpi/armoire_background.png b/Habitica/res/drawable-hdpi/armoire_background.png
new file mode 100644
index 000000000..786e741c9
Binary files /dev/null and b/Habitica/res/drawable-hdpi/armoire_background.png differ
diff --git a/Habitica/res/drawable-hdpi/armoire_circle.png b/Habitica/res/drawable-hdpi/armoire_circle.png
new file mode 100644
index 000000000..1cd176a08
Binary files /dev/null and b/Habitica/res/drawable-hdpi/armoire_circle.png differ
diff --git a/Habitica/res/drawable-hdpi/armoire_experience.png b/Habitica/res/drawable-hdpi/armoire_experience.png
new file mode 100644
index 000000000..0cbc6046b
Binary files /dev/null and b/Habitica/res/drawable-hdpi/armoire_experience.png differ
diff --git a/Habitica/res/drawable-night/armoire_gold_background.xml b/Habitica/res/drawable-night/armoire_gold_background.xml
new file mode 100644
index 000000000..1f8085a95
--- /dev/null
+++ b/Habitica/res/drawable-night/armoire_gold_background.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Habitica/res/drawable-xhdpi/armoire_background.png b/Habitica/res/drawable-xhdpi/armoire_background.png
new file mode 100644
index 000000000..f8b5f68a7
Binary files /dev/null and b/Habitica/res/drawable-xhdpi/armoire_background.png differ
diff --git a/Habitica/res/drawable-xhdpi/armoire_circle.png b/Habitica/res/drawable-xhdpi/armoire_circle.png
new file mode 100644
index 000000000..4311171b5
Binary files /dev/null and b/Habitica/res/drawable-xhdpi/armoire_circle.png differ
diff --git a/Habitica/res/drawable-xhdpi/armoire_experience.png b/Habitica/res/drawable-xhdpi/armoire_experience.png
new file mode 100644
index 000000000..1cc7e208a
Binary files /dev/null and b/Habitica/res/drawable-xhdpi/armoire_experience.png differ
diff --git a/Habitica/res/drawable-xxhdpi/armoire_background.png b/Habitica/res/drawable-xxhdpi/armoire_background.png
new file mode 100644
index 000000000..db4955ec8
Binary files /dev/null and b/Habitica/res/drawable-xxhdpi/armoire_background.png differ
diff --git a/Habitica/res/drawable-xxhdpi/armoire_circle.png b/Habitica/res/drawable-xxhdpi/armoire_circle.png
new file mode 100644
index 000000000..c54039f9f
Binary files /dev/null and b/Habitica/res/drawable-xxhdpi/armoire_circle.png differ
diff --git a/Habitica/res/drawable-xxhdpi/armoire_experience.png b/Habitica/res/drawable-xxhdpi/armoire_experience.png
new file mode 100644
index 000000000..5f505e3ac
Binary files /dev/null and b/Habitica/res/drawable-xxhdpi/armoire_experience.png differ
diff --git a/Habitica/res/drawable-xxxhdpi/armoire_background.png b/Habitica/res/drawable-xxxhdpi/armoire_background.png
new file mode 100644
index 000000000..d7db87396
Binary files /dev/null and b/Habitica/res/drawable-xxxhdpi/armoire_background.png differ
diff --git a/Habitica/res/drawable-xxxhdpi/armoire_circle.png b/Habitica/res/drawable-xxxhdpi/armoire_circle.png
new file mode 100644
index 000000000..79ee00ecd
Binary files /dev/null and b/Habitica/res/drawable-xxxhdpi/armoire_circle.png differ
diff --git a/Habitica/res/drawable-xxxhdpi/armoire_experience.png b/Habitica/res/drawable-xxxhdpi/armoire_experience.png
new file mode 100644
index 000000000..9346537b4
Binary files /dev/null and b/Habitica/res/drawable-xxxhdpi/armoire_experience.png differ
diff --git a/Habitica/res/drawable/ad_button_background.xml b/Habitica/res/drawable/ad_button_background.xml
new file mode 100644
index 000000000..87c8b59d1
--- /dev/null
+++ b/Habitica/res/drawable/ad_button_background.xml
@@ -0,0 +1,27 @@
+
+
+ -
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
diff --git a/Habitica/res/drawable/ad_button_background_disabled.xml b/Habitica/res/drawable/ad_button_background_disabled.xml
new file mode 100644
index 000000000..144721661
--- /dev/null
+++ b/Habitica/res/drawable/ad_button_background_disabled.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Habitica/res/drawable/armoire_gold_background.xml b/Habitica/res/drawable/armoire_gold_background.xml
new file mode 100644
index 000000000..5db42576d
--- /dev/null
+++ b/Habitica/res/drawable/armoire_gold_background.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Habitica/res/drawable/border_1f000000.xml b/Habitica/res/drawable/border_1f000000.xml
index b2336280b..7329bc223 100644
--- a/Habitica/res/drawable/border_1f000000.xml
+++ b/Habitica/res/drawable/border_1f000000.xml
@@ -1,10 +1,8 @@
-
-
diff --git a/Habitica/res/drawable/bottom_sheet_background.xml b/Habitica/res/drawable/bottom_sheet_background.xml
new file mode 100644
index 000000000..811619d0d
--- /dev/null
+++ b/Habitica/res/drawable/bottom_sheet_background.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/Habitica/res/drawable/bottom_sheet_title.xml b/Habitica/res/drawable/bottom_sheet_title.xml
index d33c4bc4c..73543f65b 100644
--- a/Habitica/res/drawable/bottom_sheet_title.xml
+++ b/Habitica/res/drawable/bottom_sheet_title.xml
@@ -2,4 +2,5 @@
+
diff --git a/Habitica/res/drawable/pill_bg_radio_selected.xml b/Habitica/res/drawable/pill_bg_radio_selected.xml
index 7160d31ed..88587cc4e 100644
--- a/Habitica/res/drawable/pill_bg_radio_selected.xml
+++ b/Habitica/res/drawable/pill_bg_radio_selected.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/Habitica/res/drawable/pill_bg_radio_unselected.xml b/Habitica/res/drawable/pill_bg_radio_unselected.xml
index 3c7f7ed72..e09990c80 100644
--- a/Habitica/res/drawable/pill_bg_radio_unselected.xml
+++ b/Habitica/res/drawable/pill_bg_radio_unselected.xml
@@ -1,7 +1,5 @@
-
-
-
-
+
+
\ No newline at end of file
diff --git a/Habitica/res/drawable/pill_bg_yellow_500.xml b/Habitica/res/drawable/pill_bg_yellow_500.xml
new file mode 100644
index 000000000..e528d884d
--- /dev/null
+++ b/Habitica/res/drawable/pill_bg_yellow_500.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Habitica/res/drawable/radio_button_text_color.xml b/Habitica/res/drawable/radio_button_text_color.xml
index 8c1cf7168..5ef737485 100644
--- a/Habitica/res/drawable/radio_button_text_color.xml
+++ b/Habitica/res/drawable/radio_button_text_color.xml
@@ -1,5 +1,5 @@
-
+
\ No newline at end of file
diff --git a/Habitica/res/layout/activity_armoire.xml b/Habitica/res/layout/activity_armoire.xml
new file mode 100644
index 000000000..43eacc7bc
--- /dev/null
+++ b/Habitica/res/layout/activity_armoire.xml
@@ -0,0 +1,129 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Habitica/res/layout/activity_main.xml b/Habitica/res/layout/activity_main.xml
index 99dd8f028..4cc80650d 100644
--- a/Habitica/res/layout/activity_main.xml
+++ b/Habitica/res/layout/activity_main.xml
@@ -74,7 +74,7 @@
android:layout_toStartOf="@id/toolbar_accessory_container"
android:layout_alignParentStart="true"
style="@style/TextAppearance.AppCompat.Widget.ActionBar.Title"
- tools:text="Habitica"/>
+ tools:text="Habitica" />
-
+
+ app:drawableStartCompat="@drawable/ic_warning_black"
+ app:drawableTint="@color/maroon_500"
+ android:drawablePadding="4dp"/>
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Habitica/res/layout/armoire_drop_rate_dialog.xml b/Habitica/res/layout/armoire_drop_rate_dialog.xml
new file mode 100644
index 000000000..9c6fc6b01
--- /dev/null
+++ b/Habitica/res/layout/armoire_drop_rate_dialog.xml
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Habitica/res/layout/bottom_sheet_backgrounds_filter.xml b/Habitica/res/layout/bottom_sheet_backgrounds_filter.xml
new file mode 100644
index 000000000..b33f4e8f9
--- /dev/null
+++ b/Habitica/res/layout/bottom_sheet_backgrounds_filter.xml
@@ -0,0 +1,203 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Habitica/res/layout/bottom_sheet_wrapper.xml b/Habitica/res/layout/bottom_sheet_wrapper.xml
new file mode 100644
index 000000000..3f9dbdca8
--- /dev/null
+++ b/Habitica/res/layout/bottom_sheet_wrapper.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/Habitica/res/layout/dialog_challenge_filter.xml b/Habitica/res/layout/dialog_challenge_filter.xml
index 0bdd075aa..c84ec77d9 100644
--- a/Habitica/res/layout/dialog_challenge_filter.xml
+++ b/Habitica/res/layout/dialog_challenge_filter.xml
@@ -1,28 +1,41 @@
-
-
-
-
+ xmlns:android="http://schemas.android.com/apk/res/android">
- android:orientation="vertical">
-
+
+
+
+
+ android:paddingStart="8dp"
+ android:paddingEnd="0dp"/>
+ android:paddingStart="8dp"
+ android:paddingEnd="0dp"/>
-
\ No newline at end of file
diff --git a/Habitica/res/layout/dialog_task_filter.xml b/Habitica/res/layout/dialog_task_filter.xml
index 3a191934e..87f31eb75 100644
--- a/Habitica/res/layout/dialog_task_filter.xml
+++ b/Habitica/res/layout/dialog_task_filter.xml
@@ -4,7 +4,38 @@
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:layout_marginBottom="16dp">
+ android:layout_marginBottom="16dp"
+ android:paddingStart="@dimen/bottom_sheet_inset"
+ android:paddingEnd="@dimen/bottom_sheet_inset">
+
+
+
+
+
+
-
+ android:id="@+id/gear_container"
+ android:gravity="center_vertical">
+ android:layout_height="wrap_content">
+ android:clickable="false">
+ android:layout_marginEnd="8dp"/>
+
+
diff --git a/Habitica/res/navigation/navigation.xml b/Habitica/res/navigation/navigation.xml
index 95648dfb3..f91e9c017 100644
--- a/Habitica/res/navigation/navigation.xml
+++ b/Habitica/res/navigation/navigation.xml
@@ -35,6 +35,11 @@
app:argType="string"
app:nullable="true"
android:defaultValue=""/>
+
@@ -142,6 +147,20 @@
android:id="@+id/openMountDetail"
app:destination="@id/mountDetailRecyclerFragment" />
+
+
+
+
+
+
-
-
-
\ No newline at end of file
diff --git a/Habitica/res/values/attrs.xml b/Habitica/res/values/attrs.xml
index 07b0dcc1c..4a25555fe 100644
--- a/Habitica/res/values/attrs.xml
+++ b/Habitica/res/values/attrs.xml
@@ -27,6 +27,8 @@
+
+
@@ -77,7 +79,7 @@
-
+
@@ -153,4 +155,8 @@
+
+
+
+
diff --git a/Habitica/res/values/colors.xml b/Habitica/res/values/colors.xml
index 35368c0c5..ce192a4c0 100644
--- a/Habitica/res/values/colors.xml
+++ b/Habitica/res/values/colors.xml
@@ -212,4 +212,5 @@
@color/brand_300
@color/brand_700
@color/white
+ @color/maroon_5
diff --git a/Habitica/res/values/dimens.xml b/Habitica/res/values/dimens.xml
index 2391873b4..894f505c0 100644
--- a/Habitica/res/values/dimens.xml
+++ b/Habitica/res/values/dimens.xml
@@ -118,4 +118,6 @@
5dp
4dp
10dp
+ 20dp
+ 16dp
diff --git a/Habitica/res/values/strings.xml b/Habitica/res/values/strings.xml
index 6f5505cf5..eda78cf4e 100644
--- a/Habitica/res/values/strings.xml
+++ b/Habitica/res/values/strings.xml
@@ -276,8 +276,8 @@
Mounts
You\'ve found %d quest items
- You found %s in the Armoire
- You rummage in the Armoire and find%1$s %2$s. What\'s that doing in here?
+ You find a piece of rare Equipment in the Armoire!
+ You rummage in the Armoire and find food. What\'s that doing in here?
You wrestle with the Armoire and gain Experience. Take that!
Sell (%d Gold)
Hatch with potion
@@ -1230,4 +1230,39 @@
Compact
Minimal
Are you sure you want to delete?
+ Armoire drop rates
+ Armoire
+ Equipment Remaining: %d
+ New pieces added every month
+ Watch Ad
+ Available in %s
+ Watch ad to open again
+ Watch Ad to revive
+ Enchanted Armoire Drop Rates
+ New Equipment pieces are added every month. If you own all pieces of equipment, then you\'ll get Food or Ezperience, 50/50 odds.
+ 60% Piece of Equipment
+ 20% Piece of Food
+ During special events, normal food items will change to their cake or candy counterparts.
+ 20% Experience points
+ The amount gained varies randomly from 10 to 50
+ Day Start Adjustment
+ Purchased
+ Show Me
+ Background Filters
+ Newest
+ Oldest
+ Sort By
+ January
+ Febuary
+ March
+ April
+ May
+ June
+ July
+ August
+ September
+ October
+ November
+ December
+ Adjust when your day switches over past the default time of midnight.
diff --git a/Habitica/res/values/styles.xml b/Habitica/res/values/styles.xml
index ac1e85465..1a29f13e8 100644
--- a/Habitica/res/values/styles.xml
+++ b/Habitica/res/values/styles.xml
@@ -714,6 +714,11 @@
- @color/yellow_100
+
+
+
+
+
+
+
+
+
12dp
\ No newline at end of file
diff --git a/Habitica/res/values/values.xml b/Habitica/res/values/values.xml
index 785d47092..9bd3ec3a7 100644
--- a/Habitica/res/values/values.xml
+++ b/Habitica/res/values/values.xml
@@ -28,6 +28,39 @@
- 1
+
+ - Default (12:00 AM)
+ - +1 Hour (01:00 AM)
+ - +2 Hours (02:00 AM)
+ - +3 Hours (03:00 AM)
+ - +4 Hours (04:00 AM)
+ - +5 Hours (05:00 AM)
+ - +6 Hours (06:00 AM)
+ - +7 Hours (07:00 AM)
+ - +8 Hours (08:00 AM)
+ - +9 Hours (09:00 AM)
+ - +10 Hours (10:00 AM)
+ - +11 Hours (11:00 AM)
+ - +12 Hours (12:00 PM)
+
+
+
+
+ - 0
+ - 1
+ - 2
+ - 3
+ - 4
+ - 5
+ - 6
+ - 7
+ - 8
+ - 9
+ - 10
+ - 11
+ - 12
+
+
- @string/avatar_size_slim
- @string/avatar_size_broad
diff --git a/Habitica/res/xml/preferences_fragment.xml b/Habitica/res/xml/preferences_fragment.xml
index 60fdd8086..dd811f5ae 100644
--- a/Habitica/res/xml/preferences_fragment.xml
+++ b/Habitica/res/xml/preferences_fragment.xml
@@ -182,6 +182,13 @@
android:layout="@layout/preference_child_summary"
android:summary="@string/pref_first_day_of_the_week_summary"
android:title="@string/pref_first_day_of_the_week_title" />
+
+
-
-
-
-
-
-
diff --git a/Habitica/res/xml/remote_config_defaults.xml b/Habitica/res/xml/remote_config_defaults.xml
index 920e9c679..1509d0db7 100644
--- a/Habitica/res/xml/remote_config_defaults.xml
+++ b/Habitica/res/xml/remote_config_defaults.xml
@@ -104,5 +104,26 @@
enableTeamBoards
false
+
+ hideFacebook
+ false
+
+
+
+ enableNewArmoire
+ true
+
+
+ enableArmoireAds
+ true
+
+
+ enableFaintAds
+ false
+
+
+ enableSpellAds
+ false
+
\ No newline at end of file
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/HabiticaBaseApplication.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/HabiticaBaseApplication.kt
index 6df86c481..ad02a63e7 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/HabiticaBaseApplication.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/HabiticaBaseApplication.kt
@@ -34,6 +34,7 @@ import com.google.firebase.remoteconfig.FirebaseRemoteConfigSettings
import com.habitrpg.android.habitica.components.AppComponent
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.ApiClient
+import com.habitrpg.android.habitica.helpers.AdHandler
import com.habitrpg.android.habitica.helpers.LanguageHelper
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.helpers.notifications.PushNotificationManager
@@ -73,6 +74,7 @@ abstract class HabiticaBaseApplication : Application(), Application.ActivityLife
setLocale()
setupRemoteConfig()
setupNotifications()
+ setupAdHandler()
HabiticaIconsHelper.init(this)
MarkdownParser.setup(this)
@@ -110,6 +112,10 @@ abstract class HabiticaBaseApplication : Application(), Application.ActivityLife
checkIfNewVersion()
}
+ private fun setupAdHandler() {
+ AdHandler.setup(sharedPrefs, analyticsManager)
+ }
+
private fun setLocale() {
val resources = resources
val configuration: Configuration = resources.configuration
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/components/UserComponent.java b/Habitica/src/main/java/com/habitrpg/android/habitica/components/UserComponent.java
index 2d6351f7b..fa88402d2 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/components/UserComponent.java
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/components/UserComponent.java
@@ -13,6 +13,7 @@ import com.habitrpg.android.habitica.receivers.NotificationPublisher;
import com.habitrpg.android.habitica.receivers.TaskAlarmBootReceiver;
import com.habitrpg.android.habitica.receivers.TaskReceiver;
import com.habitrpg.android.habitica.ui.activities.AdventureGuideActivity;
+import com.habitrpg.android.habitica.ui.activities.ArmoireActivity;
import com.habitrpg.android.habitica.ui.activities.ChallengeFormActivity;
import com.habitrpg.android.habitica.ui.activities.ClassSelectionActivity;
import com.habitrpg.android.habitica.ui.activities.FixCharacterValuesActivity;
@@ -98,13 +99,13 @@ import com.habitrpg.android.habitica.ui.fragments.support.FAQOverviewFragment;
import com.habitrpg.android.habitica.ui.fragments.support.SupportMainFragment;
import com.habitrpg.android.habitica.ui.fragments.tasks.TaskRecyclerViewFragment;
import com.habitrpg.android.habitica.ui.fragments.tasks.TasksFragment;
-import com.habitrpg.android.habitica.ui.fragments.tasks.TeamBoardFragment;
import com.habitrpg.android.habitica.ui.viewmodels.AuthenticationViewModel;
import com.habitrpg.android.habitica.ui.viewmodels.GroupViewModel;
import com.habitrpg.android.habitica.ui.viewmodels.InboxViewModel;
import com.habitrpg.android.habitica.ui.viewmodels.MainActivityViewModel;
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel;
import com.habitrpg.android.habitica.ui.viewmodels.NotificationsViewModel;
+import com.habitrpg.android.habitica.ui.viewmodels.TasksViewModel;
import com.habitrpg.android.habitica.ui.viewmodels.inventory.equipment.EquipmentOverviewViewModel;
import com.habitrpg.android.habitica.ui.views.dialogs.PetSuggestHatchDialog;
import com.habitrpg.android.habitica.ui.views.insufficientCurrency.InsufficientGemsDialog;
@@ -337,8 +338,6 @@ public interface UserComponent {
void inject(PromoInfoFragment promoInfoFragment);
- void inject(@NotNull TeamBoardFragment teamBoardFragment);
-
void inject(@NotNull GuildOverviewFragment guildOverviewFragment);
void inject(@NotNull PromoWebFragment promoWebFragment);
@@ -358,4 +357,8 @@ public interface UserComponent {
void inject(@NotNull MainUserViewModel mainUserViewModel);
void inject(@NotNull PetSuggestHatchDialog petSuggestHatchDialog);
+
+ void inject(@NotNull ArmoireActivity armoireActivity);
+
+ void inject(@NotNull TasksViewModel tasksViewModel);
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ApiClientImpl.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ApiClientImpl.kt
index 42b9ce697..a6db4b107 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ApiClientImpl.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ApiClientImpl.kt
@@ -58,13 +58,6 @@ import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.core.FlowableTransformer
import io.reactivex.rxjava3.functions.Consumer
import io.reactivex.rxjava3.schedulers.Schedulers
-import java.io.IOException
-import java.net.SocketException
-import java.net.SocketTimeoutException
-import java.net.UnknownHostException
-import java.util.GregorianCalendar
-import java.util.concurrent.TimeUnit
-import javax.net.ssl.SSLException
import okhttp3.Cache
import okhttp3.OkHttpClient
import okhttp3.Request
@@ -73,6 +66,13 @@ import retrofit2.HttpException
import retrofit2.Retrofit
import retrofit2.adapter.rxjava3.RxJava3CallAdapterFactory
import retrofit2.converter.gson.GsonConverterFactory
+import java.io.IOException
+import java.net.SocketException
+import java.net.SocketTimeoutException
+import java.net.UnknownHostException
+import java.util.GregorianCalendar
+import java.util.concurrent.TimeUnit
+import javax.net.ssl.SSLException
class ApiClientImpl(
private val gsonConverter: GsonConverterFactory,
@@ -94,6 +94,9 @@ class ApiClientImpl(
habitResponse.notifications?.let {
notificationsManager.setNotifications(it)
}
+ if (hadError) {
+ hideConnectionProblemDialog()
+ }
habitResponse.data
}
.subscribeOn(Schedulers.io())
@@ -102,6 +105,7 @@ class ApiClientImpl(
}
private var languageCode: String? = null
private var lastAPICallURL: String? = null
+ private var hadError = false
init {
HabiticaBaseApplication.userComponent?.inject(this)
@@ -306,12 +310,21 @@ class ApiClientImpl(
resourceTitleString: String?,
resourceMessageString: String
) {
+ hadError = true
val application = (context as? HabiticaBaseApplication)
?: (context.applicationContext as? HabiticaBaseApplication)
application?.currentActivity?.get()
?.showConnectionProblem(resourceTitleString, resourceMessageString)
}
+ private fun hideConnectionProblemDialog() {
+ hadError = false
+ val application = (context as? HabiticaBaseApplication)
+ ?: (context.applicationContext as? HabiticaBaseApplication)
+ application?.currentActivity?.get()
+ ?.hideConnectionProblem()
+ }
+
/*
This function is used with Observer.compose to reuse transformers across the application.
See here for more info: http://blog.danlew.net/2015/03/02/dont-break-the-chain/
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/extensions/Date-Extensions.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/extensions/Date-Extensions.kt
index 7582f866a..db8cbe6df 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/extensions/Date-Extensions.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/extensions/Date-Extensions.kt
@@ -4,16 +4,12 @@ import android.content.res.Resources
import com.habitrpg.android.habitica.R
import java.util.Calendar
import java.util.Date
-import kotlin.math.round
import kotlin.time.Duration
import kotlin.time.DurationUnit
-import kotlin.time.ExperimentalTime
-import kotlin.time.milliseconds
+import kotlin.time.toDuration
class DateUtils {
-
companion object {
-
fun createDate(year: Int, month: Int, day: Int): Date {
val cal = Calendar.getInstance()
cal.set(Calendar.YEAR, year)
@@ -33,11 +29,11 @@ fun Date.getAgoString(res: Resources): String {
}
fun Long.getAgoString(res: Resources): String {
- val diff = Date().time - this
+ val diff = (Date().time - this).toDuration(DurationUnit.MILLISECONDS)
- val diffMinutes = diff / (60 * 1000) % 60
- val diffHours = diff / (60 * 60 * 1000) % 24
- val diffDays = diff / (24 * 60 * 60 * 1000)
+ val diffMinutes = diff.inWholeMinutes
+ val diffHours = diff.inWholeHours
+ val diffDays = diff.inWholeDays
val diffWeeks = diffDays / 7
val diffMonths = diffDays / 30
@@ -63,30 +59,29 @@ fun Date.getRemainingString(res: Resources): String {
return this.time.getRemainingString(res)
}
-@OptIn(ExperimentalTime::class)
fun Long.getRemainingString(res: Resources): String {
- val diff = (this - Date().time).milliseconds
+ val diff = (this - Date().time).toDuration(DurationUnit.MILLISECONDS)
- val diffMinutes = diff.inMinutes
- val diffHours = diff.inHours
- val diffDays = diff.inDays
- val diffWeeks = diffDays / 7f
- val diffMonths = diffDays / 30f
+ val diffMinutes = diff.inWholeMinutes
+ val diffHours = diff.inWholeHours
+ val diffDays = diff.inWholeDays
+ val diffWeeks = diffDays / 7
+ val diffMonths = diffDays / 30
return when {
- diffMonths != 0.0 -> if (round(diffMonths) == 1.0) {
+ diffMonths != 0L -> if (diffMonths == 1L) {
res.getString(R.string.remaining_1month)
- } else res.getString(R.string.remaining_months, round(diffMonths).toInt())
- diffWeeks != 0.0 -> if (round(diffWeeks) == 1.0) {
+ } else res.getString(R.string.remaining_months, diffMonths)
+ diffWeeks != 0L -> if (diffWeeks == 1L) {
res.getString(R.string.remaining_1week)
- } else res.getString(R.string.remaining_weeks, round(diffWeeks).toInt())
- diffDays != 0.0 -> if (diffDays == 1.0) {
+ } else res.getString(R.string.remaining_weeks, diffWeeks)
+ diffDays != 0L -> if (diffDays == 1L) {
res.getString(R.string.remaining_1day)
} else res.getString(R.string.remaining_days, diffDays)
- diffHours != 0.0 -> if (diffHours == 1.0) {
+ diffHours != 0L -> if (diffHours == 1L) {
res.getString(R.string.remaining_1hour)
} else res.getString(R.string.remaining_hours, diffHours)
- diffMinutes == 1.0 -> res.getString(R.string.remaining_1Minute)
+ diffMinutes == 1L -> res.getString(R.string.remaining_1Minute)
else -> res.getString(R.string.remaining_minutes, diffMinutes)
}
}
@@ -95,16 +90,15 @@ fun Date.getShortRemainingString(): String {
return time.getShortRemainingString()
}
-@OptIn(ExperimentalTime::class)
fun Long.getShortRemainingString(): String {
- var diff = Duration.milliseconds((this - Date().time))
+ var diff = (this - Date().time).toDuration(DurationUnit.MILLISECONDS)
val diffDays = diff.toInt(DurationUnit.DAYS)
- diff -= Duration.days(diffDays)
+ diff -= diffDays.toDuration(DurationUnit.DAYS)
val diffHours = diff.toInt(DurationUnit.HOURS)
- diff -= Duration.hours(diffHours)
+ diff -= diffDays.toDuration(DurationUnit.HOURS)
val diffMinutes = diff.toInt(DurationUnit.MINUTES)
- diff -= Duration.minutes(diffMinutes)
+ diff -= diffMinutes.toDuration(DurationUnit.MINUTES)
val diffSeconds = diff.toInt(DurationUnit.SECONDS)
var str = "${diffMinutes}m"
@@ -119,3 +113,7 @@ fun Long.getShortRemainingString(): String {
}
return str
}
+
+fun Duration.getMinuteOrSeconds(): DurationUnit {
+ return if (this.inWholeHours < 1) DurationUnit.SECONDS else DurationUnit.MINUTES
+}
\ No newline at end of file
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/AdHandler.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/AdHandler.kt
new file mode 100644
index 000000000..67f9b7671
--- /dev/null
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/AdHandler.kt
@@ -0,0 +1,224 @@
+package com.habitrpg.android.habitica.helpers
+
+import android.app.Activity
+import android.content.Context
+import android.content.SharedPreferences
+import android.provider.Settings
+import android.util.Log
+import androidx.core.content.edit
+import androidx.core.os.bundleOf
+import com.google.android.gms.ads.AdRequest
+import com.google.android.gms.ads.FullScreenContentCallback
+import com.google.android.gms.ads.LoadAdError
+import com.google.android.gms.ads.MobileAds
+import com.google.android.gms.ads.OnUserEarnedRewardListener
+import com.google.android.gms.ads.RequestConfiguration
+import com.google.android.gms.ads.rewarded.RewardItem
+import com.google.android.gms.ads.rewarded.RewardedAd
+import com.google.android.gms.ads.rewarded.RewardedAdLoadCallback
+import com.google.firebase.analytics.FirebaseAnalytics
+import com.google.firebase.crashlytics.FirebaseCrashlytics
+import com.habitrpg.android.habitica.BuildConfig
+import com.habitrpg.android.habitica.proxy.AnalyticsManager
+import java.io.UnsupportedEncodingException
+import java.security.MessageDigest
+import java.util.Date
+import kotlin.time.DurationUnit
+import kotlin.time.toDuration
+
+enum class AdType {
+ ARMOIRE,
+ SPELL,
+ FAINT;
+
+ val adUnitID: String
+ get() {
+ if (BuildConfig.DEBUG || BuildConfig.TESTING_LEVEL == "staff" || BuildConfig.TESTING_LEVEL == "alpha") {
+ return "ca-app-pub-3940256099942544/5224354917"
+ }
+ return when (this) {
+ ARMOIRE -> "ca-app-pub-5911973472413421/9392092486"
+ SPELL -> "ca-app-pub-5911973472413421/1738504765"
+ FAINT -> "ca-app-pub-5911973472413421/1738504765"
+ }
+ }
+}
+
+fun String.md5(): String? {
+ try {
+ val md = MessageDigest.getInstance("MD5")
+ val array = md.digest(this.toByteArray())
+ val sb = StringBuffer()
+ for (i in array.indices) {
+ sb.append(Integer.toHexString(array[i].toInt() and 0xFF or 0x100).substring(1, 3))
+ }
+ return sb.toString()
+ } catch (e: java.security.NoSuchAlgorithmException) {
+ } catch (ex: UnsupportedEncodingException) {
+ }
+ return null
+}
+
+class AdHandler(val activity: Activity, val type: AdType, val rewardAction: (Boolean) -> Unit): OnUserEarnedRewardListener {
+ private var rewardedAd: RewardedAd? = null
+
+ companion object {
+ private enum class AdStatus {
+ UNINITIALIZED,
+ INITIALIZING,
+ READY,
+ DISABLED
+ }
+
+ private lateinit var analyticsManager: AnalyticsManager
+ private lateinit var sharedPreferences: SharedPreferences
+ const val TAG = "AdHandler"
+
+ private var currentAdStatus = AdStatus.UNINITIALIZED
+
+ private var nextAdAllowed: MutableMap = mutableMapOf()
+
+ fun nextAdAllowedDate(type: AdType): Date? {
+ return nextAdAllowed[type]
+ }
+
+ fun isAllowed(type: AdType): Boolean {
+ return nextAdAllowedDate(type)?.after(Date()) == true
+ }
+
+ fun setNextAllowedDate(type: AdType, date: Date) {
+ nextAdAllowed[type] = date
+ sharedPreferences.edit {
+ putLong("nextAd${type.name}", date.time)
+ }
+ }
+
+ fun initialize(context: Context, onComplete: () -> Unit) {
+ if (currentAdStatus != AdStatus.UNINITIALIZED) return
+
+ if (BuildConfig.DEBUG || BuildConfig.TESTING_LEVEL == "staff" || BuildConfig.TESTING_LEVEL == "alpha") {
+ val android_id: String =
+ Settings.Secure.getString(context.contentResolver, Settings.Secure.ANDROID_ID)
+ val deviceId: String = android_id.md5()?.uppercase() ?: ""
+ val configuration = RequestConfiguration.Builder().setTestDeviceIds(listOf(deviceId)).build()
+ MobileAds.setRequestConfiguration(configuration)
+ }
+
+ currentAdStatus = AdStatus.INITIALIZING
+ MobileAds.initialize(context) {
+ currentAdStatus = AdStatus.READY
+ onComplete()
+ FirebaseCrashlytics.getInstance().recordException(Throwable("Ads Initialized"))
+ }
+ }
+
+ fun whenAdsInitialized(context: Context, onComplete: () -> Unit) {
+ when (currentAdStatus) {
+ AdStatus.READY -> {
+ onComplete()
+ }
+ AdStatus.DISABLED -> {
+ return
+ }
+ AdStatus.UNINITIALIZED -> {
+ initialize(context) {
+ onComplete()
+ }
+ }
+ AdStatus.INITIALIZING -> {
+ return
+ }
+ }
+ }
+
+ fun setup(sharedPrefs: SharedPreferences, analyticsManager: AnalyticsManager) {
+ this.sharedPreferences = sharedPrefs
+ this.analyticsManager = analyticsManager
+
+ for (type in AdType.values()) {
+ val time = sharedPrefs.getLong("nextAd${type.name}", 0)
+ if (time > 0) {
+ nextAdAllowed[type] = Date(time)
+ }
+ }
+ }
+ }
+
+ fun prepare(onComplete: ((Boolean) -> Unit)? = null) {
+ whenAdsInitialized(activity) {
+ val adRequest = AdRequest.Builder()
+ .build()
+
+ if (BuildConfig.DEBUG || BuildConfig.TESTING_LEVEL == "staff" || BuildConfig.TESTING_LEVEL == "alpha") {
+ if (!adRequest.isTestDevice(activity)) {
+ // users in this group need to be configured as Test device. better to fail if they aren't
+ // currentAdStatus = AdStatus.DISABLED
+ FirebaseCrashlytics.getInstance().recordException(Throwable("Device not test device"))
+ }
+ }
+
+ RewardedAd.load(activity, type.adUnitID, adRequest, object : RewardedAdLoadCallback() {
+ override fun onAdFailedToLoad(adError: LoadAdError) {
+ FirebaseCrashlytics.getInstance().recordException(Throwable(adError.message))
+ rewardAction(false)
+ onComplete?.invoke(false)
+ }
+
+ override fun onAdLoaded(rewardedAd: RewardedAd) {
+ this@AdHandler.rewardedAd = rewardedAd
+ configureReward()
+ onComplete?.invoke(true)
+ }
+ })
+ }
+ }
+
+ fun show() {
+ when (currentAdStatus) {
+ AdStatus.READY -> {
+ showRewardedAd()
+ }
+ AdStatus.DISABLED -> {
+ rewardAction(false)
+ return
+ }
+ AdStatus.UNINITIALIZED -> {
+ initialize(activity) {
+ showRewardedAd()
+ }
+ }
+ AdStatus.INITIALIZING -> {
+ return
+ }
+ }
+ }
+
+ private fun configureReward() {
+ rewardedAd?.run {
+ fullScreenContentCallback = object : FullScreenContentCallback() {
+ }
+ }
+ }
+
+ private fun showRewardedAd() {
+ if (nextAdAllowedDate(type)?.after(Date()) == true) {
+ return
+ }
+ if (rewardedAd != null) {
+ rewardedAd?.show(activity, this)
+ setNextAllowedDate(type, Date(Date().time + 1.toDuration(DurationUnit.HOURS).inWholeMilliseconds))
+ } else {
+ Log.d(TAG, "The rewarded ad wasn't ready yet.")
+ }
+ }
+
+ override fun onUserEarnedReward(rewardItem: RewardItem) {
+ analyticsManager.logEvent("adRewardEarned", bundleOf(
+ Pair("type", type.name)
+ ))
+ FirebaseAnalytics.getInstance(activity).logEvent("adRewardEarned", bundleOf(
+ Pair("type", type.name)
+ ))
+ rewardAction(true)
+ }
+}
\ No newline at end of file
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/AppConfigManager.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/AppConfigManager.kt
index bbf4a5b3c..afdb08fc2 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/AppConfigManager.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/AppConfigManager.kt
@@ -136,6 +136,29 @@ class AppConfigManager(contentRepository: ContentRepository?) {
}
fun enableTeamBoards(): Boolean {
+ if (BuildConfig.DEBUG) {
+ return true
+ }
return remoteConfig.getBoolean("enableTeamBoards")
}
+
+ fun enableArmoireAds(): Boolean {
+ return remoteConfig.getBoolean("enableArmoireAds")
+ }
+
+ fun enableFaintAds(): Boolean {
+ return remoteConfig.getBoolean("enableFaintAds")
+ }
+
+ fun enableSpellAds(): Boolean {
+ return remoteConfig.getBoolean("enableSpellAds")
+ }
+
+ fun enableNewArmoire(): Boolean {
+ return remoteConfig.getBoolean("enableNewArmoire")
+ }
+
+ fun hideFacebook(): Boolean {
+ return remoteConfig.getBoolean("hideFacebook")
+ }
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/LanguageHelper.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/LanguageHelper.kt
index 217eba3d3..4b68749ab 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/LanguageHelper.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/LanguageHelper.kt
@@ -27,6 +27,10 @@ class LanguageHelper(languageSharedPref: String?) {
locale = Locale("pt", "PT")
languageCode = "pt"
}
+ "uk" -> {
+ locale = Locale("uk", "UA")
+ languageCode = "uk"
+ }
else -> {
locale = if (pref.contains("_")) {
val languageCodeParts = pref.split("_".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/NumberAbbreviator.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/NumberAbbreviator.kt
index fc57cdcc5..6f198fff3 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/NumberAbbreviator.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/NumberAbbreviator.kt
@@ -7,10 +7,10 @@ import java.text.DecimalFormat
object NumberAbbreviator {
- fun abbreviate(context: Context, number: Double, numberOfDecimals: Int = 2): String {
+ fun abbreviate(context: Context, number: Double, numberOfDecimals: Int = 2, minForAbbrevation: Int = 0): String {
var usedNumber = number
var counter = 0
- while (usedNumber >= 1000) {
+ while (usedNumber >= 1000 && number >= minForAbbrevation) {
counter++
usedNumber /= 1000
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/TaskFilterHelper.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/TaskFilterHelper.kt
index 429ddae72..63eac0127 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/TaskFilterHelper.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/TaskFilterHelper.kt
@@ -1,125 +1,5 @@
package com.habitrpg.android.habitica.helpers
-import com.habitrpg.android.habitica.models.tasks.Task
-import com.habitrpg.android.habitica.models.tasks.TaskType
-import io.realm.Case
-import io.realm.OrderedRealmCollection
-import io.realm.RealmQuery
-import io.realm.Sort
-
class TaskFilterHelper {
- var searchQuery: String? = null
- private val activeFilters = HashMap()
- var tags: MutableList = mutableListOf()
-
- fun howMany(type: TaskType?): Int {
- return this.tags.size + if (isTaskFilterActive(type)) 1 else 0
- }
-
- private fun isTaskFilterActive(type: TaskType?): Boolean {
- if (activeFilters[type] == null) {
- return false
- }
- return if (TaskType.TODO == type) {
- Task.FILTER_ACTIVE != activeFilters[type]
- } else {
- Task.FILTER_ALL != activeFilters[type]
- }
- }
-
- fun filter(tasks: List): List {
- if (tasks.isEmpty()) {
- return tasks
- }
- val filtered = ArrayList()
- var activeFilter: String? = null
- if (activeFilters.size > 0) {
- activeFilter = activeFilters[tasks[0].type]
- }
- for (task in tasks) {
- if (isFiltered(task, activeFilter)) {
- filtered.add(task)
- }
- }
-
- return filtered
- }
-
- private fun isFiltered(task: Task, activeFilter: String?): Boolean {
- if (!task.containsAllTagIds(tags)) {
- return false
- }
- return if (activeFilter != null && activeFilter != Task.FILTER_ALL) {
- when (activeFilter) {
- Task.FILTER_ACTIVE -> if (task.type == TaskType.DAILY) {
- task.isDisplayedActive
- } else {
- !task.completed
- }
- Task.FILTER_GRAY -> task.completed || !task.isDisplayedActive
- Task.FILTER_WEAK -> task.value < 1
- Task.FILTER_STRONG -> task.value >= 1
- Task.FILTER_DATED -> task.dueDate != null
- Task.FILTER_COMPLETED -> task.completed
- else -> true
- }
- } else {
- true
- }
- }
-
- fun setActiveFilter(type: TaskType, activeFilter: String) {
- activeFilters[type] = activeFilter
- }
-
- fun getActiveFilter(type: TaskType?): String? {
- return if (activeFilters.containsKey(type)) {
- activeFilters[type]
- } else {
- null
- }
- }
-
- fun createQuery(unfilteredData: OrderedRealmCollection): RealmQuery? {
- if (!unfilteredData.isValid) {
- return null
- }
- var query = unfilteredData.where()
-
- if (unfilteredData.size != 0) {
- val taskType = unfilteredData[0].type
- val activeFilter = getActiveFilter(taskType)
-
- if (tags.size > 0) {
- query = query.`in`("tags.id", tags.toTypedArray())
- }
- if (searchQuery?.isNotEmpty() == true) {
- query = query
- .beginGroup()
- .contains("text", searchQuery ?: "", Case.INSENSITIVE)
- .or()
- .contains("notes", searchQuery ?: "", Case.INSENSITIVE)
- .endGroup()
- }
- if (activeFilter != null && activeFilter != Task.FILTER_ALL) {
- when (activeFilter) {
- Task.FILTER_ACTIVE -> query = if (TaskType.DAILY == taskType) {
- query.equalTo("completed", false).equalTo("isDue", true)
- } else {
- query.equalTo("completed", false)
- }
- Task.FILTER_GRAY -> query = query.equalTo("completed", true).or().equalTo("isDue", false)
- Task.FILTER_WEAK -> query = query.lessThan("value", 1.0)
- Task.FILTER_STRONG -> query = query.greaterThanOrEqualTo("value", 1.0)
- Task.FILTER_DATED -> query = query.isNotNull("dueDate").equalTo("completed", false).sort("dueDate")
- Task.FILTER_COMPLETED -> query = query.equalTo("completed", true)
- }
- }
- if (activeFilter != Task.FILTER_DATED) {
- query = query.sort("position", Sort.ASCENDING, "dateCreated", Sort.DESCENDING)
- }
- }
- return query
- }
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/models/CustomizationFilter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/models/CustomizationFilter.kt
new file mode 100644
index 000000000..460dcc503
--- /dev/null
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/models/CustomizationFilter.kt
@@ -0,0 +1,12 @@
+package com.habitrpg.android.habitica.models
+
+data class CustomizationFilter(
+ var onlyPurchased: Boolean = false,
+ var ascending: Boolean = false,
+ var months: MutableList = mutableListOf()
+) {
+ val isFiltering: Boolean
+ get() {
+ return onlyPurchased || months.isNotEmpty()
+ }
+}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ArmoireActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ArmoireActivity.kt
new file mode 100644
index 000000000..67a6e9847
--- /dev/null
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ArmoireActivity.kt
@@ -0,0 +1,184 @@
+package com.habitrpg.android.habitica.ui.activities
+
+import android.os.Bundle
+import android.util.Log
+import android.view.Gravity
+import android.view.View
+import android.view.animation.AccelerateInterpolator
+import android.widget.FrameLayout
+import androidx.core.content.ContextCompat
+import androidx.lifecycle.lifecycleScope
+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.databinding.ActivityArmoireBinding
+import com.habitrpg.android.habitica.extensions.observeOnce
+import com.habitrpg.android.habitica.helpers.AdHandler
+import com.habitrpg.android.habitica.helpers.AdType
+import com.habitrpg.android.habitica.helpers.AppConfigManager
+import com.habitrpg.android.habitica.helpers.RxErrorHandler
+import com.habitrpg.android.habitica.ui.helpers.loadImage
+import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
+import com.habitrpg.android.habitica.ui.views.ads.AdButton
+import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaBottomSheetDialog
+import com.plattysoft.leonids.ParticleSystem
+import javax.inject.Inject
+
+class ArmoireActivity: BaseActivity() {
+
+ private var equipmentKey: String? = null
+ private var gold: Double? = null
+ private var hasAnimatedChanges: Boolean = false
+ private lateinit var binding: ActivityArmoireBinding
+
+ @Inject
+ internal lateinit var inventoryRepository: InventoryRepository
+ @Inject
+ internal lateinit var appConfigManager: AppConfigManager
+ @Inject
+ lateinit var userViewModel: MainUserViewModel
+
+ override fun getLayoutResId(): Int = R.layout.activity_armoire
+
+ override fun injectActivity(component: UserComponent?) {
+ component?.inject(this)
+ }
+
+ override fun getContentView(): View {
+ binding = ActivityArmoireBinding.inflate(layoutInflater)
+ return binding.root
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ binding.goldView.currency = "gold"
+ binding.goldView.animationDuration = 1000
+ binding.goldView.animationDelay = 500
+ binding.goldView.minForAbbrevation = 1000000
+ binding.goldView.decimals = 0
+
+ userViewModel.user.observeOnce(this) { user ->
+ gold = user?.stats?.gp
+ val remaining = inventoryRepository.getArmoireRemainingCount()
+ binding.equipmentCountView.text = getString(R.string.equipment_remaining, remaining)
+ binding.noEquipmentView.visibility = if (remaining > 0) View.GONE else View.VISIBLE
+ }
+
+ if (appConfigManager.enableArmoireAds()) {
+ val handler = AdHandler(this, AdType.ARMOIRE) {
+ if (!it) {
+ return@AdHandler
+ }
+ Log.d("AdHandler", "Giving Armoire")
+ val user = userViewModel.user.value ?: return@AdHandler
+ val currentGold = user.stats?.gp ?: return@AdHandler
+ compositeSubscription.add(userRepository.updateUser("stats.gp", currentGold + 100)
+ .flatMap { inventoryRepository.buyItem(user, "armoire", 100.0, 1) }
+ .subscribe({
+ configure(it.armoire["type"] ?: "",
+ it.armoire["dropKey"] ?: "",
+ it.armoire["dropText"] ?: "")
+ binding.adButton.updateForAdType(AdType.ARMOIRE, lifecycleScope)
+ hasAnimatedChanges = false
+ gold = null
+ }, RxErrorHandler.handleEmptyError()))
+ }
+ handler.prepare {
+ if (it && binding.adButton.state == AdButton.State.EMPTY) {
+ binding.adButton.state = AdButton.State.READY
+ } else if (!it) {
+ binding.adButton.visibility = View.INVISIBLE
+ }
+ }
+ binding.adButton.updateForAdType(AdType.ARMOIRE, lifecycleScope)
+ binding.adButton.setOnClickListener {
+ binding.adButton.state = AdButton.State.LOADING
+ handler.show()
+ }
+ } else {
+ binding.adButton.visibility = View.GONE
+ }
+
+ binding.closeButton.setOnClickListener {
+ finish()
+ }
+ binding.equipButton.setOnClickListener {
+ equipmentKey?.let { it1 -> inventoryRepository.equip("gear", it1).subscribe() }
+ finish()
+ }
+ binding.dropRateButton.setOnClickListener {
+ showDropRateDialog()
+ }
+ intent.extras?.let {
+ val args = ArmoireActivityArgs.fromBundle(it)
+ equipmentKey = args.key
+ configure(args.type, args.key, args.text)
+ }
+ }
+
+ override fun onResume() {
+ super.onResume()
+ startAnimation()
+ }
+
+ private fun startAnimation() {
+ val gold = gold?.toInt()
+ if (hasAnimatedChanges) return
+ if (gold != null) {
+ binding.goldView.value = (gold).toDouble()
+ binding.goldView.value = (gold - 100).toDouble()
+ }
+
+ val container = binding.confettiAnchor
+ container.postDelayed(
+ {
+ createParticles(container, R.drawable.confetti_blue)
+ createParticles(container, R.drawable.confetti_red)
+ createParticles(container, R.drawable.confetti_yellow)
+ createParticles(container, R.drawable.confetti_purple)
+ },
+ 500
+ )
+ hasAnimatedChanges = true
+ }
+
+ private fun createParticles(container: FrameLayout, resource: Int) {
+ ParticleSystem(
+ container,
+ 30,
+ ContextCompat.getDrawable(this, resource),
+ 6000
+ )
+ .setRotationSpeed(144f)
+ .setScaleRange(1.0f, 1.6f)
+ .setSpeedByComponentsRange(-0.15f, 0.15f, 0.15f, 0.45f)
+ .setFadeOut(200, AccelerateInterpolator())
+ .emitWithGravity(binding.confettiAnchor, Gravity.TOP, 15, 2000)
+ }
+
+ fun configure(type: String, key: String, text: String) {
+ binding.titleView.text = text
+ binding.equipButton.visibility = if (type == "gear") View.VISIBLE else View.GONE
+ when (type) {
+ "gear" -> {
+ binding.subtitleView.text = getString(R.string.armoireEquipment_new)
+ binding.iconView.loadImage("shop_$key")
+ }
+ "food" -> {
+ binding.subtitleView.text = getString(R.string.armoireFood_new)
+ binding.iconView.loadImage("Pet_Food_$key")
+ }
+ else -> {
+ binding.subtitleView.text = getString(R.string.armoireExp)
+ binding.iconView.setImageResource(R.drawable.armoire_experience)
+ }
+ }
+ }
+
+ fun showDropRateDialog() {
+ val dialog = HabiticaBottomSheetDialog(this)
+ dialog.setContentView(R.layout.armoire_drop_rate_dialog)
+ dialog.show()
+ }
+}
\ No newline at end of file
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/BaseActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/BaseActivity.kt
index d3ce786ea..7c8aaf75c 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/BaseActivity.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/BaseActivity.kt
@@ -236,6 +236,10 @@ abstract class BaseActivity : AppCompatActivity() {
alert.enqueue()
}
+ open fun hideConnectionProblem() {
+
+ }
+
fun shareContent(identifier: String, message: String, image: Bitmap? = null) {
analyticsManager.logEvent("shared", bundleOf(Pair("identifier", identifier)))
val sharingIntent = Intent(Intent.ACTION_SEND)
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ChallengeFormActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ChallengeFormActivity.kt
index a5db0cd06..1db6c66b9 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ChallengeFormActivity.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ChallengeFormActivity.kt
@@ -36,16 +36,17 @@ import com.habitrpg.android.habitica.modules.AppModule
import com.habitrpg.android.habitica.ui.adapter.social.challenges.ChallengeTasksRecyclerViewAdapter
import com.habitrpg.android.habitica.ui.fragments.social.challenges.ChallengesOverviewFragmentDirections
import com.habitrpg.android.habitica.ui.helpers.ToolbarColorHelper
+import com.habitrpg.android.habitica.ui.viewmodels.TasksViewModel
import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaProgressDialog
import io.reactivex.rxjava3.core.Flowable
-import java.util.UUID
-import javax.inject.Inject
-import javax.inject.Named
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
+import java.util.UUID
+import javax.inject.Inject
+import javax.inject.Named
class ChallengeFormActivity : BaseActivity() {
@@ -223,7 +224,7 @@ class ChallengeFormActivity : BaseActivity() {
val bundle = intent.extras
ChallengeTasksRecyclerViewAdapter(
- null, 0, this, "",
+ TasksViewModel(), 0, this, "",
openTaskDisabled = false,
taskActionsDisabled = true
).also { challengeTasks = it }
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/LoginActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/LoginActivity.kt
index c3706bcfb..6131642fb 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/LoginActivity.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/LoginActivity.kt
@@ -224,7 +224,7 @@ class LoginActivity : BaseActivity() {
}
binding.password.imeOptions = EditorInfo.IME_ACTION_DONE
binding.fbLoginButton.setText(R.string.login_btn_fb)
- binding.fbLoginButton.visibility = View.VISIBLE
+ binding.fbLoginButton.visibility = if (configManager.hideFacebook()) View.GONE else View.VISIBLE
binding.googleLoginButton.setText(R.string.login_btn_google)
}
this.resetLayout()
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.kt
index 18b328682..ea676cfa0 100755
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.kt
@@ -9,6 +9,7 @@ import android.content.pm.PackageManager
import android.content.res.Configuration
import android.os.Build
import android.os.Bundle
+import android.util.Log
import android.view.KeyEvent
import android.view.MenuItem
import android.view.View
@@ -17,6 +18,7 @@ import androidx.activity.viewModels
import androidx.appcompat.app.ActionBarDrawerToggle
import androidx.core.view.children
import androidx.drawerlayout.widget.DrawerLayout
+import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavDestination
import androidx.navigation.findNavController
import androidx.navigation.fragment.NavHostFragment
@@ -35,6 +37,8 @@ import com.habitrpg.android.habitica.extensions.hideKeyboard
import com.habitrpg.android.habitica.extensions.isUsingNightModeResources
import com.habitrpg.android.habitica.extensions.subscribeWithErrorHandler
import com.habitrpg.android.habitica.extensions.updateStatusBarColor
+import com.habitrpg.android.habitica.helpers.AdHandler
+import com.habitrpg.android.habitica.helpers.AdType
import com.habitrpg.android.habitica.helpers.AmplitudeManager
import com.habitrpg.android.habitica.helpers.AppConfigManager
import com.habitrpg.android.habitica.helpers.MainNavigationController
@@ -63,10 +67,13 @@ import com.habitrpg.android.habitica.widget.AvatarStatsWidgetProvider
import com.habitrpg.android.habitica.widget.DailiesWidgetProvider
import com.habitrpg.android.habitica.widget.HabitButtonWidgetProvider
import com.habitrpg.android.habitica.widget.TodoListWidgetProvider
-import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
-import io.reactivex.rxjava3.core.Observable
-import java.util.concurrent.TimeUnit
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
import javax.inject.Inject
+import kotlin.time.DurationUnit
+import kotlin.time.toDuration
open class MainActivity : BaseActivity(), SnackbarActivity {
private var launchScreen: String? = null
@@ -445,6 +452,7 @@ open class MainActivity : BaseActivity(), SnackbarActivity {
}
if (this.faintDialog == null && !this.isFinishing) {
+
val binding = DialogFaintBinding.inflate(this.layoutInflater)
binding.hpBar.setLightBackground(true)
binding.hpBar.setIcon(HabiticaIconsHelper.imageOfHeartLightBg())
@@ -457,6 +465,20 @@ open class MainActivity : BaseActivity(), SnackbarActivity {
faintDialog = null
userRepository.revive().subscribe({ }, RxErrorHandler.handleEmptyError())
}
+ if (AdHandler.isAllowed(AdType.FAINT)) {
+ val handler = AdHandler(this, AdType.FAINT) {
+ Log.d("AdHandler", "Reviving user")
+ compositeSubscription.add(
+ userRepository.updateUser("stats.hp", 50)
+ .subscribe({}, RxErrorHandler.handleEmptyError())
+ )
+ }
+ handler.prepare()
+ faintDialog?.addButton(R.string.watch_ad_to_revive, true) { _, _ ->
+ faintDialog = null
+ handler.show()
+ }
+ }
soundManager.loadAndPlayAudio(SoundManager.SoundDeath)
this.faintDialog?.enqueue()
}
@@ -528,22 +550,33 @@ open class MainActivity : BaseActivity(), SnackbarActivity {
return snackbarContainer
}
+ private var errorJob: Job? = null
+
override fun showConnectionProblem(title: String?, message: String) {
if (title != null) {
super.showConnectionProblem(title, message)
} else {
- binding.connectionIssueTextview.visibility = View.VISIBLE
+ if (errorJob?.isCancelled == false) {
+ // a new error resets the timer to hide the error message
+ errorJob?.cancel()
+ }
+ binding.connectionIssueView.visibility = View.VISIBLE
binding.connectionIssueTextview.text = message
- compositeSubscription.add(
- Observable.just("")
- .delay(500, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
- .subscribe(
- {
- binding.connectionIssueTextview.visibility = View.GONE
- },
- {}
- )
- )
+ errorJob = lifecycleScope.launch(Dispatchers.Main) {
+ delay(1.toDuration(DurationUnit.MINUTES))
+ binding.connectionIssueView.visibility = View.GONE
+ }
+ }
+ }
+
+ override fun hideConnectionProblem() {
+ if (errorJob?.isCancelled == false) {
+ errorJob?.cancel()
+ }
+ lifecycleScope.launch(Dispatchers.Main) {
+ if (binding.connectionIssueView.visibility == View.VISIBLE) {
+ binding.connectionIssueView.visibility = View.GONE
+ }
}
}
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/NavigationDrawerAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/NavigationDrawerAdapter.kt
index 4f56bba4c..9d3fef785 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/NavigationDrawerAdapter.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/NavigationDrawerAdapter.kt
@@ -94,8 +94,8 @@ class NavigationDrawerAdapter(tintColor: Int, backgroundTintColor: Int) : Recycl
notifyItemRemoved(x)
}
for ((index, team) in teams.withIndex()) {
- val item = HabiticaDrawerItem(R.id.teamBoardFragment, team.id, team.summary)
- item.bundle = bundleOf(Pair("teamID", team.id))
+ val item = HabiticaDrawerItem(R.id.tasksFragment, team.id, team.summary)
+ item.bundle = bundleOf(Pair("ownerID", team.id))
val newIndex = teamHeaderIndex + index + 1
items.add(newIndex, item)
notifyItemInserted(newIndex)
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/challenges/ChallengeTasksRecyclerViewAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/challenges/ChallengeTasksRecyclerViewAdapter.kt
index 36c8c1274..531a3f43f 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/challenges/ChallengeTasksRecyclerViewAdapter.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/challenges/ChallengeTasksRecyclerViewAdapter.kt
@@ -7,7 +7,6 @@ import android.widget.Button
import android.widget.TextView
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.UserComponent
-import com.habitrpg.android.habitica.helpers.TaskFilterHelper
import com.habitrpg.android.habitica.models.tasks.Task
import com.habitrpg.android.habitica.models.tasks.TaskType
import com.habitrpg.android.habitica.ui.adapter.tasks.BaseTasksRecyclerViewAdapter
@@ -17,18 +16,19 @@ import com.habitrpg.android.habitica.ui.viewHolders.tasks.DailyViewHolder
import com.habitrpg.android.habitica.ui.viewHolders.tasks.HabitViewHolder
import com.habitrpg.android.habitica.ui.viewHolders.tasks.RewardViewHolder
import com.habitrpg.android.habitica.ui.viewHolders.tasks.TodoViewHolder
+import com.habitrpg.android.habitica.ui.viewmodels.TasksViewModel
import io.reactivex.rxjava3.core.BackpressureStrategy
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.subjects.PublishSubject
class ChallengeTasksRecyclerViewAdapter(
- taskFilterHelper: TaskFilterHelper?,
+ viewModel: TasksViewModel,
layoutResource: Int,
newContext: Context,
userID: String,
private val openTaskDisabled: Boolean,
private val taskActionsDisabled: Boolean
-) : BaseTasksRecyclerViewAdapter>(TaskType.HABIT, taskFilterHelper, layoutResource, newContext, userID) {
+) : BaseTasksRecyclerViewAdapter>(TaskType.HABIT, viewModel, layoutResource, newContext, userID) {
private val addItemSubject = PublishSubject.create()
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/BaseTasksRecyclerViewAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/BaseTasksRecyclerViewAdapter.kt
index 802708bae..e294243ce 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/BaseTasksRecyclerViewAdapter.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/BaseTasksRecyclerViewAdapter.kt
@@ -7,17 +7,17 @@ import android.view.ViewGroup
import com.habitrpg.android.habitica.HabiticaBaseApplication
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.TaskRepository
-import com.habitrpg.android.habitica.helpers.TaskFilterHelper
import com.habitrpg.android.habitica.models.tasks.Task
import com.habitrpg.android.habitica.models.tasks.TaskType
import com.habitrpg.android.habitica.proxy.AnalyticsManager
import com.habitrpg.android.habitica.ui.adapter.BaseRecyclerViewAdapter
import com.habitrpg.android.habitica.ui.viewHolders.BindableViewHolder
+import com.habitrpg.android.habitica.ui.viewmodels.TasksViewModel
import javax.inject.Inject
abstract class BaseTasksRecyclerViewAdapter>(
var taskType: TaskType,
- private val taskFilterHelper: TaskFilterHelper?,
+ private val viewModel: TasksViewModel,
private val layoutResource: Int,
newContext: Context,
private val userID: String?
@@ -73,12 +73,12 @@ abstract class BaseTasksRecyclerViewAdapter>(
}
fun filter() {
- if (this.taskFilterHelper == null || this.taskFilterHelper.howMany(taskType) == 0) {
+ if (this.viewModel.howMany(taskType) == 0) {
filteredContent = content
} else {
filteredContent = ArrayList()
content?.let {
- filteredContent?.addAll(this.taskFilterHelper.filter(it))
+ filteredContent?.addAll(this.viewModel.filter(it))
}
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/DailiesRecyclerViewHolder.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/DailiesRecyclerViewHolder.kt
index bd728ec3c..b645979aa 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/DailiesRecyclerViewHolder.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/DailiesRecyclerViewHolder.kt
@@ -2,10 +2,10 @@ package com.habitrpg.android.habitica.ui.adapter.tasks
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
-import com.habitrpg.android.habitica.helpers.TaskFilterHelper
import com.habitrpg.android.habitica.ui.viewHolders.tasks.DailyViewHolder
+import com.habitrpg.android.habitica.ui.viewmodels.TasksViewModel
-class DailiesRecyclerViewHolder(layoutResource: Int, taskFilterHelper: TaskFilterHelper) : RealmBaseTasksRecyclerViewAdapter(layoutResource, taskFilterHelper) {
+class DailiesRecyclerViewHolder(layoutResource: Int, viewModel: TasksViewModel) : RealmBaseTasksRecyclerViewAdapter(layoutResource, viewModel) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return if (viewType == 0) {
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/HabitsRecyclerViewAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/HabitsRecyclerViewAdapter.kt
index 2d520180a..f207c107e 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/HabitsRecyclerViewAdapter.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/HabitsRecyclerViewAdapter.kt
@@ -2,10 +2,10 @@ package com.habitrpg.android.habitica.ui.adapter.tasks
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
-import com.habitrpg.android.habitica.helpers.TaskFilterHelper
import com.habitrpg.android.habitica.ui.viewHolders.tasks.HabitViewHolder
+import com.habitrpg.android.habitica.ui.viewmodels.TasksViewModel
-class HabitsRecyclerViewAdapter(layoutResource: Int, taskFilterHelper: TaskFilterHelper) : RealmBaseTasksRecyclerViewAdapter(layoutResource, taskFilterHelper) {
+class HabitsRecyclerViewAdapter(layoutResource: Int, viewModel: TasksViewModel) : RealmBaseTasksRecyclerViewAdapter(layoutResource, viewModel) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return if (viewType == 0) {
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/RealmBaseTasksRecyclerViewAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/RealmBaseTasksRecyclerViewAdapter.kt
index e3b6ec854..db6cf825a 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/RealmBaseTasksRecyclerViewAdapter.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/RealmBaseTasksRecyclerViewAdapter.kt
@@ -11,13 +11,13 @@ import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.databinding.AdventureGuideMenuBannerBinding
import com.habitrpg.android.habitica.extensions.dpToPx
import com.habitrpg.android.habitica.extensions.layoutInflater
-import com.habitrpg.android.habitica.helpers.TaskFilterHelper
import com.habitrpg.android.habitica.models.responses.TaskDirection
import com.habitrpg.android.habitica.models.tasks.ChecklistItem
import com.habitrpg.android.habitica.models.tasks.Task
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.ui.adapter.BaseRecyclerViewAdapter
import com.habitrpg.android.habitica.ui.viewHolders.tasks.BaseTaskViewHolder
+import com.habitrpg.android.habitica.ui.viewmodels.TasksViewModel
import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
import io.reactivex.rxjava3.core.BackpressureStrategy
import io.reactivex.rxjava3.core.Flowable
@@ -27,7 +27,7 @@ import io.realm.OrderedRealmCollection
abstract class RealmBaseTasksRecyclerViewAdapter(
private val layoutResource: Int,
- private val taskFilterHelper: TaskFilterHelper?
+ private val viewModel: TasksViewModel
) : BaseRecyclerViewAdapter(), TaskRecyclerViewAdapter {
override var canScoreTasks = true
private var unfilteredData: List? = null
@@ -120,8 +120,8 @@ abstract class RealmBaseTasksRecyclerViewAdapter(
final override fun filter() {
val unfilteredData = this.unfilteredData ?: return
- if (taskFilterHelper != null && unfilteredData is OrderedRealmCollection) {
- val query = taskFilterHelper.createQuery(unfilteredData)
+ if (unfilteredData is OrderedRealmCollection) {
+ val query = viewModel.createQuery(unfilteredData)
if (query != null) {
data = query.findAll()
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/TodosRecyclerViewAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/TodosRecyclerViewAdapter.kt
index 88cce159d..dbebdd259 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/TodosRecyclerViewAdapter.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/TodosRecyclerViewAdapter.kt
@@ -2,10 +2,10 @@ package com.habitrpg.android.habitica.ui.adapter.tasks
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
-import com.habitrpg.android.habitica.helpers.TaskFilterHelper
import com.habitrpg.android.habitica.ui.viewHolders.tasks.TodoViewHolder
+import com.habitrpg.android.habitica.ui.viewmodels.TasksViewModel
-class TodosRecyclerViewAdapter(layoutResource: Int, taskFilterHelper: TaskFilterHelper) : RealmBaseTasksRecyclerViewAdapter(layoutResource, taskFilterHelper) {
+class TodosRecyclerViewAdapter(layoutResource: Int, viewModel: TasksViewModel) : RealmBaseTasksRecyclerViewAdapter(layoutResource, viewModel) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return if (viewType == 0) {
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/BaseDialogFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/BaseDialogFragment.kt
index 76e990ba8..451d9bc88 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/BaseDialogFragment.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/BaseDialogFragment.kt
@@ -4,8 +4,8 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
-import androidx.fragment.app.DialogFragment
import androidx.viewbinding.ViewBinding
+import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.habitrpg.android.habitica.HabiticaBaseApplication
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.TutorialRepository
@@ -18,7 +18,7 @@ import io.reactivex.rxjava3.functions.Consumer
import java.util.concurrent.TimeUnit
import javax.inject.Inject
-abstract class BaseDialogFragment : DialogFragment() {
+abstract class BaseDialogFragment : BottomSheetDialogFragment() {
var isModal: Boolean = false
abstract var binding: VB?
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/NavigationDrawerFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/NavigationDrawerFragment.kt
index fad356ae5..8206c0afe 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/NavigationDrawerFragment.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/NavigationDrawerFragment.kt
@@ -24,6 +24,7 @@ import com.habitrpg.android.habitica.data.InventoryRepository
import com.habitrpg.android.habitica.data.SocialRepository
import com.habitrpg.android.habitica.data.UserRepository
import com.habitrpg.android.habitica.databinding.DrawerMainBinding
+import com.habitrpg.android.habitica.extensions.getMinuteOrSeconds
import com.habitrpg.android.habitica.extensions.getRemainingString
import com.habitrpg.android.habitica.extensions.getShortRemainingString
import com.habitrpg.android.habitica.extensions.getThemeColor
@@ -49,16 +50,18 @@ import com.habitrpg.android.habitica.ui.viewmodels.NotificationsViewModel
import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.disposables.CompositeDisposable
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
import java.util.Calendar
import java.util.Date
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import kotlin.time.Duration
+import kotlin.time.DurationUnit
import kotlin.time.ExperimentalTime
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.launch
+import kotlin.time.toDuration
class NavigationDrawerFragment : DialogFragment() {
@@ -227,11 +230,9 @@ class NavigationDrawerFragment : DialogFragment() {
subscriptions?.add(
Flowable.combineLatest(
- contentRepository.getWorldState(), inventoryRepository.getAvailableLimitedItems(),
- { state, items ->
- return@combineLatest Pair(state, items)
- }
- ).subscribe(
+ contentRepository.getWorldState(), inventoryRepository.getAvailableLimitedItems()) { state, items ->
+ return@combineLatest Pair(state, items)
+ }.subscribe(
{ pair ->
val gearEvent = pair.first.events.firstOrNull { it.gear }
createUpdatingJob("seasonal", {
@@ -332,6 +333,10 @@ class NavigationDrawerFragment : DialogFragment() {
}
private fun updateUser(user: User) {
+ binding?.avatarView?.setOnClickListener {
+ MainNavigationController.navigate(R.id.openProfileActivity, bundleOf(Pair("userID", user.id)))
+ }
+
setMessagesCount(user.inbox)
setSettingsCount(if (user.flags?.verifiedUsername != true) 1 else 0)
setDisplayName(user.profile?.name)
@@ -675,8 +680,8 @@ class NavigationDrawerFragment : DialogFragment() {
createUpdatingJob(activePromo.promoType.name, {
activePromo.isActive
}, {
- val diff = activePromo.endDate.time - Date().time
- if (diff < (Duration.hours(1).inWholeMilliseconds)) Duration.seconds(1) else Duration.minutes(1)
+ val diff = (activePromo.endDate.time - Date().time).toDuration(DurationUnit.SECONDS)
+ 1.toDuration(diff.getMinuteOrSeconds())
}) {
if (activePromo.isActive) {
promotedItem.subtitle = context?.getString(R.string.sale_ends_in, activePromo.endDate.getShortRemainingString())
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/customization/AvatarCustomizationFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/customization/AvatarCustomizationFragment.kt
index 31328b5ee..f49198736 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/customization/AvatarCustomizationFragment.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/customization/AvatarCustomizationFragment.kt
@@ -1,29 +1,47 @@
package com.habitrpg.android.habitica.ui.fragments.inventory.customization
+import android.graphics.PorterDuff
import android.os.Bundle
import android.view.LayoutInflater
+import android.view.Menu
+import android.view.MenuInflater
+import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
+import android.widget.CheckBox
+import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.GridLayoutManager
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.CustomizationRepository
import com.habitrpg.android.habitica.data.InventoryRepository
+import com.habitrpg.android.habitica.databinding.BottomSheetBackgroundsFilterBinding
import com.habitrpg.android.habitica.databinding.FragmentRefreshRecyclerviewBinding
+import com.habitrpg.android.habitica.extensions.getThemeColor
+import com.habitrpg.android.habitica.extensions.setTintWith
import com.habitrpg.android.habitica.helpers.RxErrorHandler
+import com.habitrpg.android.habitica.models.CustomizationFilter
+import com.habitrpg.android.habitica.models.inventory.Customization
+import com.habitrpg.android.habitica.models.user.OwnedCustomization
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.ui.adapter.CustomizationRecyclerViewAdapter
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
import com.habitrpg.android.habitica.ui.helpers.MarginDecoration
import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
+import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaBottomSheetDialog
+import io.reactivex.rxjava3.core.BackpressureStrategy
+import io.reactivex.rxjava3.kotlin.combineLatest
+import io.reactivex.rxjava3.subjects.BehaviorSubject
+import io.reactivex.rxjava3.subjects.PublishSubject
import javax.inject.Inject
class AvatarCustomizationFragment :
BaseMainFragment(),
SwipeRefreshLayout.OnRefreshListener {
+ private var filterMenuItem: MenuItem? = null
override var binding: FragmentRefreshRecyclerviewBinding? = null
override fun createBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentRefreshRecyclerviewBinding {
@@ -44,6 +62,9 @@ class AvatarCustomizationFragment :
internal var adapter: CustomizationRecyclerViewAdapter = CustomizationRecyclerViewAdapter()
internal var layoutManager: GridLayoutManager = GridLayoutManager(activity, 2)
+ private val currentFilter = BehaviorSubject.create()
+ private val ownedCustomizations = PublishSubject.create>()
+
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
@@ -115,6 +136,7 @@ class AvatarCustomizationFragment :
this.loadCustomizations()
userViewModel.user.observe(viewLifecycleOwner) { updateUser(it) }
+ currentFilter.onNext(CustomizationFilter())
}
override fun onDestroy() {
@@ -122,6 +144,42 @@ class AvatarCustomizationFragment :
super.onDestroy()
}
+ override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
+ inflater.inflate(R.menu.menu_list_customizations, menu)
+
+ filterMenuItem = menu.findItem(R.id.action_filter)
+ updateFilterIcon()
+ }
+
+ private fun updateFilterIcon() {
+ if (currentFilter.value?.isFiltering != true) {
+ filterMenuItem?.setIcon(R.drawable.ic_action_filter_list)
+ context?.let {
+ val filterIcon = ContextCompat.getDrawable(it, R.drawable.ic_action_filter_list)
+ filterIcon?.setTintWith(it.getThemeColor(R.attr.headerTextColor), PorterDuff.Mode.MULTIPLY)
+ filterMenuItem?.setIcon(filterIcon)
+ }
+ } else {
+ context?.let {
+ val filterIcon = ContextCompat.getDrawable(it, R.drawable.ic_filters_active)
+ filterIcon?.setTintWith(it.getThemeColor(R.attr.textColorPrimaryDark), PorterDuff.Mode.MULTIPLY)
+ filterMenuItem?.setIcon(filterIcon)
+ }
+ }
+ }
+
+ @Suppress("ReturnCount")
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ when (item.itemId) {
+ R.id.action_filter -> {
+ showFilterDialog()
+ return true
+ }
+ }
+
+ return super.onOptionsItemSelected(item)
+ }
+
override fun injectFragment(component: UserComponent) {
component.inject(this)
}
@@ -129,10 +187,44 @@ class AvatarCustomizationFragment :
private fun loadCustomizations() {
val type = this.type ?: return
compositeSubscription.add(
- customizationRepository.getCustomizations(type, category, false).subscribe(
- {
- adapter.setCustomizations(if (type == "background") { it.reversed() } else { it })
- },
+ customizationRepository.getCustomizations(type, category, false)
+ .combineLatest(currentFilter.toFlowable(BackpressureStrategy.DROP),
+ ownedCustomizations.toFlowable(BackpressureStrategy.DROP))
+ .subscribe(
+ { (customizations, filter, ownedCustomizations) ->
+ if (filter.isFiltering) {
+ val displayedCustomizations = mutableListOf()
+ for (customization in customizations) {
+ if (filter.onlyPurchased) {
+ if (ownedCustomizations.find { it.key == customization.identifier } == null) {
+ continue
+ }
+ }
+ if (filter.months.isNotEmpty()) {
+ if (!filter.months.contains(customization.customizationSetName?.substringAfter('.'))) {
+ continue
+ }
+ }
+ displayedCustomizations.add(customization)
+ }
+ adapter.setCustomizations(
+ if (!filter.ascending) {
+ displayedCustomizations.reversed()
+ } else {
+ displayedCustomizations
+ }
+ )
+ } else {
+ adapter.setCustomizations(
+ if (!filter.ascending) {
+ customizations.reversed()
+ } else {
+ customizations
+ }
+ )
+ }
+ adapter.ownedCustomizations = ownedCustomizations.map { it.key + "_" + it.type + "_" + it.category }
+ },
RxErrorHandler.handleEmptyError()
)
)
@@ -154,9 +246,7 @@ class AvatarCustomizationFragment :
fun updateUser(user: User?) {
if (user == null) return
this.updateActiveCustomization(user)
- val ownedCustomizations = ArrayList()
- user.purchased?.customizations?.filter { it.type == this.type && it.purchased }?.mapTo(ownedCustomizations) { it.key + "_" + it.type + "_" + it.category }
- adapter.updateOwnership(ownedCustomizations)
+ ownedCustomizations.onNext(user.purchased?.customizations?.filter { it.type == this.type && it.purchased })
this.adapter.userSize = user.preferences?.size
this.adapter.hairColor = user.preferences?.hair?.color
this.adapter.gemBalance = user.gemCount
@@ -192,7 +282,7 @@ class AvatarCustomizationFragment :
override fun onRefresh() {
compositeSubscription.add(
- userRepository.retrieveUser(false, true).subscribe(
+ userRepository.retrieveUser(withTasks = false, forced = true).subscribe(
{
binding?.refreshLayout?.isRefreshing = false
},
@@ -200,4 +290,60 @@ class AvatarCustomizationFragment :
)
)
}
+
+ fun showFilterDialog() {
+ val filter = currentFilter.value ?: CustomizationFilter()
+ val context = context ?: return
+ val dialog = HabiticaBottomSheetDialog(context)
+ val binding = BottomSheetBackgroundsFilterBinding.inflate(layoutInflater)
+ binding.showMeWrapper.check(if (filter.onlyPurchased) R.id.show_purchased_button else R.id.show_all_button)
+ binding.showMeWrapper.setOnCheckedChangeListener { _, checkedId ->
+ filter.onlyPurchased = checkedId == R.id.show_purchased_button
+ currentFilter.onNext(filter)
+ }
+ binding.clearButton.setOnClickListener {
+ currentFilter.onNext(CustomizationFilter())
+ dialog.dismiss()
+ }
+ if (type == "background") {
+ binding.sortByWrapper.check(if (filter.ascending) R.id.oldest_button else R.id.newest_button)
+ binding.sortByWrapper.setOnCheckedChangeListener { _, checkedId ->
+ filter.ascending = checkedId == R.id.oldest_button
+ currentFilter.onNext(filter)
+ }
+ configureMonthFilterButton(binding.januaryButton, 1, filter)
+ configureMonthFilterButton(binding.febuaryButton, 2, filter)
+ configureMonthFilterButton(binding.marchButton, 3, filter)
+ configureMonthFilterButton(binding.aprilButton, 4, filter)
+ configureMonthFilterButton(binding.mayButton, 5, filter)
+ configureMonthFilterButton(binding.juneButton, 6, filter)
+ configureMonthFilterButton(binding.julyButton, 7, filter)
+ configureMonthFilterButton(binding.augustButton, 8, filter)
+ configureMonthFilterButton(binding.septemberButton, 9, filter)
+ configureMonthFilterButton(binding.octoberButton, 10, filter)
+ configureMonthFilterButton(binding.novemberButton, 11, filter)
+ configureMonthFilterButton(binding.decemberButton, 12, filter)
+ } else {
+ binding.sortByTitle.visibility = View.GONE
+ binding.sortByWrapper.visibility = View.GONE
+ binding.monthReleasedTitle.visibility = View.GONE
+ binding.monthReleasedWrapper.visibility = View.GONE
+ }
+ dialog.setContentView(binding.root)
+ dialog.show()
+ }
+
+ private fun configureMonthFilterButton(button: CheckBox, value: Int, filter: CustomizationFilter) {
+ val identifier = value.toString().padStart(2, '0')
+ button.isChecked = filter.months.contains(identifier)
+ button.text
+ button.setOnCheckedChangeListener { _, isChecked ->
+ if (!isChecked && filter.months.contains(identifier)) {
+ filter.months.remove(identifier)
+ } else if (isChecked && !filter.months.contains(identifier)) {
+ filter.months.add(identifier)
+ }
+ currentFilter.onNext(filter)
+ }
+ }
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/PreferencesFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/PreferencesFragment.kt
index a05cef7dc..97c13e2d4 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/PreferencesFragment.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/PreferencesFragment.kt
@@ -70,14 +70,6 @@ class PreferencesFragment : BasePreferencesFragment(), SharedPreferences.OnShare
val useReminder = preferenceManager.sharedPreferences?.getBoolean("use_reminder", false)
timePreference?.isEnabled = useReminder ?: false
- pushNotificationsPreference = findPreference("pushNotifications") as? PreferenceScreen
- val usePushNotifications = preferenceManager.sharedPreferences?.getBoolean("usePushNotifications", true)
- pushNotificationsPreference?.isEnabled = usePushNotifications ?: false
-
- emailNotificationsPreference = findPreference("emailNotifications") as? PreferenceScreen
- val useEmailNotifications = preferenceManager.sharedPreferences?.getBoolean("useEmailNotifications", true)
- emailNotificationsPreference?.isEnabled = useEmailNotifications ?: false
-
classSelectionPreference = findPreference("choose_class")
val weekdayPreference = findPreference("FirstDayOfTheWeek") as? ListPreference
@@ -193,23 +185,24 @@ class PreferencesFragment : BasePreferencesFragment(), SharedPreferences.OnShare
"usePushNotifications" -> {
val userPushNotifications = sharedPreferences.getBoolean(key, false)
pushNotificationsPreference?.isEnabled = userPushNotifications
+ userRepository.updateUser("preferences.pushNotifications.unsubscribeFromAll", userPushNotifications).subscribe()
if (userPushNotifications) {
pushNotificationManager.addPushDeviceUsingStoredToken()
} else {
pushNotificationManager.removePushDeviceUsingStoredToken()
}
}
- "useEmailNotifications" -> {
+ "useEmails" -> {
val useEmailNotifications = sharedPreferences.getBoolean(key, false)
emailNotificationsPreference?.isEnabled = useEmailNotifications
+ userRepository.updateUser("preferences.emailNotifications.unsubscribeFromAll", useEmailNotifications).subscribe()
}
"cds_time" -> {
- val timeval = sharedPreferences.getString("cds_time", "00:00")
- val pieces = timeval?.split(":".toRegex())?.dropLastWhile { it.isEmpty() }?.toTypedArray()
- if (pieces != null) {
- val hour = Integer.parseInt(pieces[0])
- userRepository.changeCustomDayStart(hour).subscribe({ }, RxErrorHandler.handleEmptyError())
- }
+ val timeval = sharedPreferences.getString("cds_time", "0") ?: "0"
+ val hour = Integer.parseInt(timeval)
+ userRepository.changeCustomDayStart(hour).subscribe({ }, RxErrorHandler.handleEmptyError())
+ val preference = findPreference(key)
+ preference?.summary = preference?.entry
}
"language" -> {
val languageHelper = LanguageHelper(sharedPreferences.getString(key, "en"))
@@ -316,8 +309,9 @@ class PreferencesFragment : BasePreferencesFragment(), SharedPreferences.OnShare
} else {
classSelectionPreference?.isVisible = false
}
- val cdsTimePreference = findPreference("cds_time") as? TimePreference
- cdsTimePreference?.text = user?.preferences?.dayStart.toString() + ":00"
+ val cdsTimePreference = findPreference("cds_time") as? ListPreference
+ cdsTimePreference?.value = user?.preferences?.dayStart.toString()
+ cdsTimePreference?.summary = cdsTimePreference?.entry
findPreference("dailyDueDefaultView")?.setDefaultValue(user?.preferences?.dailyDueDefaultView)
val languagePreference = findPreference("language") as? ListPreference
languagePreference?.value = user?.preferences?.language
@@ -345,6 +339,18 @@ class PreferencesFragment : BasePreferencesFragment(), SharedPreferences.OnShare
val inbox = user?.inbox
disablePMsPreference?.isChecked = inbox?.optOut ?: true
+ val usePushPreference = findPreference("usePushNotifications") as? CheckBoxPreference
+ pushNotificationsPreference = findPreference("pushNotifications") as? PreferenceScreen
+ val usePushNotifications = user?.preferences?.pushNotifications?.unsubscribeFromAll ?: false
+ pushNotificationsPreference?.isEnabled = usePushNotifications
+ usePushPreference?.isChecked = usePushNotifications
+
+ val useEmailPreference = findPreference("useEmails") as? CheckBoxPreference
+ emailNotificationsPreference = findPreference("emailNotifications") as? PreferenceScreen
+ val useEmailNotifications = user?.preferences?.emailNotifications?.unsubscribeFromAll ?: false
+ emailNotificationsPreference?.isEnabled = useEmailNotifications
+ useEmailPreference?.isChecked = useEmailNotifications
+
if (configManager.testingLevel() == AppTestingLevel.STAFF || BuildConfig.DEBUG) {
serverUrlPreference?.isVisible = true
taskListPreference?.isVisible = true
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/challenges/ChallengeFilterDialogHolder.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/challenges/ChallengeFilterDialogHolder.kt
index 973dc8ebb..34f97aeda 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/challenges/ChallengeFilterDialogHolder.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/challenges/ChallengeFilterDialogHolder.kt
@@ -8,6 +8,7 @@ import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.databinding.DialogChallengeFilterBinding
import com.habitrpg.android.habitica.models.social.Group
import com.habitrpg.android.habitica.ui.adapter.social.challenges.ChallengesFilterRecyclerViewAdapter
+import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaBottomSheetDialog
import com.habitrpg.android.habitica.utils.Action1
internal class ChallengeFilterDialogHolder private constructor(
@@ -28,13 +29,10 @@ internal class ChallengeFilterDialogHolder private constructor(
}
fun bind(
- builder: AlertDialog.Builder,
filterGroups: List,
currentFilter: ChallengeFilterOptions?,
selectedGroupsCallback: Action1
) {
- builder.setPositiveButton(context.getString(R.string.done)) { _, _ -> doneClicked() }
- .show()
this.filterGroups = filterGroups
this.currentFilter = currentFilter
this.selectedGroupsCallback = selectedGroupsCallback
@@ -86,11 +84,11 @@ internal class ChallengeFilterDialogHolder private constructor(
val challengeFilterDialogHolder = ChallengeFilterDialogHolder(dialogLayout, activity)
- val builder = AlertDialog.Builder(activity)
- .setTitle(R.string.filter)
- .setView(dialogLayout)
+ val sheet = HabiticaBottomSheetDialog(activity)
+ sheet.setContentView(dialogLayout)
- challengeFilterDialogHolder.bind(builder, filterGroups, currentFilter, selectedGroupsCallback)
+ challengeFilterDialogHolder.bind(filterGroups, currentFilter, selectedGroupsCallback)
+ sheet.show()
}
}
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/RewardsRecyclerviewFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/RewardsRecyclerviewFragment.kt
index f8d4bb346..044bcb68b 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/RewardsRecyclerviewFragment.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/RewardsRecyclerviewFragment.kt
@@ -12,6 +12,7 @@ import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import com.habitrpg.android.habitica.R
+import com.habitrpg.android.habitica.data.InventoryRepository
import com.habitrpg.android.habitica.extensions.subscribeWithErrorHandler
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.models.shops.ShopItem
@@ -22,6 +23,7 @@ import com.habitrpg.android.habitica.ui.adapter.tasks.RewardsRecyclerViewAdapter
import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator
import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar
import io.reactivex.rxjava3.functions.Consumer
+import javax.inject.Inject
class RewardsRecyclerviewFragment : TaskRecyclerViewFragment() {
@@ -87,6 +89,11 @@ class RewardsRecyclerviewFragment : TaskRecyclerViewFragment() {
)
}
+ override fun onDestroy() {
+ inventoryRepository.close()
+ super.onDestroy()
+ }
+
override fun getLayoutManager(context: Context?): LinearLayoutManager {
return GridLayoutManager(context, 4)
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TaskRecyclerViewFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TaskRecyclerViewFragment.kt
index 5545c5fc2..7a1790b68 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TaskRecyclerViewFragment.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TaskRecyclerViewFragment.kt
@@ -5,18 +5,15 @@ import android.content.Intent
import android.content.SharedPreferences
import android.graphics.drawable.BitmapDrawable
import android.os.Bundle
-import android.text.format.DateUtils
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.content.ContextCompat
-import androidx.core.content.edit
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.NO_POSITION
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.UserComponent
-import com.habitrpg.android.habitica.data.ApiClient
import com.habitrpg.android.habitica.data.InventoryRepository
import com.habitrpg.android.habitica.data.TaskRepository
import com.habitrpg.android.habitica.data.UserRepository
@@ -24,13 +21,11 @@ import com.habitrpg.android.habitica.databinding.FragmentRefreshRecyclerviewBind
import com.habitrpg.android.habitica.extensions.observeOnce
import com.habitrpg.android.habitica.extensions.setScaledPadding
import com.habitrpg.android.habitica.extensions.subscribeWithErrorHandler
-import com.habitrpg.android.habitica.helpers.AmplitudeManager
import com.habitrpg.android.habitica.helpers.AppConfigManager
import com.habitrpg.android.habitica.helpers.HapticFeedbackManager
import com.habitrpg.android.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.helpers.SoundManager
-import com.habitrpg.android.habitica.helpers.TaskFilterHelper
import com.habitrpg.android.habitica.models.responses.TaskDirection
import com.habitrpg.android.habitica.models.responses.TaskScoringResult
import com.habitrpg.android.habitica.models.tasks.Task
@@ -47,16 +42,21 @@ import com.habitrpg.android.habitica.ui.fragments.BaseFragment
import com.habitrpg.android.habitica.ui.helpers.EmptyItem
import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator
import com.habitrpg.android.habitica.ui.viewHolders.tasks.BaseTaskViewHolder
+import com.habitrpg.android.habitica.ui.viewmodels.TasksViewModel
import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.disposables.CompositeDisposable
+import io.reactivex.rxjava3.disposables.Disposable
import java.util.Date
import java.util.concurrent.TimeUnit
import javax.inject.Inject
open class TaskRecyclerViewFragment : BaseFragment(), androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener {
+ var viewModel: TasksViewModel? = null
+
+ private var taskSubscription: Disposable? = null
internal var canEditTasks: Boolean = true
internal var canScoreTaks: Boolean = true
override var binding: FragmentRefreshRecyclerviewBinding? = null
@@ -69,10 +69,6 @@ open class TaskRecyclerViewFragment : BaseFragment Unit) -> Unit)? = null
-
internal val className: TaskType
get() = this.taskType
@@ -103,18 +97,19 @@ open class TaskRecyclerViewFragment : BaseFragment? = when (this.taskType) {
- TaskType.HABIT -> HabitsRecyclerViewAdapter(R.layout.habit_item_card, taskFilterHelper)
- TaskType.DAILY -> DailiesRecyclerViewHolder(R.layout.daily_item_card, taskFilterHelper)
- TaskType.TODO -> TodosRecyclerViewAdapter(R.layout.todo_item_card, taskFilterHelper)
- TaskType.REWARD -> RewardsRecyclerViewAdapter(null, R.layout.reward_item_card)
- else -> null
+ viewModel?.let { viewModel ->
+ val adapter: BaseRecyclerViewAdapter<*, *>? = when (this.taskType) {
+ TaskType.HABIT -> HabitsRecyclerViewAdapter(R.layout.habit_item_card, viewModel)
+ TaskType.DAILY -> DailiesRecyclerViewHolder(R.layout.daily_item_card, viewModel)
+ TaskType.TODO -> TodosRecyclerViewAdapter(R.layout.todo_item_card, viewModel)
+ TaskType.REWARD -> RewardsRecyclerViewAdapter(null, R.layout.reward_item_card)
+ else -> null
+ }
+
+ recyclerAdapter = adapter as? TaskRecyclerViewAdapter
+ recyclerAdapter?.canScoreTasks = canScoreTaks
+ binding?.recyclerView?.adapter = adapter
}
-
- recyclerAdapter = adapter as? TaskRecyclerViewAdapter
- recyclerAdapter?.canScoreTasks = canScoreTaks
- binding?.recyclerView?.adapter = adapter
-
context?.let { recyclerAdapter?.taskDisplayMode = configManager.taskDisplayMode(it) }
recyclerAdapter?.errorButtonEvents?.subscribe(
@@ -136,14 +131,11 @@ open class TaskRecyclerViewFragment : BaseFragment 0) {
+ if ((viewModel?.howMany(taskType) ?: 0) > 0) {
newPosition = if ((newPosition + 1) == recyclerAdapter?.data?.size) {
recyclerAdapter?.data?.get(newPosition - 1)?.position ?: newPosition
} else {
@@ -318,6 +309,16 @@ open class TaskRecyclerViewFragment : BaseFragment 0) {
+ binding?.recyclerView?.emptyItem = if ((viewModel?.howMany(taskType) ?: 0) > 0) {
when (this.taskType) {
TaskType.HABIT -> {
EmptyItem(
@@ -422,21 +423,9 @@ open class TaskRecyclerViewFragment : BaseFragment
- handleTaskResult(result, task.value.toInt())
- if (!DateUtils.isToday(sharedPreferences.getLong("last_task_reporting", 0))) {
- AmplitudeManager.sendEvent(
- "task scored",
- AmplitudeManager.EVENT_CATEGORY_BEHAVIOUR,
- AmplitudeManager.EVENT_HITTYPE_EVENT
- )
- sharedPreferences.edit {
- putLong("last_task_reporting", Date().time)
- }
- }
- }.subscribe()
- )
+ viewModel?.scoreTask(task, direction) { result, value ->
+ handleTaskResult(result, value)
+ }
}
override fun onSaveInstanceState(outState: Bundle) {
@@ -449,7 +438,7 @@ open class TaskRecyclerViewFragment : BaseFragment taskFilterHelper.setActiveFilter(
+ TaskType.TODO -> viewModel?.setActiveFilter(
TaskType.TODO,
Task.FILTER_ACTIVE
)
TaskType.DAILY -> {
if (it.isValid && it.preferences?.dailyDueDefaultView == true) {
- taskFilterHelper.setActiveFilter(TaskType.DAILY, Task.FILTER_ACTIVE)
+ viewModel?.setActiveFilter(TaskType.DAILY, Task.FILTER_ACTIVE)
}
}
}
@@ -481,7 +470,7 @@ open class TaskRecyclerViewFragment : BaseFragment? = null
if (context != null) {
when (fragment.taskType) {
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TasksFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TasksFragment.kt
index a2de583c6..ecf838e98 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TasksFragment.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TasksFragment.kt
@@ -2,7 +2,6 @@ package com.habitrpg.android.habitica.ui.fragments.tasks
import android.app.Activity
import android.content.Intent
-import android.content.SharedPreferences
import android.graphics.PorterDuff
import android.os.Bundle
import android.text.format.DateUtils
@@ -16,51 +15,38 @@ import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.widget.SearchView
import androidx.core.content.ContextCompat
import androidx.core.content.edit
+import androidx.core.os.bundleOf
import androidx.fragment.app.Fragment
+import androidx.fragment.app.viewModels
import androidx.viewpager2.adapter.FragmentStateAdapter
import androidx.viewpager2.widget.ViewPager2
import com.habitrpg.android.habitica.HabiticaBaseApplication
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.UserComponent
-import com.habitrpg.android.habitica.data.TagRepository
import com.habitrpg.android.habitica.databinding.FragmentViewpagerBinding
import com.habitrpg.android.habitica.extensions.getThemeColor
import com.habitrpg.android.habitica.extensions.setTintWith
import com.habitrpg.android.habitica.helpers.AmplitudeManager
-import com.habitrpg.android.habitica.helpers.AppConfigManager
+import com.habitrpg.android.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.helpers.RxErrorHandler
-import com.habitrpg.android.habitica.helpers.TaskFilterHelper
import com.habitrpg.android.habitica.models.tasks.TaskType
-import com.habitrpg.android.habitica.modules.AppModule
import com.habitrpg.android.habitica.ui.activities.TaskFormActivity
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
+import com.habitrpg.android.habitica.ui.viewmodels.TasksViewModel
import com.habitrpg.android.habitica.ui.views.navigation.HabiticaBottomNavigationViewListener
import com.habitrpg.android.habitica.ui.views.tasks.TaskFilterDialog
import io.reactivex.rxjava3.disposables.Disposable
import java.util.Date
import java.util.WeakHashMap
-import javax.inject.Inject
-import javax.inject.Named
class TasksFragment : BaseMainFragment(), SearchView.OnQueryTextListener, HabiticaBottomNavigationViewListener {
-
+ internal val viewModel: TasksViewModel by viewModels()
override var binding: FragmentViewpagerBinding? = null
override fun createBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentViewpagerBinding {
return FragmentViewpagerBinding.inflate(inflater, container, false)
}
- @field:[Inject Named(AppModule.NAMED_USER_ID)]
- lateinit var userID: String
- @Inject
- lateinit var taskFilterHelper: TaskFilterHelper
- @Inject
- lateinit var tagRepository: TagRepository
- @Inject
- lateinit var appConfigManager: AppConfigManager
- @Inject
- lateinit var sharedPreferences: SharedPreferences
-
private var refreshItem: MenuItem? = null
internal var viewFragmentsDictionary: MutableMap? = WeakHashMap()
@@ -93,11 +79,16 @@ class TasksFragment : BaseMainFragment(), SearchView.O
arguments?.let {
val args = TasksFragmentArgs.fromBundle(it)
val taskTypeValue = args.taskType
+ if (args.ownerID?.isNotBlank() == true) {
+ viewModel.ownerID.value = args.ownerID ?: viewModel.userID
+ } else {
+ viewModel.ownerID.value = viewModel.userID
+ }
if (taskTypeValue?.isNotBlank() == true) {
val taskType = TaskType.from(taskTypeValue)
switchToTaskTab(taskType)
} else {
- when (sharedPreferences.getString("launch_screen", "")) {
+ when (viewModel.sharedPreferences.getString("launch_screen", "")) {
"/user/tasks/habits" -> onTabSelected(TaskType.HABIT, false)
"/user/tasks/dailies" -> onTabSelected(TaskType.DAILY, false)
"/user/tasks/todos" -> onTabSelected(TaskType.TODO, false)
@@ -105,6 +96,10 @@ class TasksFragment : BaseMainFragment(), SearchView.O
}
}
}
+
+ viewModel.ownerID.observe(viewLifecycleOwner) {
+ updateBoardDisplay()
+ }
}
override fun onResume() {
@@ -118,7 +113,11 @@ class TasksFragment : BaseMainFragment(), SearchView.O
}
binding?.viewPager?.currentItem = binding?.viewPager?.currentItem ?: 0
bottomNavigation?.listener = this
- bottomNavigation?.canAddTasks = true
+ bottomNavigation?.canAddTasks = viewModel.isPersonalBoard
+
+ activity?.binding?.toolbarTitle?.setOnClickListener {
+ viewModel.cycleOwnerIDs()
+ }
}
override fun onPause() {
@@ -128,17 +127,16 @@ class TasksFragment : BaseMainFragment(), SearchView.O
super.onPause()
}
- override fun onDestroy() {
- tagRepository.close()
- super.onDestroy()
- }
-
override fun injectFragment(component: UserComponent) {
component.inject(this)
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
- inflater.inflate(R.menu.menu_main_activity, menu)
+ if (viewModel.isPersonalBoard) {
+ inflater.inflate(R.menu.menu_main_activity, menu)
+ } else {
+ inflater.inflate(R.menu.menu_team_board, menu)
+ }
filterMenuItem = menu.findItem(R.id.action_filter)
updateFilterIcon()
@@ -167,7 +165,7 @@ class TasksFragment : BaseMainFragment(), SearchView.O
}
override fun onQueryTextChange(newText: String?): Boolean {
- taskFilterHelper.searchQuery = newText
+ viewModel.searchQuery = newText
viewFragmentsDictionary?.values?.forEach { values -> values.recyclerAdapter?.filter() }
return true
}
@@ -180,7 +178,11 @@ class TasksFragment : BaseMainFragment(), SearchView.O
}
R.id.action_reload -> {
refreshItem = item
- refresh()
+ viewModel.refreshData { }
+ true
+ }
+ R.id.action_team_info -> {
+ MainNavigationController.navigate(R.id.guildFragment, bundleOf(Pair("groupID", viewModel.ownerID)))
true
}
else -> super.onOptionsItemSelected(item)
@@ -191,15 +193,15 @@ class TasksFragment : BaseMainFragment(), SearchView.O
context?.let {
val disposable: Disposable
val dialog = TaskFilterDialog(it, HabiticaBaseApplication.userComponent)
- disposable = tagRepository.getTags().subscribe({ tagsList -> dialog.setTags(tagsList) }, RxErrorHandler.handleEmptyError())
- dialog.setActiveTags(taskFilterHelper.tags)
+ disposable = viewModel.tagRepository.getTags().subscribe({ tagsList -> dialog.setTags(tagsList) }, RxErrorHandler.handleEmptyError())
+ dialog.setActiveTags(viewModel.tags)
// There are some cases where these things might not be correctly set after the app resumes. This is just to catch that as best as possible
val navigation = bottomNavigation ?: activity?.binding?.bottomNavigation
val taskType = navigation?.activeTaskType ?: activeFragment?.taskType
if (taskType != null) {
- dialog.setTaskType(taskType, taskFilterHelper.getActiveFilter(taskType))
+ dialog.setTaskType(taskType, viewModel.getActiveFilter(taskType))
}
dialog.setListener(object : TaskFilterDialog.OnFilterCompletedListener {
override fun onFilterCompleted(
@@ -209,7 +211,7 @@ class TasksFragment : BaseMainFragment(), SearchView.O
if (viewFragmentsDictionary == null) {
return
}
- taskFilterHelper.tags = activeTags
+ viewModel.tags = activeTags
if (activeTaskFilter != null) {
activeFragment?.setActiveFilter(activeTaskFilter)
}
@@ -226,34 +228,18 @@ class TasksFragment : BaseMainFragment(), SearchView.O
}
}
- private fun refresh() {
- activeFragment?.onRefresh()
- }
-
private fun loadTaskLists() {
val fragmentManager = childFragmentManager
binding?.viewPager?.adapter = object : FragmentStateAdapter(fragmentManager, lifecycle) {
-
override fun createFragment(position: Int): Fragment {
val fragment: TaskRecyclerViewFragment = when (position) {
- 0 -> TaskRecyclerViewFragment.newInstance(context, TaskType.HABIT)
- 1 -> TaskRecyclerViewFragment.newInstance(context, TaskType.DAILY)
+ 0 -> TaskRecyclerViewFragment.newInstance(context, TaskType.HABIT, viewModel)
+ 1 -> TaskRecyclerViewFragment.newInstance(context, TaskType.DAILY, viewModel)
3 -> RewardsRecyclerviewFragment.newInstance(context, TaskType.REWARD, true)
- else -> TaskRecyclerViewFragment.newInstance(context, TaskType.TODO)
- }
- fragment.refreshAction = {
- compositeSubscription.add(
- userRepository.retrieveUser(
- withTasks = true,
- forced = true
- ).doOnTerminate {
- it()
- }.subscribe({ }, RxErrorHandler.handleEmptyError())
- )
+ else -> TaskRecyclerViewFragment.newInstance(context, TaskType.TODO, viewModel)
}
viewFragmentsDictionary?.put(position, fragment)
-
return fragment
}
@@ -271,7 +257,7 @@ class TasksFragment : BaseMainFragment(), SearchView.O
}
private fun updateFilterIcon() {
- val filterCount = taskFilterHelper.howMany(activeFragment?.taskType)
+ val filterCount = viewModel.howMany(activeFragment?.taskType)
filterMenuItem?.isVisible = activeFragment?.taskType != TaskType.REWARD
if (filterCount == 0) {
@@ -361,7 +347,7 @@ class TasksFragment : BaseMainFragment(), SearchView.O
val bundle = Bundle()
bundle.putString(TaskFormActivity.TASK_TYPE_KEY, type.value)
- bundle.putStringArrayList(TaskFormActivity.SELECTED_TAGS_KEY, ArrayList(taskFilterHelper.tags))
+ bundle.putStringArrayList(TaskFormActivity.SELECTED_TAGS_KEY, ArrayList(viewModel.tags))
val intent = Intent(activity, TaskFormActivity::class.java)
intent.putExtras(bundle)
@@ -391,13 +377,13 @@ class TasksFragment : BaseMainFragment(), SearchView.O
}
}
- if (!DateUtils.isToday(sharedPreferences.getLong("last_creation_reporting", 0))) {
+ if (!DateUtils.isToday(viewModel.sharedPreferences.getLong("last_creation_reporting", 0))) {
AmplitudeManager.sendEvent(
"task created",
AmplitudeManager.EVENT_CATEGORY_BEHAVIOUR,
AmplitudeManager.EVENT_HITTYPE_EVENT
)
- sharedPreferences.edit {
+ viewModel.sharedPreferences.edit {
putLong("last_creation_reporting", Date().time)
}
}
@@ -448,4 +434,10 @@ class TasksFragment : BaseMainFragment(), SearchView.O
override fun onAdd(taskType: TaskType) {
openNewTaskActivity(taskType)
}
+
+ private fun updateBoardDisplay() {
+ activity?.title = viewModel.ownerTitle
+ val isPersonalBoard = viewModel.isPersonalBoard
+ bottomNavigation?.canAddTasks = isPersonalBoard
+ }
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TeamBoardFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TeamBoardFragment.kt
deleted file mode 100644
index 773fbef34..000000000
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TeamBoardFragment.kt
+++ /dev/null
@@ -1,391 +0,0 @@
-package com.habitrpg.android.habitica.ui.fragments.tasks
-
-import android.app.Activity
-import android.content.Intent
-import android.graphics.PorterDuff
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.Menu
-import android.view.MenuInflater
-import android.view.MenuItem
-import android.view.View
-import android.view.ViewGroup
-import androidx.activity.result.contract.ActivityResultContracts
-import androidx.appcompat.widget.SearchView
-import androidx.core.content.ContextCompat
-import androidx.core.os.bundleOf
-import androidx.viewpager2.adapter.FragmentStateAdapter
-import androidx.viewpager2.widget.ViewPager2
-import com.habitrpg.android.habitica.HabiticaBaseApplication
-import com.habitrpg.android.habitica.R
-import com.habitrpg.android.habitica.components.UserComponent
-import com.habitrpg.android.habitica.data.TagRepository
-import com.habitrpg.android.habitica.databinding.FragmentViewpagerBinding
-import com.habitrpg.android.habitica.extensions.getThemeColor
-import com.habitrpg.android.habitica.extensions.setTintWith
-import com.habitrpg.android.habitica.helpers.AmplitudeManager
-import com.habitrpg.android.habitica.helpers.AppConfigManager
-import com.habitrpg.android.habitica.helpers.MainNavigationController
-import com.habitrpg.android.habitica.helpers.RxErrorHandler
-import com.habitrpg.android.habitica.helpers.TaskFilterHelper
-import com.habitrpg.android.habitica.models.tasks.TaskType
-import com.habitrpg.android.habitica.ui.activities.TaskFormActivity
-import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
-import com.habitrpg.android.habitica.ui.views.navigation.HabiticaBottomNavigationViewListener
-import com.habitrpg.android.habitica.ui.views.tasks.TaskFilterDialog
-import io.reactivex.rxjava3.disposables.Disposable
-import java.util.Date
-import java.util.WeakHashMap
-import javax.inject.Inject
-
-class TeamBoardFragment : BaseMainFragment(), SearchView.OnQueryTextListener, HabiticaBottomNavigationViewListener {
-
- override var binding: FragmentViewpagerBinding? = null
-
- override fun createBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentViewpagerBinding {
- return FragmentViewpagerBinding.inflate(inflater, container, false)
- }
-
- var teamID: String = ""
-
- @Inject
- lateinit var taskFilterHelper: TaskFilterHelper
- @Inject
- lateinit var tagRepository: TagRepository
- @Inject
- lateinit var appConfigManager: AppConfigManager
-
- private var refreshItem: MenuItem? = null
- internal var viewFragmentsDictionary: MutableMap? = WeakHashMap()
-
- private var filterMenuItem: MenuItem? = null
-
- private val activeFragment: TaskRecyclerViewFragment?
- get() {
- var fragment = viewFragmentsDictionary?.get(binding?.viewPager?.currentItem)
- if (fragment == null) {
- if (isAdded) {
- fragment = (childFragmentManager.findFragmentByTag("android:switcher:" + R.id.viewPager + ":" + binding?.viewPager?.currentItem) as? TaskRecyclerViewFragment)
- }
- }
- return fragment
- }
-
- override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View? {
- this.usesTabLayout = false
- this.hidesToolbar = true
- this.usesBottomNavigation = true
- return super.onCreateView(inflater, container, savedInstanceState)
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
-
- arguments?.let {
- val args = TeamBoardFragmentArgs.fromBundle(it)
- teamID = args.teamID
- }
-
- compositeSubscription.add(
- userRepository.getTeamPlan(teamID)
- .subscribe(
- {
- activity?.title = it.name
- },
- RxErrorHandler.handleEmptyError()
- )
- )
-
- compositeSubscription.add(userRepository.retrieveTeamPlan(teamID).subscribe({ }, RxErrorHandler.handleEmptyError()))
-
- loadTaskLists()
- }
-
- override fun onResume() {
- super.onResume()
- bottomNavigation?.activeTaskType = when (binding?.viewPager?.currentItem) {
- 0 -> TaskType.HABIT
- 1 -> TaskType.DAILY
- 2 -> TaskType.TODO
- 3 -> TaskType.REWARD
- else -> TaskType.HABIT
- }
- bottomNavigation?.listener = this
- bottomNavigation?.canAddTasks = false
- }
-
- override fun onPause() {
- if (bottomNavigation?.listener == this) {
- bottomNavigation?.listener = null
- }
-
- super.onPause()
- }
-
- override fun onDestroy() {
- tagRepository.close()
- super.onDestroy()
- }
-
- override fun injectFragment(component: UserComponent) {
- component.inject(this)
- }
-
- override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
- inflater.inflate(R.menu.menu_team_board, menu)
-
- filterMenuItem = menu.findItem(R.id.action_filter)
- updateFilterIcon()
-
- val item = menu.findItem(R.id.action_search)
- tintMenuIcon(item)
- val sv = item.actionView as? SearchView
- sv?.setOnQueryTextListener(this)
- sv?.setIconifiedByDefault(false)
- item.setOnActionExpandListener(object : MenuItem.OnActionExpandListener {
- override fun onMenuItemActionCollapse(item: MenuItem): Boolean {
- filterMenuItem?.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS)
- return true
- }
-
- override fun onMenuItemActionExpand(item: MenuItem): Boolean {
- // Do something when expanded
- filterMenuItem?.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER)
- return true
- }
- })
- }
-
- override fun onQueryTextSubmit(query: String?): Boolean {
- return true
- }
-
- override fun onQueryTextChange(newText: String?): Boolean {
- taskFilterHelper.searchQuery = newText
- viewFragmentsDictionary?.values?.forEach { values -> values.recyclerAdapter?.filter() }
- return true
- }
-
- override fun onOptionsItemSelected(item: MenuItem): Boolean {
- return when (item.itemId) {
- R.id.action_filter -> {
- showFilterDialog()
- true
- }
- R.id.action_reload -> {
- refreshItem = item
- refresh()
- true
- }
- R.id.action_team_info -> {
- MainNavigationController.navigate(R.id.guildFragment, bundleOf(Pair("groupID", teamID)))
- true
- }
- else -> super.onOptionsItemSelected(item)
- }
- }
-
- private fun showFilterDialog() {
- context?.let {
- val disposable: Disposable
- val dialog = TaskFilterDialog(it, HabiticaBaseApplication.userComponent)
- disposable = tagRepository.getTags().subscribe({ tagsList -> dialog.setTags(tagsList) }, RxErrorHandler.handleEmptyError())
- dialog.setActiveTags(taskFilterHelper.tags)
- if (activeFragment != null) {
- val taskType = activeFragment?.taskType
- if (taskType != null) {
- dialog.setTaskType(taskType, taskFilterHelper.getActiveFilter(taskType))
- }
- }
- dialog.setListener(object : TaskFilterDialog.OnFilterCompletedListener {
- override fun onFilterCompleted(
- activeTaskFilter: String?,
- activeTags: MutableList
- ) {
- if (viewFragmentsDictionary == null) {
- return
- }
- taskFilterHelper.tags = activeTags
- if (activeTaskFilter != null) {
- activeFragment?.setActiveFilter(activeTaskFilter)
- }
- viewFragmentsDictionary?.values?.forEach { values -> values.recyclerAdapter?.filter() }
- updateFilterIcon()
- }
- })
- dialog.setOnDismissListener {
- if (!disposable.isDisposed) {
- disposable.dispose()
- }
- }
- dialog.show()
- }
- }
-
- private fun refresh() {
- activeFragment?.onRefresh()
- }
-
- private fun loadTaskLists() {
- val fragmentManager = childFragmentManager
-
- binding?.viewPager?.adapter = object : FragmentStateAdapter(fragmentManager, lifecycle) {
-
- override fun createFragment(position: Int): androidx.fragment.app.Fragment {
- val fragment: TaskRecyclerViewFragment = when (position) {
- 0 -> TaskRecyclerViewFragment.newInstance(context, TaskType.HABIT)
- 1 -> TaskRecyclerViewFragment.newInstance(context, TaskType.DAILY)
- 3 -> RewardsRecyclerviewFragment.newInstance(context, TaskType.REWARD, false)
- else -> TaskRecyclerViewFragment.newInstance(context, TaskType.TODO)
- }
- fragment.canEditTasks = false
- fragment.canScoreTaks = false
- fragment.refreshAction = {
- compositeSubscription.add(
- userRepository.retrieveTeamPlan(teamID)
- .doOnTerminate {
- it()
- }.subscribe({ }, RxErrorHandler.handleEmptyError())
- )
- }
-
- viewFragmentsDictionary?.put(position, fragment)
-
- return fragment
- }
-
- override fun getItemCount(): Int = 4
- }
-
- binding?.viewPager?.registerOnPageChangeCallback(object :
- ViewPager2.OnPageChangeCallback() {
- override fun onPageSelected(position: Int) {
- bottomNavigation?.selectedPosition = position
- updateFilterIcon()
- }
- })
- }
-
- private fun updateFilterIcon() {
- if (filterMenuItem == null) {
- return
- }
- var filterCount = 0
- if (activeFragment != null) {
- filterCount = taskFilterHelper.howMany(activeFragment?.taskType)
- }
- if (filterCount == 0) {
- filterMenuItem?.setIcon(R.drawable.ic_action_filter_list)
- context?.let {
- val filterIcon = ContextCompat.getDrawable(it, R.drawable.ic_action_filter_list)
- filterIcon?.setTintWith(it.getThemeColor(R.attr.headerTextColor), PorterDuff.Mode.MULTIPLY)
- filterMenuItem?.setIcon(filterIcon)
- }
- } else {
- context?.let {
- val filterIcon = ContextCompat.getDrawable(it, R.drawable.ic_filters_active)
- filterIcon?.setTintWith(it.getThemeColor(R.attr.textColorPrimaryDark), PorterDuff.Mode.MULTIPLY)
- filterMenuItem?.setIcon(filterIcon)
- }
- }
- }
- // endregion
-
- private fun openNewTaskActivity(type: TaskType) {
- if (Date().time - (lastTaskFormOpen?.time ?: 0) < 2000) {
- return
- }
-
- val additionalData = HashMap()
- additionalData["created task type"] = type
- additionalData["viewed task type"] = when (binding?.viewPager?.currentItem) {
- 0 -> TaskType.HABIT
- 1 -> TaskType.DAILY
- 2 -> TaskType.TODO
- 3 -> TaskType.REWARD
- else -> ""
- }
- AmplitudeManager.sendEvent("open create task form", AmplitudeManager.EVENT_CATEGORY_BEHAVIOUR, AmplitudeManager.EVENT_HITTYPE_EVENT, additionalData)
-
- val bundle = Bundle()
- bundle.putString(TaskFormActivity.TASK_TYPE_KEY, type.value)
- bundle.putStringArrayList(TaskFormActivity.SELECTED_TAGS_KEY, ArrayList(taskFilterHelper.tags))
-
- val intent = Intent(activity, TaskFormActivity::class.java)
- intent.putExtras(bundle)
- intent.flags = Intent.FLAG_ACTIVITY_REORDER_TO_FRONT
- if (this.isAdded) {
- lastTaskFormOpen = Date()
- taskCreatedResult.launch(intent)
- }
- }
-
- //endregion Events
-
- private val taskCreatedResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
- onTaskCreatedResult(it.resultCode, it.data)
- }
-
- private fun onTaskCreatedResult(resultCode: Int, data: Intent?) {
- if (resultCode == Activity.RESULT_OK) {
- val taskTypeValue = data?.getStringExtra(TaskFormActivity.TASK_TYPE_KEY)
- if (taskTypeValue != null) {
- val taskType = TaskType.from(taskTypeValue)
- switchToTaskTab(taskType)
-
- val index = indexForTaskType(taskType)
- if (index != -1) {
- val fragment = viewFragmentsDictionary?.get(index)
- fragment?.binding?.recyclerView?.scrollToPosition(0)
- }
- }
- }
- }
-
- private fun switchToTaskTab(taskType: TaskType?) {
- val index = indexForTaskType(taskType)
- if (binding?.viewPager != null && index != -1) {
- binding?.viewPager?.currentItem = index
- }
- }
-
- private fun indexForTaskType(taskType: TaskType?): Int {
- if (taskType != null) {
- for (index in 0 until (viewFragmentsDictionary?.size ?: 0)) {
- val fragment = viewFragmentsDictionary?.get(index)
- if (fragment != null && taskType == fragment.className) {
- return index
- }
- }
- }
- return -1
- }
-
- override val displayedClassName: String?
- get() = null
-
- override fun addToBackStack(): Boolean = false
-
- companion object {
- var lastTaskFormOpen: Date? = null
- }
-
- override fun onTabSelected(taskType: TaskType, smooth: Boolean) {
- val newItem = when (taskType) {
- TaskType.HABIT -> 0
- TaskType.DAILY -> 1
- TaskType.TODO -> 2
- TaskType.REWARD -> 3
- else -> 0
- }
- binding?.viewPager?.setCurrentItem(newItem, smooth)
- }
-
- override fun onAdd(taskType: TaskType) {
- openNewTaskActivity(taskType)
- }
-}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/menu/BottomSheetMenu.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/menu/BottomSheetMenu.kt
index 2bf3e8fc5..a7dbbcf7c 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/menu/BottomSheetMenu.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/menu/BottomSheetMenu.kt
@@ -2,21 +2,16 @@ package com.habitrpg.android.habitica.ui.menu
import android.content.Context
import android.view.View
-import com.google.android.material.bottomsheet.BottomSheetBehavior
-import com.google.android.material.bottomsheet.BottomSheetDialog
import com.habitrpg.android.habitica.databinding.MenuBottomSheetBinding
+import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaBottomSheetDialog
-class BottomSheetMenu(context: Context) : BottomSheetDialog(context), View.OnClickListener {
+class BottomSheetMenu(context: Context) : HabiticaBottomSheetDialog(context), View.OnClickListener {
private var binding = MenuBottomSheetBinding.inflate(layoutInflater)
private var runnable: ((Int) -> Unit)? = null
init {
setContentView(binding.root)
binding.titleView.visibility = View.GONE
-
- val behavior = BottomSheetBehavior.from(binding.root.parent as View)
- behavior.state = BottomSheetBehavior.STATE_EXPANDED
- behavior.peekHeight = 0
}
fun setSelectionRunnable(runnable: (Int) -> Unit) {
@@ -26,12 +21,14 @@ class BottomSheetMenu(context: Context) : BottomSheetDialog(context), View.OnCli
override fun setTitle(title: CharSequence?) {
binding.titleView.text = title
binding.titleView.visibility = View.VISIBLE
+ grabberVisibility = View.GONE
}
fun addMenuItem(menuItem: BottomSheetMenuItem) {
val item = menuItem.inflate(this.context, layoutInflater, this.binding.menuItems)
item.setOnClickListener(this)
this.binding.menuItems.addView(item)
+ binding.root.requestLayout()
}
override fun onClick(v: View) {
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/MainUserViewModel.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/MainUserViewModel.kt
index b8468f4b7..59cc6d41c 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/MainUserViewModel.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/MainUserViewModel.kt
@@ -20,6 +20,8 @@ class MainUserViewModel(val userRepository: UserRepository) {
get() = user.value?.id
val username: CharSequence
get() = user.value?.username ?: ""
+ val displayName: CharSequence
+ get() = user.value?.profile?.name ?: ""
val partyID: String?
get() = user.value?.party?.id
val isUserFainted: Boolean
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/TasksViewModel.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/TasksViewModel.kt
new file mode 100644
index 000000000..9f52ddd3e
--- /dev/null
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/TasksViewModel.kt
@@ -0,0 +1,232 @@
+package com.habitrpg.android.habitica.ui.viewmodels
+
+import android.content.SharedPreferences
+import android.text.format.DateUtils
+import androidx.core.content.edit
+import androidx.lifecycle.MutableLiveData
+import com.habitrpg.android.habitica.components.UserComponent
+import com.habitrpg.android.habitica.data.TagRepository
+import com.habitrpg.android.habitica.data.TaskRepository
+import com.habitrpg.android.habitica.helpers.AmplitudeManager
+import com.habitrpg.android.habitica.helpers.AppConfigManager
+import com.habitrpg.android.habitica.helpers.RxErrorHandler
+import com.habitrpg.android.habitica.helpers.TaskFilterHelper
+import com.habitrpg.android.habitica.models.responses.TaskDirection
+import com.habitrpg.android.habitica.models.responses.TaskScoringResult
+import com.habitrpg.android.habitica.models.tasks.Task
+import com.habitrpg.android.habitica.models.tasks.TaskType
+import com.habitrpg.android.habitica.modules.AppModule
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import io.realm.Case
+import io.realm.OrderedRealmCollection
+import io.realm.RealmQuery
+import io.realm.Sort
+import java.util.Date
+import javax.inject.Inject
+import javax.inject.Named
+
+class TasksViewModel: BaseViewModel() {
+ private var compositeSubscription: CompositeDisposable = CompositeDisposable()
+
+ override fun inject(component: UserComponent) {
+ component.inject(this)
+ }
+
+ @field:[Inject Named(AppModule.NAMED_USER_ID)]
+ lateinit var userID: String
+ @Inject
+ lateinit var taskRepository: TaskRepository
+ @Inject
+ lateinit var taskFilterHelper: TaskFilterHelper
+ @Inject
+ lateinit var tagRepository: TagRepository
+ @Inject
+ lateinit var appConfigManager: AppConfigManager
+ @Inject
+ lateinit var sharedPreferences: SharedPreferences
+
+ private var owners: List> = listOf()
+
+ val ownerID: MutableLiveData by lazy {
+ MutableLiveData()
+ }
+
+ val isPersonalBoard: Boolean
+ get() {
+ return ownerID.value == userID
+ }
+ val ownerTitle: CharSequence
+ get() {
+ return owners.firstOrNull { it.first == ownerID.value }?.second ?: ""
+ }
+
+ init {
+ compositeSubscription.add(userRepository.getTeamPlans()
+ .subscribe({
+ owners = listOf(Pair(userID ?: "", userViewModel.displayName)) + it.map { Pair(it.id, it.summary) }
+ }, RxErrorHandler.handleEmptyError()))
+ compositeSubscription.add(userRepository.retrieveTeamPlans().subscribe({}, RxErrorHandler.handleEmptyError()))
+ }
+
+ internal fun refreshData(onComplete: () -> Unit) {
+ if (isPersonalBoard) {
+ compositeSubscription.add(
+ userRepository.retrieveUser(
+ withTasks = true,
+ forced = true
+ ).doOnTerminate {
+ onComplete()
+ }.subscribe({ }, RxErrorHandler.handleEmptyError())
+ )
+ } else {
+ compositeSubscription.add(
+ userRepository.retrieveTeamPlan(ownerID.value ?: "")
+ .doOnTerminate {
+ onComplete()
+ }.subscribe({ }, RxErrorHandler.handleEmptyError())
+ )
+ }
+ }
+
+ fun cycleOwnerIDs() {
+ val nextIndex = owners.indexOfFirst { it.first == ownerID.value } + 1
+ if (nextIndex < owners.size) {
+ ownerID.value = owners[nextIndex].first
+ } else {
+ ownerID.value = owners[0].first
+ }
+ }
+
+ fun scoreTask(task: Task, direction: TaskDirection, onResult: (TaskScoringResult, Int) -> Unit) {
+ compositeSubscription.add(
+ taskRepository.taskChecked(null, task.id ?: "", direction == TaskDirection.UP, false) { result ->
+ onResult(result, task.value.toInt())
+ if (!DateUtils.isToday(sharedPreferences.getLong("last_task_reporting", 0))) {
+ AmplitudeManager.sendEvent(
+ "task scored",
+ AmplitudeManager.EVENT_CATEGORY_BEHAVIOUR,
+ AmplitudeManager.EVENT_HITTYPE_EVENT
+ )
+ sharedPreferences.edit {
+ putLong("last_task_reporting", Date().time)
+ }
+ }
+ }.subscribe({}, RxErrorHandler.handleEmptyError())
+ )
+ }
+
+ var searchQuery: String? = null
+ private val activeFilters = HashMap()
+
+ var tags: MutableList = mutableListOf()
+
+ fun howMany(type: TaskType?): Int {
+ return this.tags.size + if (isTaskFilterActive(type)) 1 else 0
+ }
+
+ private fun isTaskFilterActive(type: TaskType?): Boolean {
+ if (activeFilters[type] == null) {
+ return false
+ }
+ return if (TaskType.TODO == type) {
+ Task.FILTER_ACTIVE != activeFilters[type]
+ } else {
+ Task.FILTER_ALL != activeFilters[type]
+ }
+ }
+
+ fun filter(tasks: List): List {
+ if (tasks.isEmpty()) {
+ return tasks
+ }
+ val filtered = ArrayList()
+ var activeFilter: String? = null
+ if (activeFilters.size > 0) {
+ activeFilter = activeFilters[tasks[0].type]
+ }
+ for (task in tasks) {
+ if (isFiltered(task, activeFilter)) {
+ filtered.add(task)
+ }
+ }
+
+ return filtered
+ }
+
+ private fun isFiltered(task: Task, activeFilter: String?): Boolean {
+ if (!task.containsAllTagIds(tags)) {
+ return false
+ }
+ return if (activeFilter != null && activeFilter != Task.FILTER_ALL) {
+ when (activeFilter) {
+ Task.FILTER_ACTIVE -> if (task.type == TaskType.DAILY) {
+ task.isDisplayedActive
+ } else {
+ !task.completed
+ }
+ Task.FILTER_GRAY -> task.completed || !task.isDisplayedActive
+ Task.FILTER_WEAK -> task.value < 1
+ Task.FILTER_STRONG -> task.value >= 1
+ Task.FILTER_DATED -> task.dueDate != null
+ Task.FILTER_COMPLETED -> task.completed
+ else -> true
+ }
+ } else {
+ true
+ }
+ }
+
+ fun setActiveFilter(type: TaskType, activeFilter: String) {
+ activeFilters[type] = activeFilter
+ }
+
+ fun getActiveFilter(type: TaskType?): String? {
+ return if (activeFilters.containsKey(type)) {
+ activeFilters[type]
+ } else {
+ null
+ }
+ }
+
+ fun createQuery(unfilteredData: OrderedRealmCollection): RealmQuery? {
+ if (!unfilteredData.isValid) {
+ return null
+ }
+ var query = unfilteredData.where()
+
+ if (unfilteredData.size != 0) {
+ val taskType = unfilteredData[0].type
+ val activeFilter = getActiveFilter(taskType)
+
+ if (tags.size > 0) {
+ query = query.`in`("tags.id", tags.toTypedArray())
+ }
+ if (searchQuery?.isNotEmpty() == true) {
+ query = query
+ .beginGroup()
+ .contains("text", searchQuery ?: "", Case.INSENSITIVE)
+ .or()
+ .contains("notes", searchQuery ?: "", Case.INSENSITIVE)
+ .endGroup()
+ }
+ if (activeFilter != null && activeFilter != Task.FILTER_ALL) {
+ when (activeFilter) {
+ Task.FILTER_ACTIVE -> query = if (TaskType.DAILY == taskType) {
+ query.equalTo("completed", false).equalTo("isDue", true)
+ } else {
+ query.equalTo("completed", false)
+ }
+ Task.FILTER_GRAY -> query = query.equalTo("completed", true).or().equalTo("isDue", false)
+ Task.FILTER_WEAK -> query = query.lessThan("value", 1.0)
+ Task.FILTER_STRONG -> query = query.greaterThanOrEqualTo("value", 1.0)
+ Task.FILTER_DATED -> query = query.isNotNull("dueDate").equalTo("completed", false).sort("dueDate")
+ Task.FILTER_COMPLETED -> query = query.equalTo("completed", true)
+ }
+ }
+ if (activeFilter != Task.FILTER_DATED) {
+ query = query.sort("position", Sort.ASCENDING, "dateCreated", Sort.DESCENDING)
+ }
+ }
+ return query
+ }
+}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/CurrencyView.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/CurrencyView.kt
index a39781901..88fe54100 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/CurrencyView.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/CurrencyView.kt
@@ -1,5 +1,6 @@
package com.habitrpg.android.habitica.ui.views
+import android.animation.ValueAnimator
import android.content.Context
import android.graphics.Bitmap
import android.graphics.drawable.BitmapDrawable
@@ -7,6 +8,7 @@ import android.util.AttributeSet
import android.util.TypedValue
import android.view.Gravity
import android.view.View
+import androidx.core.animation.doOnEnd
import androidx.core.content.ContextCompat
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.extensions.isUsingNightModeResources
@@ -40,6 +42,7 @@ class CurrencyView : androidx.appcompat.widget.AppCompatTextView {
} catch (_: ArrayIndexOutOfBoundsException) {
!context.isUsingNightModeResources()
}
+ currency = attributes?.getString(R.styleable.CurrencyView_currency)
visibility = GONE
}
@@ -105,13 +108,38 @@ class CurrencyView : androidx.appcompat.widget.AppCompatTextView {
}
}
+ var minForAbbrevation = 0
+ var decimals = 2
+ var animationDuration = 500L
+ var animationDelay = 0L
+
+ private fun update(value: Double) {
+ text = NumberAbbreviator.abbreviate(context, value, decimals, minForAbbrevation = minForAbbrevation)
+ }
+
+ private fun endUpdate() {
+ contentDescription = "$text $currencyContentDescription"
+ updateVisibility()
+ }
+
var value = 0.0
set(value) {
+ if (text.isEmpty() || animationDuration == 0L) {
+ update(value)
+ endUpdate()
+ } else {
+ val animator = ValueAnimator.ofFloat(field.toFloat(), value.toFloat())
+ animator.duration = animationDuration
+ animator.startDelay = animationDelay
+ animator.addUpdateListener {
+ update((it.animatedValue as Float).toDouble())
+ }
+ animator.doOnEnd {
+ endUpdate()
+ }
+ animator.start()
+ }
field = value
- val abbreviatedValue = NumberAbbreviator.abbreviate(context, value)
- text = abbreviatedValue
- contentDescription = "$abbreviatedValue $currencyContentDescription"
- updateVisibility()
}
var isLocked = false
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/HabiticaListPreference.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/HabiticaListPreference.kt
new file mode 100644
index 000000000..4648494db
--- /dev/null
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/HabiticaListPreference.kt
@@ -0,0 +1,43 @@
+package com.habitrpg.android.habitica.ui.views
+
+import android.app.AlertDialog
+import android.content.Context
+import android.util.AttributeSet
+import android.widget.TextView
+import androidx.preference.ListPreference
+import com.habitrpg.android.habitica.R
+import com.habitrpg.android.habitica.extensions.setScaledPadding
+
+class HabiticaListPreference: ListPreference {
+ constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) :
+ super(context, attrs, defStyleAttr, defStyleRes)
+
+ constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) :
+ super(context,attrs,defStyleAttr)
+
+ constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
+
+ constructor(context: Context) : super(context)
+
+ override fun onClick() {
+ val subtitleText = TextView(context)
+ subtitleText.setText(R.string.cds_subtitle)
+ val builder = AlertDialog.Builder(context).setSingleChoiceItems(entries,getValueIndex())
+ { dialog, index ->
+ if (callChangeListener(entryValues[index].toString())) {
+ setValueIndex(index)
+ }
+ dialog.dismiss()
+ }
+ .setNegativeButton(R.string.cancel) { dialog, _ -> dialog.dismiss() }
+ .setTitle(title)
+
+ val dialog = builder.create()
+ subtitleText.setScaledPadding(context, 24, 0, 24, 8)
+ dialog.listView.addHeaderView(subtitleText)
+ dialog.window?.decorView?.setBackgroundResource(R.color.window_background)
+ dialog.show()
+ }
+
+ private fun getValueIndex() = entryValues.indexOf(value)
+}
\ No newline at end of file
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ValueBar.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ValueBar.kt
index 1c18d420f..d970d12e6 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ValueBar.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ValueBar.kt
@@ -1,5 +1,6 @@
package com.habitrpg.android.habitica.ui.views
+import android.animation.ValueAnimator
import android.content.Context
import android.graphics.Bitmap
import android.graphics.drawable.Drawable
@@ -164,9 +165,23 @@ class ValueBar(context: Context, attrs: AttributeSet?) : FrameLayout(context, at
binding.descriptionTextView.setTextColor(textColor)
}
+ var animationDuration = 500L
+ var animationDelay = 0L
+
fun set(value: Double, valueMax: Double) {
if (currentValue != value || maxValue != valueMax) {
- currentValue = value
+ if (animationDuration == 0L || binding.valueTextView.text.isEmpty()) {
+ currentValue = value
+ } else {
+ val animator = ValueAnimator.ofInt(currentValue.toInt(), value.toInt())
+ animator.duration = animationDuration
+ animator.startDelay = animationDelay
+ animator.addUpdateListener {
+ currentValue = (it.animatedValue as Int).toDouble()
+ updateBar()
+ }
+ animator.start()
+ }
maxValue = valueMax
updateBar()
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ads/AdButton.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ads/AdButton.kt
new file mode 100644
index 000000000..6f2ad0075
--- /dev/null
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ads/AdButton.kt
@@ -0,0 +1,116 @@
+package com.habitrpg.android.habitica.ui.views.ads
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.Gravity
+import android.widget.LinearLayout
+import androidx.core.content.ContextCompat
+import androidx.lifecycle.LifecycleCoroutineScope
+import com.habitrpg.android.habitica.R
+import com.habitrpg.android.habitica.databinding.AdButtonBinding
+import com.habitrpg.android.habitica.extensions.getMinuteOrSeconds
+import com.habitrpg.android.habitica.extensions.getShortRemainingString
+import com.habitrpg.android.habitica.extensions.layoutInflater
+import com.habitrpg.android.habitica.helpers.AdHandler
+import com.habitrpg.android.habitica.helpers.AdType
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+import java.util.Date
+import kotlin.time.DurationUnit
+import kotlin.time.toDuration
+
+class AdButton @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null
+) : LinearLayout(context, attrs) {
+ var state: State = State.EMPTY
+ set(value) {
+ field = value
+ updateViews()
+ }
+
+ enum class State {
+ EMPTY,
+ READY,
+ LOADING,
+ UNAVAILABLE
+ }
+
+ private var updateJob: Job? = null
+ private var nextAdDate: Date? = null
+ private val binding = AdButtonBinding.inflate(context.layoutInflater, this)
+
+ var text: String = ""
+ set(value) {
+ field = value
+ updateViews()
+ }
+
+ init {
+ context.theme?.obtainStyledAttributes(
+ attrs,
+ R.styleable.AdButton,
+ 0, 0
+ )?.let { attributes ->
+ text = attributes.getString(R.styleable.AdButton_text) ?: ""
+ binding.currencyView.currency = attributes.getString(R.styleable.AdButton_currency)
+ }
+ binding.textView.setTextColor(ContextCompat.getColor(context, R.color.white))
+ binding.currencyView.setTextColor(ContextCompat.getColor(context, R.color.white))
+ binding.currencyView.value = 0.0
+ gravity = Gravity.CENTER
+ state = State.EMPTY
+ }
+
+ private fun updateViews() {
+ when (state) {
+ State.READY -> {
+ binding.loadingIndicator.visibility = GONE
+ binding.textView.text = text
+ binding.textView.alpha = 1.0f
+ binding.textView.visibility = VISIBLE
+ binding.currencyView.visibility = VISIBLE
+ setBackgroundResource(R.drawable.ad_button_background)
+ }
+ State.UNAVAILABLE -> {
+ binding.loadingIndicator.visibility = GONE
+ binding.textView.text = context.getString(R.string.available_in, nextAdDate?.getShortRemainingString() ?: "")
+ binding.textView.alpha = 0.75f
+ binding.textView.visibility = VISIBLE
+ binding.currencyView.visibility = GONE
+ setBackgroundResource(R.drawable.ad_button_background_disabled)
+ }
+ State.EMPTY -> {
+ binding.loadingIndicator.visibility = GONE
+ binding.textView.visibility = GONE
+ binding.currencyView.visibility = GONE
+ }
+ State.LOADING -> {
+ binding.loadingIndicator.visibility = VISIBLE
+ binding.textView.visibility = GONE
+ binding.currencyView.visibility = GONE
+ }
+ }
+ isEnabled = state == State.READY
+ }
+
+ fun updateForAdType(type: AdType, lifecycleScope: LifecycleCoroutineScope) {
+ if (updateJob?.isActive == true) {
+ updateJob?.cancel()
+ }
+ nextAdDate = AdHandler.nextAdAllowedDate(type)
+ if (nextAdDate?.after(Date()) == true) {
+ updateJob = lifecycleScope.launch(Dispatchers.Main) {
+ while (nextAdDate?.after(Date()) == true) {
+ val remaining = ((nextAdDate?.time ?: 0L) - Date().time).toDuration(DurationUnit.MILLISECONDS)
+ state = if (remaining.isNegative()) State.READY else State.UNAVAILABLE
+ updateViews()
+ delay(1.toDuration(remaining.getMinuteOrSeconds()))
+ }
+ state = State.READY
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/dialogs/HabiticaBottomDialog.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/dialogs/HabiticaBottomDialog.kt
new file mode 100644
index 000000000..3931e8cef
--- /dev/null
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/dialogs/HabiticaBottomDialog.kt
@@ -0,0 +1,31 @@
+package com.habitrpg.android.habitica.ui.views.dialogs
+
+import android.content.Context
+import android.view.View
+import com.google.android.material.bottomsheet.BottomSheetDialog
+import com.habitrpg.android.habitica.R
+import com.habitrpg.android.habitica.databinding.BottomSheetWrapperBinding
+
+open class HabiticaBottomSheetDialog(context: Context) : BottomSheetDialog(context, R.style.SheetDialog) {
+ private val wrapperBinding = BottomSheetWrapperBinding.inflate(layoutInflater)
+
+ init {
+ behavior.peekHeight = context.resources.displayMetrics.heightPixels / 2
+ }
+
+ var grabberVisibility: Int
+ get() = wrapperBinding.grabber.visibility
+ set(value) {
+ wrapperBinding.grabber.visibility = value
+ }
+
+ override fun setContentView(view: View) {
+ wrapperBinding.container.addView(view)
+ super.setContentView(wrapperBinding.root)
+ }
+
+ override fun setContentView(layoutResId: Int) {
+ layoutInflater.inflate(layoutResId, wrapperBinding.container)
+ super.setContentView(wrapperBinding.root)
+ }
+}
\ No newline at end of file
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/shops/PurchaseDialog.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/shops/PurchaseDialog.kt
index f6ce245f7..be0f8ae14 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/shops/PurchaseDialog.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/shops/PurchaseDialog.kt
@@ -27,6 +27,8 @@ import com.habitrpg.android.habitica.models.shops.Shop
import com.habitrpg.android.habitica.models.shops.ShopItem
import com.habitrpg.android.habitica.models.user.OwnedItem
import com.habitrpg.android.habitica.models.user.User
+import com.habitrpg.android.habitica.ui.activities.ArmoireActivityArgs
+import com.habitrpg.android.habitica.ui.activities.ArmoireActivityDirections
import com.habitrpg.android.habitica.ui.views.CurrencyView
import com.habitrpg.android.habitica.ui.views.CurrencyViews
import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
@@ -357,12 +359,10 @@ class PurchaseDialog(context: Context, component: UserComponent?, val item: Shop
return
} else if ("gold" == shopItem.currency && "gem" != shopItem.key) {
observable = inventoryRepository.buyItem(user, shopItem.key, shopItem.value.toDouble(), quantity).map { buyResponse ->
- if (shopItem.key == "armoire") {
- snackbarText[0] = when {
- buyResponse.armoire["type"] == "gear" -> context.getString(R.string.armoireEquipment, buyResponse.armoire["dropText"])
- buyResponse.armoire["type"] == "food" -> context.getString(R.string.armoireFood, buyResponse.armoire["dropArticle"] ?: "", buyResponse.armoire["dropText"])
- else -> context.getString(R.string.armoireExp)
- }
+ if (shopItem.key == "armoire" && configManager.enableNewArmoire()) {
+ MainNavigationController.navigate(R.id.armoireActivity, ArmoireActivityDirections.openArmoireActivity(buyResponse.armoire["type"] ?: "",
+ buyResponse.armoire["dropText"] ?: "",
+ buyResponse.armoire["dropKey"] ?: "").arguments)
}
buyResponse
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/tasks/TaskFilterDialog.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/tasks/TaskFilterDialog.kt
index 876b0374a..3b540e446 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/tasks/TaskFilterDialog.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/tasks/TaskFilterDialog.kt
@@ -12,9 +12,7 @@ import android.widget.Button
import android.widget.EditText
import android.widget.ImageButton
import android.widget.LinearLayout
-import android.widget.RadioButton
import android.widget.RadioGroup
-import android.widget.TextView
import androidx.annotation.IdRes
import androidx.appcompat.widget.AppCompatCheckBox
import androidx.core.content.ContextCompat
@@ -22,32 +20,24 @@ import androidx.core.widget.CompoundButtonCompat
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.TagRepository
+import com.habitrpg.android.habitica.databinding.DialogTaskFilterBinding
import com.habitrpg.android.habitica.extensions.OnChangeTextWatcher
import com.habitrpg.android.habitica.extensions.getThemeColor
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.models.Tag
import com.habitrpg.android.habitica.models.tasks.Task
import com.habitrpg.android.habitica.models.tasks.TaskType
-import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
+import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaBottomSheetDialog
import io.reactivex.rxjava3.core.Observable
import java.util.UUID
import javax.inject.Inject
-class TaskFilterDialog(context: Context, component: UserComponent?) : HabiticaAlertDialog(context), RadioGroup.OnCheckedChangeListener {
-
- private var clearButton: Button
+class TaskFilterDialog(context: Context, component: UserComponent?) : HabiticaBottomSheetDialog(context), RadioGroup.OnCheckedChangeListener {
+ private val binding = DialogTaskFilterBinding.inflate(layoutInflater)
@Inject
lateinit var repository: TagRepository
- private var taskTypeTitle: TextView
- private var taskFilters: RadioGroup
- private var allTaskFilter: RadioButton
- private var secondTaskFilter: RadioButton
- private var thirdTaskFilter: RadioButton
- private var tagsEditButton: Button
- private var tagsList: LinearLayout
-
private var taskType: TaskType? = null
private var listener: OnFilterCompletedListener? = null
@@ -65,22 +55,12 @@ class TaskFilterDialog(context: Context, component: UserComponent?) : HabiticaAl
component?.inject(this)
addIcon = ContextCompat.getDrawable(context, R.drawable.ic_add_purple_300_36dp)
- val inflater = LayoutInflater.from(context)
- val view = inflater.inflate(R.layout.dialog_task_filter, null)
setTitle(R.string.filters)
- this.setAdditionalContentView(view)
+ this.setContentView(binding.root)
- taskTypeTitle = view.findViewById(R.id.task_type_title)
- taskFilters = view.findViewById(R.id.task_filter_wrapper)
- allTaskFilter = view.findViewById(R.id.all_task_filter)
- secondTaskFilter = view.findViewById(R.id.second_task_filter)
- thirdTaskFilter = view.findViewById(R.id.third_task_filter)
- tagsEditButton = view.findViewById(R.id.tag_edit_button)
- tagsList = view.findViewById(R.id.tags_list)
+ binding.taskFilterWrapper.setOnCheckedChangeListener(this)
- taskFilters.setOnCheckedChangeListener(this)
-
- clearButton = addButton(R.string.clear, false, false, false) { _, _ ->
+ binding.clearButton.setOnClickListener {
if (isEditing) {
stopEditing()
}
@@ -88,16 +68,12 @@ class TaskFilterDialog(context: Context, component: UserComponent?) : HabiticaAl
setActiveTags(null)
}
- addButton(R.string.done, false) { _, _ ->
- if (isEditing) {
- stopEditing()
- }
- listener?.onFilterCompleted(filterType, activeTags)
- this.dismiss()
- }
- buttonAxis = LinearLayout.HORIZONTAL
+ binding.tagEditButton.setOnClickListener { editButtonClicked() }
+ }
- tagsEditButton.setOnClickListener { editButtonClicked() }
+ override fun dismiss() {
+ listener?.onFilterCompleted(filterType, activeTags)
+ super.dismiss()
}
override fun show() {
@@ -111,7 +87,7 @@ class TaskFilterDialog(context: Context, component: UserComponent?) : HabiticaAl
}
private fun createTagViews() {
- tagsList.removeAllViews()
+ binding.tagsList.removeAllViews()
val colorStateList = ColorStateList(
arrayOf(
intArrayOf(-android.R.attr.state_checked), // disabled
@@ -149,7 +125,7 @@ class TaskFilterDialog(context: Context, component: UserComponent?) : HabiticaAl
}
filtersChanged()
}
- tagsList.addView(tagCheckbox)
+ binding.tagsList.addView(tagCheckbox)
}
createAddTagButton()
}
@@ -164,7 +140,7 @@ class TaskFilterDialog(context: Context, component: UserComponent?) : HabiticaAl
}
button.setBackgroundResource(R.drawable.layout_rounded_bg_lighter_gray)
button.setTextColor(ContextCompat.getColor(context, R.color.text_secondary))
- tagsList.addView(button)
+ binding.tagsList.addView(button)
}
private fun createTag() {
@@ -177,17 +153,17 @@ class TaskFilterDialog(context: Context, component: UserComponent?) : HabiticaAl
private fun startEditing() {
isEditing = true
- tagsList.removeAllViews()
+ binding.tagsList.removeAllViews()
createTagEditViews()
- tagsEditButton.setText(R.string.done)
+ binding.tagEditButton.setText(R.string.done)
this.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE)
}
private fun stopEditing() {
isEditing = false
- tagsList.removeAllViews()
+ binding.tagsList.removeAllViews()
createTagViews()
- tagsEditButton.setText(R.string.edit_tag_btn_edit)
+ binding.tagEditButton.setText(R.string.edit_tag_btn_edit)
this.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN)
repository.updateTags(editedTags.values).toObservable().flatMap { tags -> Observable.fromIterable(tags) }.subscribe({ tag -> editedTags.remove(tag.id) }, RxErrorHandler.handleEmptyError())
repository.createTags(createdTags.values).toObservable().flatMap { tags -> Observable.fromIterable(tags) }.subscribe({ tag -> createdTags.remove(tag.id) }, RxErrorHandler.handleEmptyError())
@@ -204,7 +180,7 @@ class TaskFilterDialog(context: Context, component: UserComponent?) : HabiticaAl
}
private fun createTagEditView(inflater: LayoutInflater, index: Int, tag: Tag) {
- val wrapper = inflater.inflate(R.layout.edit_tag_item, tagsList, false) as? LinearLayout
+ val wrapper = inflater.inflate(R.layout.edit_tag_item, binding.tagsList, false) as? LinearLayout
val tagEditText = wrapper?.findViewById(R.id.edit_text) as? EditText
tagEditText?.setText(tag.name)
tagEditText?.addTextChangedListener(
@@ -233,9 +209,9 @@ class TaskFilterDialog(context: Context, component: UserComponent?) : HabiticaAl
}
activeTags.remove(tag.id)
tags.remove(tag)
- tagsList.removeView(wrapper)
+ binding.tagsList.removeView(wrapper)
}
- tagsList.addView(wrapper)
+ binding.tagsList.addView(wrapper)
}
fun setActiveTags(tagIds: MutableList?) {
@@ -244,13 +220,13 @@ class TaskFilterDialog(context: Context, component: UserComponent?) : HabiticaAl
} else {
this.activeTags = tagIds
}
- for (index in 0 until tagsList.childCount - 1) {
- (tagsList.getChildAt(index) as? AppCompatCheckBox)?.isChecked = false
+ for (index in 0 until binding.tagsList.childCount - 1) {
+ (binding.tagsList.getChildAt(index) as? AppCompatCheckBox)?.isChecked = false
}
for (tagId in this.activeTags) {
val index = indexForId(tagId)
if (index >= 0) {
- (tagsList.getChildAt(index) as? AppCompatCheckBox)?.isChecked = true
+ (binding.tagsList.getChildAt(index) as? AppCompatCheckBox)?.isChecked = true
}
}
filtersChanged()
@@ -269,22 +245,22 @@ class TaskFilterDialog(context: Context, component: UserComponent?) : HabiticaAl
this.taskType = taskType
when (taskType) {
TaskType.HABIT -> {
- taskTypeTitle.setText(R.string.habits)
- allTaskFilter.setText(R.string.all)
- secondTaskFilter.setText(R.string.weak)
- thirdTaskFilter.setText(R.string.strong)
+ binding.taskTypeTitle.setText(R.string.habits)
+ binding.allTaskFilter.setText(R.string.all)
+ binding.secondTaskFilter.setText(R.string.weak)
+ binding.thirdTaskFilter.setText(R.string.strong)
}
TaskType.DAILY -> {
- taskTypeTitle.setText(R.string.dailies)
- allTaskFilter.setText(R.string.all)
- secondTaskFilter.setText(R.string.due)
- thirdTaskFilter.setText(R.string.gray)
+ binding.taskTypeTitle.setText(R.string.dailies)
+ binding.allTaskFilter.setText(R.string.all)
+ binding.secondTaskFilter.setText(R.string.due)
+ binding.thirdTaskFilter.setText(R.string.gray)
}
TaskType.TODO -> {
- taskTypeTitle.setText(R.string.todos)
- allTaskFilter.setText(R.string.active)
- secondTaskFilter.setText(R.string.dated)
- thirdTaskFilter.setText(R.string.completed)
+ binding.taskTypeTitle.setText(R.string.todos)
+ binding.allTaskFilter.setText(R.string.active)
+ binding.secondTaskFilter.setText(R.string.dated)
+ binding.thirdTaskFilter.setText(R.string.completed)
}
}
setActiveFilter(activeFilter)
@@ -307,7 +283,7 @@ class TaskFilterDialog(context: Context, component: UserComponent?) : HabiticaAl
}
}
}
- taskFilters.check(checkedId)
+ binding.taskFilterWrapper.check(checkedId)
filtersChanged()
}
@@ -345,9 +321,9 @@ class TaskFilterDialog(context: Context, component: UserComponent?) : HabiticaAl
}
private fun filtersChanged() {
- clearButton.isEnabled = hasActiveFilters()
- clearButton.setTextColor(
- if (clearButton.isEnabled) {
+ binding.clearButton.isEnabled = hasActiveFilters()
+ binding.clearButton.setTextColor(
+ if (binding.clearButton.isEnabled) {
context.getThemeColor(R.attr.colorAccent)
} else {
ContextCompat.getColor(context, R.color.text_dimmed)
@@ -356,7 +332,7 @@ class TaskFilterDialog(context: Context, component: UserComponent?) : HabiticaAl
}
private fun hasActiveFilters(): Boolean {
- return taskFilters.checkedRadioButtonId != R.id.all_task_filter || activeTags.size > 0
+ return binding.taskFilterWrapper.checkedRadioButtonId != R.id.all_task_filter || activeTags.size > 0
}
fun setListener(listener: OnFilterCompletedListener) {
diff --git a/build.gradle b/build.gradle
index 1d36d355a..346180ec6 100644
--- a/build.gradle
+++ b/build.gradle
@@ -8,7 +8,7 @@ buildscript {
maven { url "https://plugins.gradle.org/m2/" }
}
dependencies {
- classpath 'com.android.tools.build:gradle:7.0.2'
+ classpath 'com.android.tools.build:gradle:7.1.3'
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
classpath 'com.google.gms:google-services:4.3.10'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.8.1'
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index f0de54789..67d60d5fa 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Sun Apr 17 15:16:40 EDT 2022
+#Fri Apr 22 14:22:42 CEST 2022
distributionBase=GRADLE_USER_HOME
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME