Merge branch 'version/4.1' into Hafiz/change-class-update
|
|
@ -83,7 +83,7 @@ GEM
|
|||
xcpretty-travis-formatter (>= 0.0.3)
|
||||
fastlane-plugin-properties (1.1.2)
|
||||
java-properties
|
||||
fastlane-plugin-semantic_release (1.14.1)
|
||||
fastlane-plugin-semantic_release (1.18.0)
|
||||
fastlane-plugin-versioning_android (0.1.0)
|
||||
gh_inspector (1.1.3)
|
||||
google-api-client (0.38.0)
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
|
||||
<uses-permission android:name="com.android.vending.BILLING" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
|
||||
<uses-permission android:name="com.google.android.gms.permission.AD_ID"/>
|
||||
|
||||
<application
|
||||
android:name=".HabiticaApplication"
|
||||
|
|
@ -29,7 +30,6 @@
|
|||
android:value="@string/application_ad_id"/>
|
||||
<activity
|
||||
android:name=".ui.activities.MainActivity"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/LaunchAppTheme"
|
||||
android:windowSoftInputMode="stateHidden|adjustResize"
|
||||
android:configChanges="screenSize | smallestScreenSize | screenLayout | orientation"
|
||||
|
|
@ -256,6 +256,12 @@
|
|||
<action android:name="com.google.firebase.MESSAGING_EVENT"/>
|
||||
</intent-filter>
|
||||
</service>
|
||||
<meta-data
|
||||
android:name="com.google.firebase.messaging.default_notification_icon"
|
||||
android:resource="@drawable/ic_gryphon_white" />
|
||||
<meta-data
|
||||
android:name="com.google.firebase.messaging.default_notification_color"
|
||||
android:resource="@color/brand_300" />
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="@string/content_provider"
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ dependencies {
|
|||
compileOnly 'javax.annotation:javax.annotation-api:1.3.2'
|
||||
//App Compatibility and Material Design
|
||||
implementation "androidx.appcompat:appcompat:$appcompat_version"
|
||||
implementation 'com.google.android.material:material:1.6.1'
|
||||
implementation 'com.google.android.material:material:1.7.0'
|
||||
implementation "androidx.recyclerview:recyclerview:$recyclerview_version"
|
||||
implementation "androidx.preference:preference-ktx:$preferences_version"
|
||||
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
|
||||
|
|
@ -79,7 +79,7 @@ dependencies {
|
|||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
||||
androidTestImplementation 'androidx.test:runner:1.4.0'
|
||||
androidTestImplementation 'androidx.test:rules:1.4.0'
|
||||
debugImplementation 'androidx.fragment:fragment-testing:1.5.2'
|
||||
debugImplementation 'androidx.fragment:fragment-testing:1.5.4'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
||||
androidTestImplementation 'androidx.test:core-ktx:1.4.0'
|
||||
androidTestImplementation 'androidx.test.ext:junit-ktx:1.1.3'
|
||||
|
|
@ -98,7 +98,7 @@ dependencies {
|
|||
implementation 'com.google.firebase:firebase-messaging-ktx'
|
||||
implementation 'com.google.firebase:firebase-config-ktx'
|
||||
implementation 'com.google.firebase:firebase-perf-ktx'
|
||||
implementation 'com.google.android.gms:play-services-ads:21.1.0'
|
||||
implementation 'com.google.android.gms:play-services-ads:21.3.0'
|
||||
implementation "com.google.android.gms:play-services-auth:$play_auth_version"
|
||||
implementation 'com.google.android.flexbox:flexbox:3.0.0'
|
||||
implementation "com.google.android.gms:play-services-wearable:$play_wearables_version"
|
||||
|
|
@ -107,12 +107,20 @@ dependencies {
|
|||
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
|
||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
|
||||
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
|
||||
implementation 'androidx.navigation:navigation-fragment-ktx:2.5.1'
|
||||
implementation 'androidx.navigation:navigation-ui-ktx:2.5.1'
|
||||
implementation "androidx.fragment:fragment-ktx:1.5.2"
|
||||
implementation "androidx.navigation:navigation-fragment-ktx:$navigation_version"
|
||||
implementation "androidx.navigation:navigation-ui-ktx:$navigation_version"
|
||||
implementation "androidx.fragment:fragment-ktx:1.5.4"
|
||||
implementation "androidx.paging:paging-runtime-ktx:3.1.1"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
|
||||
implementation "com.google.android.material:compose-theme-adapter:1.1.21"
|
||||
|
||||
implementation 'androidx.activity:activity-compose:1.6.1'
|
||||
implementation "androidx.compose.runtime:runtime-livedata:$compose_version"
|
||||
implementation "androidx.compose.material:material:$compose_version"
|
||||
implementation "androidx.compose.animation:animation:$compose_version"
|
||||
implementation "androidx.compose.ui:ui-tooling:$compose_version"
|
||||
implementation "androidx.lifecycle:lifecycle-viewmodel-compose:$lifecycle_version"
|
||||
|
||||
implementation 'com.willowtreeapps:signinwithapplebutton:0.3'
|
||||
|
||||
|
|
@ -139,7 +147,7 @@ android {
|
|||
}
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 21
|
||||
minSdkVersion min_sdk
|
||||
applicationId "com.habitrpg.android.habitica"
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
buildConfigField "String", "STORE", "\"google\""
|
||||
|
|
@ -160,6 +168,11 @@ android {
|
|||
|
||||
buildFeatures {
|
||||
viewBinding true
|
||||
compose true
|
||||
}
|
||||
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion = "1.3.2"
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
|
|
|
|||
BIN
Habitica/res/drawable-hdpi/assign.png
Normal file
|
After Width: | Height: | Size: 1,012 B |
BIN
Habitica/res/drawable-hdpi/fall_gems_21.png
Normal file
|
After Width: | Height: | Size: 6.7 KiB |
BIN
Habitica/res/drawable-hdpi/fall_gems_4.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
Habitica/res/drawable-hdpi/fall_gems_42.png
Normal file
|
After Width: | Height: | Size: 8.7 KiB |
BIN
Habitica/res/drawable-hdpi/fall_gems_84.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
Habitica/res/drawable-hdpi/spooky_gems_21.png
Normal file
|
After Width: | Height: | Size: 6.8 KiB |
BIN
Habitica/res/drawable-hdpi/spooky_gems_4.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
Habitica/res/drawable-hdpi/spooky_gems_42.png
Normal file
|
After Width: | Height: | Size: 8.7 KiB |
BIN
Habitica/res/drawable-hdpi/spooky_gems_84.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
Habitica/res/drawable-mdpi/assign.png
Normal file
|
After Width: | Height: | Size: 691 B |
|
Before Width: | Height: | Size: 109 B |
BIN
Habitica/res/drawable-night-hdpi/assign.png
Normal file
|
After Width: | Height: | Size: 1 KiB |
BIN
Habitica/res/drawable-night-mdpi/assign.png
Normal file
|
After Width: | Height: | Size: 695 B |
BIN
Habitica/res/drawable-night-xhdpi/assign.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
Habitica/res/drawable-night-xxhdpi/assign.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
Habitica/res/drawable-xhdpi/assign.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
Habitica/res/drawable-xhdpi/fall_gems_21.png
Normal file
|
After Width: | Height: | Size: 8.6 KiB |
BIN
Habitica/res/drawable-xhdpi/fall_gems_4.png
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
BIN
Habitica/res/drawable-xhdpi/fall_gems_42.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
Habitica/res/drawable-xhdpi/fall_gems_84.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
Habitica/res/drawable-xhdpi/spooky_gems_21.png
Normal file
|
After Width: | Height: | Size: 8.7 KiB |
BIN
Habitica/res/drawable-xhdpi/spooky_gems_4.png
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
BIN
Habitica/res/drawable-xhdpi/spooky_gems_42.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
Habitica/res/drawable-xhdpi/spooky_gems_84.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
Habitica/res/drawable-xxhdpi/assign.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
Habitica/res/drawable-xxhdpi/fall_gems_21.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
Habitica/res/drawable-xxhdpi/fall_gems_4.png
Normal file
|
After Width: | Height: | Size: 7.6 KiB |
BIN
Habitica/res/drawable-xxhdpi/fall_gems_42.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
Habitica/res/drawable-xxhdpi/fall_gems_84.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
Habitica/res/drawable-xxhdpi/spooky_gems_21.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
Habitica/res/drawable-xxhdpi/spooky_gems_4.png
Normal file
|
After Width: | Height: | Size: 7.8 KiB |
BIN
Habitica/res/drawable-xxhdpi/spooky_gems_42.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
Habitica/res/drawable-xxhdpi/spooky_gems_84.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
Habitica/res/drawable-xxxhdpi/fall_gems_21.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
Habitica/res/drawable-xxxhdpi/fall_gems_4.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
Habitica/res/drawable-xxxhdpi/fall_gems_42.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
Habitica/res/drawable-xxxhdpi/fall_gems_84.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
Habitica/res/drawable-xxxhdpi/spooky_gems_21.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
Habitica/res/drawable-xxxhdpi/spooky_gems_4.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
Habitica/res/drawable-xxxhdpi/spooky_gems_42.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
Habitica/res/drawable-xxxhdpi/spooky_gems_84.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
5
Habitica/res/drawable/icon_chat.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<vector android:autoMirrored="true" android:height="24dp"
|
||||
android:tint="#FFFFFF" android:viewportHeight="24"
|
||||
android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M20,2L4,2c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM6,9h12v2L6,11L6,9zM14,14L6,14v-2h8v2zM18,8L6,8L6,6h12v2z"/>
|
||||
</vector>
|
||||
|
|
@ -28,16 +28,15 @@
|
|||
app:expandedTitleMarginStart="0dp"
|
||||
app:layout_scrollFlags="scroll|exitUntilCollapsed">
|
||||
|
||||
<include
|
||||
android:id="@+id/avatar_with_bars"
|
||||
layout="@layout/avatar_with_bars"
|
||||
<androidx.compose.ui.platform.ComposeView
|
||||
android:id="@+id/header_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="?attr/actionBarSize"
|
||||
android:layout_marginEnd="@dimen/header_border_spacing"
|
||||
android:layout_marginStart="@dimen/header_border_spacing"
|
||||
android:layout_marginBottom="@dimen/spacing_medium"
|
||||
app:layout_collapseMode="parallax" />
|
||||
app:layout_collapseMode="parallax"/>
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
|
|
|
|||
|
|
@ -179,6 +179,8 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="11dp"
|
||||
android:lineSpacingExtra="4sp"
|
||||
android:background="@drawable/layout_rounded_bg_window"
|
||||
android:padding="@dimen/spacing_large"
|
||||
tools:text="There’s always more to learn! Maybe it’s a specialized topic you’re interested in, or maybe it’s the experiences of a notable contributor. Read about it and you may win 15 gems!" />
|
||||
</com.habitrpg.android.habitica.ui.views.CollapsibleSectionView>
|
||||
|
||||
|
|
|
|||
4
Habitica/res/layout/fragment_compose.xml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.compose.ui.platform.ComposeView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
10
Habitica/res/layout/fragment_compose_scrolling.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.core.widget.NestedScrollView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<androidx.compose.ui.platform.ComposeView
|
||||
android:id="@+id/compose_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
|
@ -17,6 +17,7 @@
|
|||
android:background="@drawable/g1g1_box"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:layout_marginBottom="@dimen/spacing_large"
|
||||
android:clipChildren="true"
|
||||
android:clipToPadding="true"
|
||||
android:clickable="true"
|
||||
|
|
@ -29,14 +30,16 @@
|
|||
android:scaleType="center"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentBottom="false"/>
|
||||
android:layout_alignParentBottom="false"
|
||||
android:importantForAccessibility="no"/>
|
||||
<ImageView
|
||||
android:id="@+id/promo_banner_right_image"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:scaleType="center"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_alignParentEnd="true"/>
|
||||
android:layout_alignParentEnd="true"
|
||||
android:importantForAccessibility="no"/>
|
||||
<LinearLayout
|
||||
android:layout_centerInParent="true"
|
||||
android:layout_width="wrap_content"
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
android:layout_height="match_parent"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/refreshLayout"
|
||||
android:background="@color/window_background">
|
||||
android:background="@color/content_background">
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
|
@ -101,10 +101,6 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="?attr/colorContentBackground">
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="@color/content_background_offset" />
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
@ -130,11 +126,6 @@
|
|||
style="@style/HabiticaButton.Green"
|
||||
android:layout_marginStart="@dimen/spacing_large"/>
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="@color/content_background_offset" />
|
||||
<LinearLayout
|
||||
android:id="@+id/leader_wrapper"
|
||||
android:layout_width="match_parent"
|
||||
|
|
@ -183,7 +174,9 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:text="@string/inn_description"
|
||||
android:layout_marginTop="@dimen/spacing_large" />
|
||||
android:layout_marginTop="@dimen/spacing_large"
|
||||
android:background="@drawable/layout_rounded_bg_window"
|
||||
android:padding="@dimen/spacing_large"/>
|
||||
</com.habitrpg.android.habitica.ui.views.CollapsibleSectionView>
|
||||
<com.habitrpg.android.habitica.ui.views.CollapsibleSectionView
|
||||
android:layout_width="match_parent"
|
||||
|
|
@ -195,14 +188,11 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:text="@string/inn_description"
|
||||
android:layout_marginTop="@dimen/spacing_large" />
|
||||
android:layout_marginTop="@dimen/spacing_large"
|
||||
android:background="@drawable/layout_rounded_bg_window"
|
||||
android:padding="@dimen/spacing_large" />
|
||||
</com.habitrpg.android.habitica.ui.views.CollapsibleSectionView>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="@color/content_background_offset"
|
||||
android:layout_marginTop="@dimen/spacing_medium"/>
|
||||
</LinearLayout>
|
||||
<Button
|
||||
android:id="@+id/leave_button"
|
||||
|
|
|
|||
|
|
@ -38,9 +38,7 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="?attr/colorContentBackground"
|
||||
android:showDividers="beginning|end|middle"
|
||||
android:divider="@drawable/vertical_divider">
|
||||
android:background="?attr/colorContentBackground">
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
@ -180,7 +178,9 @@
|
|||
android:id="@+id/description_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:text="This is our super awesome party!"/>
|
||||
tools:text="This is our super awesome party!"
|
||||
android:background="@drawable/layout_rounded_bg_window"
|
||||
android:padding="@dimen/spacing_large"/>
|
||||
</LinearLayout>
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
|
|
|
|||
|
|
@ -43,7 +43,9 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/inn_description"
|
||||
android:layout_marginTop="@dimen/spacing_large"/>
|
||||
android:layout_marginTop="@dimen/spacing_large"
|
||||
android:background="@drawable/layout_rounded_bg_window"
|
||||
android:padding="@dimen/spacing_large"/>
|
||||
</com.habitrpg.android.habitica.ui.views.CollapsibleSectionView>
|
||||
<com.habitrpg.android.habitica.ui.views.CollapsibleSectionView
|
||||
android:layout_width="match_parent"
|
||||
|
|
@ -61,7 +63,9 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/guidelines_description"
|
||||
android:layout_marginTop="@dimen/spacing_large"/>
|
||||
android:layout_marginTop="@dimen/spacing_large"
|
||||
android:background="@drawable/layout_rounded_bg_window"
|
||||
android:padding="@dimen/spacing_large"/>
|
||||
</com.habitrpg.android.habitica.ui.views.CollapsibleSectionView>
|
||||
<com.habitrpg.android.habitica.ui.views.CollapsibleSectionView
|
||||
android:layout_width="match_parent"
|
||||
|
|
@ -100,7 +104,9 @@
|
|||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/tiers_descriptions"/>
|
||||
android:text="@string/tiers_descriptions"
|
||||
android:background="@drawable/layout_rounded_bg_window"
|
||||
android:padding="@dimen/spacing_large"/>
|
||||
</com.habitrpg.android.habitica.ui.views.CollapsibleSectionView>
|
||||
</LinearLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
|
@ -5,9 +5,11 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clickable="true"
|
||||
android:background="@drawable/layout_rounded_bg_window"
|
||||
android:padding="@dimen/spacing_large"
|
||||
android:foreground="?android:attr/selectableItemBackground"
|
||||
android:focusable="true">
|
||||
android:focusable="true"
|
||||
android:layout_marginBottom="@dimen/spacing_medium">
|
||||
<com.habitrpg.common.habitica.views.AvatarView
|
||||
android:id="@+id/avatarView"
|
||||
android:layout_width="97dp"
|
||||
|
|
@ -88,7 +90,7 @@
|
|||
android:layout_height="@dimen/bar_size"
|
||||
android:id="@+id/health_bar"
|
||||
app:barForegroundColor="@color/hpColor"
|
||||
app:barBackgroundColor="@color/window_background"/>
|
||||
app:barBackgroundColor="@color/offset_background"/>
|
||||
<TextView
|
||||
android:id="@+id/health_textview"
|
||||
android:layout_width="wrap_content"
|
||||
|
|
@ -106,7 +108,7 @@
|
|||
android:layout_height="@dimen/bar_size"
|
||||
android:id="@+id/experience_bar"
|
||||
app:barForegroundColor="@color/xpColor"
|
||||
app:barBackgroundColor="@color/window_background"/>
|
||||
app:barBackgroundColor="@color/offset_background"/>
|
||||
<TextView
|
||||
android:id="@+id/experience_textview"
|
||||
android:layout_width="wrap_content"
|
||||
|
|
@ -124,7 +126,7 @@
|
|||
android:layout_height="@dimen/bar_size"
|
||||
android:id="@+id/mana_bar"
|
||||
app:barForegroundColor="@color/mpColor"
|
||||
app:barBackgroundColor="@color/window_background"/>
|
||||
app:barBackgroundColor="@color/offset_background"/>
|
||||
<TextView
|
||||
android:id="@+id/mana_textview"
|
||||
android:layout_width="wrap_content"
|
||||
|
|
|
|||
|
|
@ -17,10 +17,6 @@
|
|||
android:contentDescription="@string/gems"
|
||||
android:layout_marginTop="20dp"/>
|
||||
|
||||
<com.habitrpg.android.habitica.ui.views.SparkView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
@ -40,6 +36,7 @@
|
|||
android:layout_marginEnd="2dp"
|
||||
tools:text="21" />
|
||||
<com.habitrpg.android.habitica.ui.views.DayNightTextView
|
||||
android:id="@+id/gem_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:dayTextColor="@color/white"
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@
|
|||
android:paddingBottom="2dp"/>
|
||||
<TextView
|
||||
android:id="@+id/priceLabel"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/yellow_10"
|
||||
android:gravity="center_horizontal"
|
||||
|
|
|
|||
|
|
@ -12,6 +12,17 @@
|
|||
android:paddingBottom="@dimen/task_top_bottom_padding"
|
||||
android:layout_marginEnd="@dimen/task_text_padding"
|
||||
android:layout_marginStart="@dimen/task_text_padding">
|
||||
<TextView
|
||||
android:id="@+id/assigned_textview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/Caption4"
|
||||
android:text="@string/pending_approval"
|
||||
android:textColor="@color/text_ternary"
|
||||
android:drawableStart="@drawable/assign"
|
||||
android:drawablePadding="@dimen/spacing_small"
|
||||
android:layout_marginBottom="2dp"
|
||||
/>
|
||||
<com.habitrpg.android.habitica.ui.views.EllipsisTextView
|
||||
android:id="@+id/checkedTextView"
|
||||
style="@style/Subheader3"
|
||||
|
|
|
|||
|
|
@ -5,11 +5,6 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:parentTag="android.widget.LinearLayout">
|
||||
<View
|
||||
android:id="@+id/separator"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="@color/content_background_offset"/>
|
||||
<LinearLayout
|
||||
android:id="@+id/section_title_view"
|
||||
android:layout_width="match_parent"
|
||||
|
|
|
|||
|
|
@ -2,10 +2,6 @@
|
|||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:context="com.habitrpg.android.habitica.TaskActivity">
|
||||
<item android:id="@+id/action_team_info"
|
||||
android:title="@string/team_information"
|
||||
android:icon="@drawable/team_info_icon"
|
||||
app:showAsAction="collapseActionView|always" />
|
||||
<item android:id="@+id/action_search"
|
||||
android:title="@string/search"
|
||||
android:icon="@drawable/ic_search"
|
||||
|
|
|
|||
|
|
@ -4,16 +4,6 @@
|
|||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/main_nav"
|
||||
app:startDestination="@id/tasksFragment">
|
||||
|
||||
<fragment
|
||||
android:id="@+id/equipmentOverviewFragment"
|
||||
android:name="com.habitrpg.android.habitica.ui.fragments.inventory.equipment.EquipmentOverviewFragment"
|
||||
android:label="@string/sidebar_equipment" >
|
||||
<deepLink app:uri="habitica.com/inventory/equipment" />
|
||||
<action
|
||||
android:id="@+id/openEquipmentDetail"
|
||||
app:destination="@id/equipmentDetailFragment" />
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/equipmentDetailFragment"
|
||||
android:name="com.habitrpg.android.habitica.ui.fragments.inventory.equipment.EquipmentDetailFragment"
|
||||
|
|
@ -44,6 +34,16 @@
|
|||
<deepLink app:uri="habitica.com/tasks" />
|
||||
<deepLink app:uri="habitica.com" />
|
||||
</fragment>
|
||||
<activity
|
||||
android:id="@+id/taskFormActivity"
|
||||
android:name="com.habitrpg.android.habitica.ui.activities.TaskFormActivity">
|
||||
|
||||
</activity>
|
||||
<activity
|
||||
android:id="@+id/taskSummaryActivity"
|
||||
android:name="com.habitrpg.android.habitica.ui.activities.TaskSummaryActivity">
|
||||
|
||||
</activity>
|
||||
<fragment
|
||||
android:id="@+id/partyFragment"
|
||||
android:name="com.habitrpg.android.habitica.ui.fragments.social.party.PartyFragment"
|
||||
|
|
@ -121,8 +121,8 @@
|
|||
android:id="@+id/openAvatarDetail"
|
||||
app:destination="@id/avatarCustomizationFragment" />
|
||||
<action
|
||||
android:id="@+id/openAvatarEquipment"
|
||||
app:destination="@id/avatarEquipmentFragment" />
|
||||
android:id="@+id/openEquipmentDetail"
|
||||
app:destination="@id/equipmentDetailFragment" />
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/itemsFragment"
|
||||
|
|
@ -298,17 +298,6 @@
|
|||
android:name="category"
|
||||
app:argType="string" />
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/avatarEquipmentFragment"
|
||||
android:name="com.habitrpg.android.habitica.ui.fragments.inventory.customization.AvatarEquipmentFragment"
|
||||
android:label="@string/sidebar_avatar" >
|
||||
<argument
|
||||
android:name="type"
|
||||
app:argType="string" />
|
||||
<argument
|
||||
android:name="category"
|
||||
app:argType="string" />
|
||||
</fragment>
|
||||
<activity
|
||||
android:id="@+id/prefsActivity"
|
||||
android:name="com.habitrpg.android.habitica.ui.activities.PrefsActivity"
|
||||
|
|
|
|||
|
|
@ -68,4 +68,5 @@
|
|||
|
||||
|
||||
<color name="widget_background">#2B203A</color>
|
||||
<color name="text_gold">@color/yellow_100</color>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -119,4 +119,5 @@
|
|||
<color name="lightly_tinted_background">@color/brand_700</color>
|
||||
<color name="dialog_background">@color/white</color>
|
||||
<color name="error_banner_background">@color/maroon_5</color>
|
||||
<color name="text_gold">@color/yellow_1</color>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@
|
|||
<dimen name="checkbox_size">24dp</dimen>
|
||||
<dimen name="checkbox_compact_size">20dp</dimen>
|
||||
|
||||
<dimen name="task_top_bottom_padding">10dp</dimen>
|
||||
<dimen name="task_top_bottom_padding">8dp</dimen>
|
||||
<dimen name="task_top_bottom_compact_padding">8dp</dimen>
|
||||
<dimen name="reward_spacing">8dp</dimen>
|
||||
<dimen name="grid_item_margin">6dp</dimen>
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
<string name="sidebar_challenges">Challenges</string>
|
||||
<string name="sidebar_section_inventory">Inventory</string>
|
||||
<string name="sidebar_avatar">Avatar Customization</string>
|
||||
<string name="sidebar_avatar_equipment">Avatar & Equipment</string>
|
||||
<string name="sidebar_equipment">Equipment</string>
|
||||
<string name="sidebar_stable">Pets & Mounts</string>
|
||||
<string name="sidebar_news">News</string>
|
||||
|
|
|
|||
|
|
@ -1255,4 +1255,14 @@
|
|||
<string name="copy_tasks_description">Show assigned and open tasks on your personal task lists</string>
|
||||
<string name="copy_shared_tasks">Copy shared tasks</string>
|
||||
<string name="group_plan_settings">Group Plan Settings</string>
|
||||
<string name="task_summary">Task Summary</string>
|
||||
<plurals name="you_x_others">
|
||||
<item quantity="zero">You</item>
|
||||
<item quantity="one">You, %d other</item>
|
||||
<item quantity="other">You, %d others</item>
|
||||
</plurals>
|
||||
<plurals name="people">
|
||||
<item quantity="one">%d Person</item>
|
||||
<item quantity="other">%d People</item>
|
||||
</plurals>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@
|
|||
<item name="colorPrimaryDistinct">@color/brand_500</item>
|
||||
<item name="colorBadgeBackground">@color/brand_400</item>
|
||||
<item name="taskFormTint">@color/brand_300</item>
|
||||
<item name="colorSecondaryVariant">@color/brand</item>
|
||||
|
||||
<item name="textColorPrimary">@color/text_primary</item>
|
||||
<item name="textColorSecondary">@color/text_secondary</item>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package com.habitrpg.android.habitica
|
|||
import android.content.SharedPreferences
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.habitrpg.android.habitica.api.GSonFactoryCreator
|
||||
import com.habitrpg.common.habitica.api.HostConfig
|
||||
import com.habitrpg.android.habitica.api.MaintenanceApiService
|
||||
import com.habitrpg.android.habitica.data.ApiClient
|
||||
import com.habitrpg.android.habitica.data.ContentRepository
|
||||
|
|
@ -14,9 +13,9 @@ import com.habitrpg.android.habitica.data.TaskRepository
|
|||
import com.habitrpg.android.habitica.data.TutorialRepository
|
||||
import com.habitrpg.android.habitica.data.UserRepository
|
||||
import com.habitrpg.android.habitica.helpers.AppConfigManager
|
||||
import com.habitrpg.android.habitica.helpers.ExceptionHandler
|
||||
import com.habitrpg.android.habitica.helpers.MainNavigationController
|
||||
import com.habitrpg.android.habitica.helpers.NotificationsManager
|
||||
import com.habitrpg.android.habitica.helpers.RxErrorHandler
|
||||
import com.habitrpg.android.habitica.helpers.SoundManager
|
||||
import com.habitrpg.android.habitica.interactors.FeedPetUseCase
|
||||
import com.habitrpg.android.habitica.interactors.HatchPetUseCase
|
||||
|
|
@ -29,22 +28,23 @@ import com.habitrpg.android.habitica.models.inventory.QuestContent
|
|||
import com.habitrpg.android.habitica.models.user.User
|
||||
import com.habitrpg.android.habitica.proxy.AnalyticsManager
|
||||
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
|
||||
import com.habitrpg.common.habitica.api.HostConfig
|
||||
import com.kaspersky.kaspresso.testcases.api.testcase.TestCase
|
||||
import io.mockk.clearAllMocks
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.mockkObject
|
||||
import io.mockk.slot
|
||||
import io.reactivex.rxjava3.core.BackpressureStrategy
|
||||
import io.reactivex.rxjava3.core.Flowable
|
||||
import io.reactivex.rxjava3.subjects.PublishSubject
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import org.junit.Before
|
||||
import java.io.InputStreamReader
|
||||
import java.lang.reflect.Type
|
||||
import kotlin.reflect.KCallable
|
||||
import kotlin.reflect.KMutableProperty1
|
||||
import kotlin.reflect.full.starProjectedType
|
||||
import kotlin.reflect.jvm.javaField
|
||||
import org.junit.Before
|
||||
|
||||
open class HabiticaTestCase : TestCase() {
|
||||
val gson = GSonFactoryCreator.createGson()
|
||||
|
|
@ -68,8 +68,7 @@ open class HabiticaTestCase : TestCase() {
|
|||
val hatchPetUseCase: HatchPetUseCase = mockk(relaxed = true)
|
||||
val feedPetUseCase: FeedPetUseCase = mockk(relaxed = true)
|
||||
|
||||
val userSubject = PublishSubject.create<User>()
|
||||
val userEvents: Flowable<User> = userSubject.toFlowable(BackpressureStrategy.DROP)
|
||||
val userState = MutableStateFlow<User?>(null)
|
||||
var user = User()
|
||||
lateinit var content: ContentResult
|
||||
|
||||
|
|
@ -85,27 +84,27 @@ open class HabiticaTestCase : TestCase() {
|
|||
user = loadJsonFile("user", User::class.java)
|
||||
user.stats?.lvl = 20
|
||||
user.stats?.points = 30
|
||||
every { userRepository.getUser() } returns userEvents
|
||||
every { userRepository.getUser() } returns userState
|
||||
every { userViewModel.user } returns MutableLiveData<User?>(user)
|
||||
mockkObject(RxErrorHandler)
|
||||
every { RxErrorHandler.reportError(capture(errorSlot)) } answers {
|
||||
mockkObject(ExceptionHandler)
|
||||
every { ExceptionHandler.reportError(capture(errorSlot)) } answers {
|
||||
throw errorSlot.captured
|
||||
}
|
||||
every { socialRepository.getUnmanagedCopy(capture(unmanagedSlot)) } answers { unmanagedSlot.captured }
|
||||
content = loadJsonFile("content", ContentResult::class.java)
|
||||
every { inventoryRepository.getPets() } returns Flowable.just(content.pets)
|
||||
every { inventoryRepository.getMounts() } returns Flowable.just(content.mounts)
|
||||
every { inventoryRepository.getPets() } returns flowOf(content.pets)
|
||||
every { inventoryRepository.getMounts() } returns flowOf(content.mounts)
|
||||
every { inventoryRepository.getItemsFlowable(Food::class.java) } returns Flowable.just(content.food)
|
||||
every { inventoryRepository.getItemsFlowable(Egg::class.java) } returns Flowable.just(content.eggs)
|
||||
every { inventoryRepository.getItemsFlowable(HatchingPotion::class.java) } returns Flowable.just(content.hatchingPotions)
|
||||
every { inventoryRepository.getItemsFlowable(QuestContent::class.java) } returns Flowable.just(content.quests)
|
||||
|
||||
every { inventoryRepository.getItemsFlowable(Food::class.java, any()) } returns Flowable.just(content.food)
|
||||
every { inventoryRepository.getItemsFlowable(Egg::class.java, any()) } answers {
|
||||
Flowable.just(content.eggs)
|
||||
every { inventoryRepository.getItems(Food::class.java, any()) } returns flowOf(content.food)
|
||||
every { inventoryRepository.getItems(Egg::class.java, any()) } answers {
|
||||
flowOf(content.eggs)
|
||||
}
|
||||
every { inventoryRepository.getItemsFlowable(HatchingPotion::class.java, any()) } returns Flowable.just(content.hatchingPotions)
|
||||
every { inventoryRepository.getItemsFlowable(QuestContent::class.java, any()) } returns Flowable.just(content.quests)
|
||||
every { inventoryRepository.getItems(HatchingPotion::class.java, any()) } returns flowOf(content.hatchingPotions)
|
||||
every { inventoryRepository.getItems(QuestContent::class.java, any()) } returns flowOf(content.quests)
|
||||
}
|
||||
|
||||
internal fun <T> loadJsonFile(s: String, type: Type): T {
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ class StatsFragmentTest : FragmentTestCase<StatsFragment, FragmentStatsBinding,
|
|||
fun setUpUser() {
|
||||
user.stats?.lvl = 20
|
||||
user.stats?.points = 30
|
||||
userSubject.onNext(user)
|
||||
userState.onNext(user)
|
||||
|
||||
every { inventoryRepository.getEquipment(listOf()) } returns Flowable.just(listOf())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -115,7 +115,7 @@ internal class ItemRecyclerFragmentTest : FragmentTestCase<ItemRecyclerFragment,
|
|||
items = (items + items).sortedBy { it.key }
|
||||
Flowable.just(items)
|
||||
}
|
||||
every { inventoryRepository.getItemsFlowable(Food::class.java, any()) } answers {
|
||||
every { inventoryRepository.getItems(Food::class.java, any()) } answers {
|
||||
Flowable.just((content.eggs + content.eggs).sortedBy { it.key })
|
||||
}
|
||||
fragment.itemType = "food"
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ 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.RxErrorHandler
|
||||
import com.habitrpg.android.habitica.helpers.ExceptionHandler
|
||||
import com.habitrpg.android.habitica.helpers.notifications.PushNotificationManager
|
||||
import com.habitrpg.android.habitica.modules.UserModule
|
||||
import com.habitrpg.android.habitica.modules.UserRepositoryModule
|
||||
|
|
@ -86,7 +86,7 @@ abstract class HabiticaBaseApplication : Application(), Application.ActivityLife
|
|||
}
|
||||
setupCoil()
|
||||
|
||||
RxErrorHandler.init(analyticsManager)
|
||||
ExceptionHandler.init(analyticsManager)
|
||||
|
||||
FirebaseAnalytics.getInstance(this).setUserProperty("app_testing_level", BuildConfig.TESTING_LEVEL)
|
||||
|
||||
|
|
@ -120,7 +120,6 @@ abstract class HabiticaBaseApplication : Application(), Application.ActivityLife
|
|||
.deleteRealmIfMigrationNeeded()
|
||||
.allowWritesOnUiThread(true)
|
||||
.compactOnLaunch { totalBytes, usedBytes ->
|
||||
|
||||
// Compact if the file is over 100MB in size and less than 50% 'used'
|
||||
val oneHundredMB = 50 * 1024 * 1024
|
||||
(totalBytes > oneHundredMB) && (usedBytes / totalBytes) < 0.5
|
||||
|
|
|
|||
|
|
@ -26,13 +26,13 @@ import com.habitrpg.android.habitica.models.tasks.TaskList
|
|||
import com.habitrpg.android.habitica.models.user.Items
|
||||
import com.habitrpg.android.habitica.models.user.Stats
|
||||
import com.habitrpg.android.habitica.models.user.User
|
||||
import com.habitrpg.common.habitica.models.HabitResponse
|
||||
import com.habitrpg.common.habitica.models.PurchaseValidationRequest
|
||||
import com.habitrpg.common.habitica.models.PurchaseValidationResult
|
||||
import com.habitrpg.common.habitica.models.auth.UserAuth
|
||||
import com.habitrpg.common.habitica.models.auth.UserAuthResponse
|
||||
import com.habitrpg.common.habitica.models.auth.UserAuthSocial
|
||||
import com.habitrpg.shared.habitica.models.responses.FeedResponse
|
||||
import com.habitrpg.common.habitica.models.HabitResponse
|
||||
import com.habitrpg.shared.habitica.models.responses.Status
|
||||
import com.habitrpg.shared.habitica.models.responses.TaskDirectionData
|
||||
import com.habitrpg.shared.habitica.models.responses.VerifyUsernameResponse
|
||||
|
|
@ -49,27 +49,27 @@ import retrofit2.http.Query
|
|||
|
||||
@JvmSuppressWildcards
|
||||
interface ApiService {
|
||||
@get:GET("status")
|
||||
val status: Flowable<HabitResponse<Status>>
|
||||
@GET("status")
|
||||
suspend fun getStatus(): HabitResponse<Status>
|
||||
|
||||
/* user API */
|
||||
|
||||
@get:GET("user/")
|
||||
val user: Flowable<HabitResponse<User>>
|
||||
@GET("user/")
|
||||
suspend fun getUser(): HabitResponse<User>
|
||||
|
||||
@GET("inbox/messages")
|
||||
fun getInboxMessages(@Query("conversation") uuid: String, @Query("page") page: Int): Flowable<HabitResponse<List<ChatMessage>>>
|
||||
suspend fun getInboxMessages(@Query("conversation") uuid: String, @Query("page") page: Int): HabitResponse<List<ChatMessage>>
|
||||
@GET("inbox/conversations")
|
||||
fun getInboxConversations(): Flowable<HabitResponse<List<InboxConversation>>>
|
||||
|
||||
@get:GET("tasks/user")
|
||||
val tasks: Flowable<HabitResponse<TaskList>>
|
||||
@GET("tasks/user")
|
||||
suspend fun getTasks(): HabitResponse<TaskList>
|
||||
|
||||
@get:GET("world-state")
|
||||
val worldState: Flowable<HabitResponse<WorldState>>
|
||||
@GET("world-state")
|
||||
suspend fun worldState(): HabitResponse<WorldState>
|
||||
|
||||
@GET("content")
|
||||
fun getContent(@Query("language") language: String?): Flowable<HabitResponse<ContentResult>>
|
||||
suspend fun getContent(@Query("language") language: String?): HabitResponse<ContentResult>
|
||||
|
||||
@PUT("user/")
|
||||
fun updateUser(@Body updateDictionary: Map<String, Any>): Flowable<HabitResponse<User>>
|
||||
|
|
@ -130,7 +130,7 @@ interface ApiService {
|
|||
fun getTask(@Path("id") id: String): Flowable<HabitResponse<Task>>
|
||||
|
||||
@POST("tasks/{id}/score/{direction}")
|
||||
fun postTaskDirection(@Path("id") id: String, @Path("direction") direction: String): Flowable<HabitResponse<TaskDirectionData>>
|
||||
suspend fun postTaskDirection(@Path("id") id: String, @Path("direction") direction: String): HabitResponse<TaskDirectionData>
|
||||
@POST("tasks/bulk-score")
|
||||
fun bulkScoreTasks(@Body data: List<Map<String, String>>): Flowable<HabitResponse<BulkTaskScoringData>>
|
||||
|
||||
|
|
@ -138,7 +138,7 @@ interface ApiService {
|
|||
fun postTaskNewPosition(@Path("id") id: String, @Path("position") position: Int): Flowable<HabitResponse<List<String>>>
|
||||
|
||||
@POST("tasks/{taskId}/checklist/{itemId}/score")
|
||||
fun scoreChecklistItem(@Path("taskId") taskId: String, @Path("itemId") itemId: String): Flowable<HabitResponse<Task>>
|
||||
suspend fun scoreChecklistItem(@Path("taskId") taskId: String, @Path("itemId") itemId: String): HabitResponse<Task>
|
||||
|
||||
@POST("tasks/user")
|
||||
fun createTask(@Body item: Task): Flowable<HabitResponse<Task>>
|
||||
|
|
@ -177,10 +177,10 @@ interface ApiService {
|
|||
fun loginApple(@Body auth: Map<String, Any>): Flowable<HabitResponse<UserAuthResponse>>
|
||||
|
||||
@POST("user/sleep")
|
||||
fun sleep(): Flowable<HabitResponse<Boolean>>
|
||||
suspend fun sleep(): HabitResponse<Boolean>
|
||||
|
||||
@POST("user/revive")
|
||||
fun revive(): Flowable<HabitResponse<User>>
|
||||
suspend fun revive(): HabitResponse<User>
|
||||
|
||||
@POST("user/class/cast/{skill}")
|
||||
fun useSkill(
|
||||
|
|
@ -193,13 +193,13 @@ interface ApiService {
|
|||
fun useSkill(@Path("skill") skillName: String, @Query("targetType") targetType: String): Flowable<HabitResponse<SkillResponse>>
|
||||
|
||||
@POST("user/change-class")
|
||||
fun changeClass(): Flowable<HabitResponse<User>>
|
||||
suspend fun changeClass(): HabitResponse<User>
|
||||
|
||||
@POST("user/change-class")
|
||||
fun changeClass(@Query("class") className: String): Flowable<HabitResponse<User>>
|
||||
suspend fun changeClass(@Query("class") className: String): HabitResponse<User>
|
||||
|
||||
@POST("user/disable-classes")
|
||||
fun disableClasses(): Flowable<HabitResponse<User>>
|
||||
suspend fun disableClasses(): HabitResponse<User>
|
||||
|
||||
@POST("user/mark-pms-read")
|
||||
fun markPrivateMessagesRead(): Flowable<Void>
|
||||
|
|
@ -210,25 +210,25 @@ interface ApiService {
|
|||
fun listGroups(@Query("type") type: String): Flowable<HabitResponse<List<Group>>>
|
||||
|
||||
@GET("groups/{gid}")
|
||||
fun getGroup(@Path("gid") groupId: String): Flowable<HabitResponse<Group>>
|
||||
suspend fun getGroup(@Path("gid") groupId: String): HabitResponse<Group>
|
||||
|
||||
@POST("groups")
|
||||
fun createGroup(@Body item: Group): Flowable<HabitResponse<Group>>
|
||||
suspend fun createGroup(@Body item: Group): HabitResponse<Group>
|
||||
|
||||
@PUT("groups/{id}")
|
||||
fun updateGroup(@Path("id") id: String, @Body item: Group): Flowable<HabitResponse<Group>>
|
||||
suspend fun updateGroup(@Path("id") id: String, @Body item: Group): HabitResponse<Group>
|
||||
|
||||
@POST("groups/{groupID}/removeMember/{userID}")
|
||||
fun removeMemberFromGroup(@Path("groupID") groupID: String, @Path("userID") userID: String): Flowable<HabitResponse<Void>>
|
||||
suspend fun removeMemberFromGroup(@Path("groupID") groupID: String, @Path("userID") userID: String): HabitResponse<Void>
|
||||
|
||||
@GET("groups/{gid}/chat")
|
||||
fun listGroupChat(@Path("gid") groupId: String): Flowable<HabitResponse<List<ChatMessage>>>
|
||||
suspend fun listGroupChat(@Path("gid") groupId: String): HabitResponse<List<ChatMessage>>
|
||||
|
||||
@POST("groups/{gid}/join")
|
||||
fun joinGroup(@Path("gid") groupId: String): Flowable<HabitResponse<Group>>
|
||||
suspend fun joinGroup(@Path("gid") groupId: String): HabitResponse<Group>
|
||||
|
||||
@POST("groups/{gid}/leave")
|
||||
fun leaveGroup(@Path("gid") groupId: String, @Query("keepChallenges") keepChallenges: String): Flowable<HabitResponse<Void>>
|
||||
suspend fun leaveGroup(@Path("gid") groupId: String, @Query("keepChallenges") keepChallenges: String): HabitResponse<Void>
|
||||
|
||||
@POST("groups/{gid}/chat")
|
||||
fun postGroupChat(@Path("gid") groupId: String, @Body message: Map<String, String>): Flowable<HabitResponse<PostChatMessageResult>>
|
||||
|
|
@ -240,17 +240,17 @@ interface ApiService {
|
|||
fun deleteInboxMessage(@Path("messageId") messageId: String): Flowable<HabitResponse<Void>>
|
||||
|
||||
@GET("groups/{gid}/members")
|
||||
fun getGroupMembers(
|
||||
suspend fun getGroupMembers(
|
||||
@Path("gid") groupId: String,
|
||||
@Query("includeAllPublicFields") includeAllPublicFields: Boolean?
|
||||
): Flowable<HabitResponse<List<Member>>>
|
||||
): HabitResponse<List<Member>>
|
||||
|
||||
@GET("groups/{gid}/members")
|
||||
fun getGroupMembers(
|
||||
suspend fun getGroupMembers(
|
||||
@Path("gid") groupId: String,
|
||||
@Query("includeAllPublicFields") includeAllPublicFields: Boolean?,
|
||||
@Query("lastId") lastId: String
|
||||
): Flowable<HabitResponse<List<Member>>>
|
||||
): HabitResponse<List<Member>>
|
||||
|
||||
// Like returns the full chat list
|
||||
@POST("groups/{gid}/chat/{mid}/like")
|
||||
|
|
@ -300,7 +300,7 @@ interface ApiService {
|
|||
fun validateSubscription(@Body request: PurchaseValidationRequest): Flowable<HabitResponse<Void>>
|
||||
|
||||
@GET("/iap/android/subscribe/cancel")
|
||||
fun cancelSubscription(): Flowable<HabitResponse<Void>>
|
||||
suspend fun cancelSubscription(): HabitResponse<Void>
|
||||
|
||||
@POST("/iap/android/norenew-subscribe")
|
||||
fun validateNoRenewSubscription(@Body request: PurchaseValidationRequest): Flowable<HabitResponse<Void>>
|
||||
|
|
@ -310,16 +310,16 @@ interface ApiService {
|
|||
|
||||
// Members URL
|
||||
@GET("members/{mid}")
|
||||
fun getMember(@Path("mid") memberId: String): Flowable<HabitResponse<Member>>
|
||||
suspend fun getMember(@Path("mid") memberId: String): HabitResponse<Member>
|
||||
|
||||
@GET("members/username/{username}")
|
||||
fun getMemberWithUsername(@Path("username") username: String): Flowable<HabitResponse<Member>>
|
||||
suspend fun getMemberWithUsername(@Path("username") username: String): HabitResponse<Member>
|
||||
|
||||
@GET("members/{mid}/achievements")
|
||||
fun getMemberAchievements(@Path("mid") memberId: String, @Query("lang") language: String?): Flowable<HabitResponse<List<Achievement>>>
|
||||
|
||||
@POST("members/send-private-message")
|
||||
fun postPrivateMessage(@Body messageDetails: Map<String, String>): Flowable<HabitResponse<PostChatMessageResult>>
|
||||
suspend fun postPrivateMessage(@Body messageDetails: Map<String, String>): HabitResponse<PostChatMessageResult>
|
||||
|
||||
@GET("members/find/{username}")
|
||||
fun findUsernames(
|
||||
|
|
@ -440,7 +440,7 @@ interface ApiService {
|
|||
fun blockMember(@Path("userID") userID: String): Flowable<HabitResponse<List<String>>>
|
||||
|
||||
@POST("user/reroll")
|
||||
fun reroll(): Flowable<HabitResponse<User>>
|
||||
suspend fun reroll(): HabitResponse<User>
|
||||
|
||||
// Team Plans
|
||||
|
||||
|
|
@ -448,5 +448,5 @@ interface ApiService {
|
|||
fun getTeamPlans(): Flowable<HabitResponse<List<TeamPlan>>>
|
||||
|
||||
@GET("tasks/group/{groupID}")
|
||||
fun getTeamPlanTasks(@Path("groupID") groupId: String): Flowable<HabitResponse<TaskList>>
|
||||
suspend fun getTeamPlanTasks(@Path("groupID") groupId: String): HabitResponse<TaskList>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ import com.google.gson.reflect.TypeToken;
|
|||
import com.habitrpg.android.habitica.models.Achievement;
|
||||
import com.habitrpg.android.habitica.models.ContentResult;
|
||||
import com.habitrpg.android.habitica.models.FAQArticle;
|
||||
import com.habitrpg.android.habitica.models.tasks.GroupAssignedDetails;
|
||||
import com.habitrpg.android.habitica.utils.AssignedDetailsDeserializer;
|
||||
import com.habitrpg.common.habitica.models.Notification;
|
||||
import com.habitrpg.android.habitica.models.Skill;
|
||||
import com.habitrpg.android.habitica.models.Tag;
|
||||
|
|
@ -101,6 +103,8 @@ public class GSonFactoryCreator {
|
|||
}.getType();
|
||||
Type achievementsListType = new TypeToken<List<Achievement>>() {
|
||||
}.getType();
|
||||
Type assignedDetailsListType = new TypeToken<RealmList<GroupAssignedDetails>>() {
|
||||
}.getType();
|
||||
|
||||
return new GsonBuilder()
|
||||
.registerTypeAdapter(taskTagClassListType, new TaskTagDeserializer())
|
||||
|
|
@ -129,6 +133,7 @@ public class GSonFactoryCreator {
|
|||
.registerTypeAdapter(ownedPetListType, new OwnedPetListDeserializer())
|
||||
.registerTypeAdapter(ownedMountListType, new OwnedMountListDeserializer())
|
||||
.registerTypeAdapter(achievementsListType, new AchievementListDeserializer())
|
||||
.registerTypeAdapter(assignedDetailsListType, new AssignedDetailsDeserializer())
|
||||
.registerTypeAdapter(Quest.class, new QuestDeserializer())
|
||||
.registerTypeAdapter(Member.class, new MemberSerialization())
|
||||
.registerTypeAdapter(WorldState.class, new WorldStateSerialization())
|
||||
|
|
|
|||
|
|
@ -38,6 +38,8 @@ import com.habitrpg.android.habitica.ui.activities.SetupActivity;
|
|||
import com.habitrpg.android.habitica.ui.activities.SkillMemberActivity;
|
||||
import com.habitrpg.android.habitica.ui.activities.SkillTasksActivity;
|
||||
import com.habitrpg.android.habitica.ui.activities.TaskFormActivity;
|
||||
import com.habitrpg.android.habitica.ui.activities.TaskSummaryActivity;
|
||||
import com.habitrpg.android.habitica.ui.activities.TaskSummaryViewModel;
|
||||
import com.habitrpg.android.habitica.ui.activities.VerifyUsernameActivity;
|
||||
import com.habitrpg.android.habitica.ui.adapter.social.challenges.ChallengeTasksRecyclerViewAdapter;
|
||||
import com.habitrpg.android.habitica.ui.adapter.tasks.DailiesRecyclerViewHolder;
|
||||
|
|
@ -52,10 +54,8 @@ import com.habitrpg.android.habitica.ui.fragments.PromoInfoFragment;
|
|||
import com.habitrpg.android.habitica.ui.fragments.PromoWebFragment;
|
||||
import com.habitrpg.android.habitica.ui.fragments.StatsFragment;
|
||||
import com.habitrpg.android.habitica.ui.fragments.inventory.customization.AvatarCustomizationFragment;
|
||||
import com.habitrpg.android.habitica.ui.fragments.inventory.customization.AvatarEquipmentFragment;
|
||||
import com.habitrpg.android.habitica.ui.fragments.inventory.customization.AvatarOverviewFragment;
|
||||
import com.habitrpg.android.habitica.ui.fragments.inventory.equipment.EquipmentDetailFragment;
|
||||
import com.habitrpg.android.habitica.ui.fragments.inventory.equipment.EquipmentOverviewFragment;
|
||||
import com.habitrpg.android.habitica.ui.fragments.inventory.items.ItemDialogFragment;
|
||||
import com.habitrpg.android.habitica.ui.fragments.inventory.items.ItemRecyclerFragment;
|
||||
import com.habitrpg.android.habitica.ui.fragments.inventory.items.ItemsFragment;
|
||||
|
|
@ -165,8 +165,6 @@ public interface UserComponent {
|
|||
|
||||
void inject(EquipmentDetailFragment equipmentDetailFragment);
|
||||
|
||||
void inject(EquipmentOverviewFragment equipmentOverviewFragment);
|
||||
|
||||
void inject(ItemRecyclerFragment itemRecyclerFragment);
|
||||
|
||||
void inject(ItemsFragment itemsFragment);
|
||||
|
|
@ -333,8 +331,6 @@ public interface UserComponent {
|
|||
|
||||
void inject(BugFixFragment bugFixFragment);
|
||||
|
||||
void inject(AvatarEquipmentFragment avatarEquipmentFragment);
|
||||
|
||||
void inject(FAQDetailFragment faqDetailFragment);
|
||||
|
||||
void inject(AdventureGuideActivity adventureGuideFragment);
|
||||
|
|
@ -370,4 +366,8 @@ public interface UserComponent {
|
|||
void inject(@NotNull DeathActivity deathActivity);
|
||||
|
||||
void inject(@NotNull DeviceCommunicationService deviceCommunicationService);
|
||||
|
||||
void inject(@NotNull TaskSummaryActivity taskSummaryActivity);
|
||||
|
||||
void inject(@NotNull TaskSummaryViewModel taskSummaryViewModel);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,12 +27,12 @@ import com.habitrpg.android.habitica.models.user.Items
|
|||
import com.habitrpg.android.habitica.models.user.Stats
|
||||
import com.habitrpg.android.habitica.models.user.User
|
||||
import com.habitrpg.common.habitica.api.HostConfig
|
||||
import com.habitrpg.common.habitica.models.HabitResponse
|
||||
import com.habitrpg.common.habitica.models.PurchaseValidationRequest
|
||||
import com.habitrpg.common.habitica.models.PurchaseValidationResult
|
||||
import com.habitrpg.common.habitica.models.auth.UserAuthResponse
|
||||
import com.habitrpg.shared.habitica.models.responses.ErrorResponse
|
||||
import com.habitrpg.shared.habitica.models.responses.FeedResponse
|
||||
import com.habitrpg.common.habitica.models.HabitResponse
|
||||
import com.habitrpg.shared.habitica.models.responses.Status
|
||||
import com.habitrpg.shared.habitica.models.responses.TaskDirectionData
|
||||
import com.habitrpg.shared.habitica.models.responses.VerifyUsernameResponse
|
||||
|
|
@ -44,23 +44,19 @@ interface ApiClient {
|
|||
|
||||
val hostConfig: HostConfig
|
||||
|
||||
val status: Flowable<Status>
|
||||
|
||||
val content: Flowable<ContentResult>
|
||||
suspend fun getStatus(): Status?
|
||||
|
||||
/* user API */
|
||||
|
||||
val user: Flowable<User>
|
||||
|
||||
val tasks: Flowable<TaskList>
|
||||
suspend fun getTasks(): TaskList?
|
||||
|
||||
/* challenges api */
|
||||
|
||||
fun getUserChallenges(page: Int, memberOnly: Boolean): Flowable<List<Challenge>>
|
||||
|
||||
val worldState: Flowable<WorldState>
|
||||
suspend fun getWorldState(): WorldState?
|
||||
fun setLanguageCode(languageCode: String)
|
||||
fun getContent(language: String): Flowable<ContentResult>
|
||||
suspend fun getContent(language: String? = null): ContentResult?
|
||||
|
||||
fun updateUser(updateDictionary: Map<String, Any>): Flowable<User>
|
||||
|
||||
|
|
@ -83,7 +79,7 @@ interface ApiClient {
|
|||
fun purchaseSpecialSpell(key: String): Flowable<Void>
|
||||
fun validateSubscription(request: PurchaseValidationRequest): Flowable<Any>
|
||||
fun validateNoRenewSubscription(request: PurchaseValidationRequest): Flowable<Any>
|
||||
fun cancelSubscription(): Flowable<Void>
|
||||
suspend fun cancelSubscription(): Void?
|
||||
|
||||
fun sellItem(itemType: String, itemKey: String): Flowable<User>
|
||||
|
||||
|
|
@ -97,12 +93,12 @@ interface ApiClient {
|
|||
|
||||
fun getTask(id: String): Flowable<Task>
|
||||
|
||||
fun postTaskDirection(id: String, direction: String): Flowable<TaskDirectionData>
|
||||
suspend fun postTaskDirection(id: String, direction: String): TaskDirectionData?
|
||||
fun bulkScoreTasks(data: List<Map<String, String>>): Flowable<BulkTaskScoringData>
|
||||
|
||||
fun postTaskNewPosition(id: String, position: Int): Flowable<List<String>>
|
||||
|
||||
fun scoreChecklistItem(taskId: String, itemId: String): Flowable<Task>
|
||||
suspend fun scoreChecklistItem(taskId: String, itemId: String): Task?
|
||||
|
||||
fun createTask(item: Task): Flowable<Task>
|
||||
|
||||
|
|
@ -127,19 +123,16 @@ interface ApiClient {
|
|||
|
||||
fun loginApple(authToken: String): Flowable<UserAuthResponse>
|
||||
|
||||
fun sleep(): Flowable<Boolean>
|
||||
|
||||
fun revive(): Flowable<User>
|
||||
suspend fun sleep(): Boolean?
|
||||
suspend fun revive(): User?
|
||||
|
||||
fun useSkill(skillName: String, targetType: String, targetId: String): Flowable<SkillResponse>
|
||||
|
||||
fun useSkill(skillName: String, targetType: String): Flowable<SkillResponse>
|
||||
|
||||
fun changeClass(): Flowable<User>
|
||||
suspend fun changeClass(className: String?): User?
|
||||
|
||||
fun changeClass(className: String): Flowable<User>
|
||||
|
||||
fun disableClasses(): Flowable<User>
|
||||
suspend fun disableClasses(): User?
|
||||
|
||||
fun markPrivateMessagesRead(): Flowable<Void>
|
||||
|
||||
|
|
@ -147,26 +140,26 @@ interface ApiClient {
|
|||
|
||||
fun listGroups(type: String): Flowable<List<Group>>
|
||||
|
||||
fun getGroup(groupId: String): Flowable<Group>
|
||||
suspend fun getGroup(groupId: String): Group?
|
||||
|
||||
fun createGroup(group: Group): Flowable<Group>
|
||||
fun updateGroup(id: String, item: Group): Flowable<Group>
|
||||
fun removeMemberFromGroup(groupID: String, userID: String): Flowable<Void>
|
||||
suspend fun createGroup(group: Group): Group?
|
||||
suspend fun updateGroup(id: String, item: Group): Group?
|
||||
suspend fun removeMemberFromGroup(groupID: String, userID: String): Void?
|
||||
|
||||
fun listGroupChat(groupId: String): Flowable<List<ChatMessage>>
|
||||
suspend fun listGroupChat(groupId: String): List<ChatMessage>?
|
||||
|
||||
fun joinGroup(groupId: String): Flowable<Group>
|
||||
suspend fun joinGroup(groupId: String): Group?
|
||||
|
||||
fun leaveGroup(groupId: String, keepChallenges: String): Flowable<Void>
|
||||
suspend fun leaveGroup(groupId: String, keepChallenges: String): Void?
|
||||
|
||||
fun postGroupChat(groupId: String, message: Map<String, String>): Flowable<PostChatMessageResult>
|
||||
|
||||
fun deleteMessage(groupId: String, messageId: String): Flowable<Void>
|
||||
fun deleteInboxMessage(id: String): Flowable<Void>
|
||||
|
||||
fun getGroupMembers(groupId: String, includeAllPublicFields: Boolean?): Flowable<List<Member>>
|
||||
suspend fun getGroupMembers(groupId: String, includeAllPublicFields: Boolean?): List<Member>?
|
||||
|
||||
fun getGroupMembers(groupId: String, includeAllPublicFields: Boolean?, lastId: String): Flowable<List<Member>>
|
||||
suspend fun getGroupMembers(groupId: String, includeAllPublicFields: Boolean?, lastId: String): List<Member>?
|
||||
|
||||
// Like returns the full chat list
|
||||
fun likeMessage(groupId: String, mid: String): Flowable<ChatMessage>
|
||||
|
|
@ -199,12 +192,12 @@ interface ApiClient {
|
|||
fun changeCustomDayStart(updateObject: Map<String, Any>): Flowable<User>
|
||||
|
||||
// Members URL
|
||||
fun getMember(memberId: String): Flowable<Member>
|
||||
fun getMemberWithUsername(username: String): Flowable<Member>
|
||||
suspend fun getMember(memberId: String): Member?
|
||||
suspend fun getMemberWithUsername(username: String): Member?
|
||||
|
||||
fun getMemberAchievements(memberId: String): Flowable<List<Achievement>>
|
||||
|
||||
fun postPrivateMessage(messageDetails: Map<String, String>): Flowable<PostChatMessageResult>
|
||||
suspend fun postPrivateMessage(messageDetails: Map<String, String>): PostChatMessageResult?
|
||||
|
||||
fun retrieveShopIventory(identifier: String): Flowable<Shop>
|
||||
|
||||
|
|
@ -243,8 +236,8 @@ interface ApiClient {
|
|||
|
||||
fun hasAuthenticationKeys(): Boolean
|
||||
|
||||
fun retrieveUser(withTasks: Boolean): Flowable<User>
|
||||
fun retrieveInboxMessages(uuid: String, page: Int): Flowable<List<ChatMessage>>
|
||||
suspend fun retrieveUser(withTasks: Boolean = false): User?
|
||||
suspend fun retrieveInboxMessages(uuid: String, page: Int): List<ChatMessage>?
|
||||
fun retrieveInboxConversations(): Flowable<List<InboxConversation>>
|
||||
|
||||
fun <T : Any> configureApiCallObserver(): FlowableTransformer<HabitResponse<T>, T>
|
||||
|
|
@ -253,7 +246,7 @@ interface ApiClient {
|
|||
|
||||
fun runCron(): Flowable<Void>
|
||||
|
||||
fun reroll(): Flowable<User>
|
||||
suspend fun reroll(): User?
|
||||
|
||||
fun resetAccount(): Flowable<Void>
|
||||
fun deleteAccount(password: String): Flowable<Void>
|
||||
|
|
@ -282,5 +275,5 @@ interface ApiClient {
|
|||
fun unlinkAllTasks(challengeID: String?, keepOption: String): Flowable<Void>
|
||||
fun blockMember(userID: String): Flowable<List<String>>
|
||||
fun getTeamPlans(): Flowable<List<TeamPlan>>
|
||||
fun getTeamPlanTasks(teamID: String): Flowable<TaskList>
|
||||
suspend fun getTeamPlanTasks(teamID: String): TaskList?
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@ import com.habitrpg.android.habitica.models.ContentResult
|
|||
import com.habitrpg.android.habitica.models.WorldState
|
||||
import io.reactivex.rxjava3.core.Flowable
|
||||
|
||||
interface ContentRepository : BaseRepository {
|
||||
fun retrieveContent(forced: Boolean = false): Flowable<ContentResult>
|
||||
interface ContentRepository: BaseRepository {
|
||||
suspend fun retrieveContent(forced: Boolean = false): ContentResult?
|
||||
|
||||
fun retrieveWorldState(): Flowable<WorldState>
|
||||
suspend fun retrieveWorldState(): WorldState?
|
||||
fun getWorldState(): Flowable<WorldState>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ interface InventoryRepository : BaseRepository {
|
|||
fun getPets(): Flow<List<Pet>>
|
||||
|
||||
fun getOwnedPets(): Flow<List<OwnedPet>>
|
||||
fun getQuestContent(key: String): Flowable<QuestContent>
|
||||
fun getQuestContent(key: String): Flow<QuestContent?>
|
||||
fun getQuestContent(keys: List<String>): Flow<List<QuestContent>>
|
||||
|
||||
fun getEquipment(searchedKeys: List<String>): Flowable<out List<Equipment>>
|
||||
|
|
@ -86,7 +86,7 @@ interface InventoryRepository : BaseRepository {
|
|||
fun purchaseItem(purchaseType: String, key: String, purchaseQuantity: Int): Flowable<Void>
|
||||
|
||||
fun togglePinnedItem(item: ShopItem): Flowable<List<ShopItem>>
|
||||
fun getItemsFlowable(itemClass: Class<out Item>, keys: Array<String>): Flow<List<Item>>
|
||||
fun getItems(itemClass: Class<out Item>, keys: Array<String>): Flow<List<Item>>
|
||||
fun getItemsFlowable(itemClass: Class<out Item>): Flowable<out List<Item>>
|
||||
fun getItems(itemClass: Class<out Item>): Flow<List<Item>>
|
||||
fun getLatestMysteryItem(): Flowable<Equipment>
|
||||
|
|
|
|||
|
|
@ -11,14 +11,14 @@ import com.habitrpg.android.habitica.models.social.GroupMembership
|
|||
import com.habitrpg.android.habitica.models.social.InboxConversation
|
||||
import com.habitrpg.android.habitica.models.user.User
|
||||
import io.reactivex.rxjava3.core.Flowable
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
import io.realm.RealmResults
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface SocialRepository : BaseRepository {
|
||||
fun getPublicGuilds(): Flowable<out List<Group>>
|
||||
|
||||
fun getUserGroups(type: String?): Flowable<out List<Group>>
|
||||
fun retrieveGroupChat(groupId: String): Single<List<ChatMessage>>
|
||||
fun getUserGroups(type: String?): Flow<List<Group>>
|
||||
suspend fun retrieveGroupChat(groupId: String): List<ChatMessage>?
|
||||
fun getGroupChat(groupId: String): Flowable<out List<ChatMessage>>
|
||||
|
||||
fun markMessagesSeen(seenGroupId: String)
|
||||
|
|
@ -40,52 +40,52 @@ interface SocialRepository : BaseRepository {
|
|||
|
||||
fun postGroupChat(groupId: String, message: String): Flowable<PostChatMessageResult>
|
||||
|
||||
fun retrieveGroup(id: String): Flowable<Group>
|
||||
suspend fun retrieveGroup(id: String): Group?
|
||||
fun getGroup(id: String?): Flow<Group?>
|
||||
fun getGroupFlowable(id: String?): Flowable<Group>
|
||||
|
||||
fun leaveGroup(id: String?, keepChallenges: Boolean): Flowable<Group>
|
||||
suspend fun leaveGroup(id: String?, keepChallenges: Boolean): Group?
|
||||
|
||||
fun joinGroup(id: String?): Flowable<Group>
|
||||
suspend fun joinGroup(id: String?): Group?
|
||||
|
||||
fun createGroup(
|
||||
suspend fun createGroup(
|
||||
name: String?,
|
||||
description: String?,
|
||||
leader: String?,
|
||||
type: String?,
|
||||
privacy: String?,
|
||||
leaderCreateChallenge: Boolean?
|
||||
): Flowable<Group>
|
||||
): Group?
|
||||
|
||||
fun updateGroup(
|
||||
suspend fun updateGroup(
|
||||
group: Group?,
|
||||
name: String?,
|
||||
description: String?,
|
||||
leader: String?,
|
||||
leaderCreateChallenge: Boolean?
|
||||
): Flowable<Group>
|
||||
): Group?
|
||||
|
||||
fun retrieveGroups(type: String): Flowable<List<Group>>
|
||||
fun getGroups(type: String): Flowable<out List<Group>>
|
||||
|
||||
fun getInboxMessages(replyToUserID: String?): Flowable<out List<ChatMessage>>
|
||||
fun retrieveInboxMessages(uuid: String, page: Int): Flowable<List<ChatMessage>>
|
||||
fun getInboxMessages(replyToUserID: String?): Flow<RealmResults<ChatMessage>>
|
||||
suspend fun retrieveInboxMessages(uuid: String, page: Int): List<ChatMessage>?
|
||||
fun retrieveInboxConversations(): Flowable<List<InboxConversation>>
|
||||
fun getInboxConversations(): Flowable<out List<InboxConversation>>
|
||||
fun postPrivateMessage(
|
||||
fun getInboxConversations(): Flow<RealmResults<InboxConversation>>
|
||||
suspend fun postPrivateMessage(
|
||||
recipientId: String,
|
||||
messageObject: HashMap<String, String>
|
||||
): Flowable<List<ChatMessage>>
|
||||
): List<ChatMessage>?
|
||||
|
||||
fun postPrivateMessage(recipientId: String, message: String): Flowable<List<ChatMessage>>
|
||||
suspend fun postPrivateMessage(recipientId: String, message: String): List<ChatMessage>?
|
||||
|
||||
fun getGroupMembers(id: String): Flow<List<Member>>
|
||||
fun retrieveGroupMembers(id: String, includeAllPublicFields: Boolean): Flowable<List<Member>>
|
||||
suspend fun getPartyMembers(id: String): Flow<List<Member>>
|
||||
suspend fun getGroupMembers(id: String): Flow<List<Member>>
|
||||
suspend fun retrievePartyMembers(id: String, includeAllPublicFields: Boolean): List<Member>?
|
||||
|
||||
fun inviteToGroup(id: String, inviteData: Map<String, Any>): Flowable<List<Void>>
|
||||
|
||||
fun getMember(userId: String?): Flowable<Member>
|
||||
fun getMemberWithUsername(username: String?): Flowable<Member>
|
||||
suspend fun retrieveMember(userId: String?): Member?
|
||||
suspend fun retrieveMemberWithUsername(username: String?): Member?
|
||||
|
||||
fun findUsernames(
|
||||
username: String,
|
||||
|
|
@ -97,8 +97,8 @@ interface SocialRepository : BaseRepository {
|
|||
|
||||
fun markSomePrivateMessagesAsRead(user: User?, messages: List<ChatMessage>)
|
||||
|
||||
fun transferGroupOwnership(groupID: String, userID: String): Flowable<Group>
|
||||
fun removeMemberFromGroup(groupID: String, userID: String): Flowable<List<Member>>
|
||||
suspend fun transferGroupOwnership(groupID: String, userID: String): Group?
|
||||
suspend fun removeMemberFromGroup(groupID: String, userID: String): List<Member>?
|
||||
|
||||
fun acceptQuest(user: User?, partyId: String = "party"): Flowable<Void>
|
||||
fun rejectQuest(user: User?, partyId: String = "party"): Flowable<Void>
|
||||
|
|
@ -117,7 +117,7 @@ interface SocialRepository : BaseRepository {
|
|||
|
||||
fun transferGems(giftedID: String, amount: Int): Flowable<Void>
|
||||
|
||||
fun getGroupMembership(id: String): Flowable<GroupMembership>
|
||||
fun getGroupMembership(id: String): Flow<GroupMembership?>
|
||||
fun getGroupMemberships(): Flowable<out List<GroupMembership>>
|
||||
fun blockMember(userID: String): Flowable<List<String>>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,27 +19,27 @@ interface TaskRepository : BaseRepository {
|
|||
fun getTasksFlowable(taskType: TaskType, userID: String? = null, includedGroupIDs: Array<String>): Flowable<out List<Task>>
|
||||
fun saveTasks(userId: String, order: TasksOrder, tasks: TaskList)
|
||||
|
||||
fun retrieveTasks(userId: String, tasksOrder: TasksOrder): Flowable<TaskList>
|
||||
suspend fun retrieveTasks(userId: String, tasksOrder: TasksOrder): TaskList?
|
||||
fun retrieveTasks(userId: String, tasksOrder: TasksOrder, dueDate: Date): Flowable<TaskList>
|
||||
|
||||
fun taskChecked(
|
||||
suspend fun taskChecked(
|
||||
user: User?,
|
||||
task: Task,
|
||||
up: Boolean,
|
||||
force: Boolean,
|
||||
notifyFunc: ((TaskScoringResult) -> Unit)?
|
||||
): Flowable<TaskScoringResult>
|
||||
fun taskChecked(
|
||||
): TaskScoringResult?
|
||||
suspend fun taskChecked(
|
||||
user: User?,
|
||||
taskId: String,
|
||||
up: Boolean,
|
||||
force: Boolean,
|
||||
notifyFunc: ((TaskScoringResult) -> Unit)?
|
||||
): Maybe<TaskScoringResult>
|
||||
fun scoreChecklistItem(taskId: String, itemId: String): Flowable<Task>
|
||||
): TaskScoringResult?
|
||||
suspend fun scoreChecklistItem(taskId: String, itemId: String): Task?
|
||||
|
||||
fun getTask(taskId: String): Flowable<Task>
|
||||
fun getTaskCopy(taskId: String): Flowable<Task>
|
||||
fun getTask(taskId: String): Flow<Task>
|
||||
fun getTaskCopy(taskId: String): Flow<Task>
|
||||
fun createTask(task: Task, force: Boolean = false): Flowable<Task>
|
||||
fun updateTask(task: Task, force: Boolean = false): Maybe<Task>?
|
||||
fun deleteTask(taskId: String): Flowable<Void>
|
||||
|
|
@ -55,7 +55,7 @@ interface TaskRepository : BaseRepository {
|
|||
|
||||
fun updateTaskPosition(taskType: TaskType, taskID: String, newPosition: Int): Maybe<List<String>>
|
||||
|
||||
fun getUnmanagedTask(taskid: String): Flowable<Task>
|
||||
fun getUnmanagedTask(taskid: String): Flow<Task>
|
||||
|
||||
fun updateTaskInBackground(task: Task)
|
||||
|
||||
|
|
|
|||
|
|
@ -26,18 +26,13 @@ interface UserRepository : BaseRepository {
|
|||
fun updateUser(updateData: Map<String, Any>): Flowable<User>
|
||||
fun updateUser(key: String, value: Any): Flowable<User>
|
||||
|
||||
fun retrieveUser(withTasks: Boolean): Flowable<User>
|
||||
fun retrieveUser(
|
||||
withTasks: Boolean = false,
|
||||
forced: Boolean = false,
|
||||
overrideExisting: Boolean = false
|
||||
): Flowable<User>
|
||||
suspend fun retrieveUser(withTasks: Boolean = false, forced: Boolean = false, overrideExisting: Boolean = false): User?
|
||||
|
||||
fun revive(): Flowable<User>
|
||||
suspend fun revive(): User?
|
||||
|
||||
fun resetTutorial(): Maybe<User>
|
||||
|
||||
fun sleep(user: User): Flowable<User>
|
||||
suspend fun sleep(user: User): User?
|
||||
|
||||
fun getSkills(user: User): Flowable<out List<Skill>>
|
||||
|
||||
|
|
@ -46,17 +41,14 @@ interface UserRepository : BaseRepository {
|
|||
fun useSkill(key: String, target: String?, taskId: String): Flowable<SkillResponse>
|
||||
fun useSkill(key: String, target: String?): Flowable<SkillResponse>
|
||||
|
||||
fun changeClass(): Flowable<User>
|
||||
|
||||
fun disableClasses(): Flowable<User>
|
||||
|
||||
fun changeClass(selectedClass: String): Flowable<User>
|
||||
suspend fun disableClasses(): User?
|
||||
suspend fun changeClass(selectedClass: String? = null): User?
|
||||
|
||||
fun unlockPath(path: String, price: Int): Flowable<UnlockResponse>
|
||||
fun unlockPath(customization: Customization): Flowable<UnlockResponse>
|
||||
|
||||
fun runCron(tasks: MutableList<Task>)
|
||||
fun runCron()
|
||||
suspend fun runCron(tasks: MutableList<Task>)
|
||||
suspend fun runCron()
|
||||
|
||||
fun readNotification(id: String): Flowable<List<Any>>
|
||||
fun readNotifications(notificationIds: Map<String, List<String>>): Flowable<List<Any>>
|
||||
|
|
@ -66,7 +58,7 @@ interface UserRepository : BaseRepository {
|
|||
|
||||
fun updateLanguage(languageCode: String): Flowable<User>
|
||||
|
||||
fun resetAccount(): Flowable<User>
|
||||
suspend fun resetAccount(): User?
|
||||
fun deleteAccount(password: String): Flowable<Void>
|
||||
|
||||
fun sendPasswordResetEmail(email: String): Flowable<Void>
|
||||
|
|
@ -86,9 +78,9 @@ interface UserRepository : BaseRepository {
|
|||
|
||||
fun getUserQuestStatus(): Flowable<UserQuestStatus>
|
||||
|
||||
fun reroll(): Flowable<User>
|
||||
suspend fun reroll(): User?
|
||||
fun retrieveTeamPlans(): Flowable<List<TeamPlan>>
|
||||
fun getTeamPlans(): Flow<List<TeamPlan>>
|
||||
fun retrieveTeamPlan(teamID: String): Flowable<Group>
|
||||
suspend fun retrieveTeamPlan(teamID: String): Group?
|
||||
fun getTeamPlan(teamID: String): Flowable<Group>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import com.habitrpg.android.habitica.R
|
|||
import com.habitrpg.android.habitica.api.ApiService
|
||||
import com.habitrpg.android.habitica.api.GSonFactoryCreator
|
||||
import com.habitrpg.android.habitica.data.ApiClient
|
||||
import com.habitrpg.android.habitica.extensions.filterMap
|
||||
import com.habitrpg.android.habitica.helpers.NotificationsManager
|
||||
import com.habitrpg.android.habitica.models.Achievement
|
||||
import com.habitrpg.android.habitica.models.ContentResult
|
||||
|
|
@ -40,6 +39,7 @@ import com.habitrpg.android.habitica.models.user.User
|
|||
import com.habitrpg.android.habitica.proxy.AnalyticsManager
|
||||
import com.habitrpg.common.habitica.api.HostConfig
|
||||
import com.habitrpg.common.habitica.api.Server
|
||||
import com.habitrpg.common.habitica.models.HabitResponse
|
||||
import com.habitrpg.common.habitica.models.PurchaseValidationRequest
|
||||
import com.habitrpg.common.habitica.models.PurchaseValidationResult
|
||||
import com.habitrpg.common.habitica.models.auth.UserAuth
|
||||
|
|
@ -48,7 +48,6 @@ import com.habitrpg.common.habitica.models.auth.UserAuthSocial
|
|||
import com.habitrpg.common.habitica.models.auth.UserAuthSocialTokens
|
||||
import com.habitrpg.shared.habitica.models.responses.ErrorResponse
|
||||
import com.habitrpg.shared.habitica.models.responses.FeedResponse
|
||||
import com.habitrpg.common.habitica.models.HabitResponse
|
||||
import com.habitrpg.shared.habitica.models.responses.Status
|
||||
import com.habitrpg.shared.habitica.models.responses.TaskDirectionData
|
||||
import com.habitrpg.shared.habitica.models.responses.VerifyUsernameResponse
|
||||
|
|
@ -90,19 +89,31 @@ class ApiClientImpl(
|
|||
|
||||
private val apiCallTransformer = FlowableTransformer<HabitResponse<Any>, Any> { observable ->
|
||||
observable
|
||||
.filterMap { habitResponse ->
|
||||
habitResponse.notifications?.let {
|
||||
notificationsManager.setNotifications(it)
|
||||
}
|
||||
if (hadError) {
|
||||
hideConnectionProblemDialog()
|
||||
}
|
||||
habitResponse.data
|
||||
.filter { it.data != null }
|
||||
.map { habitResponse ->
|
||||
processResponse(habitResponse)
|
||||
}
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.doOnError(this)
|
||||
}
|
||||
|
||||
private fun <T> processResponse(habitResponse: HabitResponse<T>): T? {
|
||||
habitResponse.notifications?.let {
|
||||
notificationsManager.setNotifications(it)
|
||||
}
|
||||
return habitResponse.data
|
||||
}
|
||||
|
||||
suspend fun <T> handleSuspendCall(apiCall: suspend () -> HabitResponse<T>): T? {
|
||||
try {
|
||||
return processResponse(apiCall())
|
||||
} catch (throwable: Throwable) {
|
||||
accept(throwable)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private var languageCode: String? = null
|
||||
private var lastAPICallURL: String? = null
|
||||
private var hadError = false
|
||||
|
|
@ -268,25 +279,15 @@ class ApiClientImpl(
|
|||
}
|
||||
}
|
||||
|
||||
override fun retrieveUser(withTasks: Boolean): Flowable<User> {
|
||||
|
||||
var userObservable = this.user
|
||||
|
||||
if (withTasks) {
|
||||
val tasksObservable = this.tasks
|
||||
|
||||
userObservable = Flowable.zip(
|
||||
userObservable, tasksObservable
|
||||
) { habitRPGUser, tasks ->
|
||||
habitRPGUser.tasks = tasks
|
||||
habitRPGUser
|
||||
}
|
||||
}
|
||||
return userObservable
|
||||
override suspend fun retrieveUser(withTasks: Boolean): User? {
|
||||
val user = handleSuspendCall { apiService.getUser() }
|
||||
val tasks = getTasks()
|
||||
user?.tasks = tasks
|
||||
return user
|
||||
}
|
||||
|
||||
override fun retrieveInboxMessages(uuid: String, page: Int): Flowable<List<ChatMessage>> {
|
||||
return apiService.getInboxMessages(uuid, page).compose(configureApiCallObserver())
|
||||
override suspend fun retrieveInboxMessages(uuid: String, page: Int): List<ChatMessage>? {
|
||||
return handleSuspendCall { apiService.getInboxMessages(uuid, page) }
|
||||
}
|
||||
|
||||
override fun retrieveInboxConversations(): Flowable<List<InboxConversation>> {
|
||||
|
|
@ -345,16 +346,12 @@ class ApiClientImpl(
|
|||
this.languageCode = languageCode
|
||||
}
|
||||
|
||||
override val status: Flowable<Status>
|
||||
get() = apiService.status.compose(configureApiCallObserver())
|
||||
override suspend fun getStatus(): Status? = handleSuspendCall { apiService.getStatus() }
|
||||
|
||||
override fun getContent(language: String): Flowable<ContentResult> {
|
||||
return apiService.getContent(language).compose(configureApiCallObserver())
|
||||
override suspend fun getContent(language: String?): ContentResult? {
|
||||
return handleSuspendCall { apiService.getContent(language) }
|
||||
}
|
||||
|
||||
override val user: Flowable<User>
|
||||
get() = apiService.user.compose(configureApiCallObserver())
|
||||
|
||||
override fun updateUser(updateDictionary: Map<String, Any>): Flowable<User> {
|
||||
return apiService.updateUser(updateDictionary).compose(configureApiCallObserver())
|
||||
}
|
||||
|
|
@ -399,8 +396,8 @@ class ApiClientImpl(
|
|||
return apiService.validateNoRenewSubscription(request).compose(configureApiCallObserver())
|
||||
}
|
||||
|
||||
override fun cancelSubscription(): Flowable<Void> {
|
||||
return apiService.cancelSubscription().compose(configureApiCallObserver())
|
||||
override suspend fun cancelSubscription(): Void? {
|
||||
return processResponse(apiService.cancelSubscription())
|
||||
}
|
||||
|
||||
override fun purchaseHourglassItem(type: String, itemKey: String): Flowable<Void> {
|
||||
|
|
@ -436,8 +433,7 @@ class ApiClientImpl(
|
|||
return apiService.hatchPet(eggKey, hatchingPotionKey).compose(configureApiCallObserver())
|
||||
}
|
||||
|
||||
override val tasks: Flowable<TaskList>
|
||||
get() = apiService.tasks.compose(configureApiCallObserver())
|
||||
override suspend fun getTasks(): TaskList? = handleSuspendCall { apiService.getTasks() }
|
||||
|
||||
override fun getTasks(type: String): Flowable<TaskList> {
|
||||
return apiService.getTasks(type).compose(configureApiCallObserver())
|
||||
|
|
@ -455,8 +451,8 @@ class ApiClientImpl(
|
|||
return apiService.getTask(id).compose(configureApiCallObserver())
|
||||
}
|
||||
|
||||
override fun postTaskDirection(id: String, direction: String): Flowable<TaskDirectionData> {
|
||||
return apiService.postTaskDirection(id, direction).compose(configureApiCallObserver())
|
||||
override suspend fun postTaskDirection(id: String, direction: String): TaskDirectionData? {
|
||||
return handleSuspendCall { apiService.postTaskDirection(id, direction) }
|
||||
}
|
||||
|
||||
override fun bulkScoreTasks(data: List<Map<String, String>>): Flowable<BulkTaskScoringData> {
|
||||
|
|
@ -467,8 +463,8 @@ class ApiClientImpl(
|
|||
return apiService.postTaskNewPosition(id, position).compose(configureApiCallObserver())
|
||||
}
|
||||
|
||||
override fun scoreChecklistItem(taskId: String, itemId: String): Flowable<Task> {
|
||||
return apiService.scoreChecklistItem(taskId, itemId).compose(configureApiCallObserver())
|
||||
override suspend fun scoreChecklistItem(taskId: String, itemId: String): Task? {
|
||||
return handleSuspendCall { apiService.scoreChecklistItem(taskId, itemId) }
|
||||
}
|
||||
|
||||
override fun createTask(item: Task): Flowable<Task> {
|
||||
|
|
@ -499,13 +495,9 @@ class ApiClientImpl(
|
|||
return apiService.deleteTag(id).compose(configureApiCallObserver())
|
||||
}
|
||||
|
||||
override fun sleep(): Flowable<Boolean> {
|
||||
return apiService.sleep().compose(configureApiCallObserver())
|
||||
}
|
||||
override suspend fun sleep(): Boolean? = handleSuspendCall { apiService.sleep() }
|
||||
|
||||
override fun revive(): Flowable<User> {
|
||||
return apiService.revive().compose(configureApiCallObserver())
|
||||
}
|
||||
override suspend fun revive(): User? = handleSuspendCall { apiService.revive() }
|
||||
|
||||
override fun useSkill(skillName: String, targetType: String, targetId: String): Flowable<SkillResponse> {
|
||||
return apiService.useSkill(skillName, targetType, targetId).compose(configureApiCallObserver())
|
||||
|
|
@ -515,17 +507,17 @@ class ApiClientImpl(
|
|||
return apiService.useSkill(skillName, targetType).compose(configureApiCallObserver())
|
||||
}
|
||||
|
||||
override fun changeClass(): Flowable<User> {
|
||||
return apiService.changeClass().compose(configureApiCallObserver())
|
||||
override suspend fun changeClass(className: String?): User? {
|
||||
return handleSuspendCall {
|
||||
if (className != null) {
|
||||
apiService.changeClass(className)
|
||||
} else {
|
||||
apiService.changeClass()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun changeClass(className: String): Flowable<User> {
|
||||
return apiService.changeClass(className).compose(configureApiCallObserver())
|
||||
}
|
||||
|
||||
override fun disableClasses(): Flowable<User> {
|
||||
return apiService.disableClasses().compose(configureApiCallObserver())
|
||||
}
|
||||
override suspend fun disableClasses(): User? = handleSuspendCall { apiService.disableClasses() }
|
||||
|
||||
override fun markPrivateMessagesRead(): Flowable<Void> {
|
||||
// This is necessary, because the API call returns weird data.
|
||||
|
|
@ -539,32 +531,32 @@ class ApiClientImpl(
|
|||
return apiService.listGroups(type).compose(configureApiCallObserver())
|
||||
}
|
||||
|
||||
override fun getGroup(groupId: String): Flowable<Group> {
|
||||
return apiService.getGroup(groupId).compose(configureApiCallObserver())
|
||||
override suspend fun getGroup(groupId: String): Group? {
|
||||
return processResponse(apiService.getGroup(groupId))
|
||||
}
|
||||
|
||||
override fun createGroup(group: Group): Flowable<Group> {
|
||||
return apiService.createGroup(group).compose(configureApiCallObserver())
|
||||
override suspend fun createGroup(group: Group): Group? {
|
||||
return processResponse(apiService.createGroup(group))
|
||||
}
|
||||
|
||||
override fun updateGroup(id: String, item: Group): Flowable<Group> {
|
||||
return apiService.updateGroup(id, item).compose(configureApiCallObserver())
|
||||
override suspend fun updateGroup(id: String, item: Group): Group? {
|
||||
return processResponse(apiService.updateGroup(id, item))
|
||||
}
|
||||
|
||||
override fun removeMemberFromGroup(groupID: String, userID: String): Flowable<Void> {
|
||||
return apiService.removeMemberFromGroup(groupID, userID).compose(configureApiCallObserver())
|
||||
override suspend fun removeMemberFromGroup(groupID: String, userID: String): Void? {
|
||||
return processResponse(apiService.removeMemberFromGroup(groupID, userID))
|
||||
}
|
||||
|
||||
override fun listGroupChat(groupId: String): Flowable<List<ChatMessage>> {
|
||||
return apiService.listGroupChat(groupId).compose(configureApiCallObserver())
|
||||
override suspend fun listGroupChat(groupId: String): List<ChatMessage>? {
|
||||
return processResponse(apiService.listGroupChat(groupId))
|
||||
}
|
||||
|
||||
override fun joinGroup(groupId: String): Flowable<Group> {
|
||||
return apiService.joinGroup(groupId).compose(configureApiCallObserver())
|
||||
override suspend fun joinGroup(groupId: String): Group? {
|
||||
return processResponse(apiService.joinGroup(groupId))
|
||||
}
|
||||
|
||||
override fun leaveGroup(groupId: String, keepChallenges: String): Flowable<Void> {
|
||||
return apiService.leaveGroup(groupId, keepChallenges).compose(configureApiCallObserver())
|
||||
override suspend fun leaveGroup(groupId: String, keepChallenges: String): Void? {
|
||||
return processResponse(apiService.leaveGroup(groupId, keepChallenges))
|
||||
}
|
||||
|
||||
override fun postGroupChat(groupId: String, message: Map<String, String>): Flowable<PostChatMessageResult> {
|
||||
|
|
@ -578,12 +570,12 @@ class ApiClientImpl(
|
|||
return apiService.deleteInboxMessage(id).compose(configureApiCallObserver())
|
||||
}
|
||||
|
||||
override fun getGroupMembers(groupId: String, includeAllPublicFields: Boolean?): Flowable<List<Member>> {
|
||||
return apiService.getGroupMembers(groupId, includeAllPublicFields).compose(configureApiCallObserver())
|
||||
override suspend fun getGroupMembers(groupId: String, includeAllPublicFields: Boolean?): List<Member>? {
|
||||
return processResponse(apiService.getGroupMembers(groupId, includeAllPublicFields))
|
||||
}
|
||||
|
||||
override fun getGroupMembers(groupId: String, includeAllPublicFields: Boolean?, lastId: String): Flowable<List<Member>> {
|
||||
return apiService.getGroupMembers(groupId, includeAllPublicFields, lastId).compose(configureApiCallObserver())
|
||||
override suspend fun getGroupMembers(groupId: String, includeAllPublicFields: Boolean?, lastId: String): List<Member>? {
|
||||
return processResponse(apiService.getGroupMembers(groupId, includeAllPublicFields, lastId))
|
||||
}
|
||||
|
||||
override fun likeMessage(groupId: String, mid: String): Flowable<ChatMessage> {
|
||||
|
|
@ -646,13 +638,8 @@ class ApiClientImpl(
|
|||
return apiService.changeCustomDayStart(updateObject).compose(configureApiCallObserver())
|
||||
}
|
||||
|
||||
override fun getMember(memberId: String): Flowable<Member> {
|
||||
return apiService.getMember(memberId).compose(configureApiCallObserver())
|
||||
}
|
||||
|
||||
override fun getMemberWithUsername(username: String): Flowable<Member> {
|
||||
return apiService.getMemberWithUsername(username).compose(configureApiCallObserver())
|
||||
}
|
||||
override suspend fun getMember(memberId: String) = processResponse(apiService.getMember(memberId))
|
||||
override suspend fun getMemberWithUsername(username: String) = processResponse(apiService.getMemberWithUsername(username))
|
||||
|
||||
override fun getMemberAchievements(memberId: String): Flowable<List<Achievement>> {
|
||||
return apiService.getMemberAchievements(memberId, languageCode).compose(configureApiCallObserver())
|
||||
|
|
@ -662,8 +649,8 @@ class ApiClientImpl(
|
|||
return apiService.findUsernames(username, context, id).compose(configureApiCallObserver())
|
||||
}
|
||||
|
||||
override fun postPrivateMessage(messageDetails: Map<String, String>): Flowable<PostChatMessageResult> {
|
||||
return apiService.postPrivateMessage(messageDetails).compose(configureApiCallObserver())
|
||||
override suspend fun postPrivateMessage(messageDetails: Map<String, String>): PostChatMessageResult? {
|
||||
return handleSuspendCall { apiService.postPrivateMessage(messageDetails) }
|
||||
}
|
||||
|
||||
override fun retrieveShopIventory(identifier: String): Flowable<Shop> {
|
||||
|
|
@ -738,9 +725,6 @@ class ApiClientImpl(
|
|||
return apiService.seeNotifications(notificationIds).compose(configureApiCallObserver())
|
||||
}
|
||||
|
||||
override val content: Flowable<ContentResult>
|
||||
get() = apiService.getContent(languageCode).compose(configureApiCallObserver())
|
||||
|
||||
override fun openMysteryItem(): Flowable<Equipment> {
|
||||
return apiService.openMysteryItem().compose(configureApiCallObserver())
|
||||
}
|
||||
|
|
@ -749,9 +733,7 @@ class ApiClientImpl(
|
|||
return apiService.runCron().compose(configureApiCallObserver())
|
||||
}
|
||||
|
||||
override fun reroll(): Flowable<User> {
|
||||
return apiService.reroll().compose(configureApiCallObserver())
|
||||
}
|
||||
override suspend fun reroll(): User? = handleSuspendCall { apiService.reroll() }
|
||||
|
||||
override fun resetAccount(): Flowable<Void> {
|
||||
return apiService.resetAccount().compose(configureApiCallObserver())
|
||||
|
|
@ -825,8 +807,8 @@ class ApiClientImpl(
|
|||
return apiService.getTeamPlans().compose(configureApiCallObserver())
|
||||
}
|
||||
|
||||
override fun getTeamPlanTasks(teamID: String): Flowable<TaskList> {
|
||||
return apiService.getTeamPlanTasks(teamID).compose(configureApiCallObserver())
|
||||
override suspend fun getTeamPlanTasks(teamID: String): TaskList? {
|
||||
return processResponse(apiService.getTeamPlanTasks(teamID))
|
||||
}
|
||||
|
||||
override fun bulkAllocatePoints(
|
||||
|
|
@ -849,8 +831,7 @@ class ApiClientImpl(
|
|||
return apiService.retrieveMarketGear(languageCode).compose(configureApiCallObserver())
|
||||
}
|
||||
|
||||
override val worldState: Flowable<WorldState>
|
||||
get() = apiService.worldState.compose(configureApiCallObserver())
|
||||
override suspend fun getWorldState(): WorldState? = handleSuspendCall { apiService.worldState() }
|
||||
|
||||
companion object {
|
||||
fun createGsonFactory(): GsonConverterFactory {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import com.habitrpg.android.habitica.models.ContentResult
|
|||
import com.habitrpg.android.habitica.models.WorldState
|
||||
import com.habitrpg.android.habitica.models.inventory.SpecialItem
|
||||
import io.reactivex.rxjava3.core.Flowable
|
||||
import io.realm.RealmList
|
||||
import java.util.Date
|
||||
|
||||
class ContentRepositoryImpl<T : ContentLocalRepository>(
|
||||
|
|
@ -22,34 +23,33 @@ class ContentRepositoryImpl<T : ContentLocalRepository>(
|
|||
private var lastContentSync = 0L
|
||||
private var lastWorldStateSync = 0L
|
||||
|
||||
override fun retrieveContent(forced: Boolean): Flowable<ContentResult> {
|
||||
override suspend fun retrieveContent(forced: Boolean): ContentResult? {
|
||||
val now = Date().time
|
||||
return if (forced || now - this.lastContentSync > 300000) {
|
||||
if (forced || now - this.lastContentSync > 300000) {
|
||||
val content = apiClient.getContent() ?: return null
|
||||
lastContentSync = now
|
||||
apiClient.content.doOnNext {
|
||||
it.special.add(mysteryItem)
|
||||
localRepository.saveContent(it)
|
||||
}
|
||||
} else {
|
||||
Flowable.just(ContentResult())
|
||||
content.special = RealmList()
|
||||
content.special.add(mysteryItem)
|
||||
localRepository.saveContent(content)
|
||||
return content
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override fun retrieveWorldState(): Flowable<WorldState> {
|
||||
override suspend fun retrieveWorldState(): WorldState? {
|
||||
val now = Date().time
|
||||
return if (now - this.lastWorldStateSync > 3600000) {
|
||||
if (now - this.lastWorldStateSync > 3600000) {
|
||||
val state = apiClient.getWorldState() ?: return null
|
||||
lastWorldStateSync = now
|
||||
apiClient.worldState.doOnNext {
|
||||
localRepository.saveWorldState(it)
|
||||
for (event in it.events) {
|
||||
if (event.aprilFools != null && event.isCurrentlyActive) {
|
||||
AprilFoolsHandler.handle(event.aprilFools, event.end)
|
||||
}
|
||||
localRepository.save(state)
|
||||
for (event in state.events) {
|
||||
if (event.aprilFools != null && event.isCurrentlyActive) {
|
||||
AprilFoolsHandler.handle(event.aprilFools, event.end)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Flowable.just(WorldState())
|
||||
return state
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override fun getWorldState(): Flowable<WorldState> {
|
||||
|
|
|
|||
|
|
@ -31,13 +31,9 @@ class InventoryRepositoryImpl(
|
|||
userID: String,
|
||||
var appConfigManager: AppConfigManager
|
||||
) : BaseRepositoryImpl<InventoryLocalRepository>(localRepository, apiClient, userID), InventoryRepository {
|
||||
override fun getQuestContent(keys: List<String>): Flow<List<QuestContent>> {
|
||||
return localRepository.getQuestContent(keys)
|
||||
}
|
||||
override fun getQuestContent(keys: List<String>) = localRepository.getQuestContent(keys)
|
||||
|
||||
override fun getQuestContent(key: String): Flowable<QuestContent> {
|
||||
return localRepository.getQuestContent(key)
|
||||
}
|
||||
override fun getQuestContent(key: String) = localRepository.getQuestContent(key)
|
||||
|
||||
override fun getEquipment(searchedKeys: List<String>): Flowable<out List<Equipment>> {
|
||||
return localRepository.getEquipment(searchedKeys)
|
||||
|
|
@ -75,7 +71,7 @@ class InventoryRepositoryImpl(
|
|||
return localRepository.getOwnedItems(userID, includeZero)
|
||||
}
|
||||
|
||||
override fun getItemsFlowable(itemClass: Class<out Item>, keys: Array<String>): Flow<List<Item>> {
|
||||
override fun getItems(itemClass: Class<out Item>, keys: Array<String>): Flow<List<Item>> {
|
||||
return localRepository.getItemsFlowable(itemClass, keys)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import com.habitrpg.android.habitica.BuildConfig
|
|||
import com.habitrpg.android.habitica.data.ApiClient
|
||||
import com.habitrpg.android.habitica.data.SocialRepository
|
||||
import com.habitrpg.android.habitica.data.local.SocialLocalRepository
|
||||
import com.habitrpg.android.habitica.helpers.RxErrorHandler
|
||||
import com.habitrpg.android.habitica.helpers.ExceptionHandler
|
||||
import com.habitrpg.android.habitica.models.Achievement
|
||||
import com.habitrpg.android.habitica.models.inventory.Quest
|
||||
import com.habitrpg.android.habitica.models.members.Member
|
||||
|
|
@ -16,12 +16,10 @@ import com.habitrpg.android.habitica.models.social.GroupMembership
|
|||
import com.habitrpg.android.habitica.models.social.InboxConversation
|
||||
import com.habitrpg.android.habitica.models.user.User
|
||||
import io.reactivex.rxjava3.core.Flowable
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
import kotlinx.coroutines.MainScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
import retrofit2.HttpException
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import java.util.UUID
|
||||
|
||||
class SocialRepositoryImpl(
|
||||
|
|
@ -29,45 +27,31 @@ class SocialRepositoryImpl(
|
|||
apiClient: ApiClient,
|
||||
userID: String
|
||||
) : BaseRepositoryImpl<SocialLocalRepository>(localRepository, apiClient, userID), SocialRepository {
|
||||
override fun transferGroupOwnership(groupID: String, userID: String): Flowable<Group> {
|
||||
return localRepository.getGroupFlowable(groupID)
|
||||
.map {
|
||||
val group = localRepository.getUnmanagedCopy(it)
|
||||
group.leaderID = userID
|
||||
group
|
||||
}
|
||||
.flatMap {
|
||||
apiClient.updateGroup(it.id, it)
|
||||
}
|
||||
override suspend fun transferGroupOwnership(groupID: String, userID: String): Group? {
|
||||
val group = localRepository.getGroup(groupID).first()?.let { localRepository.getUnmanagedCopy(it) }
|
||||
group?.leaderID = userID
|
||||
return group?.let { apiClient.updateGroup(groupID, it) }
|
||||
}
|
||||
|
||||
override fun removeMemberFromGroup(groupID: String, userID: String): Flowable<List<Member>> {
|
||||
return apiClient.removeMemberFromGroup(groupID, userID)
|
||||
.flatMap {
|
||||
retrieveGroupMembers(groupID, true)
|
||||
}
|
||||
override suspend fun removeMemberFromGroup(groupID: String, userID: String): List<Member>? {
|
||||
apiClient.removeMemberFromGroup(groupID, userID)
|
||||
return retrievePartyMembers(groupID, true)
|
||||
}
|
||||
|
||||
override fun blockMember(userID: String): Flowable<List<String>> {
|
||||
return apiClient.blockMember(userID)
|
||||
}
|
||||
|
||||
override fun getGroupMembership(id: String): Flowable<GroupMembership> {
|
||||
return localRepository.getGroupMembership(userID, id)
|
||||
}
|
||||
override fun getGroupMembership(id: String) = localRepository.getGroupMembership(userID, id)
|
||||
|
||||
override fun getGroupMemberships(): Flowable<out List<GroupMembership>> {
|
||||
return localRepository.getGroupMemberships(userID)
|
||||
}
|
||||
|
||||
override fun retrieveGroupChat(groupId: String): Single<List<ChatMessage>> {
|
||||
return apiClient.listGroupChat(groupId)
|
||||
.flatMap { Flowable.fromIterable(it) }
|
||||
.map { chatMessage ->
|
||||
chatMessage.groupId = groupId
|
||||
chatMessage
|
||||
}
|
||||
.toList()
|
||||
override suspend fun retrieveGroupChat(groupId: String): List<ChatMessage>? {
|
||||
val messages = apiClient.listGroupChat(groupId)
|
||||
messages?.forEach { it.groupId = groupId }
|
||||
return messages
|
||||
}
|
||||
|
||||
override fun getGroupChat(groupId: String): Flowable<out List<ChatMessage>> {
|
||||
|
|
@ -75,7 +59,7 @@ class SocialRepositoryImpl(
|
|||
}
|
||||
|
||||
override fun markMessagesSeen(seenGroupId: String) {
|
||||
apiClient.seenMessages(seenGroupId).subscribe({ }, RxErrorHandler.handleEmptyError())
|
||||
apiClient.seenMessages(seenGroupId).subscribe({ }, ExceptionHandler.rx())
|
||||
}
|
||||
|
||||
override fun flagMessage(chatMessageID: String, additionalInfo: String, groupID: String?): Flowable<Void> {
|
||||
|
|
@ -131,76 +115,70 @@ class SocialRepositoryImpl(
|
|||
return postGroupChat(groupId, messageObject)
|
||||
}
|
||||
|
||||
override fun retrieveGroup(id: String): Flowable<Group> {
|
||||
return Flowable.zip(
|
||||
apiClient.getGroup(id).doOnNext { localRepository.saveGroup(it) },
|
||||
retrieveGroupChat(id)
|
||||
.toFlowable()
|
||||
) { group, _ ->
|
||||
group
|
||||
}.doOnError {
|
||||
if (it is HttpException && it.code() == 404) {
|
||||
MainScope().launch {
|
||||
val group = localRepository.getGroup(id).first()
|
||||
if (group != null) {
|
||||
localRepository.delete(group)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
override suspend fun retrieveGroup(id: String): Group? {
|
||||
val group = apiClient.getGroup(id)
|
||||
group?.let { localRepository.saveGroup(it) }
|
||||
retrieveGroupChat(id)
|
||||
return group
|
||||
}
|
||||
|
||||
override fun getGroup(id: String?) = id?.let { localRepository.getGroup(it) } ?: emptyFlow()
|
||||
override fun getGroupFlowable(id: String?): Flowable<Group> = id?.let { localRepository.getGroupFlowable(it) } ?: Flowable.empty()
|
||||
|
||||
override fun leaveGroup(id: String?, keepChallenges: Boolean): Flowable<Group> {
|
||||
override fun getGroup(id: String?): Flow<Group?> {
|
||||
if (id?.isNotBlank() != true) {
|
||||
return Flowable.empty()
|
||||
return emptyFlow()
|
||||
}
|
||||
return apiClient.leaveGroup(id, if (keepChallenges) "remain-in-challenges" else "leave-challenges")
|
||||
.doOnNext { localRepository.updateMembership(userID, id, false) }
|
||||
.flatMapMaybe { localRepository.getGroupFlowable(id).firstElement() }
|
||||
return localRepository.getGroup(id)
|
||||
}
|
||||
|
||||
override fun joinGroup(id: String?): Flowable<Group> {
|
||||
override suspend fun leaveGroup(id: String?, keepChallenges: Boolean): Group? {
|
||||
if (id?.isNotBlank() != true) {
|
||||
return Flowable.empty()
|
||||
return null
|
||||
}
|
||||
return apiClient.joinGroup(id)
|
||||
.doOnNext { group ->
|
||||
localRepository.updateMembership(userID, id, true)
|
||||
localRepository.save(group)
|
||||
}
|
||||
|
||||
apiClient.leaveGroup(id, if (keepChallenges) "remain-in-challenges" else "leave-challenges")
|
||||
localRepository.updateMembership(userID, id, false)
|
||||
return localRepository.getGroup(id).firstOrNull()
|
||||
}
|
||||
|
||||
override fun createGroup(
|
||||
override suspend fun joinGroup(id: String?): Group? {
|
||||
if (id?.isNotBlank() != true) {
|
||||
return null
|
||||
}
|
||||
val group = apiClient.joinGroup(id)
|
||||
group?.let {
|
||||
localRepository.updateMembership(userID, id, true)
|
||||
localRepository.save(group)
|
||||
}
|
||||
return group
|
||||
}
|
||||
|
||||
override suspend fun createGroup(
|
||||
name: String?,
|
||||
description: String?,
|
||||
leader: String?,
|
||||
type: String?,
|
||||
privacy: String?,
|
||||
leaderCreateChallenge: Boolean?
|
||||
): Flowable<Group> {
|
||||
): Group? {
|
||||
val group = Group()
|
||||
group.name = name
|
||||
group.description = description
|
||||
group.type = type
|
||||
group.leaderID = leader
|
||||
group.privacy = privacy
|
||||
return apiClient.createGroup(group).doOnNext {
|
||||
localRepository.save(it)
|
||||
}
|
||||
val savedGroup = apiClient.createGroup(group)
|
||||
savedGroup?.let { localRepository.save(it) }
|
||||
return savedGroup
|
||||
}
|
||||
|
||||
override fun updateGroup(
|
||||
override suspend fun updateGroup(
|
||||
group: Group?,
|
||||
name: String?,
|
||||
description: String?,
|
||||
leader: String?,
|
||||
leaderCreateChallenge: Boolean?
|
||||
): Flowable<Group> {
|
||||
): Group? {
|
||||
if (group == null) {
|
||||
return Flowable.empty()
|
||||
return null
|
||||
}
|
||||
val copiedGroup = localRepository.getUnmanagedCopy(group)
|
||||
copiedGroup.name = name
|
||||
|
|
@ -224,21 +202,21 @@ class SocialRepositoryImpl(
|
|||
}
|
||||
}
|
||||
|
||||
override fun getGroups(type: String): Flowable<out List<Group>> = localRepository.getGroups(type)
|
||||
override fun getGroups(type: String) = localRepository.getGroups(type)
|
||||
|
||||
override fun getPublicGuilds(): Flowable<out List<Group>> = localRepository.getPublicGuilds()
|
||||
override fun getPublicGuilds() = localRepository.getPublicGuilds()
|
||||
|
||||
override fun getInboxConversations(): Flowable<out List<InboxConversation>> = localRepository.getInboxConversation(userID)
|
||||
override fun getInboxConversations() = localRepository.getInboxConversation(userID)
|
||||
|
||||
override fun getInboxMessages(replyToUserID: String?): Flowable<out List<ChatMessage>> = localRepository.getInboxMessages(userID, replyToUserID)
|
||||
override fun getInboxMessages(replyToUserID: String?) = localRepository.getInboxMessages(userID, replyToUserID)
|
||||
|
||||
override fun retrieveInboxMessages(uuid: String, page: Int): Flowable<List<ChatMessage>> {
|
||||
return apiClient.retrieveInboxMessages(uuid, page).doOnNext { messages ->
|
||||
messages.forEach {
|
||||
it.isInboxMessage = true
|
||||
}
|
||||
localRepository.saveInboxMessages(userID, uuid, messages, page)
|
||||
override suspend fun retrieveInboxMessages(uuid: String, page: Int): List<ChatMessage>? {
|
||||
val messages = apiClient.retrieveInboxMessages(uuid, page) ?: return null
|
||||
messages.forEach {
|
||||
it.isInboxMessage = true
|
||||
}
|
||||
localRepository.saveInboxMessages(userID, uuid, messages, page)
|
||||
return messages
|
||||
}
|
||||
|
||||
override fun retrieveInboxConversations(): Flowable<List<InboxConversation>> {
|
||||
|
|
@ -247,29 +225,32 @@ class SocialRepositoryImpl(
|
|||
}
|
||||
}
|
||||
|
||||
override fun postPrivateMessage(recipientId: String, messageObject: HashMap<String, String>): Flowable<List<ChatMessage>> {
|
||||
return apiClient.postPrivateMessage(messageObject).flatMap { retrieveInboxMessages(recipientId, 0) }
|
||||
override suspend fun postPrivateMessage(recipientId: String, messageObject: HashMap<String, String>): List<ChatMessage>? {
|
||||
val message = apiClient.postPrivateMessage(messageObject)
|
||||
return retrieveInboxMessages(recipientId, 0)
|
||||
}
|
||||
|
||||
override fun postPrivateMessage(recipientId: String, message: String): Flowable<List<ChatMessage>> {
|
||||
override suspend fun postPrivateMessage(recipientId: String, message: String): List<ChatMessage>? {
|
||||
val messageObject = HashMap<String, String>()
|
||||
messageObject["message"] = message
|
||||
messageObject["toUserId"] = recipientId
|
||||
return postPrivateMessage(recipientId, messageObject)
|
||||
}
|
||||
|
||||
override fun getGroupMembers(id: String) = localRepository.getGroupMembers(id)
|
||||
override suspend fun getPartyMembers(id: String) = localRepository.getPartyMembers(id)
|
||||
override suspend fun getGroupMembers(id: String) = localRepository.getGroupMembers(id)
|
||||
|
||||
override fun retrieveGroupMembers(id: String, includeAllPublicFields: Boolean): Flowable<List<Member>> {
|
||||
return apiClient.getGroupMembers(id, includeAllPublicFields)
|
||||
.doOnNext { members -> localRepository.saveGroupMembers(id, members) }
|
||||
override suspend fun retrievePartyMembers(id: String, includeAllPublicFields: Boolean): List<Member>? {
|
||||
val members = apiClient.getGroupMembers(id, includeAllPublicFields)
|
||||
members?.let { localRepository.savePartyMembers(id, it) }
|
||||
return members
|
||||
}
|
||||
|
||||
override fun inviteToGroup(id: String, inviteData: Map<String, Any>): Flowable<List<Void>> = apiClient.inviteToGroup(id, inviteData)
|
||||
|
||||
override fun getMember(userId: String?): Flowable<Member> {
|
||||
override suspend fun retrieveMember(userId: String?): Member? {
|
||||
return if (userId == null) {
|
||||
Flowable.empty()
|
||||
null
|
||||
} else {
|
||||
try {
|
||||
apiClient.getMember(UUID.fromString(userId).toString())
|
||||
|
|
@ -279,8 +260,8 @@ class SocialRepositoryImpl(
|
|||
}
|
||||
}
|
||||
|
||||
override fun getMemberWithUsername(username: String?): Flowable<Member> {
|
||||
return getMember(username)
|
||||
override suspend fun retrieveMemberWithUsername(username: String?): Member? {
|
||||
return retrieveMember(username)
|
||||
}
|
||||
|
||||
override fun findUsernames(username: String, context: String?, id: String?): Flowable<List<FindUsernameResult>> {
|
||||
|
|
@ -315,7 +296,7 @@ class SocialRepositoryImpl(
|
|||
}
|
||||
}
|
||||
|
||||
override fun getUserGroups(type: String?): Flowable<out List<Group>> = localRepository.getUserGroups(userID, type)
|
||||
override fun getUserGroups(type: String?) = localRepository.getUserGroups(userID, type)
|
||||
|
||||
override fun acceptQuest(user: User?, partyId: String): Flowable<Void> {
|
||||
return apiClient.acceptQuest(partyId)
|
||||
|
|
|
|||
|
|
@ -5,25 +5,26 @@ import com.habitrpg.android.habitica.data.ApiClient
|
|||
import com.habitrpg.android.habitica.data.TaskRepository
|
||||
import com.habitrpg.android.habitica.data.local.TaskLocalRepository
|
||||
import com.habitrpg.android.habitica.helpers.AppConfigManager
|
||||
import com.habitrpg.android.habitica.helpers.RxErrorHandler
|
||||
import com.habitrpg.android.habitica.helpers.ExceptionHandler
|
||||
import com.habitrpg.android.habitica.interactors.ScoreTaskLocallyInteractor
|
||||
import com.habitrpg.android.habitica.models.BaseMainObject
|
||||
import com.habitrpg.android.habitica.models.responses.BulkTaskScoringData
|
||||
import com.habitrpg.shared.habitica.models.responses.TaskDirection
|
||||
import com.habitrpg.shared.habitica.models.responses.TaskDirectionData
|
||||
import com.habitrpg.shared.habitica.models.responses.TaskScoringResult
|
||||
import com.habitrpg.android.habitica.models.tasks.ChecklistItem
|
||||
import com.habitrpg.android.habitica.models.tasks.Task
|
||||
import com.habitrpg.android.habitica.models.tasks.TaskList
|
||||
import com.habitrpg.shared.habitica.models.tasks.TaskType
|
||||
import com.habitrpg.shared.habitica.models.tasks.TasksOrder
|
||||
import com.habitrpg.android.habitica.models.user.OwnedItem
|
||||
import com.habitrpg.android.habitica.models.user.User
|
||||
import com.habitrpg.android.habitica.proxy.AnalyticsManager
|
||||
import com.habitrpg.shared.habitica.models.responses.TaskDirection
|
||||
import com.habitrpg.shared.habitica.models.responses.TaskDirectionData
|
||||
import com.habitrpg.shared.habitica.models.responses.TaskScoringResult
|
||||
import com.habitrpg.shared.habitica.models.tasks.TaskType
|
||||
import com.habitrpg.shared.habitica.models.tasks.TasksOrder
|
||||
import io.reactivex.rxjava3.core.Flowable
|
||||
import io.reactivex.rxjava3.core.Maybe
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.flow.map
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
|
|
@ -49,9 +50,10 @@ class TaskRepositoryImpl(
|
|||
localRepository.saveTasks(userId, order, tasks)
|
||||
}
|
||||
|
||||
override fun retrieveTasks(userId: String, tasksOrder: TasksOrder): Flowable<TaskList> {
|
||||
return this.apiClient.tasks
|
||||
.doOnNext { res -> this.localRepository.saveTasks(userId, tasksOrder, res) }
|
||||
override suspend fun retrieveTasks(userId: String, tasksOrder: TasksOrder): TaskList? {
|
||||
val tasks = apiClient.getTasks() ?: return null
|
||||
this.localRepository.saveTasks(userId, tasksOrder, tasks)
|
||||
return tasks
|
||||
}
|
||||
|
||||
override fun retrieveCompletedTodos(userId: String?): Flowable<TaskList> {
|
||||
|
|
@ -69,13 +71,13 @@ class TaskRepositoryImpl(
|
|||
}
|
||||
|
||||
@Suppress("ReturnCount")
|
||||
override fun taskChecked(
|
||||
override suspend fun taskChecked(
|
||||
user: User?,
|
||||
task: Task,
|
||||
up: Boolean,
|
||||
force: Boolean,
|
||||
notifyFunc: ((TaskScoringResult) -> Unit)?
|
||||
): Flowable<TaskScoringResult> {
|
||||
): TaskScoringResult? {
|
||||
val localData = if (user != null && appConfigManager.enableLocalTaskScoring()) {
|
||||
ScoreTaskLocallyInteractor.score(user, task, if (up) TaskDirection.UP else TaskDirection.DOWN)
|
||||
} else null
|
||||
|
|
@ -89,41 +91,33 @@ class TaskRepositoryImpl(
|
|||
val now = Date().time
|
||||
val id = task.id
|
||||
if (lastTaskAction > now - 500 && !force || id == null) {
|
||||
return Flowable.empty()
|
||||
return null
|
||||
}
|
||||
|
||||
lastTaskAction = now
|
||||
return this.apiClient.postTaskDirection(id, (if (up) TaskDirection.UP else TaskDirection.DOWN).text)
|
||||
.flatMapMaybe {
|
||||
// There are cases where the user object is not set correctly. So the app refetches it as a fallback
|
||||
if (user == null) {
|
||||
localRepository.getUser(userID).firstElement()
|
||||
} else {
|
||||
Maybe.just(user)
|
||||
}.map { user -> Pair(it, user) }
|
||||
}
|
||||
.map { (res, user): Pair<TaskDirectionData, User> ->
|
||||
// save local task changes
|
||||
val res = this.apiClient.postTaskDirection(id, (if (up) TaskDirection.UP else TaskDirection.DOWN).text) ?: return null
|
||||
// There are cases where the user object is not set correctly. So the app refetches it as a fallback
|
||||
val thisUser = user ?: localRepository.getUser(userID).firstOrNull() ?: return null
|
||||
// save local task changes
|
||||
|
||||
analyticsManager.logEvent(
|
||||
"task_scored",
|
||||
bundleOf(
|
||||
Pair("type", task.type),
|
||||
Pair("scored_up", up),
|
||||
Pair("value", task.value)
|
||||
)
|
||||
)
|
||||
if (res.lvl == 0) {
|
||||
// Team tasks that require approval have weird data that we should just ignore.
|
||||
return@map TaskScoringResult()
|
||||
}
|
||||
val result = TaskScoringResult(res, user.stats)
|
||||
if (localData == null) {
|
||||
notifyFunc?.invoke(result)
|
||||
}
|
||||
handleTaskResponse(user, res, task, up, localData?.delta ?: 0f)
|
||||
result
|
||||
}
|
||||
analyticsManager.logEvent(
|
||||
"task_scored",
|
||||
bundleOf(
|
||||
Pair("type", task.type),
|
||||
Pair("scored_up", up),
|
||||
Pair("value", task.value)
|
||||
)
|
||||
)
|
||||
if (res.lvl == 0) {
|
||||
// Team tasks that require approval have weird data that we should just ignore.
|
||||
return TaskScoringResult()
|
||||
}
|
||||
val result = TaskScoringResult(res, thisUser.stats)
|
||||
if (localData == null) {
|
||||
notifyFunc?.invoke(result)
|
||||
}
|
||||
handleTaskResponse(thisUser, res, task, up, localData?.delta ?: 0f)
|
||||
return result
|
||||
}
|
||||
|
||||
override fun bulkScoreTasks(data: List<Map<String, String>>): Flowable<BulkTaskScoringData> {
|
||||
|
|
@ -143,9 +137,13 @@ class TaskRepositoryImpl(
|
|||
if (bgTask.type != TaskType.REWARD && (bgTask.value - localDelta) + res.delta != bgTask.value) {
|
||||
bgTask.value = (bgTask.value - localDelta) + res.delta
|
||||
if (TaskType.DAILY == bgTask.type || TaskType.TODO == bgTask.type) {
|
||||
bgTask.completed = up
|
||||
if (TaskType.DAILY == bgTask.type && up) {
|
||||
bgTask.streak = (bgTask.streak ?: 0) + 1
|
||||
bgTask.completeForUser(userID, up)
|
||||
if (TaskType.DAILY == bgTask.type) {
|
||||
if (up) {
|
||||
bgTask.streak = (bgTask.streak ?: 0) + 1
|
||||
} else {
|
||||
bgTask.streak = (bgTask.streak ?: 0) - 1
|
||||
}
|
||||
}
|
||||
} else if (TaskType.HABIT == bgTask.type) {
|
||||
if (up) {
|
||||
|
|
@ -190,31 +188,29 @@ class TaskRepositoryImpl(
|
|||
}
|
||||
}
|
||||
|
||||
override fun taskChecked(
|
||||
override suspend fun taskChecked(
|
||||
user: User?,
|
||||
taskId: String,
|
||||
up: Boolean,
|
||||
force: Boolean,
|
||||
notifyFunc: ((TaskScoringResult) -> Unit)?
|
||||
): Maybe<TaskScoringResult> {
|
||||
return localRepository.getTask(taskId).firstElement()
|
||||
.flatMap { task -> taskChecked(user, task, up, force, notifyFunc).singleElement() }
|
||||
): TaskScoringResult? {
|
||||
val task = localRepository.getTask(taskId).firstOrNull() ?: return null
|
||||
return taskChecked(user, task, up, force, notifyFunc)
|
||||
}
|
||||
|
||||
override fun scoreChecklistItem(taskId: String, itemId: String): Flowable<Task> {
|
||||
return apiClient.scoreChecklistItem(taskId, itemId)
|
||||
.flatMapMaybe { localRepository.getTask(taskId).firstElement() }
|
||||
.doOnNext { task ->
|
||||
val updatedItem: ChecklistItem? = task.checklist?.lastOrNull { itemId == it.id }
|
||||
if (updatedItem != null) {
|
||||
localRepository.modify(updatedItem) { liveItem -> liveItem.completed = !liveItem.completed }
|
||||
}
|
||||
}
|
||||
override suspend fun scoreChecklistItem(taskId: String, itemId: String): Task? {
|
||||
val task = apiClient.scoreChecklistItem(taskId, itemId)
|
||||
val updatedItem: ChecklistItem? = task?.checklist?.lastOrNull { itemId == it.id }
|
||||
if (updatedItem != null) {
|
||||
localRepository.modify(updatedItem) { liveItem -> liveItem.completed = !liveItem.completed }
|
||||
}
|
||||
return task
|
||||
}
|
||||
|
||||
override fun getTask(taskId: String): Flowable<Task> = localRepository.getTask(taskId)
|
||||
override fun getTask(taskId: String) = localRepository.getTask(taskId)
|
||||
|
||||
override fun getTaskCopy(taskId: String): Flowable<Task> = localRepository.getTaskCopy(taskId)
|
||||
override fun getTaskCopy(taskId: String) = localRepository.getTaskCopy(taskId)
|
||||
|
||||
override fun createTask(task: Task, force: Boolean): Flowable<Task> {
|
||||
val now = Date().time
|
||||
|
|
@ -305,15 +301,14 @@ class TaskRepositoryImpl(
|
|||
.doOnSuccess { localRepository.updateTaskPositions(it) }
|
||||
}
|
||||
|
||||
override fun getUnmanagedTask(taskid: String): Flowable<Task> =
|
||||
getTask(taskid).map { localRepository.getUnmanagedCopy(it) }
|
||||
override fun getUnmanagedTask(taskid: String) = getTask(taskid).map { localRepository.getUnmanagedCopy(it) }
|
||||
|
||||
override fun updateTaskInBackground(task: Task) {
|
||||
updateTask(task).subscribe({ }, RxErrorHandler.handleEmptyError())
|
||||
updateTask(task).subscribe({ }, ExceptionHandler.rx())
|
||||
}
|
||||
|
||||
override fun createTaskInBackground(task: Task) {
|
||||
createTask(task).subscribe({ }, RxErrorHandler.handleEmptyError())
|
||||
createTask(task).subscribe({ }, ExceptionHandler.rx())
|
||||
}
|
||||
|
||||
override fun getTaskCopies(userId: String): Flow<List<Task>> =
|
||||
|
|
|
|||
|
|
@ -1,14 +1,12 @@
|
|||
package com.habitrpg.android.habitica.data.implementation
|
||||
|
||||
import androidx.core.os.bundleOf
|
||||
import com.habitrpg.android.habitica.data.ApiClient
|
||||
import com.habitrpg.android.habitica.data.TaskRepository
|
||||
import com.habitrpg.android.habitica.data.UserRepository
|
||||
import com.habitrpg.android.habitica.data.local.UserLocalRepository
|
||||
import com.habitrpg.android.habitica.models.user.UserQuestStatus
|
||||
import com.habitrpg.android.habitica.extensions.filterMapEmpty
|
||||
import com.habitrpg.android.habitica.helpers.AppConfigManager
|
||||
import com.habitrpg.android.habitica.helpers.RxErrorHandler
|
||||
import com.habitrpg.android.habitica.helpers.ExceptionHandler
|
||||
import com.habitrpg.android.habitica.models.Achievement
|
||||
import com.habitrpg.android.habitica.models.QuestAchievement
|
||||
import com.habitrpg.android.habitica.models.Skill
|
||||
|
|
@ -17,9 +15,11 @@ import com.habitrpg.android.habitica.models.inventory.Customization
|
|||
import com.habitrpg.android.habitica.models.responses.SkillResponse
|
||||
import com.habitrpg.android.habitica.models.responses.UnlockResponse
|
||||
import com.habitrpg.android.habitica.models.social.Group
|
||||
import com.habitrpg.android.habitica.models.social.GroupMembership
|
||||
import com.habitrpg.android.habitica.models.tasks.Task
|
||||
import com.habitrpg.android.habitica.models.user.Stats
|
||||
import com.habitrpg.android.habitica.models.user.User
|
||||
import com.habitrpg.android.habitica.models.user.UserQuestStatus
|
||||
import com.habitrpg.android.habitica.proxy.AnalyticsManager
|
||||
import com.habitrpg.common.habitica.extensions.Optional
|
||||
import com.habitrpg.shared.habitica.models.responses.TaskDirection
|
||||
|
|
@ -28,7 +28,9 @@ import com.habitrpg.shared.habitica.models.tasks.Attribute
|
|||
import io.reactivex.rxjava3.core.Flowable
|
||||
import io.reactivex.rxjava3.core.Maybe
|
||||
import io.reactivex.rxjava3.functions.BiFunction
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.Date
|
||||
import java.util.GregorianCalendar
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
|
@ -70,45 +72,39 @@ class UserRepositoryImpl(
|
|||
return updateUser(userID, key, value)
|
||||
}
|
||||
|
||||
override fun retrieveUser(withTasks: Boolean): Flowable<User> =
|
||||
retrieveUser(withTasks, false)
|
||||
|
||||
@Suppress("ReturnCount")
|
||||
override fun retrieveUser(withTasks: Boolean, forced: Boolean, overrideExisting: Boolean): Flowable<User> {
|
||||
override suspend fun retrieveUser(withTasks: Boolean, forced: Boolean, overrideExisting: Boolean): User? {
|
||||
// Only retrieve again after 3 minutes or it's forced.
|
||||
if (forced || this.lastSync == null || Date().time - (this.lastSync?.time ?: 0) > 180000) {
|
||||
val user = apiClient.retrieveUser(withTasks) ?: return null
|
||||
lastSync = Date()
|
||||
return apiClient.retrieveUser(withTasks)
|
||||
.doOnNext { localRepository.saveUser(it, overrideExisting) }
|
||||
.doOnNext { user ->
|
||||
if (withTasks) {
|
||||
val id = user.id
|
||||
val tasksOrder = user.tasksOrder
|
||||
val tasks = user.tasks
|
||||
if (id != null && tasksOrder != null && tasks != null) {
|
||||
taskRepository.saveTasks(id, tasksOrder, tasks)
|
||||
}
|
||||
}
|
||||
}
|
||||
.flatMap { user ->
|
||||
val calendar = GregorianCalendar()
|
||||
val timeZone = calendar.timeZone
|
||||
val offset = -TimeUnit.MINUTES.convert(timeZone.getOffset(calendar.timeInMillis).toLong(), TimeUnit.MILLISECONDS)
|
||||
if (offset.toInt() != (user.preferences?.timezoneOffset ?: 0)) {
|
||||
return@flatMap updateUser(user.id ?: "", "preferences.timezoneOffset", offset.toString())
|
||||
} else {
|
||||
return@flatMap Flowable.just(user)
|
||||
}
|
||||
localRepository.saveUser(user)
|
||||
if (withTasks) {
|
||||
val id = user.id
|
||||
val tasksOrder = user.tasksOrder
|
||||
val tasks = user.tasks
|
||||
if (id != null && tasksOrder != null && tasks != null) {
|
||||
taskRepository.saveTasks(id, tasksOrder, tasks)
|
||||
}
|
||||
}
|
||||
val calendar = GregorianCalendar()
|
||||
val timeZone = calendar.timeZone
|
||||
val offset = -TimeUnit.MINUTES.convert(timeZone.getOffset(calendar.timeInMillis).toLong(), TimeUnit.MILLISECONDS)
|
||||
/*if (offset.toInt() != user.preferences?.timezoneOffset ?: 0) {
|
||||
return@flatMap updateUser(user.id ?: "", "preferences.timezoneOffset", offset.toString())
|
||||
} else {
|
||||
return@flatMap Flowable.just(user)
|
||||
}*/
|
||||
return user
|
||||
} else {
|
||||
return localRepository.getUserFlowable(userID).take(1)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
override fun revive(): Flowable<User> = zipWithLiveUser(apiClient.revive()) { newUser, user ->
|
||||
mergeUser(user, newUser)
|
||||
override suspend fun revive(): User? {
|
||||
apiClient.revive()
|
||||
return retrieveUser(false, true)
|
||||
}
|
||||
.flatMap { retrieveUser(false, true) }
|
||||
|
||||
override fun resetTutorial(): Maybe<User> {
|
||||
return localRepository.getTutorialSteps()
|
||||
|
|
@ -123,9 +119,13 @@ class UserRepositoryImpl(
|
|||
.flatMap { updateData -> updateUser(updateData).firstElement() }
|
||||
}
|
||||
|
||||
override fun sleep(user: User): Flowable<User> {
|
||||
localRepository.modify(user) { it.preferences?.sleep = !(it.preferences?.sleep ?: false) }
|
||||
return apiClient.sleep().map { user }
|
||||
override suspend fun sleep(user: User): User {
|
||||
val newValue = !(user.preferences?.sleep ?: false)
|
||||
localRepository.modify(user) { it.preferences?.sleep = newValue }
|
||||
if (apiClient.sleep() != true) {
|
||||
localRepository.modify(user) { it.preferences?.sleep = !newValue }
|
||||
}
|
||||
return user
|
||||
}
|
||||
|
||||
override fun getSkills(user: User): Flowable<out List<Skill>> =
|
||||
|
|
@ -137,7 +137,7 @@ class UserRepositoryImpl(
|
|||
override fun useSkill(key: String, target: String?, taskId: String): Flowable<SkillResponse> {
|
||||
return zipWithLiveUser(apiClient.useSkill(key, target ?: "", taskId)) { response, user ->
|
||||
response.hpDiff = (response.user?.stats?.hp ?: 0.0) - (user.stats?.hp ?: 0.0)
|
||||
response.expDiff = (response.user?.stats?.exp ?: 0.0) - (user.stats?.exp ?: 0.0)
|
||||
response.expDiff =(response.user?.stats?.exp ?: 0.0) - (user.stats?.exp ?: 0.0)
|
||||
response.goldDiff = (response.user?.stats?.gp ?: 0.0) - (user.stats?.gp ?: 0.0)
|
||||
response.damage = (response.user?.party?.quest?.progress?.up ?: 0.0f) - (user.party?.quest?.progress?.up ?: 0.0f)
|
||||
response.user?.let { mergeUser(user, it) }
|
||||
|
|
@ -148,7 +148,7 @@ class UserRepositoryImpl(
|
|||
override fun useSkill(key: String, target: String?): Flowable<SkillResponse> {
|
||||
return zipWithLiveUser(apiClient.useSkill(key, target ?: "")) { response, user ->
|
||||
response.hpDiff = (response.user?.stats?.hp ?: 0.0) - (user.stats?.hp ?: 0.0)
|
||||
response.expDiff = (response.user?.stats?.exp ?: 0.0) - (user.stats?.exp ?: 0.0)
|
||||
response.expDiff =(response.user?.stats?.exp ?: 0.0) - (user.stats?.exp ?: 0.0)
|
||||
response.goldDiff = (response.user?.stats?.gp ?: 0.0) - (user.stats?.gp ?: 0.0)
|
||||
response.damage = (response.user?.party?.quest?.progress?.up ?: 0.0f) - (user.party?.quest?.progress?.up ?: 0.0f)
|
||||
response.user?.let { mergeUser(user, it) }
|
||||
|
|
@ -156,12 +156,16 @@ class UserRepositoryImpl(
|
|||
}
|
||||
}
|
||||
|
||||
override fun changeClass(): Flowable<User> = apiClient.changeClass().flatMap { retrieveUser(withTasks = false, forced = true) }
|
||||
override suspend fun disableClasses(): User? = apiClient.disableClasses()
|
||||
|
||||
override fun disableClasses(): Flowable<User> = apiClient.disableClasses().flatMap { retrieveUser(withTasks = false, forced = true) }
|
||||
override suspend fun changeClass(selectedClass: String?): User? {
|
||||
apiClient.changeClass(selectedClass)
|
||||
return retrieveUser(false, forced = true)
|
||||
}
|
||||
|
||||
override fun changeClass(selectedClass: String): Flowable<User> = apiClient.changeClass(selectedClass)
|
||||
.flatMap { retrieveUser(false, forced = true) }
|
||||
override fun unlockPath(customization: Customization): Flowable<UnlockResponse> {
|
||||
return unlockPath(customization.path, customization.price ?: 0)
|
||||
}
|
||||
|
||||
override fun unlockPath(path: String, price: Int): Flowable<UnlockResponse> {
|
||||
return zipWithLiveUser(apiClient.unlockPath(path)) { unlockResponse, copiedUser ->
|
||||
|
|
@ -175,11 +179,7 @@ class UserRepositoryImpl(
|
|||
}
|
||||
}
|
||||
|
||||
override fun unlockPath(customization: Customization): Flowable<UnlockResponse> {
|
||||
return unlockPath(customization.unlockPath, customization.price ?: 0)
|
||||
}
|
||||
|
||||
override fun runCron() {
|
||||
override suspend fun runCron() {
|
||||
runCron(ArrayList())
|
||||
}
|
||||
|
||||
|
|
@ -188,9 +188,8 @@ class UserRepositoryImpl(
|
|||
return localRepository.getUserQuestStatus(userID)
|
||||
}
|
||||
|
||||
override fun reroll(): Flowable<User> {
|
||||
override suspend fun reroll(): User? {
|
||||
return apiClient.reroll()
|
||||
.flatMap { retrieveUser(true, true, true) }
|
||||
}
|
||||
|
||||
override fun readNotifications(notificationIds: Map<String, List<String>>): Flowable<List<Any>> =
|
||||
|
|
@ -210,8 +209,9 @@ class UserRepositoryImpl(
|
|||
.doOnNext { apiClient.setLanguageCode(languageCode) }
|
||||
}
|
||||
|
||||
override fun resetAccount(): Flowable<User> {
|
||||
return apiClient.resetAccount().flatMap { retrieveUser(withTasks = true, forced = true) }
|
||||
override suspend fun resetAccount(): User? {
|
||||
apiClient.resetAccount()
|
||||
return retrieveUser(withTasks = true, forced = true)
|
||||
}
|
||||
|
||||
override fun deleteAccount(password: String): Flowable<Void> =
|
||||
|
|
@ -262,7 +262,7 @@ class UserRepositoryImpl(
|
|||
liveUser.stats?.points = liveUser.stats?.points?.dec()
|
||||
}
|
||||
},
|
||||
RxErrorHandler.handleEmptyError()
|
||||
ExceptionHandler.rx()
|
||||
)
|
||||
return zipWithLiveUser(apiClient.allocatePoint(stat.value)) { stats, user ->
|
||||
localRepository.modify(user) { liveUser ->
|
||||
|
|
@ -295,26 +295,33 @@ class UserRepositoryImpl(
|
|||
stats
|
||||
}
|
||||
|
||||
override fun runCron(tasks: MutableList<Task>) {
|
||||
var observable: Maybe<Any> = localRepository.getUserFlowable(userID).firstElement()
|
||||
.filter { it.needsCron }
|
||||
.map { user ->
|
||||
localRepository.modify(user) { liveUser ->
|
||||
liveUser.needsCron = false
|
||||
liveUser.lastCron = Date()
|
||||
override suspend fun runCron(tasks: MutableList<Task>) {
|
||||
withContext(Dispatchers.Main) {
|
||||
var observable: Maybe<Any> = localRepository.getUserFlowable(userID).firstElement()
|
||||
.filter { it.needsCron }
|
||||
.map { user ->
|
||||
localRepository.modify(user) { liveUser ->
|
||||
liveUser.needsCron = false
|
||||
liveUser.lastCron = Date()
|
||||
}
|
||||
user
|
||||
}
|
||||
user
|
||||
if (tasks.isNotEmpty()) {
|
||||
val scoringList = mutableListOf<Map<String, String>>()
|
||||
for (task in tasks) {
|
||||
val map = mutableMapOf<String, String>()
|
||||
map["id"] = task.id ?: ""
|
||||
map["direction"] = TaskDirection.UP.text
|
||||
scoringList.add(map)
|
||||
}
|
||||
observable = observable.flatMap { taskRepository.bulkScoreTasks(scoringList).firstElement() }
|
||||
}
|
||||
if (tasks.isNotEmpty()) {
|
||||
val scoringList = tasks.map { mapOf(Pair("id", it.id ?: ""), Pair("direction", TaskDirection.UP.text)) }
|
||||
observable = observable.flatMap { taskRepository.bulkScoreTasks(scoringList).firstElement() }
|
||||
observable.flatMap { apiClient.runCron().firstElement() }
|
||||
// .flatMap {
|
||||
// this.retrieveUser(withTasks = true, forced = true)
|
||||
// }
|
||||
.subscribe({ }, ExceptionHandler.rx())
|
||||
}
|
||||
observable.flatMap { apiClient.runCron().firstElement() }
|
||||
.flatMap { this.retrieveUser(withTasks = true, forced = true).firstElement() }
|
||||
.subscribe({ }, {
|
||||
analyticsManager.logEvent("cron failed", bundleOf(Pair("error", it.localizedMessage)))
|
||||
RxErrorHandler.reportError(it)
|
||||
})
|
||||
}
|
||||
|
||||
override fun useCustomization(type: String, category: String?, identifier: String): Flowable<User> {
|
||||
|
|
@ -340,7 +347,7 @@ class UserRepositoryImpl(
|
|||
}
|
||||
}
|
||||
},
|
||||
RxErrorHandler.handleEmptyError()
|
||||
ExceptionHandler.rx()
|
||||
)
|
||||
}
|
||||
var updatePath = "preferences.$type"
|
||||
|
|
@ -375,22 +382,19 @@ class UserRepositoryImpl(
|
|||
return localRepository.getTeamPlans(userID)
|
||||
}
|
||||
|
||||
override fun retrieveTeamPlan(teamID: String): Flowable<Group> {
|
||||
return Flowable.zip(
|
||||
apiClient.getGroup(teamID), apiClient.getTeamPlanTasks(teamID)
|
||||
) { team, tasks ->
|
||||
team.tasks = tasks
|
||||
team
|
||||
override suspend fun retrieveTeamPlan(teamID: String): Group? {
|
||||
val team = apiClient.getGroup(teamID) ?: return null
|
||||
val tasks = apiClient.getTeamPlanTasks(teamID)
|
||||
localRepository.save(team)
|
||||
val id = team.id
|
||||
val tasksOrder = team.tasksOrder
|
||||
if (id.isNotBlank() && tasksOrder != null && tasks != null) {
|
||||
taskRepository.saveTasks(id, tasksOrder, tasks)
|
||||
}
|
||||
.doOnNext { localRepository.save(it) }
|
||||
.doOnNext { team ->
|
||||
val id = team.id
|
||||
val tasksOrder = team.tasksOrder
|
||||
val tasks = team.tasks
|
||||
if (id.isNotBlank() && tasksOrder != null && tasks != null) {
|
||||
taskRepository.saveTasks(id, tasksOrder, tasks)
|
||||
}
|
||||
}
|
||||
val members = apiClient.getGroupMembers(teamID, true) ?: return team
|
||||
localRepository.save(members.map { it.id?.let { member -> GroupMembership(member, id) } }.filterNotNull())
|
||||
members.let { localRepository.save(members) }
|
||||
return team
|
||||
}
|
||||
|
||||
override fun getTeamPlan(teamID: String): Flowable<Group> {
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ interface InventoryLocalRepository : ContentLocalRepository {
|
|||
fun getOwnedPets(userID: String): Flow<List<OwnedPet>>
|
||||
|
||||
fun getInAppRewards(): Flowable<out List<ShopItem>>
|
||||
fun getQuestContent(key: String): Flowable<QuestContent>
|
||||
fun getQuestContent(key: String): Flow<QuestContent?>
|
||||
fun getQuestContent(keys: List<String>): Flow<List<QuestContent>>
|
||||
|
||||
fun getEquipment(searchedKeys: List<String>): Flowable<out List<Equipment>>
|
||||
|
|
|
|||
|
|
@ -7,29 +7,30 @@ import com.habitrpg.android.habitica.models.social.GroupMembership
|
|||
import com.habitrpg.android.habitica.models.social.InboxConversation
|
||||
import com.habitrpg.android.habitica.models.user.User
|
||||
import io.reactivex.rxjava3.core.Flowable
|
||||
import io.realm.RealmResults
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface SocialLocalRepository : BaseLocalRepository {
|
||||
fun getPublicGuilds(): Flowable<out List<Group>>
|
||||
|
||||
fun getUserGroups(userID: String, type: String?): Flowable<out List<Group>>
|
||||
fun getUserGroups(userID: String, type: String?): Flow<List<Group>>
|
||||
fun getGroups(type: String): Flowable<out List<Group>>
|
||||
|
||||
fun getGroup(id: String): Flow<Group?>
|
||||
fun getGroupFlowable(id: String): Flowable<Group>
|
||||
fun saveGroup(group: Group)
|
||||
|
||||
fun getGroupChat(groupId: String): Flowable<out List<ChatMessage>>
|
||||
|
||||
fun deleteMessage(id: String)
|
||||
|
||||
fun getGroupMembers(partyId: String): Flow<List<Member>>
|
||||
fun getPartyMembers(partyId: String): Flow<List<Member>>
|
||||
fun getGroupMembers(groupID: String): Flow<List<Member>>
|
||||
|
||||
fun updateRSVPNeeded(user: User?, newValue: Boolean)
|
||||
|
||||
fun likeMessage(chatMessage: ChatMessage, userId: String, liked: Boolean)
|
||||
|
||||
fun saveGroupMembers(groupId: String?, members: List<Member>)
|
||||
fun savePartyMembers(groupId: String?, members: List<Member>)
|
||||
|
||||
fun removeQuest(partyId: String)
|
||||
|
||||
|
|
@ -39,13 +40,13 @@ interface SocialLocalRepository : BaseLocalRepository {
|
|||
|
||||
fun doesGroupExist(id: String): Boolean
|
||||
fun updateMembership(userId: String, id: String, isMember: Boolean)
|
||||
fun getGroupMembership(userId: String, id: String): Flowable<GroupMembership>
|
||||
fun getGroupMembership(userId: String, id: String): Flow<GroupMembership?>
|
||||
fun getGroupMemberships(userId: String): Flowable<out List<GroupMembership>>
|
||||
fun rejectGroupInvitation(userID: String, groupID: String)
|
||||
|
||||
fun getInboxMessages(userId: String, replyToUserID: String?): Flowable<out List<ChatMessage>>
|
||||
fun getInboxMessages(userId: String, replyToUserID: String?): Flow<RealmResults<ChatMessage>>
|
||||
|
||||
fun getInboxConversation(userId: String): Flowable<out List<InboxConversation>>
|
||||
fun getInboxConversation(userId: String): Flow<RealmResults<InboxConversation>>
|
||||
fun saveGroupMemberships(userID: String?, memberships: List<GroupMembership>)
|
||||
fun saveInboxMessages(
|
||||
userID: String,
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@ interface TaskLocalRepository : BaseLocalRepository {
|
|||
|
||||
fun deleteTask(taskID: String)
|
||||
|
||||
fun getTask(taskId: String): Flowable<Task>
|
||||
fun getTaskCopy(taskId: String): Flowable<Task>
|
||||
fun getTask(taskId: String): Flow<Task>
|
||||
fun getTaskCopy(taskId: String): Flow<Task>
|
||||
|
||||
fun markTaskCompleted(taskId: String, isCompleted: Boolean)
|
||||
|
||||
|
|
@ -33,6 +33,7 @@ interface TaskLocalRepository : BaseLocalRepository {
|
|||
fun updateTaskPositions(taskOrder: List<String>)
|
||||
fun saveCompletedTodos(userId: String, tasks: MutableCollection<Task>)
|
||||
fun getErroredTasks(userID: String): Flowable<out List<Task>>
|
||||
fun getUser(userID: String): Flowable<User>
|
||||
fun getUserFlowable(userID: String): Flowable<User>
|
||||
fun getUser(userID: String): Flow<User>
|
||||
fun getTasksForChallenge(challengeID: String?, userID: String?): Flowable<out List<Task>>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package com.habitrpg.android.habitica.data.local.implementation
|
||||
|
||||
import com.habitrpg.android.habitica.data.local.InventoryLocalRepository
|
||||
import com.habitrpg.android.habitica.helpers.RxErrorHandler
|
||||
import com.habitrpg.android.habitica.helpers.ExceptionHandler
|
||||
import com.habitrpg.android.habitica.models.inventory.Egg
|
||||
import com.habitrpg.android.habitica.models.inventory.Equipment
|
||||
import com.habitrpg.android.habitica.models.inventory.Food
|
||||
|
|
@ -39,14 +39,12 @@ class RealmInventoryLocalRepository(realm: Realm) : RealmContentLocalRepository(
|
|||
.filter { it.isLoaded }
|
||||
}
|
||||
|
||||
override fun getQuestContent(key: String): Flowable<QuestContent> {
|
||||
return RxJavaBridge.toV3Flowable(
|
||||
realm.where(QuestContent::class.java).equalTo("key", key)
|
||||
override fun getQuestContent(key: String): Flow<QuestContent?> {
|
||||
return realm.where(QuestContent::class.java).equalTo("key", key)
|
||||
.findAll()
|
||||
.asFlowable()
|
||||
.toFlow()
|
||||
.filter { content -> content.isLoaded && content.isValid && !content.isEmpty() }
|
||||
.map { content -> content.first() }
|
||||
)
|
||||
}
|
||||
|
||||
override fun getEquipment(searchedKeys: List<String>): Flowable<out List<Equipment>> {
|
||||
|
|
@ -240,7 +238,7 @@ class RealmInventoryLocalRepository(realm: Realm) : RealmContentLocalRepository(
|
|||
}
|
||||
|
||||
override fun changeOwnedCount(type: String, key: String, userID: String, amountToAdd: Int) {
|
||||
getOwnedItem(userID, type, key, true).firstElement().subscribe({ changeOwnedCount(it, amountToAdd) }, RxErrorHandler.handleEmptyError())
|
||||
getOwnedItem(userID, type, key, true).firstElement().subscribe({ changeOwnedCount(it, amountToAdd) }, ExceptionHandler.rx())
|
||||
}
|
||||
|
||||
override fun changeOwnedCount(item: OwnedItem, amountToAdd: Int?) {
|
||||
|
|
|
|||
|
|
@ -14,21 +14,21 @@ import io.reactivex.rxjava3.core.Flowable
|
|||
import io.realm.Realm
|
||||
import io.realm.Sort
|
||||
import io.realm.kotlin.toFlow
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
class RealmSocialLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm), SocialLocalRepository {
|
||||
|
||||
override fun getGroupMembership(userId: String, id: String): Flowable<GroupMembership> = RxJavaBridge.toV3Flowable(
|
||||
realm.where(GroupMembership::class.java)
|
||||
override fun getGroupMembership(userId: String, id: String) = realm.where(GroupMembership::class.java)
|
||||
.equalTo("userID", userId)
|
||||
.equalTo("groupID", id)
|
||||
.findAll()
|
||||
.asFlowable()
|
||||
.toFlow()
|
||||
.filter { it.isLoaded && it.isNotEmpty() }
|
||||
.map { it.first() }
|
||||
)
|
||||
|
||||
override fun getGroupMemberships(userId: String): Flowable<out List<GroupMembership>> = RxJavaBridge.toV3Flowable(
|
||||
realm.where(GroupMembership::class.java)
|
||||
|
|
@ -134,29 +134,25 @@ class RealmSocialLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm)
|
|||
.filter { it.isLoaded }
|
||||
)
|
||||
|
||||
override fun getUserGroups(userID: String, type: String?): Flowable<out List<Group>> = RxJavaBridge.toV3Flowable(
|
||||
realm.where(GroupMembership::class.java)
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
override fun getUserGroups(userID: String, type: String?) = realm.where(GroupMembership::class.java)
|
||||
.equalTo("userID", userID)
|
||||
.findAll()
|
||||
.asFlowable()
|
||||
.filter { it.isLoaded }
|
||||
)
|
||||
.flatMap { memberships ->
|
||||
RxJavaBridge.toV3Flowable(
|
||||
realm.where(Group::class.java)
|
||||
.equalTo("type", type ?: "guild")
|
||||
.notEqualTo("id", Group.TAVERN_ID)
|
||||
.`in`(
|
||||
"id",
|
||||
memberships.map {
|
||||
return@map it.groupID
|
||||
}.toTypedArray()
|
||||
)
|
||||
.sort("memberCount", Sort.DESCENDING)
|
||||
.findAll()
|
||||
.asFlowable()
|
||||
.filter { it.isLoaded }
|
||||
)
|
||||
.toFlow()
|
||||
.filter { it.isLoaded }
|
||||
.flatMapLatest { memberships ->
|
||||
realm.where(Group::class.java)
|
||||
.equalTo("type", type ?: "guild")
|
||||
.notEqualTo("id", Group.TAVERN_ID)
|
||||
.`in`(
|
||||
"id",
|
||||
memberships.map {
|
||||
return@map it.groupID
|
||||
}.toTypedArray()
|
||||
)
|
||||
.sort("memberCount", Sort.DESCENDING)
|
||||
.findAll()
|
||||
.toFlow()
|
||||
}
|
||||
|
||||
override fun getGroups(type: String): Flowable<out List<Group>> {
|
||||
|
|
@ -169,17 +165,6 @@ class RealmSocialLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm)
|
|||
)
|
||||
}
|
||||
|
||||
override fun getGroupFlowable(id: String): Flowable<Group> {
|
||||
return RxJavaBridge.toV3Flowable(
|
||||
realm.where(Group::class.java)
|
||||
.equalTo("id", id)
|
||||
.findAll()
|
||||
.asFlowable()
|
||||
.filter { group -> group.isLoaded && group.isValid && !group.isEmpty() }
|
||||
.map { groups -> groups.first() }
|
||||
)
|
||||
}
|
||||
|
||||
override fun getGroup(id: String): Flow<Group?> {
|
||||
return realm.where(Group::class.java)
|
||||
.equalTo("id", id)
|
||||
|
|
@ -205,13 +190,17 @@ class RealmSocialLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm)
|
|||
executeTransaction { chatMessage?.deleteFromRealm() }
|
||||
}
|
||||
|
||||
override fun getGroupMembers(partyId: String): Flow<List<Member>> {
|
||||
return realm.where(Member::class.java)
|
||||
override fun getPartyMembers(partyId: String) = realm.where(Member::class.java)
|
||||
.equalTo("party.id", partyId)
|
||||
.findAll()
|
||||
.toFlow()
|
||||
.filter { it.isLoaded }
|
||||
}
|
||||
.toFlow()
|
||||
|
||||
override fun getGroupMembers(groupID: String) = realm.where(GroupMembership::class.java)
|
||||
.equalTo("groupID", groupID)
|
||||
.findAll()
|
||||
.toFlow()
|
||||
.map { memberships -> memberships.map { it.userID }.toTypedArray() }
|
||||
.flatMapLatest { realm.where(Member::class.java).`in`("id", it).findAll().toFlow() }
|
||||
|
||||
override fun updateRSVPNeeded(user: User?, newValue: Boolean) {
|
||||
executeTransaction { user?.party?.quest?.RSVPNeeded = newValue }
|
||||
|
|
@ -239,7 +228,7 @@ class RealmSocialLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm)
|
|||
}
|
||||
}
|
||||
|
||||
override fun saveGroupMembers(groupId: String?, members: List<Member>) {
|
||||
override fun savePartyMembers(groupId: String?, members: List<Member>) {
|
||||
saveSyncronous(members)
|
||||
if (groupId != null) {
|
||||
val existingMembers = realm.where(Member::class.java).equalTo("party.id", groupId).findAll()
|
||||
|
|
@ -302,27 +291,19 @@ class RealmSocialLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm)
|
|||
return party != null && party.isValid
|
||||
}
|
||||
|
||||
override fun getInboxMessages(userId: String, replyToUserID: String?): Flowable<out List<ChatMessage>> {
|
||||
return RxJavaBridge.toV3Flowable(
|
||||
realm.where(ChatMessage::class.java)
|
||||
override fun getInboxMessages(userId: String, replyToUserID: String?) = realm.where(ChatMessage::class.java)
|
||||
.equalTo("isInboxMessage", true)
|
||||
.equalTo("uuid", replyToUserID)
|
||||
.equalTo("userID", userId)
|
||||
.sort("timestamp", Sort.DESCENDING)
|
||||
.findAll()
|
||||
.asFlowable()
|
||||
.toFlow()
|
||||
.filter { it.isLoaded }
|
||||
)
|
||||
}
|
||||
|
||||
override fun getInboxConversation(userId: String): Flowable<out List<InboxConversation>> {
|
||||
return RxJavaBridge.toV3Flowable(
|
||||
realm.where(InboxConversation::class.java)
|
||||
override fun getInboxConversation(userId: String) = realm.where(InboxConversation::class.java)
|
||||
.equalTo("userID", userId)
|
||||
.sort("timestamp", Sort.DESCENDING)
|
||||
.findAll()
|
||||
.asFlowable()
|
||||
.toFlow()
|
||||
.filter { it.isLoaded }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,42 +12,55 @@ import hu.akarnokd.rxjava3.bridge.RxJavaBridge
|
|||
import io.reactivex.rxjava3.core.Flowable
|
||||
import io.reactivex.rxjava3.core.Maybe
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmResults
|
||||
import io.realm.Sort
|
||||
import io.realm.kotlin.toFlow
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
class RealmTaskLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm), TaskLocalRepository {
|
||||
|
||||
override fun getTasks(taskType: TaskType, userID: String, includedGroupIDs: Array<String>): Flow<List<Task>> {
|
||||
if (realm.isClosed) return emptyFlow()
|
||||
return realm.where(Task::class.java)
|
||||
.equalTo("typeValue", taskType.value)
|
||||
.beginGroup()
|
||||
.equalTo("userId", userID)
|
||||
.or()
|
||||
.`in`("group.groupID", includedGroupIDs)
|
||||
.endGroup()
|
||||
.sort("position", Sort.ASCENDING, "dateCreated", Sort.DESCENDING)
|
||||
.findAll()
|
||||
return findTasks(taskType, userID, includedGroupIDs)
|
||||
.toFlow()
|
||||
.filter { it.isLoaded }
|
||||
}
|
||||
|
||||
override fun getTasksFlowable(taskType: TaskType, userID: String, includedGroupIDs: Array<String>): Flowable<out List<Task>> {
|
||||
if (realm.isClosed) return Flowable.empty()
|
||||
return RxJavaBridge.toV3Flowable(realm.where(Task::class.java)
|
||||
return RxJavaBridge.toV3Flowable(findTasks(taskType, userID, includedGroupIDs)
|
||||
.asFlowable()
|
||||
.filter { it.isLoaded })
|
||||
}
|
||||
|
||||
private fun findTasks(
|
||||
taskType: TaskType,
|
||||
ownerID: String,
|
||||
includedGroupIDs: Array<String>
|
||||
): RealmResults<Task> {
|
||||
return realm.where(Task::class.java)
|
||||
.equalTo("typeValue", taskType.value)
|
||||
.beginGroup()
|
||||
.equalTo("userId", userID)
|
||||
.equalTo("userId", ownerID)
|
||||
.or()
|
||||
.beginGroup()
|
||||
.`in`("group.groupID", includedGroupIDs)
|
||||
.and()
|
||||
.beginGroup()
|
||||
.contains("group.assignedUsers", ownerID)
|
||||
.or()
|
||||
.isEmpty("group.assignedUsers")
|
||||
.endGroup()
|
||||
.endGroup()
|
||||
.or()
|
||||
.equalTo("group.groupID", ownerID)
|
||||
.endGroup()
|
||||
.sort("position", Sort.ASCENDING, "dateCreated", Sort.DESCENDING)
|
||||
.findAll()
|
||||
.asFlowable()
|
||||
.filter { it.isLoaded })
|
||||
}
|
||||
|
||||
override fun getTasks(userId: String): Flow<List<Task>> {
|
||||
|
|
@ -126,14 +139,13 @@ class RealmTaskLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm),
|
|||
return taskList
|
||||
}
|
||||
|
||||
private fun removeOldTasks(userID: String, onlineTaskList: List<Task>) {
|
||||
val groupIDs = onlineTaskList.map { it.group?.groupID }.distinct().toTypedArray()
|
||||
private fun removeOldTasks(ownerID: String, onlineTaskList: List<Task>) {
|
||||
if (realm.isClosed) return
|
||||
val localTasks = realm.where(Task::class.java)
|
||||
.beginGroup()
|
||||
.equalTo("userId", userID)
|
||||
.equalTo("userId", ownerID)
|
||||
.or()
|
||||
.`in`("group.groupID", groupIDs)
|
||||
.equalTo("group.groupID", ownerID)
|
||||
.endGroup()
|
||||
.beginGroup()
|
||||
.beginGroup()
|
||||
|
|
@ -177,19 +189,17 @@ class RealmTaskLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm),
|
|||
}
|
||||
}
|
||||
|
||||
override fun getTask(taskId: String): Flowable<Task> {
|
||||
override fun getTask(taskId: String): Flow<Task> {
|
||||
if (realm.isClosed) {
|
||||
return Flowable.empty()
|
||||
return emptyFlow()
|
||||
}
|
||||
return RxJavaBridge.toV3Flowable(
|
||||
realm.where(Task::class.java).equalTo("id", taskId).findAll().asFlowable()
|
||||
.filter { realmObject -> realmObject.isLoaded && realmObject.isNotEmpty() }
|
||||
.map { it.first() }
|
||||
.cast(Task::class.java)
|
||||
)
|
||||
return realm.where(Task::class.java).equalTo("id", taskId).findAll().toFlow()
|
||||
.filter { realmObject -> realmObject.isLoaded && realmObject.isNotEmpty() }
|
||||
.map { it.first() }
|
||||
.filterNotNull()
|
||||
}
|
||||
|
||||
override fun getTaskCopy(taskId: String): Flowable<Task> {
|
||||
override fun getTaskCopy(taskId: String): Flow<Task> {
|
||||
return getTask(taskId)
|
||||
.map { task ->
|
||||
return@map if (task.isManaged && task.isValid) {
|
||||
|
|
@ -260,7 +270,7 @@ class RealmTaskLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm),
|
|||
).retry(1)
|
||||
}
|
||||
|
||||
override fun getUser(userID: String): Flowable<User> {
|
||||
override fun getUserFlowable(userID: String): Flowable<User> {
|
||||
return RxJavaBridge.toV3Flowable(
|
||||
realm.where(User::class.java)
|
||||
.equalTo("id", userID)
|
||||
|
|
@ -271,6 +281,16 @@ class RealmTaskLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm),
|
|||
)
|
||||
}
|
||||
|
||||
override fun getUser(userID: String): Flow<User> {
|
||||
return realm.where(User::class.java)
|
||||
.equalTo("id", userID)
|
||||
.findAll()
|
||||
.toFlow()
|
||||
.filter { realmObject -> realmObject.isLoaded && realmObject.isValid && !realmObject.isEmpty() }
|
||||
.map { users -> users.first() }
|
||||
.filterNotNull()
|
||||
}
|
||||
|
||||
override fun getTasksForChallenge(challengeID: String?, userID: String?): Flowable<out List<Task>> {
|
||||
return RxJavaBridge.toV3Flowable(
|
||||
realm.where(Task::class.java)
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
package com.habitrpg.android.habitica.extensions
|
||||
|
||||
import com.habitrpg.android.habitica.helpers.RxErrorHandler
|
||||
import com.habitrpg.android.habitica.helpers.ExceptionHandler
|
||||
import io.reactivex.rxjava3.core.Flowable
|
||||
import io.reactivex.rxjava3.disposables.Disposable
|
||||
import io.reactivex.rxjava3.functions.Consumer
|
||||
|
||||
fun <T : Any> Flowable<T>.subscribeWithErrorHandler(function: Consumer<T>): Disposable {
|
||||
return subscribe(function, RxErrorHandler.handleEmptyError())
|
||||
return subscribe(function, ExceptionHandler.rx())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,12 +18,16 @@ class AppConfigManager(contentRepository: ContentRepository?): com.habitrpg.comm
|
|||
private var worldState: WorldState? = null
|
||||
|
||||
init {
|
||||
contentRepository?.getWorldState()?.subscribe(
|
||||
{
|
||||
worldState = it
|
||||
},
|
||||
RxErrorHandler.handleEmptyError()
|
||||
)
|
||||
try {
|
||||
contentRepository?.getWorldState()?.subscribe(
|
||||
{
|
||||
worldState = it
|
||||
},
|
||||
ExceptionHandler.rx()
|
||||
)
|
||||
} catch (_: java.lang.IllegalStateException) {
|
||||
// pass
|
||||
}
|
||||
}
|
||||
|
||||
private val remoteConfig = FirebaseRemoteConfig.getInstance()
|
||||
|
|
@ -137,9 +141,7 @@ class AppConfigManager(contentRepository: ContentRepository?): com.habitrpg.comm
|
|||
}
|
||||
|
||||
fun enableTeamBoards(): Boolean {
|
||||
if (BuildConfig.DEBUG) {
|
||||
return true
|
||||
}
|
||||
return true
|
||||
return remoteConfig.getBoolean("enableTeamBoards")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,24 +4,31 @@ import android.util.Log
|
|||
import com.habitrpg.android.habitica.BuildConfig
|
||||
import com.habitrpg.android.habitica.proxy.AnalyticsManager
|
||||
import io.reactivex.rxjava3.functions.Consumer
|
||||
import java.io.EOFException
|
||||
import java.io.IOException
|
||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||
import okhttp3.internal.http2.ConnectionShutdownException
|
||||
import retrofit2.HttpException
|
||||
import java.io.EOFException
|
||||
import java.io.IOException
|
||||
|
||||
class RxErrorHandler {
|
||||
class ExceptionHandler {
|
||||
private var analyticsManager: AnalyticsManager? = null
|
||||
|
||||
companion object {
|
||||
|
||||
private var instance: RxErrorHandler? = null
|
||||
private var instance = ExceptionHandler()
|
||||
|
||||
fun init(analyticsManager: AnalyticsManager) {
|
||||
instance = RxErrorHandler()
|
||||
instance?.analyticsManager = analyticsManager
|
||||
instance.analyticsManager = analyticsManager
|
||||
}
|
||||
|
||||
fun handleEmptyError(): Consumer<Throwable> {
|
||||
fun coroutine(handler: ((Throwable) -> Unit)? = null): CoroutineExceptionHandler {
|
||||
return CoroutineExceptionHandler { _, throwable ->
|
||||
reportError(throwable)
|
||||
handler?.invoke(throwable)
|
||||
}
|
||||
}
|
||||
|
||||
fun rx(): Consumer<Throwable> {
|
||||
// Can't be turned into a lambda, because it then doesn't work for some reason.
|
||||
return Consumer { reportError(it) }
|
||||
}
|
||||
|
|
@ -40,7 +47,7 @@ class RxErrorHandler {
|
|||
!retrofit2.adapter.rxjava3.HttpException::class.java.isAssignableFrom(throwable.javaClass) &&
|
||||
throwable !is ConnectionShutdownException
|
||||
) {
|
||||
instance?.analyticsManager?.logException(throwable)
|
||||
instance.analyticsManager?.logException(throwable)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.habitrpg.android.habitica.helpers
|
||||
|
||||
import android.content.res.Resources
|
||||
import com.habitrpg.android.habitica.models.tasks.Task
|
||||
|
||||
interface GroupPlanInfoProvider {
|
||||
fun assignedTextForTask(resources: Resources, assignedUsers: List<String>): String
|
||||
fun canScoreTask(task: Task): Boolean
|
||||
fun canEditTask(task: Task): Boolean
|
||||
}
|
||||
|
|
@ -120,6 +120,6 @@ class MainNotificationsManager: NotificationsManager {
|
|||
|
||||
private fun readNotification(notification: Notification) {
|
||||
apiClient?.get()?.readNotification(notification.id)
|
||||
?.subscribe({ }, RxErrorHandler.handleEmptyError())
|
||||
?.subscribe({ }, ExceptionHandler.rx())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,26 +25,25 @@ import com.habitrpg.android.habitica.HabiticaBaseApplication
|
|||
import com.habitrpg.android.habitica.R
|
||||
import com.habitrpg.android.habitica.data.ApiClient
|
||||
import com.habitrpg.android.habitica.extensions.addOkButton
|
||||
import com.habitrpg.android.habitica.extensions.subscribeWithErrorHandler
|
||||
import com.habitrpg.common.habitica.models.IAPGift
|
||||
import com.habitrpg.common.habitica.models.PurchaseValidationRequest
|
||||
import com.habitrpg.common.habitica.models.Transaction
|
||||
import com.habitrpg.android.habitica.models.user.User
|
||||
import com.habitrpg.android.habitica.proxy.AnalyticsManager
|
||||
import com.habitrpg.android.habitica.ui.activities.PurchaseActivity
|
||||
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
|
||||
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
|
||||
import io.reactivex.rxjava3.core.Flowable
|
||||
import java.util.Date
|
||||
import kotlin.time.DurationUnit
|
||||
import kotlin.time.toDuration
|
||||
import com.habitrpg.common.habitica.models.IAPGift
|
||||
import com.habitrpg.common.habitica.models.PurchaseValidationRequest
|
||||
import com.habitrpg.common.habitica.models.Transaction
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.MainScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.json.JSONObject
|
||||
import retrofit2.HttpException
|
||||
import java.util.Date
|
||||
import kotlin.time.DurationUnit
|
||||
import kotlin.time.toDuration
|
||||
|
||||
class PurchaseHandler(
|
||||
private val context: Context,
|
||||
|
|
@ -88,7 +87,7 @@ class PurchaseHandler(
|
|||
return
|
||||
}
|
||||
BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED -> {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
CoroutineScope(Dispatchers.IO).launch(ExceptionHandler.coroutine()) {
|
||||
for (purchase in purchases) {
|
||||
consume(purchase)
|
||||
}
|
||||
|
|
@ -186,7 +185,8 @@ class PurchaseHandler(
|
|||
return skuDetailsResult.skuDetailsList
|
||||
}
|
||||
|
||||
fun purchase(activity: Activity, skuDetails: SkuDetails, recipient: String? = null) {
|
||||
fun purchase(activity: Activity, skuDetails: SkuDetails, recipient: String? = null, isSaleGemPurchase: Boolean = false) {
|
||||
this.isSaleGemPurchase = isSaleGemPurchase
|
||||
recipient?.let {
|
||||
addGift(skuDetails.sku, it)
|
||||
}
|
||||
|
|
@ -219,7 +219,7 @@ class PurchaseHandler(
|
|||
apiClient.validatePurchase(validationRequest).subscribe({
|
||||
processedPurchase(purchase)
|
||||
val gift = removeGift(sku)
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
CoroutineScope(Dispatchers.IO).launch(ExceptionHandler.coroutine()) {
|
||||
consume(purchase)
|
||||
}
|
||||
displayConfirmationDialog(purchase, gift?.second)
|
||||
|
|
@ -232,7 +232,7 @@ class PurchaseHandler(
|
|||
apiClient.validateNoRenewSubscription(validationRequest).subscribe({
|
||||
processedPurchase(purchase)
|
||||
val gift = removeGift(sku)
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
CoroutineScope(Dispatchers.IO).launch(ExceptionHandler.coroutine()) {
|
||||
consume(purchase)
|
||||
}
|
||||
displayConfirmationDialog(purchase, gift?.second)
|
||||
|
|
@ -245,7 +245,7 @@ class PurchaseHandler(
|
|||
apiClient.validateSubscription(validationRequest).subscribe({
|
||||
processedPurchase(purchase)
|
||||
analyticsManager.logEvent("user_subscribed", bundleOf(Pair("sku", sku)))
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
CoroutineScope(Dispatchers.IO).launch(ExceptionHandler.coroutine()) {
|
||||
acknowledgePurchase(purchase)
|
||||
}
|
||||
displayConfirmationDialog(purchase)
|
||||
|
|
@ -268,7 +268,9 @@ class PurchaseHandler(
|
|||
}
|
||||
|
||||
private fun processedPurchase(purchase: Purchase) {
|
||||
userViewModel.userRepository.retrieveUser(false, true).subscribeWithErrorHandler {}
|
||||
MainScope().launch(ExceptionHandler.coroutine()) {
|
||||
userViewModel.userRepository.retrieveUser(false, true)
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildValidationRequest(purchase: Purchase): PurchaseValidationRequest {
|
||||
|
|
@ -296,7 +298,7 @@ class PurchaseHandler(
|
|||
if (res.message != null && res.message == "RECEIPT_ALREADY_USED") {
|
||||
processedPurchase(purchase)
|
||||
removeGift(purchase.skus.firstOrNull())
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
CoroutineScope(Dispatchers.IO).launch(ExceptionHandler.coroutine()) {
|
||||
consume(purchase)
|
||||
}
|
||||
return
|
||||
|
|
@ -331,9 +333,9 @@ class PurchaseHandler(
|
|||
return fallback
|
||||
}
|
||||
|
||||
fun cancelSubscription(): Flowable<User> {
|
||||
return apiClient.cancelSubscription()
|
||||
.flatMap { userViewModel.userRepository.retrieveUser(false, true) }
|
||||
suspend fun cancelSubscription(): User? {
|
||||
apiClient.cancelSubscription()
|
||||
return userViewModel.userRepository.retrieveUser(false, true)
|
||||
}
|
||||
|
||||
private fun durationString(sku: String): String {
|
||||
|
|
@ -346,18 +348,31 @@ class PurchaseHandler(
|
|||
}
|
||||
}
|
||||
|
||||
private var isSaleGemPurchase = false
|
||||
|
||||
private fun gemAmountString(sku: String): String {
|
||||
return when (sku) {
|
||||
PurchaseTypes.Purchase4Gems -> "4"
|
||||
PurchaseTypes.Purchase21Gems -> "21"
|
||||
PurchaseTypes.Purchase42Gems -> "42"
|
||||
PurchaseTypes.Purchase84Gems -> "84"
|
||||
else -> ""
|
||||
if (isSaleGemPurchase) {
|
||||
isSaleGemPurchase = false
|
||||
return when (sku) {
|
||||
PurchaseTypes.Purchase4Gems -> "5"
|
||||
PurchaseTypes.Purchase21Gems -> "30"
|
||||
PurchaseTypes.Purchase42Gems -> "60"
|
||||
PurchaseTypes.Purchase84Gems -> "125"
|
||||
else -> ""
|
||||
}
|
||||
} else {
|
||||
return when (sku) {
|
||||
PurchaseTypes.Purchase4Gems -> "4"
|
||||
PurchaseTypes.Purchase21Gems -> "21"
|
||||
PurchaseTypes.Purchase42Gems -> "42"
|
||||
PurchaseTypes.Purchase84Gems -> "84"
|
||||
else -> ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun displayConfirmationDialog(purchase: Purchase, giftedTo: String? = null) {
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
CoroutineScope(Dispatchers.Main).launch(ExceptionHandler.coroutine()) {
|
||||
val application = (context as? HabiticaBaseApplication)
|
||||
?: (context.applicationContext as? HabiticaBaseApplication) ?: return@launch
|
||||
val sku = purchase.skus.firstOrNull() ?: return@launch
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ class SoundFile(val theme: String, private val fileName: String) {
|
|||
player?.setDataSource(file?.path)
|
||||
val attributes = AudioAttributes.Builder()
|
||||
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
|
||||
.setLegacyStreamType(AudioManager.STREAM_NOTIFICATION)
|
||||
.setLegacyStreamType(AudioManager.STREAM_MUSIC)
|
||||
.build()
|
||||
player?.setAudioAttributes(attributes)
|
||||
player?.prepare()
|
||||
|
|
@ -48,7 +48,7 @@ class SoundFile(val theme: String, private val fileName: String) {
|
|||
player?.start()
|
||||
} catch (e: IllegalStateException) {
|
||||
} catch (e: Exception) {
|
||||
RxErrorHandler.reportError(e)
|
||||
ExceptionHandler.reportError(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ class SoundManager {
|
|||
soundFiles.add(SoundFile(soundTheme, SoundReward))
|
||||
soundFiles.add(SoundFile(soundTheme, SoundTodo))
|
||||
soundFileLoader.download(soundFiles)
|
||||
.subscribe({}, RxErrorHandler.handleEmptyError())
|
||||
.subscribe({}, ExceptionHandler.rx())
|
||||
}
|
||||
|
||||
fun loadAndPlayAudio(type: String) {
|
||||
|
|
@ -54,7 +54,7 @@ class SoundManager {
|
|||
loadedSoundFiles[type] = file
|
||||
file.play()
|
||||
},
|
||||
RxErrorHandler.handleEmptyError()
|
||||
ExceptionHandler.rx()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,11 @@ import com.habitrpg.android.habitica.receivers.TaskReceiver
|
|||
import com.habitrpg.shared.habitica.HLogger
|
||||
import com.habitrpg.shared.habitica.LogLevel
|
||||
import com.habitrpg.shared.habitica.models.tasks.TaskType
|
||||
import kotlinx.coroutines.MainScope
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.launch
|
||||
import java.time.ZoneId
|
||||
import java.time.ZonedDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
|
@ -55,10 +59,12 @@ class TaskAlarmManager(
|
|||
// We currently only use this function to schedule the next reminder for dailies
|
||||
// We may be able to use repeating alarms instead of this in the future
|
||||
fun addAlarmForTaskId(taskId: String) {
|
||||
taskRepository.getTaskCopy(taskId)
|
||||
.filter { task -> task.isValid && task.isManaged && TaskType.DAILY == task.type }
|
||||
.firstElement()
|
||||
.subscribe({ this.setAlarmsForTask(it) }, RxErrorHandler.handleEmptyError())
|
||||
MainScope().launch(ExceptionHandler.coroutine()) {
|
||||
val task = taskRepository.getTaskCopy(taskId)
|
||||
.filter { task -> task.isValid && task.isManaged && TaskType.DAILY == task.type }
|
||||
.first()
|
||||
setAlarmsForTask(task)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun scheduleAllSavedAlarms(preventDailyReminder: Boolean) {
|
||||
|
|
@ -216,7 +222,11 @@ class TaskAlarmManager(
|
|||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
||||
alarmManager?.setWindow(AlarmManager.RTC_WAKEUP, time, 60000, pendingIntent)
|
||||
} else {
|
||||
alarmManager?.setAlarmClock(AlarmClockInfo(time, pendingIntent), pendingIntent)
|
||||
try {
|
||||
alarmManager?.setAlarmClock(AlarmClockInfo(time, pendingIntent), pendingIntent)
|
||||
} catch (e: SecurityException) {
|
||||
alarmManager?.setWindow(AlarmManager.RTC_WAKEUP, time, 60000, pendingIntent)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||